Building a multi-lingual 'Last Modified' Footer for MOSS
Anyone familiar with my work will know that most of my efforts just recently have been directed towards a MOSS 2007 Publishing Site for a major multi-national company. The site is used in several countries, and uses Site Variations to provide multi-lingual content.
Just to make life interesting, I was pulled into a discussion with my client about the footer on their content pages. A user spotted that the ‘Last Modified Date’ field on the page footer is never ever updated. Not very much investigation later, it’s clear that this field has never worked. The guys who initially set up this particular site put it on the content pages, but never got it hooked up to anything before they cleared out.
Whilst we’re at it, the client also points out that he doesn’t like the wording of the label, the layout needs amending, and the Publishing Contact field should actually be a ‘Last Modified By’ field. In keeping with Publishing Contact, the displayed name should provide a hyperlink to the user information page. Time to get working then!
‘Last Modified Date’ is a bit of an issue. There is no Site Column in the underlying Content Type I can get this from.
A swift visit to my favourite search engine quickly revealed the answer. A very helpful blog article by Jag at this address describes how to write a custom footer control that obtains Last Modified Date and Person information from Sharepoints SPFile object.
This is an almost perfect find, but my client site has two problems that Jag doesn’t address;
First, my client site is a multi-lingual Publishing Portal using Site Variations. I can’t hard code my label information as his sample does. I need to find a way to get my labels translated into the correct language for any given variation.
Second, I need to hyperlink my username to a user information page. Jags control just has a flat text name.
1. Solving the Translation Issue
Have you ever looked at the Page Settings for your pages and noticed the Historical Detail section? It looks like this;
But that same section in my French Variation Site looks like this;
Those ‘Last Modified’ labels are just what I need. They are strings held in the language packs that my variations use. I can display them in my control by using a Sharepoint Encoded Literal control, and Sharepoint will handle the translation for me across my sites.
I register the library I need in my control;
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
Then I replace the hard coded display text with this for Last Modified Date;
<SharePoint:EncodedLiteral runat='server' ID='literalLastModifiedDate1' text='<%$Resources:cms,pagesettings_modifieddate_label%>' EncodeMethod='HtmlEncode'/>
…and this for Last Modified By;
<SharePoint:EncodedLiteral runat='server' ID='literalLastModifiedBy1' text='<%$Resources:cms,pagesettings_modifiedby_label%>' EncodeMethod='HtmlEncode'/>
So, I’m pulling text resources out of the same place that the multi-language Sharepoint pages use. Seems like a good approach to me.
That solves my multi-lingual problems. Now for the url link.
2. Creating a hyperlink to the User Information Page
My original page displayed the Publishing Contact site column in a User control. That control generated an appropriate url automatically. Alas there’s different data under the covers, and putting my Last Modified By user in the same control doesn’t give me a url. I need to solve this problem manually.
Looking at the source of the url in my original page, I need a url that looks something like this;
<nobr><span><A ONCLICK="GoToLink(this);return false;" HREF="/Your/Folders/ _layouts/userdisp.aspx?ID=1">Some User Or Other</A><img border="0" height="1" width="3" src="/_layouts/images/blank.gif" alt="" /><img border="0" height="12" width="12" src="/_layouts/images/blank.gif" alt=""/></span></nobr>
No need to re-invent the wheel, so as long as I can get hold of my users display name, and Member ID I can slot that into a similar string, and we’re in business. The challenge is where to get hold of the Member ID.
Jag kindly included a way to get the user name in his code sample. However the file.ModifiedBy property returns you an SPUser instance, which gives me everything I need. So, instead of ;
string LastModifiedByMemberID = file.ModifiedBy.ToString();
I’m going to do this;
SPUser user = file.ModifiedBy;
LastModifiedBy = user.Name.ToString();
LastModifiedByMemberID = user.ID.ToString();
I get my ID and name in one go.
The Complete Code
<%@ Control Language="C#" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="Microsoft.SharePoint.Administration" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<script runat="server">
string LastModifiedDate = String.Empty;
string LastModifiedBy = String.Empty;
string LastModifiedByMemberID = String.Empty;
public string FileName()
{
return System.Web.HttpContext.Current.Request.Url.AbsolutePath;
}
void Page_Load(object sender, EventArgs e)
{
string PageName = FileName();
using(SPSite RootSite = new SPSite(Page.Request.Url.ToString()))
{
using (SPWeb SiteCollection = RootSite.OpenWeb())
{
string path = RootSite.MakeFullUrl(PageName);
SPFile file = SiteCollection.GetFile(path);
LastModifiedDate = file.TimeLastModified.ToShortDateString();
SPUser user = file.ModifiedBy;
LastModifiedBy = user.Name.ToString();
LastModifiedByMemberID = user.ID.ToString();
}
}
}
</script>
<table class="ms-informationbar">
<tr>
<td>
<SharePoint:EncodedLiteral runat='server' ID='literalLastModifiedDate1' text='<%$Resources:cms,pagesettings_modifieddate_label%>' EncodeMethod='HtmlEncode'/>
<%= LastModifiedDate %>
</td>
<td>
<SharePoint:EncodedLiteral runat='server' ID='literalLastModifiedBy1' text='<%$Resources:cms,pagesettings_modifiedby_label%>' EncodeMethod='HtmlEncode'/>
<nobr><span><A ONCLICK="GoToLink(this);return false;" HREF="../_layouts/userdisp.aspx?ID=<%= LastModifiedByMemberID %>"><%= LastModifiedBy %></A><img border="0" height="1" width="3" src="/_layouts/images/blank.gif" alt="" /><img border="0" height="12" width="12" src="/_layouts/images/blank.gif" alt=""/></span></nobr>
</td>
</tr>
</table>
To deploy this code
Create an ascx file in the TEMPLATE->CONTROLTEMPLATES folder under your 12 hive with the above code.
Register the user control where you need to use it. This could be a masterpage, or in my case a page layout;
<%@ Register TagPrefix="MyCustomStuff" TagName="MyCustomFooter" src="~/_controltemplates/MyCustomFooter.ascx" %>
The control is now placed at the bottom of my page with this code;
<MyCustomStuff:MyCustomFooter id="MyCustomFooter1" runat="server" EnableViewState="true">
</MyCustomStuff:MyCustomFooter>
The Results
Now the footer in my English speaking Variations look like this;
In the French speaking Variations it looks like this;
Update; Care should be taken using the techniques I mention here. See my later post https://jamiemcallister.com/Reflections-on-the-use-of-Language-Pack-Resources
Further Update; I knew that using Sharepoints own language resources was a tacky hack (though kind of justified in this case as the text is very standard). If you're not happy with the wording of the labels in this control (or worry the resources may get altered by a service pack etc) you can create your own language resources. See https://jamiemcallister.com/post/Mutli-lingual-Page-Layout-Text-for-Site-Variations-Part-2---Using-Resource-Files for more details.