Home » Sitecore Client (Page 3)

Category Archives: Sitecore Client

Bucket Items in Sitecore using a Custom Commandlet in Sitecore PowerShell Extensions

Last Wednesday I had the privilege to present Sitecore PowerShell Extensions (SPE) at the Milwaukee Sitecore Meetup. During my presentation, I demonstrated how easy it is to add, execute and reuse PowerShell scripts in SPE, and I showcased version 3.0 of SPE on Sitecore XP 8.

Unfortunately, I ran out of time before showing how one can go about creating a custom commandlet in SPE, and hope to make it up to everyone by sharing the commandlet I wrote for the presentation in this post.

I wrote the following commandlet to convert an Item into an Item Bucket in Sitecore:

using System;
using System.Management.Automation;

using Sitecore.Data.Items;
using Sitecore.Shell.Framework.Commands;

using Cognifide.PowerShell.Commandlets;
using Cognifide.PowerShell.Commandlets.Interactive.Messages;

namespace Sitecore.Sandbox.SPE.Commandlets.Buckets
{
    [Cmdlet(VerbsData.ConvertTo, "Bucket"), OutputType(new Type[] { typeof(Item) })]
    public class ConvertToBucketCommand : BaseItemCommand
    {
        protected override void ProcessItem(Item item)
        {
            try
            {
                PutMessage(new ShellCommandInItemContextMessage(item, "item:bucket"));   
            }
            catch (Exception exception)
            {
                WriteError(new ErrorRecord(exception, "sitecore_new_bucket_error", ErrorCategory.NotSpecified, Item));
            }

            WriteItem(Item);
        }
    }
}

The above commandlet implements the ProcessItem() method — this method is declared abstract in one of the ancestor classes of the class above — and leverages the framework of SPE to invoke a Sheer UI command to bucket the Item passed to the method — one of the ancestor classes of this class passes the Item to be processed.

The above highlights how in SPE we are employing the Template method pattern for many “out of the box” commandlets. This involves inheriting from an abstract base class — Cognifide.PowerShell.Commandlets.BaseItemCommand in Cognifide.PowerShell.dll (this assembly comes with the SPE module) is an example of one of these base classes — and implementing methods that are defined as abstract. The parent or an ancestor class will do the brunt of the work behind the scenes, and use your method implementation for specifics.

As a side note, we also provide method hooks as well — these are virtual methods defined on a base or ancestor class — which you can override to change how they work to meet your particular needs.

I then wired the above up using a Sitecore include configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <powershell>
      <commandlets>
        <add Name="Custom Bucket Commandlets" type="*, Sitecore.Sandbox.SPE" />
      </commandlets>
    </powershell>
  </sitecore>
</configuration>

I deployed the above to my Sitecore instance; loaded up the Integrated Scripting Environment (ISE) in SPE; and saw that my commandlet was registered using the Control-Space shortcut key:

convert-to-bucket-ise-control-space

Let’s take this for a spin. Let’s convert the Home Item into an Item Bucket:

home-before-bucket

Here’s my script to do that:

ise-convert-home-bucket

I clicked the execute button, and then got this confirmation dialog:

ise-convert-home-bucket-confirm

I then clicked the “Ok” button and was immediately presented with this dialog:

ise-convert-home-bucket-processing

As you can see it worked! The Home Item in my content tree is now an Item Bucket:

home-after-bucket

If you have any thoughts on this or ideas for other custom commandlets for SPE, please share in a comment.

If you would like to watch the Milwaukee Sitecore Meetup presentation where I showcased Sitecore PowerShell Extensions — and as a bonus you’ll also get to see some real-life application of SPE from Adam Brauer, Senior Product Engineer at Active Commerce, in this presentation as well — it has been recorded for posterity, and you can watch it here:

Until next time, stay curious, keep experimenting, and let’s keep on sharing all the Sitecore things!

Bucket and Unbucket Items via Custom Item Context Menu Options Using Sitecore PowerShell Extensions

Last Wednesday I was honored to present Sitecore PowerShell Extensions (SPE) at the Milwaukee Sitecore Meetup. My presentation was all about how easy it is to add, execute and reuse PowerShell scripts in SPE, and I showcased this using version 3.0 of SPE on Sitecore XP 8.

During the presentation, I demonstrated how one can go about adding custom Item Context Menu options using SPE, and did so with the following scripts which bucket and unbucket Sitecore Items:

 <#
    .NAME 
        Convert To Item Bucket

    .SYNOPSIS
        Converts the context item to an Item Bucket
     
    .NOTES
        Mike Reynolds
#>
 
$item = Get-Item .

if($item."__Is Bucket" -eq "1") {
   return 
}

Get-ChildItem . -Recurse | %{ $_.__Bucketable = "1" }
Invoke-ShellCommand -Name "item:bucket" -Item $item
Close-Window

The above PowerShell script basically checks to see if an Item is an Item Bucket and does nothing if it is. If the Item is not an Item Bucket, we make sure all sub-items are bucketable — we just tick the “__Bucketable” Checkbox field on them — and invoke the Sheer UI command for converting an Item into an Item Bucket — this is done via the Invoke-ShellCommand commandlet that ships with SPE — and then close the script execution dialog users are presented with when SPE executes a script in the Item Context Menu.

The following script does the exact opposite of the script above — it converts an Item Bucket back to a regular Sitecore Item using the Sheer UI command for unbucketing:

<#
    .NAME 
        Convert Item Bucket To A Regular Item

    .SYNOPSIS
        Converts an Item Bucket to a regular Item
     
    .NOTES
        Mike Reynolds
#>

$item = Get-Item .

