Thursday, August 25, 2016

Sitecore remove renderings or sublayouts with missing datasources

As a Sitecore implementation starts to get larger and older, items can start to have renderings or sublayouts which refer to datasources which no longer exist. The main cause for this is usually a content editor deleting the datasource item and leaving the links intact.

The following code can be setup as a scheduled task in Sitecore or added to a custom admin page to be run as needed. It will recursively move down the content tree and find items which have a layout, it then checks the renderings on those items with datasources and ensures the datasource exists. If a rendering with a broken datasource is found, it is removed from the item.
using Sitecore.Layouts;
using Sitecore.Data.Fields;
using Sitecore.Diagnostics;

public partial class DatasourceFix : System.Web.UI.Page
{
 private static Database database = Sitecore.Configuration.Factory.GetDatabase("master");
 private static string StartPath = "/sitecore/content/"; // Start path to enumerate
 private static string DefaultDevice = "{FE5D7FDF-89C0-4D99-9AA3-B5FBD009C9F3}"; // Default device ID

 protected void btnRun_Click(object sender, EventArgs e)
 {
  var startItem = database.Items.GetItem(StartPath);
  CheckItem(startItem);
 }

 private Item CheckItem(Item mainItem)
 {
  RenderingRemover(mainItem);

  if (mainItem.HasChildren)
  {
   foreach (var myInnerItem in mainItem.Children.ToList())
   {
    CheckItem(myInnerItem);
   }
  }

  return null;
 }
 
 private void RenderingRemover(Item item)
 {
  if (item.Fields[Sitecore.FieldIDs.LayoutField] != null && !String.IsNullOrEmpty(item.Fields[Sitecore.FieldIDs.FinalLayoutField].Value))
  {
   // If item has a layout
   LayoutField layoutField = new LayoutField(item.Fields[Sitecore.FieldIDs.LayoutField]);
   LayoutDefinition layout = LayoutDefinition.Parse(layoutField.Value);
   DeviceDefinition device = device = layout.GetDevice(DefaultDevice); // Get the default device

   // Get renderings for the device
   var d = layout.GetDevice(device.ID.ToString());
   var ren = d.Renderings;

   // Query the renderings for those with a datasource
   var withDatasource = from RenderingDefinition renDef in ren
      where !string.IsNullOrEmpty(renDef.Datasource)
      select renDef;

   bool itemEdited = false; // item edited flag
   
   // Find missing datasources
   foreach (RenderingDefinition rendering in withDatasource.ToList() ?? Enumerable.Empty())
   {
    var datasource = database.GetItem(new Sitecore.Data.ID(rendering.Datasource)); // Get the datasource

    if (datasource == null)
    {
     // if datasource is broken, flag an item edit and remove the rendering
     itemEdited = true;
     d.Renderings.Remove(rendering);
     Log.Info(string.Format("Removing rendering {0} from item {1} due to datasource not being found.", rendering.ItemID.ToString(), item.Paths.Path), "");
    }
   }

   // edit the item if rendering(s) were removed
   if(itemEdited)
   {
    item.Editing.BeginEdit();
    layoutField.Value = layout.ToXml();
    item.Editing.EndEdit();
   }
  }
 }
}
It can be quite an expensive operation as it will go through an entire content tree, so be careful when/how it is run.

No comments:

Post a Comment