Work with Unpublished PageData from code

Mari Jørgensen wrote about Breaking change in GetChildren() and I would like to share some of my findings when working with PageData from code when you want to use the built-in flow for publishing.

As you might know a Page Version can have VersionStatus Not Ready (CheckedOut), Ready To Publish (CheckedOut), Published and Previously Published. Since you in almost all cases are only interested in the published version most methods in DataFactory class only returns PageData objects with the published version.

Get unpublished pages and pages not in current language branch

GetPage() and GetChildren() returns page(s) published in the current language. You always have to use in a ILanguageSelector if you want to get PageData for another Language Branch than the current language branch.

PageDataCollection pages =
  DataFactory.Instance.GetChildren(
    CurrentPage.PageLink,  LanguageSelector.AutoDetect(true));

This will retrieve all children the same way as the Page Tree in the Structure Tab in Edit Mode. If there is no published version in the Current Content Language it will return PageData for the Master Langauge Branch, regardless of Publish Status.

Saving a page without publishing it

It is easy to create a new page and not publish it. This can be used for moderation where an Editor uses the publish button in Edit mode to approve.

PageData page = DataFactory.Instance.GetDefaultPageData(
                  rootpage.PageLink, "My Page Type");
page.PageName = "New Page";
DataFactory.Instance.Save(page, SaveAction.CheckIn,
                          AccessLevel.NoAccess);

SaveAction.CheckIn will make you page Ready to Publish.

Access Rights

Even if the current your is not an Editor you may give them Edit access rights to their pages.  It is very easy to add access rights for the current user after the page is saved. Note that all existing access right on the parent page will be inherited as usual.

PageAccessControlList acl = new PageAccessControlList(page.PageLink);
acl.Add(new AccessControlEntry(
          Membership.GetUser().UserName,
          AccessLevel.Read | AccessLevel.Edit | AccessLevel.Create | AccessLevel.Delete,
          SecurityEntityType.User));
acl.Save();

It is also easy to filter a collection and remove pages you should not be able to change.

new FilterAccess(AccessLevel.Edit).Filter(pages);

Another approach is to show the page but maybe disable the edit button.

bool canChange = page.QueryDistinctAccess(AccessLevel.Edit);

Page Versions and Unpublished Pages

Property Values for the Published Version of a Page is stored in different tables in the database than all other versions of the page. You need something called WorkID in your PageReference to load other versions of a page than the published version.

WARNING! Last time I checked GetPage() and GetPages() returned skeleton PageData objects, where all user defined properties are null, for unpublished pages if you did not have a WorkID.

This is an example of how you have to use PageVersion class to retrieve a list of all versions of a page. Each PageVersion has a PageReference with both PageID and WorkID

public static PageData GetLastVersion(PageReference pageRef)
{
    PageVersionCollection pageVersions = PageVersion.List(pageRef);
    PageReference lastVersion = pageVersions[0].ID;
    foreach (PageVersion pageVersion in pageVersions)
    {
        if (pageVersion.IsMasterLanguageBranch)
        {
            lastVersion = pageVersion.ID;
        }
    }
    return DataFactory.Instance.GetPage(lastVersion,
             LanguageSelector.AutoDetect(true));
}

When you have a PageReference with WorkID you can use it with GetPage() to retrieve other versions of a Page. Using and a LanguageSelector with fallback to Master Language is required to get around the filter.

Update a page without creating a new version

Sometimes you want to change a PageData object without creating a new version. In the example below UpdatePageFromForm copies values from text boxes to the page. If a value has changed it will be saved.

page = GetLastVersion(pageRef).CreateWritableClone();
UpdatePageFromForm(page);
if (page.IsModified)
{
    SaveAction saveAction = SaveAction.CheckIn;
    if (page.Status != VersionStatus.Published)
    {
        // Update existing version if it is not published
        saveAction = saveAction | SaveAction.ForceCurrentVersion;
    }
    DataFactory.Instance.Save(page, saveAction);
}

That’s all for now folks!

Please, leave a comment if you learned something. It is good for my blogging morale to know that someone got helped…

Bookmark and Share

Tags: , , , , , , , , , ,

  1. Mari’s avatar

    Good examples, thanks!

    Reply

  2. Nicklas’s avatar

    Keep up the posting, Fredrik! This is good stuff.

    Reply

  3. Petter’s avatar

    Yes keep up the posting! I read em all and hand them out right and left when people are having problems. Your the man!

    Reply

  4. Ross Neilson’s avatar

    Great stuff Fredrik this example really helped me out with something I was working on recently.

    Reply

  5. Ben’s avatar

    Thanks for this – it gave me a solution for a completely un-related problem.

    I was trying to cheekily update page properties during the PublishedPage event without creating a new version of the page… I figured that it was getting the right [undocumented] SaveAction argument and the code in your last example gave me a solution, i.e.

    DataFactory.Instance.Save(page, SaveAction.CheckIn | SaveAction.ForceCurrentVersion);

    Reply

  6. Emil’s avatar

    Life saver! :-)

    Reply

  7. Sharron Clemons’s avatar

    Good examples, thanks!

    Reply

  8. Ranjit J. Vaity’s avatar

    Hi,
    I am trying following:
    PageData page = itemDetailPageType.CreateWritableClone();
    page["ThumbNailImage"] = uploadProductThumbDirectory + unifiledThumbFile.Name;
    DataFactory.Instance.Save(page, SaveAction.CheckIn | SaveAction.ForceCurrentVersion);

    In here the thumb nail Image url gets saved to the page and is visible on the view mode. But Pages referring to this page gets “ThumbNailImage” ImageToUrl property as null.

    Unless I specifically publish this thumbnail page. I am capturing Page_Published event. tried Page_Publishing page but error comes “PageWorkId” is null.

    Please suggest!

    Thanks,
    Ranjit J. Vaity

    Reply

    1. Fredrik Haglund’s avatar

      Ranjit, what is your question?

      Explain what you try to acomplish instead, then maybe I can suggest a strategy to solve it.

      Reply

  9. Christian’s avatar

    Hi,

    I am having problems with a tricky scenario:
    1. A page is created in the master language branch (Swedish)
    2. The page gets translated to Norwegian and Danish
    3. The Swedish page is unpublished

    Now we don’t get any of the pages when using GetChildren (with or without LanguageSelector.Autodetect). Have you experienced the same problem with unpublished pages in master language branch but with versions still existing in other languages?

    Reply

    1. Fredrik Haglund’s avatar

      Yes, I can confirm that you always have to publish the Master Language before publishing any other Language Branches.

      But what exactly do you mean by “Unpublish”? Are you setting the Stop Publish date or do you use SaveAction to force the current version to another status?

      Reply

  10. Christian’s avatar

    Thanks for the answer and yes, I am manually setting a publishing date. If I try to access the page in Norwegian it works good but from get children method I only get null in return.

    I would expect to get the Norwegian branch if I explicitly ask for the no branch but I only get null.

    Thanks again!

    Christian, Iteam

    Reply

    1. Fredrik Haglund’s avatar

      That sounds strange. DataFactory.Instance.GetChildren() in EPiServer CMS 5 and later should return all published children regardless of their Start and Stop publish date. GetChildren should not return null at all, it should return an empty PageDataCollection.

      Reply