001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH (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, 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.ade.galleries.shared.I_CmsGalleryProviderConstants;
031import org.opencms.file.CmsFile;
032import org.opencms.file.CmsObject;
033import org.opencms.file.CmsResource;
034import org.opencms.i18n.CmsEncoder;
035import org.opencms.i18n.CmsMessages;
036import org.opencms.json.JSONException;
037import org.opencms.json.JSONObject;
038import org.opencms.main.CmsException;
039import org.opencms.main.CmsLog;
040import org.opencms.main.OpenCms;
041import org.opencms.main.OpenCmsSpellcheckHandler;
042import org.opencms.util.CmsStringUtil;
043import org.opencms.workplace.editors.CmsEditorDisplayOptions;
044import org.opencms.workplace.editors.CmsWorkplaceEditorConfiguration;
045import org.opencms.workplace.editors.I_CmsEditorCssHandler;
046import org.opencms.xml.content.I_CmsXmlContentHandler.DisplayType;
047import org.opencms.xml.types.A_CmsXmlContentValue;
048
049import java.io.UnsupportedEncodingException;
050import java.util.Arrays;
051import java.util.Collections;
052import java.util.HashSet;
053import java.util.Iterator;
054import java.util.List;
055import java.util.Locale;
056import java.util.Map;
057import java.util.Properties;
058import java.util.Set;
059
060import org.apache.commons.logging.Log;
061
062import com.google.common.collect.Lists;
063import com.google.common.collect.Sets;
064
065/**
066 * Provides a widget that creates a rich input field using the matching component, for use on a widget dialog.<p>
067 *
068 * The matching component is determined by checking the installed editors for the best matching component to use.<p>
069 *
070 * @since 6.0.1
071 */
072public class CmsHtmlWidget extends A_CmsHtmlWidget implements I_CmsADEWidget {
073
074    /** Labels for the default block format options. */
075    public static final Map<String, String> TINYMCE_DEFAULT_BLOCK_FORMAT_LABELS = Collections.unmodifiableMap(
076        CmsStringUtil.splitAsMap(
077            "p:Paragraph|address:Address|pre:Pre|h1:Header 1|h2:Header 2|h3:Header 3|h4:Header 4|h5:Header 5|h6:Header 6",
078            "|",
079            ":"));
080
081    /** The log object for this class. */
082    private static final Log LOG = CmsLog.getLog(CmsHtmlWidget.class);
083
084    /** The editor widget to use depending on the current users settings, current browser and installed editors. */
085    private I_CmsWidget m_editorWidget;
086
087    /**
088     * Creates a new html editing widget.<p>
089     */
090    public CmsHtmlWidget() {
091
092        // empty constructor is required for class registration
093        this("");
094    }
095
096    /**
097     * Creates a new html editing widget with the given configuration.<p>
098     *
099     * @param configuration the configuration to use
100     */
101    public CmsHtmlWidget(CmsHtmlWidgetOption configuration) {
102
103        super(configuration);
104    }
105
106    /**
107     * Creates a new html editing widget with the given configuration.<p>
108     *
109     * @param configuration the configuration to use
110     */
111    public CmsHtmlWidget(String configuration) {
112
113        super(configuration);
114    }
115
116    /**
117     * Gets the block format configuration string for TinyMCE from the configured format select options.<p>
118     *
119     * @param formatSelectOptions the format select options
120     *
121     * @return the block_formats configuration
122     */
123    public static String getTinyMceBlockFormats(String formatSelectOptions) {
124
125        String[] options = formatSelectOptions.split(";");
126        List<String> resultParts = Lists.newArrayList();
127        for (String option : options) {
128            String label = TINYMCE_DEFAULT_BLOCK_FORMAT_LABELS.get(option);
129            if (label == null) {
130                label = option;
131            }
132            resultParts.add(label + "=" + option);
133        }
134        String result = CmsStringUtil.listAsString(resultParts, ";");
135        return result;
136    }
137
138    /**
139     * @see org.opencms.widgets.I_CmsADEWidget#getConfiguration(org.opencms.file.CmsObject, org.opencms.xml.types.A_CmsXmlContentValue, org.opencms.i18n.CmsMessages, org.opencms.file.CmsResource, java.util.Locale)
140     */
141    public String getConfiguration(
142        CmsObject cms,
143        A_CmsXmlContentValue schemaType,
144        CmsMessages messages,
145        CmsResource resource,
146        Locale contentLocale) {
147
148        JSONObject result = getJSONConfiguration(cms, resource, contentLocale);
149        try {
150            addEmbeddedGalleryOptions(result, cms, schemaType, messages, resource, contentLocale);
151        } catch (JSONException e) {
152            LOG.error(e.getLocalizedMessage(), e);
153        }
154        return result.toString();
155    }
156
157    /**
158     * @see org.opencms.widgets.I_CmsADEWidget#getCssResourceLinks(org.opencms.file.CmsObject)
159     */
160    public List<String> getCssResourceLinks(CmsObject cms) {
161
162        // not needed for internal widget
163        return null;
164    }
165
166    /**
167     * @see org.opencms.widgets.I_CmsADEWidget#getDefaultDisplayType()
168     */
169    public DisplayType getDefaultDisplayType() {
170
171        return DisplayType.wide;
172    }
173
174    /**
175     * @see org.opencms.widgets.I_CmsWidget#getDialogIncludes(org.opencms.file.CmsObject, org.opencms.widgets.I_CmsWidgetDialog)
176     */
177    @Override
178    public String getDialogIncludes(CmsObject cms, I_CmsWidgetDialog widgetDialog) {
179
180        return getEditorWidget(cms, widgetDialog).getDialogIncludes(cms, widgetDialog);
181    }
182
183    /**
184     * @see org.opencms.widgets.I_CmsWidget#getDialogInitCall(org.opencms.file.CmsObject, org.opencms.widgets.I_CmsWidgetDialog)
185     */
186    @Override
187    public String getDialogInitCall(CmsObject cms, I_CmsWidgetDialog widgetDialog) {
188
189        return getEditorWidget(cms, widgetDialog).getDialogInitCall(cms, widgetDialog);
190    }
191
192    /**
193     * @see org.opencms.widgets.I_CmsWidget#getDialogInitMethod(org.opencms.file.CmsObject, org.opencms.widgets.I_CmsWidgetDialog)
194     */
195    @Override
196    public String getDialogInitMethod(CmsObject cms, I_CmsWidgetDialog widgetDialog) {
197
198        return getEditorWidget(cms, widgetDialog).getDialogInitMethod(cms, widgetDialog);
199    }
200
201    /**
202     * @see org.opencms.widgets.I_CmsWidget#getDialogWidget(org.opencms.file.CmsObject, org.opencms.widgets.I_CmsWidgetDialog, org.opencms.widgets.I_CmsWidgetParameter)
203     */
204    public String getDialogWidget(CmsObject cms, I_CmsWidgetDialog widgetDialog, I_CmsWidgetParameter param) {
205
206        return getEditorWidget(cms, widgetDialog).getDialogWidget(cms, widgetDialog, param);
207    }
208
209    /**
210     * @see org.opencms.widgets.I_CmsADEWidget#getInitCall()
211     */
212    public String getInitCall() {
213
214        // not needed for internal widget
215        return null;
216    }
217
218    /**
219     * @see org.opencms.widgets.I_CmsADEWidget#getJavaScriptResourceLinks(org.opencms.file.CmsObject)
220     */
221    public List<String> getJavaScriptResourceLinks(CmsObject cms) {
222
223        // not needed for internal widget
224        return null;
225    }
226
227    /**
228     * @see org.opencms.widgets.I_CmsADEWidget#getWidgetName()
229     */
230    public String getWidgetName() {
231
232        return CmsHtmlWidget.class.getName();
233    }
234
235    /**
236     * @see org.opencms.widgets.I_CmsADEWidget#isInternal()
237     */
238    public boolean isInternal() {
239
240        return true;
241    }
242
243    /**
244     * @see org.opencms.widgets.I_CmsWidget#newInstance()
245     */
246    public I_CmsWidget newInstance() {
247
248        return new CmsHtmlWidget(getConfiguration());
249    }
250
251    /**
252     * @see org.opencms.widgets.I_CmsWidget#setEditorValue(org.opencms.file.CmsObject, java.util.Map, org.opencms.widgets.I_CmsWidgetDialog, org.opencms.widgets.I_CmsWidgetParameter)
253     */
254    @Override
255    public void setEditorValue(
256        CmsObject cms,
257        Map<String, String[]> formParameters,
258        I_CmsWidgetDialog widgetDialog,
259        I_CmsWidgetParameter param) {
260
261        String[] values = formParameters.get(param.getId());
262        if ((values != null) && (values.length > 0)) {
263            String val = CmsEncoder.decode(values[0], CmsEncoder.ENCODING_UTF_8);
264            param.setStringValue(cms, val);
265        }
266    }
267
268    /**
269     * Adds the configuration for embedded gallery widgets the the JSON object.<p>
270     *
271     * @param result the  JSON object to modify
272     * @param cms the OpenCms context
273     * @param schemaType the schema type
274     * @param messages the messages
275     * @param resource the edited resource
276     * @param contentLocale the content locale
277     *
278     * @throws JSONException in case JSON manipulation fails
279     */
280    protected void addEmbeddedGalleryOptions(
281        JSONObject result,
282        CmsObject cms,
283        A_CmsXmlContentValue schemaType,
284        CmsMessages messages,
285        CmsResource resource,
286        Locale contentLocale) throws JSONException {
287
288        String embeddedImageGalleryOptions = getHtmlWidgetOption().getEmbeddedConfigurations().get("imagegallery");
289        String embeddedDownloadGalleryOptions = getHtmlWidgetOption().getEmbeddedConfigurations().get(
290            "downloadgallery");
291
292        if (embeddedDownloadGalleryOptions != null) {
293            CmsAdeDownloadGalleryWidget widget = new CmsAdeDownloadGalleryWidget();
294            widget.setConfiguration(embeddedDownloadGalleryOptions);
295            String downloadJsonString = widget.getConfiguration(
296                cms,
297                schemaType/*?*/,
298                messages,
299                resource,
300                contentLocale);
301
302            JSONObject downloadJsonObj = new JSONObject(downloadJsonString);
303            filterEmbeddedGalleryOptions(downloadJsonObj);
304            result.put("downloadGalleryConfig", downloadJsonObj);
305        }
306
307        if (embeddedImageGalleryOptions != null) {
308            CmsAdeImageGalleryWidget widget = new CmsAdeImageGalleryWidget();
309            widget.setConfiguration(embeddedImageGalleryOptions);
310            String imageJsonString = widget.getConfiguration(cms, schemaType/*?*/, messages, resource, contentLocale);
311            JSONObject imageJsonObj = new JSONObject(imageJsonString);
312            filterEmbeddedGalleryOptions(imageJsonObj);
313            result.put("imageGalleryConfig", imageJsonObj);
314        }
315    }
316
317    /**
318     * Returns the WYSIWYG editor configuration as a JSON object.<p>
319     *
320     * @param cms the OpenCms context
321     * @param resource the edited resource
322     * @param contentLocale the edited content locale
323     *
324     * @return the configuration
325     */
326    protected JSONObject getJSONConfiguration(CmsObject cms, CmsResource resource, Locale contentLocale) {
327
328        JSONObject result = new JSONObject();
329
330        CmsHtmlWidgetOption widgetOptions = getHtmlWidgetOption();
331        CmsEditorDisplayOptions options = OpenCms.getWorkplaceManager().getEditorDisplayOptions();
332        Properties displayOptions = options.getDisplayOptions(cms);
333        try {
334            if (options.showElement("gallery.enhancedoptions", displayOptions)) {
335                result.put("cmsGalleryEnhancedOptions", true);
336            }
337            if (options.showElement("gallery.usethickbox", displayOptions)) {
338                result.put("cmsGalleryUseThickbox", true);
339            }
340            result.put("fullpage", widgetOptions.isFullPage());
341            List<String> toolbarItems = widgetOptions.getButtonBarShownItems();
342            result.put("toolbar_items", toolbarItems);
343            Locale workplaceLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
344            result.put("language", workplaceLocale.getLanguage());
345            String editorHeight = widgetOptions.getEditorHeight();
346            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(editorHeight)) {
347                editorHeight = editorHeight.replaceAll("px", "");
348                result.put("height", editorHeight);
349            }
350            // set CSS style sheet for current editor widget if configured
351            boolean cssConfigured = false;
352            String cssPath = "";
353            if (widgetOptions.useCss()) {
354                cssPath = widgetOptions.getCssPath();
355                // set the CSS path to null (the created configuration String passed to JS will not include this path then)
356                widgetOptions.setCssPath(null);
357                cssConfigured = true;
358            } else if (OpenCms.getWorkplaceManager().getEditorCssHandlers().size() > 0) {
359                Iterator<I_CmsEditorCssHandler> i = OpenCms.getWorkplaceManager().getEditorCssHandlers().iterator();
360                try {
361                    String editedResourceSitePath = resource == null ? null : cms.getSitePath(resource);
362                    while (i.hasNext()) {
363                        I_CmsEditorCssHandler handler = i.next();
364                        if (handler.matches(cms, editedResourceSitePath)) {
365                            cssPath = handler.getUriStyleSheet(cms, editedResourceSitePath);
366                            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(cssPath)) {
367                                cssConfigured = true;
368                            }
369                            break;
370                        }
371                    }
372                } catch (Exception e) {
373                    // ignore, CSS could not be set
374                }
375            }
376            if (cssConfigured) {
377                result.put("content_css", OpenCms.getLinkManager().substituteLink(cms, cssPath));
378            }
379
380            if (widgetOptions.showStylesFormat()) {
381                try {
382                    CmsFile file = cms.readFile(widgetOptions.getStylesFormatPath());
383                    String characterEncoding = OpenCms.getSystemInfo().getDefaultEncoding();
384                    result.put("style_formats", new String(file.getContents(), characterEncoding));
385                } catch (CmsException cmsException) {
386                    LOG.error("Can not open file:" + widgetOptions.getStylesFormatPath(), cmsException);
387                } catch (UnsupportedEncodingException ex) {
388                    LOG.error(ex);
389                }
390            }
391            if (widgetOptions.isImportCss()) {
392                result.put("importCss", true);
393            }
394            String formatSelectOptions = widgetOptions.getFormatSelectOptions();
395            if (!CmsStringUtil.isEmpty(formatSelectOptions)
396                && !widgetOptions.isButtonHidden(CmsHtmlWidgetOption.OPTION_FORMATSELECT)) {
397                result.put("block_formats", getTinyMceBlockFormats(formatSelectOptions));
398            }
399            CmsWorkplaceEditorConfiguration editorConfig = OpenCms.getWorkplaceManager().getWorkplaceEditorManager().getEditorConfiguration(
400                "tinymce");
401            Boolean pasteText = Boolean.valueOf(editorConfig.getParameters().get("paste_text"));
402            JSONObject directOptions = new JSONObject();
403            directOptions.put("paste_text_sticky_default", pasteText);
404            directOptions.put("paste_text_sticky", pasteText);
405            result.put("tinyMceOptions", directOptions);
406            // if spell checking is enabled, add the spell handler URL
407            if (OpenCmsSpellcheckHandler.isSpellcheckingEnabled()) {
408                result.put(
409                    "spellcheck_url",
410                    OpenCms.getLinkManager().substituteLinkForUnknownTarget(
411                        cms,
412                        OpenCmsSpellcheckHandler.getSpellcheckHandlerPath()));
413
414                result.put(
415                    "spellcheck_language",
416                    "+" + contentLocale.getDisplayLanguage(workplaceLocale) + "=" + contentLocale.getLanguage());
417            }
418        } catch (JSONException e) {
419            LOG.error(e.getLocalizedMessage(), e);
420        }
421        return result;
422    }
423
424    /**
425     * Removes all keys from the given JSON object which do not directly result from the embedded gallery configuration strings.<p>
426     *
427     * @param json the JSON object to modify
428     */
429    private void filterEmbeddedGalleryOptions(JSONObject json) {
430
431        Set<String> validKeys = Sets.newHashSet(
432            Arrays.asList(
433                I_CmsGalleryProviderConstants.CONFIG_GALLERY_TYPES,
434                I_CmsGalleryProviderConstants.CONFIG_GALLERY_PATH,
435                I_CmsGalleryProviderConstants.CONFIG_USE_FORMATS,
436                I_CmsGalleryProviderConstants.CONFIG_IMAGE_FORMAT_NAMES,
437                I_CmsGalleryProviderConstants.CONFIG_IMAGE_FORMATS));
438
439        // delete all keys not listed above
440        Set<String> toDelete = new HashSet<String>(Sets.difference(json.keySet(), validKeys));
441        for (String toDeleteKey : toDelete) {
442            json.remove(toDeleteKey);
443        }
444    }
445
446    /**
447     * Returns the editor widget to use depending on the current users settings, current browser and installed editors.<p>
448     *
449     * @param cms the current CmsObject
450     * @param widgetDialog the dialog where the widget is used on
451     * @return the editor widget to use depending on the current users settings, current browser and installed editors
452     */
453    private I_CmsWidget getEditorWidget(CmsObject cms, I_CmsWidgetDialog widgetDialog) {
454
455        if (m_editorWidget == null) {
456            // get HTML widget to use from editor manager
457            String widgetClassName = OpenCms.getWorkplaceManager().getWorkplaceEditorManager().getWidgetEditor(
458                cms.getRequestContext(),
459                widgetDialog.getUserAgent());
460            boolean foundWidget = true;
461            if (CmsStringUtil.isEmpty(widgetClassName)) {
462                // no installed widget found, use default text area to edit HTML value
463                widgetClassName = CmsTextareaWidget.class.getName();
464                foundWidget = false;
465            }
466            try {
467                if (foundWidget) {
468                    // get widget instance and set the widget configuration
469                    Class<?> widgetClass = Class.forName(widgetClassName);
470                    A_CmsHtmlWidget editorWidget = (A_CmsHtmlWidget)widgetClass.newInstance();
471                    editorWidget.setHtmlWidgetOption(getHtmlWidgetOption());
472                    m_editorWidget = editorWidget;
473                } else {
474                    // set the text area to display 15 rows for editing
475                    Class<?> widgetClass = Class.forName(widgetClassName);
476                    I_CmsWidget editorWidget = (I_CmsWidget)widgetClass.newInstance();
477                    editorWidget.setConfiguration("15");
478                    m_editorWidget = editorWidget;
479                }
480            } catch (Exception e) {
481                // failed to create widget instance
482                LOG.error(
483                    Messages.get().container(Messages.LOG_CREATE_HTMLWIDGET_INSTANCE_FAILED_1, widgetClassName).key());
484            }
485
486        }
487        return m_editorWidget;
488    }
489}