Thursday, September 4, 2008

Programmatically Update Page Layouts

I updated my page layouts that I deployed as a feature, but found that I could not overwrite the Layout File. The solution was to add a new page layout and then you would have to associate the new layout with every page on your site that used it.

Obviously with a site of any size you'd have a lot of manual work to get this done, so doing it programmatically makes for a much better (and thorough) approach. After you've got your new page layouts, you could do something like this (in your event receiver class):

public override void FeatureActivated(SPFeatureReceiverProperties properties) {
//replace current page layouts with new page layouts on all pages

SPWeb web = SPContext.Current.Web;

SwapPageLayout(web, "FullWidthContentWithTitleV1.aspx", "FullWidthContentWithTitleV2.aspx");
}

private void SwapPageLayout(SPWeb web, string oldPageLayoutName, string newPageLayoutName)
{
PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(web);

PageLayout[] layouts = pubWeb.GetAvailablePageLayouts();
PageLayout newLayout = null;
PageLayout oldLayout = null;

foreach (PageLayout layout in layouts)
{
if (layout.Name == newPageLayoutName)
{
newLayout = layout;
}

if (layout.Name == oldPageLayoutName)
{
oldLayout = layout;
}
}

DoPageLayoutSwap(web, oldLayout, newLayout);
}

private void DoPageLayoutSwap(SPWeb web, PageLayout oldLayout, PageLayout newLayout)
{
string checkInComment = "PageLayout Feature automatically updated this page layout";

if (PublishingWeb.IsPublishingWeb(web))
{
PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(web);



foreach (SPListItem listItem in pubWeb.PagesList.Items)
{
if(PublishingPage.IsPublishingPage(listItem))
{
PublishingPage page = PublishingPage.GetPublishingPage(listItem);
if (page.Layout.Name == oldLayout.Name)
{
page.CheckOut();
page.Layout = newLayout;
page.Update();
page.CheckIn(checkInComment);
SPFile pageFile = page.ListItem.File;

pageFile.Publish(checkInComment);

if(listItem.ModerationInformation.Status.Equals(SPModerationStatusType.Pending))
pageFile.Approve(checkInComment);
}
}
}
}

//visit children
foreach (SPWeb childWeb in web.Webs)
{
DoPageLayoutSwap(childWeb, oldLayout, newLayout);
}
}

10 comments:

Mark Stokes said...

Thanks for this post. I am trying to sort out this same issue, but have a question.

Do you deploy the page layout twice? So we will always have 2 pagelayout for each one in the masterpage library?

If so, can I create the page layout once in my project and just provision it twice with different names? or do you have 2 copies of the page layout in your project?

I am about to try this now, so we will see :)

Cheers

Mark Stokes
http://sharepointstudio.com

Paul said...

Mark,

In my case I'm reprovisioning with a new filename (you might want to do that if you are eliminating columns). If you don't want to do that then don't specify a new name for the existing file in your provisioning file. If you simply want to update/overwrite just do an stsdev -upgradesolution.

Hope that helps.

Mark Stokes said...

Thanks for the reply.

All I am trying to do is overwrite the page layout file. At the moment, this doesn't get overwritten. I have to manually upload, check in and approve the page layout.

I am not using STSDev in my project, so need to find a way to provision the file.

Thanks for the reply though.

Mark
http://sharepointstudio.com

Paul said...

Mark,

So I take it you aren't using a solution package? In the future I would definitely suggest doing that.

What you want to do is basically what I describe in the above post. You'll have to give your new file a new name and add it to the Page Layout Gallery. As long as the new page layout uses the same content type as the old page layout you can do a layout swap (either manually or using the above code). If you choose to use the above code since you didnt deploy your page as a solution/feature, you'll need to create a command line app or some other app that will execute the code.

The only other difference is you will swap out the

SPWeb web = SPContext.Current.Web;

line with code to open the web (since you don't have the SPContext).

I hope that helps.

Mark Stokes said...

I am using a solution package, but not the tool stsdev. I use stsadm to install my packages.

I have actually found a workable solution to this, using this blog post:
http://neganov.blogspot.com/2007/12/master-page-deployment-through-features.html

I can upload my updated file and check it in. I am just working on how to approve it in the same feature receiver.

Thanks for the replies :)

Mark
http://sharepointstudio.com

OnMing said...

Excause me, I'm student developer and I don't the above concept.

I would requre the coding of FullWidthContentWithTitleV1.aspx
and
FullWidthContentWithTitleV2.aspx
for in-deep investigation.

could you please share these?

Anonymous said...

Excause me, I'm student developer and I don't the above concept.

I would requre the coding of FullWidthContentWithTitleV1.aspx
and
FullWidthContentWithTitleV2.aspx
for in-deep investigation.

could you please share these?

Manotas said...

Hi,
We're using a similar approach to update our layouts.
The issue we're facing is due to the fact that at the bottom of pages the name and date of last modification si displayed, but since this update process is done by the System account, all other data is overwritten during the checking and approuving of the new layout...
Did you figure out something about this ?

Paul said...

Manotas,

No, we haven't had to deal with that issue, however a workaround (off the top of my head) would be to create a custom field that would track last modified date and the name of the person who did the modification and then add an event receiver that, when the item was updated, would check to see if the user was the System Account and ignore it, otherwise it would add the Name and Time to the custom field.

Make sense?

jasear said...

Guys you dont have to do this. The pagelayouts are stored in a list. If you get a SPListItem object representing your particular page layout you can basically uncustomise it.

Here is some sample code:

using (SPWeb web = site.OpenWeb())
{
SPList list = web.GetCatalog(SPListTemplateType.MasterPageCatalog);
var items = list.Items;
foreach (SPListItem item in items)
{
if (item["Name"].Equals("Pagelayout.aspx"))
{
if (item.File != null && item.File.CustomizedPageStatus == SPCustomizedPageStatus.Customized)
{
item.File.CheckOut();
item.File.RevertContentStream();
item.File.CheckIn("Uncustomised");
var spfile = item.File;
spfile.Update();
}
}
}

}

Basically the pagelayout is customised the first time someone accesses a page that references it i.e. it is stored in the db. The SPFile.RevertToContentstream() method uncustomises it.

So the next time someone accesses a page referencing that pagelayout SharePoint looks at the file system picks up a fresh copy and then again customises it in the db.

Hope this helps.