If you have read some of my older posts, you probably know by now how much I love writing code that expands tokens on Items in Sitecore, and decided to build another solution that expands new tokens added to Standard Values Items of Templates — out of the box, these aren’t expanded on preexisting Items that use the Template of the Standard Values Item, and end up making their way in fields on those preexisting Items (for an alternative solution, check out this older post I wrote some time ago).

In the following solution — this solution is primarily composed of a custom pipeline — tokens that are added to fields on the Standard Values Item will be expanded on all Items that use the Template of the Standard Values Item after the Standard Values Item is saved in the Sitecore client (I hook into the <saveUI> pipeline for this action on save).

We first need a class whose instance serves as the custom pipeline’s argument object:

using Sitecore.Data.Items; using Sitecore.Pipelines; using System.Collections.Generic; namespace Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems { public class ExpandNewTokensOnAllItemsArgs : PipelineArgs { public Item StandardValuesItem { get; set; } private List<Item> items; public List<Item> Items { get { if(items == null) { items = new List<Item>(); } return items; } set { items = value; } } } }

The caller of the custom pipeline is required to pass the Standard Values Item that contains the new tokens. One of the processors of the custom pipeline will collect all Items that use its Template — these are stored in the Items collection property.

The instance of the following class serves as the first processor of the custom pipeline:

using System; using Sitecore.Data; using Sitecore.Data.Items; using Sitecore.Data.Managers; using Sitecore.Diagnostics; namespace Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems { public class EnsureStandardValues { public void Process(ExpandNewTokensOnAllItemsArgs args) { Assert.ArgumentNotNull(args, "args"); Assert.ArgumentNotNull(args.StandardValuesItem, "args.StandardValuesItem"); if(IsStandardValues(args.StandardValuesItem)) { return; } args.AbortPipeline(); } protected virtual bool IsStandardValues(Item item) { Assert.ArgumentNotNull(item, "item"); return StandardValuesManager.IsStandardValuesHolder(item); } } }

This processor basically just ascertains whether the Item passed as the Standard Values Item is indeed a Standard Values Item — the code just delegates to the static IsStandardValuesHolder() method on Sitecore.Data.StandardValuesManager (this lives in Sitecore.Kernel.dll).

The instance of the next class serves as the second step of the custom pipeline:

using System.Collections.Generic; using System.Linq; using Sitecore.Data.Fields; using Sitecore.Diagnostics; namespace Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems { public class EnsureUnexpandedTokens { private List<string> Tokens { get; set; } public EnsureUnexpandedTokens() { Tokens = new List<string>(); } public void Process(ExpandNewTokensOnAllItemsArgs args) { Assert.ArgumentNotNull(args, "args"); Assert.ArgumentNotNull(args.StandardValuesItem, "args.StandardValuesItem"); if (!Tokens.Any()) { args.AbortPipeline(); return; } args.StandardValuesItem.Fields.ReadAll(); foreach(Field field in args.StandardValuesItem.Fields) { if(HasUnexpandedTokens(field)) { return; } } args.AbortPipeline(); } protected virtual bool HasUnexpandedTokens(Field field) { Assert.ArgumentNotNull(field, "field"); foreach(string token in Tokens) { if(field.Value.Contains(token)) { return true; } } return false; } } }

A collection of tokens are injected into the class’ instance via the Sitecore Configuration Factory — see the patch configuration file further down in this post — and determines if tokens exist in any of its fields. If no tokens are found, then the pipeline is aborted. Otherwise, we exit the Process() method immediately.

The instance of the following class serves as the third processor of the custom pipeline:

using System; using System.Collections.Generic; using System.Linq; using Sitecore.ContentSearch; using Sitecore.ContentSearch.SearchTypes; using Sitecore.Data; using Sitecore.Data.Items; using Sitecore.Data.Managers; using Sitecore.Diagnostics; namespace Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems { public class CollectAllItems { public void Process(ExpandNewTokensOnAllItemsArgs args) { Assert.ArgumentNotNull(args, "args"); Assert.ArgumentNotNull(args.StandardValuesItem, "args.StandardValuesItem"); args.Items = GetAllItemsByTemplateID(args.StandardValuesItem.TemplateID); if(args.Items.Any()) { return; } args.AbortPipeline(); } protected virtual List<Item> GetAllItemsByTemplateID(ID templateID) { Assert.ArgumentCondition(!ID.IsNullOrEmpty(templateID), "templateID", "templateID cannot be null or empty!"); using (var context = ContentSearchManager.GetIndex("sitecore_master_index").CreateSearchContext()) { var query = context.GetQueryable<SearchResultItem>().Where(i => i.TemplateId == templateID); return query.ToList().Select(result => result.GetItem()).ToList(); } } } }

This class uses the Sitecore.ContentSearch API to find all Items that use the Template of the Standard Values Item. If at least one Item is found, we exit the Process() method immediately. Otherwise, we abort the pipeline.

The instance of the class below serves as the fourth processor of the custom pipeline:

