I am not sure if this type of information has been posted before. I looked around for a good 2 days or so trying to find a solution to my particular problem and could not. Here is a short description of the problem.
My current project involves a public facing WCM site. The site has 2 zones, one for anonymous access and forms users and another zone for AD users.
The content owners all have AD accounts and will be managing the content via the AD zone. Many of the layout pages use Content Editor Web Parts. When a user selects an image or a link using a CEWP, the relative url that appears in the text box is replaced with an absolute url in the database (genius!). Why this happens is beyond me but the end result was unacceptable. Since AD users were uploading content to the site, the url's were being converted to the absolute url of the AD site, which of course is not accessible to anonymous or forms users. My solution was to create an event handler to handle the OnUpdating event of the Page (which is just a list item) and cycle thru the CEWP's on the page to replace any absolute url's with relative ones.
I began by creating my Feature file:
<!-- _lcid="1033" _version="12.0.4518" _dal="1" -->
<!-- _LocalBinding -->
<Feature
DefaultResourceFile="core"
Description="Changes absolute urls to relative urls for all Content Editor Web Parts used in the Pages library."
Id="E4E20B7F-F948-4b38-893B-F9F1AA202B84"
ReceiverAssembly="Ratman.SharePoint.EventHandlers.ForceRelativeUrl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxtokenxxx"
ReceiverClass="Ratman.SharePoint.EventHandlers.ForceRelativeUrl.ForceRelativeUrlFeature"
Scope="Web"
SolutionId="7B09E255-2BFC-43d5-86AB-3154FC60E443"
Title="Ratman Force Relative Url"
Version="1.0.0.0"
xmlns="http://schemas.microsoft.com/sharepoint/">
</Feature>
Notice that the above Feature definition defines an assembly. In this assembly we will be hooking up our event handler to the appropriate library in a single site. The reason I did it this way was because the List Type Id of the library I am hooking it up to is 850 and is not in the list of available List Type Id's available when defining an event handler via XML. Well, now to think of it I am not sure if I actually confirmed that. Either way, activating event handlers this way gives you more control over "where" it is activated. For example, when defining an event handler via XML, you must provide (as mentioned above) a List Type Id. This will attach the event handler to all lists of a certain type. For example, were we to define 101, the event handler would be attached to all document libraries. This may not be the behavior that we want. With all that said it is also important to note that the List Template Id for a Pages library is 850. This number can be viewed by looking at the source of a list view of a Pages library. I have not found a single reference to this Id in any MSDN documentation so be weary. I am guessing it may be hard-coded in many places.
The following code is my Feature handler, obviously replace xxxtokenxxx with your own token:
public class ForceRelativeUrlFeature : SPFeatureReceiver
{
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
// get a reference to the current SPWeb
using (SPWeb _SPWeb = SPContext.Current.Web)
{
// get a reference to the "Pages" library
SPDocumentLibrary _SPDocumentLibrary = (SPDocumentLibrary)_SPWeb.Lists["Pages"];// if the "Pages" library exists
if (_SPDocumentLibrary != null)
{
// create an empty Guid
Guid _ItemUpdatingGuid = Guid.Empty;// enumerate thru all of the event receiver definitions, attempting to
// locate the one we are adding
foreach (SPEventReceiverDefinition _SPEventReceiverDefinition in _SPDocumentLibrary.EventReceivers)
{
// if we find the event receiver we are about to add
// record its Guid
if (_SPEventReceiverDefinition.Type == SPEventReceiverType.ItemUpdating &&
_SPEventReceiverDefinition.Assembly == "Ratman.SharePoint.EventHandlers.ForceRelativeUrl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxtokenxxx" &&
_SPEventReceiverDefinition.Class == "Ratman.SharePoint.EventHandlers.ForceRelativeUrl.ForceRelativeUrlItem")
{
_ItemUpdatingGuid = _SPEventReceiverDefinition.Id;
}
}// if we did not find the event receiver we are adding, add it
if (_ItemUpdatingGuid == Guid.Empty)
{
try
{
_SPDocumentLibrary.EventReceivers.Add(SPEventReceiverType.ItemUpdating, "Ratman.SharePoint.EventHandlers.ForceRelativeUrl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxtokenxxx", "Ratman.SharePoint.EventHandlers.ForceRelativeUrl.ForceRelativeUrlItem");
}
catch (Exception ex)
{
Debug.Write(ex.Message);
}
}
}
}
}
I made a few assumptions in this code, some of which you may want to remove. I assumed that the Pages library is the name of the Pages library. I assumed this because if you look at the list definition of a Pages library, located @ C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\Publishing you will see that the UNIQUE attribute is set to true. What this means is that you really can have only one of these types of list on a single site. It has also been suggested that many of the publishing features themselves will not work if this is not the name of the library. Here is the Pages library list template definition as it is defined on above on the file system.
<!-- _lcid="1033" _version="12.0.4518" _dal="1" -->
<!-- _LocalBinding -->
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ListTemplate
Name="Pages"
Type="850"
BaseType="1"
Hidden="TRUE"
Unique="TRUE"
SecurityBits="11"
DisplayName="$Resources:cmscore,List_Pages_DisplayName"
Description="$Resources:cmscore,List_Pages_Description"
Image="/_layouts/images/itdl.gif">
</ListTemplate>
</Elements>
The code after that is pretty straight forward.
- Get a reference to the current SPWeb.
- Get a reference to the Pages library (I assume this name) and check if it exists.
- Check to make sure that the event handler you are attaching to this library is not already attached.
- If the event handler is not found, attach it.
Here is the code for the event handler itself:
public class ForceRelativeUrlItem : SPItemEventReceiver
{
public override void ItemUpdating(SPItemEventProperties properties)
{
this.DisableEventFiring();// 850 is the ListTemplateId of the OOB Pages library on a publishing site
if (Int32.Parse(properties.ListItem.ParentList.BaseTemplate.ToString()) == 850)
{
// get a reference to the list item (the page in this case)
SPListItem _SPListItem = properties.ListItem;// get a reference to the containing SPWeb
using (SPWeb _SPWeb = _SPListItem.Web)
{
// get a reference to the the web part manager on the page
using (SPLimitedWebPartManager _SPLimitedWebPartManager = _SPWeb.GetLimitedWebPartManager(_SPListItem.Url, PersonalizationScope.Shared))
{
// loop thru all of the web parts on the page and up update
// all of the CEWP's
foreach (Microsoft.SharePoint.WebPartPages.WebPart _WebPart in _SPLimitedWebPartManager.WebParts)
{
if (_WebPart.GetType().Equals(typeof(ContentEditorWebPart)))
{
using (ContentEditorWebPart _ContentEditorWebPart = (ContentEditorWebPart)_WebPart)
{
// get the contents of the CEWP
string _ContentString = _ContentEditorWebPart.Content.InnerText;// remove the absolute url
_ContentString = _ContentString.Replace(_SPWeb.Site.RootWeb.Url, "");
// create an Xml element to use to update the CEWP
XmlDocument _XmlDocument = new XmlDocument();
XmlElement _XmlElement = _XmlDocument.CreateElement("MyElement");
_XmlElement.InnerText = _ContentString;// update the Content property of the CEWP
_ContentEditorWebPart.Content = _XmlElement;try
{
_SPLimitedWebPartManager.SaveChanges(_ContentEditorWebPart);
}
catch (Exception ex)
{
Debug.Write(ex.Message);
}}
}
}
}
}
}this.EnableEventFiring();
}
}
The code here is a little more complex, but not terribly difficult.
- Disable event firing. Since we are updating this item, we wouldn't want to get caught in some sort of loop, since this is the OnUpdating event handler :)
- Ensure that the List Template Id of the list containing this item is in fact 850. This is really just a double check, since we know it is true since we hooked the event handler up to a Pages library in the Feature handler defined above.
- Get a reference to the Web Part Manager on the page itself which is really just an item in the Pages library.
- Loop thru the web parts on the page and identify CEWP's.
- Update the CEWP's content. This was a little tricky. You can't just update the XML directly, you need to create the new XML using an XmlElement and update the XmlElement that defines the Content property of the CEWP. This part took me a while. For some reason intellisense lead me to think that you can update the XML directly.
- Re-enable event firing.
It is important to be sure that all of your disposable objects are disposed of. If there is a single gotcha to SharePoint coding (coding, not development, that's another set of gotchas) it is this. Memory leaks can sprout up quickly and are hard to trace after code is deployed. I hope you find this techniques as useful as I did. I am sure there are some improvements that can be made. This was a relatively quick fix to a VERY BIG problem.
The end result here is that all images and links created via a CEWP on a page in the Pages library on a publishing will contain relative url's as opposed to absolute url's!