Site Specific Insert Option Rules For Sitecore

January 18, 2013

I recently had the need to add a new template type for a single site on my multi-site system and wanted to be able to provide insert options for this template on content folders only in this site. The content folders are a global template and are shared across multiple sites so adding this new template type to the insert options of the content folder was not a viable option.

I know, from previous experience that rules engine would be great solution for this but there were a few additional constraints that I had which the default settings didn't provide for. I wanted to limit the scope of the rule to work only on items in that site. Using the default setup I would have to specify that the selected item would need to be an ancestor of the web root item but this isn't a preferable option since I would need to add this condition to every rule.

I wanted to store the rules at least in folders separated by site and I figured I could use this separation to know when to run the rules and save the added conditions. By default the rules for all insert options are stored in a single folder within Sitecore in /sitecore/system/rules/insert options/rules, which means that rules for all sites is in one folder. This isn't optimal because the list could potentially become very long and difficult to know which rules are for which sites. Also I wanted to store the rules for each individual site separately so that when a site is taken down and removed from the system all the rules would be easy to remove along with the site specific layouts, sublayouts and templates. I potentially could add subfolders to the insert rules. I would have to override the default processor so that it would look recursively into folders but I would also have to rely on finding the folder to match the site name which could be flaky since someone could misspell the folder. I decided to store the rules for the site below the site root at the same level at the home node for the site.

site specific rules folder

Now that I had decided where to store the rule I had to figure out the best way to apply them. I could either add the rules to the template or override the processor. Adding the rules to the template meant that it would be cluttered with rules from all sites and would require maintenance if/when sites were added or removed. I decided that the best option was to override the processor. This would allow me to query for the rules from the site start path by matching it to the current item and then add any insert options that are stored there.

I found that the pipeline processor uiGetMasters/RunRules is what needs to be overridden. I pulled the source from a decompiler and noticed that it was directly looking for the global folder. I dug a little deeper and found that its taking all the children of the folder and passing it to an overload of the GetRules methods that takes an IEnumerable<Item>. From here I knew that I still wanted to include the global items, I just wanted to add a few more things to the enumeration. I used the context item that is passed in with the mastersArgs and checked the ancestors for a site root node. If one is found then I look through its children for a rules folder and then check the rules folder for any insert option rules in its ancestors. I then add these to the list and then run rules like it was originally.

Here's the code for my override of the RunRules class: 

using System;using System.Collections.Generic;using System.Linq;using System.Text;using Sitecore.Pipelines.GetMasters;using Sitecore.Data.Items;using Sitecore.Diagnostics;using Sitecore.SecurityModel;using Sitecore.Rules.InsertOptions;using Sitecore.Rules;namespace MySite.Pipelines.GetMasters{	public class RunRules	{		protected string RulesFolder = "RulesFolder";		protected string InsertOptionsRule = "{664E5035-EB8C-4BA1-9731-A098FCC9127A}";		protected string Website = "WebsiteRoot";		// Methods		public void Process(GetMastersArgs args) {						//check if null			Assert.ArgumentNotNull(args, "args");			if (args.Item == null)				return;			//init start list			List<Item> insertRuleItems = new List<Item>();						//skip permissions			using (new SecurityDisabler()) {				//get the default rules				Item DefaultRules = args.Item.Database.GetItem("/sitecore/system/Settings/Rules/Insert Options/Rules");				if (DefaultRules != null)					insertRuleItems.AddRange(DefaultRules.Children);				//get any site specific rules.				Item SiteRoot = GetSiteItem(args.Item);				if (SiteRoot != null) {					Item RulesFolder = ChildByTemplate(SiteRoot, this.RulesFolder);					if(RulesFolder != null) {						var rules = from val in RulesFolder.Axes.GetDescendants()									where IsTemplateID(val, this.InsertOptionsRule)									select val;						if (rules.Any())							insertRuleItems.AddRange(rules);					}				}			}			//if you find any rules then use them.			if (insertRuleItems.Any()) {				//setup context				InsertOptionsRuleContext ruleContext = new InsertOptionsRuleContext {					Item = args.Item,					InsertOptions = args.Masters				};				//get those rules				RuleList<InsertOptionsRuleContext> rules = RuleFactory.GetRules<InsertOptionsRuleContext>(insertRuleItems, "Rule");				if (rules != null) {					rules.Run(ruleContext);				}			}		}		/// <summary>		/// This gets the first child matching the provided template name		/// </summary>		public static Item ChildByTemplate(Item Parent, string Templatename) {			IEnumerable<Item> children = from child in Parent.GetChildren() 										 where child.TemplateName.Equals(Templatename) 										 select child;			return (children.Any()) ? children.First() : null;		}		/// <summary>		/// this checks if the current item's template id matches the given template id		/// </summary>		protected static bool IsTemplateID(Item item, string targetTemplateId) {			return item.TemplateID.ToString().Equals(targetTemplateId);		}		/// <summary>		/// this gets any website root item from the ancestors of the given item, if there is one.		/// </summary>		protected Item GetSiteItem(Item item) {			return item.Axes.SelectSingleItem("ancestor-or-self::*[@@templatename='" + this.Website + "']");		}	}}

I wanted to note that I embedded the insert options under the rules folder because I wanted to leave the door open to the possibility that more types of rules other than insert options could be supported for the sites.

After I'd implemented this I found the All About Insert Options article by John West which was a good source of information as well.