A Tale Of Three Friends
Once upon a time there lived three friends – Copy
, Clone
, and Duplicate
. Copy and Duplicate were almost identical twins but they didn’t get along very well. Duplicate was lazy and everything he could delegate he would ask Copy to do. Copy has always been a good guy and would happily cary all the heavy lifting for the Duplicate. Clone, on the other hand, was a foster child. He had a pretty rough childhood and he learned to do things his own unique way. When he joined the family many saw him as a weirdo and he eventually started hiding behind the Duplicate. Till this day Duplicate has a big ribbon button and a separate context menu item all to himself with Copy right next to it sharing the room with Move. Clone doesn’t like to come out very often so he hides behind Duplicate in the ribbon.
When it was time to build pipelines the three friends went their separate ways. Copy and Clone teamed up and did what they had to do and their pipelines adhered to the spec. The Execute()
would not only do the work but also pass on enhanced arguments to whoever later decides to join in. Duplicate, as lazy as he always was, made its pipeline do the work with minimal efforts. Two hops into the Execute()
and his pipeline runs Item.CopyTo()
where Copy silently and without complains takes over. Duplicate figured he wouldn’t lift a finger for an imaginary somebody who might (or might not, right?) later join in and so arguments after Execute()
remain as they were on the way in.
Then one day the Buckets
came. And there were many. Buckets took over everything and tweaked the pipelines to their liking. Buckets were not lazy and above all they were strong. Sitecore, who ruled their world, made them army strong. They could carefully extend the old pipelines’ processors and have them do the right thing for everybody but that would be too nice of them, too soft some of them thought, not manly enough. New Execute()
s were written and patched before the old ones. Buckets appreciated Copy’s hard work and let the old processor run for those remaining few items that didn’t bucket. They also knew about Clone’s past and allowed his pipeline to do the same. But that was it. Their processors were built to not care about anybody but themselves and they would clog the pipe once Execute finishes. args.AbortPipeline()
says it all. Duplicate argued with them, tried to bribe them and even threatened them. All in vain. Buckets (probably didn’t even notice) just closed the pipe for Duplicate. His old pipeline never runs in the world ruled by Buckets.
The time passed. In a land far far away a boy was born. This boy was destined to change the world of Copy, Clone, and Duplicate forever. His name was SPEAK… [To Be Continued]
Reference
I have to make a disclaimer that everything in this section is the reflection of my personal experience and my looking into the Sitecore with Reflector/Resharper glasses on. It’s representative of Sitecore 7.0 and may not be fully accurate. Please use it at your own risk and consult with Sitecore support team if in doubt.
Status Quo
The three pipelines are defined in Web.config
as:
<uiCopyItems> <!-- GetDestination -> CheckDestination -> CheckLanguage -> Execute --> </uiCopyItems> <uiCloneItems> <!-- GetDestination -> CheckDestination -> CheckLanguage -> Execute --> </uiCloneItems> <uiDuplicateItem> <!-- CheckPermissions -> GetName -> Execute --> </uiDuplicateItem>
When these pipelines run here’s the sequence of item:*
events that each of them generates:
Copy and Duplicate :copying -> :created (repeated for all child items) -> :copied
Clone :creating -> :created -> :versionAdding -> :saving -> :saved -> :versionAdded -> :added -> :sortorderchanged
Note: to observe events as they fire add this patch into your App_ConfigInclude
:
<sitecore> <events> <patch:attribute name="timingLevel">high</patch:attribute> </events> </sitecore>
If you know of a better way to observe events (and especially if you know how to tell Sitecore to log the details of the item it fires the events on) please let me know.
Sitecore.Shell.Framework.Pipelines.CloneItems
inherits from Sitecore.Shell.Framework.Pipelines.CopyItems
and overrides Execute()
. The preliminary steps are thus the same. These two don’s ask for the name of the item to be created. The logic they run to come up with the name is part of the Execute
– call to ItemUtil.GetCopyOfName()
– and it comes down to:
Translate.Text("Copy of") + " " + name // + add an auto-incremented number to make it unique
The Sitecore.Shell.Framework.Pipelines.DuplicateItem
is different. The GetName
step asks the Sitecore user for the new name and passes it on to Execute
step in the pipeline’s arg.Parameters["name"]
. Unlike it’s friends – Copy and Clone – Duplicate does not record the item it creates into the pipeline arguments. Duplicate is basically the same as Copy with two deviations: 1) predefined target – the item’s parent item and 2) custom name for the new item.
Item Buckets
Introduction of the buckets adds the following (similar for all three pipelines) via Sitecore.Buckets.Config
:
<processor patch:before="*[@method='Execute']" type="Sitecore.Buckets.Pipelines.UI.ItemCopy, Sitecore.Buckets" method="Execute"/>
The patch:before
puts the new pipeline processor in front of the old one. The new Copy and Clone do the following in the very beginning of the Execute()
:
if (!BucketManager.IsBucket(this.GetItemByParameter(args, "destination"))) { return; }
which allows the original Execute
processor to run for the items copied into a non-bucketed parent item. Otherwise the method runs a new buckets-aware logic and finishes with args.AbortPipeline()
.
The new Duplicate’s Execute
incorporates the logic of the legacy version and runs simple Context.Workflow.DuplicateItem
for non-bucketed items but it also abruptly ends the pipeline with args.AbortPipeline()
.
Thoughts
I believe a few simple things would make these pipelines better (= easier to extend and customize):
- Introduce a non-interactive
GetName
step into Copy and Clone pipelines so that developers implementing Sitecore could easily provide their own logic to create these “copy of” names. It would pass the generated name to theExecute()
inParameters["name"]
just like Duplicate does - Make Duplicate record the created copy (it is a copy into the item’s parent after all) just like Copy does and pass it on in
args.Copies
- Have buckets-aware version incorporate the legacy logic and insert themselves as
patch:instead
instead ofpatch:before
+arg.AbortPipeline()
. And, of course, don’t abort the pipeline on success.
P.S.
If you wonder why I was looking into it here’s a link that will explain it: Changing Sitecore item references when creating, copying, duplicating and cloning. And knowing the events each pipeline triggers helps troubleshoot why on earth item:saved
is not called for items created as a result of Copy and Duplicate. They are all created and then saved after all.
Thank you for this post, it helped me a lot. I was looking into the same post about copying, cloning and duplicating you referred to. A very nice way to be sure the tree-copying/duplicating is done correctly, also when not doing it trough the UI, is using the item:copied event as well as the item:added event.
Thank you for this, ..i am stuck with issue where i created a custom command to “copy” an item and custom processor to “change the template” of copied item. Unfortunately the destination folder is “bucketed” and the “bucket” pipline is before the “itemcopy” execute method..so my processor never gets hit may be because of args.AbortPipeline in bucket processor code. I am required to add my processor after “execute” command of itemCopy..
If i remove the folder “isbucket” it works fine… any solution to this ?