001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.widgets;
029
030import org.opencms.main.OpenCms;
031import org.opencms.util.CmsPair;
032import org.opencms.util.CmsStringUtil;
033import org.opencms.util.I_CmsRegexSubstitution;
034import org.opencms.workplace.galleries.CmsAjaxDownloadGallery;
035import org.opencms.workplace.galleries.CmsAjaxImageGallery;
036import org.opencms.workplace.galleries.CmsAjaxLinkGallery;
037
038import java.util.ArrayList;
039import java.util.Arrays;
040import java.util.Collections;
041import java.util.Iterator;
042import java.util.List;
043import java.util.Map;
044import java.util.regex.Matcher;
045import java.util.regex.Pattern;
046
047import com.google.common.collect.Maps;
048
049/**
050 * An option of a HTML type widget.<p>
051 *
052 * Options can be defined for each element of the type <code>OpenCmsHtml</code> using the widget <code>HtmlWidget</code>.
053 * They have to be placed in the annotation section of a XSD describing an XML content. The <code>configuration</code> attribute
054 * in the <code>layout</code> node for the element must contain the activated options as a comma separated String value:<p>
055 *
056 * <code><layout element="Text" widget="HtmlWidget" configuration="height:400px,link,anchor,imagegallery,downloadgallery,formatselect,source" /></code><p>
057 *
058 * Available options are:
059 * <ul>
060 * <li><code>anchor</code>: the anchor dialog button</li>
061 * <li><code>buttonbar:${button bar items, separated by ';'}</code>: an individual button bar configuration,
062 *     see {@link #BUTTONBAR_DEFAULT} for an example.</li>
063 * <li><code>css:/vfs/path/to/cssfile.css</code>: the absolute path in the OpenCms VFS to the CSS style sheet
064 *     to use to render the contents in the editor (availability depends on the integrated editor)</li>
065 * <li><code>formatselect</code>: the format selector for selecting text format like paragraph or headings</li>
066 * <li><code>formatselect.options:${list of options, separated by ';'}</code>: the options that should be available in the format selector,
067 *     e.g. <code>formatselect.options:p;h1;h2</code></li>
068 * <li><code>fullpage</code>: the editor creates an entire HTML page code</li>
069 * <li><code>${gallerytype}</code>: Shows a gallery dialog button, e.g. <code>imagegallery</code> displays
070 *     the image gallery button or <code>downloadgallery</code> displays the download gallery button</li>
071 * <li><code>height:${editorheight}</code>: the editor height, where the height can be specified in px or %, e.g. <code>400px</code></li>
072 * <li><code>hidebuttons:${list of buttons to hide, separated by ';'}</code>: the buttons to hide that usually appear in
073 *     the default button bar, e.g. <code>hidebuttons:bold;italic;underline;strikethrough</code> hides some formatting buttons</li>
074 * <li><code>image</code>: the image dialog button (availability depends on the integrated editor)</li>
075 * <li><code>link</code>: the link dialog button</li>
076 * <li><code>source</code>: shows the source code toggle button(s)</li>
077 * <li><code>stylesxml:/vfs/path/to/stylefile.xml</code>: the absolute path in the OpenCms VFS to the user defined
078 *     styles that should be displayed in the style selector (availability depends on the integrated editor)</li>
079 * <li><code>stylesformat:/vfs/path/to/stylefile.xml</code>: the absolute path in the OpenCms VFS to the user defined
080 *     styles format that should be displayed in the style selector (availability depends on the integrated editor)</li>
081 * <li><code>table</code>: the table dialog button (availability depends on the integrated editor)</li>
082 * </ul>
083 * Some things like the button bar items should be defined in the global widget configuration of the file <code>opencms-vfs.xml</code>.<p>
084 *
085 * @since 6.0.1
086 */
087public class CmsHtmlWidgetOption {
088
089    /** The button bar end block indicator. */
090    public static final String BUTTONBAR_BLOCK_END = "]";
091
092    /** The button bar start block indicator. */
093    public static final String BUTTONBAR_BLOCK_START = "[";
094
095    /** The default editor widget button bar configuration. */
096    public static final String BUTTONBAR_DEFAULT = "[;undo;redo;];"
097        + "[;find;replace;];"
098        + "[;copy;paste;pastetext;];"
099        + "[;visualchars;-;ltr;rtl;];"
100        + "[;removeformat;-;formatselect;-;style;];"
101        + "[;bold;italic;underline;strikethrough;];"
102        + "[;subscript;superscript;];"
103        + "[;orderedlist;unorderedlist;];"
104        + "[;alignleft;aligncenter;alignright;justify;];"
105        + "[;outdent;indent;-;blockquote;];"
106        + "[;link;unlink;-;anchor;];"
107        + "[;fontselect;-;fontsizeselect;];"
108        + "[;backcolor;forecolor;];"
109        + "[;imagegallery;downloadgallery;linkgallery;-;media;];"
110        + "[;specialchar;emotions;];"
111        + "[;table;-;hr;-;nonbreaking;];"
112        // the next line of options seem to be broken or useless:
113        + "[;editorlink;abbr;absolute;acronym;advhr;attribs;cite;cleanup;del;ins;insertdate;insertlayer;inserttime;movebackward;moveforward;newdocument;pagebreak;styleprops;template;visualaid;];"
114        + "[;print;-;spellcheck;-;fitwindow;-;source;];";
115
116    /** The default button bar configuration as List. */
117    public static final List<String> BUTTONBAR_DEFAULT_LIST = CmsStringUtil.splitAsList(BUTTONBAR_DEFAULT, ';');
118
119    /** The button bar separator. */
120    public static final String BUTTONBAR_SEPARATOR = "-";
121
122    /** The delimiter to use in the configuration String. */
123    public static final String DELIMITER_OPTION = ",";
124
125    /** The delimiter to use for separation of option values. */
126    public static final char DELIMITER_VALUE = ';';
127
128    /** The editor widget default maximum height to use. */
129    public static final String EDITOR_DEFAULMAXTHEIGHT = "400px";
130
131    /** Option for the "abbreviation" button. */
132    public static final String OPTION_ABBR = "abbr";
133
134    /** Option for the "absolute" button. */
135    public static final String OPTION_ABSOLUTE = "absolute";
136
137    /** Option for the "acronym" button. */
138    public static final String OPTION_ACRONYM = "acronym";
139
140    /** Option for the "advanced hr" button. */
141    public static final String OPTION_ADVHR = "advhr";
142
143    /** Allow scripts in source code editor. */
144    public static final String OPTION_ALLOWSCRIPTS = "allowscripts";
145
146    /** Option for the "anchor" dialog. */
147    public static final String OPTION_ANCHOR = "anchor";
148
149    /** Option for the "insert/edit attributes" button. */
150    public static final String OPTION_ATTRIBS = "attribs";
151
152    /** Option for the "background color" button. */
153    public static final String OPTION_BACKCOLOR = "backcolor";
154
155    /** Option for the "block quote" button. */
156    public static final String OPTION_BLOCKQUOTE = "blockquote";
157
158    /** Option for the "buttonbar" configuration. */
159    public static final String OPTION_BUTTONBAR = "buttonbar:";
160
161    /** Option for the "citation" button. */
162    public static final String OPTION_CITE = "cite";
163
164    /** Option for the "clean up messy code" button. */
165    public static final String OPTION_CLEANUP = "cleanup";
166
167    /** Option for the css style sheet VFS path to use in the widget area. */
168    public static final String OPTION_CSS = "css:";
169
170    /** Option for the "mark text as deletion" button. */
171    public static final String OPTION_DEL = "del";
172
173    /** Option for the "editor link" dialog (editor specific). */
174    public static final String OPTION_EDITORLINK = "editorlink";
175
176    /** Option for the "emotions" button. */
177    public static final String OPTION_EMOTIONS = "emotions";
178
179    /** Option for the "find" dialog. */
180    public static final String OPTION_FIND = "find";
181
182    /** Option for the "font select" button. */
183    public static final String OPTION_FONTSELECT = "fontselect";
184
185    /** Option for the "font size" button. */
186    public static final String OPTION_FONTSIZESELECT = "fontsizeselect";
187
188    /** Option for the "text color" button. */
189    public static final String OPTION_FORECOLOR = "forecolor";
190
191    /** Option for the "formatselect" selector. */
192    public static final String OPTION_FORMATSELECT = "formatselect";
193
194    /** Option for the "formatselect" options selector. */
195    public static final String OPTION_FORMATSELECT_OPTIONS = "formatselect.options:";
196
197    /** Option for the "fullpage" editor variant. */
198    public static final String OPTION_FULLPAGE = "fullpage";
199
200    /** Option for the "height" configuration. */
201    public static final String OPTION_HEIGHT = "height:";
202
203    /** Option for the "hidebuttons" configuration. */
204    public static final String OPTION_HIDEBUTTONS = "hidebuttons:";
205
206    /** Option for the "hr" button. */
207    public static final String OPTION_HR = "hr";
208
209    /** Option for the "image" dialog. */
210    public static final String OPTION_IMAGE = "image";
211
212    /** Option to import styles from stylesheet into the style selector. */
213    public static final String OPTION_IMPORTCSS = "importcss";
214
215    /** Option for the "mark text as insertion" button. */
216    public static final String OPTION_INS = "ins";
217
218    /** Option for the "insert date" button. */
219    public static final String OPTION_INSERTDATE = "insertdate";
220
221    /** Option for the "insert layer" button. */
222    public static final String OPTION_INSERTLAYER = "insertlayer";
223
224    /** Option for the "insert time" button. */
225    public static final String OPTION_INSERTTIME = "inserttime";
226
227    /** Option for the "link" dialog. */
228    public static final String OPTION_LINK = "link";
229
230    /** Option for the "left to right text" button. */
231    public static final String OPTION_LTR = "ltr";
232
233    /** Option for the "insert media (flash, video, audio)" button. */
234    public static final String OPTION_MEDIA = "media";
235
236    /** Option for the "move backward (layer context)" button. */
237    public static final String OPTION_MOVEBACKWARD = "movebackward";
238
239    /** Option for the "move forward (layer context)" button. */
240    public static final String OPTION_MOVEFORWARD = "moveforward";
241
242    /** Option for the "new document (remove existing content)" button. */
243    public static final String OPTION_NEWDOCUMENT = "newdocument";
244
245    /** Option for the "non breaking white space" button. */
246    public static final String OPTION_NONBREAKING = "nonbreaking";
247
248    /** Option for the "page break" button. */
249    public static final String OPTION_PAGEBREAK = "pagebreak";
250
251    /** Option for the "paste from word" button. */
252    public static final String OPTION_PASTEWORD = "pasteword";
253
254    /** Option for the "print" button. */
255    public static final String OPTION_PRINT = "print";
256
257    /** Option for the "replace" dialog. */
258    public static final String OPTION_REPLACE = "replace";
259
260    /** Option for the "right to left text" button. */
261    public static final String OPTION_RTL = "rtl";
262
263    /** Option for the "source" code mode. */
264    public static final String OPTION_SOURCE = "source";
265
266    /** Option for the "special char dialog" button. */
267    public static final String OPTION_SPECIALCHAR = "specialchar";
268
269    /** Option for the "spell check" dialog. */
270    public static final String OPTION_SPELLCHECK = "spellcheck";
271
272    /** Option for the style select box. */
273    public static final String OPTION_STYLE = "style";
274
275    /** Option for the "edit CSS style" button. */
276    public static final String OPTION_STYLEPROPS = "styleprops";
277
278    /** Option for the styles XML VFS path to use in the widget area. */
279    public static final String OPTION_STYLES = "stylesxml:";
280
281    /** Option for the styles format VFS path to use in the widget area. */
282    public static final String OPTION_STYLES_FORMAT = "stylesformat:";
283
284    /** Option for the "table" dialog. */
285    public static final String OPTION_TABLE = "table";
286
287    /** Option for the "insert predefined template content" button. */
288    public static final String OPTION_TEMPLATE = "template";
289
290    /** Option for the "unlink" button. */
291    public static final String OPTION_UNLINK = "unlink";
292
293    /** Option for the "show/hide guidelines/invisible elements" button. */
294    public static final String OPTION_VISUALAID = "visualaid";
295
296    /** Option for the "show/hide visual control characters" button. */
297    public static final String OPTION_VISUALCHARS = "visualchars";
298
299    /** Option for the default protocol for links */
300    public static final String OPTION_LINKDEFAULTPROTOCOL = "linkdefaultprotocol:";
301
302    /** The optional buttons that can be additionally added to the button bar. */
303    public static final String[] OPTIONAL_BUTTONS = {
304        OPTION_ANCHOR,
305        OPTION_EDITORLINK,
306        OPTION_FIND,
307        OPTION_FORMATSELECT,
308        OPTION_IMAGE,
309        OPTION_LINK,
310        OPTION_REPLACE,
311        OPTION_SOURCE,
312        OPTION_SPELLCHECK,
313        OPTION_STYLE,
314        OPTION_TABLE,
315        OPTION_UNLINK,
316        OPTION_HR,
317        OPTION_ABBR,
318        OPTION_ABSOLUTE,
319        OPTION_ACRONYM,
320        OPTION_ADVHR,
321        OPTION_ATTRIBS,
322        OPTION_BACKCOLOR,
323        OPTION_BLOCKQUOTE,
324        OPTION_CITE,
325        OPTION_CLEANUP,
326        OPTION_DEL,
327        OPTION_EMOTIONS,
328        OPTION_FONTSELECT,
329        OPTION_FONTSIZESELECT,
330        OPTION_FORECOLOR,
331        OPTION_INS,
332        OPTION_INSERTDATE,
333        OPTION_INSERTLAYER,
334        OPTION_INSERTTIME,
335        OPTION_LTR,
336        OPTION_MEDIA,
337        OPTION_MOVEBACKWARD,
338        OPTION_MOVEFORWARD,
339        OPTION_NEWDOCUMENT,
340        OPTION_NONBREAKING,
341        OPTION_PAGEBREAK,
342        OPTION_PASTEWORD,
343        OPTION_PRINT,
344        OPTION_RTL,
345        OPTION_STYLEPROPS,
346        OPTION_SPECIALCHAR,
347        OPTION_TEMPLATE,
348        OPTION_VISUALAID,
349        CmsAjaxImageGallery.GALLERYTYPE_NAME,
350        CmsAjaxDownloadGallery.GALLERYTYPE_NAME,
351        CmsAjaxLinkGallery.GALLERYTYPE_NAME};
352
353    /** The optional buttons that can be additionally added to the button bar as list. */
354    public static final List<String> OPTIONAL_BUTTONS_LIST = Arrays.asList(OPTIONAL_BUTTONS);
355
356    /** Pattern used for matching embedded gallery configurations. */
357    public static final Pattern PATTERN_EMBEDDED_GALLERY_CONFIG = Pattern.compile(
358        "(?<![a-zA-Z0-9_])(imagegallery|downloadgallery)(\\{.*?\\})");
359
360    /** If this is set, the contents of the path following the ':' will be interpreted as JSON and passed to TinyMCE directly. */
361    public static final String OPTION_EDITORCONFIG = "editorconfig:";
362
363    /** Holds the global button bar configuration options to increase performance. */
364    private static List<String> m_globalButtonBarOption;
365
366    /** The additional buttons list. */
367    private List<String> m_additionalButtons;
368
369    /** Flag which controls whether scripts are allowed in the source code editor. */
370    private boolean m_allowScripts;
371
372    /** The button bar items. */
373    private List<String> m_buttonBar;
374
375    /** The button bar configuration options. */
376    private List<String> m_buttonBarOption;
377
378    /** The button bar options. */
379    private String m_buttonBarOptionString;
380
381    /** The configuration. */
382    private String m_configuration;
383
384    /** The CSS style sheet path. */
385    private String m_cssPath;
386
387    /** The editor height. */
388    private String m_editorHeight;
389
390    /** The embedded configuration  strings for galleries, if available. */
391    private Map<String, String> m_embeddedConfigurations = Maps.newHashMap();
392
393    /** The format select options. */
394    private String m_formatSelectOptions;
395
396    /** The full page flag. */
397    private boolean m_fullPage;
398
399    /** The hidden buttons. */
400    private List<String> m_hiddenButtons;
401
402    /** True if styles from stylesheet should be imported into the style selector. */
403    private boolean m_importCss;
404
405    /**
406    private boolean m_allowScripts;
407
408    /** The path for custom styles. */
409    private String m_stylesFormatPath;
410
411    /** The style XML path. */
412    private String m_stylesXmlPath;
413
414    /** Path to an external TinyMCE JSON config file. */
415    private String m_editorConfigPath;
416
417    /** The link default protocol */
418    private String m_linkDefaultProtocol;
419
420    /**
421     * Creates a new empty HTML widget object object.<p>
422     */
423    public CmsHtmlWidgetOption() {
424
425        // initialize the options
426        init(null);
427    }
428
429    /**
430     * Creates a new HTML widget object object, configured by the given configuration String.<p>
431     *
432     * @param configuration configuration String to parse
433     */
434    public CmsHtmlWidgetOption(String configuration) {
435
436        // initialize the options
437        init(configuration);
438    }
439
440    /**
441     * Returns a HTML widget configuration String created from the given HTML widget option.<p>
442     *
443     * @param option the HTML widget options to create the configuration String for
444     *
445     * @return a select widget configuration String created from the given HTML widget option object
446     */
447    public static String createConfigurationString(CmsHtmlWidgetOption option) {
448
449        StringBuffer result = new StringBuffer(512);
450        boolean added = false;
451        if (!option.getEditorHeight().equals(EDITOR_DEFAULMAXTHEIGHT)) {
452            // append the height configuration
453            result.append(OPTION_HEIGHT);
454            result.append(option.getEditorHeight());
455            added = true;
456        }
457        if (option.useCss()) {
458            // append the CSS VFS path
459            if (added) {
460                result.append(DELIMITER_OPTION);
461            }
462            result.append(OPTION_CSS);
463            result.append(option.getCssPath());
464            added = true;
465        }
466        if (option.showStylesXml()) {
467            // append the styles XML VFS path
468            if (added) {
469                result.append(DELIMITER_OPTION);
470            }
471            result.append(OPTION_STYLES);
472            result.append(option.getStylesXmlPath());
473            added = true;
474        }
475        if (!option.getAdditionalButtons().isEmpty()) {
476            // append the additional buttons to show
477            if (added) {
478                result.append(DELIMITER_OPTION);
479            }
480            result.append(
481                CmsStringUtil.collectionAsString(option.getAdditionalButtons(), String.valueOf(DELIMITER_OPTION)));
482            added = true;
483        }
484        if (!option.getHiddenButtons().isEmpty()) {
485            // append the buttons to hide from tool bar
486            if (added) {
487                result.append(DELIMITER_OPTION);
488            }
489            result.append(OPTION_HIDEBUTTONS);
490            result.append(CmsStringUtil.collectionAsString(option.getHiddenButtons(), String.valueOf(DELIMITER_VALUE)));
491            added = true;
492        }
493        if (CmsStringUtil.isNotEmpty(option.getButtonBarOptionString())) {
494            // append the button bar definition
495            if (added) {
496                result.append(DELIMITER_OPTION);
497            }
498            result.append(OPTION_BUTTONBAR);
499            result.append(option.getButtonBarOptionString());
500            added = true;
501        }
502        if (option.isImportCss()) {
503            if (added) {
504                result.append(DELIMITER_OPTION);
505            }
506            result.append(OPTION_IMPORTCSS);
507            added = true;
508        }
509        if (CmsStringUtil.isNotEmpty(option.getFormatSelectOptions())) {
510            // append the format select option String
511            if (added) {
512                result.append(DELIMITER_OPTION);
513            }
514            result.append(OPTION_FORMATSELECT_OPTIONS);
515            result.append(option.getFormatSelectOptions());
516            added = true;
517        }
518
519        if (null != option.getEditorConfigPath()) {
520            if (added) {
521                result.append(DELIMITER_OPTION);
522            }
523            result.append(OPTION_EDITORCONFIG);
524            result.append(option.getEditorConfigPath());
525            added = true;
526        }
527
528        if (CmsStringUtil.isNotEmpty(option.getLinkDefaultProtocol())) {
529            result.append(OPTION_LINKDEFAULTPROTOCOL);
530            result.append(option.getLinkDefaultProtocol());
531        }
532
533        return result.toString();
534    }
535
536    /**
537     * Parses and removes embedded gallery configuration strings.
538     *
539     * @param configuration the configuration string to parse
540     *
541     * @return a map containing both the string resulting from removing the embedded configurations, and the embedded configurations as a a map
542     */
543    public static CmsPair<String, Map<String, String>> parseEmbeddedGalleryOptions(String configuration) {
544
545        final Map<String, String> galleryOptions = Maps.newHashMap();
546        String resultConfig = CmsStringUtil.substitute(
547            PATTERN_EMBEDDED_GALLERY_CONFIG,
548            configuration,
549            new I_CmsRegexSubstitution() {
550
551                public String substituteMatch(String string, Matcher matcher) {
552
553                    String galleryName = string.substring(matcher.start(1), matcher.end(1));
554                    String embeddedConfig = string.substring(matcher.start(2), matcher.end(2));
555                    galleryOptions.put(galleryName, embeddedConfig);
556                    return galleryName;
557                }
558            });
559        return CmsPair.create(resultConfig, galleryOptions);
560    }
561
562    /**
563     * Returns the buttons to show additionally as list with button names.<p>
564     *
565     * @return the buttons to show additionally as list with button names
566     */
567    public List<String> getAdditionalButtons() {
568
569        return m_additionalButtons;
570    }
571
572    /**
573     * Returns the specific editor button bar string generated from the configuration.<p>
574     *
575     * The lookup map can contain translations for the button names, the separator and the block names.
576     * The button bar will be automatically surrounded by block start and end items if they are not explicitly defined.<p>
577     *
578     * It may be necessary to write your own method to generate the button bar string for a specific editor widget.
579     * In this case, use the method {@link #getButtonBarShownItems()} to get the calculated list of shown button bar items.<p>
580     *
581     * @param buttonNamesLookUp the lookup map with translations for the button names, the separator and the block names or <code>null</code>
582     * @param itemSeparator the separator for the tool bar items
583     * @return the button bar string generated from the configuration
584     */
585    public String getButtonBar(Map<String, String> buttonNamesLookUp, String itemSeparator) {
586
587        return getButtonBar(buttonNamesLookUp, itemSeparator, true);
588    }
589
590    /**
591     * Returns the specific editor button bar string generated from the configuration.<p>
592     *
593     * The lookup map can contain translations for the button names, the separator and the block names.<p>
594     *
595     * It may be necessary to write your own method to generate the button bar string for a specific editor widget.
596     * In this case, use the method {@link #getButtonBarShownItems()} to get the calculated list of shown button bar items.<p>
597     *
598     * @param buttonNamesLookUp the lookup map with translations for the button names, the separator and the block names or <code>null</code>
599     * @param itemSeparator the separator for the tool bar items
600     * @param addMissingBlock flag indicating if the button bar should be automatically surrounded by a block if not explicitly defined
601     * @return the button bar string generated from the configuration
602     */
603    public String getButtonBar(Map<String, String> buttonNamesLookUp, String itemSeparator, boolean addMissingBlock) {
604
605        // first get the calculated button bar items
606        List<String> buttonBar = getButtonBarShownItems();
607        if (addMissingBlock) {
608            // the button bar has to be surrounded by block items, check it
609            if (!buttonBar.isEmpty()) {
610                if (!buttonBar.get(0).equals(BUTTONBAR_BLOCK_START)) {
611                    // add missing start block item
612                    buttonBar.add(0, BUTTONBAR_BLOCK_START);
613                }
614                if (!buttonBar.get(buttonBar.size() - 1).equals(BUTTONBAR_BLOCK_END)) {
615                    // add missing end block items
616                    buttonBar.add(BUTTONBAR_BLOCK_END);
617                }
618            }
619        }
620        StringBuffer result = new StringBuffer(512);
621        boolean isFirst = true;
622        for (Iterator<String> i = buttonBar.iterator(); i.hasNext();) {
623            String barItem = i.next();
624            if (BUTTONBAR_BLOCK_START.equals(barItem)) {
625                // start a block
626                if (!isFirst) {
627                    result.append(itemSeparator);
628                }
629                result.append(getButtonName(barItem, buttonNamesLookUp));
630                // starting a block means also: next item is the first (of the block)
631                isFirst = true;
632            } else if (BUTTONBAR_BLOCK_END.equals(barItem)) {
633                // end a block (there is no item separator added before ending the block)
634                result.append(getButtonName(barItem, buttonNamesLookUp));
635                isFirst = false;
636            } else {
637                // button or separator
638                if (!isFirst) {
639                    result.append(itemSeparator);
640                }
641                result.append(getButtonName(barItem, buttonNamesLookUp));
642                isFirst = false;
643            }
644        }
645        return result.toString();
646    }
647
648    /**
649     * Returns the individual button bar configuration option.<p>
650     *
651     * @return the individual button bar configuration option
652     */
653    public List<String> getButtonBarOption() {
654
655        if (m_buttonBarOption == null) {
656            // use lazy initializing for performance reasons
657            if (CmsStringUtil.isEmpty(getButtonBarOptionString())) {
658                // no individual configuration defined, create empty list
659                m_buttonBarOption = Collections.emptyList();
660            } else {
661                // create list of button bar options from configuration string
662                m_buttonBarOption = CmsStringUtil.splitAsList(getButtonBarOptionString(), DELIMITER_VALUE, true);
663            }
664        }
665        return m_buttonBarOption;
666    }
667
668    /**
669     * Returns the individual button bar configuration option string.<p>
670     *
671     * @return the individual button bar configuration option string
672     */
673    public String getButtonBarOptionString() {
674
675        return m_buttonBarOptionString;
676    }
677
678    /**
679     * Returns the calculated button bar items, including blocks and separators, considering the current widget configuration.<p>
680     *
681     * Use this method to get the calculated list of button bar items if {@link #getButtonBar(Map, String)} can not
682     * be used for a specific editor widget.<p>
683     *
684     * @return the calculated button bar items
685     */
686    public List<String> getButtonBarShownItems() {
687
688        if (m_buttonBar == null) {
689            // first get individual button bar configuration
690            List<String> buttonBar = getButtonBarOption();
691            if (buttonBar.isEmpty()) {
692                // no specific button bar defined, try to get global configuration first
693                if (m_globalButtonBarOption == null) {
694                    // global configuration not yet parsed, check it now
695                    String defaultConf = OpenCms.getXmlContentTypeManager().getWidgetDefaultConfiguration(
696                        CmsHtmlWidget.class.getName());
697                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(defaultConf)
698                        && defaultConf.contains(OPTION_BUTTONBAR)) {
699                        // found a global configuration containing a button bar definition, parse it
700                        CmsHtmlWidgetOption option = new CmsHtmlWidgetOption(defaultConf);
701                        // set global configuration in static member
702                        m_globalButtonBarOption = option.getButtonBarOption();
703                    } else {
704                        // no global configuration present, set static member to empty list
705                        m_globalButtonBarOption = Collections.emptyList();
706                    }
707                }
708                if (m_globalButtonBarOption.isEmpty()) {
709                    // no global button bar configuration found, use default button bar
710                    buttonBar = BUTTONBAR_DEFAULT_LIST;
711                } else {
712                    // found a global configuration containing a button bar definition, use it
713                    buttonBar = m_globalButtonBarOption;
714                }
715            }
716
717            List<String> result = new ArrayList<String>(buttonBar.size());
718            int lastSep = -1;
719            int lastBlock = -1;
720            boolean buttonInBlockAdded = false;
721            boolean buttonSinceSepAdded = false;
722            for (Iterator<String> i = buttonBar.iterator(); i.hasNext();) {
723                String barItem = i.next();
724                if (BUTTONBAR_BLOCK_START.equals(barItem)) {
725                    // start a block
726                    if ((lastSep != -1) && (lastSep == (result.size() - 1))) {
727                        // remove last separator before block start
728                        result.remove(lastSep);
729                    }
730                    lastBlock = result.size();
731                    lastSep = -1;
732                    buttonInBlockAdded = false;
733                    buttonSinceSepAdded = false;
734                    result.add(BUTTONBAR_BLOCK_START);
735                } else if (BUTTONBAR_BLOCK_END.equals(barItem)) {
736                    // end a block
737                    if (lastBlock != -1) {
738                        // block has been started
739                        if (lastSep == (result.size() - 1)) {
740                            // remove last separator before block end
741                            result.remove(lastSep);
742                        }
743                        //now check if there are items in it
744                        if (buttonInBlockAdded) {
745                            // block has items, add end
746                            result.add(BUTTONBAR_BLOCK_END);
747                        } else {
748                            // block has no items, remove block start ite,
749                            result.remove(lastBlock);
750                        }
751                        lastBlock = -1;
752                        lastSep = -1;
753                        buttonInBlockAdded = false;
754                        buttonSinceSepAdded = false;
755                    }
756                } else if (BUTTONBAR_SEPARATOR.equals(barItem)) {
757                    // insert a separator depending on preconditions
758                    if (buttonSinceSepAdded) {
759                        lastSep = result.size();
760                        result.add(BUTTONBAR_SEPARATOR);
761                        buttonSinceSepAdded = false;
762                    }
763                } else {
764                    // insert a button depending on preconditions
765                    if (getHiddenButtons().contains(barItem)) {
766                        // skip hidden buttons
767                        continue;
768                    }
769                    if (OPTIONAL_BUTTONS_LIST.contains(barItem)) {
770                        // check optional buttons
771                        if (CmsAjaxImageGallery.GALLERYTYPE_NAME.equals(barItem)) {
772                            // special handling of image button to keep compatibility
773                            if (!(getAdditionalButtons().contains(barItem)
774                                || getAdditionalButtons().contains(OPTION_IMAGE))) {
775                                // skip image gallery as it is not defined as additional button
776                                continue;
777                            }
778                        } else if (OPTION_UNLINK.equals(barItem)) {
779                            // special handling of unlink button to show only if anchor, editor link or link button are active
780                            if (!(getAdditionalButtons().contains(OPTION_LINK)
781                                || getAdditionalButtons().contains(OPTION_EDITORLINK)
782                                || getAdditionalButtons().contains(OPTION_ANCHOR))) {
783                                // skip unlink button because no link buttons are defined as additional buttons
784                                continue;
785                            }
786                        } else if (OPTION_STYLE.equals(barItem)) {
787                            boolean showStyles = getAdditionalButtons().contains(barItem)
788                                || (getStylesFormatPath() != null)
789                                || (getStylesXmlPath() != null);
790                            if (!showStyles) {
791                                continue;
792                            }
793                        } else if (!getAdditionalButtons().contains(barItem)) {
794                            // skip all optional buttons that are not defined
795                            continue;
796                        }
797                    }
798                    result.add(barItem);
799                    buttonSinceSepAdded = true;
800                    if (lastBlock != -1) {
801                        buttonInBlockAdded = true;
802                    }
803                }
804            }
805            m_buttonBar = result;
806        }
807        return m_buttonBar;
808    }
809
810    /**
811     * Returns the original configuration String that was used to initialize the HTML widget options.<p>
812     *
813     * @return the original configuration String
814     */
815    public String getConfiguration() {
816
817        return m_configuration;
818    }
819
820    /**
821     * Returns the CSS style sheet VFS path to use in the widget area.<p>
822     *
823     * @return the CSS style sheet VFS path to use in the widget area
824     */
825    public String getCssPath() {
826
827        return m_cssPath;
828    }
829
830    /**
831     * Gets the path of a JSON file containing options to be passed directly into TinyMCE.
832     *
833     * @return the path of a JSON with direct TinyMCE options
834     */
835    public String getEditorConfigPath() {
836
837        return m_editorConfigPath;
838    }
839
840    /**
841     * Returns the widget editor height.<p>
842     *
843     * @return the widget editor height
844     */
845    public String getEditorHeight() {
846
847        return m_editorHeight;
848    }
849
850    /**
851     * Gets the embedded gallery configurations.<p>
852     *
853     * @return the embedded gallery configurations
854     */
855    public Map<String, String> getEmbeddedConfigurations() {
856
857        return m_embeddedConfigurations;
858    }
859
860    /**
861     * Returns the options for the format select box as String.<p>
862     *
863     * @return the options for the format select box as String
864     */
865    public String getFormatSelectOptions() {
866
867        return m_formatSelectOptions;
868    }
869
870    /**
871     * Returns the buttons to hide as list with button names.<p>
872     *
873     * @return the buttons to hide as list with button names
874     */
875    public List<String> getHiddenButtons() {
876
877        return m_hiddenButtons;
878    }
879
880    /**
881     * Returns the link default protocol to use when inserting/editing links via the link dialog.
882     *
883     * @return the link default protocol to use when inserting/editing links via the link dialog
884     */
885    public String getLinkDefaultProtocol() {
886
887        return m_linkDefaultProtocol;
888    }
889
890    /**
891     * Returns the styles format VFS path to use in the widget area.<p>
892     *
893     * @return the styles XML format path to use in the widget area
894     */
895    public String getStylesFormatPath() {
896
897        return m_stylesFormatPath;
898    }
899
900    /**
901     * Returns the styles XML VFS path to use in the widget area.<p>
902     *
903     * @return the styles XML VFS path to use in the widget area
904     */
905    public String getStylesXmlPath() {
906
907        return m_stylesXmlPath;
908    }
909
910    /**
911     * Initializes the widget options from the given configuration String.<p>
912     *
913     * @param configuration the configuration String
914     */
915    public void init(String configuration) {
916
917        // initialize the members
918        m_additionalButtons = new ArrayList<String>(OPTIONAL_BUTTONS_LIST.size());
919        m_configuration = configuration;
920        m_editorHeight = EDITOR_DEFAULMAXTHEIGHT;
921        m_hiddenButtons = new ArrayList<String>();
922        // initialize the widget options
923        parseOptions(configuration);
924    }
925
926    /**
927     * Returns true if scripts should be allowed in the source code editor.<p>
928     *
929     * @return true if scripts should be allowed in the source code editor
930     */
931    public boolean isAllowScripts() {
932
933        return m_allowScripts;
934    }
935
936    /**
937     * Returns if the button with the given name should be additionally shown.<p>
938     *
939     * @param buttonName the button name to check
940     *
941     * @return <code>true</code> if the button with the given name should be additionally shown, otherwise <code>false</code>
942     */
943    public boolean isButtonAdditional(String buttonName) {
944
945        return getAdditionalButtons().contains(buttonName);
946    }
947
948    /**
949     * Returns if the button with the given name should be hidden.<p>
950     *
951     * @param buttonName the button name to check
952     *
953     * @return <code>true</code> if the button with the given name should be hidden, otherwise <code>false</code>
954     */
955    public boolean isButtonHidden(String buttonName) {
956
957        return getHiddenButtons().contains(buttonName);
958    }
959
960    /**
961     * Returns if the button with the given name is optional.<p>
962     *
963     * @param buttonName the button name to check
964     *
965     * @return <code>true</code> if the button with the given name is optional, otherwise <code>false</code>
966     */
967    public boolean isButtonOptional(String buttonName) {
968
969        return OPTIONAL_BUTTONS_LIST.contains(buttonName);
970    }
971
972    /**
973     * Returns if the editor should be used in full page mode.<p>
974     *
975     * @return true if the editor should be used in full page mode, otherwise false
976     */
977    public boolean isFullPage() {
978
979        return m_fullPage;
980    }
981
982    /**
983     * Return true if the content stylesheet's styles should be imported into the style selector.<p>
984     *
985     * @return true if the content stylesheet's styles should be imported into the style selector
986     */
987    public boolean isImportCss() {
988
989        return m_importCss;
990    }
991
992    /**
993     * Sets the buttons to show additionally as list with button names.<p>
994     *
995     * @param buttons the buttons to show additionally as list with button names
996     */
997    public void setAdditionalButtons(List<String> buttons) {
998
999        m_additionalButtons = buttons;
1000    }
1001
1002    /**
1003     * Sets the individual button bar configuration option.<p>
1004     *
1005     * @param buttonBar the individual button bar configuration option
1006     */
1007    public void setButtonBarOption(List<String> buttonBar) {
1008
1009        m_buttonBarOption = buttonBar;
1010    }
1011
1012    /**
1013     * Sets the individual button bar configuration option string.<p>
1014     *
1015     * @param buttonBar the individual button bar configuration option string
1016     */
1017    public void setButtonBarOptionString(String buttonBar) {
1018
1019        m_buttonBarOptionString = buttonBar;
1020    }
1021
1022    /**
1023     * Sets the CSS style sheet VFS path to use in the widget area.<p>
1024     *
1025     * @param cssPath the CSS style sheet VFS path to use in the widget area
1026     */
1027    public void setCssPath(String cssPath) {
1028
1029        m_cssPath = cssPath;
1030    }
1031
1032    /**
1033     * Sets the path for a file containing JSON options to be passed directly into TinyMCE.
1034     *
1035     * @param optionJsonPath the path of a JSON file
1036     */
1037    public void setEditorConfigPath(String optionJsonPath) {
1038
1039        m_editorConfigPath = optionJsonPath;
1040    }
1041
1042    /**
1043     * Sets the widget editor height.<p>
1044     *
1045     * @param editorHeight the widget editor height
1046     */
1047    public void setEditorHeight(String editorHeight) {
1048
1049        m_editorHeight = editorHeight;
1050    }
1051
1052    /**
1053     * Sets the options for the format select box as String.<p>
1054     *
1055     * @param formatSelectOptions the options for the format select box as String
1056     */
1057    public void setFormatSelectOptions(String formatSelectOptions) {
1058
1059        m_formatSelectOptions = formatSelectOptions;
1060    }
1061
1062    /**
1063     * Sets if the editor should be used in full page mode.<p>
1064     *
1065     * @param fullPage true if the editor should be used in full page mode, otherwise false
1066     */
1067    public void setFullPage(boolean fullPage) {
1068
1069        m_fullPage = fullPage;
1070    }
1071
1072    /**
1073     * Sets the buttons to hide as list with button names.<p>
1074     *
1075     * @param buttons the buttons to hide as list with button names
1076     */
1077    public void setHiddenButtons(List<String> buttons) {
1078
1079        m_hiddenButtons = buttons;
1080    }
1081
1082    /**
1083     * Set the link default protocol to use when inserting/editing links via the link dialog
1084     *
1085     * @param linkDefaultProtocol
1086     *            the link default protocol to use when inserting/editing links via the link dialog
1087     */
1088    public void setLinkDefaultProtocol(String linkDefaultProtocol) {
1089
1090        m_linkDefaultProtocol = linkDefaultProtocol;
1091    }
1092
1093    /**
1094     * Sets the styles format VFS path to use in the widget area.<p>
1095     *
1096     * @param stylesFormatPath the styles XML VFS path to use in the widget area
1097     */
1098    public void setStylesFormatPath(String stylesFormatPath) {
1099
1100        m_stylesFormatPath = stylesFormatPath;
1101    }
1102
1103    /**
1104     * Sets the styles XML VFS path to use in the widget area.<p>
1105     *
1106     * @param stylesXmlPath the styles XML VFS path to use in the widget area
1107     */
1108    public void setStylesXmlPath(String stylesXmlPath) {
1109
1110        m_stylesXmlPath = stylesXmlPath;
1111    }
1112
1113    /**
1114     * Returns true if the anchor dialog button should be available.<p>
1115     *
1116     * @return if the anchor dialog button should be available
1117     */
1118    public boolean showAnchorDialog() {
1119
1120        return getAdditionalButtons().contains(OPTION_ANCHOR);
1121    }
1122
1123    /**
1124     * Returns true if the format selector should be available.<p>
1125     *
1126     * @return if the format selector should be available
1127     */
1128    public boolean showFormatSelect() {
1129
1130        return getAdditionalButtons().contains(OPTION_FORMATSELECT);
1131    }
1132
1133    /**
1134     * Returns true if the specified gallery type dialog button is shown.<p>
1135     *
1136     * @param galleryType the gallery type to check
1137     * @return true if the specified gallery type dialog button is shown, otherwise false
1138     */
1139    public boolean showGalleryDialog(String galleryType) {
1140
1141        return getAdditionalButtons().contains(galleryType);
1142    }
1143
1144    /**
1145     * Returns true if the image dialog button should be available.<p>
1146     *
1147     * @return if the image dialog button should be available
1148     */
1149    public boolean showImageDialog() {
1150
1151        return getAdditionalButtons().contains(OPTION_IMAGE);
1152    }
1153
1154    /**
1155     * Returns true if the link dialog button should be available.<p>
1156     *
1157     * @return if the link dialog button should be available
1158     */
1159    public boolean showLinkDialog() {
1160
1161        return getAdditionalButtons().contains(OPTION_LINK);
1162    }
1163
1164    /**
1165     * Returns true if the source code button should be available.<p>
1166     *
1167     * @return if the source code button should be available
1168     */
1169    public boolean showSourceEditor() {
1170
1171        return getAdditionalButtons().contains(OPTION_SOURCE);
1172    }
1173
1174    /**
1175     * Returns true if the styles format selector should be available.<p>
1176     *
1177     * @return if the styles format selector should be available
1178     */
1179    public boolean showStylesFormat() {
1180
1181        return CmsStringUtil.isNotEmpty(getStylesFormatPath());
1182    }
1183
1184    /**
1185     * Returns true if the styles selector should be available.<p>
1186     *
1187     * @return if the styles selector should be available
1188     */
1189    public boolean showStylesXml() {
1190
1191        return CmsStringUtil.isNotEmpty(getStylesXmlPath());
1192    }
1193
1194    /**
1195     * Returns true if the table dialog button should be available.<p>
1196     *
1197     * @return if the table dialog button should be available
1198     */
1199    public boolean showTableDialog() {
1200
1201        return getAdditionalButtons().contains(OPTION_TABLE);
1202    }
1203
1204    /**
1205     * Returns true if the widget editor should use a defined CSS style sheet.<p>
1206     *
1207     * @return if the widget editor should use a defined CSS style sheet
1208     */
1209    public boolean useCss() {
1210
1211        return CmsStringUtil.isNotEmpty(getCssPath());
1212    }
1213
1214    /**
1215     * Adds a button to the list of defined additional buttons.<p>
1216     *
1217     * @param buttonName the button name to add
1218     */
1219    protected void addAdditionalButton(String buttonName) {
1220
1221        m_additionalButtons.add(buttonName);
1222    }
1223
1224    /**
1225     * Returns the real button name matched with the look up map.<p>
1226     *
1227     * If no value is found in the look up map, the button name is returned unchanged.<p>
1228     *
1229     * @param barItem the button bar item name to look up
1230     * @param buttonNamesLookUp the look up map containing the button names and/or separator name to use
1231     * @return the translated button name
1232     */
1233    protected String getButtonName(String barItem, Map<String, String> buttonNamesLookUp) {
1234
1235        String result = barItem;
1236        if (buttonNamesLookUp != null) {
1237            String translatedName = buttonNamesLookUp.get(barItem);
1238            if (CmsStringUtil.isNotEmpty(translatedName)) {
1239                result = translatedName;
1240            }
1241        }
1242        return result;
1243    }
1244
1245    /**
1246     * Parses the given configuration String.<p>
1247     *
1248     * @param configuration the configuration String to parse
1249     */
1250    protected void parseOptions(String configuration) {
1251
1252        if (CmsStringUtil.isNotEmpty(configuration)) {
1253
1254            CmsPair<String, Map<String, String>> simplifiedStringAndGalleryOptions = parseEmbeddedGalleryOptions(
1255                configuration);
1256            configuration = simplifiedStringAndGalleryOptions.getFirst();
1257            m_embeddedConfigurations = simplifiedStringAndGalleryOptions.getSecond();
1258
1259            List<String> options = CmsStringUtil.splitAsList(configuration, DELIMITER_OPTION, true);
1260            Iterator<String> i = options.iterator();
1261            while (i.hasNext()) {
1262                String option = i.next();
1263                // check which option is defined
1264                if (option.startsWith(OPTION_FORMATSELECT_OPTIONS)) {
1265                    // the format select options
1266                    option = option.substring(OPTION_FORMATSELECT_OPTIONS.length());
1267                    setFormatSelectOptions(option);
1268                } else if (option.startsWith(OPTION_HEIGHT)) {
1269                    // the editor height
1270                    option = option.substring(OPTION_HEIGHT.length());
1271                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(option)) {
1272                        setEditorHeight(option);
1273                    }
1274                } else if (option.startsWith(OPTION_HIDEBUTTONS)) {
1275                    // buttons to hide from the tool bar
1276                    option = option.substring(OPTION_HIDEBUTTONS.length());
1277                    setHiddenButtons(CmsStringUtil.splitAsList(option, DELIMITER_VALUE, true));
1278                } else if (option.startsWith(OPTION_CSS)) {
1279                    // the editor CSS
1280                    option = option.substring(OPTION_CSS.length());
1281                    setCssPath(option);
1282                } else if (option.startsWith(OPTION_STYLES)) {
1283                    // the editor styles XML path
1284                    option = option.substring(OPTION_STYLES.length());
1285                    setStylesXmlPath(option);
1286                } else if (option.startsWith(OPTION_STYLES_FORMAT)) {
1287                    // the editor styles format path
1288                    option = option.substring(OPTION_STYLES_FORMAT.length());
1289                    setStylesFormatPath(option);
1290                } else if (option.startsWith(OPTION_BUTTONBAR)) {
1291                    // the button bar definition string
1292                    option = option.substring(OPTION_BUTTONBAR.length());
1293                    setButtonBarOptionString(option);
1294                } else if (option.startsWith(OPTION_EDITORCONFIG)) {
1295                    option = option.substring(OPTION_EDITORCONFIG.length());
1296                    setEditorConfigPath(option);
1297                } else if (option.startsWith(OPTION_IMPORTCSS)) {
1298                    m_importCss = true;
1299                } else if (option.startsWith(OPTION_ALLOWSCRIPTS)) {
1300                    m_allowScripts = true;
1301                } else if (option.startsWith(OPTION_LINKDEFAULTPROTOCOL)) {
1302                    // the link default protocol
1303                    option = option.substring(OPTION_LINKDEFAULTPROTOCOL.length());
1304                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(option)) {
1305                        setLinkDefaultProtocol(option);
1306                    }
1307                } else {
1308                    // check if option describes an additional button
1309                    if (OPTIONAL_BUTTONS_LIST.contains(option)) {
1310                        addAdditionalButton(option);
1311                    }
1312                }
1313            }
1314        }
1315    }
1316}