Home » Clones

Category Archives: Clones

Advertisements

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.

Advertisements

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.

Rename Sitecore Clones When Renaming Their Source Item

Earlier today I discovered that clones in Sitecore are not renamed when their source Items are renamed — I’m baffled over how I have not noticed this before since I’ve been using Sitecore clones for a while now :-/

I’ve created some clones in my Sitecore instance to illustrate:

clones-not-renamed-yet

I then initiated the process for renaming the source item:

clones-renaming

As you can see the clones were not renamed:

clones-not-renamed-sad-face

One might argue this is expected behavior for clones — only source Item field values are propagated to its clones when there are no data collisions (i.e. a source Item’s field value is pushed to the same field in its clone when that data has not changed directly on the clone — and the Item name should not be included in this process since it does not live in a field.

Sure, I see that point of view but one of the requirements of the project I am currently working on mandates that source Item name changes be pushed to the clones of that source Item.

So what did I do to solve this? I created an item:renamed event handler similar to the following (the one I built for my project is slightly different though the idea is the same):

using System;
using System.Collections.Generic;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Events;
using Sitecore.Links;
using Sitecore.SecurityModel;

namespace Sitecore.Sandbox.Data.Clones
{
    public class ItemEventHandler
    {
        protected void OnItemRenamed(object sender, EventArgs args)
        {
            Item item = GetItem(args);
            if (item == null)
            {
                return;
            }

            RenameClones(item);
        } 

        protected virtual Item GetItem(EventArgs args)
        {
            if (args == null)
            {
                return null;
            }

            return Event.ExtractParameter(args, 0) as Item;
        }

        protected virtual void RenameClones(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            using (new LinkDisabler())
            {
                using (new SecurityDisabler())
                {
                    using (new StatisticDisabler())
                    {
                        Rename(GetClones(item), item.Name);
                    }
                }
            }
        }

        protected virtual IEnumerable<Item> GetClones(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            IEnumerable<Item> clones = item.GetClones();
            if (clones == null)
            {
                return new List<Item>();
            }

            return clones;
        }

        protected virtual void Rename(IEnumerable<Item> items, string newName)
        {
            Assert.ArgumentNotNull(items, "items");
            Assert.ArgumentNotNullOrEmpty(newName, "newName");
            foreach (Item item in items)
            {
                Rename(item, newName);
            }
        }

        protected virtual void Rename(Item item, string newName)
        {
            Assert.ArgumentNotNull(item, "item");
            Assert.ArgumentNotNullOrEmpty(newName, "newName");
            if (!item.Access.CanRename())
            {
                return;
            }
            
            item.Editing.BeginEdit();
            item.Name = newName;
            item.Editing.EndEdit();
        }
    }
}

The handler above retrieves all clones for the Item being renamed, and renames them using the new name of the source Item — I borrowed some logic from the Execute method in Sitecore.Shell.Framework.Pipelines.RenameItem in Sitecore.Kernel.dll (this serves as a processor of the <uiRenameItem> pipeline).

If you would like to learn more about events and their handlers, I encourage you to check out John West‘s post about them, and also take a look at this page on the
Sitecore Developer Network (SDN).

I then registered the above event handler in Sitecore using the following configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <events>
      <event name="item:renamed">
        <handler type="Sitecore.Sandbox.Data.Clones.ItemEventHandler, Sitecore.Sandbox" method="OnItemRenamed"/>
      </event>
    </events>
  </sitecore>
</configuration>

Let’s take this for a spin.

I went back to my source item, renamed it back to ‘My Cool Item’, and then initiated another rename operation on it:

clones-renaming

As you can see all clones were renamed:

clones-renamed

If you have any thoughts/concerns on this approach, or ideas on other ways to accomplish this, please share in a comment.

Replace Proxies With Clones in the Sitecore CMS

The other day I stumbled upon a thread in the Sitecore Developer Network (SDN) forums that briefly touched upon replacing proxies with clones, and I wondered whether anyone had built any sort of tool that creates clones for items being proxied — basically a tool that would automate creating clones from proxies — and removes the proxies once the clones are in place.

Since I am not aware of such a tool — not to mention that I’m hooked on programming and just love coding — I decided to create one.

The following command is my attempt at such a tool:

using System;
using System.Linq;

using Sitecore.Configuration;
using Sitecore.Data.Items;
using Sitecore.Data.Proxies;
using Sitecore.Diagnostics;
using Sitecore.Shell.Framework.Commands;

namespace Sitecore.Sandbox.Commands
{
    public class TransformProxyToClones : Command
    {
        public override void Execute(CommandContext context)
        {
            Assert.ArgumentNotNull(context, "context");
            TransformProxyItemToClones(GetContextItem(context));
        }

        private static void TransformProxyItemToClones(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            if(!CanTransform(item))
            {
                return;
            }

            string proxyType = GetProxyType(item);
            Item source = GetItem(GetSourceItemFieldValue(item));
            Item target = GetItem(GetTargetItemFieldValue(item));
            
            if (AreEqualIgnoreCase(proxyType, "Entire sub-tree"))
            {
                DeleteItem(item);
                CloneEntireSubtree(source, target);
            }
            else if (AreEqualIgnoreCase(proxyType, "Root item only"))
            {
                DeleteItem(item);
                CloneRootOnly(source, target);
            }
        }

        private static void CloneEntireSubtree(Item source, Item destination)
        {
            Clone(source, destination, true);
        }
        
        private static void CloneRootOnly(Item root, Item destination)
        {
            Clone(root, destination, false);
        }

        private static Item Clone(Item cloneSource, Item cloneDestination, bool deep)
        {
            Assert.ArgumentNotNull(cloneSource, "cloneSource");
            Assert.ArgumentNotNull(cloneDestination, "cloneDestination");
            return cloneSource.CloneTo(cloneDestination, deep);
        }

        private static void DeleteItem(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            if (Settings.RecycleBinActive)
            {
                item.Recycle();
            }
            else
            {
                item.Delete();
            }
        }

        public override CommandState QueryState(CommandContext context)
        {
            Assert.ArgumentNotNull(context, "context");
            if (CanTransform(GetContextItem(context)))
            {
                return CommandState.Enabled;
            }

            return CommandState.Hidden;
        }

        private static Item GetContextItem(CommandContext context)
        {
            Assert.ArgumentNotNull(context, "context");
            return context.Items.FirstOrDefault();
        }

        private static bool CanTransform(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return IsProxy(item)
                    && IsSourceDatabaseFieldEmpty(item)
                    && !string.IsNullOrWhiteSpace(GetProxyType(item))
                    && !string.IsNullOrWhiteSpace(GetSourceItemFieldValue(item))
                    && !string.IsNullOrWhiteSpace(GetTargetItemFieldValue(item));
        }

        private static bool IsProxy(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return ProxyManager.IsProxy(item);
        }

        private static string GetProxyType(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return item["Proxy type"];
        }

        private static bool IsSourceDatabaseFieldEmpty(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return string.IsNullOrWhiteSpace(item["Source database"]);
        }

        private static string GetSourceItemFieldValue(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return item["Source item"];
        }

        private static string GetTargetItemFieldValue(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return item["Target item"];
        }
        
        private static Item GetItem(string path)
        {
            Assert.ArgumentNotNullOrEmpty(path, "path");
            return Context.ContentDatabase.GetItem(path);
        }

        private static bool AreEqualIgnoreCase(string one, string two)
        {
            return string.Equals(one, two, StringComparison.CurrentCultureIgnoreCase);
        }
    }
}

The above command is only visible for proxy items having both source and target items set, and the proxy is for the context content database.

When the command is invoked, the source item — conjoined with its descendants if its sub-tree is also being proxied — is cloned to the target item, after the proxy definition item is deleted.

I registered the above command in Sitecore using an include configuration file:

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

I also wired this up in the core database for the item context menu (I’ve omitted a screenshot on how this is done; If you would like to see how this is done, please see part 1 and part 2 of my post showing how to add to the item context menu).

Let’s take this for a drive.

I created a bunch of items for testing:

proxy-test-tree-created-items

I then created a proxy for these test items:

proxy-item-sub-tree

I then right-clicked on our test proxy item to launch its context menu, and then clicked on the “Transform Proxy To Clones” menu option:

transform-proxy-to-clones-context

The proxy item was removed, and we now have clones:

proxy-gone-now-clones

If you can think of any other ideas around proxies or clones, or know of other tools that create clones from proxies, please leave a comment.

Show Clones of An Item In the Sitecore CMS

“Out of the box”, there is no specific way to see all clones for a given item — at least I haven’t found one yet.

One could see these by looking at referrers of an item:

navigate-links-dropdown-clones

Unfortunately, referrers in this dropdown aren’t just reserved for clones — all referrers of the item will display in this dropdown. An example would include an item referencing the item in a Droplink field.

In a previous post, I provided a solution for auto-cloning new subitems to clones of their parents. That solution leveraged an instance of the ItemClonesGatherer utility class I defined in that post to return a collection of clones for a given item.

Yesterday, I realized this class could be reused for a feature to show a listing of clones for an item in Sitecore, and this post showcases that solution.

I had to come up with a medium for displaying a list of clones of an item. I decided I would display these in a new content editor tab.

I recalled reading an article by Sitecore MVP Mark Stiles on adding new Editor tabs in Sitecore.

As Mark Stiles had done in his post, I created a stand-alone ASP.NET web form (/sitecore modules/Shell/ShowClones/default.aspx). This web form will display the content of our new “Show Clones” tab:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Sitecore650rev120706.sitecore_modules.Shell.ShowClones.Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Show Clones</title>
    <style type="text/css">
        h1
        {
            color: #072d6b;
            font-size: 14pt;
            margin-bottom: 5px;
        }
        a
        {
            text-decoration: none;
            font-size: 8pt;
            font-family: Tahoma;
        }
        a:hover
        {
            text-decoration: underline;
        }
        #clones
        {
            margin: 10px;
        }
        #clone_listing
        {
            list-style-type: none;
            margin: 0 0 0 15px;
        }
        #clone_listing li
        {
            margin: 0;
        }
        #clone_listing li + li
        {
            margin-top: 5px;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <div id="clones">
            <h1>Clones</h1>
            <asp:MultiView ID="mvClones" ActiveViewIndex="0" runat="server">
                <asp:View ID="vClones" runat="server">
                    <asp:Repeater ID="rptClones" runat="server">
                        <HeaderTemplate>
                            <ol id="clone_listing">
                        </HeaderTemplate>
                        <ItemTemplate>
                            <li>
                                <asp:HyperLink 
                                    ID="hlClone" 
                                    NavigateUrl="#"
                                    onclick='<%# string.Format("parent.scForm.invoke(\"item:load(id={0})\"); return false;", Eval("ID").ToString()) %>'
                                    Text='<%# Eval("Paths.FullPath") %>'
                                    runat="server" />
                            </li>
                        </ItemTemplate>
                        <FooterTemplate>
                            </ol>
                        </FooterTemplate>
                    </asp:Repeater>
                </asp:View>
                <asp:View ID="vNoClones" runat="server">
                    <script type="text/javascript">
                        parent.scContent.closeEditorTab('ShowClones');
                    </script>
                </asp:View>
            </asp:MultiView>
        </div>
    </form>
</body>
</html>

The web form’s code-behind:

using System;
using System.Collections.Generic;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Web;

using Sitecore.Sandbox.Utilities.Gatherers.Base;
using Sitecore.Sandbox.Utilities.Gatherers;

namespace Sitecore650rev120706.sitecore_modules.Shell.ShowClones
{
    public partial class Default : System.Web.UI.Page
    {
        private static readonly IItemsGatherer ClonesGatherer = ItemClonesGatherer.CreateNewItemClonesGatherer();

        protected void Page_Load(object sender, EventArgs e)
        {
            ShowClones();
        }

        private void ShowClones()
        {
            BindRepeater();
            ToggleViews();
        }

        private void BindRepeater()
        {
            rptClones.DataSource = GetClones();
            rptClones.DataBind();
        }

        private void ToggleViews()
        {
            if (rptClones.Items.Count > 0)
            {
                mvClones.SetActiveView(vClones);
            }
            else
            {
                mvClones.SetActiveView(vNoClones);
            }
        }

        private IEnumerable<Item> GetClones()
        {
            Item item = GetItem();
            if(item == null)
            {
                return new List<Item>();
            }

            ClonesGatherer.Source = item;
            return ClonesGatherer.Gather();
        }

        private Item GetItem()
        {
            Item item = null;

            try
            {
                item = Sitecore.Context.ContentDatabase.GetItem(GetID(), GetLanguage(), GetVersion());
            }
            catch(Exception ex)
            {
                Log.Error(this.ToString(), ex, this);
            }

            return item;
        }

        private ID GetID()
        {
            Sitecore.Data.ID id;
            if(Sitecore.Data.ID.TryParse(WebUtil.GetQueryString("id"), out id))
            {
                return id;
            }

            return Sitecore.Data.ID.Null;
        }

        private Language GetLanguage()
        {
            Language language;
            if(Language.TryParse(WebUtil.GetQueryString("la"), out language))
            {
                return language;
            }

            return Sitecore.Context.Language;
        }

        private Sitecore.Data.Version GetVersion()
        {
            Sitecore.Data.Version version;
            if (Sitecore.Data.Version.TryParse(WebUtil.GetQueryString("vs"), out version))
            {
                return version;
            }

            return Sitecore.Data.Version.Latest;
        }
    }
}

The code-behind above uses an instance of the ItemClonesGatherer class to get all clones for the passed item. If clones are found, these are bound to a repeater to display them as links.

If there are no clones, the Multiview in the web form is toggled to render JavaScript to close the tab — the tab should not be open if the item does not have any clones.

After revisiting Mark’s article, and realized I needed a different solution: one that will allow me to add a new tab on the fly.

Such a solution should only allow the tab to open when an item has clones, and it should not be be associated with any templates — it must be template oblivious.

I stumbled upon a command in one of the Sitecore DLLs — which one it was is evading me at the moment — and noticed it was using an instance of Sitecore.Web.UI.Framework.Scripts.ShowEditorTab. I decided to take a chance on using an instance of this object, hoping it might open up a new content editor tab on the fly.

Using that command as a model, I came up with the following command:

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

using Sitecore.Sandbox.Utilities.Gatherers.Base;
using Sitecore.Sandbox.Utilities.Gatherers;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Resources;
using Sitecore.Shell.Framework.Commands;
using Sitecore.Text;
using Sitecore.Web;
using Sitecore.Web.UI.Framework.Scripts;
using Sitecore.Web.UI.Sheer;

namespace Sitecore.Sandbox.Commands
{
    public class ShowClones : Command
    {
        private static readonly IItemsGatherer ClonesGatherer = ItemClonesGatherer.CreateNewItemClonesGatherer();

        public override void Execute(CommandContext commandContext)
        {
            ShowClonesEditorTab(GetItem(commandContext));
        }

        private static void ShowClonesEditorTab(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            const string command = "contenteditor:pagedesigner";
            const string id = "ShowClones";

            bool shouldClickEditorTab = IsEditorTabOpen(command);

            if (IsEditorTabOpen(command))
            {
                ClickOpenEditorTab(id);
                return;
            }
            
            OpenEditorTab(CreateNewShowClonesEditorTab(item, command, id));
        }

        private static bool IsEditorTabOpen(string command)
        {
            Assert.ArgumentNotNullOrEmpty(command, "command");
            return WebUtil.GetFormValue("scEditorTabs").Contains(command);
        }

        private static void ClickOpenEditorTab(string id)
        {
            Assert.ArgumentNotNullOrEmpty(id, "id");
            SheerResponse.Eval(string.Format("scContent.onEditorTabClick(null, null, '{0}')", id));
        }

        private static void OpenEditorTab(ShowEditorTab tab)
        {
            Assert.ArgumentNotNull(tab, "tab");
            SheerResponse.Eval(tab.ToString());
        }

        private static ShowEditorTab CreateNewShowClonesEditorTab(Item item, string command, string id)
        {
            Assert.ArgumentNotNull(item, "item");
            Assert.ArgumentNotNullOrEmpty(command, "command");
            Assert.ArgumentNotNullOrEmpty(id, "id");

            UrlString urlString = new UrlString("/sitecore modules/shell/showclones/default.aspx");
            item.Uri.AddToUrlString(urlString);
            UIUtil.AddContentDatabaseParameter(urlString);
            return new ShowEditorTab
            {
                Command = command,
                Header = Translate.Text("Show Clones"),
                Icon = Images.GetThemedImageSource("Network/32x32/link_view.png"),
                Url = urlString.ToString(),
                Id = id,
                Closeable = true,
                Activate = true
            };
        }

        public override CommandState QueryState(CommandContext context)
        {
            if (!HasClones(GetItem(context)))
            {
                return CommandState.Hidden;
            }

            return CommandState.Enabled;
        }

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

        private static bool HasClones(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return GetClones(item).Any();
        }

        private static IEnumerable<Item> GetClones(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            ClonesGatherer.Source = item;
            return ClonesGatherer.Gather();
        }
    }
}

The command above uses an instance of ItemClonesGatherer to get all clones for the item in the content tree, and ensures it is visible when the item has clones. The logic hides the command when the item does not have clones.

When the command is invoked, it will open a new “Show Clones” tab, or set focus on the “Show Clones” tab if it’s already present.

I then registered the command above in a patch include configuration file:

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <commands>
      <command name="item:showclones" type="Sitecore.Sandbox.Commands.ShowClones,Sitecore.Sandbox"/>
    </commands>
  </sitecore>
</configuration>

Now, we need to lock and load this command in Sitecore. I switched over to the core database, and added a new item context menu option:

show-clones-command

Let’s see this in action.

I first created a bunch of clones:

clones-in-content-tree

On my source item, I right-clicked, and was presented with a new context menu option to “Show Clones”:

show-clones-context-menu

After clicking the “Show Clones” context menu option, a new “Show Clones” tab appeared:

show-clones-tab

I then clicked on one of the links in the “Show Clones” tab, and was brought to its associated clone:

was-brough-to-clone

If you have other ideas around using clones in Sitecore, or if you know of another way of listing clones of an item, please leave a comment.

Automagically Clone New Child Items to Clones of Parent Items In the Sitecore CMS

“Out of the box”, content authors are prompted via a content editor warning to take action on clones of a source item when a new subitem is added under the source item:

being-notified-about-new-child-item

Content authors have a choice to either clone or not clone the new subitem, and such an action must be repeated on all clones of the source item — such might be a daunting task, especially when there are multiple clones for a given source item.

In a recent project, I had a requirement to automatically clone newly added subitems under clones of their parents, and remove any content editor warnings by programmatically accepting the Sitecore notifications driving these warnings.

Although I cannot show you that solution, I did wake up from a sound sleep early this morning with another solution to this problem — it was quite a dream — and this post captures that idea.

In a previous post, I built a utility object that gathers things, and decided to reuse this basic concept. Here, I define a contract for what constitutes a general gatherer:

using System.Collections.Generic;

namespace Sitecore.Sandbox.Utilities.Gatherers.Base
{
    public interface IGatherer<T, U>
    {
        T Source { get; set; }

        IEnumerable<U> Gather();
    }
}

In this post, we will be gathering clones — these are Sitecore items:

using Sitecore.Data.Items;

namespace Sitecore.Sandbox.Utilities.Gatherers.Base
{
    public interface IItemsGatherer : IGatherer<Item, Item>
    {
    }
}

The following gatherer grabs all clones for a given item as they are cataloged in an instance of the LinkDatabase — in this solution we are using Globals.LinkDatabase as the default instance:

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

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

using Sitecore.Sandbox.Utilities.Gatherers.Base;

namespace Sitecore.Sandbox.Utilities.Gatherers
{
    public class ItemClonesGatherer : IItemsGatherer
    {
        private LinkDatabase LinkDatabase { get; set; }

        private Item _Source;
        public Item Source 
        {
            get
            {
                return _Source;
            }
            set
            {
                Assert.ArgumentNotNull(value, "Source");
                _Source = value;
            }
        }

        private ItemClonesGatherer()
            : this(GetDefaultLinkDatabase())
        {
        }

        private ItemClonesGatherer(LinkDatabase linkDatabase)
        {
            SetLinkDatabase(linkDatabase);
        }

        private ItemClonesGatherer(LinkDatabase linkDatabase, Item source)
        {
            SetLinkDatabase(linkDatabase);
            SetSource(source);
        }

        private void SetLinkDatabase(LinkDatabase linkDatabase)
        {
            Assert.ArgumentNotNull(linkDatabase, "linkDatabase");
            LinkDatabase = linkDatabase;
        }

        private void SetSource(Item source)
        {
            Source = source;
        }

        public IEnumerable<Item> Gather()
        {
            return (from itemLink in GetReferrers()
                    where IsClonedItem(itemLink)
                    select itemLink.GetSourceItem()).ToList();
        }

        private IEnumerable<ItemLink> GetReferrers()
        {
            Assert.ArgumentNotNull(Source, "Source");
            return LinkDatabase.GetReferrers(Source);
        }

        private static bool IsClonedItem(ItemLink itemLink)
        {
            return IsSourceField(itemLink) 
                    && !IsSourceItemNull(itemLink);
        }

        private static bool IsSourceField(ItemLink itemLink)
        {
            Assert.ArgumentNotNull(itemLink, "itemLink");
            return itemLink.SourceFieldID == FieldIDs.Source;
        }

        private static bool IsSourceItemNull(ItemLink itemLink)
        {
            Assert.ArgumentNotNull(itemLink, "itemLink");
            return itemLink.GetSourceItem() == null;
        }

        private static LinkDatabase GetDefaultLinkDatabase()
        {
            return Globals.LinkDatabase;
        }

        public static IItemsGatherer CreateNewItemClonesGatherer()
        {
            return new ItemClonesGatherer();
        }

        public static IItemsGatherer CreateNewItemClonesGatherer(LinkDatabase linkDatabase)
        {
            return new ItemClonesGatherer(linkDatabase);
        }

        public static IItemsGatherer CreateNewItemClonesGatherer(LinkDatabase linkDatabase, Item source)
        {
            return new ItemClonesGatherer(linkDatabase, source);
        }
    }
}

Next, we need a way to remove the content editor warning I alluded to above. The way to do this is to accept the notification that is triggered on the source item’s clones.

I decided to use the decorator pattern to accomplish this:

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

namespace Sitecore.Sandbox.Data.Clones
{
    public class AcceptAsIsNotification : Notification
    {
        private Notification InnerNotification { get; set; }

        private AcceptAsIsNotification(Notification innerNotification)
        {
            SetInnerNotification(innerNotification);
            SetProperties();
        }

        private void SetInnerNotification(Notification innerNotification)
        {
            Assert.ArgumentNotNull(innerNotification, "innerNotification");
            InnerNotification = innerNotification;
        }

        private void SetProperties()
        {
            ID = InnerNotification.ID;
            Processed = InnerNotification.Processed;
            Uri = InnerNotification.Uri;
        }

        public override void Accept(Item item)
        {
            base.Accept(item);
        }

        public override Notification Clone()
        {
            return InnerNotification.Clone();
        }

        public static Notification CreateNewAcceptAsIsNotification(Notification innerNotification)
        {
            return new AcceptAsIsNotification(innerNotification);
        }
    }
}

An instance of the above AcceptAsIsNotification class would wrap an instance of a notification, and accept the wrapped notification without any other action.

Notifications we care about in this solution are instances of Sitecore.Data.Clones.ChildCreatedNotification — in Sitecore.Kernel — for a given clone parent, and this notification clones a subitem during acceptance, a behavior we do not want to leverage since we have already cloned the subitem.

I then added an item:added event handler to use an instance of our gatherer defined above to get all clones of the newly added subitem’s parent; clone the subitem under these clones; and accept all instances of Sitecore.Data.Clones.ChildCreatedNotification on the parent item’s clones:

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

using Sitecore.Data;
using Sitecore.Data.Clones;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Events;

using Sitecore.Sandbox.Data.Clones;
using Sitecore.Sandbox.Utilities.Gatherers;
using Sitecore.Sandbox.Utilities.Gatherers.Base;

namespace Sitecore.Sandbox.Events.Handlers
{
    public class AutomagicallyCloneChildItemEventHandler
    {
        private static readonly IItemsGatherer ClonesGatherer = ItemClonesGatherer.CreateNewItemClonesGatherer();

        public void OnItemAdded(object sender, EventArgs args)
        {
            CloneItemIfApplicable(GetItem(args));
        }

        private static void CloneItemIfApplicable(Item item)
        {
            if (item == null)
            {
                return;
            }

            ChildCreatedNotification childCreatedNotification = CreateNewChildCreatedNotification();
            childCreatedNotification.ChildId = item.ID;
            IEnumerable<Item> clones = GetClones(item.Parent);
            foreach (Item clone in clones)
            {
                Item clonedChild = item.CloneTo(clone);
                RemoveChildCreatedNotifications(Context.ContentDatabase, clone);
            }
        }

        private static void RemoveChildCreatedNotifications(Database database, Item clone)
        {
            Assert.ArgumentNotNull(database, "database");
            Assert.ArgumentNotNull(clone, "clone");
            foreach (Notification notification in GetChildCreatedNotifications(database, clone))
            {
                AcceptNotificationAsIs(notification, clone);
            }
        }

        private static void AcceptNotificationAsIs(Notification notification, Item item)
        {
            Assert.ArgumentNotNull(notification, "notification");
            Assert.ArgumentNotNull(item, "item");
            Notification acceptAsIsNotification = AcceptAsIsNotification.CreateNewAcceptAsIsNotification(notification);
            acceptAsIsNotification.Accept(item);
        }

        private static IEnumerable<Notification> GetChildCreatedNotifications(Database database, Item clone)
        {
            Assert.ArgumentNotNull(database, "database");
            Assert.ArgumentNotNull(clone, "clone");
            return GetNotifications(database, clone).Where(notification => notification.GetType() == typeof(ChildCreatedNotification)).ToList();
        }
        private static IEnumerable<Notification> GetNotifications(Database database, Item clone)
        {
            Assert.ArgumentNotNull(database, "database");
            Assert.ArgumentNotNull(clone, "clone");
            return database.NotificationProvider.GetNotifications(clone);
        }

        private static IEnumerable<Item> GetClones(Item item)
        {
            ClonesGatherer.Source = item;
            return ClonesGatherer.Gather();
        }

        private static Item GetItem(EventArgs args)
        {
            return Event.ExtractParameter(args, 0) as Item;
        }

        private static ChildCreatedNotification CreateNewChildCreatedNotification()
        {
            return new ChildCreatedNotification();
        }
    }
}

I then plugged in the above in a patch include configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <sitecore>
    <events>
      <event name="item:added">
        <handler type="Sitecore.Sandbox.Events.Handlers.AutomagicallyCloneChildItemEventHandler, Sitecore.Sandbox" method="OnItemAdded"/>
      </event>
    </events>
  </sitecore>
</configuration>

Let’s try this out, and see how we did.

I created a new child item under my source item:

added-new-child-automatically-cloned

As you can see, clones were added under all clones of the source item.

Plus, the notification about a new child item being added under the source item is not present:

no-notification-accepted-programmatically

This notification was automatically accepted in code via an instance of AcceptAsIsNotification within the item:added event handler defined above.

If you can think of any other interesting ways to leverage Sitecore’s item cloning capabilities, please leave a comment.