Multi Site Rich Text Editor Stylesheet in Sitecore 6.4

April 29, 2011
Tags: Sitecore, Wysiwyg

So while doing a dry run of an upgrade from Sitecore 6.2 to Sitecore 6.4, I noticed something that stopped working in my rich text editor: dynamic stylesheets. I have a large multi-site platform and it's using code from the dynamic stylesheets SDN Article to set a unique stylesheet in the wysiwyg editor for each site. The Telerik codebase has changed a bit and the rich text editor has also. So I'll go through what I did to get it working so you can shortcut the hassle. The first thing you'll need to know is that the file loading the rich text editor is now located at EditorPage.aspx instead of Default.aspx. The class supporting this page (Sitecore.Shell.Controls.RichTextEditor.EditorPage) is also different from it's predecessor (Sitecore.Shell.Controls.RADEditor.RADEditor). I've stubbed out the page directive you'll need to update below:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="MultiSiteEditorPage.aspx.cs" Inherits="YourNamespace.MultiSiteEditorPage" %>

I've also pulled the EditorPage codebehind class together that you'll need to add to your library to set the EdtiorPage.aspx to. This class is mostly just a copy of Sitecore.Shell.Controls.RichTextEditor.EditorPage with a few specific additions. One such change is in the "OnLoad" method there is a method call to the "this.AddSiteSpecificCSS();". This method is custom coded for you to add in your own stylesheet. There are a number of settings you can customize so you may not just use this method for adding stylesheets and want to rename it. There's nothing special about the name so change it if it fits your paradigm. One way or the other you'll want to work within that method to get your specific site css files loaded.

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Web.UI;using System.Web;using Sitecore.StringExtensions;using System.Web.UI.HtmlControls;using Telerik.Web.UI;using Sitecore.Web.UI.XamlSharp.Ajax;using Sitecore.Web;using Sitecore.Diagnostics;using Sitecore.Shell.Controls.RichTextEditor.Pipelines.LoadRichTextContent;using Sitecore.Pipelines;using Sitecore.Shell.Controls.RichTextEditor.Pipelines.SaveRichTextContent;using Sitecore.Shell.Applications.ContentEditor.RichTextEditor;using Sitecore.Web.UI.Sheer;using Sitecore;using Sitecore.Web.UI.WebControls;using Sitecore.Shell.Controls.RichTextEditor;using Sitecore.Security.Accounts;using Sitecore.SecurityModel;using Sitecore.Data.Items;using Sitecore.Configuration;using Sitecore.Resources.Media;using System.Web.UI.WebControls;using System.Diagnostics;using Sitecore.Sites;using System.IO;namespace Sitecore.Customization{	public class MultiSiteEditorPage : Page {		// Fields		protected Sitecore.Web.UI.HtmlControls.Button CancelButton;		protected RadEditor Editor;		protected PlaceHolder EditorClientScripts;		protected PlaceHolder EditorStyles;		protected RadFormDecorator formDecorator;		protected HtmlForm mainForm;		protected Sitecore.Web.UI.HtmlControls.Button OkButton;		protected PlaceHolder ScriptConstants;		protected RadScriptManager ScriptManager1;		protected UpdatePanel UpdatePanel1;				// Methods		private void AjaxScriptManager_OnExecute(object sender, AjaxCommandEventArgs args)		{			if (args.Name == "editorpage:accept")			{				this.OnAccept();			}			else if (args.Name == "editorpage:reject")			{				this.OnReject();			}		}		private void LoadHtml()		{			string queryString = WebUtil.GetQueryString("hdl");			Assert.IsNotNullOrEmpty(queryString, "html value handle");			string sessionString = WebUtil.GetSessionString(queryString);			WebUtil.RemoveSessionValue(queryString);			LoadRichTextContentArgs args = new LoadRichTextContentArgs(sessionString);			using (new LongRunningOperationWatcher(250, "loadRichTextContent", new string[0]))			{				CorePipeline.Run("loadRichTextContent", args);			}			this.Editor.Content = args.Content;		}		protected void OnAccept()		{			string content = base.Request.Form["EditorValue"];			SaveRichTextContentArgs args = new SaveRichTextContentArgs(content);			using (new LongRunningOperationWatcher(250, "saveRichTextContent", new string[0]))			{				CorePipeline.Run("saveRichTextContent", args);			}			if (!RichTextEditorUrl.Parse(this.Context.Request.RawUrl).IsPageEdit)			{				SheerResponse.Eval(string.Format("scRichText.saveRichText({0})", StringUtil.EscapeJavascriptString(args.Content)));			}			else			{				SheerResponse.SetDialogValue(args.Content);			}			SheerResponse.Eval("scCloseEditor();");		}		protected override void OnInit(EventArgs e)		{			base.OnInit(e);			Client.AjaxScriptManager.OnExecute += new AjaxScriptManager.ExecuteDelegate(this.AjaxScriptManager_OnExecute);		}		protected override void OnLoad(EventArgs e)		{			base.OnLoad(e);			if (!base.IsPostBack && !string.IsNullOrEmpty(WebUtil.GetQueryString("hdl")))			{				EditorConfigurationResult result;				Sitecore.Security.Accounts.User user = Sitecore.Context.User;				if (!user.IsAuthenticated)				{					user = Sitecore.Security.Accounts.User.FromName(WebUtil.GetQueryString("us"), true);				}				using (new UserSwitcher(user))				{					using (new SecurityEnabler())					{						string queryString = WebUtil.GetQueryString("so", Settings.HtmlEditor.DefaultProfile);						Item profile = Sitecore.Context.Database.GetItem(queryString);						Assert.IsTrue(profile != null, string.Format("HTML editor profile "{0}" not found.", queryString));						EditorConfiguration configuration = EditorConfiguration.Create(profile);						configuration.Language = WebUtil.GetQueryString("la");						result = configuration.Apply(this.Editor);					}				}				this.RegisterMediaPrefixes();				this.EditorClientScripts.Controls.Add(new LiteralControl(result.Scripts.ToString()));				this.EditorStyles.Controls.Add(new LiteralControl(result.Styles.ToString()));				this.RenderScriptConstants();				//call the extra function for site specific settings				this.AddSiteSpecificCSS();				this.LoadHtml();			}		}		//this is stubbed out as a sample but you'll need to implement you're own logic here.		protected void AddSiteSpecificCSS() {						// get instance of item that is edited, and its corresponding site item			Item item = Sitecore.Context.ContentDatabase.GetItem(WebUtil.GetQueryString("id"));						List<siteinfo></siteinfo> si = Factory.GetSiteInfoList().Where(siteInfo => Sitecore.Context.Item.Paths.ContentPath.Contains(siteInfo.StartItem)).ToList();			if (si.Any()) {				SiteInfo site = si.First();				if (site != null) {					//you'll want to change this to match your css file path convention					string yourLocalPath = "/css/global.css";					string siteCssPath = SitecoreUtility.PathCombine(site.PhysicalFolder, yourLocalPath);					if (File.Exists(Server.MapPath(siteCssPath)) == true)						this.Editor.CssFiles.Add(new EditorCssFile(siteCssPath));				}			}		}		protected void OnReject()		{			SheerResponse.Eval("scCloseEditor();");		}		private void RegisterMediaPrefixes()		{			StringBuilder builder = new StringBuilder();			foreach (string str in MediaManager.Provider.Config.MediaPrefixes)			{				builder.Append("|" + str.Replace(@"", @"\").Replace("/", @"/"));			}			base.ClientScript.RegisterClientScriptBlock(base.GetType(), "mediaPrefixes", "var prefixes = '" + builder + "';", true);		}		private void RenderScriptConstants()		{			string text = "
            var scClientID = '{0}';
            var scItemID = '{1}';
            var scLanguage = '{2}';
         ".FormatWith(new object[] { this.Editor.ClientID, WebUtil.GetQueryString("id"), WebUtil.GetQueryString("la") });			this.ScriptConstants.Controls.Add(new LiteralControl(text));		}	}}

As I mentioned earlier there are a number of other settings that can be configured for each of your individual sites so you may find it makes more sense to consolidate those settings in a Tools File. If you plan to use a Tools File to configure your editor settings you can do this by setting it on the editor property like so: {this.Edtior.ToolsFile = "your string value";}. If you're curious about how to configure a tools file to add features to the editor you can try to start by looking at the tools file that Sitecore uses at /sitecore/shell/Controls/Rich Text/Editor/ToolsFile.xml or you may be better served by getting started with at Telerik's RadEditor Demo page.

There is also another way to augment how a rich text editor adds stylesheets. In the web.config there is a setting, HtmlEditor.DefaultConfigurationType, that controls the class that is used to construct the wysiwyg editor and all it's settings. This class, Sitecore.Shell.Controls.RichTextEditor.EditorConfiguration, was recently changed by Sitecore. They set several methods type to virtual to allow you to extend the class and override them. One of those methods is the SetupStylesheets. In this method you could then try to determine which site is being edited and get the local stylesheets. This would be a fairly clean way to configure a dynamic stylesheet. I didn't use this method in the example because I learned of it later, but when I looked over it there was no clear way to determine what the context item was. It might be possible to get the item ID from the querystring like is done in the previous example like this:

Item item = Sitecore.Context.ContentDatabase.GetItem(WebUtil.GetQueryString("id"));

I don't know if the context is the same or if the querystring is available. If you do try to use this method please let me know if this works for you.