Goal
Multi Site Manager is a fantastic concept, very useful in creating multinational sites. You create a source site, create live copies of it for each region and maintain the relationship between source site and live copies, so that any updates on source can be rolled out to live copies
An admin of live copy can break the inheritance relationship with source, by clicking on a component's Cancel Inheritance Lock Icon
In this post we'll work on logic to provide an option to live copy creator, to disable cancel inheritance in live copies. So the live copy author cannot edit components rolled out from source; he can always add new components, but cannot alter existing ones. Check demo and Download package install. For a more granular approach on how to set the Cancel Inheritance option for various users/groups check this post
when a live copy creator checks the Disable Cancel Inheritance option in Create Size Wizard, a property cq:isDisableCancelInheritance is set to true on the live copy root jcr:content node
Page dialogs, components read the property cq:isDisableCancelInheritance and act accordinly
Solution - 1
With few JS lines of code we can disable the cancel inheritance options
1) Login to CRXDE Lite, create folder (nt:folder) /apps/msm-disable-cancel-inheritance
3) Create clientlib (type cq:ClientLibraryFolder) /apps/msm-disable-cancel-inheritance/clientlib and set a property categories of String type to cq.widgets
4) Create file ( type nt:file ) /apps/msm-disable-cancel-inheritance/clientlib/js.txt, add the following
disable-cancel-inheritance.js
5) Create file ( type nt:file ) /apps/msm-disable-cancel-inheritance/clientlib/disable-cancel-inheritance.js, add the following code
CQ.Ext.ns("ExperienceAEM"); ExperienceAEM.CreateSite = { disableCancelInheritanceFlag: false, //add the disable cancel inheritance option to create site wizard addDisableCancelInheritance: function(grid){ var toolBar = grid.getTopToolbar(); var newMenu = toolBar.findBy(function(comp){ return comp["iconCls"] == "cq-siteadmin-create-page-icon"; }, toolBar)[0]; var newSite = newMenu.menu.findBy(function(comp){ return comp["iconCls"] == "cq-siteadmin-create-site-icon"; }, newMenu)[0]; newSite.on('click', function(){ var dlg = CQ.WCM.getDialog("", "cq-siteadmin-csw", true); dlg.navHandler = function(d) { CQ.wcm.CreateSiteWizard.prototype.navHandler.call(this, d); var idx = this.activePage + d; //we are at the live copy page in wizard if(idx == 4){ var liveCopyPanel = this.wizPanel.layout.activeItem; liveCopyPanel.add(new CQ.Ext.form.Checkbox({ fieldDescription: "Live copy owners will not be able to cancel component inheritance", fieldLabel: 'Disable Cancel Inheritance', name: "./cq:isDisableCancelInheritance", inputValue: true, checked: false })); liveCopyPanel.doLayout(); } }; }) }, disableCancelInheritance: function(){ var sk = CQ.WCM.getSidekick(); var pathTokens = sk.getPath().split("/"); var siteSourcePath = "/" + pathTokens[1] + "/" + pathTokens[2] + "/jcr:content.json"; $.ajax({ url: siteSourcePath, async: false, dataType: "json", success: function(data){ this.disableCancelInheritanceFlag = eval(data["cq:isDisableCancelInheritance"]); }.createDelegate(this) }); if(!this.disableCancelInheritanceFlag){ return; } var editables = CQ.utils.WCM.getEditables(); CQ.Ext.iterate(editables, function(path, editable) { if(!editable.addElementEventListener){ if(editable.liveStatus){ editable.liveStatus.setDisabled(true); editable.liveStatus.setTooltip("Creator of this livecopy has disabled cancel inheritance"); } return; } editable.on(CQ.wcm.EditBase.EVENT_BEFORE_EDIT, function(){ var INTERVAL = setInterval(function(){ var dialog = editable.dialogs[CQ.wcm.EditBase.EDIT]; if(dialog){ clearInterval(INTERVAL); if(dialog.editLockButton){ dialog.editLockButton.setDisabled(true); dialog.editLockButton.setTooltip("Creator of this livecopy has disabled cancel inheritance"); } } }, 200); }); //disable some inheritance specific options in context menu editable.addElementEventListener(editable.element.dom, "contextmenu" , function(){ var msm = this["msm:liveRelationship"]; if(!msm || !msm["msm:status"] || !msm["msm:status"]["msm:isSourceExisting"]){ return; } var component = this.element.linkedEditComponent; if (!component || !component.menuComponent) { return; } var menu = component.menuComponent; var opts = [ menu.find('text', "Delete"), menu.find('text', "Cut") ]; CQ.Ext.each(opts, function(opt){ if(opt && opt.length > 0){ opt[0].setDisabled(true); } }); }, true, editable); //disable the top right lock icon of editable editable.element.on('click', function(){ var lock = this.highlight.lock; if(lock){ lock.getEl().dom.title = "Creator of this livecopy has disabled cancel inheritance"; lock.setDisabled(true); lock.getEl().removeAllListeners(); } }, editable); }); //disable the sidekick lock status button which allows cancel inheritance of editables var lcsBut = sk.liveCopyStatusButton; if(lcsBut){ lcsBut.setDisabled(true); lcsBut.setTooltip("Creator of this livecopy has disabled livecopy status"); }else{ sk.on('loadcontent', function(){ lcsBut = sk.liveCopyStatusButton; lcsBut.setDisabled(true); lcsBut.setTooltip("Creator of this livecopy has disabled livecopy status"); }); } }, //register this function as listener for "loadcontent" event on dialog disableSkCancelInheritance: function(dialog){ if(!this.disableCancelInheritanceFlag){ return; } var fields = CQ.Util.findFormFields(dialog.formPanel); CQ.Ext.iterate(fields, function(name, f){ CQ.Ext.each(f, function(field){ if(field.lockPanel){ field.lockPanel.setDisabled(true); }else if(field.fieldEditLockBtn){ field.fieldEditLockBtn.setDisabled(true); field.fieldEditLockBtn.setTooltip("Creator of this livecopy has disabled cancel inheritance"); } }) }); } }; (function(){ var E = ExperienceAEM.CreateSite; if(window.location.pathname == "/siteadmin"){ var INTERVAL = setInterval(function(){ var grid = CQ.Ext.getCmp("cq-siteadmin-grid"); if(grid){ clearInterval(INTERVAL); E.addDisableCancelInheritance(grid); } }, 250); }else if( ( window.location.pathname == "/cf" ) || ( window.location.pathname.indexOf("/content") == 0)){ if(CQ.WCM.isEditMode()){ CQ.WCM.on("editablesready", E.disableCancelInheritance, E); } } })();
4) In the above code addDisableCancelInheritance function adds a checkbox option Disable Cancel Inheritance to create size wizard; when checked the live copy user cannot disable cancel inheritance, when unchecked the live copies will have cancel inheritance lock enabled
5) The following logic in disableCancelInheritance function, iterates through available editables on page, and disables the lock icon in edit dialogs
editable.on(CQ.wcm.EditBase.EVENT_BEFORE_EDIT, function(){ var INTERVAL = setInterval(function(){ var dialog = editable.dialogs[CQ.wcm.EditBase.EDIT]; if(dialog){ clearInterval(INTERVAL); dialog.editLockButton.setDisabled(true); dialog.editLockButton.setTooltip("Creator of this livecopy has disabled cancel inheritance"); } }, 200); });
6) We should also disable some context menu options that allow the user to cancel inheritance like deleting a component rolled out from source. Below logic in function disableCancelInheritance does the part of disabling such context menu options
editable.addElementEventListener(editable.element.dom, "contextmenu" , function(){ var msm = this["msm:liveRelationship"]; .... .... .... }, true, editable);
7) The Page properties dialog of sidekick also allows user to cancel inheritance. The function disableSkCancelInheritance takes care of cancel inheritance lock icons in page properties. For this logic to execute we register a loadcontent event listener on page component dialog
function(d){ ExperienceAEM.CreateSite.disableSkCancelInheritance(d); }
8) This extension was not extensively tested with all available integrations like Sitecatalyst, Target. There is scope for some options that come with integrations allowing user to cancel inheritance, so there is always room for improvement
Leave a comment if you find bugs...
Solution 2 - Deny Cancel Inheritance to User/Group
A privilege at JCR level also gives an option to restrict authors from cancelling inheritance - check this post. Consider a scenario where a live copy administrator would like to restrict user author from cancelling inheritance of a paragraph..
1) In CRXDE Lite (http://localhost:4502/crx/de) browse to par of live copy page (eg. /content/my-blueprint-site1/en/summer/jcr:content/par)
2) In the right pane, click on Access Control -> Add Entry
4) A text component added in par inherits the ACL
6) Deny jcr:nodeTypeManagement should be set on every live copy page paragraph for NOT allowing cancel inheritance for a user/group. Rollout from blueprint will not effect the deny permission set.
Is there a way to change the error message in solution 2 ?
ReplyDelete