Moving template fields with standard values in Sitecore XM

,

I recently did a major refactor of a large multilingual Sitecore XM solution which involved splitting a lot of fields out into base templates. However, when moving fields from a template with standard values, the standard values are not automatically moved to the destination template which in this case was part of the refactoring. As the solution had standard values in 7 languages, I created a simple event handler to do this automatically.

To understand the scenario and my event handler, lets look at this example template:

The template consists of two sections – a Header and a Footer section, each containing a Title and a Description field. Also the template have standard values defined for all fields:

What I would like to do is to split the fields into two base templates (_Header and _Footer), which I have already prepared:

As fields are represented in Sitecore using IDs, if I simply add the two base templates to the the Content Page templates list of base templates:

And then drag and drop each field from the template sections of the Content Page template to the matching base template, any existing items using the Content Page template should be unaffected:

However, as you can see – during this move the standard value from Content Page is not moved to the _Header base template:

Of cause in some cases keeping the standard values in the Content Page template standard values makes perfectly sense, but in my case, I actually wanted to move the standard values to the two base templates along with the fields . So I introduced this event handler:

namespace sc10xm.Foundation.StandardValues.Events
{
    using Sitecore;
    using Sitecore.Data;
    using Sitecore.Data.Items;
    using Sitecore.Data.Managers;
    using Sitecore.Events;
    using System;
    using System.Collections.Generic;
    using System.Linq;

    public class MoveStandardValues
    {
        public void OnItemMoved(object sender, EventArgs args)
        {
            var destinationItem = Event.ExtractParameter<Item>(args, 0);

            if (destinationItem == null)
                return;

            var database = destinationItem.Database;

            // We will only consider template fields or sections
            if (!destinationItem.IsTemplateField() && !destinationItem.IsTemplateSection())
                return;

            var sourceId = Event.ExtractParameter<ID>(args, 1);
            var sourceItem = database.GetItem(sourceId);

            if (sourceItem == null)
                return;

            var sourceStandardValues = GetContainingTemplate(sourceItem)?.StandardValues;
            var destinationStandardValues = GetContainingTemplate(destinationItem)?.StandardValues;


            if (sourceStandardValues == null || destinationStandardValues == null)
                return;

            var fieldIds = new List<ID>();

            // If the moved item is a template field, we will simply move that
            if (destinationItem.IsTemplateField())
                fieldIds.Add(destinationItem.ID);

            // If the moved item is a template section, we will move all containing fields
            if (destinationItem.IsTemplateSection())
                fieldIds.AddRange(destinationItem.GetChildren().Where(c=> c.IsTemplateField()).Select(c=> c.ID));

            MoveFields(sourceStandardValues, destinationStandardValues, fieldIds);
        }

        private static void MoveFields(Item source, Item destination, List<ID> fieldIds)
        {
            if (!fieldIds.Any())
                return;

            foreach (var language in source.Languages)
            {
                var sourceVersion = source.Database.GetItem(source.ID, language);
                var destinationVersion = destination.Database.GetItem(destination.ID, language);

                if (sourceVersion == null || destinationVersion == null)
                    return;

                var fieldsToMove = new Dictionary<ID, string>();

                using (new EditContext(sourceVersion))
                using (new EditContext(destinationVersion))
                {
                    foreach (var fieldId in fieldIds)
                    {
                        var value = sourceVersion.Fields[fieldId].GetValue(false, false);

                        if (string.IsNullOrEmpty(value))
                            continue;

                        sourceVersion.Fields[fieldId]?.Reset();
                        destinationVersion.Fields[fieldId]?.SetValue(value, false);
                    }
                }
            }
        }

        private TemplateItem GetContainingTemplate(Item item)
        {
            if (item.TemplateID.Equals(TemplateIDs.Template))
                return new TemplateItem(item);

            var innerItem = item.Axes.GetAncestors().FirstOrDefault(a => a.TemplateID.Equals(TemplateIDs.Template));
        
            return innerItem != null ? new TemplateItem(innerItem) : null;
        }
    }

    public static class Extensions
    {
        public static bool IsTemplateField(this Item item)
        {
            return item.IsDerived(TemplateIDs.TemplateField);
        }

        public static bool IsTemplateSection(this Item item)
        {
            return item.IsDerived(TemplateIDs.TemplateSection);
        }

        public static bool IsDerived(this Item item, ID templateId)
        {
            if (item == null)
                return false;

            if (item.TemplateID.Equals(templateId))
                return true;

            return !templateId.IsNull && item.Template != null && item.IsDerived(item.Database.Templates[templateId]);
        }

        public static bool IsDerived(this Item item, Item templateItem)
        {
            if (item == null)
                return false;

            if (templateItem == null)
                return false;

            var itemTemplate = TemplateManager.GetTemplate(item);
            return itemTemplate != null && 
                (itemTemplate.ID == templateItem.ID || itemTemplate.DescendsFrom(templateItem.ID));
        }
    }
}

With this event handler patched in:

<configuration xmlns:role="http://www.sitecore.net/xmlconfig/role/">
  <sitecore role:require="Standalone or ContentManagement">
    <events>
      <event name="item:moved">
        <handler type="sc10xm.Foundation.StandardValues.Events.MoveStandardValues, sc10xm" method="OnItemMoved"/>
      </event>
    </events>
  </sitecore>
</configuration>

It is now possible to drag and drop template fields and sections from one template to another together with the the standard values.