if($item."__Is Bucket" -ne "1") {
   return 
}

Invoke-ShellCommand -Name "item:unbucket" -Item $item
Close-Window
 

I then saved the above scripts to a Context Menu integration point in a SPE module I created during the presentation using the SPE Integrated Scripting Environment (ISE) (to get to the ISE in SPE, go to Sitecore ==> Development Tools ==> PowerShell ISE in the Sitecore Start menu of the Sitecore Desktop):

Saved-context-menu-script

I’ve omitted screenshots on saving the “Convert Item Bucket To A Regular Item” script for brevity.

I then set rules in the “Show if rules are met or not defined” field on both Context Menu script Items — we only want the “Convert To Item Bucket” Context Menu option to show when the Item isn’t an Item Bucket, and the “Convert Item Bucket To A Regular Item” Context Menu option to show when the Item it is an Item Bucket:

The “Convert To Item Bucket” Context Menu item (this Item was saved to /sitecore/system/Modules/PowerShell/Script Library/SitecoreUG Module/Content Editor/Context Menu/Convert To Item Bucket in my Sitecore instance):

set-rules-convert-to-bucket

The “Convert Item Bucket To A Regular Item” Context Menu item:

set-rules-convert-to-unbucket

After saving the above, I navigated to an Item in my content tree that has sub-items, right-clicked on it, and clicked on the “Convert To Item Bucket” Context Menu option:

convert-to-bucket-right-click

I was then presented with a confirmation dialog:

convert-to-bucket-confirm

As you can see the Item is now an Item Bucket:

item-is-a-bucket

I right-clicked on the Item again, and clicked on the “Convert Item Bucket To A Regular Item” Context Menu option:

convert-to-unbucket-right-click

I was presented with another confirmation dialog:

convert-to-ubucket-confirm

As you can see the Item is no longer an Item Bucket:

no-longer-a-bucket

If you have any thoughts on this or ideas for other Context Menu PowerShell scripts for SPE, 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 cool Sitecore PowerShell Extensions stuff from Adam Brauer, Senior Product Engineer at Active Commerce, in this presentation as well — have a look below:

Until next time, have a Sitecoretastic day!

Warn Content Authors on Having Too Many Sub-items Under an Item in Sitecore

In my previous post, I shared two field validators that will warn content authors/editors when they link to Items without presentation in Internal and General Link fields.

When I was building those two validators, I came up with another validator idea: how about warning content authors/editors when they have too many sub-items under an Item?

To accomplish this, I came up with the following class that serves as an Item validator:

using System;
using System.Runtime.Serialization;

