Since SXA templates are pre-built by Sitecore, enabling Standard Values or Workflows can be very time-consuming. This becomes even more difficult if certain items need to go through separate workflows. Sitecore’s recommendation to “Add and extend an SXA template” states that “You might want to add a data template when, for example, a project requires fields that are not defined in existing data templates, or when new items require unique default field values or default settings (for example, a default workflow).

As you can imagine, there are dozens of template that someone may use to create a standard SXA website. Since modifying the actual SXA templates is a bad idea, following Sitecore’s recommendation seems to be the only choice, until now. In this post, I will demonstrate how to use the powerful Sitecore Rule Engine to make these changes for us upon the item creation.

Basically, we’re going to point Sitecore to a folder of Rules that can get evaluated at the time of item creation, and then perform the necessary actions as defined in the rules engine. For example, we can say, “if item is of template “A”, the add workflow “1” to the new item”. Let’s get started:

First, you’ll need add a processor to the <itemProvider> node:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <group name="itemProvider" groupName="itemProvider">
        <pipelines>
          <addFromTemplate>
            <processor mode="on" type="MasterToWeb.Feature.XA.Extensions.Pipelines.AddFromTemplate.AddFromTemplateRulesProcessor,MasterToWeb.Feature.XA.Extensions">
              <ruleFolderId>{3DD3053A-88B6-44EF-B3E5-A4B42ECB84E5}</ruleFolderId>
            </processor>
          </addFromTemplate>
        </pipelines>
      </group>
    </pipelines>
  </sitecore>
</configuration>

Now, let’s look at the processor code:

using System;
using MasterToWeb.Feature.XA.Extensions.Rules;
using Sitecore;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.ItemProvider.AddFromTemplate;

namespace MasterToWeb.Feature.XA.Extensions.Pipelines.AddFromTemplate
{
    public class AddFromTemplateRulesProcessor : AddFromTemplateProcessor
    {
        #region Public Properties
        [CanBeNull]
        [UsedImplicitly]
        public string RuleFolderId { get; set; }
        #endregion

        #region Public Methods and Operators
        public override void Process([NotNull] AddFromTemplateArgs args)
        {
            if (args.Aborted)
            {
                return;
            }

            Assert.IsNotNull(args.FallbackProvider, "FallbackProvider is null");

            try
            {
                var item = args.FallbackProvider.AddFromTemplate(args.ItemName, args.TemplateId, args.Destination, args.NewId);
                if (item == null)
                {
                    return;
                }

                args.ProcessorItem = args.Result = item;
            }
            catch (Exception ex)
            {
                Log.Error("AddFromTemplateRulesProcessor failed. Removing partially created item if it exists.", ex, this);

                var item = args.Destination.Database.GetItem(args.NewId);
                item?.Delete();

                throw;
            }

            ID id;
            if (string.IsNullOrWhiteSpace(RuleFolderId)
                || !Settings.Rules.ItemEventHandlers.RulesSupported(args.Destination.Database)
                || !ID.TryParse(RuleFolderId, out id))
            {
                return;
            }

            var ruleContext = new PipelineArgsRuleContext<AddFromTemplateArgs>(args);
            RuleManager.RunRules(ruleContext, id);


        }
        #endregion
    }
}

What the above is doing is taking a look at the rules defined by the ruleFolderId in the configuration, and letting the Sitecore Rules Manager evaluate the conditions and perform the assigned actions. We also use a RuleManager and IPipelineArgsRuleContext class to drive the functionality.

using Sitecore;
using Sitecore.Data.Items;

namespace MasterToWeb.Feature.XA.Extensions.Rules
{
    /// <summary>
    /// The PipelineArgsRuleContext interface.
    /// </summary>
    /// <remarks>
    /// Credit to Jim "Jimbo" Baltika for developing this class
    /// </remarks>
    /// <typeparam name="TArgs">The type of the arguments.</typeparam>
    public interface IPipelineArgsRuleContext<out TArgs>
        where TArgs : class
    {
        /// <summary>
        /// Gets the args.
        /// </summary>
        [NotNull]
        TArgs Args { get; }

        /// <summary>
        /// Gets the processor item.
        /// </summary>
        [NotNull]
        Item Item { get; }
    }
}

And the implementation:

