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}