package org.opencms.editors.tinymce; import org.opencms.file.CmsFile; import org.opencms.file.CmsObject; import org.opencms.i18n.CmsEncoder; import org.opencms.main.CmsException; import org.opencms.main.CmsLog; import org.opencms.main.OpenCms; import org.opencms.util.CmsStringUtil; import org.opencms.widgets.A_CmsHtmlWidget; import org.opencms.widgets.CmsHtmlWidgetOption; import org.opencms.widgets.I_CmsWidget; import org.opencms.widgets.I_CmsWidgetDialog; import org.opencms.widgets.I_CmsWidgetParameter; import org.opencms.workplace.CmsWorkplace; import org.opencms.workplace.editors.CmsEditorDisplayOptions; import org.opencms.workplace.editors.I_CmsEditorCssHandler; import org.opencms.xml.types.I_CmsXmlContentValue; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; /** * The TinyMCE implementation of the HTML widget.<p> */ public class CmsTinyMCEWidget extends A_CmsHtmlWidget { /** Path of the base content CSS. */ public static final String BASE_CONTENT_CSS = "/system/workplace/editors/tinymce/base_content.css"; /** The translation of the generic widget button names to TinyMCE specific button names. */ public static final String BUTTON_TRANSLATION = /* Row 1*/ "|newdocument:newdocument|bold:bold|italic:italic|underline:underline|strikethrough:strikethrough|alignleft:justifyleft" + "|aligncenter:justifycenter|alignright:justifyright|justify:justifyfull|style:styleselect|formatselect:formatselect" + "|fontselect:fontselect|fontsizeselect:fontsizeselect" /* Row 2*/ + "|cut:cut|copy:copy|paste:paste,pastetext,pasteword|pastetext:pastetext|pasteword:pasteword|find:search|replace:replace|unorderedlist:bullist" + "|orderedlist:numlist|outdent:outdent|indent:indent|blockquote:blockquote|undo:undo|redo:redo|editorlink:link|unlink:unlink" + "|anchor:anchor|image:image|cleanup:cleanup|source:code|insertdate:insertdate|inserttime:inserttime|forecolor:forecolor|backcolor:backcolor" /* Row 3*/ + "|table:table|hr:hr|removeformat:removeformat|visualaid:visualaid|subscript:sub|superscript:sup|specialchar:charmap" + "|emotions:emotions|spellcheck:iespell|media:media|advhr:advhr|print:print|ltr:ltr|rtl:rtl|fitwindow:fullscreen" /* Row 4*/ + "|insertlayer:insertlayer|moveforward:moveforward|movebackward:movebackward|absolute:absolute|styleprops:styleprops|cite:cite" + "|abbr:abbr|acronym:acronym|del:del|ins:ins|attribs:attribs|visualchars:visualchars|nonbreaking:nonbreaking|template:template" + "|pagebreak:pagebreak|selectall:selectall|fullpage:fullpage|imagegallery:OcmsImageGallery|downloadgallery:OcmsDownloadGallery" + "|linkgallery:OcmsLinkGallery|htmlgallery:OcmsHtmlGallery|tablegallery:OcmsTableGallery|link:oc-link"; /** The map containing the translation of the generic widget button names to TinyMCE specific button names. */ public static final Map<String, String> BUTTON_TRANSLATION_MAP = CmsStringUtil.splitAsMap( BUTTON_TRANSLATION, "|", ":"); /** Request parameter name for the tool bar configuration parameter. */ public static final String PARAM_CONFIGURATION = "config"; /** The log object for this class. */ private static final Log LOG = CmsLog.getLog(org.opencms.editors.tinymce.CmsTinyMCEWidget.class); /** * Creates a new TinyMCE widget.<p> */ public CmsTinyMCEWidget() { // empty constructor is required for class registration this(""); } /** * Creates a new TinyMCE widget with the given configuration.<p> * * @param configuration the configuration to use */ public CmsTinyMCEWidget(CmsHtmlWidgetOption configuration) { super(configuration); } /** * Creates a new TinyMCE widget with the given configuration.<p> * * @param configuration the configuration to use */ public CmsTinyMCEWidget(String configuration) { super(configuration); } /** * @see org.opencms.widgets.I_CmsWidget#getDialogIncludes(org.opencms.file.CmsObject, org.opencms.widgets.I_CmsWidgetDialog) */ @Override public String getDialogIncludes(CmsObject cms, I_CmsWidgetDialog widgetDialog) { StringBuilder result = new StringBuilder(128); // general TinyMCE JS result.append(getJSIncludeFile(CmsWorkplace.getSkinUri() + "editors/tinymce/jscripts/tiny_mce/tiny_mce.js")); result.append("\n"); result.append(getJSIncludeFile(OpenCms.getLinkManager().substituteLinkForRootPath( cms, "/system/workplace/editors/tinymce/opencms_plugin.js"))); result.append("\n"); // special TinyMCE widget functions result.append(getJSIncludeFile(CmsWorkplace.getSkinUri() + "components/widgets/tinymce.js")); String cssUri = CmsWorkplace.getSkinUri() + "components/widgets/tinymce.css"; result.append("<link type='text/css' rel='stylesheet' href='" + cssUri + "'>"); return result.toString(); } /** * @see org.opencms.widgets.I_CmsWidget#getDialogWidget(org.opencms.file.CmsObject, org.opencms.widgets.I_CmsWidgetDialog, org.opencms.widgets.I_CmsWidgetParameter) */ public String getDialogWidget(CmsObject cms, I_CmsWidgetDialog widgetDialog, I_CmsWidgetParameter param) { String id = param.getId(); String value = param.getStringValue(cms); StringBuilder result = new StringBuilder(); CmsTinyMCEConfiguration configuration = CmsTinyMCEConfiguration.get(cms); result.append("<td class=\"cmsTinyMCE xmlTd\">"); result.append("<textarea class=\"xmlInput maxwidth\" name=\"ta_"); result.append(id); result.append("\" id=\"ta_"); result.append(id); result.append("\" style=\""); result.append("\" rows=\"20\" cols=\"60\">"); result.append(CmsEncoder.escapeXml(value)); result.append("</textarea>"); result.append("<input type=\"hidden\" name=\""); result.append(id); result.append("\" id=\""); result.append(id); result.append("\" value=\""); result.append(CmsEncoder.encode(value)); result.append("\">"); result.append("<script type=\"text/javascript\">\n"); CmsEditorDisplayOptions options = OpenCms.getWorkplaceManager().getEditorDisplayOptions(); Properties displayOptions = options.getDisplayOptions(cms); String preprocessorFunction = configuration.generateOptionPreprocessor("cms_preprocess_options"); result.append(preprocessorFunction); result.append("tinyMCE.init(cms_preprocess_options({\n"); result.append(" // General options\n"); result.append("relative_urls: false,\n"); result.append("remove_script_host: false,\n"); // the flexible menu bars produced by the special CSS throw off the height calculations // of TinyMCE; the following setting corrects this result.append("theme_advanced_row_height: 0,\n"); result.append("skin_variant: 'ocms',\n"); result.append(" mode : \"exact\",\n"); result.append(" elements : \"ta_" + id + "\",\n"); String editorHeight = getHtmlWidgetOption().getEditorHeight(); if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(editorHeight)) { editorHeight = editorHeight.replaceAll("px", ""); result.append("height: \"" + editorHeight + "\",\n"); } result.append(" theme : \"advanced\",\n"); result.append(" file_browser_callback : 'cmsTinyMceFileBrowser',\n"); result.append("setup : function(editor) { setupTinyMCE(editor); },\n"); if (options.showElement("gallery.enhancedoptions", displayOptions)) { result.append("cmsGalleryEnhancedOptions: true,\n"); } if (options.showElement("gallery.usethickbox", displayOptions)) { result.append("cmsGalleryUseThickbox: true,\n"); } result.append(" plugins : \"autolink,lists,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,wordcount,advlist,-opencms"); //check for fullpage mode if (getHtmlWidgetOption().isFullPage()) { // add fullpage plugin result.append(",fullpage"); } result.append("\",\n"); result.append(" // Theme options\n"); result.append(getToolbar()); result.append(" theme_advanced_toolbar_location : \"top\",\n"); result.append(" theme_advanced_toolbar_align : \"left\",\n"); result.append(" theme_advanced_statusbar_location : \"bottom\",\n"); result.append("paste_retain_style_properties : '*',\n"); result.append("width: '100%',"); result.append("language: '" + OpenCms.getWorkplaceManager().getWorkplaceLocale(cms).getLanguage() + "',\n"); // set CSS style sheet for current editor widget if configured boolean cssConfigured = false; String cssPath = ""; if (getHtmlWidgetOption().useCss()) { cssPath = getHtmlWidgetOption().getCssPath(); // set the CSS path to null (the created configuration String passed to JS will not include this path then) getHtmlWidgetOption().setCssPath(null); cssConfigured = true; } else if (OpenCms.getWorkplaceManager().getEditorCssHandlers().size() > 0) { Iterator<I_CmsEditorCssHandler> i = OpenCms.getWorkplaceManager().getEditorCssHandlers().iterator(); try { // cast parameter to I_CmsXmlContentValue I_CmsXmlContentValue contentValue = (I_CmsXmlContentValue)param; // now extract the absolute path of the edited resource String editedResource = cms.getSitePath(contentValue.getDocument().getFile()); while (i.hasNext()) { I_CmsEditorCssHandler handler = i.next(); if (handler.matches(cms, editedResource)) { cssPath = handler.getUriStyleSheet(cms, editedResource); if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(cssPath)) { cssConfigured = true; } break; } } } catch (Exception e) { // ignore, CSS could not be set } } List<String> contentCssLinks = new ArrayList<String>(); contentCssLinks.add(OpenCms.getLinkManager().substituteLink(cms, BASE_CONTENT_CSS)); if (cssConfigured) { contentCssLinks.add(OpenCms.getLinkManager().substituteLink(cms, cssPath)); } result.append("content_css : \""); result.append(CmsStringUtil.listAsString(contentCssLinks, ",")); result.append("\",\n"); if (getHtmlWidgetOption().showStylesFormat()) { try { CmsFile file = cms.readFile(getHtmlWidgetOption().getStylesFormatPath()); String characterEncoding = OpenCms.getSystemInfo().getDefaultEncoding(); String formatSelect = "style_formats : " + new String(file.getContents(), characterEncoding) + ",\n"; result.append(formatSelect); } catch (CmsException cmsException) { LOG.error("Can not open file:" + getHtmlWidgetOption().getStylesFormatPath(), cmsException); } catch (UnsupportedEncodingException ex) { LOG.error(ex); } } String formatSelectOptions = getHtmlWidgetOption().getFormatSelectOptions(); if (!CmsStringUtil.isEmpty(formatSelectOptions) && !getHtmlWidgetOption().isButtonHidden(CmsHtmlWidgetOption.OPTION_FORMATSELECT)) { formatSelectOptions = StringUtils.replace(formatSelectOptions, ";", ","); result.append("theme_advanced_blockformats : \"" + formatSelectOptions + "\",\n"); } result.append("theme_advanced_resizing : false,\n"); result.append("theme_advanced_resizing_use_cookie : false"); result.append("}));\n"); result.append("contentFields[contentFields.length] = document.getElementById(\"").append(id).append("\");\n"); result.append("</script>\n"); result.append("</td>"); return result.toString(); } /** * @see org.opencms.widgets.I_CmsWidget#newInstance() */ public I_CmsWidget newInstance() { return new CmsTinyMCEWidget(getHtmlWidgetOption()); } /** * Builds the toolbar. * * @return Javascript code for toolbar configuration */ private String getToolbar() { String result = ""; List<String> barItems = getHtmlWidgetOption().getButtonBarShownItems(); List<List<String>> blocks = new ArrayList<List<String>>(); blocks.add(new ArrayList<String>()); String lastItem = null; List<String> processedItems = new ArrayList<String>(); // translate buttons and eliminate adjacent separators for (String barItem : barItems) { if (BUTTON_TRANSLATION_MAP.containsKey(barItem)) { barItem = BUTTON_TRANSLATION_MAP.get(barItem); } if (barItem.equals("[") || barItem.equals("]") || barItem.equals("-")) { barItem = "|"; if ("|".equals(lastItem)) { continue; } } if (barItem.indexOf(",") > -1) { for (String subItem : barItem.split(",")) { processedItems.add(subItem); } } else { processedItems.add(barItem); } lastItem = barItem; } // remove leading or trailing '|' if ((processedItems.size() > 0) && processedItems.get(0).equals("|")) { processedItems.remove(0); } if ((processedItems.size() > 0) && processedItems.get(processedItems.size() - 1).equals("|")) { processedItems.remove(processedItems.size() - 1); } // transform flat list into list of groups for (String processedItem : processedItems) { blocks.get(blocks.size() - 1).add(processedItem); if ("|".equals(processedItem)) { blocks.add(new ArrayList<String>()); } } // produce the TinyMCE toolbar options from the groups // we use TinyMCE's button rows as groups instead of rows and fix the layout using CSS. // This is because we want the button bars to wrap automatically when there is not enough space. // Using this method, the wraps can only occur between different blocks/rows. int row = 1; for (List<String> block : blocks) { result = result + "theme_advanced_buttons" + row + ": '" + CmsStringUtil.listAsString(block, ",") + "',\n"; row += 1; } // overwrite default toolbar rows for (int r = row; r <= 4; r++) { result = result + "theme_advanced_buttons" + r + ": '',\n"; } return result; } }