This post rounds out the series with presentation magic! Following my hypothetical 😉 theme, the next challenge is to somehow translate the presentation from webforms to MVC. Along with moving content, we would also have to set the presentation of these items.
I know that in my hypothetical situation, I have made it a bit more difficult by not only upgrading to Sitecore 8 but also moving from webforms to MVC. Life would be easier if I just stayed with webforms!!! Nope!
I am glad in this situation I decided to go with Sitecore MVC. Was it the right decision, absolutely! Was it easy, absolutely not!
With the final solution it makes it feasible to have an infrastructure which forces re-usability and test-ability. No complaints about that.
Now back to presentation. The first thing you need to do is to keep a tally of the sublayouts to views translations in a spreadsheet. There might be cases where you have consolidate multiple sublayouts into one view or the other way round. You might also have a need for one sublayout to be replaced by multiple views.
In my case, I went with this simple assumption: For a specific kind of presentable item, the standard values presentation on that template should match the functionality in the webforms solution and the MVC solution. Both the standard values presentation (in webforms and MVC) might not match in terms of the number of presentation items but they will match in what they render.
Once that simple rule is set in stone (well for the most part, there will always be exceptions, right?), the rest should be achievable by scripts.
I am not an MVC guru by any means and I feel that in technology its not what you know its how you learn it!. I pride myself in picking up technology as I work, as do a lot of Sitecore folks I know!
For scripting I always use Sitecore PowerShell. I love using PowerShell commandlets and rolling out libraries in solutions to get complicated tasks done and have a repeatable process.
In this scenario we are going to do the following:
- Traverse through the current content tree ( you define what to go through, but typically be sitecore\content\home)
- For each item we look to see if there is presentation on the standard values, if so we save it
- We do a diff of the standard values and the item in questions and store the diff
- At the end of this process, we should have an items.xml (diff’ed presentation for each item) and a standardvalues.xml
- Take these XML definitions and transfer them to the new site
- Build an xml file called presentationdefinitions.xml which has the translation from sublayouts to views
- At this point the assumption is that you have set the standard values on the new Sitecore 8 instance, so that template items have base UI
- Traverse through the new content tree (this would be the same since we are importing content package from current Sitecore 6.5+)
- While traversing the tree which should be the same as the current tree, we reset presentation
- Look up the item in the items.xml from the current content site, find the translated presentation
- Set the current item with the diff’ed and translated (sublayouts to views) presentation items
- Publish and enjoy!
Current PowerShell Commandlet in Sitecore 6.5+
The commandlet which I have in the Sitecore 6.5+ solution shown below. The main purpose is to catalog standard values, use the standard values to get diff’s on item presentation and also keep tabs of unique sublayouts used across the current 6.5+ website.
[Cmdlet("Store", "Presentation")] public class PresentationCommandlet : BaseCommand { [Parameter] public string PassID { get; set; } XDocument docStandardValues = new XDocument(new XDeclaration("1.0", "utf-8", "yes"), new XElement("templates")); XDocument docItems = new XDocument(new XDeclaration("1.0", "utf-8", "yes"), new XElement("items")); XDocument docUniqueRenderings = new XDocument(new XDeclaration("1.0", "utf-8", "yes"), new XElement("renderings")); protected override void ProcessRecord() { //get the master database Sitecore.Data.Database master = Sitecore.Configuration.Factory.GetDatabase("master"); var selectItem = master.GetItem(new ID(PassID)); if (selectItem != null) { LoopForChildren(selectItem); } docStandardValues.Save(@"D:\inetpub\Sitecore\Website\temp\xml\StandardValues.xml"); docItems.Save(@"D:\inetpub\Sitecore\Website\temp\xml\Items.xml"); docUniqueRenderings.Save(@"D:\inetpub\Sitecore\Website\temp\xml\UniqueRenderings.xml"); } private XElement StoreStandardValuesPresentation(TemplateItem template) { XElement svaluesNode = new XElement("temp"); Item standardValues = template.StandardValues; if (!IsStandardValuesLoaded(template.ID.ToString(), ref svaluesNode) && standardValues != null) { svaluesNode = new XElement("template", new XAttribute("id", template.ID.ToString()), new XAttribute("nm", template.Name)); string rawLayout = Sitecore.Data.Fields.LayoutField.GetFieldValue(standardValues.Fields[Sitecore.FieldIDs.LayoutField]); Sitecore.Layouts.LayoutDefinition layout = Sitecore.Layouts.LayoutDefinition.Parse(rawLayout); for (int i = 0; i < layout.Devices.Count; i++) { Sitecore.Layouts.DeviceDefinition device = layout.Devices[i] as Sitecore.Layouts.DeviceDefinition; if (device != null) { for (int j = 0; j < device.Renderings.Count; j++) { Sitecore.Layouts.RenderingDefinition rendering = device.Renderings[j] as Sitecore.Layouts.RenderingDefinition; if (rendering != null) { if (!string.IsNullOrEmpty(rendering.ItemID)) { Database master = Sitecore.Configuration.Factory.GetDatabase("master"); Item renderingItem = master.GetItem(rendering.ItemID); string dataSource = string.IsNullOrEmpty(rendering.Datasource) ? "" : rendering.Datasource; XElement rNode = new XElement("r", new XAttribute("nm", renderingItem.DisplayName) , new XAttribute("ph", rendering.Placeholder), new XAttribute("id", rendering.ItemID), new XAttribute("ds", dataSource)); svaluesNode.Add(rNode); } } } } } docStandardValues.Element("templates").Add(svaluesNode); } return svaluesNode; } private void StoreItemPresentation(Item itm, XElement svNode) { if (itm != null) { XElement itmNode = new XElement("item", new XAttribute("id", itm.ID.ToString()), new XAttribute("nm", itm.Name)); string rawLayout = Sitecore.Data.Fields.LayoutField.GetFieldValue(itm.Fields[Sitecore.FieldIDs.LayoutField]); Sitecore.Layouts.LayoutDefinition layout = Sitecore.Layouts.LayoutDefinition.Parse(rawLayout); for (int i = 0; i < layout.Devices.Count; i++) { Sitecore.Layouts.DeviceDefinition device = layout.Devices[i] as Sitecore.Layouts.DeviceDefinition; if (device != null) { for (int j = 0; j < device.Renderings.Count; j++) { Sitecore.Layouts.RenderingDefinition rendering = device.Renderings[j] as Sitecore.Layouts.RenderingDefinition; if (rendering != null) { if (!string.IsNullOrEmpty(rendering.ItemID)) { Database master = Sitecore.Configuration.Factory.GetDatabase("master"); Item renderingItem = master.GetItem(rendering.ItemID); string dataSource = string.IsNullOrEmpty(rendering.Datasource) ? "" : rendering.Datasource; //check if there are rendering which match the standard values exactly, if they donot record them //we are only worried about one device, the default device, you can extend it further for all devices and versions var svItems = (from t in svNode.Descendants("r") where t.Attribute("ph").Value == rendering.Placeholder && t.Attribute("id").Value == rendering.ItemID && t.Attribute("ds").Value == dataSource select t).ToList(); if (svItems != null && svItems.Count() == 0) { StoreUniquePresentationDetails(renderingItem.DisplayName, rendering.ItemID); try { //depending on your situation, you would want to do before j-1 or after j+1 so that you get the right placement Sitecore.Layouts.RenderingDefinition nextRendering = device.Renderings[j - 1] as Sitecore.Layouts.RenderingDefinition; string nextDataSource = string.IsNullOrEmpty(nextRendering.Datasource) ? "" : nextRendering.Datasource; Item nextRenderingItem = master.GetItem(nextRendering.ItemID); XElement rNode = new XElement("r", new XAttribute("nm", renderingItem.DisplayName) , new XAttribute("ph", rendering.Placeholder), new XAttribute("id", rendering.ItemID), new XAttribute("ds", dataSource), new XElement("aftr", new XAttribute("nm", nextRenderingItem.DisplayName) , new XAttribute("ph", nextRendering.Placeholder), new XAttribute("id", nextRendering.ItemID), new XAttribute("ds", nextDataSource))); itmNode.Add(rNode); } catch { XElement rNode = new XElement("r", new XAttribute("nm", renderingItem.DisplayName) , new XAttribute("ph", rendering.Placeholder), new XAttribute("id", rendering.ItemID), new XAttribute("ds", dataSource)); itmNode.Add(rNode); } } } } } } } docItems.Element("items").Add(itmNode); } } private void StoreUniquePresentationDetails(string renderingName, string renderingID) { var items = (from t in docUniqueRenderings.Descendants("r") where t.Attribute("id").Value == renderingID select t).ToList(); if (items != null && items.Count() == 0) { XElement itmNode = new XElement("r", new XAttribute("id", renderingID), new XAttribute("nm", renderingName)); docUniqueRenderings.Element("renderings").Add(itmNode); } } private bool IsStandardValuesLoaded(string templateID, ref XElement svNode) { var svItems = (from t in docStandardValues.Descendants("template") where t.Attribute("id").Value == templateID select t).ToList(); if (svItems != null && svItems.Count() == 1) svNode = svItems.FirstOrDefault(); return (svItems!= null && svItems.Count() == 1); } private void LoopForChildren(Item itm) { if (!string.IsNullOrEmpty(itm[Sitecore.FieldIDs.LayoutField])) { XElement svNode = StoreStandardValuesPresentation(itm.Template); //get svNode if not recorded, create and get it //get the standard values node //look at the current item's layout details //ignore whats in the standard values //add whats not in the standard values StoreItemPresentation(itm, svNode); } //Iterate though children Sitecore.Collections.ChildList childList = itm.Children; foreach (Item child in childList) { LoopForChildren(child); } } }
This commandlet produced the following sample outputs.
Items.xml
UniqueRenderings.xml
StandardValues.xml
Once this is done transfer the items.xml file to a location under the new Sitecore 8.x site. Define the mapping for the old sublayout to the new renderings and store them in PresentationDefinitions.xml
PresentationDefinitions.xml
This is just a prototype but you could essentially customize it to your heart’s content. Store parameters, have the ability to push to different place holder than the origin etc.
Once this is done, you will be running the following script on the new Sitecore 8.x site.
[Cmdlet("Fix", "Presentation")] public class FixPresentation : PSCmdlet { [Parameter(Mandatory = true)] [ValidateNotNullOrEmpty] public string XMLFolderPath { get; set; } [Parameter(Mandatory = true)] [ValidateNotNullOrEmpty] public string RootID { get; set; } XDocument docItems; XDocument docPresentationDefinitions; const string ItemsFileName = "items.xml"; const string PresentationDefinitionsFileName = "presentationdefinitions.xml"; protected override void ProcessRecord() { if (ValidFolderFiles(XMLFolderPath)) { //get the master database Database master = Factory.GetDatabase("master"); var selectItem = master.GetItem(new ID(RootID)); if (selectItem != null) { LoopForChildren(selectItem); } } else { WriteObject(string.Format("Could not find {0} and {1} in {2} folder.", ItemsFileName, PresentationDefinitionsFileName, XMLFolderPath)); } } private void ResetPresentation(Item itm) { Listfields = new List (); fields.Add(itm.Fields[FieldIDs.LayoutField]); //shared layout field //reset versioned layout fields fields.AddRange( from v in (IEnumerable - )itm.Versions.GetVersions(true) select v.Fields[FieldIDs.FinalLayoutField]); using (new Sitecore.SecurityModel.SecurityDisabler()) { using (new EditContext(itm)) { foreach (Field layoutFieldsToReset in fields) { layoutFieldsToReset.Item.Editing.BeginEdit(); layoutFieldsToReset.Reset(); layoutFieldsToReset.Item.Editing.EndEdit(); } } } } private bool IsFoundInItemXml(string itemID, ref XElement itmNode) { var items = (from i in docItems.Descendants("item") where i.Attribute("id").Value == itemID select i).ToList(); if (items != null && items.Count() == 1) itmNode = items.FirstOrDefault(); return (items != null && items.Count() == 1); } private bool FoundInPresentatinDefinitionsXml(string itemID, ref XElement itmNode) { var items = (from i in docPresentationDefinitions.Descendants("pd") where i.Attribute("oldid").Value == itemID select i).ToList(); if (items != null && items.Count() == 1) itmNode = items.FirstOrDefault(); return (items != null && items.Count() == 1); } private void FixItemPresentation(Item itm, XElement itmNode) { //loop through the r items find replacements, add to the item layout using (new Sitecore.SecurityModel.SecurityDisabler()) { // Get the layout definitions and the device definition LayoutField layoutField = new LayoutField(itm.Fields[Sitecore.FieldIDs.LayoutField]); LayoutDefinition layoutDefinition = LayoutDefinition.Parse(layoutField.Value); DeviceDefinition deviceDefinition = layoutDefinition.GetDevice("{FE5D7FDF-89C0-4D99-9AA3-B5FBD009C9F3}"); foreach (XElement r in itmNode.Descendants("r")) { //for every sublayout/rendering we need to replace, find and entry from the presentation definitions xml XElement xmlItem = new XElement("temp"); if (FoundInPresentatinDefinitionsXml(r.Attribute("id").Value, ref xmlItem)) { //this allows us to have one old sublayout/rendering to be replaced by multiple new sublayouts/renderings foreach (XElement newr in xmlItem.Descendants("r")) { //Create a RenderingDefinition and add the reference of sublayout or rendering RenderingDefinition renderingDefinition = new RenderingDefinition(); //future enhancement can include changing the place holder and datasource renderingDefinition.ItemID = newr.Attribute("id").Value; //pull the value for the new rendering and keep the same ph and ds //Set placeholder where the rendering should be displayed renderingDefinition.Placeholder = r.Attribute("ph").Value; // Set the datasource of sublayout, if any renderingDefinition.Datasource = r.Attribute("ds").Value; XElement aftr = (from i in r.Descendants("aftr") select i).FirstOrDefault(); if (aftr != null) { //find the matching control in presentation details // find that matched new rendering id and find it in the layout rendering // if found, insert after that index XElement xmlAfterItem = new XElement("temp"); if (FoundInPresentatinDefinitionsXml(aftr.Attribute("id").Value, ref xmlAfterItem)) { XElement xmlNewAfterItem = xmlAfterItem.Descendants("r").FirstOrDefault(); RenderingDefinition afterRendering = null; if (string.IsNullOrEmpty(aftr.Attribute("ds").Value)) { afterRendering = (from RenderingDefinition rd in deviceDefinition.Renderings where rd.ItemID == xmlNewAfterItem.Attribute("id").Value && rd.Datasource == null && rd.Placeholder == aftr.Attribute("ph").Value select rd).FirstOrDefault(); } if (afterRendering == null) //if we couldnt find the item with ds as null try with ds = "" { afterRendering = (from RenderingDefinition rd in deviceDefinition.Renderings where rd.ItemID == xmlNewAfterItem.Attribute("id").Value && rd.Datasource == aftr.Attribute("ds").Value && rd.Placeholder == aftr.Attribute("ph").Value select rd).FirstOrDefault(); } if (afterRendering != null) { var index = deviceDefinition.Renderings.IndexOf(afterRendering); if (index >= 0) { //insert the rendering at a specific location deviceDefinition.Renderings.Insert((index + 1), renderingDefinition); continue; //skip to next } } } } //Add the RenderingReference to the DeviceDefinition deviceDefinition.AddRendering(renderingDefinition); } } } // Save the layout changes using (new EditContext(itm)) { layoutField.Value = layoutDefinition.ToXml(); } } } public void AddRendering(DeviceDefinition deviceDefinition, RenderingDefinition renderingDefinition) { Assert.ArgumentNotNull(renderingDefinition, "renderingDefinition"); ArrayList renderings = deviceDefinition.Renderings; if (renderings == null) { renderings = new ArrayList(); deviceDefinition.Renderings = renderings; } renderings.Add(renderingDefinition); } private void LoopForChildren(Item itm) { if (!string.IsNullOrEmpty(itm[Sitecore.FieldIDs.LayoutField])) { WriteObject("Processing: " + itm.Paths.Path); XElement xmlItem = new XElement("temp"); if (IsFoundInItemXml(itm.ID.ToString(), ref xmlItem)) { //assuming that we already setup the standard values for all applicable templates with display //reset presentation ResetPresentation(itm); if (xmlItem.Descendants("r").Any()) { FixItemPresentation(itm, xmlItem); } } } //Iterate though children Sitecore.Collections.ChildList childList = itm.Children; foreach (Item child in childList) { LoopForChildren(child); } } private bool ValidFolderFiles(string path) { bool valid = false; if (Directory.Exists(path)) { string[] files = Directory.GetFiles(path, "*.xml", SearchOption.AllDirectories); if (files.Where(f => f.ToLower().Contains(ItemsFileName) || f.ToLower().Contains(PresentationDefinitionsFileName)).Count() == 2) { valid = true; foreach (string s in files) { if (s.ToLower().Contains(ItemsFileName)) docItems = XDocument.Load(s); else if (s.ToLower().Contains(PresentationDefinitionsFileName)) docPresentationDefinitions = XDocument.Load(s); } } } return valid; } }
There you go, most of the work is done. There will be one off’s which you have to either run a script or manually do them.
If you have any questions or comments please let me know.