AEM CQ 56 - Disable Cancel Inheritance in Live Copies

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


This is a manual task and user has to add listener on dialogs of "disable cancel inheritance required page components". I couldn't find a way to automate it, which would have been ideal

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

3) Search for user author, Select Type as Deny, Check jcr:nodeTypeManagement privilege and Click ok

4) A text component added in par inherits the ACL

5) when user author tries to cancel the relationship by clicking on Cancel Inheritance Lock icon, he sees error.

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.

1 comment:

  1. Is there a way to change the error message in solution 2 ?

    ReplyDelete