Home » Tokens
Category Archives: Tokens
Expand New Tokens Added to Standard Values on All Items Using Its Template in Sitecore
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.
Expand Tokens on Items Using a Sitecore PowerShell Extensions Toolbox Script
Last Wednesday I had the opportunity of presenting Sitecore PowerShell Extensions (SPE) at the Milwaukee Sitecore Meetup. During this presentation, I demonstrated how quickly and easily one can add, execute and reuse PowerShell scripts in SPE, and I did this using version 3.0 of SPE on Sitecore XP 8.
During one segment of the presentation, I shared how one can seamlessly add scripts to the SPE Toolbox — a repository of utility scripts if you will — and used the following script when showing this:
<# .NAME Expand tokens in all content items .SYNOPSIS Expand tokens in all fields in all content items .NOTES Mike Reynolds #> $items = Get-ChildItem -Path "master:\sitecore\content" -Recurse $items | ForEach-Object { $_.Fields.ReadAll() } $items | Expand-Token Close-Window
The script above grabs all descendant Items under /sitecore/content/; iterates over them to ensure all field values are available — the ReadAll() method on the FieldCollection instance will ensure values from fields on the Item’s template’s Standard Values Item are pulled in for processing; and sends in these Items into the Expand-Token commandlet which comes “out of the box” with SPE.
The script also closes the processing dialog.
I then saved the above script into my Toolbox library in my SPE module:
Let’s try this out. Let’s find some Items with tokens in some fields. It looks like the Home Item has some:
Here’s another Item that also has tokens:
Let’s go to the SPE Toolbox, and click on our Toolbox utility:
As you can see the tokens were expanded on the Home Item:
Tokens were also expanded on the descendant Item:
If you have any thoughts and/or suggestions on this, or have ideas for other SPE Toolbox scripts, please drop a comment.
If you would like to watch the Milwaukee Sitecore Meetup presentation where I showed the above — you’ll also get to see some epic Sitecore PowerShell Extensions stuff from Adam Brauer, Senior Product Engineer at Active Commerce, in this presentation as well — have a look below:
If you would like to see another example of adding a script to the SPE Toolbox, please see my previous post on this subject.
Until next time, have a scriptaculous day!
Make Bulk Item Updates using Sitecore PowerShell Extensions
In my Sitecore PowerShell Extensions presentation at the Sitecore User Group Conference 2014, I demonstrated how simple it is to make bulk Item updates — perform the same update to multiple Sitecore items — using a simple PowerShell script, and thought I would write down what I had shown.
Sadly, I do not remember which script I had shared with the audience — the scratchpad text file I referenced during my presentation contains multiple scripts for making bulk updates to Items (if you attended my talk, and remember exactly what I had shown, please drop a comment).
Since I cannot recall which script I had shown — please forgive me š — let’s look at the following PowerShell script (this might be the script I had shown):
@(Get-Item .) + (Get-ChildItem -r .) | ForEach-Object { Expand-Token $_ }
This script grabs the context Item — this is denoted by a period — within the PowerShell ISE via the Get-Item command, and puts it into an array so that we can concatenate it with an array of all of its descendants — this is returned by the Get-ChildItem command with the -r parameter (r stands for recursive). The script then iterates over all Items in the resulting array, passes each to the Expand-Token command — this command is offered “out of the box” in Sitecore PowerShell Extensions — which expands tokens in every field on the Item.
Let’s see this in action!
My home Item has some tokens in its Title field:
One of its descendants also has tokens in its Title field:
I opened up the PowerShell ISE, wrote my script, and executed:
As you can see, the tokens on the home Item were expanded:
They were also expanded on the home Item’s descendant:
If you have any thoughts or questions on this, please share in a comment.
Expand Tokens on Sitecore Items Using a Custom Command in Sitecore PowerShell Extensions
During my Sitecore from the Command Line presentation at the Sitecore User Group – New England, I had shown attendees how they could go about adding a custom command into the Sitecore PowerShell Extensions module.
This blog post shows what I had presented — although the code in this post is an improved version over what I had presented at my talk. Many thanks to Sitecore MVP Adam Najmanowicz for helping me make this code better!
The following command will expand “out of the box” tokens in all fields of a supplied Sitecore item — check out Expand Tokens on Sitecore Items Using a Custom Command in Revolver where I discuss the problem commands like this address, and this article by Sitecore MVP Jens Mikkelsen which lists “out of the box” tokens available in Sitecore:
using System; using System.Management.Automation; using Sitecore.Configuration; using Sitecore.Data; using Sitecore.Data.Items; using Cognifide.PowerShell.PowerShellIntegrations.Commandlets; namespace CommandLineExtensions.PowerShell.Commandlets { [Cmdlet("Expand", "Token")] [OutputType(new[] { typeof(Item) })] public class ExpandTokenCommand : BaseCommand { private static readonly MasterVariablesReplacer TokenReplacer = Factory.GetMasterVariablesReplacer(); [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] public Item Item { get; set; } protected override void ProcessRecord() { Item.Editing.BeginEdit(); try { TokenReplacer.ReplaceItem(Item); Item.Editing.EndEdit(); } catch (Exception ex) { Item.Editing.CancelEdit(); throw ex; } WriteItem(Item); } } }
The command above subclasses Cognifide.PowerShell.PowerShellIntegrations.Commandlets.BaseCommand — the base class for most (if not all) commands in Sitecore PowerShell Extensions.
An item is passed to the command via a parameter, and is magically set on the Item property of the command class instance.
The ValueFromPipeline parameter being set to “true” on the Item property’s Parameter attribute will allow for chaining of this command with others so that items can be fed into it via a pipe bridging the commands together in PowerShell.
An instance of the Sitecore.Data.MasterVariablesReplacer class — which is created by the GetMasterVariablesReplacer() method of the Sitecore.Configuration.Factory class based on the āMasterVariablesReplacerā setting of your Sitecore instanceās Web.config — is used to expand tokens on the supplied Sitecore item after the item was flagged for editing.
Once tokens have been expanded on the item — or not in the event an exception is encountered — the item is written to the Results window via the WriteItem method which is defined in the BaseCommand class.
I then had to wire up the custom command via a patch configuration file:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <powershell> <commandlets> <add Name="Custom Commandlets" type="*, CommandLineExtensions" /> </commandlets> </powershell> </sitecore> </configuration>
Let’s take this custom command for a spin.
I created a bunch of test items, and set tokens in their fields. I then selected the following page at random for testing:
I opened up the Integrated Scripting Environment of Sitecore PowerShell Extensions, typed in the following PowerShell code, and executed by pressing Ctrl-E:
As you can see tokens were expanded on the Page One item:
How about expanding tokens on all descendants of the Home item? Let’s see an example of how we can do that.
I chose the following content item — a grandchild of the Home item — for testing:
I switched back over to the Integrated Scripting Environment, wrote the following code for testing — the Get-ChildItem command with the -r parameter (this means do this recursively) will grab all descendants of the Home item, and pipe each item in the result set into the Expand-Token command — and clicked the Execute button:
I then went back to the grandchild item of the Home page in the content tree, and saw that tokens were expanded in its fields:
If you have any thoughts or comments on this, or ideas for new commands in Sitecore PowerShell Extensions, please share in a comment.
Until next time, have a scriptolicious day!
Expand Tokens on Sitecore Items Using a Custom Command in Revolver
On September 18, 2013, I presented Sitecore from the Command Line at the Sitecore User Group – New England.
During my presentation, I gave an example of creating a custom command in Revolver — the first scripting platform for Sitecore built by Alistair Deneys — and thought I would write something up for those who had missed the presentation, or wanted to revisit what I had shown.
One thing that plagues some Sitecore developers — if you disagree please leave a comment — is not having a nice way to expand tokens on items when tokens are added to Standard Values after items had been created previously.
Newly added tokens “bleed” into preexisting items’ fields, and I’ve seen developers perform crazy feats of acrobatic gymnastics to expand them — writing a standalone web form to recursive crawl the content tree to expand these is such an example (take a look at Empower Your Content Authors to Expand Standard Values Tokens in the Sitecore Client where I offer an alternative way to expand tokens on content items).
The following custom Revolver command will expand tokens on a supplied Sitecore item, and help out on the front of expanding newly added tokens on preexisting items:
using System; using Sitecore.Configuration; using System.Linq; using Sitecore.Data; using Sitecore.Data.Items; using Revolver.Core; using Revolver.Core.Commands; namespace CommandLineExtensions.Revolver.Commands { public class ExpandTokensCommand : BaseCommand { private static readonly MasterVariablesReplacer TokenReplacer = Factory.GetMasterVariablesReplacer(); public override string Description() { return "Expand tokens on an item"; } public override HelpDetails Help() { HelpDetails details = new HelpDetails { Description = Description(), Usage = "<cmd> [path]" }; details.AddExample("<cmd>"); details.AddExample("<cmd> /item1/item2"); return details; } public override CommandResult Run(string[] args) { string path = string.Empty; if (args.Any()) { path = args.FirstOrDefault(); } using (new ContextSwitcher(Context, path)) { if (!Context.LastGoodPath.EndsWith(path, StringComparison.CurrentCultureIgnoreCase)) { return new CommandResult ( CommandStatus.Failure, string.Format("Failed to expand tokens on item {0}\nReason:\n\n An item does not exist at that location!", path) ); } CommandResult result; Item item = Context.CurrentItem; item.Editing.BeginEdit(); try { TokenReplacer.ReplaceItem(item); result = new CommandResult(CommandStatus.Success, string.Concat("Expanded tokens on item ", Context.LastGoodPath)); item.Editing.EndEdit(); } catch (Exception ex) { item.Editing.CancelEdit(); result = new CommandResult(CommandStatus.Failure, string.Format("Failed to expand tokens on item {0}\nReason:\n\n{1}", path, ex)); } return result; } } } }
Tokens are expanded using an instance of the Sitecore.Data.MasterVariablesReplacer class — you can roll your own, and wire it up in the “MasterVariablesReplacer” setting of your Sitecore instance’s Web.config — which is provided by Sitecore.Configuration.Factory.GetMasterVariablesReplacer().
All custom commands in Revolver must implement the Revolver.Core.ICommand interface. I subclassed Revolver.Core.Commands.BaseCommand — which does implement this interface — since it seemed like the right thing to do given that all “out of the box” commands I saw in Revolver were subclassing it, and then implemented the Description(), Help() and Run() abstract methods.
I then had to bind the custom command to a new name — I chose “et” for “Expand Tokens”:
@echooff @stoponerror bind CommandLineExtensions.Revolver.Commands.ExpandTokensCommand,CommandLineExtensions et @echoon
Since it wouldn’t be efficient to type and run this bind script every time I want to use the “et” command, I added it into a startup script in the core database:
I then had to create a user script for the startup script to run. I chose the Everyone role here for demonstration purposes:
The above startup script will be invoked when Revolver is opened, and our custom command will be bound.
Let’s see all of the above in action.
I added some tokens in my home item:
I then opened up Revolver, navigated to /sitecore/content, and ran the custom command on the home item:
As you can see the tokens were expanded:
You might be thinking “that’s wonderful Mike — except now I have to navigate to every item in my content tree using Revolver, and then run this custom command on it”.
Well, I do have a solution for this: a custom script that grabs an item and all of its descendants using a Sitecore query, and passes them to the custom command to expand tokens:
@echooff @stoponerror if ($1$ = \$1\$) (exit (Missing required parameter path)) @echoon query -ns $1$/descendant-or-self::* et
I put this script in the core database, and named it “etr” for “Expand Tokens Recursively”:
I navigated to a descendant of /sitecore/content/home, and see that it has some unexpanded tokens on it:
I went back to Revolver, and ran the “etr” command on the home item:
As you can see tokens were expanded on the descendant item:
If you have any thoughts on this, or have ideas for other custom commands in Revolver, please share in a comment.