Overriding Optimizely CMS Approval Sequences

Optimziely CMS Approval Sequences are an important tool for organisations that use CMS to translate, review and quality check content before publishing. A typical Optimziely CMS Approval sequences configuration involves multiple stages of approval, each requiring actions such as click to Approve/Decline and Commenting.

The Problem

I recently worked with a client who needed to bypass the administration overhead that Approval Sequences adds but only for certain CMS users , whose role within the organisation was to coordinate the large bulk publishing operations sometimes associated with releases.

While there is the option for administrators to use the “Approve Entire Approval Sequence” button, the consensus for this use case was that using this still added overhead for the editors.

Specifically, we needed to force the approval of Approval Sequences for these editorial users, reducing the number of interactions required. The key requirement is to streamline operations and minimise the effort involved in the publishing process for these CMS Editors.

The Solution

We settled on a solution to customise the Approval Engine using the IApprovalEngine interface:

When a CMS Editor clicks ‘Ready for Review’, use the Approval Engine events to:

  • Check is this user is part of the relevant User Group to override the approval sequence
  • If so, use the Approval Engine to force the approval of this sequence

Additionally we decided to give this Editorial team the option of reducing their clicks even more by Publishing content directly after clicking ‘Ready for Review’. As this isn’t necessarily recommended, but may be useful for this team in some scenarios, we put this feature behind a Site Setting feature toggle.

Technical Implementation

The following code demonstrates setting up an InitializationModule to override the approval sequence and publish. I’ve hard coded the admin email address in this example so you will should swap that out for your desired business logic.

        [InitializableModule]
        [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
        public class CustomApprovalsInitialization : IInitializableModule
        {
            private IApprovalEngineEvents _approvalEngineEvents;
            private ICustomerService _customerService;
            private IContentRepository contentRepository;
            private ISettingsService _settingsService;

            public void Initialize(InitializationEngine context)
            {
                _customerService = ServiceLocator.Current.GetInstance<ICustomerService>();
                _settingsService = ServiceLocator.Current.GetInstance<ISettingsService>();

                var contentEvents = ServiceLocator.Current.GetInstance<IContentEvents>();
                contentEvents.RequestedApproval += ContentEvents_RequestedApproval;

                _approvalEngineEvents = context.Locate.Advanced.GetInstance<IApprovalEngineEvents>();
                _approvalEngineEvents.StepStarted += _approvalEngineEvents_StepStarted;
            }

            private Task _approvalEngineEvents_StepStarted(object sender, ApprovalStepEventArgs e)
            {
                var user = _customerService.UserManager().FindByEmailAsync("admin@example.com").GetAwaiter().GetResult();
                if (user != null)
                {
                    var approvalEngine = ServiceLocator.Current.GetInstance<IApprovalEngine>();

                    Task.Run(() =>  {
                                        approvalEngine.ForceApproveAsync(e.ApprovalID, "admin@example.com", "Auto-approved by Admin.").GetAwaiter().GetResult();
                                    }).GetAwaiter().GetResult();

                }

                return Task.CompletedTask;
            }

            private void ContentEvents_RequestedApproval(object sender, ContentEventArgs e)
            {
                var settingsPage = _settingsService.GetSiteSettings<ReferencePageSettings>();
                if (settingsPage.ApprovalSequenceOverrideAutoPublish)
                {
                    var user = _customerService.UserManager().FindByEmailAsync("admin@example.com").GetAwaiter().GetResult();
                    if (user != null)
                    {
                        contentRepository = ServiceLocator.Current.GetInstance<IContentRepository>();

                        var content = contentRepository.Get<PageData>(e.ContentLink).CreateWritableClone();

                        contentRepository.Publish(content as IContent);
                    }
                }
            }

            public void Uninitialize(InitializationEngine context)
            {
                //Add uninitialization logic
            }
        }

Leave a comment