Skip to content

Commit

Permalink
fix(core): Adding support for listing templates across multiple sites (
Browse files Browse the repository at this point in the history
…#31086)

### Proposed Changes
* Wildcard host support was included in the endpoint that lists
templates to be able to get all the templates across multiple sites

### Checklist
- [x] Tests

This pull request includes several changes to improve the handling of
template selection and filtering in both the backend and frontend of the
dotCMS application. The most important changes include updates to the
`TemplateResource` class, improvements to the template selection logic
in the frontend, and the addition of a new test case.

### Backend changes:
*
[`dotCMS/src/main/java/com/dotcms/rest/api/v1/template/TemplateResource.java`](diffhunk://#diff-448b3630547f743e0d556752c2e68b797b03e9c4b9d7e238c84889274435635eL139-R156):
Refactored the method to handle template listing by incorporating logic
to support wildcard host IDs, which allows fetching templates across
multiple sites.

### Frontend changes:
*
[`dotCMS/src/main/webapp/WEB-INF/velocity/static/htmlpage_assets/template_custom_field.vtl`](diffhunk://#diff-27bf5cec90b9630400c776d8f3fd37151da487ffbebbef2a630946b559bf0dd6L96-R122):
Added a new function `resetTemplateSelection` to reset the template
selection, and modified the template fetch logic to handle the wildcard
host ID.
[[1]](diffhunk://#diff-27bf5cec90b9630400c776d8f3fd37151da487ffbebbef2a630946b559bf0dd6L96-R122)
[[2]](diffhunk://#diff-27bf5cec90b9630400c776d8f3fd37151da487ffbebbef2a630946b559bf0dd6L154-R158)
[[3]](diffhunk://#diff-27bf5cec90b9630400c776d8f3fd37151da487ffbebbef2a630946b559bf0dd6L173-R177)
[[4]](diffhunk://#diff-27bf5cec90b9630400c776d8f3fd37151da487ffbebbef2a630946b559bf0dd6L185-R189)
*
[`dotCMS/src/main/webapp/html/js/dotcms/dojo/data/TemplateReadStore.js`](diffhunk://#diff-5453f9a344625845fd35818fe8b7853f0b5ef2428b53b0e775a6f5cd13bf7c22R14-L17):
Added a new constant `ALL_SITE_TEMPLATE` to emulate the old backend
behavior and updated the fetch logic to include this template when the
wildcard host ID is used.
[[1]](diffhunk://#diff-5453f9a344625845fd35818fe8b7853f0b5ef2428b53b0e775a6f5cd13bf7c22R14-L17)
[[2]](diffhunk://#diff-5453f9a344625845fd35818fe8b7853f0b5ef2428b53b0e775a6f5cd13bf7c22R111-R130)

### Test changes:
*
[`dotcms-integration/src/test/java/com/dotcms/rest/api/v1/template/TemplateResourceTest.java`](diffhunk://#diff-745f7eccb02b77ba5b0bc21bba208f1fa5113e2e1b93261173f2091a579936b5R1072-R1114):
Added a new test case `test_listTemplate_filterByHost_usingWildcardHost`
to verify the correct behavior when listing templates with a wildcard
host ID.

### Video


https://github.com/user-attachments/assets/0fbb9f5a-3341-4760-a4d0-d477a584192e

---------

Co-authored-by: Rafael Velazco <[email protected]>
  • Loading branch information
nollymar and rjvelazco authored Jan 14, 2025
1 parent f4ff6a0 commit ef325bf
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import com.dotcms.rest.api.BulkResultView;
import com.dotcms.rest.api.FailedResultView;
import com.dotcms.util.PaginationUtil;
import com.dotcms.util.pagination.ContainerPaginator;
import com.dotcms.util.pagination.OrderDirection;
import com.dotcms.util.pagination.TemplatePaginator;
import com.dotmarketing.beans.Host;
Expand All @@ -26,7 +25,6 @@
import com.dotmarketing.portlets.templates.business.TemplateSaveParameters;
import com.dotmarketing.portlets.templates.design.bean.TemplateLayout;
import com.dotmarketing.portlets.templates.design.util.DesignTemplateUtil;
import com.dotmarketing.portlets.templates.factories.TemplateFactory;
import com.dotmarketing.portlets.templates.model.Template;
import com.dotmarketing.util.ActivityLogger;
import com.dotmarketing.util.InodeUtils;
Expand Down Expand Up @@ -136,15 +134,26 @@ public final Response list(@Context final HttpServletRequest httpRequest,
final InitDataObject initData = new WebResource.InitBuilder(webResource)
.requestAndResponse(httpRequest, httpResponse).rejectWhenNoUser(true).init();
final User user = initData.getUser();
final Lazy<String> lazyCurrentHost = Lazy.of(() -> Try.of(() -> Host.class.cast(httpRequest.getSession().getAttribute(WebKeys.CURRENT_HOST)).getIdentifier()).getOrNull());
final Optional<String> checkedHostId = Optional.ofNullable(Try.of(()-> APILocator.getHostAPI()
.find(hostId, user, false).getIdentifier()).getOrElse(lazyCurrentHost.get()));


Logger.debug(this, ()-> "Getting the List of templates");

final Map<String, Object> extraParams = new HashMap<>();
extraParams.put(ARCHIVE_PARAM, archive);
checkedHostId.ifPresent(checkedHostIdentifier -> extraParams.put(ContainerPaginator.HOST_PARAMETER_ID, checkedHostIdentifier));

//In case we need to get the list of templates across multiple sites, we don't set the TemplatePaginator.HOST_PARAMETER_ID
if (null == hostId || !StringPool.STAR.equals(hostId)) {
final Lazy<String> lazyCurrentHost = Lazy.of(() -> Try.of(() -> Host.class.cast(
httpRequest.getSession().getAttribute(WebKeys.CURRENT_HOST)).getIdentifier())
.getOrNull());
final Optional<String> checkedHostId = Optional.ofNullable(
Try.of(() -> APILocator.getHostAPI()
.find(hostId, user, false).getIdentifier())
.getOrElse(lazyCurrentHost.get()));
checkedHostId.ifPresent(
checkedHostIdentifier -> extraParams.put(TemplatePaginator.HOST_PARAMETER_ID,
checkedHostIdentifier));
}
return this.paginationUtil.getPage(httpRequest, user, filter, page, perPage, orderBy, OrderDirection.valueOf(direction),
extraParams);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,10 @@ function fetchTemplateImage(templateId) {
* Callback function to handle the template fetch
*
*/
const onTemplateFetchComplete = function(templates, currentRequest){
const onTemplateFetchComplete = function(templates, currentRequest) {
const templateId = dojo.byId("template").value;
const isTemplateValid = templateId && templateId != "0";

console.log("templateId", templateId);

if(!templates || templates.length === 0){
return;
}
Expand All @@ -93,36 +91,26 @@ const onTemplateFetchComplete = function(templates, currentRequest){
}

const { identifier, fullTitle } = template;
// We set the values directly into the components because setting it direcrtly into`templateSel` fires another load operation.

// 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);
};

/**
* Handles the template change event
*
*/
function templateChanged() {
const templateSel=dijit.byId("templateSel");
const value=templateSel.get('value');

if(!value) {
return;
}

if(value == "0") {
templateSel.set("value","");
templateSel.filter();

return;
}

dojo.byId("template").value=value;
fetchTemplateImage(value);
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
*
Expand Down Expand Up @@ -151,11 +139,12 @@ dojo.ready(function(){
currentTemplateIdElement.value = templateId;

const templateStore = new dotcms.dojo.data.TemplateReadStore({
hostId: hostId,
templateSelected: templateId
hostId: '',
templateSelected: templateId,
allSiteLabel: true
});

const templateSelect=new dijit.form.FilteringSelect({
const templateSelect = new dijit.form.FilteringSelect({
id:"templateSel",
name:"templateSel",
style:"width:350px;",
Expand All @@ -170,7 +159,7 @@ dojo.ready(function(){
labelAttr: "htmlTitle",
searchAttr: "fullTitle",
value: templateId,
invalidMessage: '$text.get("Invalid-option-selected")'
invalidMessage: '$text.get("Invalid-option-selected")',
},"templateHolder");

if (isTemplateValid){
Expand All @@ -182,8 +171,7 @@ dojo.ready(function(){

const templateFetchParams = {
query: {
fullTitle: '*',
hostId: hostId
fullTitle: '*'
},
queryOptions: {},
start: 0,
Expand All @@ -192,6 +180,34 @@ dojo.ready(function(){
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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,26 @@ dojo.declare("dotcms.dojo.data.TemplateReadStore", null, {
includeArchived: false,
includeTemplate: null,
templateSelected: '',
allSiteLabel: false,

/**
* To Emulate this old Backend Behavion
* https://github.com/dotCMS/core/blob/7bc05d335b98ffb30d909de9ec82dd4557b37078/dotCMS/src/main/java/com/dotmarketing/portlets/templates/ajax/TemplateAjax.java#L72-L76
*
* @type {*}
* */
ALL_SITE_TEMPLATE: {
title: "All Sites",
fullTitle: "All Sites",
htmlTitle: '<div>-- All Sites ---</div>',
identifier: "0",
inode: "0"
},

constructor: function (options) {
this.hostId = options.hostId;
this.allSiteLabel = options.allSiteLabel;
this.templateSelected = options.templateSelected;
window.top._dotTemplateStore = this;
},

getValue: function (item, attribute, defaultValue) {
Expand Down Expand Up @@ -95,20 +110,25 @@ dojo.declare("dotcms.dojo.data.TemplateReadStore", null, {

} else {

const hostId = keywordArgs.query.hostId;

let url = "/api/v1/templates/?filter=" + keywordArgs.query.fullTitle.replace('*','') + "&page=" + keywordArgs.start + "&per_page=" + keywordArgs.count +
(keywordArgs.sort == undefined || keywordArgs.sort != ""? "": "&orderby=" + keywordArgs.sort);

if (keywordArgs.query.hostId != undefined && keywordArgs.query.hostId != "") {
url += "&host=" + keywordArgs.query.hostId;
if (hostId != undefined && hostId != "") {
url += "&host=" + hostId;
}

fetch(url)
.then((fetchResp) => fetchResp.json())
.then(responseEntity => {

this.fetchTemplatesCallback(keywordArgs, responseEntity);
}
);
if(this.allSiteLabel) {
responseEntity.entity.unshift(this.ALL_SITE_TEMPLATE);
};

this.fetchTemplatesCallback(keywordArgs, responseEntity);
});

this.currentRequest = keywordArgs;
this.currentRequest.abort = function () { };
Expand All @@ -117,7 +137,6 @@ dojo.declare("dotcms.dojo.data.TemplateReadStore", null, {
},

fetchTemplatesCallback: function (keywordArgs, templatesEntity) {

var scope = keywordArgs.scope;
if(keywordArgs.onBegin) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.dotcms.rest.api.v1.template;

import static com.dotcms.rendering.velocity.directive.ParseContainer.PARSE_CONTAINER_UUID_PREFIX;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -32,7 +31,6 @@
import com.dotmarketing.beans.MultiTree;
import com.dotmarketing.beans.Permission;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.ApiProvider;
import com.dotmarketing.business.PermissionAPI;
import com.dotmarketing.exception.DoesNotExistException;
import com.dotmarketing.exception.DotDataException;
Expand All @@ -46,17 +44,18 @@
import com.dotmarketing.portlets.templates.model.Template;
import com.dotmarketing.util.PageMode;
import com.dotmarketing.util.PaginatedArrayList;
import com.dotmarketing.util.StringUtils;
import com.dotmarketing.util.UUIDGenerator;
import com.dotmarketing.util.WebKeys;
import com.liferay.portal.model.User;
import com.liferay.util.Base64;
import com.liferay.util.StringPool;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.junit.Assert;
Expand Down Expand Up @@ -1070,6 +1069,49 @@ public void test_listTemplate_filterByHost_usingCurrentHost()
APILocator.getIdentifierAPI().find(((TemplateView) paginatedArrayListWihoutSystemTemplate.get(0)).getIdentifier()).getHostId());
}

/**
* Method to test: list in the TemplateResource
* Given Scenario: Create a template on hostA, and a template on hostB. List templates using the
* wildcard host as the query param.
* ExpectedResult: The endpoint should return 200, and templateA and templateB should be returned
*/
@Test
public void test_listTemplate_filterByHost_usingWildcardHost()
throws DotSecurityException, DotDataException {
final String title = "Template" + System.currentTimeMillis();
final Host newHostA = new SiteDataGen().nextPersisted();
final Host newHostB = new SiteDataGen().nextPersisted();

//Create templates in two different sites
final Template templateA = APILocator.getTemplateAPI()
.saveTemplate(new TemplateDataGen().title(title).next(), newHostA, adminUser,
false);
final Template templateB = APILocator.getTemplateAPI()
.saveTemplate(new TemplateDataGen().title(title).next(), newHostB, adminUser,
false);

//Call Resource
final Response responseResource = resource.list(
getHttpRequest(adminUser.getEmailAddress(), "admin"), response, title, 0, 40,
"mod_date", "DESC",
StringPool.STAR, false);
//Check that the response is 200, OK
Assert.assertEquals(Status.OK.getStatusCode(), responseResource.getStatus());
final ResponseEntityView responseEntityView = ResponseEntityView.class.cast(
responseResource.getEntity());
final PaginatedArrayList paginatedArrayList = PaginatedArrayList.class.cast(
responseEntityView.getEntity());
final PaginatedArrayList paginatedArrayListWihoutSystemTemplate = removeSystemTemplate(
paginatedArrayList);
Assert.assertEquals(2, paginatedArrayListWihoutSystemTemplate.size());
paginatedArrayListWihoutSystemTemplate.stream().anyMatch(
templateView -> ((TemplateView) templateView).getIdentifier()
.equals(templateA.getIdentifier()));
paginatedArrayListWihoutSystemTemplate.stream().anyMatch(
templateView -> ((TemplateView) templateView).getIdentifier()
.equals(templateB.getIdentifier()));
}

/**
* Method to test: list in the TemplateResource
* Given Scenario: Create 2 templates, and a limited user. Give READ Permissions to one template to
Expand Down

0 comments on commit ef325bf

Please sign in to comment.