HaikoFalk
Software Engineer

Clones and Canonical-Tags - how to prevent duplicate content


This is a tricky one! As you may know, you can use a canonical tags in the header to prevent duplicated content on your page. On the other hand, Sitecore has a nice feature since version 6.4, the cloning of items. But if you clone a content item in your content tree you'll produce duplicated content.

So far so good. The item knows that it is cloned and knows the source, of course. So the canonical tag links to the source item … But I told you, this is a tricky one, didn’t I?

When publishing the item, Sitecore removes all the important information. Then the item is technically uncloned and the canonical tag can’t be determined anymore.
So John West had to save my day once more… ;)

I amended his code slightly, because I think it is better to only implement one return per method. In addition to that there were some more requirements from our side:

- If the cloned item refers to a different version of the source item, it’s content isn’t a duplicate.
- Because this is a multi-site and multi-language solution, the canonical item has to be in the same site tree (each site has its own content tree)
So I decided to use a VersionedLinkField, because here we have database, ItemId, language and version stored. The __Source Field is of this kind too, so I can just copy the string and change the DB name.

Code:
public class PerformAction : Sitecore.Publishing.Pipelines.PublishItem.PerformAction
{
	public override void Process(Sitecore.Publishing.Pipelines.PublishItemPublishItemContext context)
	{
		base.Process(context);
		if (!context.Aborted &&
			context.VersionToPublish != null &&
			context.VersionToPublish.Source != null &&
			context.VersionToPublish.Fields[Configuration.SC.Fields.StandardFields.Source] != null &&
			context.VersionToPublish.IsClone)
		{
			Sitecore.Data.ItemsItem target = context.PublishOptions.TargetDatabase.GetItem(
				context.VersionToPublish.ID,
				context.VersionToPublish.Language);

			if (target != null &&
				target[Configuration.SC.Fields.StandardFields.Source] !=
				context.VersionToPublish.Source.ID.ToString())
			{
				using (new Sitecore.Data.ItemsEditContext(target, 
					updateStatistics: false, silent: true))
				{
					target[Configuration.SC.Fields.StandardFields.Source] =
						context.VersionToPublish["__Source"]
							.Replace(context.PublishOptions.SourceDatabase.Name, 
								context.PublishOptions.TargetDatabase.Name);

				}
			}
		}
	}
}

The using of an ItemUri object makes it quite easy to get the ItemId, the language and the version. These methods are placed in a class that extends the Item API like mentioned in John’s blog post.

Code:
/// <summary>
/// determine if a item is a clone ( even in web DB )
/// </summary>
/// <param name="item">the item</param>
/// <returns>true if iit is a clone</returns>
public static bool IsCloneItem(this Item item)
{
	Sitecore.Diagnostics.Assert.ArgumentNotNull(item, "item");
	return item.IsClone
	  || !string.IsNullOrEmpty(item[Configuration.SC.Fields.StandardFields.Source]);
}

/// <summary>
/// returns the source of a clone item
/// </summary>
/// <param name="item">the item</param>
/// <returns>the source item of the clone or the item itself</returns>
public static Item GetCloneSourceItem(this Item item )
{
	Item result = item;
	if (item.IsCloneItem()  )
	{
		// in content management database (master)
		if (item.Source != null)
			result = item.Source;
		// in contetn delivery database (web)
		else
		{
			Sitecore.DataItemUri field = new Sitecore.DataItemUri(item[Fields.StandardFields.Source]);
			result = item.Database.GetItem(field.ItemID, field.Language, field.Version);
		}
	}
	return result;
}

Finally, for the canonical tag itself, you have to check in your layout (or put it in an own control) if it is a clone item, create and write the link.

Code:
/// <summary>
/// determine the canonical tag depending on context item with parameter
/// </summary>
/// <param name="output">rendering result</param>
void DeterminCanonicalTag(HtmlTextWriter output)
{
    Item currentItem = Sitecore.Context.Item.IsCloneItem()
		? Sitecore.Context.Item.GetCloneSourceItem() : Sitecore.Context.Item;
	Sitecore.LinksUrlOptions urlOptions = Sitecore.Links.LinkManager.GetDefaultUrlOptions();
	urlOptions.AlwaysIncludeServerUrl = true;
	string url = Sitecore.Links.LinkManager.GetItemUrl(currentItem, urlOptions);
	
	output.AddAttribute(HtmlTextWriterAttribute.Rel, "canonical");
	output.AddAttribute(HtmlTextWriterAttribute.Href, url);
	output.RenderBeginTag(HtmlTextWriterTag.Link);	
}

Take care that your Link-Manager is configured well to get SEO friendly URLs

Good Luck and Thank you John
Haiko

Kommentare
Es wurden noch keine Kommentare zu diesem Eintrag geschrieben.
Kommentar hinzufügen
Vor und Zuname
E-Mail
E-Mail bei weiteren Kommentaren
Mein Kommentar