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