One of the great additions to Sitecore 8 is the ability to publish related items when executing a publish. Using this feature, you’ll be sure to publish out any necessary items that may be needed to render the page correctly, such as data sources, referenced taxonomy items, or images.
However, you may still have some gaps when using this feature. Consider common scenario where you have a new page, and you add a component to the page that uses an separate item as a data source. On that data source is a field for an image. When publishing the page, the newly created data source item goes out, but the media item linked to on that data source does not.
This is because of the way Sitecore processes referenced items. In essence, it only goes one level deep in the reference tree. So, items referenced by the item being published will be added to the queue, but items referenced by those referenced items will not.
Normally this is ok. If the publisher crawled references recursively, you’d probably wind up in an infinite publishing loop, or you’d at least wind up doing a large publish unintentionally. But it is common for data source items to reference new content, like media, so we need to include those in the publish too.
There’s a pipeline in Sitecore 8 we can use specifically for this purpose, the <getItemReferences> pipeline. Out of the box, it includes a step to AddItemLinkReferences. This step is the one responsible for adding our referenced data source item, so we can override this step to add logic to include media referenced by that data source.
Like all great Sitecore developers, we customize Sitecore by reflecting on their code and replacing it with our own logic. I opened up Sitecore.Publishing.Pipelines.GetItemReferences.AddItemLinkReferences, and added the following.
... foreach (Item obj in itemLinkArray.Select(link => link.GetTargetItem()).Where(relatedItem => relatedItem != null)) { list.AddRange(PublishQueue.GetParents(obj)); list.Add(obj); // This will look at the item's links looking for media items. list.AddRange(GetLinkedMediaItems(obj)); } return list.Distinct(new ItemIdComparer()); }
Then we’ll add the GetLinkedMediaItems method,
protected virtual List<Item> GetLinkedMediaItems(Item item) { List<Item> mediaList = new List<Item>(); ItemLink[] itemLinkArray = item.Links.GetValidLinks() .Where(link => item.Database.Name.Equals(link.TargetDatabaseName, StringComparison.OrdinalIgnoreCase)) .ToArray(); foreach (ItemLink link in itemLinkArray) { try { Item target = link.GetTargetItem(); if (target == null || !target.Paths.IsMediaItem) continue; // add parent media items or folders Item parent = target.Parent; while(parent != null && parent.ID != ItemIDs.MediaLibraryRoot) { mediaList.Insert(0, parent); parent = parent.Parent; } mediaList.Add(target); } catch (Exception ex) { Log.Error("Error publishing reference link related media items", ex, typeof(AddItemAndMediaLinkReferences)); } } return mediaList; }
We can include this new pipeline by replacing the old one we reflected on.
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pipelines> <getItemReferences> <processor type="Sitecore.SharedSource.Pipelines.Publish.AddItemAndMediaLinkReferences, Sitecore.SharedSource" patch:instead="processor[@type='Sitecore.Publishing.Pipelines.GetItemReferences.AddItemLinkReferences, Sitecore.Kernel']"/> </getItemReferences> </pipelines> </sitecore> </configuration>
With this in place, media items referenced on any linked item will be published. You can further refine the logic to just consider data sources, perhaps by checking the path or template to ensure it’s a data source, to cut down on unintentional publishes.