using Sitecore.Buckets.Managers;
using Sitecore.Data.Items;
using Sitecore.Data.Validators;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators
{
    [Serializable]
    public class ItemHasTooManySubitemsValidator : StandardValidator
    {
        public override string Name
        {
            get
            {
                return Parameters["Name"];
            }
        }

        private int MaxNumberOfSubitems
        {
            get
            {
                int maxNumberOfSubitems;
                if (!int.TryParse(Parameters["MaxNumberOfSubitems"], out maxNumberOfSubitems))
                {
                    return 0;
                }

                return maxNumberOfSubitems;
            }
        }

        public ItemHasTooManySubitemsValidator()
        {
        }

        public ItemHasTooManySubitemsValidator(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        protected override ValidatorResult Evaluate()
        {
            Item item = GetItem();
            if(IsValid(item))
            {
                return ValidatorResult.Valid;
            }

            Text = GetErrorMessage(item);
            return GetFailedResult(ValidatorResult.Suggestion);
        }

        protected virtual bool IsValid(Item item)
        {
            return MaxNumberOfSubitems < 1
                   || item == null 
                   || IsBucket(item)
                   || !item.HasChildren
                   || item.Children.Count <= MaxNumberOfSubitems;
        }

        protected virtual bool IsBucket(Item item)
        {
            if(item == null)
            {
                return false;
            }

            return BucketManager.IsBucket(item);
        }

        protected virtual string GetErrorMessage(Item item)
        {
            string message = Parameters["ErrorMessage"];
            if (string.IsNullOrWhiteSpace(message))
            {
                return string.Empty;
            }

            return GetText(message, new[] { item.DisplayName });
        }

        protected override ValidatorResult GetMaxValidatorResult()
        {
            return base.GetFailedResult(ValidatorResult.Suggestion);
        }
    }
}

The class above inherits from Sitecore.Data.Validators.StandardValidator in Sitecore.Kernel.dll — this is the base class which most validators in Sitecore inherit from — and ascertains whether the Item being validated has too many sub-items underneath it (the maximum number of allowed sub-items is passed to the class’ instance via the MaxNumberOfSubitems parameter set on the Validation Rule item — these have the /sitecore/templates/System/Validation/Validation Rule template — in Sitecore which is shown later in this post).

If the Item being validated has more sub-items than is allowed and isn’t an Item Bucket, the validator’s error message is set on the Text property of the class instance — the error message is passed via a parameter on the Validation Rule item — and a ValidatorResult instance is returned to the caller.

I then wired up the above class in Sitecore on a Validation Rule item, and set the maximum number of allowed sub-items to be four for testing (no, I’m not going to create a gazillion Items to test this):

create-validator-in-sitecore

Now that we have the Validation Rule Item in place, we should probably give content authors/editors the ability to remedy having too many sub-items under an Item.

How?

Let’s give them the ability to convert the Item into an Item Bucket. I created the following Menu item — this has the template of /sitecore/templates/System/Menus/Menu item — to empower content authors/editors on making this conversion:

covert-to-item-bucket-menu-item

I then had to set up my Sample Item template to be bucketable since we are giving the ability to bucket Items with this template:

sample-item-bucketable

I then mapped the Item validator to the Standard Values item of my Sample Item template:

item-validator-on-standard-values

For testing, I created some Items underneath another Item:

item-with-four-items

As you can see, we haven’t exceeded the maximum number of 4 quite yet.

I then created a fifth item, and was presented with a validation warning:

item-with-five-subitems

I right clicked on the square in the Validation Bar, and was presented with some options:

item-with-five-subitems-convert-to-bucket

I clicked on “Convert to Item Bucket”, and then saw a magical progress dialog followed by this:

item-is-now-a-bucket

If you have any thoughts on this, or ideas for other Item validators, please drop a comment.

Warn Content Authors of Linking to Items With No Presentation using Custom Field Validators in Sitecore

The other day John West, CTO of Sitecore USA, published his 500th blog post — quite an epic feat if you ask me — where he built a custom field validator that checks whether external links in the Rich Text field resolve:

This got me thinking: what other types of field validators might be useful?

I pondered over this for the past couple of days, and couldn’t think of anything useful but finally did come up with an idea this morning (out of the blue I might add): how about field validators that check to see whether Items linked in General and Internal Link fields have presentation?

After searching through the library of field validators available in Sitecore — I did this to make sure I wouldn’t be wasting my time given that Sitecore offers a lot of field validators “out of the box” (these live under /sitecore/system/Settings/Validation Rules/Field Rules in the master database), so I suggest having a look through these before building a custom one — I came up with the following solution that employs the Template method design pattern:

using System;
using System.Runtime.Serialization;

using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Data.Validators;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.HasPresentation;

namespace Sitecore.Sandbox.Data.Validators.FieldValidators
{
    public abstract class ReferencedItemHasPresentationValidator : StandardValidator
    {
        public override string Name
        {
            get
            {
                return Parameters["Name"];
            }
        }

        public ReferencedItemHasPresentationValidator()
        {
        }

        public ReferencedItemHasPresentationValidator(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        protected override ValidatorResult Evaluate()
        {
            Item linkedItem = GetReferencedItem();
            if (linkedItem == null || HasPresentation(linkedItem))
            {
                return ValidatorResult.Valid;
            }

            Text = GetErrorMessage();
            return GetFailedResult(ValidatorResult.Error);
        }

        protected virtual bool HasPresentation(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return HasPresentationPipeline.Run(item);
        }

        protected abstract Item GetReferencedItem();

        protected virtual string GetErrorMessage()
        {
            string message = Parameters["ErrorMessage"];
            if (string.IsNullOrWhiteSpace(message))
            {
                return string.Empty;
            }

            return GetText(ExpandTokens(message), new[] { GetFieldDisplayName() });
        }

        protected override ValidatorResult GetMaxValidatorResult()
        {
            return GetFailedResult(ValidatorResult.Error);
        }

        protected virtual string ExpandTokens(string value)
        {
            if(string.IsNullOrWhiteSpace(value))
            {
                return value;
            }

            string valueExpanded = value;
            Field field = GetField();
            if(field != null)
            {
                valueExpanded = valueExpanded.Replace("$fieldName", field.Name);
            }

            return valueExpanded;
        }
    }
}

The above abstract class inherits from Sitecore.Data.Validators.StandardValidator in Sitecore.Kernel.dll — this is the base class which most validators in Sitecore inherit from — and checks to see if the Item referenced in the field has presentation (this check is done in the HasPresentation() method which basically delegates to the Run() method on the Sitecore.Pipelines.HasPresentation.HasPresentationPipeline class).

The referenced Item is returned by the GetReferencedItem() method which must be defined by subclasses of the above class.

Further, I’m passing in the validator’s name and error message through parameters (the error message allows for $fieldName as a token, and the ExpandTokens() method replaces this token with the name of the field being validated).

I then created a subclass of the above to return the Item referenced in an Internal Link field:

using System;
using System.Runtime.Serialization;

using Sitecore.Data.Fields;
using Sitecore.Data.Items;

namespace Sitecore.Sandbox.Data.Validators.FieldValidators
{
    [Serializable]
    public class InternalLinkItemHasPresentationValidator : ReferencedItemHasPresentationValidator
    {
        public InternalLinkItemHasPresentationValidator()
        {
        }

        public InternalLinkItemHasPresentationValidator(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        protected override Item GetReferencedItem()
        {
            InternalLinkField internalLinkField = GetField();
            if (internalLinkField == null)
            {
                return null;
            }
            
            return internalLinkField.TargetItem;
        }
    }
}

Nothing magical is happening in the above class. The GetReferencedItem() method is casting the field to a Sitecore.Data.Fields.InternalLinkField instance, and returns the value of its TargetItem property.

Now that we have a class to handle Items referenced in Internal Link fields, we need another for General Link fields:

using System;
using System.Runtime.Serialization;

using Sitecore.Data.Fields;
using Sitecore.Data.Items;

namespace Sitecore.Sandbox.Data.Validators.FieldValidators
{
    [Serializable]
    public class GeneralLinkItemHasPresentationValidator : ReferencedItemHasPresentationValidator
    {
        public GeneralLinkItemHasPresentationValidator()
        {
        }

        public GeneralLinkItemHasPresentationValidator(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        protected override Item GetReferencedItem()
        {
            LinkField linkField = GetField();
            if (linkField == null)
            {
                return null;
            }
            
            return linkField.TargetItem;
        }
    }
}

The GetReferencedItem() method in the GeneralLinkItemHasPresentationValidator class above does virtually the same thing as the same method in the InternalLinkItemHasPresentationValidator class. The only difference is the GetReferencedItem() method in the class above is casting the field to a Sitecore.Data.Fields.LinkField instance, and returns the value of its TargetItem property.

I then had to map the above field validator classes to Validation Rule Items — these have the /sitecore/templates/System/Validation/Validation Rule template — in Sitecore:

The Internal Link field validator:

internal-link-validator

The General Link field validator:

general-link-validator-item

I then added two Internal Link fields on my Sample item template, and mapped the Internal Link field validator to them:

set-validator-on-internal-link-fields

I also created two General Link fields on my Sample item template, and mapped the General Link field validator to them:

set-validator-on-general-link-fields

Once I had the validators mapped to their specific fields, I went ahead and removed presentation from one of my test items:

no-presentation-bing

Before, the above item had these presentation components on it:

presentation-on-google-item

I then linked to my test items in my test fields. As you can see, there are errors on the “Bing” item which does not have presentation:

invalid-field-links

If you have any thoughts on this, or ideas for other field validators, please share in a comment.

Until next time, have a Sitecorelicious day! 😀

Add Scripts to the PowerShell Toolbox in Sitecore PowerShell Extensions

During our ‘Take charge of your Sitecore instance using Sitecore tools’ session at Sitecore Symposium 2014 Las Vegas, Sitecore MVP Sean Holmesby and I shared how easy it is to leverage/extend popular Sitecore development tools out there, and built up a fictitious Sitecore website where we pulled in #SitecoreSelfie Tweets.

The code that pulls in these Tweets is supposed to follow a naming convention where Tweet IDs are appended to Media Library Item names, as you can see here:

sean-profile-image

Sadly, right before our talk, I mistakenly 😉 made a code change which broke our naming convention for some images:

sean-selfie-image

Upon further investigation, we had discovered our issue was much larger than anticipated: all Selfie Media Library Item names do not end with their Tweet IDs:

no-tweet-ids

To fix this, I decided to create a PowerShell Toolbox script in Sitecore PowerShell Extensions using the following script:

<#
    .SYNOPSIS
        Rename selfie image items to include tweet ID where missing.
     
    .NOTES
        Mike Reynolds
#>
$items = Get-ChildItem -Path "master:\sitecore\content\Social-Media\Twitter\Tweets" -Recurse | Where-Object { $_.TemplateName -eq "Tweet" }

$changedItems = @()
foreach($item in $items) {
	$tweetID = $item["TweetID"]
	$selfieImageField = [Sitecore.Data.Fields.ImageField]$item.Fields["SelfieImage"]
	$selfieImage = $selfieImageField.MediaItem
	if($selfieImage -ne $null -and -not $selfieImage.Name.EndsWith($tweetID)) {
		$oldName = $selfieImage.Name
		$newName = $oldName + "_" + $tweetID
		$selfieImage.Editing.BeginEdit()
		$selfieImage.Name = $newName
		$selfieImage.Editing.EndEdit()
		
		$changedItem = New-Object PSObject -Property @{            
		    Icon = $selfieImage.Appearance.Icon
			OldName = $oldName
			NewName = $newName  
			Path = $selfieImage.Paths.Path
			Alt = $selfieImage["Alt"]
			Title = $selfieImage["Title"]
			Width = $selfieImage["Width"]
			Height = $selfieImage["Height"]
			MimeType = $selfieImage["Mime Type"]
			Size = $selfieImage["Size"]           
		}
		
		$changedItems += $changedItem
	}
}

if($changedItems.Count -gt 0) {
    $changedItems |
        Show-ListView -Property @{Label="Icon"; Expression={$_.Icon} },
            @{Label="Old Name"; Expression={$_.OldName} },
    		@{Label="New Name"; Expression={$_.NewName} },
    		@{Label="Path"; Expression={$_.Path} },
            @{Label="Alt"; Expression={$_.Alt} },
    		@{Label="Title"; Expression={$_.Title} },
            @{Label="Width"; Expression={$_.Width} },
            @{Label="Height"; Expression={$_.Height} },
            @{Label="Mime Type"; Expression={$_.MimeType} },
    		@{Label="Size"; Expression={$_.Size} }
} else {
    Show-Alert "There are no selfie image items missing tweet IDs in their name."
}
Close-Window

The above PowerShell script grabs all Tweet Items in Sitecore; ascertains whether referenced Selfie images in the Media Library — these are referenced in the “SelfieImage” field on the Tweet Items — end with the Tweet IDs of their referring Tweet Items (the Tweet ID is stored in a field on the Tweet Item); and renames the Selfie images to include their Tweet IDs if not. The script also launches a dialog showing the images that have changed.

To save the above script in the PowerShell Toolbox, I launched the PowerShell Integrated Scripting Environment (ISE) in Sitecore PowerShell Extensions:

powershell-ise-context-menu

I pasted in the above script, and saved it in the PowerShell Toolbox library:

toolbox-save-as

As you can see, our new script is in the PowerShell Toolbox:

new-script-in-toolbox

I then clicked the new PowerShell Toolbox option, and was presented with the following dialog:

selfie-toolbox-script-results

The above dialog gives information about the images along with their old and new Item names.

I then navigated to where these images live in the Media Library, and see that they were all renamed to include Tweet IDs:

selfie-images-tweet-ids

If you have any thoughts on this, or suggestions for other PowerShell Toolbox scripts, please share in a comment.

Until next time, have a #SitecoreSelfie type of day!

Clone Items using the Sitecore Item Web API

Yesterday, I had the privilege to present with Ben Lipson and Jamie Michalski, both of Velir, on the Sitecore Item Web API at the New England Sitecore User Group — if you want to see us in action, check out the recording of our presentation!

Plus, my slides are available here!

During my presentation, I demonstrated how easy it is to customize the Sitecore Item API by adding a custom <itemWebApiRequest> pipeline processor, and a custom pipeline to handle a cloning request — for another example on adding a custom <itemWebApiRequest> pipeline processor, and another pipeline to execute a different custom operation, have a look at this post where I show how to publish Items using the Sitecore Item Web API.

For any custom pipeline you build for the Sitecore Item Web API, you must define a Parameter Object that inherits from Sitecore.ItemWebApi.Pipelines.OperationArgs:

using System.Collections.Generic;

using Sitecore.Data.Items;

using Sitecore.ItemWebApi.Pipelines;

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Clone
{
    public class CloneArgs : OperationArgs
    {
        public CloneArgs(Item[] scope)
            : base(scope)
        {
        }

        public IEnumerable<Item> Destinations { get; set; }

        public bool IsRecursive { get; set; }

        public IEnumerable<Item> Clones { get; set; }
    }
}

I added three properties to the class above: a property to hold parent destinations for clones; another indicating whether all descendants should be cloned; and a property to hold a collection of the clones.

I then created a base class for processors of my custom pipeline for cloning:

using Sitecore.ItemWebApi.Pipelines;

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Clone
{
    public abstract class CloneProcessor : OperationProcessor<CloneArgs>
    {
        protected CloneProcessor()
        {
        }
    }
}

The above class inherits from Sitecore.ItemWebApi.Pipelines.OperationProcessor which is the base class for most Sitecore Item Web API pipelines.

The following class serves as one processor of my custom cloning pipeline:

using System.Collections.Generic;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Clone
{
    public class CloneItems : CloneProcessor
    {
        public override void Process(CloneArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.Scope, "args.Scope");
            Assert.ArgumentNotNull(args.Destinations, "args.Destinations");
            IList<Item> clones = new List<Item>();
            foreach (Item itemToClone in args.Scope)
            {
                foreach (Item destination in args.Destinations)
                {
                    clones.Add(CloneItem(itemToClone, destination, args.IsRecursive));
                }   
            }

            args.Clones = clones;
        }

        private Item CloneItem(Item item, Item destination, bool isRecursive)
        {
            Assert.ArgumentNotNull(item, "item");
            Assert.ArgumentNotNull(destination, "destination");
            return item.CloneTo(destination, isRecursive);
        }
    }
}

The class above iterates over all Items in scope — these are the Items being cloned — and clones all to the specified destinations (parent Items of the clones).

I then spun up the following class to serve as another processor in my custom cloning pipeline:

using System.Linq;

using Sitecore.Diagnostics;
using Sitecore.Pipelines;

using Sitecore.ItemWebApi.Pipelines.Read;

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Clone
{
    public class SetResult : CloneProcessor
    {
        public override void Process(CloneArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.Clones, "args.Clones");
            if (args.Result == null)
            {
                ReadArgs readArgs = new ReadArgs(args.Clones.ToArray());
                CorePipeline.Run("itemWebApiRead", readArgs);
                args.Result = readArgs.Result;
            }
        }
    }
}

The above class delegates to the <itemWebApiRead> pipeline which retrieves the clones from Sitecore, and stores these in the Parameter Object instance for the custom cloning pipeline.

In order to handle custom requests in the Sitecore Item Web API, you must create a custom <itemWebApiRequest> pipeline processor. I put together the following class to handle my cloning operation:

using System;
using System.Collections.Generic;
using System.Linq;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.ItemWebApi;
using Sitecore.ItemWebApi.Pipelines.Request;
using Sitecore.Pipelines;
using Sitecore.Text;
using Sitecore.Web;

using Sitecore.Sandbox.ItemWebApi.Pipelines.Clone;

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Request
{
    public class ResolveCloneAction : RequestProcessor
    {
        public override void Process(RequestArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNullOrEmpty(RequestMethod, "RequestMethod");
            Assert.ArgumentNotNullOrEmpty(MultipleItemsDelimiter, "MultipleItemsDelimiter");
            if (!ShouldProcessRequest(args))
            {
                return;
            }

            IEnumerable<Item> destinations = GetDestinationItems();
            if (!destinations.Any())
            {
                Logger.Warn("Cannot process clone action: there are no destination items!");
                return;
            }
            
            CloneArgs cloneArgs = new CloneArgs(args.Scope) 
            { 
                Destinations = destinations,
                IsRecursive = DoRecursiveCloning() 
            };
            CorePipeline.Run("itemWebApiClone", cloneArgs);
            args.Result = cloneArgs.Result;
        }

        private bool ShouldProcessRequest(RequestArgs args)
        {
            // Is this the request method we care about?
            if (!AreEqualIgnoreCase(args.Context.HttpContext.Request.HttpMethod, RequestMethod))
            {
                return false;
            }

            // are multiple axes supplied?
            if (WebUtil.GetQueryString("scope").Contains(MultipleItemsDelimiter))
            {
                Logger.Warn("Cannot process clone action: multiple axes detected!");
                return false;
            }

            // are there any items in scope?
            if (!args.Scope.Any())
            {
                Logger.Warn("Cannot process clone action: there are no items in Scope!");
                return false;
            }

            return true;
        }

        private static bool AreEqualIgnoreCase(string one, string two)
        {
            return string.Equals(one, two, StringComparison.CurrentCultureIgnoreCase);
        }
        
        private IEnumerable<Item> GetDestinationItems()
        {
            char delimiter;
            Assert.ArgumentCondition(char.TryParse(MultipleItemsDelimiter, out delimiter), "MultipleItemsDelimiter", "MultipleItemsDelimiter must be a single character!");
            ListString destinations = new ListString(WebUtil.GetQueryString("destinations"), delimiter);
            return (from destination in destinations
                    let destinationItem = GetItem(destination)
                    where destinationItem != null
                    select destinationItem).ToList();
        }

        private Item GetItem(string path)
        {
            try
            {
                return Sitecore.ItemWebApi.Context.Current.Database.Items[path];
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
            }

            return null;
        }

        private bool DoRecursiveCloning()
        {
            bool recursive;
            if (bool.TryParse(WebUtil.GetQueryString("recursive"), out recursive))
            {
                return recursive;
            }
            
            return false;
        }

        private string RequestMethod { get; set; }

        private string MultipleItemsDelimiter { get; set; }
    }
}

The above class ascertains whether it should handle the request: is the RequestMethod passed via configuration equal to the request method detected, and are there any Items in scope? I also built this processor to handle only one axe in order to keep the code simple.

Once the class determines it should handle the request, it grabs all destination Items from the context database — this is Sitecore.ItemWebApi.Context.Current.Database which is populated via the sc_database query string parameter passed via the request.

Further, the class above detects whether the cloning operation is recursive: should we clone all descendants of the Items in scope? This is also passed by a query string parameter.

I then glued everything together using the following Sitecore configuration file:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <itemWebApiClone>
        <processor type="Sitecore.Sandbox.ItemWebApi.Pipelines.Clone.CloneItems, Sitecore.Sandbox" />
        <processor type="Sitecore.Sandbox.ItemWebApi.Pipelines.Clone.SetResult, Sitecore.Sandbox" />
      </itemWebApiClone>
      <itemWebApiRequest>
        <processor patch:before="*[@type='Sitecore.ItemWebApi.Pipelines.Request.ResolveAction, Sitecore.ItemWebApi']"
                   type="Sitecore.Sandbox.ItemWebApi.Pipelines.Request.ResolveCloneAction, Sitecore.Sandbox">
          <RequestMethod>clone</RequestMethod>
          <MultipleItemsDelimiter>|</MultipleItemsDelimiter>
        </processor>
      </itemWebApiRequest>
    </pipelines>
  </sitecore>
</configuration>

Let’s clone the following Sitecore Item with descendants to two folders:

item-to-clone-destinations

In order to make this happen, I spun up the following HTML page using jQuery — no doubt the front-end gurus reading this are cringing when seeing the following code, but I am not much of a front-end developer:

<!DOCTYPE html>
<html lang="en">
	<head>
		<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
		<script src="//cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.min.js"></script>
		<script src="//cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.js"></script>
		<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.css" />
	</head>
	<body>
		<img width="400" style="display: block; margin-left: auto; margin-right: auto" src="/assets/img/clone-all-the-things.jpg" />
		<input type="button" id="button" value="Clone" style="width:100px;height:50px;font-size: 24px;" />
		<h2 id="confirmation" style="display: none;">Whoa! Something happened!</h2>
		<div id="working" style="display: none;"><img style="display: block; margin-left: auto; margin-right: auto" src="/assets/img/arrow-working.gif" /></div>
		<pre id="responseContainer" class="prettyprint" style="display: none;"><code id="response" class="language-javascript"></code></pre>
		<script type="text/javascript">
		$('#button').click(function() {
			$('#confirmation').hide();
			$('#responseContainer').hide();
			$('#working').show();
			$.ajax({
					type:'clone',
					url: "http://sandbox7/-/item/v1/sitecore/content/Home/Landing Page One?scope=s&destinations=/sitecore/content/Home/Clones|/sitecore/content/Home/Some More Clones&recursive=true&sc_database=master",
					headers:{
						"X-Scitemwebapi-Username":"extranet\\ItemWebAPI",
						"X-Scitemwebapi-Password":"1t3mW3bAP1"}
				}).done(function(response) {
					$('#confirmation').show();
					$('#response').html(JSON.stringify(response, null, 4));
					$('#working').hide();
					$('#responseContainer').show();
				});
		});
		</script>
	</body>
</html>

Plus, please pardon the hard-coded Sitecore credentials — I know you would never store a username and password in front-end code, right? 😉

The above HTML page looks like this on initial load:

clone-items-html-page-no-data

I then clicked the ‘Clone’ button, and saw the following:

cloned-items-html-page

As you can see, the target Item with descendants were cloned to the destination folders set in the jQuery above:

items-cloned-sitecore

If you have any thoughts on this, or have other ideas around customizing the Sitecore Item Web API, please share in a comment.

Chain Source and Clone Items Together in Sitecore Workflow

Two months ago, I worked on a project where I had to find a solution to chain source Items and their clones together in Sitecore workflow — don’t worry, the clone Items were “locked down” by being protected so content authors cannot make changes to content on the clones — the clones serve as content copies of their source Items for a multi-site solution in a single Sitecore instance.

After some research, a few mistakes — well, maybe more than a few 😉 — and massive help from Oleg Burov, Escalation Engineer at Sitecore USA, I put together a subclass of Sitecore.Workflows.Simple.Workflow — this lives in Sitecore.Kernel.dll — similar to the following:

using Sitecore.Data.Items;
using Sitecore.Workflows;
using Sitecore.Workflows.Simple;

namespace Sitecore.Sandbox.Workflows.Simple
{
    public class ChainSourceClonesWorkflow : Workflow 
    {
        public ChainSourceClonesWorkflow(string workflowID, WorkflowProvider owner)
            : base(workflowID, owner)
        {

        }
        public override WorkflowResult Execute(string commandID, Item item, string comments, bool allowUI, params object[] parameters)
        {
            WorkflowResult result = base.Execute(commandID, item, comments, allowUI, parameters);
            foreach (Item clone in item.GetClones())
            {
                base.Execute(commandID, clone, comments, allowUI, parameters);
            }

            return result;
        }
    }
}

The Execute() method above basically moves the passed Item through to the next workflow state by calling the base class’ Execute() method, and grabs all clones for the passed Item — each are also pushed through to the next workflow state via the base class’ Execute() method.

Workflow instances are created by Sitecore.Workflows.Simple.WorkflowProvider. I created the following class to return an instance of the ChainSourceClonesWorkflow class above:

using Sitecore.Workflows;
using Sitecore.Workflows.Simple;

namespace Sitecore.Sandbox.Workflows.Simple
{
    public class ChainSourceClonesWorkflowProvider : WorkflowProvider
    {
        public ChainSourceClonesWorkflowProvider(string databaseName, HistoryStore historyStore)
            : base(databaseName, historyStore)
        {
        }

        protected override IWorkflow InstantiateWorkflow(string workflowId, WorkflowProvider owner)
        {
            return new ChainSourceClonesWorkflow(workflowId, owner);
        }
    }
}

I then replaced the “out of the box” WorkflowProvider with the one defined above using the following configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <databases>
      <database id="master">
        <workflowProvider type="Sitecore.Workflows.Simple.WorkflowProvider, Sitecore.Kernel">
          <patch:attribute name="type">Sitecore.Sandbox.Workflows.Simple.ChainSourceClonesWorkflowProvider, Sitecore.Sandbox</patch:attribute>
        </workflowProvider>
      </database>
    </databases>
  </sitecore>
</configuration>

Let’s take this for a spin!

I first started with a source and clone in a “Draft” workflow state:

source-clone-draft

Let’s push the source — and hopefully clone 😉 — through to the next workflow state by submitting it:

source-clone-submit

As you can see, both are “Awaiting Approval”:

source-clone-awaiting-approval

Let’s approve them:

source-clone-approve

As you can see, both are approved:

source-clone-approved

If you have any thoughts or comments on this, or know of ways to improve the code above, please drop a comment.

Also, keep in mind the paradigm above is not ideal when content authors are able to make content changes to clones which differ from their source Items. In that scenario, it would be best to let source and clone Items’ workflow be independent.

Accept All Notifications on Clones of an Item using a Custom Command in Sitecore

As I was walking along a beach near my apartment tonight, I thought “wouldn’t it be nifty to have a button in the Sitecore ribbon to accept all notifications on clones of an Item instead of having to accept these manually on each clone?”

I immediately returned home, and whipped up the following command class:

using System.Collections.Generic;
using System.Linq;

using Sitecore.Data.Clones;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Shell.Framework.Commands;

namespace Sitecore.Sandbox.Shell.Framework.Commands
{
    public class AcceptAllNotificationsOnClones : Command
    {
        public override CommandState QueryState(CommandContext context)
        {
            Assert.ArgumentNotNull(context, "context");
            IEnumerable<Item> clones = GetClonesWithNotifications(GetItem(context));
            if(!clones.Any())
            {
                return CommandState.Hidden;
            }

            return CommandState.Enabled;
        }

        public override void Execute(CommandContext context)
        {
            Assert.ArgumentNotNull(context, "context");
            Item item = GetItem(context);
            IEnumerable<Item> clones = GetClonesWithNotifications(item);
            if(!clones.Any())
            {
                return;
            }

            foreach (Item clone in clones)
            {
                AcceptAllNotifications(item.Database.NotificationProvider, clone);
            }
        }

        protected virtual Item GetItem(CommandContext context)
        {
            Assert.ArgumentNotNull(context, "context");
            return context.Items.FirstOrDefault();
        }

        protected virtual IEnumerable<Item> GetClonesWithNotifications(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            IEnumerable<Item> clones = item.GetClones();
            if(!clones.Any())
            {
                return new List<Item>();
            }
            
            IEnumerable<Item> clonesWithNotifications = GetClonesWithNotifications(item.Database.NotificationProvider, clones);
            if(!clonesWithNotifications.Any())
            {
                return new List<Item>();
            }

            return clonesWithNotifications;
        }

        protected virtual IEnumerable<Item> GetClonesWithNotifications(NotificationProvider notificationProvider, IEnumerable<Item> clones)
        {
            Assert.ArgumentNotNull(notificationProvider, "notificationProvider");
            Assert.ArgumentNotNull(clones, "clones");
            return (from clone in clones
                    let notifications = notificationProvider.GetNotifications(clone)
                    where notifications.Any()
                    select clone).ToList();
        }

        protected virtual void AcceptAllNotifications(NotificationProvider notificationProvider, Item clone)
        {
            Assert.ArgumentNotNull(notificationProvider, "notificationProvider");
            Assert.ArgumentNotNull(clone, "clone");
            foreach (Notification notification in notificationProvider.GetNotifications(clone))
            {
                notification.Accept(clone);
            }
        }
    }
}

The code in the command above ensures the command is only visible when the selected Item in the Sitecore content tree has clones, and those clones have notifications — this visibility logic is contained in the QueryState() method.

When the command is invoked — this happens through the Execute() method — all clones with notifications of the selected Item are retrieved, and iterated over — each are passed to the AcceptAllNotifications() method which contains logic to accept all notifications on them via the Accept() method on a NotificationProvider instance: this NotificationProvider instance comes from the source Item’s Database property.

I then registered the above command class in Sitecore using the following configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <commands>
      <command name="item:AcceptAllNotificationsOnClones" type="Sitecore.Sandbox.Shell.Framework.Commands.AcceptAllNotificationsOnClones, Sitecore.Sandbox"/>
    </commands>
  </sitecore>
</configuration>

We need a way to invoke this command. I created a new button to go into the ‘Item Clones’ chunk in the ribbon:

accept-notifications-on-clones-button-core

Let’s take this for a test drive!

I first created some clones:

created-clones

I then changed a field value on one of those clones:

changed-field-value-on-clone

On the clone’s source Item, I changed the same field’s value with something completely different, and added a new child item — the new button appeared after saving the Item:

new-child-item-field-value-change

Now, the clone has notifications on it:

notifications-on-clone

I went back to the source Item, clicked the ‘Accept Notifications On Clones’ button in the ribbon, and navigated back to the clone:

notifications-accepted

As you can see, the notifications were accepted.

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

Create a Custom Report in Sitecore PowerShell Extensions

During my Sitecore PowerShell Extensions presentation at the Sitecore User Group Conference 2014, I showcased a custom report I had scripted using the Sitecore PowerShell Extensions module, and thought I would jot down what I had shown coupled with some steps on how you could go about creating your own custom report.

I had shown the audience the following PowerShell script:

<#
    .SYNOPSIS
        Lists all images with an empty Alt field.
    
    .NOTES
        Mike Reynolds
#>

function Get-ImageItemNoAltText {    
    $items = Get-ChildItem -Path "master:\sitecore\media library\images" -Recurse | Where-Object { $_.Fields["Alt"] -ne $null }
    
    foreach($item in $items) {
        if($item."Alt" -eq '') {
            $item
        }
    }
}

$items = Get-ImageItemNoAltText

if($items.Count -eq 0) {
    Show-Alert "There are no images with an empty Alt field."
} else {
    $props = @{
        InfoTitle = "Images with an empty Alt field"
        InfoDescription = "Lists all images with an empty Alt field."
        PageSize = 25
    }
    
    $items |
        Show-ListView @props -Property @{Label="Name"; Expression={$_.DisplayName} },
            @{Label="Updated"; Expression={$_.__Updated} },
            @{Label="Updated by"; Expression={$_."__Updated by"} },
            @{Label="Created"; Expression={$_.__Created} },
            @{Label="Created by"; Expression={$_."__Created by"} },
            @{Label="Path"; Expression={$_.ItemPath} }
}
Close-Window

I modeled the above script after the “out of the box” ‘Unused media items’ report but made some changes: it grabs all media library items recursively under /sitecore/Media Library/Images — you could definitely change this to /sitecore/Media Library to get all images outside of the Images folder — in Sitecore that have an Alt field, and that Alt field’s value is equal to the empty string.

I then tested — yes, I do test my code, don’t you 😉 — and saved my report using the PowerShell ISE:

custom-spe-report-images-no-alt-text

The report was saved in this Item created just for it:

custom-spe-report-images-no-alt-text-location

Let’s see this in action!

I went to Sitecore –> Reporting Tools –> PowerShell Reports –> Mikes Media Audit, and clicked on the new report:

went-to-custom-report

After running the report, I was presented with this dialog containing the results:

images-with-no-alt-text-report-results

I then clicked on the first row of the report, and was brought to an image with an empty Alt field:

go-to-an-image-no-alt-text

If you have any thoughts on this, or would like to see additional reports in Sitecore PowerShell Extensions, please share in a comment.

Launch PowerShell Scripts in the Item Context Menu using Sitecore PowerShell Extensions

Last week during my Sitecore PowerShell Extensions presentation at the Sitecore User Group Conference 2014 — a conference held in Utrecht, Netherlands — I demonstrated how to invoke PowerShell scripts from the Item context menu in Sitecore, and felt I should capture what I had shown in a blog post — yes, this is indeed that blog post. 😉

During that piece of my presentation, I shared the following PowerShell script to expands tokens in fields of a Sitecore item (if you want to learn more about tokens in Sitecore, please take a look at John West’s post about them, and also be aware that one can also invoke the Expand-Token PowerShell command that comes with Sitecore PowerShell Extensions to expand tokens on Sitecore items — this makes things a whole lot easier 😉 ):

$item = Get-Item .
$tokenReplacer = [Sitecore.Configuration.Factory]::GetMasterVariablesReplacer()
$item.Editing.BeginEdit()
$tokenReplacer.ReplaceItem($item)
$item.Editing.EndEdit()
Close-Window

The script above calls Sitecore.Configuration.Factory.GetMasterVariablesReplacer() for an instance of the MasterVariablesReplacer class — which is defined and can be overridden in the “MasterVariablesReplacer” setting in your Sitecore instance’s Web.config — and passes the context item — this is denote by a period — to the MasterVariablesReplacer instance’s ReplaceItem() method after the item has been put into editing mode.

Once the Item has been processed, it is taken out of editing mode.

So how do we save this script so that we can use it in the Item context menu? The following screenshot walks you through the steps to do just that:

item-context-menu-powershell-ise

The script is saved to an Item created by the dialog above:

expand-tokens-item

Let’s test this out!

I selected an Item with unexpanded tokens:

home-tokens-to-expand

I then launched its Item context menu, and clicked the option we created to ‘Expand Tokens’:

home-item-context-menu-expand-tokens

As you can see the tokens were expanded:

home-tokens-expanded

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

Until next time, have a scriptolicious day 😉