using System; using System.Collections.Generic; using System.Text; using System.Web.UI; using System.Web.UI.WebControls; using System.Collections; using System.Collections.Specialized; using System.IO; using System.Text.RegularExpressions; using umbraco.BusinessLogic; using umbraco.cms.businesslogic.macro; using umbraco.cms.businesslogic.media; using umbraco.cms.businesslogic.property; using Content = umbraco.cms.businesslogic.Content; using ClientDependency.Core.Controls; using ClientDependency.Core; using umbraco.IO; namespace umbraco.editorControls.tinyMCE3.webcontrol { public class TinyMCEWebControl : System.Web.UI.WebControls.TextBox { public NameValueCollection config = new NameValueCollection(); private string temp; private int _nodeId = 0; private Guid _versionId; public bool IsInLiveEditingMode { get; set; } public int NodeId { set { _nodeId = value; } get { return _nodeId; } } public Guid VersionId { set { _versionId = value; } get { return _versionId; } } public string InstallPath { get { if (installPath == null) installPath = this.config["InstallPath"]; return installPath; } set { installPath = value; } } #region private private static readonly object TextChangedEvent = new object(); private int rows = 10; private int cols = 70; private bool gzipEnabled, merged; private string installPath, mode; private System.Text.StringBuilder m_scriptInitBlock = new StringBuilder(); #endregion public TinyMCEWebControl() : base() { base.TextMode = TextBoxMode.MultiLine; base.Attributes.Add("style", "visibility: hidden"); config.Add("mode", "exact"); config.Add("theme", "umbraco"); config.Add("umbraco_path", IOHelper.ResolveUrl( SystemDirectories.Umbraco )); CssClass = "tinymceContainer"; plugin.ConfigSection configSection = (plugin.ConfigSection)System.Web.HttpContext.Current.GetSection("TinyMCE"); if (configSection != null) { this.installPath = configSection.InstallPath; this.mode = configSection.Mode; this.gzipEnabled = configSection.GzipEnabled; // Copy items into local config collection foreach (string key in configSection.GlobalSettings.Keys) this.config[key] = configSection.GlobalSettings[key]; } else { configSection = new plugin.ConfigSection(); configSection.GzipExpiresOffset = TimeSpan.FromDays(10).Ticks; this.gzipEnabled = false; this.InstallPath = umbraco.editorControls.tinymce.tinyMCEConfiguration.JavascriptPath; } } protected override void OnPreRender(EventArgs e) { base.OnPreRender(e); // update macro's and images in text if (_nodeId != 0) { base.Text = parseMacrosToHtml(formatMedia(base.Text)); } } protected override void Render(HtmlTextWriter writer) { base.Render(writer); if (!IsInLiveEditingMode) writer.Write(m_scriptInitBlock.ToString()); else // add a marker to tell Live Editing when a tinyMCE control is on the page writer.Write(""); } protected override void OnLoad(EventArgs args) { if (!IsInLiveEditingMode) this.config["elements"] = this.ClientID; bool first = true; // Render HTML for TinyMCE instance // in the liveediting mode we're always preloading tinymce script if (!IsInLiveEditingMode) { //TinyMCE uses it's own compressor so leave it up to ScriptManager to render ScriptManager.RegisterClientScriptInclude(this, this.GetType(), _versionId.ToString(), this.ScriptURI); } else { //We're in live edit mode so add the base js file to the dependency list ClientDependencyLoader.Instance.RegisterDependency("tinymce3/tiny_mce_src.js", "UmbracoClient", ClientDependencyType.Javascript); } // Write script tag start m_scriptInitBlock.Append(HtmlTextWriter.TagLeftChar.ToString()); m_scriptInitBlock.Append("script"); m_scriptInitBlock.Append(" type=\"text/javascript\""); m_scriptInitBlock.Append(HtmlTextWriter.TagRightChar.ToString()); m_scriptInitBlock.Append("\n"); m_scriptInitBlock.Append("tinyMCE.init({\n"); // Write options foreach (string key in this.config.Keys) { //TODO: This is a hack to test if we can prevent tinymce from automatically download languages if (!IsInLiveEditingMode || (key != "language")) { string val = this.config[key]; if (!first) m_scriptInitBlock.Append(",\n"); else first = false; // Is boolean state or string if (val == "true" || val == "false") m_scriptInitBlock.Append(key + ":" + this.config[key]); else m_scriptInitBlock.Append(key + ":'" + this.config[key] + "'"); } } m_scriptInitBlock.Append("\n});\n"); // we're wrapping the tinymce init call in a load function when in live editing, // so we'll need to close that function declaration if (IsInLiveEditingMode) { m_scriptInitBlock.Append(@"(function() { var f = function() { if(document.getElementById('__umbraco_tinyMCE')) tinyMCE.execCommand('mceAddControl',false,'").Append(ClientID).Append(@"'); ItemEditing.remove_startEdit(f); } ItemEditing.add_startEdit(f);})();"); m_scriptInitBlock.Append(@"(function() { var f = function() { tinyMCE.execCommand('mceRemoveControl',false,'").Append(ClientID).Append(@"'); ItemEditing.remove_stopEdit(f); } ItemEditing.add_stopEdit(f);})();"); } // Write script tag end m_scriptInitBlock.Append(HtmlTextWriter.EndTagLeftChars); m_scriptInitBlock.Append("script"); m_scriptInitBlock.Append(HtmlTextWriter.TagRightChar.ToString()); // add to script manager if (IsInLiveEditingMode) { ScriptManager.RegisterClientScriptBlock(this, this.GetType(), new Guid().ToString(), m_scriptInitBlock.ToString(), false); } } string ScriptURI { get { string suffix = "", outURI; if (this.InstallPath == null) throw new Exception("Required installPath setting is missing, add it to your web.config. You can also add it directly to your tinymce:TextArea element using InstallPath but the web.config method is recommended since it allows you to switch over to gzip compression."); if (this.mode != null) suffix = "_" + this.mode; outURI = this.InstallPath + "/tiny_mce_src" + suffix + ".js"; if (!File.Exists(IOHelper.MapPath(outURI))) throw new Exception("Could not locate TinyMCE by URI:" + outURI + ", Physical path:" + IOHelper.MapPath(outURI) + ". Make sure that you configured the installPath to a valid location in your web.config. This path should be an relative or site absolute URI to where TinyMCE is located."); // Collect themes, languages and plugins and build gzip URI // TODO: Make sure gzip is re-enabled this.gzipEnabled = true; if (this.gzipEnabled) { ArrayList themes = new ArrayList(), plugins = new ArrayList(), languages = new ArrayList(); // Add themes if (config["theme"] == null) config["theme"] = "simple"; foreach (string theme in config["theme"].Split(',')) { string themeVal = theme.Trim(); if (themes.IndexOf(themeVal) == -1) themes.Add(themeVal); } // Add plugins if (config["plugins"] != null) { foreach (string plugin in config["plugins"].Split(',')) { string pluginVal = plugin.Trim(); if (plugins.IndexOf(pluginVal) == -1) plugins.Add(pluginVal); } } // Add language if (config["language"] == null) config["language"] = "en"; if (languages.IndexOf(config["language"]) == -1) languages.Add(config["language"]); // NH: No clue why these extra dashes are added, but the affect other parts of the implementation // Skip loading of themes and plugins // area.config["theme"] = "-" + area.config["theme"]; // if (area.config["plugins"] != null) // area.config["plugins"] = "-" + String.Join(",-", area.config["plugins"].Split(',')); // Build output URI // NH: Use versionId for randomize to ensure we don't download a new tinymce on every single call outURI = umbraco.editorControls.tinymce.tinyMCEConfiguration.PluginPath + "/tinymce3tinymceCompress.aspx?rnd=" + this._versionId.ToString() + "&module=gzipmodule"; if (themes.Count > 0) outURI += "&themes=" + String.Join(",", ((string[])themes.ToArray(typeof(string)))); if (plugins.Count > 0) outURI += "&plugins=" + String.Join(",", ((string[])plugins.ToArray(typeof(string)))); if (languages.Count > 0) outURI += "&languages=" + String.Join(",", ((string[])languages.ToArray(typeof(string)))); } return outURI; } } private string formatMedia(string html) { // Local media path string localMediaPath = IOHelper.ResolveUrl(SystemDirectories.Media); // Find all media images string pattern = String.Format("]*src=\"(?{0}[^\"]*)\" [^>]*>", SystemDirectories.Media); MatchCollection tags = Regex.Matches(html, pattern, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); foreach (Match tag in tags) if (tag.Groups.Count > 0) { // Replace /> to ensure we're in old-school html mode string tempTag = "", " >"), "(?\\S*)=\"(?[^\"]*)\"|(?\\S*)=(?[^\"|\\s]*)\\s", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); foreach (Match attributeSet in m) { if (attributeSet.Groups["attributeName"].Value.ToString().ToLower() != "src") ht.Add(attributeSet.Groups["attributeName"].Value.ToString(), attributeSet.Groups["attributeValue"].Value.ToString()); } // build the element // Build image tag IDictionaryEnumerator ide = ht.GetEnumerator(); while (ide.MoveNext()) tempTag += " " + ide.Key.ToString() + "=\"" + ide.Value.ToString() + "\""; // Find the original filename, by removing the might added width and height orgSrc = IOHelper.ResolveUrl(orgSrc.Replace( "_" + helper.FindAttribute(ht, "width") + "x" + helper.FindAttribute(ht, "height"), ""). Replace("%20", " ")); // Check for either id or guid from media string mediaId = getIdFromSource(orgSrc, localMediaPath); Media imageMedia = null; try { int mId = int.Parse(mediaId); Property p = new Property(mId); imageMedia = new Media(Content.GetContentFromVersion(p.VersionId).Id); } catch { try { imageMedia = new Media(Content.GetContentFromVersion(new Guid(mediaId)).Id); } catch { } } // Check with the database if any media matches this url if (imageMedia != null) { try { // Check extention if (imageMedia.getProperty("umbracoExtension").Value.ToString() != orgSrc.Substring(orgSrc.LastIndexOf(".") + 1, orgSrc.Length - orgSrc.LastIndexOf(".") - 1)) orgSrc = orgSrc.Substring(0, orgSrc.LastIndexOf(".") + 1) + imageMedia.getProperty("umbracoExtension").Value.ToString(); // Format the tag tempTag = tempTag + " rel=\"" + imageMedia.getProperty("umbracoWidth").Value.ToString() + "," + imageMedia.getProperty("umbracoHeight").Value.ToString() + "\" src=\"" + orgSrc + "\""; tempTag += "/>"; // Replace the tag html = html.Replace(tag.Value, tempTag); } catch (Exception ee) { Log.Add(LogTypes.Error, User.GetUser(0), -1, "Error reading size data from media: " + imageMedia.Id.ToString() + ", " + ee.ToString()); } } else Log.Add(LogTypes.Error, User.GetUser(0), -1, "Error reading size data from media (not found): " + orgSrc); } return html; } private string getIdFromSource(string src, string localMediaPath) { if (!localMediaPath.EndsWith("/")) localMediaPath += "/"; // important - remove out the umbraco path + media! src = src.Replace(localMediaPath, ""); string _id = ""; // Check for directory id naming if (src.Length - src.Replace("/", "").Length > 0) { string[] dirSplit = src.Split("/".ToCharArray()); string tempId = dirSplit[0]; try { // id _id = int.Parse(tempId).ToString(); } catch { // guid _id = tempId; } } else { string[] fileSplit = src.Replace("/media/", "").Split("-".ToCharArray()); // guid or id if (fileSplit.Length > 3) { for (int i = 0; i < 5; i++) _id += fileSplit[i] + "-"; _id = _id.Substring(0, _id.Length - 1); } else _id = fileSplit[0]; } return _id; } private string getLocalMediaPath() { string[] umbracoPathSplit = IOHelper.ResolveUrl(SystemDirectories.Umbraco).Split('/'); string umbracoPath = ""; for (int i = 0; i < umbracoPathSplit.Length - 1; i++) umbracoPath += umbracoPathSplit[i] + "/"; return umbracoPath + "media/"; } private string parseMacrosToHtml(string input) { int nodeId = _nodeId; Guid versionId = _versionId; string content = input; string pattern = @"(<\?UMBRACO_MACRO\W*[^>]*/>)"; MatchCollection tags = Regex.Matches(content, pattern, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); // Page for macro rendering // page p = new page(nodeId, versionId); System.Web.HttpContext.Current.Items["macrosAdded"] = 0; System.Web.HttpContext.Current.Items["pageID"] = nodeId.ToString(); foreach (Match tag in tags) { try { // Create div Hashtable attributes = helper.ReturnAttributes(tag.Groups[1].Value); string div = macro.renderMacroStartTag(attributes, nodeId, versionId); //.Replace(""", "&quot;"); // Insert macro contents here... macro m; if (helper.FindAttribute(attributes, "macroID") != "") m = new macro(int.Parse(helper.FindAttribute(attributes, "macroID"))); else { // legacy: Check if the macroAlias is typed in lowercasing string macroAlias = helper.FindAttribute(attributes, "macroAlias"); if (macroAlias == "") { macroAlias = helper.FindAttribute(attributes, "macroalias"); attributes.Remove("macroalias"); attributes.Add("macroAlias", macroAlias); } if (macroAlias != "") m = new macro(Macro.GetByAlias(macroAlias).Id); else throw new ArgumentException("umbraco is unable to identify the macro. No id or macroalias was provided for the macro in the macro tag.", tag.Groups[1].Value); } if (helper.FindAttribute(attributes, "macroAlias") == "") attributes.Add("macroAlias", m.Alias); try { div += macro.MacroContentByHttp(nodeId, versionId, attributes); } catch { div += "No macro content available for WYSIWYG editing"; } div += macro.renderMacroEndTag(); content = content.Replace(tag.Groups[1].Value, div); } catch (Exception ee) { Log.Add(LogTypes.Error, this._nodeId, "Macro Parsing Error: " + ee.ToString()); string div = "

umbraco was unable to parse a macro tag, which means that parts of this content might be corrupt.

Best solution is to rollback to a previous version by right clicking the node in the tree and then try to insert the macro again.

Please report this to your system administrator as well - this error has been logged.

"; content = content.Replace(tag.Groups[1].Value, div); } } return content; } } }