using Sitecore;
using Sitecore.Diagnostics;
using Sitecore.Pipelines;
using Sitecore.Rules;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MasterToWeb.Feature.XA.Extensions.Rules
{
    public class PipelineArgsRuleContext<TArgs> : RuleContext, IPipelineArgsRuleContext<TArgs>
        where TArgs : PipelineArgs
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="PipelineArgsRuleContext{TArgs}"/> class.
        /// </summary>
        /// <param name="args">
        /// The args.
        /// </param>
        public PipelineArgsRuleContext([NotNull] TArgs args)
        {
            Assert.IsNotNull(args, "args");

            Args = args;
            Item = args.ProcessorItem.InnerItem;
        }

        /// <summary>
        /// Gets the args.
        /// </summary>
        [NotNull]
        public TArgs Args { get; }
    }
}
using Sitecore;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Rules;
using Sitecore.SecurityModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MasterToWeb.Feature.XA.Extensions.Rules
{
    public static class RuleManager
    {
        /// <summary>
        /// The run rules.
        /// </summary>
        /// <param name="ruleContext">The rule context.</param>
        /// <param name="rulesFolder">The rules folder.</param>
        public static void RunRules<TRuleContext>([NotNull] TRuleContext ruleContext, [NotNull] ID rulesFolder)
                where TRuleContext : RuleContext
        {
            Assert.ArgumentNotNull(ruleContext, "ruleContext != null");
            Assert.ArgumentNotNull(rulesFolder, "rulesFolder");

            try
            {
                if (!Settings.Rules.ItemEventHandlers.RulesSupported(ruleContext.Item.Database))
                {
                    return;
                }

                Item rulesFolderItem;
                using (new SecurityDisabler())
                {
                    rulesFolderItem = ruleContext.Item.Database.GetItem(rulesFolder);
                    if (rulesFolderItem == null)
                    {
                        return;
                    }
                }

                var rules = RuleFactory.GetRules<TRuleContext>(rulesFolderItem, "Rule");
                if (rules == null || rules.Count == 0)
                {
                    return;
                }

                rules.Run(ruleContext);
            }
            catch (Exception exception)
            {
                Log.Error(exception.Message, exception, typeof(RuleManager));
            }
        }
    }
}

That’s all it takes from the foundation level. Now, let’s look at an example rule and action:

Figure A

We’ve created some rules under the ruleFolderId location that we specified. As you can see inFigure A above, we assigned some conditions that come out of the box from Sitecore. In this instance, we only want to apply our workflow to items below the Home item and when the template is L1 Content Page (you would use whatever template you prefer, such as any out of the box SXA tempaltes, such as a Media item /sitecore/templates/Feature/Experience Accelerator/Media/Datasource/Image). We have a final condition to ensure that the item is not already in a workflow. If all of those conditionas are met, we then assign the “assign to and start the <workflow name> workflow.” This is a custom action we created. In this instance we created a new Action item under /sitecore/system/Settings/Rules/Definitions/Elements/Workflows/
and set the following properties:

For the text, we set the following:
assign to and start the [workflowid,Tree,root={05592656-56D7-4D85-AACF-30919EE494F9}&selection=System/Workflow/Workflow,specific] workflow
The “root” property is pointing to the ID of the /sitecore/system/Workflows folder to allow the user to select from prefedefined workflows.

The “Type” field points to our custom code:
MasterToWeb.Feature.XA.Extensions.Rules.Actions.Rules.Actions.AssignAndStartWorkflow, MasterToWeb.Feature.XA.Extensions.Rules.Actions

using Sitecore;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Rules;
using Sitecore.Rules.Actions;
using Sitecore.SecurityModel;
using System.Runtime.InteropServices;

namespace MasterToWeb.Feature.XA.Extensions.Rules.Actions
{
    [UsedImplicitly]
    [Guid("73594343-DC04-4DC8-8B59-C66725AD86B1")]
    public sealed class AssignAndStartWorkflow<TRuleContext> : RuleAction<TRuleContext> where TRuleContext : RuleContext
    {
        public string workflowid { get; set; }
        public override void Apply(TRuleContext ruleContext)
        {
            if (ruleContext.Item == null) return;
            if (!ID.IsID(workflowid)) return;

            using (new SecurityDisabler())
            {
                using (new EditContext(ruleContext.Item))
                {                   
                    var workflow = Factory.GetDatabase("master")?.WorkflowProvider.GetWorkflow(workflowid);
                    if (workflow != null)
                    {
                        //ruleContext.Item[Sitecore.FieldIDs.Workflow] = new ID(workflowid).ToString();
                        workflow.Start(ruleContext.Item);
                    }
                }
            }
        }
    }
}

Now, every time that an item is created, the rules are evaluated. If the conditions are met, our Assign To Workflow action is triggered and automatically assign the workflow to that item. You can create multiple workflows, for example, one for the main pages, and another for the datasource items, and using the rules engine, assign them appropriately.

As you can probably imagine, this is not limited to workflow assignments. Assigning the workflow was simply an action we took when the conditions were met. You can create ANY action for ANY function and still utilize the same foundation. For example, if you want to email a certain group of people when pages are created, simply create a new rule and assign it to your new custom action for the email. The options are unlimited.

Samples shown above can also be downloaded from: https://github.com/MasterToWeb/Sitecore.XA.Extensions

Additional Credit: Portions of this code were inspired and copied from Zachary Kniebel’s post here: https://sitecore.stackexchange.com/questions/2919/automatically-creating-data-sources-for-renderings-on-a-new-page