using System; using System.Collections.Generic; using System.Linq; using Sitecore.ContentSearch; using Sitecore.ContentSearch.SearchTypes; using Sitecore.Data; using Sitecore.Data.Items; using Sitecore.Data.Managers; using Sitecore.Diagnostics; namespace Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems { public class FilterStandardValuesItem { public void Process(ExpandNewTokensOnAllItemsArgs args) { Assert.ArgumentNotNull(args, "args"); Assert.ArgumentNotNull(args.Items, "args.Items"); if(!args.Items.Any()) { return; } args.Items = args.Items.Where(item => !IsStandardValues(item)).ToList(); } protected virtual bool IsStandardValues(Item item) { Assert.ArgumentNotNull(item, "item"); return StandardValuesManager.IsStandardValuesHolder(item); } } }

The code in this class ensures the Stardard Values Item is not in the collection of Items. It’s probably not a good idea to expand tokens on the Standard Values Item. 🙂

The instance of the next class serves as the final processor of the custom pipeline:

using System.Linq; using Sitecore.Configuration; using Sitecore.Data.Items; using Sitecore.Diagnostics; using Sitecore.Data; namespace Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems { public class ExpandTokens { private MasterVariablesReplacer TokenReplacer { get; set; } public ExpandTokens() { TokenReplacer = GetTokenReplacer(); } public void Process(ExpandNewTokensOnAllItemsArgs args) { Assert.ArgumentNotNull(args, "args"); Assert.ArgumentNotNull(args.Items, "args.Items"); if (!args.Items.Any()) { args.AbortPipeline(); return; } foreach(Item item in args.Items) { ExpandTokensOnItem(item); } } protected virtual void ExpandTokensOnItem(Item item) { Assert.ArgumentNotNull(item, "item"); item.Fields.ReadAll(); item.Editing.BeginEdit(); TokenReplacer.ReplaceItem(item); item.Editing.EndEdit(); } protected virtual MasterVariablesReplacer GetTokenReplacer() { return Factory.GetMasterVariablesReplacer(); } } }

The code above uses the instance of Sitecore.Data.MasterVariablesReplacer (subclass or otherwise) — this is defined in your Sitecore configuration at settings/setting[@name=”MasterVariablesReplacer”] — and passes all Items housed in the pipeline argument instance to its ReplaceItem() method — each Item is placed in an editing state before having their tokens expanded.

I then built the following class to serve as a <saveUI> pipeline processor (this pipeline is triggered when someone saves an Item in the Sitecore client):

using Sitecore; using Sitecore.Data; using Sitecore.Data.Items; using Sitecore.Diagnostics; using Sitecore.Pipelines; using Sitecore.Pipelines.Save; using Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems; namespace Sitecore.Sandbox.Pipelines.SaveUI { public class ExpandNewStandardValuesTokens { private string ExpandNewTokensOnAllItemsPipeline { get; set; } public void Process(SaveArgs args) { Assert.IsNotNullOrEmpty(ExpandNewTokensOnAllItemsPipeline, "ExpandNewTokensOnAllItemsPipeline must be set in configuration!"); foreach (SaveArgs.SaveItem saveItem in args.Items) { Item item = GetItem(saveItem); if(IsStandardValues(item)) { ExpandNewTokensOnAllItems(item); } } } protected virtual Item GetItem(SaveArgs.SaveItem saveItem) { Assert.ArgumentNotNull(saveItem, "saveItem"); return Client.ContentDatabase.Items[saveItem.ID, saveItem.Language, saveItem.Version]; } protected virtual bool IsStandardValues(Item item) { Assert.ArgumentNotNull(item, "item"); return StandardValuesManager.IsStandardValuesHolder(item); } protected virtual void ExpandNewTokensOnAllItems(Item standardValues) { CorePipeline.Run(ExpandNewTokensOnAllItemsPipeline, new ExpandNewTokensOnAllItemsArgs { StandardValuesItem = standardValues }); } } }

The code above invokes the custom pipeline when the Item being saved is a Standard Values Item — the Standard Values Item is passed to the pipeline via a new ExpandNewTokensOnAllItemsArgs instance.

I then glued all of the pieces above together in the following patch configuration file:

<?xml version="1.0" encoding="utf-8" ?> <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pipelines> <expandNewTokensOnAllItems> <processor type="Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems.EnsureStandardValues, Sitecore.Sandbox" /> <processor type="Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems.EnsureUnexpandedTokens, Sitecore.Sandbox"> <Tokens hint="list"> <Token>$name</Token> <Token>$id</Token> <Token>$parentid</Token> <Token>$parentname</Token> <Token>$date</Token> <Token>$time</Token> <Token>$now</Token> </Tokens> </processor> <processor type="Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems.CollectAllItems, Sitecore.Sandbox" /> <processor type="Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems.FilterStandardValuesItem, Sitecore.Sandbox" /> <processor type="Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems.ExpandTokens, Sitecore.Sandbox" /> </expandNewTokensOnAllItems> </pipelines> <processors> <saveUI> <processor patch:before="saveUI/processor[@type='Sitecore.Pipelines.Save.Save, Sitecore.Kernel']" mode="on" type="Sitecore.Sandbox.Pipelines.SaveUI.ExpandNewStandardValuesTokens"> <ExpandNewTokensOnAllItemsPipeline>expandNewTokensOnAllItems</ExpandNewTokensOnAllItemsPipeline> </processor> </saveUI> </processors> </sitecore> </configuration>

Let’s see this in action!

I added three new fields to a template, and added some tokens in them:

After clicking save, I navigated to one of the content Items that use this Template:

As you can see, the tokens were expanded. 🙂

If you have any thoughts on this, please drop a comment.