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