-
Notifications
You must be signed in to change notification settings - Fork 480
Description
Problem Statement
When requesting a Custom Field via the API with newRenderMode set to iframe, the content is rendering incorrectly, leading to UI artifacts and functionality breakage.
Based on DOM inspection, the backend appears to be rendering the content twice:
Once as correct HTML.
Once as a raw, escaped JSON string (e.g., <div id=""displayURLTitle""...) injected directly into the document body.
This malformed injection causes an Uncaught SyntaxError: Invalid or unexpected token in the browser console, preventing the custom field scripts from executing correctly.
This issue is specifically observed when Edit Mode is enabled, with newRenderMode: iframe
Steps to Reproduce
- Go to Pages Content type
- Enable the new edit mode
- Go to template custom field
- Choose deprecated API aka iframes
- Put this code.
<script type="application/javascript">
// Global Constants
const hostId = "$request.getSession().getAttribute('CMS_SELECTED_HOST_ID')";
const defaultTemplateName = '$config.getStringProperty("DEFAULT_TEMPLATE_NAME", "System Template")'; // try to preload the default template.
dojo.require("dotcms.dojo.data.TemplateReadStore");
// UTILS
/**
* Normalizes the key of a template
*
*/
const normalizeKey = function (template) {
return template.fullTitle.replace(new RegExp("\\("+template.hostName+"\\)"), '').replace(/\s+/g,'').toLowerCase();
}
/**
* Returns a map of templates by name and by id
*
*/
const getTemplatesMaps = (templates) => {
const mapByName = templates.reduce(function(map, template) {
const key = normalizeKey(template);
if (!map[key]) {
map[key] = template;
}
return map;
}, {});
const mapById = templates.reduce(function(map, template) {
map[template.identifier] = template;
return map;
}, {});
return {
mapByName,
mapById
}
}
// UTILS END
/**
* Fetches the template image from the server
*
*/
function fetchTemplateImage(templateId) {
fetch("/api/v1/templates/image", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ templateId})
}).then(async (response) => {
// The ok value represents the result of the response status 200 codes
if (response.ok) {
const result = await response.json();
getTemplateCallBack(result); // here we pass the result of the json response to the callback function
} else {
throw new Error("Error fetching template image");
}
})
.catch((error) => {
const imageEl = dojo.byId("templateThumbnailHolder");
imageEl.src = "/html/images/shim.gif";
imageEl.style.border = "0px";
});
};
/**
* Callback function to handle the template fetch
*
*/
const onTemplateFetchComplete = function(templates, currentRequest) {
const templateId = dojo.byId("template").value;
const isTemplateValid = templateId && templateId != "0";
if(!templates || templates.length === 0){
return;
}
const { mapById, mapByName } = getTemplatesMaps(templates);
const normalizedName = defaultTemplateName.replace(/\s+/g,'').toLowerCase();
const template = isTemplateValid ? mapById[templateId]:mapByName[normalizedName];
if(!template){
return;
}
const { identifier, fullTitle } = template;
// We set the values directly into the components because setting it directly into`templateSelect` fires another load operation.
dojo.byId("currentTemplateId").value = identifier;
dojo.byId("template").value = identifier;
dijit.byId('templateSel').set("displayedValue", fullTitle);
fetchTemplateImage(identifier);
};
function resetTemplateSelection() {
const templateSel = dijit.byId("templateSel");
templateSel.set("value","");
templateSel.filter();
dojo.byId("template").value= '';
dojo.byId("templateSel").value = "";
dojo.byId("currentTemplateId").value = "";
dojo.byId("templateThumbnailHolder").src = "/html/images/shim.gif";
dojo.byId("templateThumbnailHolder").style.border = '0px';
}
/**
* Get the template callback
*
*/
function getTemplateCallBack(data) {
const imageInode = data.identifier;
const imageEl=dojo.byId("templateThumbnailHolder");
if (isInodeSet(imageInode)) {
imageEl.src = "/dA/" + imageInode + "/250w";
imageEl.style.border = '1px solid #B6CBEB';
imageEl.style.marginTop = '1rem';
} else {
imageEl.src = "/html/images/shim.gif";
imageEl.style.border = '0px';
}
}
dojo.ready(function(){
const templateId = dojo.byId("template").value;
const isTemplateValid = templateId && templateId != "0";
const currentTemplateIdElement = dojo.byId("currentTemplateId");
currentTemplateIdElement.value = templateId;
const templateStore = new dotcms.dojo.data.TemplateReadStore({
hostId: '',
templateSelected: templateId,
allSiteLabel: true,
});
const templateSelect = new dijit.form.FilteringSelect({
id:"templateSel",
name:"templateSel",
style:"width:350px;",
onChange: templateChanged,
store: templateStore,
searchDelay: 300,
pageSize: 15,
autoComplete: false,
ignoreCase: true,
labelType:"html",
searchType:"html",
labelAttr: "htmlTitle",
searchAttr: "fullTitle",
value: templateId,
invalidMessage: '$text.get("Invalid-option-selected")',
},"templateHolder");
if (isTemplateValid){
fetchTemplateImage(templateId);
const templateSel = dijit.byId("templateSel");
templateSel.set("value", templateId);
}
const templateFetchParams = {
query: {
fullTitle: '*'
},
queryOptions: {},
start: 0,
count: 15,
onComplete: onTemplateFetchComplete
};
function handleAllSiteClick() {
templateStore.hostId = "*";
templateStore.allSiteLabel=false;
resetTemplateSelection();
}
/**
* Handles the template change event
*
*/
function templateChanged() {
const templateSel = dijit.byId("templateSel");
const value = templateSel?.get('value');
if(!value) {
resetTemplateSelection();
return;
}
if(value == "0") {
handleAllSiteClick();
return;
}
dojo.byId("template").value=value;
fetchTemplateImage(value);
}
templateStore.fetch(templateFetchParams);
});
</script>
<div id="templateHolder"></div>
<input id="currentTemplateId" type="hidden" name="currentTemplateId" value=""/>
<div>
<img id="templateThumbnailHolder" src="/html/images/shim.gif" alt="Template Thumbnail"/>
</div>Acceptance Criteria
The Iframe should return valid, clean HTML only once. It should not contain escaped string representations of the DOM elements or double-rendered content.
dotCMS Version
latest
Severity
Critical - System unusable