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}