Files
Umbraco-CMS/components/editorControls/tinyMCE3/TinyMCE.cs

651 lines
26 KiB
C#

using System;
using System.Collections;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using umbraco.BasePages;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic.web;
using umbraco.editorControls.tinymce;
using umbraco.editorControls.tinyMCE3.webcontrol;
using umbraco.editorControls.wysiwyg;
using umbraco.interfaces;
using umbraco.IO;
using umbraco.presentation;
using umbraco.uicontrols;
namespace umbraco.editorControls.tinyMCE3
{
public class TinyMCE : TinyMCEWebControl, IDataEditor, IMenuElement, ILiveEditingDataEditor
{
private readonly string _activateButtons = "";
private readonly string _advancedUsers = "";
private readonly SortedList _buttons = new SortedList();
private readonly IData _data;
private readonly string _disableButtons = "help,visualaid,";
private readonly string _editorButtons = "";
private readonly bool _enableContextMenu;
private readonly bool _fullWidth;
private readonly int _height;
private readonly SortedList _mceButtons = new SortedList();
private readonly ArrayList _menuIcons = new ArrayList();
private readonly bool _showLabel;
private readonly ArrayList _stylesheets = new ArrayList();
private readonly int _width;
private readonly int m_maxImageWidth = 500;
private bool _isInitialized;
private string _plugins = "";
private bool m_isInLiveEditingMode;
public TinyMCE(IData Data, string Configuration)
{
_data = Data;
try
{
string[] configSettings = Configuration.Split("|".ToCharArray());
if (configSettings.Length > 0)
{
_editorButtons = configSettings[0];
if (configSettings.Length > 1)
if (configSettings[1] == "1")
_enableContextMenu = true;
if (configSettings.Length > 2)
_advancedUsers = configSettings[2];
if (configSettings.Length > 3)
{
if (configSettings[3] == "1")
_fullWidth = true;
else if (configSettings[4].Split(',').Length > 1)
{
_width = int.Parse(configSettings[4].Split(',')[0]);
_height = int.Parse(configSettings[4].Split(',')[1]);
}
}
// default width/height
if (_width < 1)
_width = 500;
if (_height < 1)
_height = 400;
// add stylesheets
if (configSettings.Length > 4)
{
foreach (string s in configSettings[5].Split(",".ToCharArray()))
_stylesheets.Add(s);
}
if (configSettings.Length > 6 && configSettings[6] != "")
_showLabel = bool.Parse(configSettings[6]);
if (configSettings.Length > 7 && configSettings[7] != "")
m_maxImageWidth = int.Parse(configSettings[7]);
// sizing
if (!_fullWidth)
{
config.Add("width", _width.ToString());
config.Add("height", _height.ToString());
}
if (_enableContextMenu)
_plugins += ",contextmenu";
// safari compatibility
if (HttpContext.Current.Request.Browser.Browser.ToLower().Contains("safari"))
{
_plugins += ",safari";
}
// If the editor is used in umbraco, use umbraco's own toolbar
bool onFront = false;
if (GlobalSettings.RequestIsInUmbracoApplication(HttpContext.Current) ||
UmbracoContext.Current.LiveEditingContext.Enabled)
{
config.Add("theme_umbraco_toolbar_location", "external");
config.Add("skin", "umbraco");
config.Add("inlinepopups_skin ", "umbraco");
}
else
{
onFront = true;
config.Add("theme_umbraco_toolbar_location", "top");
}
// load plugins
IDictionaryEnumerator pluginEnum = tinyMCEConfiguration.Plugins.GetEnumerator();
while (pluginEnum.MoveNext())
{
var plugin = (tinyMCEPlugin)pluginEnum.Value;
if (plugin.UseOnFrontend || (!onFront && !plugin.UseOnFrontend))
_plugins += "," + plugin.Name;
}
if (_plugins.StartsWith(","))
_plugins = _plugins.Substring(1, _plugins.Length - 1);
if (_plugins.EndsWith(","))
_plugins = _plugins.Substring(0, _plugins.Length - 1);
config.Add("plugins", _plugins);
// Check advanced settings
if (UmbracoEnsuredPage.CurrentUser != null && ("," + _advancedUsers + ",").IndexOf("," + UmbracoEnsuredPage.CurrentUser.UserType.Id + ",") >
-1)
config.Add("umbraco_advancedMode", "true");
else
config.Add("umbraco_advancedMode", "false");
// Check maximum image width
config.Add("umbraco_maximumDefaultImageWidth", m_maxImageWidth.ToString());
// Styles
string cssFiles = String.Empty;
string styles = string.Empty;
foreach (string styleSheetId in _stylesheets)
{
if (styleSheetId.Trim() != "")
try
{
var s = new StyleSheet(int.Parse(styleSheetId));
if (s.nodeObjectType == StyleSheet.ModuleObjectType)
{
cssFiles += IOHelper.ResolveUrl(SystemDirectories.Css + "/" + s.Text + ".css");
foreach (StylesheetProperty p in s.Properties)
{
if (styles != string.Empty)
{
styles += ";";
}
if (p.Alias.StartsWith("."))
styles += p.Text + "=" + p.Alias;
else
styles += p.Text + "=" + p.Alias;
}
cssFiles += ",";
}
}
catch (Exception ee)
{
Log.Add(LogTypes.Error, -1,
string.Format(
string.Format("Error adding stylesheet to tinymce (id: {{0}}). {0}", ee),
styleSheetId));
}
}
// remove any ending comma (,)
if (!string.IsNullOrEmpty(cssFiles))
{
cssFiles = cssFiles.TrimEnd(',');
}
// language
string userLang = (UmbracoEnsuredPage.CurrentUser != null) ?
(User.GetCurrent().Language.Contains("-") ?
User.GetCurrent().Language.Substring(0, User.GetCurrent().Language.IndexOf("-")) : User.GetCurrent().Language)
: "en";
config.Add("language", userLang);
config.Add("content_css", cssFiles);
config.Add("theme_umbraco_styles", styles);
// Add buttons
IDictionaryEnumerator ide = tinyMCEConfiguration.Commands.GetEnumerator();
while (ide.MoveNext())
{
var cmd = (tinyMCECommand)ide.Value;
if (_editorButtons.IndexOf("," + cmd.Alias + ",") > -1)
_activateButtons += cmd.Alias + ",";
else
_disableButtons += cmd.Alias + ",";
}
if (_activateButtons.Length > 0)
_activateButtons = _activateButtons.Substring(0, _activateButtons.Length - 1);
if (_disableButtons.Length > 0)
_disableButtons = _disableButtons.Substring(0, _disableButtons.Length - 1);
// Add buttons
initButtons();
_activateButtons = "";
int separatorPriority = 0;
ide = _mceButtons.GetEnumerator();
while (ide.MoveNext())
{
string mceCommand = ide.Value.ToString();
var curPriority = (int)ide.Key;
// Check priority
if (separatorPriority > 0 &&
Math.Floor(decimal.Parse(curPriority.ToString()) / 10) >
Math.Floor(decimal.Parse(separatorPriority.ToString()) / 10))
_activateButtons += "separator,";
_activateButtons += mceCommand + ",";
separatorPriority = curPriority;
}
config.Add("theme_umbraco_buttons1", _activateButtons);
config.Add("theme_umbraco_buttons2", "");
config.Add("theme_umbraco_buttons3", "");
config.Add("theme_umbraco_toolbar_align", "left");
config.Add("theme_umbraco_disable", _disableButtons);
config.Add("theme_umbraco_path ", "true");
config.Add("extended_valid_elements", "div[*]");
config.Add("document_base_url", "/");
config.Add("relative_urls", "false");
config.Add("remove_script_host", "true");
config.Add("event_elements", "div");
config.Add("paste_auto_cleanup_on_paste", "true");
config.Add("valid_elements",
tinyMCEConfiguration.ValidElements.Substring(1,
tinyMCEConfiguration.ValidElements.Length -
2));
config.Add("invalid_elements", tinyMCEConfiguration.InvalidElements);
// custom commands
if (tinyMCEConfiguration.ConfigOptions.Count > 0)
{
ide = tinyMCEConfiguration.ConfigOptions.GetEnumerator();
while (ide.MoveNext())
{
config.Add(ide.Key.ToString(), ide.Value.ToString());
}
}
//if (HttpContext.Current.Request.Path.IndexOf(umbraco.IO.SystemDirectories.Umbraco) > -1)
// config.Add("language", User.GetUser(BasePage.GetUserId(BasePage.umbracoUserContextID)).Language);
//else
// config.Add("language", System.Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName);
if (_fullWidth)
{
config.Add("auto_resize", "true");
base.Columns = 30;
base.Rows = 30;
}
else
{
base.Columns = 0;
base.Rows = 0;
}
EnableViewState = false;
}
}
catch (Exception ex)
{
throw new ArgumentException("Incorrect TinyMCE configuration.", "Configuration", ex);
}
}
#region TreatAsRichTextEditor
public virtual bool TreatAsRichTextEditor
{
get { return false; }
}
#endregion
#region ShowLabel
public virtual bool ShowLabel
{
get { return _showLabel; }
}
#endregion
#region Editor
public Control Editor
{
get { return this; }
}
#endregion
#region Save()
public virtual void Save()
{
string parsedString = base.Text.Trim();
if (parsedString != string.Empty)
{
parsedString = replaceMacroTags(parsedString).Trim();
// tidy html - refactored, see #30534
if (UmbracoSettings.TidyEditorContent)
{
// always wrap in a <div> - using <p> was a bad idea
parsedString = "<div>" + parsedString + "</div>";
string tidyTxt = library.Tidy(parsedString, false);
if (tidyTxt != "[error]")
{
parsedString = tidyTxt;
// remove pesky \r\n, and other empty chars
parsedString = parsedString.Trim(new char[] { '\r', '\n', '\t', ' ' });
// compensate for breaking macro tags by tidy (?)
parsedString = parsedString.Replace("/?>", "/>");
// remove the wrapping <div> - safer to check that it is still here
if (parsedString.StartsWith("<div>") && parsedString.EndsWith("</div>"))
parsedString = parsedString.Substring("<div>".Length, parsedString.Length - "<div></div>".Length);
}
else
{
// TODO
// How to log errors? _data.NodeId does not exist?
//BusinessLogic.Log.Add(BusinessLogic.LogTypes.Error, BusinessLogic.User.GetUser(0), _data.NodeId, "Error tidying txt from property: " + _data.PropertyId.ToString());
}
}
// rescue umbraco tags
parsedString = parsedString.Replace("|||?", "<?").Replace("/|||", "/>").Replace("|*|", "\"");
// fix images
parsedString = tinyMCEImageHelper.cleanImages(parsedString);
// parse current domain and instances of slash before anchor (to fix anchor bug)
// NH 31-08-2007
if (HttpContext.Current.Request.ServerVariables != null)
{
parsedString = parsedString.Replace(helper.GetBaseUrl(HttpContext.Current) + "/#", "#");
parsedString = parsedString.Replace(helper.GetBaseUrl(HttpContext.Current), "");
}
//Allow CDATA nested into RTE without exceptions
// GE 2012-01-18
parsedString = parsedString.Replace("<![CDATA[", "<!--CDATAOPENTAG-->").Replace("]]>", "<!--CDATACLOSETAG-->");
// if text is an empty parargraph, make it all empty
if (parsedString.ToLower() == "<p></p>")
parsedString = "";
}
_data.Value = parsedString;
// update internal webcontrol value with parsed result
base.Text = parsedString;
}
#endregion
public virtual string Plugins
{
get { return _plugins; }
set { _plugins = value; }
}
public object[] MenuIcons
{
get
{
initButtons();
var tempIcons = new object[_menuIcons.Count];
for (int i = 0; i < _menuIcons.Count; i++)
tempIcons[i] = _menuIcons[i];
return tempIcons;
}
}
#region ILiveEditingDataEditor Members
public Control LiveEditingControl
{
get
{
m_isInLiveEditingMode = true;
base.IsInLiveEditingMode = true;
return this;
}
}
#endregion
#region IMenuElement Members
public string ElementName
{
get { return "div"; }
}
public string ElementIdPreFix
{
get { return "umbTinymceMenu"; }
}
public string ElementClass
{
get { return "tinymceMenuBar"; }
}
public int ExtraMenuWidth
{
get
{
initButtons();
return _buttons.Count * 40 + 300;
}
}
#endregion
protected override void OnLoad(EventArgs e)
{
try
{
// add current page info
base.NodeId = ((cms.businesslogic.datatype.DefaultData)_data).NodeId;
if (NodeId != 0)
{
base.VersionId = ((cms.businesslogic.datatype.DefaultData)_data).Version;
config.Add("theme_umbraco_pageId", base.NodeId.ToString());
config.Add("theme_umbraco_versionId", base.VersionId.ToString());
// we'll need to make an extra check for the liveediting as that value is set after the constructor have initialized
if (IsInLiveEditingMode)
{
if (config["theme_umbraco_toolbar_location"] == null)
config.Add("theme_umbraco_toolbar_location", "");
config["theme_umbraco_toolbar_location"] = "external";
config.Add("umbraco_toolbar_id",
"LiveEditingClientToolbar");
}
else
{
config.Add("umbraco_toolbar_id",
ElementIdPreFix +
((cms.businesslogic.datatype.DefaultData)_data).PropertyId);
}
}
else
{
// this is for use when tinymce is used for non default Umbraco pages
config.Add("umbraco_toolbar_id",
ElementIdPreFix + "_" + this.ClientID);
}
}
catch
{
// Empty catch as this is caused by the document doesn't exists yet,
// like when using this on an autoform, partly replaced by the if/else check on nodeId above though
}
base.OnLoad(e);
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
//Allow CDATA nested into RTE without exceptions
// GE 2012-01-18
if (_data != null && _data.Value != null)
base.Text = _data.Value.ToString().Replace("<!--CDATAOPENTAG-->", "<![CDATA[").Replace("<!--CDATACLOSETAG-->", "]]>");
}
private string replaceMacroTags(string text)
{
while (findStartTag(text) > -1)
{
string result = text.Substring(findStartTag(text), findEndTag(text) - findStartTag(text));
text = text.Replace(result, generateMacroTag(result));
}
return text;
}
private string generateMacroTag(string macroContent)
{
string macroAttr = macroContent.Substring(5, macroContent.IndexOf(">") - 5);
string macroTag = "|||?UMBRACO_MACRO ";
Hashtable attributes = ReturnAttributes(macroAttr);
IDictionaryEnumerator ide = attributes.GetEnumerator();
while (ide.MoveNext())
{
if (ide.Key.ToString().IndexOf("umb_") != -1)
{
// Hack to compensate for Firefox adding all attributes as lowercase
string orgKey = ide.Key.ToString();
if (orgKey == "umb_macroalias")
orgKey = "umb_macroAlias";
macroTag += orgKey.Substring(4, orgKey.Length - 4) + "=|*|" +
ide.Value.ToString().Replace("\\r\\n", Environment.NewLine) + "|*| ";
}
}
macroTag += "/|||";
return macroTag;
}
public static Hashtable ReturnAttributes(String tag)
{
var ht = new Hashtable();
MatchCollection m =
Regex.Matches(tag, "(?<attributeName>\\S*)=\"(?<attributeValue>[^\"]*)\"",
RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
foreach (Match attributeSet in m)
ht.Add(attributeSet.Groups["attributeName"].Value,
attributeSet.Groups["attributeValue"].Value);
return ht;
}
private int findStartTag(string text)
{
string temp = "";
text = text.ToLower();
if (text.IndexOf("ismacro=\"true\"") > -1)
{
temp = text.Substring(0, text.IndexOf("ismacro=\"true\""));
return temp.LastIndexOf("<");
}
return -1;
}
private int findEndTag(string text)
{
string find = "<!-- endumbmacro -->";
int endMacroIndex = text.ToLower().IndexOf(find);
string tempText = text.ToLower().Substring(endMacroIndex + find.Length,
text.Length - endMacroIndex - find.Length);
int finalEndPos = 0;
while (tempText.Length > 5)
if (tempText.Substring(finalEndPos, 6) == "</div>")
break;
else
finalEndPos++;
return endMacroIndex + find.Length + finalEndPos + 6;
}
private void initButtons()
{
if (!_isInitialized)
{
_isInitialized = true;
// Add icons for the editor control:
// Html
// Preview
// Style picker, showstyles
// Bold, italic, Text Gen
// Align: left, center, right
// Lists: Bullet, Ordered, indent, undo indent
// Link, Anchor
// Insert: Image, macro, table, formular
foreach (string button in _activateButtons.Split(','))
{
if (button.Trim() != "")
{
try
{
var cmd = (tinyMCECommand)tinyMCEConfiguration.Commands[button];
string appendValue = "";
if (cmd.Value != "")
appendValue = ", '" + cmd.Value + "'";
_mceButtons.Add(cmd.Priority, cmd.Command);
_buttons.Add(cmd.Priority,
new editorButton(cmd.Alias, ui.Text("buttons", cmd.Alias, null), cmd.Icon,
"tinyMCE.execInstanceCommand('" + ClientID + "', '" +
cmd.Command + "', " + cmd.UserInterface + appendValue + ")"));
}
catch (Exception ee)
{
Log.Add(LogTypes.Error, User.GetUser(0), -1,
string.Format("TinyMCE: Error initializing button '{0}': {1}", button, ee));
}
}
}
// add save icon
int separatorPriority = 0;
IDictionaryEnumerator ide = _buttons.GetEnumerator();
while (ide.MoveNext())
{
object buttonObj = ide.Value;
var curPriority = (int)ide.Key;
// Check priority
if (separatorPriority > 0 &&
Math.Floor(decimal.Parse(curPriority.ToString()) / 10) >
Math.Floor(decimal.Parse(separatorPriority.ToString()) / 10))
_menuIcons.Add("|");
try
{
var e = (editorButton)buttonObj;
MenuIconI menuItem = new MenuIconClass();
menuItem.OnClickCommand = e.onClickCommand;
menuItem.ImageURL = e.imageUrl;
menuItem.AltText = e.alttag;
menuItem.ID = e.id;
_menuIcons.Add(menuItem);
}
catch
{
}
separatorPriority = curPriority;
}
}
}
}
}