Child pages of forbidden PageTypes in EPiServer after PageType convertion

In my current project we do quite a bit of automatic copying of EPiServer pages as well as creation of child pages triggered by various web editor actions (part of the review functionality that Stefan Forsberg talks about in a series of posts starting with Reviewing pages in EPiServer – background). This is not a problem however, until the web administrators start to convert pages between different page types.

Imagine a set of two page types that are different enough to be separate entities, but similar enough to occationally trigger a need for converting between them. When created, these page types each get a set of automatically created child pages of various other page types; some of them allowed under both parent pages, and some of them allowed under just one of them.

Example of an article page shown with automatically created sub pages in the EPiServer edit mode page tree.

Example of an article simple page shown with automatically created sub pages in the EPiServer edit mode page tree.

As you can see in the images above, the Article page have an automatically created container page for regional amendment pages, as well as allowed chapter pages. These child pages are not allowed under the more light weight Article simple page; which in turn allows regional amendments directly under the parent without the container.

EPiServer’s build-in page type convertion tool will not remove pages of forbidden types

When a web administrator converts one of the pages mentioned above to the other, there is bound to be pages that would not ordinary exist under the new parent page; if done without convertion, a web editor would not be able to create them. Now however, we could end up with article pages having regional amendments and simple articles with chapters and container pages. So, how is this a problem? The forbidden pages are just lying there. Well, when using EPiServer’s own code for copying pages in codebehind we will have an issue when it comes to a sub page that is not allowed under the new parent page; even though the original pages exist in that manner. The result is an exception that, if thrown and not handled while in a complex and custom built revision process, may have serious consequenses for the article in question.

One thought is of course to alter EPiServer’s own page conversion process having it clean up after itself; the obvious drawback here being possible and involuntary loss of editorial content. An in many cases better and easier way, is to make the web editor about to trigger the page copying aware that there are bad things about to happen (or in some other way allow them to automatically clean the structure when they are familiar with the consequenses of losing data).

Warning message telling web editors about forbidden pages.

EPiServer System Message – a simple way of letting the editors know

Displaying system messages in EPiServer is rather straight forward. This is how we do it in my current project. According to GitHub history my former collegue Jonatan Larsson created the first version of it in the project back in the day.

ForbiddenPageTypesSystemMessage.ascx.cs

public partial class ForbiddenPageTypesSystemMessage : EPiServer.UserControlBase, ICustomPlugInLoader
{
  protected void Page_Load(object sender, EventArgs e)
  {
    if (!HasForbiddenChildren())
    {
      return;
    }
    PageBase.LoadComplete += EditMode_LoadComplete_PageTypeWarning;
  }

Create a standard ASP.Net user control and have it extend EPiServer’s UserControlBase rather than the predefined System.Web.UI.UserControl. It also needs to implement the ICustomPlugInLoader interface from EPiServer.PlugIn.

  private bool HasForbiddenChildren()
  {
    var parentPageType = PageType.Load(CurrentPage.PageTypeID);
    return DataFactory.Instance
        .GetChildren(CurrentPage.PageLink)
        .Any(child => !parentPageType.AllowedPageTypes.Any(pt => pt == child.PageTypeID));
  }

  protected void EditMode_LoadComplete_PageTypeWarning(object sender, EventArgs e)
  {
    var editPanel = sender as EditPanel;
    var message = ForbiddenChildrenMessage();
    editPanel.Validators.Add(new StaticValidator(message));
  }

To find if there are any children of forbidden page types under the current page, first retrieve the current page type (25). After that, it is just a matter of looping through the list of allowed page types matching them against the page types of the child pages. To add the warning message itself to the EPiServer edit mode, you can use edit panel validators (35). Just add a new StaticValidator with your desired message.

        
  private string ForbiddenChildrenMessage()
  {
    var forbidden = ForbiddenChildren();
    var infos = forbidden.Select(page => string.Format("{0} (ID='{1}', PageType='{2}')", page.PageName, page.PageLink, PageType.Load(page.PageTypeID).Name));
    return string.Concat("<strong>WARNING!</strong><br />This page has child pages of forbidden page types. This may occur if a page is converted from one page type to another.<br /><strong>You may not initiate the revision process before cleaning this up.</strong><br /><br /><u>Pages of forbidden types:</u><br />", string.Join("<br />", infos));
  }

  private IEnumerable<PageData> ForbiddenChildren()
  {
    var parentPageType = PageType.Load(CurrentPage.PageTypeID);
    return DataFactory.Instance
        .GetChildren(CurrentPage.PageLink)
        .Where(child => !parentPageType.AllowedPageTypes.Any(pt => pt == child.PageTypeID));
  }
}

To make this work, you will also need something like a GuiPlugIn to add your user control.

ForbiddenPageTypesSystemMessage.cs

[GuiPlugIn(
  DisplayName = PageTypeSystemMessageTabName,
  Description = "Message that is shown when a page have children of forbidden page types.",
  Area = PlugInArea.EditPanel,
  Url = "~/CodeSample/.../ForbiddenPageTypesSystemMessage.ascx"
)]
public partial class ForbiddenPageTypesSystemMessage
{
  private const string PageTypeSystemMessageTabName = "warning_message_forbidden_page_types";

  public PlugInDescriptor[] List()
  {
    if (!IsAPageThatShouldHaveThisWarning)
    {
      return new PlugInDescriptor[] { };
    }

    var editPanel = HttpContext.Current.Handler as EditPanel;
    editPanel.LoadComplete += EditPanel_LoadComplete;

    return new[] { PlugInDescriptor.Load(GetType()) };
  }

The trick is to hide the action tab helping with the warning message as soon as it has been loaded.

  protected void EditPanel_LoadComplete(object sender, EventArgs e)
  {
    var strip = ((Control)sender).FindControlRecursive("actionTab") as TabStrip;

    if (strip == null)
    {
      throw new InvalidOperationException("Unable to find EPiServer-tab named 'actionTab'.");
    }
            
    var prevSelected = strip.SelectedTab;
    strip.SetSelectedTab(PageTypeSystemMessageTabName);
    strip.ActiveTab.Visible = false;
    strip.SetSelectedTab(prevSelected);
  }
}