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