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, 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.ade.contenteditor; 029 030import org.opencms.acacia.shared.CmsAttributeConfiguration; 031import org.opencms.acacia.shared.CmsEntity; 032import org.opencms.acacia.shared.CmsEntityAttribute; 033import org.opencms.acacia.shared.CmsEntityHtml; 034import org.opencms.acacia.shared.CmsTabInfo; 035import org.opencms.acacia.shared.CmsType; 036import org.opencms.acacia.shared.CmsValidationResult; 037import org.opencms.ade.configuration.CmsADEConfigData; 038import org.opencms.ade.containerpage.CmsContainerpageService; 039import org.opencms.ade.containerpage.CmsElementUtil; 040import org.opencms.ade.containerpage.shared.CmsCntPageData; 041import org.opencms.ade.containerpage.shared.CmsContainer; 042import org.opencms.ade.containerpage.shared.CmsContainerElement; 043import org.opencms.ade.containerpage.shared.CmsFormatterConfig; 044import org.opencms.ade.contenteditor.shared.CmsContentDefinition; 045import org.opencms.ade.contenteditor.shared.CmsEditHandlerData; 046import org.opencms.ade.contenteditor.shared.CmsEditorConstants; 047import org.opencms.ade.contenteditor.shared.CmsSaveResult; 048import org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService; 049import org.opencms.file.CmsFile; 050import org.opencms.file.CmsObject; 051import org.opencms.file.CmsPropertyDefinition; 052import org.opencms.file.CmsResource; 053import org.opencms.file.CmsResourceFilter; 054import org.opencms.file.collectors.A_CmsResourceCollector; 055import org.opencms.file.collectors.I_CmsCollectorPostCreateHandler; 056import org.opencms.file.types.CmsResourceTypeXmlContent; 057import org.opencms.file.types.I_CmsResourceType; 058import org.opencms.flex.CmsFlexController; 059import org.opencms.gwt.CmsGwtService; 060import org.opencms.gwt.CmsIconUtil; 061import org.opencms.gwt.CmsRpcException; 062import org.opencms.gwt.shared.CmsGwtConstants; 063import org.opencms.gwt.shared.CmsModelResourceInfo; 064import org.opencms.i18n.CmsEncoder; 065import org.opencms.i18n.CmsLocaleManager; 066import org.opencms.i18n.CmsMessages; 067import org.opencms.json.JSONObject; 068import org.opencms.jsp.CmsJspTagEdit; 069import org.opencms.main.CmsException; 070import org.opencms.main.CmsLog; 071import org.opencms.main.CmsRuntimeException; 072import org.opencms.main.OpenCms; 073import org.opencms.relations.CmsCategory; 074import org.opencms.relations.CmsCategoryService; 075import org.opencms.search.galleries.CmsGallerySearch; 076import org.opencms.search.galleries.CmsGallerySearchResult; 077import org.opencms.util.CmsFileUtil; 078import org.opencms.util.CmsPair; 079import org.opencms.util.CmsRequestUtil; 080import org.opencms.util.CmsStringUtil; 081import org.opencms.util.CmsUUID; 082import org.opencms.widgets.CmsCalendarWidget; 083import org.opencms.widgets.CmsCategoryWidget; 084import org.opencms.widgets.CmsCheckboxWidget; 085import org.opencms.widgets.CmsComboWidget; 086import org.opencms.widgets.CmsGroupWidget; 087import org.opencms.widgets.CmsInputWidget; 088import org.opencms.widgets.CmsMultiSelectWidget; 089import org.opencms.widgets.CmsRadioSelectWidget; 090import org.opencms.widgets.CmsSelectComboWidget; 091import org.opencms.widgets.CmsSelectWidget; 092import org.opencms.widgets.CmsVfsFileWidget; 093import org.opencms.widgets.I_CmsADEWidget; 094import org.opencms.widgets.I_CmsWidget; 095import org.opencms.workplace.CmsDialog; 096import org.opencms.workplace.CmsWorkplace; 097import org.opencms.workplace.editors.CmsEditor; 098import org.opencms.workplace.editors.CmsEditorCssHandlerDefault; 099import org.opencms.workplace.editors.CmsXmlContentEditor; 100import org.opencms.workplace.editors.directedit.I_CmsEditHandler; 101import org.opencms.xml.CmsXmlContentDefinition; 102import org.opencms.xml.CmsXmlEntityResolver; 103import org.opencms.xml.CmsXmlException; 104import org.opencms.xml.CmsXmlUtils; 105import org.opencms.xml.I_CmsXmlDocument; 106import org.opencms.xml.containerpage.CmsADESessionCache; 107import org.opencms.xml.containerpage.CmsContainerElementBean; 108import org.opencms.xml.containerpage.I_CmsFormatterBean; 109import org.opencms.xml.content.CmsXmlContent; 110import org.opencms.xml.content.CmsXmlContentErrorHandler; 111import org.opencms.xml.content.CmsXmlContentFactory; 112import org.opencms.xml.content.CmsXmlContentProperty; 113import org.opencms.xml.content.CmsXmlContentPropertyHelper; 114import org.opencms.xml.content.I_CmsXmlContentEditorChangeHandler; 115import org.opencms.xml.content.I_CmsXmlContentHandler.DisplayType; 116import org.opencms.xml.types.CmsXmlDynamicCategoryValue; 117import org.opencms.xml.types.I_CmsXmlContentValue; 118import org.opencms.xml.types.I_CmsXmlSchemaType; 119 120import java.io.UnsupportedEncodingException; 121import java.util.ArrayList; 122import java.util.Arrays; 123import java.util.Collection; 124import java.util.Collections; 125import java.util.HashMap; 126import java.util.HashSet; 127import java.util.List; 128import java.util.Locale; 129import java.util.Map; 130import java.util.Map.Entry; 131import java.util.Set; 132import java.util.TreeMap; 133 134import javax.servlet.http.HttpServletRequest; 135 136import org.apache.commons.logging.Log; 137 138import org.dom4j.Element; 139 140import com.google.common.base.Suppliers; 141import com.google.common.collect.Sets; 142 143/** 144 * Service to provide entity persistence within OpenCms. <p> 145 */ 146public class CmsContentService extends CmsGwtService implements I_CmsContentService { 147 148 /** Request context attribute to mark a writeFile() triggered by the user saving in the content editor. */ 149 public static final String ATTR_EDITOR_SAVING = "__EDITOR_SAVING"; 150 151 /** The logger for this class. */ 152 protected static final Log LOG = CmsLog.getLog(CmsContentService.class); 153 154 /** The type name prefix. */ 155 static final String TYPE_NAME_PREFIX = "http://opencms.org/types/"; 156 157 /** The settings widget name for hidden entries. */ 158 private static final String HIDDEN_SETTINGS_WIDGET_NAME = "hidden"; 159 160 /** The RDFA attributes string. */ 161 private static final String RDFA_ATTRIBUTES = CmsGwtConstants.ATTR_DATA_ID 162 + "=\"%1$s\" " 163 + CmsGwtConstants.ATTR_DATA_FIELD 164 + "=\"%2$s\""; 165 166 /** The serial version id. */ 167 private static final long serialVersionUID = 7873052619331296648L; 168 169 /** The setting type name. */ 170 private static final String SETTING_TYPE_NAME = "###SETTING_TYPE###"; 171 172 /** The settings attribute name prefix. */ 173 private static final String SETTINGS_ATTRIBUTE_NAME_PREFIX = "SETTING:::"; 174 175 /** The attribute name used for the client id. */ 176 private static final String SETTINGS_CLIENT_ID_ATTRIBUTE = "/" + SETTINGS_ATTRIBUTE_NAME_PREFIX + "CLIENT_ID:::"; 177 178 /** The settings validation rule type name. */ 179 private static final String SETTINGS_RULE_TYPE_ERROR = "error"; 180 181 /** Mapping client widget names to server side widget classes. */ 182 private static final Map<String, Class<? extends I_CmsADEWidget>> WIDGET_MAPPINGS = new HashMap<>(); 183 184 static { 185 WIDGET_MAPPINGS.put("string", CmsInputWidget.class); 186 WIDGET_MAPPINGS.put("select", CmsSelectWidget.class); 187 WIDGET_MAPPINGS.put("multicheck", org.opencms.widgets.CmsMultiSelectWidget.class); 188 WIDGET_MAPPINGS.put("selectcombo", CmsSelectComboWidget.class); 189 WIDGET_MAPPINGS.put("checkbox", CmsCheckboxWidget.class); 190 WIDGET_MAPPINGS.put("combo", CmsComboWidget.class); 191 WIDGET_MAPPINGS.put("datebox", CmsCalendarWidget.class); 192 WIDGET_MAPPINGS.put("gallery", CmsVfsFileWidget.class); 193 WIDGET_MAPPINGS.put("multiselectbox", CmsMultiSelectWidget.class); 194 WIDGET_MAPPINGS.put("radio", CmsRadioSelectWidget.class); 195 WIDGET_MAPPINGS.put("groupselection", CmsGroupWidget.class); 196 } 197 198 /** The session cache. */ 199 private CmsADESessionCache m_sessionCache; 200 201 /** The current users workplace locale. */ 202 private Locale m_workplaceLocale; 203 204 /** 205 * Creates a new resource to edit, delegating to an edit handler if edit handler data is passed in.<p> 206 * 207 * @param cms The CmsObject of the current request context. 208 * @param newLink A string, specifying where which new content should be created. 209 * @param locale The locale for which the 210 * @param referenceSitePath site path of the currently edited content. 211 * @param modelFileSitePath site path of the model file 212 * @param mode optional creation mode 213 * @param postCreateHandler optional class name of an {@link I_CmsCollectorPostCreateHandler} which is invoked after the content has been created. 214 * 215 * @return The site-path of the newly created resource. 216 * @throws CmsException if something goes wrong 217 */ 218 public static String defaultCreateResourceToEdit( 219 CmsObject cms, 220 String newLink, 221 Locale locale, 222 String referenceSitePath, 223 String modelFileSitePath, 224 String mode, 225 String postCreateHandler) 226 throws CmsException { 227 228 String newFileName; 229 if (newLink.startsWith(CmsJspTagEdit.NEW_LINK_IDENTIFIER)) { 230 231 newFileName = CmsJspTagEdit.createResource( 232 cms, 233 newLink, 234 locale, 235 referenceSitePath, 236 modelFileSitePath, 237 mode, 238 postCreateHandler); 239 } else { 240 newFileName = A_CmsResourceCollector.createResourceForCollector( 241 cms, 242 newLink, 243 locale, 244 referenceSitePath, 245 modelFileSitePath, 246 mode, 247 postCreateHandler); 248 } 249 return newFileName; 250 } 251 252 /** 253 * Returns the entity attribute name representing the given content value.<p> 254 * 255 * @param contentValue the content value 256 * 257 * @return the attribute name 258 */ 259 public static String getAttributeName(I_CmsXmlContentValue contentValue) { 260 261 return getTypeUri(contentValue.getContentDefinition()) + "/" + contentValue.getName(); 262 } 263 264 /** 265 * Returns the entity attribute name to use for this element.<p> 266 * 267 * @param elementName the element name 268 * @param parentType the parent type 269 * 270 * @return the attribute name 271 */ 272 public static String getAttributeName(String elementName, String parentType) { 273 274 return parentType + "/" + elementName; 275 } 276 277 /** 278 * Returns the entity id to the given content value.<p> 279 * 280 * @param contentValue the content value 281 * 282 * @return the entity id 283 */ 284 public static String getEntityId(I_CmsXmlContentValue contentValue) { 285 286 String result = CmsContentDefinition.uuidToEntityId( 287 contentValue.getDocument().getFile().getStructureId(), 288 contentValue.getLocale().toString()); 289 String valuePath = contentValue.getPath(); 290 if (valuePath.contains("/")) { 291 result += "/" + valuePath.substring(0, valuePath.lastIndexOf("/")); 292 } 293 if (contentValue.isChoiceOption()) { 294 result += "/" 295 + CmsType.CHOICE_ATTRIBUTE_NAME 296 + "_" 297 + contentValue.getName() 298 + "[" 299 + contentValue.getXmlIndex() 300 + "]"; 301 } 302 return result; 303 } 304 305 /** 306 * Returns the RDF annotations required for in line editing.<p> 307 * 308 * @param value the XML content value 309 * 310 * @return the RDFA 311 */ 312 public static String getRdfaAttributes(I_CmsXmlContentValue value) { 313 314 String path = ""; 315 String elementPath = value.getPath(); 316 if (elementPath.contains("/")) { 317 path += "/" + removePathIndexes(elementPath.substring(0, elementPath.lastIndexOf("/")) + ":"); 318 } 319 path += CmsContentService.getAttributeName(value); 320 return String.format(RDFA_ATTRIBUTES, CmsContentService.getEntityId(value), path); 321 } 322 323 /** 324 * Returns the RDF annotations required for in line editing.<p> 325 * 326 * @param parentValue the parent XML content value 327 * @param childNames the child attribute names separated by '|' 328 * 329 * @return the RDFA 330 */ 331 public static String getRdfaAttributes(I_CmsXmlContentValue parentValue, String childNames) { 332 333 String id = CmsContentDefinition.uuidToEntityId( 334 parentValue.getDocument().getFile().getStructureId(), 335 parentValue.getLocale().toString()) + "/" + parentValue.getPath(); 336 String path = ""; 337 String[] children = childNames.split("\\|"); 338 for (int i = 0; i < children.length; i++) { 339 I_CmsXmlSchemaType schemaType = parentValue.getContentDefinition().getSchemaType( 340 parentValue.getName() + "/" + children[i]); 341 if (schemaType != null) { 342 if (i > 0) { 343 path += " "; 344 } 345 String typePath = parentValue.getPath(); 346 path += "/" + removePathIndexes(typePath) + ":"; 347 path += getTypeUri(schemaType.getContentDefinition()) + "/" + children[i]; 348 } 349 } 350 return String.format(RDFA_ATTRIBUTES, id, path); 351 } 352 353 /** 354 * Returns the RDF annotations required for in line editing.<p> 355 * 356 * @param document the parent XML document 357 * @param contentLocale the content locale 358 * @param elementPath the element xpath to get the RDF annotation for 359 * 360 * @return the RDFA 361 */ 362 public static String getRdfaAttributes(I_CmsXmlDocument document, Locale contentLocale, String elementPath) { 363 364 I_CmsXmlSchemaType schemaType = document.getContentDefinition().getSchemaType(elementPath); 365 if (schemaType != null) { 366 String path = ""; 367 if (elementPath.contains("/")) { 368 path += "/" + removePathIndexes(elementPath.substring(0, elementPath.lastIndexOf("/")) + ":"); 369 } 370 path += getTypeUri(schemaType.getContentDefinition()) + "/" + elementPath; 371 return String.format( 372 RDFA_ATTRIBUTES, 373 CmsContentDefinition.uuidToEntityId(document.getFile().getStructureId(), contentLocale.toString()), 374 path); 375 } else { 376 return ""; 377 } 378 } 379 380 /** 381 * Returns the type URI.<p> 382 * 383 * @param xmlContentDefinition the type content definition 384 * 385 * @return the type URI 386 */ 387 public static String getTypeUri(CmsXmlContentDefinition xmlContentDefinition) { 388 389 return xmlContentDefinition.getSchemaLocation() + "/" + xmlContentDefinition.getTypeName(); 390 } 391 392 /** 393 * Fetches the initial content definition.<p> 394 * 395 * @param request the current request 396 * 397 * @return the initial content definition 398 * 399 * @throws CmsRpcException if something goes wrong 400 */ 401 public static CmsContentDefinition prefetch(HttpServletRequest request) throws CmsRpcException { 402 403 CmsContentService srv = new CmsContentService(); 404 srv.setCms(CmsFlexController.getCmsObject(request)); 405 srv.setRequest(request); 406 CmsContentDefinition result = null; 407 try { 408 result = srv.prefetch(); 409 } finally { 410 srv.clearThreadStorage(); 411 } 412 return result; 413 } 414 415 /** 416 * Removes the XPath indexes from the given path.<p> 417 * 418 * @param path the path 419 * 420 * @return the changed path 421 */ 422 private static String removePathIndexes(String path) { 423 424 return path.replaceAll("\\[.*\\]", ""); 425 } 426 427 /** 428 * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#callEditorChangeHandlers(java.lang.String, org.opencms.acacia.shared.CmsEntity, java.util.Collection, java.util.Collection) 429 */ 430 public CmsContentDefinition callEditorChangeHandlers( 431 String entityId, 432 CmsEntity editedLocaleEntity, 433 Collection<String> skipPaths, 434 Collection<String> changedScopes) 435 throws CmsRpcException { 436 437 CmsContentDefinition result = null; 438 CmsObject cms = getCmsObject(); 439 CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration( 440 cms, 441 cms.getRequestContext().getRootUri()); 442 CmsUUID structureId = CmsContentDefinition.entityIdToUuid(editedLocaleEntity.getId()); 443 if (structureId != null) { 444 445 CmsResource resource = null; 446 Locale locale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId)); 447 try { 448 resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION); 449 ensureLock(resource); 450 CmsFile file = cms.readFile(resource); 451 CmsXmlContent content = getContentDocument(file, true).clone(); 452 checkAutoCorrection(cms, content); 453 synchronizeLocaleIndependentForEntity(file, content, skipPaths, editedLocaleEntity); 454 for (I_CmsXmlContentEditorChangeHandler handler : content.getContentDefinition().getContentHandler().getEditorChangeHandlers( 455 false)) { 456 Set<String> handlerScopes = evaluateScope(handler.getScope(), content.getContentDefinition()); 457 if (!Collections.disjoint(changedScopes, handlerScopes)) { 458 handler.handleChange(cms, content, locale, changedScopes); 459 } 460 } 461 result = readContentDefinition( 462 file, 463 content, 464 entityId, 465 null, 466 locale, 467 false, 468 null, 469 editedLocaleEntity, 470 Collections.emptyMap(), 471 config); 472 } catch (Exception e) { 473 error(e); 474 } 475 } 476 return result; 477 } 478 479 /** 480 * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#copyLocale(java.util.Collection, org.opencms.acacia.shared.CmsEntity) 481 */ 482 public void copyLocale(Collection<String> locales, CmsEntity sourceLocale) throws CmsRpcException { 483 484 try { 485 CmsUUID structureId = CmsContentDefinition.entityIdToUuid(sourceLocale.getId()); 486 487 CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION); 488 CmsFile file = getCmsObject().readFile(resource); 489 CmsXmlContent content = getSessionCache().getCacheXmlContent(structureId); 490 synchronizeLocaleIndependentForEntity(file, content, Collections.<String> emptyList(), sourceLocale); 491 Locale sourceContentLocale = CmsLocaleManager.getLocale( 492 CmsContentDefinition.getLocaleFromId(sourceLocale.getId())); 493 for (String loc : locales) { 494 Locale targetLocale = CmsLocaleManager.getLocale(loc); 495 if (content.hasLocale(targetLocale)) { 496 content.removeLocale(targetLocale); 497 } 498 content.copyLocale(sourceContentLocale, targetLocale); 499 } 500 } catch (Throwable t) { 501 error(t); 502 } 503 } 504 505 /** 506 * @see org.opencms.gwt.CmsGwtService#getCmsObject() 507 */ 508 @Override 509 public CmsObject getCmsObject() { 510 511 CmsObject result = super.getCmsObject(); 512 // disable link invalidation in the editor 513 result.getRequestContext().setRequestTime(CmsResource.DATE_RELEASED_EXPIRED_IGNORE); 514 return result; 515 } 516 517 /** 518 * @see org.opencms.acacia.shared.rpc.I_CmsContentService#loadContentDefinition(java.lang.String) 519 */ 520 public CmsContentDefinition loadContentDefinition(String entityId) throws CmsRpcException { 521 522 throw new CmsRpcException(new UnsupportedOperationException()); 523 } 524 525 /** 526 * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#loadDefinition(java.lang.String, java.lang.String, org.opencms.acacia.shared.CmsEntity, java.util.Collection, java.util.Map) 527 */ 528 public CmsContentDefinition loadDefinition( 529 530 String entityId, 531 String clientId, 532 CmsEntity editedLocaleEntity, 533 Collection<String> skipPaths, 534 Map<String, String> settingPresets) 535 throws CmsRpcException { 536 537 CmsContentDefinition definition = null; 538 try { 539 CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entityId); 540 CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION); 541 CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration( 542 getCmsObject(), 543 getCmsObject().getRequestContext().getRootUri()); 544 545 Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId)); 546 CmsFile file = getCmsObject().readFile(resource); 547 CmsXmlContent content = getContentDocument(file, true); 548 if (editedLocaleEntity != null) { 549 synchronizeLocaleIndependentForEntity(file, content, skipPaths, editedLocaleEntity); 550 } 551 definition = readContentDefinition( 552 file, 553 content, 554 CmsContentDefinition.uuidToEntityId(structureId, contentLocale.toString()), 555 clientId, 556 contentLocale, 557 false, 558 null, 559 editedLocaleEntity, 560 settingPresets, 561 config); 562 } catch (Exception e) { 563 error(e); 564 } 565 return definition; 566 } 567 568 /** 569 * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#loadInitialDefinition(java.lang.String, java.lang.String, java.lang.String, org.opencms.util.CmsUUID, java.lang.String, java.lang.String, java.lang.String, java.lang.String, org.opencms.ade.contenteditor.shared.CmsEditHandlerData, java.util.Map, java.lang.String) 570 */ 571 public CmsContentDefinition loadInitialDefinition( 572 573 String entityId, 574 String clientId, 575 String newLink, 576 CmsUUID modelFileId, 577 String editContext, 578 String mainLocale, 579 String mode, 580 String postCreateHandler, 581 CmsEditHandlerData editHandlerDataForNew, 582 Map<String, String> settingPresets, 583 String editorStylesheet) 584 throws CmsRpcException { 585 586 CmsObject cms = getCmsObject(); 587 CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration( 588 cms, 589 cms.getRequestContext().getRootUri()); 590 CmsContentDefinition result = null; 591 getCmsObject().getRequestContext().setAttribute(CmsXmlContentEditor.ATTRIBUTE_EDITCONTEXT, editContext); 592 if (editorStylesheet != null) { 593 getCmsObject().getRequestContext().setAttribute( 594 CmsEditorCssHandlerDefault.ATTRIBUTE_EDITOR_STYLESHEET, 595 editorStylesheet); 596 } 597 try { 598 CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entityId); 599 CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION); 600 Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId)); 601 getSessionCache().clearDynamicValues(); 602 getSessionCache().uncacheXmlContent(structureId); 603 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(newLink)) { 604 result = readContentDefinitionForNew( 605 newLink, 606 resource, 607 modelFileId, 608 contentLocale, 609 mode, 610 postCreateHandler, 611 editHandlerDataForNew, 612 settingPresets); 613 } else { 614 CmsFile file = getCmsObject().readFile(resource); 615 CmsXmlContent content = getContentDocument(file, false); 616 result = readContentDefinition( 617 file, 618 content, 619 CmsContentDefinition.uuidToEntityId(structureId, contentLocale.toString()), 620 clientId, 621 contentLocale, 622 false, 623 mainLocale != null ? CmsLocaleManager.getLocale(mainLocale) : null, 624 null, 625 settingPresets, 626 config); 627 } 628 } catch (Throwable t) { 629 error(t); 630 } 631 return result; 632 } 633 634 /** 635 * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#loadNewDefinition(java.lang.String, java.lang.String, org.opencms.acacia.shared.CmsEntity, java.util.Collection, java.util.Map) 636 */ 637 public CmsContentDefinition loadNewDefinition( 638 639 String entityId, 640 String clientId, 641 CmsEntity editedLocaleEntity, 642 Collection<String> skipPaths, 643 Map<String, String> settingPresets) 644 throws CmsRpcException { 645 646 CmsObject cms = getCmsObject(); 647 CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration( 648 cms, 649 cms.getRequestContext().getRootUri()); 650 CmsContentDefinition definition = null; 651 try { 652 CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entityId); 653 CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION); 654 Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId)); 655 CmsFile file = getCmsObject().readFile(resource); 656 CmsXmlContent content = getContentDocument(file, true); 657 synchronizeLocaleIndependentForEntity(file, content, skipPaths, editedLocaleEntity); 658 definition = readContentDefinition( 659 file, 660 content, 661 CmsContentDefinition.uuidToEntityId(structureId, contentLocale.toString()), 662 clientId, 663 contentLocale, 664 true, 665 null, 666 editedLocaleEntity, 667 settingPresets, 668 config); 669 } catch (Exception e) { 670 error(e); 671 } 672 return definition; 673 } 674 675 /** 676 * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#prefetch() 677 */ 678 public CmsContentDefinition prefetch() throws CmsRpcException { 679 680 String paramResource = getRequest().getParameter(CmsDialog.PARAM_RESOURCE); 681 String paramDirectEdit = getRequest().getParameter(CmsEditor.PARAM_DIRECTEDIT); 682 boolean isDirectEdit = false; 683 if (paramDirectEdit != null) { 684 isDirectEdit = Boolean.parseBoolean(paramDirectEdit); 685 } 686 String paramNewLink = getRequest().getParameter(CmsXmlContentEditor.PARAM_NEWLINK); 687 boolean createNew = false; 688 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(paramNewLink)) { 689 createNew = true; 690 paramNewLink = decodeNewLink(paramNewLink); 691 } 692 String paramLocale = getRequest().getParameter(CmsEditor.PARAM_ELEMENTLANGUAGE); 693 Locale locale = null; 694 CmsObject cms = getCmsObject(); 695 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(paramResource)) { 696 697 // not necessary in most cases, but some old dialogs pass the path in encoded form 698 paramResource = CmsEncoder.decode(paramResource); 699 700 try { 701 CmsResource resource = cms.readResource(paramResource, CmsResourceFilter.IGNORE_EXPIRATION); 702 703 if (OpenCms.getADEManager().isEditorRestricted(cms, resource)) { 704 // Context menus / buttons for editing the file should be disabled if above condition is true. 705 // You only get here if you directly open the editor URL, so this does not need 706 // a particularly nice error message 707 throw new CmsRuntimeException( 708 org.opencms.ade.contenteditor.Messages.get().container( 709 org.opencms.ade.contenteditor.Messages.ERR_EDITOR_RESTRICTED_0)); 710 } 711 if (CmsResourceTypeXmlContent.isXmlContent(resource) || createNew) { 712 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(paramLocale)) { 713 locale = CmsLocaleManager.getLocale(paramLocale); 714 } 715 CmsContentDefinition result; 716 getSessionCache().clearDynamicValues(); 717 if (createNew) { 718 if (locale == null) { 719 locale = OpenCms.getLocaleManager().getDefaultLocale(cms, paramResource); 720 } 721 CmsUUID modelFileId = null; 722 String paramModelFile = getRequest().getParameter(CmsWorkplace.PARAM_MODELFILE); 723 724 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(paramModelFile)) { 725 modelFileId = cms.readResource(paramModelFile).getStructureId(); 726 } 727 728 String mode = getRequest().getParameter(CmsEditorConstants.PARAM_MODE); 729 String postCreateHandler = getRequest().getParameter( 730 CmsEditorConstants.PARAM_POST_CREATE_HANDLER); 731 result = readContentDefinitionForNew( 732 paramNewLink, 733 resource, 734 modelFileId, 735 locale, 736 mode, 737 postCreateHandler, 738 null, 739 Collections.emptyMap()); 740 } else { 741 742 CmsFile file = cms.readFile(resource); 743 CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file); 744 getSessionCache().setCacheXmlContent(resource.getStructureId(), content); 745 if (locale == null) { 746 locale = OpenCms.getLocaleManager().getBestAvailableLocaleForXmlContent( 747 getCmsObject(), 748 resource, 749 content); 750 } 751 result = readContentDefinition( 752 file, 753 content, 754 null, 755 null, 756 locale, 757 false, 758 null, 759 null, 760 Collections.emptyMap(), 761 null); 762 } 763 result.setDirectEdit(isDirectEdit); 764 return result; 765 } 766 } catch (Throwable e) { 767 error(e); 768 } 769 } 770 return null; 771 } 772 773 /** 774 * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#saveAndDeleteEntities(org.opencms.acacia.shared.CmsEntity, java.lang.String, java.util.List, java.util.Collection, java.lang.String, boolean) 775 */ 776 public CmsSaveResult saveAndDeleteEntities( 777 CmsEntity lastEditedEntity, 778 String clientId, 779 List<String> deletedEntities, 780 Collection<String> skipPaths, 781 String lastEditedLocale, 782 boolean clearOnSuccess) 783 throws CmsRpcException { 784 785 CmsUUID structureId = null; 786 if (lastEditedEntity != null) { 787 structureId = CmsContentDefinition.entityIdToUuid(lastEditedEntity.getId()); 788 } 789 if ((structureId == null) && !deletedEntities.isEmpty()) { 790 structureId = CmsContentDefinition.entityIdToUuid(deletedEntities.get(0)); 791 } 792 CmsADEConfigData configData = OpenCms.getADEManager().lookupConfiguration( 793 getCmsObject(), 794 getCmsObject().getRequestContext().getRootUri()); 795 if (structureId != null) { 796 CmsObject cms = getCmsObject(); 797 CmsResource resource = null; 798 try { 799 resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION); 800 ensureLock(resource); 801 CmsFile file = cms.readFile(resource); 802 CmsXmlContent content = getContentDocument(file, true); 803 checkAutoCorrection(cms, content); 804 if (lastEditedEntity != null) { 805 synchronizeLocaleIndependentForEntity(file, content, skipPaths, lastEditedEntity); 806 } 807 for (String deleteId : deletedEntities) { 808 Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(deleteId)); 809 if (content.hasLocale(contentLocale)) { 810 content.removeLocale(contentLocale); 811 } 812 } 813 CmsValidationResult validationResult = validateContent(cms, structureId, content); 814 if (validationResult.hasErrors()) { 815 return new CmsSaveResult(false, validationResult); 816 } 817 boolean hasChangedSettings = false; 818 if ((clientId != null) && (lastEditedEntity != null)) { 819 CmsContainerElementBean containerElement = getSessionCache().getCacheContainerElement(clientId); 820 I_CmsFormatterBean formatter = getFormatterForElement(configData, containerElement); 821 if ((formatter != null) 822 && formatter.isAllowsSettingsInEditor() 823 && !formatter.getSettings(configData).isEmpty()) { 824 Locale locale = CmsLocaleManager.getLocale(lastEditedLocale); 825 Map<String, CmsXmlContentProperty> settingsConfig = OpenCms.getADEManager().getFormatterSettings( 826 cms, 827 configData, 828 formatter, 829 containerElement.getResource(), 830 locale, 831 getRequest()); 832 validateSettings(lastEditedEntity, validationResult, settingsConfig); 833 if (validationResult.hasErrors()) { 834 return new CmsSaveResult(false, validationResult); 835 } 836 837 List<I_CmsFormatterBean> nestedFormatters = OpenCms.getADEManager().getNestedFormatters( 838 cms, 839 configData, 840 containerElement.getResource(), 841 locale, 842 getRequest()); 843 hasChangedSettings = saveSettings( 844 lastEditedEntity, 845 containerElement, 846 settingsConfig, 847 nestedFormatters); 848 849 } 850 } 851 852 writeCategories(file, content, lastEditedEntity); 853 854 writeContent(cms, file, content, getFileEncoding(cms, file)); 855 856 // update offline indices 857 OpenCms.getSearchManager().updateOfflineIndexes(); 858 if (clearOnSuccess) { 859 tryUnlock(resource); 860 getSessionCache().uncacheXmlContent(structureId); 861 } 862 return new CmsSaveResult(hasChangedSettings, null); 863 } catch (Exception e) { 864 if (resource != null) { 865 tryUnlock(resource); 866 getSessionCache().uncacheXmlContent(structureId); 867 } 868 error(e); 869 } 870 } 871 return null; 872 } 873 874 /** 875 * @see org.opencms.acacia.shared.rpc.I_CmsContentService#saveEntities(java.util.List) 876 */ 877 public CmsValidationResult saveEntities(List<CmsEntity> entities) { 878 879 throw new UnsupportedOperationException(); 880 } 881 882 /** 883 * @see org.opencms.acacia.shared.rpc.I_CmsContentService#saveEntity(org.opencms.acacia.shared.CmsEntity) 884 */ 885 public CmsValidationResult saveEntity(CmsEntity entity) { 886 887 throw new UnsupportedOperationException(); 888 } 889 890 /** 891 * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#saveValue(java.lang.String, java.lang.String, java.lang.String, java.lang.String) 892 */ 893 public String saveValue(String contentId, String contentPath, String localeString, String newValue) 894 throws CmsRpcException { 895 896 OpenCms.getLocaleManager(); 897 Locale locale = CmsLocaleManager.getLocale(localeString); 898 899 try { 900 CmsObject cms = getCmsObject(); 901 CmsResource element = cms.readResource(new CmsUUID(contentId), CmsResourceFilter.IGNORE_EXPIRATION); 902 ensureLock(element); 903 CmsFile elementFile = cms.readFile(element); 904 CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, elementFile); 905 I_CmsXmlContentValue value = content.getValue(contentPath, locale); 906 value.setStringValue(cms, newValue); 907 for (I_CmsXmlContentEditorChangeHandler handler : content.getContentDefinition().getContentHandler().getEditorChangeHandlers( 908 false)) { 909 Set<String> handlerScopes = evaluateScope(handler.getScope(), content.getContentDefinition()); 910 if (handlerScopes.contains(contentPath)) { 911 handler.handleChange(cms, content, locale, Collections.singletonList(contentPath)); 912 } 913 } 914 content.synchronizeLocaleIndependentValues(cms, Collections.<String> emptyList(), locale); 915 byte[] newData = content.marshal(); 916 elementFile.setContents(newData); 917 cms.writeFile(elementFile); 918 tryUnlock(elementFile); 919 return ""; 920 } catch (Exception e) { 921 error(e); 922 return null; 923 } 924 925 } 926 927 /** 928 * @see org.opencms.acacia.shared.rpc.I_CmsContentService#updateEntityHtml(org.opencms.acacia.shared.CmsEntity, java.lang.String, java.lang.String) 929 */ 930 public CmsEntityHtml updateEntityHtml(CmsEntity entity, String contextUri, String htmlContextInfo) 931 throws Exception { 932 933 CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entity.getId()); 934 if (structureId != null) { 935 CmsObject cms = getCmsObject(); 936 try { 937 CmsResource resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION); 938 CmsFile file = cms.readFile(resource); 939 CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file); 940 String entityId = entity.getId(); 941 Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId)); 942 if (content.hasLocale(contentLocale)) { 943 content.removeLocale(contentLocale); 944 } 945 content.addLocale(cms, contentLocale); 946 addEntityAttributes(cms, content, "", entity, contentLocale); 947 CmsValidationResult validationResult = validateContent(cms, structureId, content); 948 String htmlContent = null; 949 if (!validationResult.hasErrors()) { 950 file.setContents(content.marshal()); 951 952 JSONObject contextInfo = new JSONObject(htmlContextInfo); 953 String containerName = contextInfo.getString(CmsCntPageData.JSONKEY_NAME); 954 String containerType = contextInfo.getString(CmsCntPageData.JSONKEY_TYPE); 955 int containerWidth = contextInfo.getInt(CmsCntPageData.JSONKEY_WIDTH); 956 int maxElements = contextInfo.getInt(CmsCntPageData.JSONKEY_MAXELEMENTS); 957 boolean detailView = contextInfo.getBoolean(CmsCntPageData.JSONKEY_DETAILVIEW); 958 boolean isDetailViewContainer = contextInfo.getBoolean( 959 CmsCntPageData.JSONKEY_ISDETAILVIEWCONTAINER); 960 JSONObject presets = contextInfo.getJSONObject(CmsCntPageData.JSONKEY_PRESETS); 961 HashMap<String, String> presetsMap = new HashMap<String, String>(); 962 for (String key : presets.keySet()) { 963 String val = presets.getString(key); 964 presetsMap.put(key, val); 965 } 966 CmsContainer container = new CmsContainer( 967 containerName, 968 containerType, 969 null, 970 containerWidth, 971 maxElements, 972 isDetailViewContainer, 973 detailView, 974 true, 975 Collections.<CmsContainerElement> emptyList(), 976 null, 977 null, 978 presetsMap); 979 CmsUUID detailContentId = null; 980 if (contextInfo.has(CmsCntPageData.JSONKEY_DETAIL_ELEMENT_ID)) { 981 detailContentId = new CmsUUID(contextInfo.getString(CmsCntPageData.JSONKEY_DETAIL_ELEMENT_ID)); 982 } 983 CmsElementUtil elementUtil = new CmsElementUtil( 984 cms, 985 contextUri, 986 detailContentId, 987 getThreadLocalRequest(), 988 getThreadLocalResponse(), 989 contentLocale); 990 htmlContent = elementUtil.getContentByContainer( 991 file, 992 contextInfo.getString(CmsCntPageData.JSONKEY_ELEMENT_ID), 993 container); 994 } 995 return new CmsEntityHtml(htmlContent, validationResult); 996 997 } catch (Exception e) { 998 error(e); 999 } 1000 } 1001 return null; 1002 } 1003 1004 /** 1005 * @see org.opencms.acacia.shared.rpc.I_CmsContentService#validateEntity(org.opencms.acacia.shared.CmsEntity) 1006 */ 1007 public CmsValidationResult validateEntity(CmsEntity changedEntity) throws CmsRpcException { 1008 1009 CmsUUID structureId = null; 1010 if (changedEntity == null) { 1011 return new CmsValidationResult(null, null); 1012 } 1013 structureId = CmsContentDefinition.entityIdToUuid(changedEntity.getId()); 1014 1015 if (structureId != null) { 1016 CmsObject cms = getCmsObject(); 1017 CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration( 1018 cms, 1019 cms.getRequestContext().getRootUri()); 1020 Set<String> setFieldNames = Sets.newHashSet(); 1021 try { 1022 CmsResource resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION); 1023 CmsFile file = cms.readFile(resource); 1024 CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file); 1025 String entityId = changedEntity.getId(); 1026 Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId)); 1027 if (content.hasLocale(contentLocale)) { 1028 content.removeLocale(contentLocale); 1029 } 1030 content.addLocale(cms, contentLocale); 1031 setFieldNames.addAll(addEntityAttributes(cms, content, "", changedEntity, contentLocale)); 1032 1033 CmsValidationResult result = validateContent(cms, structureId, content, setFieldNames); 1034 CmsEntityAttribute clientIdAttr = changedEntity.getAttribute(SETTINGS_CLIENT_ID_ATTRIBUTE); 1035 if (clientIdAttr != null) { 1036 String clientId = clientIdAttr.getSimpleValue(); 1037 CmsContainerElementBean containerElement = getSessionCache().getCacheContainerElement(clientId); 1038 I_CmsFormatterBean formatter = getFormatterForElement(config, containerElement); 1039 if ((formatter != null) 1040 && formatter.isAllowsSettingsInEditor() 1041 && !formatter.getSettings(config).isEmpty()) { 1042 Map<String, CmsXmlContentProperty> settingsConfig = OpenCms.getADEManager().getFormatterSettings( 1043 cms, 1044 config, 1045 formatter, 1046 containerElement.getResource(), 1047 contentLocale, 1048 getRequest()); 1049 validateSettings(changedEntity, result, settingsConfig); 1050 } 1051 } 1052 return result; 1053 } catch (Throwable e) { 1054 error(e); 1055 } 1056 } 1057 return new CmsValidationResult(null, null); 1058 } 1059 1060 /** 1061 * Decodes the newlink request parameter if possible.<p> 1062 * 1063 * @param newLink the parameter to decode 1064 * 1065 * @return the decoded value 1066 */ 1067 protected String decodeNewLink(String newLink) { 1068 1069 String result = newLink; 1070 if (result == null) { 1071 return null; 1072 } 1073 try { 1074 result = CmsEncoder.decode(result); 1075 try { 1076 result = CmsEncoder.decode(result); 1077 } catch (Throwable e) { 1078 LOG.info(e.getLocalizedMessage(), e); 1079 } 1080 } catch (Throwable e) { 1081 LOG.info(e.getLocalizedMessage(), e); 1082 } 1083 1084 return result; 1085 } 1086 1087 /** 1088 * Returns the element name to the given element.<p> 1089 * 1090 * @param attributeName the attribute name 1091 * 1092 * @return the element name 1093 */ 1094 protected String getElementName(String attributeName) { 1095 1096 if (attributeName.contains("/")) { 1097 return attributeName.substring(attributeName.lastIndexOf("/") + 1); 1098 } 1099 return attributeName; 1100 } 1101 1102 /** 1103 * Helper method to determine the encoding of the given file in the VFS, 1104 * which must be set using the "content-encoding" property.<p> 1105 * 1106 * @param cms the CmsObject 1107 * @param file the file which is to be checked 1108 * @return the encoding for the file 1109 */ 1110 protected String getFileEncoding(CmsObject cms, CmsResource file) { 1111 1112 String result; 1113 try { 1114 result = cms.readPropertyObject(file, CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, true).getValue( 1115 OpenCms.getSystemInfo().getDefaultEncoding()); 1116 } catch (CmsException e) { 1117 result = OpenCms.getSystemInfo().getDefaultEncoding(); 1118 } 1119 return CmsEncoder.lookupEncoding(result, OpenCms.getSystemInfo().getDefaultEncoding()); 1120 } 1121 1122 /** 1123 * Parses the element into an entity.<p> 1124 * 1125 * @param content the entity content 1126 * @param element the current element 1127 * @param locale the content locale 1128 * @param entityId the entity id 1129 * @param parentPath the parent path 1130 * @param typeName the entity type name 1131 * @param visitor the content type visitor 1132 * @param includeInvisible include invisible attributes 1133 * @param editedLocalEntity the edited locale entity 1134 * 1135 * @return the entity 1136 */ 1137 protected CmsEntity readEntity( 1138 CmsXmlContent content, 1139 Element element, 1140 Locale locale, 1141 String entityId, 1142 String parentPath, 1143 String typeName, 1144 CmsContentTypeVisitor visitor, 1145 boolean includeInvisible, 1146 CmsEntity editedLocalEntity) { 1147 1148 String newEntityId = entityId + (CmsStringUtil.isNotEmptyOrWhitespaceOnly(parentPath) ? "/" + parentPath : ""); 1149 CmsEntity newEntity = new CmsEntity(newEntityId, typeName); 1150 CmsEntity result = newEntity; 1151 1152 List<Element> elements = element.elements(); 1153 CmsType type = visitor.getTypes().get(typeName); 1154 boolean isChoice = type.isChoice(); 1155 String choiceTypeName = null; 1156 // just needed for choice attributes 1157 Map<String, Integer> attributeCounter = null; 1158 if (isChoice) { 1159 choiceTypeName = type.getAttributeTypeName(CmsType.CHOICE_ATTRIBUTE_NAME); 1160 type = visitor.getTypes().get(type.getAttributeTypeName(CmsType.CHOICE_ATTRIBUTE_NAME)); 1161 attributeCounter = new HashMap<String, Integer>(); 1162 } 1163 int counter = 0; 1164 CmsObject cms = getCmsObject(); 1165 String previousName = null; 1166 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(parentPath)) { 1167 parentPath += "/"; 1168 } 1169 for (Element child : elements) { 1170 String attributeName = getAttributeName(child.getName(), typeName); 1171 String subTypeName = type.getAttributeTypeName(attributeName); 1172 if (visitor.getTypes().get(subTypeName) == null) { 1173 // in case there is no type configured for this element, the schema may have changed, skip the element 1174 continue; 1175 } 1176 if (!includeInvisible && !visitor.getAttributeConfigurations().get(attributeName).isVisible()) { 1177 // skip attributes marked as invisible, there content should not be transfered to the client 1178 continue; 1179 } 1180 if (isChoice && (attributeCounter != null)) { 1181 if (!attributeName.equals(previousName)) { 1182 if (attributeCounter.get(attributeName) != null) { 1183 counter = attributeCounter.get(attributeName).intValue(); 1184 } else { 1185 counter = 0; 1186 } 1187 previousName = attributeName; 1188 } 1189 attributeCounter.put(attributeName, Integer.valueOf(counter + 1)); 1190 } else if (!attributeName.equals(previousName)) { 1191 1192 // reset the attribute counter for every attribute name 1193 counter = 0; 1194 1195 previousName = attributeName; 1196 } 1197 if (isChoice) { 1198 result = new CmsEntity( 1199 newEntityId + "/" + CmsType.CHOICE_ATTRIBUTE_NAME + "_" + child.getName() + "[" + counter + "]", 1200 choiceTypeName); 1201 newEntity.addAttributeValue(CmsType.CHOICE_ATTRIBUTE_NAME, result); 1202 } 1203 String path = parentPath + child.getName(); 1204 if (visitor.isDynamicallyLoaded(attributeName)) { 1205 I_CmsXmlContentValue value = content.getValue(path, locale, counter); 1206 String attributeValue = getDynamicAttributeValue( 1207 content.getFile(), 1208 value, 1209 attributeName, 1210 editedLocalEntity); 1211 result.addAttributeValue(attributeName, attributeValue); 1212 } else if (visitor.getTypes().get(subTypeName).isSimpleType()) { 1213 I_CmsXmlContentValue value = content.getValue(path, locale, counter); 1214 result.addAttributeValue(attributeName, value.getStringValue(cms)); 1215 } else { 1216 CmsEntity subEntity = readEntity( 1217 content, 1218 child, 1219 locale, 1220 entityId, 1221 path + "[" + (counter + 1) + "]", 1222 subTypeName, 1223 visitor, 1224 includeInvisible, 1225 editedLocalEntity); 1226 result.addAttributeValue(attributeName, subEntity); 1227 1228 } 1229 counter++; 1230 } 1231 return newEntity; 1232 } 1233 1234 /** 1235 * Reads the types from the given content definition and adds the to the map of already registered 1236 * types if necessary.<p> 1237 * 1238 * @param xmlContentDefinition the XML content definition 1239 * @param locale the messages locale 1240 * 1241 * @return the types of the given content definition 1242 */ 1243 protected Map<String, CmsType> readTypes(CmsXmlContentDefinition xmlContentDefinition, Locale locale) { 1244 1245 CmsContentTypeVisitor visitor = new CmsContentTypeVisitor(getCmsObject(), null, locale); 1246 visitor.visitTypes(xmlContentDefinition, locale); 1247 return visitor.getTypes(); 1248 } 1249 1250 /** 1251 * Synchronizes the locale independent fields.<p> 1252 * 1253 * @param file the content file 1254 * @param content the XML content 1255 * @param skipPaths the paths to skip during locale synchronization 1256 * @param entities the edited entities 1257 * @param lastEdited the last edited locale 1258 * 1259 * @throws CmsXmlException if something goes wrong 1260 */ 1261 protected void synchronizeLocaleIndependentFields( 1262 CmsFile file, 1263 CmsXmlContent content, 1264 Collection<String> skipPaths, 1265 Collection<CmsEntity> entities, 1266 Locale lastEdited) 1267 throws CmsXmlException { 1268 1269 CmsEntity lastEditedEntity = null; 1270 for (CmsEntity entity : entities) { 1271 if (lastEdited.equals(CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entity.getId())))) { 1272 lastEditedEntity = entity; 1273 } else { 1274 synchronizeLocaleIndependentForEntity(file, content, skipPaths, entity); 1275 } 1276 } 1277 if (lastEditedEntity != null) { 1278 // prepare the last edited last, to sync locale independent fields 1279 synchronizeLocaleIndependentForEntity(file, content, skipPaths, lastEditedEntity); 1280 } 1281 } 1282 1283 /** 1284 * Transfers values marked as invisible from the original entity to the target entity.<p> 1285 * 1286 * @param original the original entity 1287 * @param target the target entiy 1288 * @param visitor the type visitor holding the content type configuration 1289 */ 1290 protected void transferInvisibleValues(CmsEntity original, CmsEntity target, CmsContentTypeVisitor visitor) { 1291 1292 List<String> invisibleAttributes = new ArrayList<String>(); 1293 for (Entry<String, CmsAttributeConfiguration> configEntry : visitor.getAttributeConfigurations().entrySet()) { 1294 if (!configEntry.getValue().isVisible()) { 1295 invisibleAttributes.add(configEntry.getKey()); 1296 } 1297 } 1298 CmsContentDefinition.transferValues( 1299 original, 1300 target, 1301 invisibleAttributes, 1302 visitor.getTypes(), 1303 visitor.getAttributeConfigurations(), 1304 true); 1305 } 1306 1307 /** 1308 * Adds the attribute values of the entity to the given XML content.<p> 1309 * 1310 * @param cms the current cms context 1311 * @param content the XML content 1312 * @param parentPath the parent path 1313 * @param entity the entity 1314 * @param contentLocale the content locale 1315 * 1316 * @return the set of xpaths of simple fields in the XML content which were set by this method 1317 */ 1318 private Set<String> addEntityAttributes( 1319 CmsObject cms, 1320 CmsXmlContent content, 1321 String parentPath, 1322 CmsEntity entity, 1323 Locale contentLocale) { 1324 1325 Set<String> fieldsSet = Sets.newHashSet(); 1326 addEntityAttributes(cms, content, parentPath, entity, contentLocale, fieldsSet); 1327 return fieldsSet; 1328 } 1329 1330 /** 1331 * Adds the attribute values of the entity to the given XML content.<p> 1332 * 1333 * @param cms the current cms context 1334 * @param content the XML content 1335 * @param parentPath the parent path 1336 * @param entity the entity 1337 * @param contentLocale the content locale 1338 * @param fieldsSet set to store which fields were set in the XML content 1339 */ 1340 private void addEntityAttributes( 1341 CmsObject cms, 1342 CmsXmlContent content, 1343 String parentPath, 1344 CmsEntity entity, 1345 Locale contentLocale, 1346 Set<String> fieldsSet) { 1347 1348 for (CmsEntityAttribute attribute : entity.getAttributes()) { 1349 if (!isSettingsAttribute(attribute.getAttributeName())) { 1350 if (CmsType.CHOICE_ATTRIBUTE_NAME.equals(attribute.getAttributeName())) { 1351 List<CmsEntity> choiceEntities = attribute.getComplexValues(); 1352 for (int i = 0; i < choiceEntities.size(); i++) { 1353 List<CmsEntityAttribute> choiceAttributes = choiceEntities.get(i).getAttributes(); 1354 // each choice entity may only have a single attribute with a single value 1355 assert (choiceAttributes.size() == 1) 1356 && choiceAttributes.get( 1357 0).isSingleValue() : "each choice entity may only have a single attribute with a single value"; 1358 CmsEntityAttribute choiceAttribute = choiceAttributes.get(0); 1359 String elementPath = parentPath + getElementName(choiceAttribute.getAttributeName()); 1360 if (choiceAttribute.isSimpleValue()) { 1361 String value = choiceAttribute.getSimpleValue(); 1362 I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i); 1363 if (field == null) { 1364 field = content.addValue(cms, elementPath, contentLocale, i); 1365 } 1366 field.setStringValue(cms, value); 1367 fieldsSet.add(field.getPath()); 1368 } else { 1369 CmsEntity child = choiceAttribute.getComplexValue(); 1370 I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i); 1371 if (field == null) { 1372 field = content.addValue(cms, elementPath, contentLocale, i); 1373 } 1374 addEntityAttributes(cms, content, field.getPath() + "/", child, contentLocale, fieldsSet); 1375 } 1376 } 1377 } else { 1378 String elementPath = parentPath + getElementName(attribute.getAttributeName()); 1379 if (attribute.isSimpleValue()) { 1380 List<String> values = attribute.getSimpleValues(); 1381 for (int i = 0; i < values.size(); i++) { 1382 String value = values.get(i); 1383 I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i); 1384 if (field == null) { 1385 field = content.addValue(cms, elementPath, contentLocale, i); 1386 } 1387 field.setStringValue(cms, value); 1388 fieldsSet.add(field.getPath()); 1389 } 1390 } else { 1391 List<CmsEntity> entities = attribute.getComplexValues(); 1392 for (int i = 0; i < entities.size(); i++) { 1393 CmsEntity child = entities.get(i); 1394 I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i); 1395 if (field == null) { 1396 field = content.addValue(cms, elementPath, contentLocale, i); 1397 } 1398 addEntityAttributes(cms, content, field.getPath() + "/", child, contentLocale, fieldsSet); 1399 } 1400 } 1401 } 1402 } 1403 } 1404 } 1405 1406 /** 1407 * Adds the setting attributes according to the setting configuration.<p> 1408 * 1409 * @param attributeConfiguration the attribute configuration 1410 * @param settingsConfig the setting configuration 1411 * @param nestedFormatters the nested formatters 1412 * @param messages the messages 1413 * @param contentLocale the content locale 1414 * @param settingPresets the setting presets 1415 * 1416 * @return the list of names of added attributes 1417 */ 1418 private List<String> addSettingsAttributes( 1419 Map<String, CmsAttributeConfiguration> attributeConfiguration, 1420 Map<String, CmsXmlContentProperty> settingsConfig, 1421 List<I_CmsFormatterBean> nestedFormatters, 1422 CmsMessages messages, 1423 Locale contentLocale, 1424 Map<String, String> settingPresets) { 1425 1426 String attrName; 1427 List<String> attributes = new ArrayList<String>(); 1428 attributeConfiguration.put( 1429 SETTINGS_CLIENT_ID_ATTRIBUTE, 1430 new CmsAttributeConfiguration( 1431 "internal_client_id", 1432 "", 1433 null, 1434 null, 1435 null, 1436 DisplayType.none.name(), 1437 false, 1438 false, 1439 false)); 1440 for (Entry<String, CmsXmlContentProperty> entry : settingsConfig.entrySet()) { 1441 CmsXmlContentProperty prop = entry.getValue(); 1442 String niceName = prop.getNiceName(); 1443 if (CmsStringUtil.isEmptyOrWhitespaceOnly(niceName)) { 1444 niceName = prop.getName(); 1445 } 1446 attrName = getSettingsAttributeName(entry.getKey()); 1447 boolean visible = !HIDDEN_SETTINGS_WIDGET_NAME.equals(prop.getWidget()) 1448 && !settingPresets.containsKey(prop.getName()); 1449 if (visible) { 1450 attributes.add(attrName); 1451 } 1452 1453 attributeConfiguration.put( 1454 attrName, 1455 new CmsAttributeConfiguration( 1456 niceName, 1457 prop.getDescription(), 1458 getWidgetName(prop.getWidget()), 1459 getWidgetConfig(prop.getWidget(), prop.getWidgetConfiguration(), messages, contentLocale), 1460 prop.getDefault(), 1461 DisplayType.singleline.name(), 1462 visible, 1463 false, 1464 false)); 1465 } 1466 if (nestedFormatters != null) { 1467 for (I_CmsFormatterBean formatter : nestedFormatters) { 1468 attrName = getSettingsAttributeName(formatter.getId()); 1469 attributes.add(attrName); 1470 attributeConfiguration.put( 1471 attrName, 1472 new CmsAttributeConfiguration( 1473 formatter.getNiceName(m_workplaceLocale), 1474 "", 1475 null, 1476 null, 1477 null, 1478 DisplayType.none.name(), 1479 true, 1480 false, 1481 false)); 1482 1483 } 1484 } 1485 return attributes; 1486 } 1487 1488 /** 1489 * Adds the entity types according to the setting configuration.<p> 1490 * 1491 * @param entityType the entity base type name 1492 * @param types the types 1493 * @param settingsConfig the setting configuration 1494 * @param nestedFormatters the nested formatters 1495 */ 1496 private void addSettingsTypes( 1497 String entityType, 1498 Map<String, CmsType> types, 1499 Map<String, CmsXmlContentProperty> settingsConfig, 1500 List<I_CmsFormatterBean> nestedFormatters) { 1501 1502 CmsType baseType = types.get(entityType); 1503 CmsType settingType = new CmsType(SETTING_TYPE_NAME); 1504 types.put(settingType.getId(), settingType); 1505 baseType.addAttribute(SETTINGS_CLIENT_ID_ATTRIBUTE, settingType, 1, 1); 1506 // add main settings first 1507 for (Entry<String, CmsXmlContentProperty> entry : settingsConfig.entrySet()) { 1508 boolean nested = false; 1509 if (nestedFormatters != null) { 1510 for (I_CmsFormatterBean formatter : nestedFormatters) { 1511 if (entry.getKey().startsWith(formatter.getId())) { 1512 nested = true; 1513 break; 1514 } 1515 } 1516 } 1517 if (!nested) { 1518 baseType.addAttribute(getSettingsAttributeName(entry.getKey()), settingType, 0, 1); 1519 } 1520 } 1521 // add nested formatter settings after 1522 for (Entry<String, CmsXmlContentProperty> entry : settingsConfig.entrySet()) { 1523 if (nestedFormatters != null) { 1524 for (I_CmsFormatterBean formatter : nestedFormatters) { 1525 if (entry.getKey().startsWith(formatter.getId()) 1526 && !HIDDEN_SETTINGS_WIDGET_NAME.equals(entry.getValue().getWidget())) { 1527 CmsType parent = types.get(formatter.getId()); 1528 if (parent == null) { 1529 parent = new CmsType(formatter.getId()); 1530 types.put(parent.getId(), parent); 1531 baseType.addAttribute(getSettingsAttributeName(formatter.getId()), parent, 1, 1); 1532 } 1533 parent.addAttribute(getSettingsAttributeName(entry.getKey()), settingType, 0, 1); 1534 break; 1535 } 1536 } 1537 } 1538 } 1539 } 1540 1541 /** 1542 * Adds the settings values to the given entity.<p> 1543 * 1544 * @param entity the entity 1545 * @param containerElement the container element bean providing the values 1546 * @param nestedFormatters the nested formatters 1547 */ 1548 private void addSettingsValues( 1549 CmsEntity entity, 1550 CmsContainerElementBean containerElement, 1551 List<I_CmsFormatterBean> nestedFormatters) { 1552 1553 entity.addAttributeValue(SETTINGS_CLIENT_ID_ATTRIBUTE, containerElement.editorHash()); 1554 for (Entry<String, String> settingEntry : containerElement.getIndividualSettings().entrySet()) { 1555 boolean nested = false; 1556 if (nestedFormatters != null) { 1557 for (I_CmsFormatterBean formatter : nestedFormatters) { 1558 if (settingEntry.getKey().startsWith(formatter.getId())) { 1559 String nestedSettingAttributeName = getSettingsAttributeName(formatter.getId()); 1560 CmsEntity nestedEntity = null; 1561 CmsEntityAttribute attribute = entity.getAttribute(nestedSettingAttributeName); 1562 if (attribute != null) { 1563 nestedEntity = attribute.getComplexValue(); 1564 } else { 1565 nestedEntity = new CmsEntity(nestedSettingAttributeName + "[1]", formatter.getId()); 1566 entity.addAttributeValue(nestedSettingAttributeName, nestedEntity); 1567 } 1568 nestedEntity.addAttributeValue( 1569 getSettingsAttributeName(settingEntry.getKey()), 1570 settingEntry.getValue()); 1571 nested = true; 1572 break; 1573 } 1574 } 1575 } 1576 if (!nested) { 1577 entity.addAttributeValue(getSettingsAttributeName(settingEntry.getKey()), settingEntry.getValue()); 1578 } 1579 1580 } 1581 } 1582 1583 /** 1584 * Check if automatic content correction is required. Returns <code>true</code> if the content was changed.<p> 1585 * 1586 * @param cms the cms context 1587 * @param content the content to check 1588 * 1589 * @return <code>true</code> if the content was changed 1590 * @throws CmsXmlException if the automatic content correction failed 1591 */ 1592 private boolean checkAutoCorrection(CmsObject cms, CmsXmlContent content) throws CmsXmlException { 1593 1594 boolean performedAutoCorrection = content.isTransformedVersion(); 1595 try { 1596 content.validateXmlStructure(new CmsXmlEntityResolver(cms)); 1597 } catch (CmsXmlException eXml) { 1598 // validation failed 1599 content.setAutoCorrectionEnabled(true); 1600 content.correctXmlStructure(cms); 1601 performedAutoCorrection = true; 1602 } 1603 return performedAutoCorrection; 1604 } 1605 1606 /** 1607 * Creates a new resource to edit, delegating to an edit handler if edit handler data is passed in.<p> 1608 * 1609 * @param newLink A string, specifying where which new content should be created. 1610 * @param locale The locale for which the 1611 * @param referenceSitePath site path of the currently edited content. 1612 * @param modelFileSitePath site path of the model file 1613 * @param mode optional creation mode 1614 * @param postCreateHandler optional class name of an {@link I_CmsCollectorPostCreateHandler} which is invoked after the content has been created. 1615 * @param editHandlerData edit handler data (will be null if no edit handler is configured or the edit handler does not handle 'new') 1616 * 1617 * @return The site-path of the newly created resource. 1618 * @throws CmsException if something goes wrong 1619 */ 1620 private String createResourceToEdit( 1621 String newLink, 1622 Locale locale, 1623 String referenceSitePath, 1624 String modelFileSitePath, 1625 String mode, 1626 String postCreateHandler, 1627 CmsEditHandlerData editHandlerData) 1628 throws CmsException { 1629 1630 CmsObject cms = getCmsObject(); 1631 HttpServletRequest request = getRequest(); 1632 if (editHandlerData != null) { 1633 CmsContainerpageService containerpageService = new CmsContainerpageService(); 1634 containerpageService.setCms(cms); 1635 containerpageService.setRequest(request); 1636 CmsResource page = cms.readResource(editHandlerData.getPageContextId(), CmsResourceFilter.ALL); 1637 CmsContainerElementBean elementBean = containerpageService.getCachedElement( 1638 editHandlerData.getClientId(), 1639 page.getRootPath()); 1640 Map<String, String[]> params = CmsRequestUtil.createParameterMap( 1641 CmsEncoder.decode(editHandlerData.getRequestParams()), 1642 true, 1643 CmsEncoder.ENCODING_UTF_8); 1644 elementBean.initResource(cms); 1645 CmsResource elementResource = elementBean.getResource(); 1646 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(elementResource); 1647 if (type instanceof CmsResourceTypeXmlContent) { 1648 CmsResourceTypeXmlContent xmlType = (CmsResourceTypeXmlContent)type; 1649 I_CmsEditHandler handler = xmlType.getEditHandler(cms); 1650 if (handler != null) { 1651 return handler.handleNew( 1652 cms, 1653 newLink, 1654 locale, 1655 referenceSitePath, 1656 modelFileSitePath, 1657 postCreateHandler, 1658 elementBean, 1659 editHandlerData.getPageContextId(), 1660 params, 1661 editHandlerData.getOption()); 1662 1663 } else { 1664 LOG.warn( 1665 "Invalid state: edit handler data passed in, but edit handler is undefined. type = " 1666 + type.getTypeName()); 1667 } 1668 } else { 1669 LOG.warn("Invalid state: edit handler data passed in for non-XML content type."); 1670 } 1671 } 1672 return defaultCreateResourceToEdit( 1673 cms, 1674 newLink, 1675 locale, 1676 referenceSitePath, 1677 modelFileSitePath, 1678 mode, 1679 postCreateHandler); 1680 } 1681 1682 /** 1683 * Evaluates any wildcards in the given scope and returns all allowed permutations of it.<p> 1684 * 1685 * a path like Paragraph* /Image should result in Paragraph[0]/Image, Paragraph[1]/Image and Paragraph[2]/Image 1686 * in case max occurrence for Paragraph is 3 1687 * 1688 * @param scope the scope 1689 * @param definition the content definition 1690 * 1691 * @return the evaluate scope permutations 1692 */ 1693 private Set<String> evaluateScope(String scope, CmsXmlContentDefinition definition) { 1694 1695 Set<String> evaluatedScopes = new HashSet<String>(); 1696 if (scope.contains("*")) { 1697 // evaluate wildcards to get all allowed permutations of the scope 1698 // a path like Paragraph*/Image should result in Paragraph[0]/Image, Paragraph[1]/Image and Paragraph[2]/Image 1699 // in case max occurrence for Paragraph is 3 1700 1701 String[] pathElements = scope.split("/"); 1702 String parentPath = ""; 1703 1704 for (int i = 0; i < pathElements.length; i++) { 1705 String elementName = pathElements[i]; 1706 boolean hasWildCard = elementName.endsWith("*"); 1707 1708 if (hasWildCard) { 1709 elementName = elementName.substring(0, elementName.length() - 1); 1710 parentPath = CmsStringUtil.joinPaths(parentPath, elementName); 1711 I_CmsXmlSchemaType type = definition.getSchemaType(parentPath); 1712 Set<String> tempScopes = new HashSet<String>(); 1713 if (type.getMaxOccurs() == Integer.MAX_VALUE) { 1714 throw new IllegalStateException( 1715 "Can not use fields with unbounded maxOccurs in scopes for editor change handler."); 1716 } 1717 for (int j = 0; j < type.getMaxOccurs(); j++) { 1718 if (evaluatedScopes.isEmpty()) { 1719 tempScopes.add(elementName + "[" + (j + 1) + "]"); 1720 } else { 1721 1722 for (String evScope : evaluatedScopes) { 1723 tempScopes.add(CmsStringUtil.joinPaths(evScope, elementName + "[" + (j + 1) + "]")); 1724 } 1725 } 1726 } 1727 evaluatedScopes = tempScopes; 1728 } else { 1729 parentPath = CmsStringUtil.joinPaths(parentPath, elementName); 1730 Set<String> tempScopes = new HashSet<String>(); 1731 if (evaluatedScopes.isEmpty()) { 1732 tempScopes.add(elementName); 1733 } else { 1734 for (String evScope : evaluatedScopes) { 1735 tempScopes.add(CmsStringUtil.joinPaths(evScope, elementName)); 1736 } 1737 } 1738 evaluatedScopes = tempScopes; 1739 } 1740 } 1741 } else { 1742 evaluatedScopes.add(scope); 1743 } 1744 return evaluatedScopes; 1745 } 1746 1747 /** 1748 * Evaluates the values of the locale independent fields and the paths to skip during locale synchronization.<p> 1749 * 1750 * @param content the XML content 1751 * @param syncValues the map of synchronization values 1752 * @param skipPaths the list o paths to skip 1753 */ 1754 private void evaluateSyncLocaleValues( 1755 CmsXmlContent content, 1756 Map<String, String> syncValues, 1757 Collection<String> skipPaths) { 1758 1759 CmsObject cms = getCmsObject(); 1760 for (Locale locale : content.getLocales()) { 1761 for (String elementPath : content.getContentDefinition().getContentHandler().getSynchronizations()) { 1762 for (I_CmsXmlContentValue contentValue : content.getSimpleValuesBelowPath(elementPath, locale)) { 1763 String valuePath = contentValue.getPath(); 1764 boolean skip = false; 1765 for (String skipPath : skipPaths) { 1766 if (valuePath.startsWith(skipPath)) { 1767 skip = true; 1768 break; 1769 } 1770 } 1771 if (!skip) { 1772 String value = contentValue.getStringValue(cms); 1773 if (syncValues.containsKey(valuePath)) { 1774 if (!syncValues.get(valuePath).equals(value)) { 1775 // in case the current value does not match the previously stored value, 1776 // remove it and add the parent path to the skipPaths list 1777 syncValues.remove(valuePath); 1778 int pathLevelDiff = (CmsResource.getPathLevel(valuePath) 1779 - CmsResource.getPathLevel(elementPath)) + 1; 1780 for (int i = 0; i < pathLevelDiff; i++) { 1781 valuePath = CmsXmlUtils.removeLastXpathElement(valuePath); 1782 } 1783 skipPaths.add(valuePath); 1784 } 1785 } else { 1786 syncValues.put(valuePath, value); 1787 } 1788 } 1789 } 1790 } 1791 } 1792 } 1793 1794 /** 1795 * Returns the change handler scopes.<p> 1796 * 1797 * @param definition the content definition 1798 * 1799 * @return the scopes 1800 */ 1801 private Set<String> getChangeHandlerScopes(CmsXmlContentDefinition definition) { 1802 1803 List<I_CmsXmlContentEditorChangeHandler> changeHandlers = definition.getContentHandler().getEditorChangeHandlers( 1804 false); 1805 Set<String> scopes = new HashSet<String>(); 1806 for (I_CmsXmlContentEditorChangeHandler handler : changeHandlers) { 1807 String scope = handler.getScope(); 1808 scopes.addAll(evaluateScope(scope, definition)); 1809 } 1810 return scopes; 1811 } 1812 1813 /** 1814 * Returns the XML content document.<p> 1815 * 1816 * @param file the resource file 1817 * @param fromCache <code>true</code> to use the cached document 1818 * 1819 * @return the content document 1820 * 1821 * @throws CmsXmlException if reading the XML fails 1822 */ 1823 private CmsXmlContent getContentDocument(CmsFile file, boolean fromCache) throws CmsXmlException { 1824 1825 CmsXmlContent content = null; 1826 if (fromCache) { 1827 content = getSessionCache().getCacheXmlContent(file.getStructureId()); 1828 } 1829 if (content == null) { 1830 content = CmsXmlContentFactory.unmarshal(getCmsObject(), file); 1831 getSessionCache().setCacheXmlContent(file.getStructureId(), content); 1832 } 1833 CmsContentTypeVisitor visitor = new CmsContentTypeVisitor(getCmsObject(), file, Locale.ENGLISH); 1834 visitor.visitTypes(content.getContentDefinition(), Locale.ENGLISH); 1835 CmsDynamicCategoryFieldList dynCatFields = visitor.getOptionalDynamicCategoryFields(); 1836 dynCatFields.ensureFields(getCmsObject(), content); 1837 return content; 1838 } 1839 1840 /** 1841 * Returns the value that has to be set for the dynamic attribute. 1842 * 1843 * @param file the file where the current content is stored 1844 * @param value the content value that is represented by the attribute 1845 * @param attributeName the attribute's name 1846 * @param editedLocalEntity the entities that where edited last 1847 * @return the value that has to be set for the dynamic attribute. 1848 */ 1849 private String getDynamicAttributeValue( 1850 CmsFile file, 1851 I_CmsXmlContentValue value, 1852 String attributeName, 1853 CmsEntity editedLocalEntity) { 1854 1855 if ((null != editedLocalEntity) && (editedLocalEntity.getAttribute(attributeName) != null)) { 1856 getSessionCache().setDynamicValue( 1857 attributeName, 1858 editedLocalEntity.getAttribute(attributeName).getSimpleValue()); 1859 } 1860 String currentValue = getSessionCache().getDynamicValue(attributeName); 1861 if (null != currentValue) { 1862 return currentValue; 1863 } 1864 if (null != file) { 1865 if (value.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME)) { 1866 List<CmsCategory> categories = new ArrayList<CmsCategory>(0); 1867 try { 1868 categories = CmsCategoryService.getInstance().readResourceCategories(getCmsObject(), file); 1869 } catch (CmsException e) { 1870 LOG.error(Messages.get().getBundle().key(Messages.ERROR_FAILED_READING_CATEGORIES_1), e); 1871 } 1872 I_CmsWidget widget = null; 1873 widget = CmsWidgetUtil.collectWidgetInfo(getCmsObject(), value).getWidget(); 1874 if ((null != widget) && (widget instanceof CmsCategoryWidget)) { 1875 String mainCategoryPath = ((CmsCategoryWidget)widget).getStartingCategory( 1876 getCmsObject(), 1877 getCmsObject().getSitePath(file)); 1878 StringBuffer pathes = new StringBuffer(); 1879 for (CmsCategory category : categories) { 1880 if (category.getPath().startsWith(mainCategoryPath)) { 1881 String path = category.getBasePath() + category.getPath(); 1882 path = getCmsObject().getRequestContext().removeSiteRoot(path); 1883 pathes.append(path).append(','); 1884 } 1885 } 1886 String dynamicConfigString = pathes.length() > 0 ? pathes.substring(0, pathes.length() - 1) : ""; 1887 getSessionCache().setDynamicValue(attributeName, dynamicConfigString); 1888 return dynamicConfigString; 1889 } 1890 } 1891 } 1892 return ""; 1893 1894 } 1895 1896 /** 1897 * Returns the formatter configuration for the given container element bean.<p> 1898 * 1899 * @param containerElement the container element 1900 * 1901 * @return the formatter configuration 1902 */ 1903 private I_CmsFormatterBean getFormatterForElement( 1904 CmsADEConfigData config, 1905 CmsContainerElementBean containerElement) { 1906 1907 if ((containerElement != null) 1908 && (containerElement.getFormatterId() != null) 1909 && !containerElement.getFormatterId().isNullUUID()) { 1910 CmsUUID formatterId = containerElement.getFormatterId(); 1911 1912 // find formatter config setting 1913 for (Entry<String, String> settingEntry : containerElement.getIndividualSettings().entrySet()) { 1914 if (settingEntry.getKey().startsWith(CmsFormatterConfig.FORMATTER_SETTINGS_KEY)) { 1915 String formatterConfigId = settingEntry.getValue(); 1916 I_CmsFormatterBean dynamicFmt = config.findFormatter(formatterConfigId); 1917 if ((dynamicFmt != null) && dynamicFmt.getJspStructureId().equals(formatterId)) { 1918 return dynamicFmt; 1919 } 1920 } 1921 } 1922 } 1923 return null; 1924 } 1925 1926 /** 1927 * Returns the path elements for the given content value.<p> 1928 * 1929 * @param content the XML content 1930 * @param value the content value 1931 * 1932 * @return the path elements 1933 */ 1934 private String[] getPathElements(CmsXmlContent content, I_CmsXmlContentValue value) { 1935 1936 List<String> pathElements = new ArrayList<String>(); 1937 String[] paths = value.getPath().split("/"); 1938 String path = ""; 1939 for (int i = 0; i < paths.length; i++) { 1940 path += paths[i]; 1941 I_CmsXmlContentValue ancestor = content.getValue(path, value.getLocale()); 1942 int valueIndex = ancestor.getXmlIndex(); 1943 if (ancestor.isChoiceOption()) { 1944 Element parent = ancestor.getElement().getParent(); 1945 valueIndex = parent.indexOf(ancestor.getElement()); 1946 } 1947 String pathElement = getAttributeName(ancestor); 1948 pathElements.add(pathElement + "[" + valueIndex + "]"); 1949 if (ancestor.isChoiceType()) { 1950 pathElements.add("ATTRIBUTE_CHOICE"); 1951 } 1952 path += "/"; 1953 } 1954 return pathElements.toArray(new String[pathElements.size()]); 1955 } 1956 1957 /** 1958 * Returns the session cache.<p> 1959 * 1960 * @return the session cache 1961 */ 1962 private CmsADESessionCache getSessionCache() { 1963 1964 if (m_sessionCache == null) { 1965 m_sessionCache = CmsADESessionCache.getCache(getRequest(), getCmsObject()); 1966 } 1967 return m_sessionCache; 1968 } 1969 1970 /** 1971 * Returns the attribute name to use for the given setting name.<p> 1972 * 1973 * @param settingName the setting name 1974 * 1975 * @return the attribute name 1976 */ 1977 private String getSettingsAttributeName(String settingName) { 1978 1979 return "/" + SETTINGS_ATTRIBUTE_NAME_PREFIX + settingName; 1980 } 1981 1982 /** 1983 * Transforms the widget configuration.<p> 1984 * @param settingsWidget the settings widget name 1985 * @param settingsConfig the setting widget configuration 1986 * @param messages the workplace messages 1987 * @param contentLocale the content locale 1988 * 1989 * @return the client widget configuration string 1990 */ 1991 private String getWidgetConfig( 1992 String settingsWidget, 1993 String settingsConfig, 1994 CmsMessages messages, 1995 Locale contentLocale) { 1996 1997 Class<? extends I_CmsADEWidget> widgetClass = WIDGET_MAPPINGS.get(settingsWidget); 1998 String config = ""; 1999 if (widgetClass == null) { 2000 widgetClass = CmsInputWidget.class; 2001 } 2002 try { 2003 I_CmsADEWidget widget = widgetClass.newInstance(); 2004 widget.setConfiguration(settingsConfig); 2005 config = widget.getConfiguration(getCmsObject(), null, messages, null, contentLocale); 2006 } catch (Exception e) { 2007 LOG.error(e.getLocalizedMessage(), e); 2008 } 2009 return config; 2010 } 2011 2012 /** 2013 * Returns the widget class name.<p> 2014 * 2015 * @param settingsWidget the settings widget name 2016 * 2017 * @return the widget class name 2018 */ 2019 private String getWidgetName(String settingsWidget) { 2020 2021 if (WIDGET_MAPPINGS.containsKey(settingsWidget)) { 2022 return WIDGET_MAPPINGS.get(settingsWidget).getName(); 2023 } else { 2024 return CmsInputWidget.class.getName(); 2025 } 2026 } 2027 2028 /** 2029 * Returns the workplace locale.<p> 2030 * 2031 * @param cms the current OpenCms context 2032 * 2033 * @return the current users workplace locale 2034 */ 2035 private Locale getWorkplaceLocale(CmsObject cms) { 2036 2037 if (m_workplaceLocale == null) { 2038 m_workplaceLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 2039 } 2040 return m_workplaceLocale; 2041 } 2042 2043 /** 2044 * Checks whether the given attribute name indicates it is a settings attribute.<p> 2045 * 2046 * @param attributeName the attribute name 2047 * 2048 * @return <code>true</code> in case of settings attributes 2049 */ 2050 private boolean isSettingsAttribute(String attributeName) { 2051 2052 return attributeName.startsWith("/" + SETTINGS_ATTRIBUTE_NAME_PREFIX); 2053 } 2054 2055 private CmsADEConfigData readConfig(CmsUUID pageId) { 2056 2057 if (pageId == null) { 2058 return null; 2059 } 2060 try { 2061 CmsResource resource = getCmsObject().readResource(pageId, CmsResourceFilter.IGNORE_EXPIRATION); 2062 return OpenCms.getADEManager().lookupConfiguration(getCmsObject(), resource.getRootPath()); 2063 } catch (CmsException e) { 2064 LOG.warn(e.getLocalizedMessage(), e); 2065 return null; 2066 } 2067 2068 } 2069 2070 /** 2071 * Reads the content definition for the given resource and locale.<p> 2072 * 2073 * @param file the resource file 2074 * @param content the XML content 2075 * @param entityId the entity id 2076 * @param clientId the container element client id if available 2077 * @param locale the content locale 2078 * @param newLocale if the locale content should be created as new 2079 * @param mainLocale the main language to copy in case the element language node does not exist yet 2080 * @param editedLocaleEntity the edited locale entity 2081 * @param settingPresets the presets for settings 2082 * @param configData the sitemap configuration to use 2083 * 2084 * @return the content definition 2085 * 2086 * @throws CmsException if something goes wrong 2087 */ 2088 private CmsContentDefinition readContentDefinition( 2089 CmsFile file, 2090 CmsXmlContent content, 2091 String entityId, 2092 String clientId, 2093 Locale locale, 2094 boolean newLocale, 2095 Locale mainLocale, 2096 CmsEntity editedLocaleEntity, 2097 Map<String, String> settingPresets, 2098 CmsADEConfigData configData) 2099 throws CmsException { 2100 2101 long timer = 0; 2102 if (LOG.isDebugEnabled()) { 2103 timer = System.currentTimeMillis(); 2104 } 2105 CmsObject cms = getCmsObject(); 2106 CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration( 2107 cms, 2108 cms.getRequestContext().getRootUri()); 2109 List<Locale> availableLocalesList = OpenCms.getLocaleManager().getAvailableLocales(cms, file); 2110 if (!availableLocalesList.contains(locale)) { 2111 availableLocalesList.retainAll(content.getLocales()); 2112 List<Locale> defaultLocales = OpenCms.getLocaleManager().getDefaultLocales(cms, file); 2113 Locale replacementLocale = OpenCms.getLocaleManager().getBestMatchingLocale( 2114 locale, 2115 defaultLocales, 2116 availableLocalesList); 2117 LOG.info( 2118 "Can't edit locale " 2119 + locale 2120 + " of file " 2121 + file.getRootPath() 2122 + " because it is not configured as available locale. Using locale " 2123 + replacementLocale 2124 + " instead."); 2125 locale = replacementLocale; 2126 entityId = CmsContentDefinition.uuidToEntityId(file.getStructureId(), locale.toString()); 2127 } 2128 2129 if (CmsStringUtil.isEmptyOrWhitespaceOnly(entityId)) { 2130 entityId = CmsContentDefinition.uuidToEntityId(file.getStructureId(), locale.toString()); 2131 } 2132 boolean performedAutoCorrection = checkAutoCorrection(cms, content); 2133 if (performedAutoCorrection) { 2134 content.initDocument(); 2135 } 2136 if (LOG.isDebugEnabled()) { 2137 LOG.debug( 2138 Messages.get().getBundle().key( 2139 Messages.LOG_TAKE_UNMARSHALING_TIME_1, 2140 "" + (System.currentTimeMillis() - timer))); 2141 } 2142 CmsContentTypeVisitor visitor = new CmsContentTypeVisitor(cms, file, locale); 2143 if (LOG.isDebugEnabled()) { 2144 timer = System.currentTimeMillis(); 2145 } 2146 visitor.visitTypes(content.getContentDefinition(), getWorkplaceLocale(cms)); 2147 if (LOG.isDebugEnabled()) { 2148 LOG.debug( 2149 Messages.get().getBundle().key( 2150 Messages.LOG_TAKE_VISITING_TYPES_TIME_1, 2151 "" + (System.currentTimeMillis() - timer))); 2152 } 2153 CmsEntity entity = null; 2154 Map<String, String> syncValues = new HashMap<String, String>(); 2155 Collection<String> skipPaths = new HashSet<String>(); 2156 evaluateSyncLocaleValues(content, syncValues, skipPaths); 2157 if (content.hasLocale(locale) && newLocale) { 2158 // a new locale is requested, so remove the present one 2159 content.removeLocale(locale); 2160 } 2161 if (!content.hasLocale(locale)) { 2162 if ((mainLocale != null) && content.hasLocale(mainLocale)) { 2163 content.copyLocale(mainLocale, locale); 2164 } else { 2165 content.addLocale(cms, locale); 2166 } 2167 // sync the locale values 2168 if (!visitor.getLocaleSynchronizations().isEmpty() && (content.getLocales().size() > 1)) { 2169 for (Locale contentLocale : content.getLocales()) { 2170 if (!contentLocale.equals(locale)) { 2171 content.synchronizeLocaleIndependentValues(cms, skipPaths, contentLocale); 2172 } 2173 } 2174 } 2175 } 2176 visitor.getOptionalDynamicCategoryFields().ensureFields(cms, content, locale); 2177 Element element = content.getLocaleNode(locale); 2178 if (LOG.isDebugEnabled()) { 2179 timer = System.currentTimeMillis(); 2180 } 2181 entity = readEntity( 2182 content, 2183 element, 2184 locale, 2185 entityId, 2186 "", 2187 getTypeUri(content.getContentDefinition()), 2188 visitor, 2189 false, 2190 editedLocaleEntity); 2191 2192 if (LOG.isDebugEnabled()) { 2193 LOG.debug( 2194 Messages.get().getBundle().key( 2195 Messages.LOG_TAKE_READING_ENTITY_TIME_1, 2196 "" + (System.currentTimeMillis() - timer))); 2197 } 2198 List<String> contentLocales = new ArrayList<String>(); 2199 for (Locale contentLocale : content.getLocales()) { 2200 contentLocales.add(contentLocale.toString()); 2201 } 2202 Locale workplaceLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 2203 TreeMap<String, String> availableLocales = new TreeMap<String, String>(); 2204 for (Locale availableLocale : OpenCms.getLocaleManager().getAvailableLocales(cms, file)) { 2205 availableLocales.put(availableLocale.toString(), availableLocale.getDisplayName(workplaceLocale)); 2206 } 2207 String title = cms.readPropertyObject(file, CmsPropertyDefinition.PROPERTY_TITLE, false).getValue(); 2208 try { 2209 CmsGallerySearchResult searchResult = CmsGallerySearch.searchById(cms, file.getStructureId(), locale); 2210 title = searchResult.getTitle(); 2211 } catch (CmsException e) { 2212 LOG.warn(e.getLocalizedMessage(), e); 2213 } 2214 String typeName = OpenCms.getResourceManager().getResourceType(file.getTypeId()).getTypeName(); 2215 boolean autoUnlock = OpenCms.getWorkplaceManager().shouldAcaciaUnlock(); 2216 Map<String, CmsEntity> entities = new HashMap<String, CmsEntity>(); 2217 entities.put(entityId, entity); 2218 2219 Map<String, CmsAttributeConfiguration> attrConfig = visitor.getAttributeConfigurations(); 2220 Map<String, CmsType> types = visitor.getTypes(); 2221 List<CmsTabInfo> tabInfos = visitor.getTabInfos(); 2222 2223 if (clientId != null) { 2224 CmsContainerElementBean containerElement = getSessionCache().getCacheContainerElement(clientId); 2225 I_CmsFormatterBean formatter = getFormatterForElement(configData, containerElement); 2226 if ((formatter != null) 2227 && formatter.isAllowsSettingsInEditor() 2228 && !formatter.getSettings(config).isEmpty()) { 2229 Map<String, CmsXmlContentProperty> settingsConfig = OpenCms.getADEManager().getFormatterSettings( 2230 cms, 2231 config, 2232 formatter, 2233 containerElement.getResource(), 2234 locale, 2235 getRequest()); 2236 com.google.common.base.Supplier<CmsXmlContent> contentSupplier = Suppliers.memoize(() -> { 2237 try { 2238 return CmsXmlContentFactory.unmarshal(cms, cms.readFile(containerElement.getResource())); 2239 } catch (CmsException e) { 2240 LOG.error(e.getLocalizedMessage(), e); 2241 return null; 2242 } 2243 }); 2244 settingsConfig = CmsXmlContentPropertyHelper.resolveMacrosForPropertyInfo( 2245 cms, 2246 null, 2247 containerElement.getResource(), 2248 contentSupplier, 2249 CmsElementUtil.createStringTemplateSource(formatter, contentSupplier), 2250 settingsConfig); 2251 CmsMessages messages = OpenCms.getWorkplaceManager().getMessages(m_workplaceLocale); 2252 List<I_CmsFormatterBean> nestedFormatters = formatter.hasNestedFormatterSettings() 2253 ? OpenCms.getADEManager().getNestedFormatters( 2254 cms, 2255 config, 2256 containerElement.getResource(), 2257 locale, 2258 getRequest()) 2259 : Collections.emptyList(); 2260 String firstContentAttributeName = types.get( 2261 entity.getTypeName()).getAttributeNames().iterator().next(); 2262 List<String> addedVisibleAttrs = addSettingsAttributes( 2263 attrConfig, 2264 settingsConfig, 2265 nestedFormatters, 2266 messages, 2267 locale, 2268 settingPresets); 2269 addSettingsTypes(entity.getTypeName(), types, settingsConfig, nestedFormatters); 2270 if (editedLocaleEntity != null) { 2271 transferSettingValues(editedLocaleEntity, entity); 2272 } else { 2273 addSettingsValues(entity, containerElement, nestedFormatters); 2274 } 2275 if (tabInfos.isEmpty()) { 2276 tabInfos.add( 2277 new CmsTabInfo( 2278 Messages.get().getBundle(workplaceLocale).key(Messages.GUI_CONTENT_TAB_LABEL_0), 2279 "content", 2280 firstContentAttributeName.substring(entity.getTypeName().length() + 1), 2281 false, 2282 null)); 2283 } 2284 if (addedVisibleAttrs.size() > 0) { 2285 tabInfos.add( 2286 new CmsTabInfo( 2287 Messages.get().getBundle(workplaceLocale).key(Messages.GUI_SETTINGS_TAB_LABEL_0), 2288 CmsContentDefinition.SETTINGS_TAB_ID, 2289 CmsFileUtil.removeLeadingSeparator(addedVisibleAttrs.iterator().next()), 2290 false, 2291 Messages.get().getBundle(workplaceLocale).key(Messages.GUI_SETTINGS_TAB_DESCRIPTION_0))); 2292 } 2293 } 2294 2295 } 2296 2297 return new CmsContentDefinition( 2298 entityId, 2299 entities, 2300 visitor.getAttributeConfigurations(), 2301 visitor.getWidgetConfigurations(), 2302 visitor.getComplexWidgetData(), 2303 visitor.getTypes(), 2304 visitor.getTabInfos(), 2305 locale.toString(), 2306 contentLocales, 2307 availableLocales, 2308 visitor.getLocaleSynchronizations(), 2309 syncValues, 2310 skipPaths, 2311 title, 2312 cms.getSitePath(file), 2313 typeName, 2314 CmsIconUtil.getIconClasses(CmsIconUtil.getDisplayType(cms, file), file.getName(), false), 2315 performedAutoCorrection, 2316 autoUnlock, 2317 getChangeHandlerScopes(content.getContentDefinition())); 2318 } 2319 2320 /** 2321 * Creates a new resource according to the new link, or returns the model file informations 2322 * modelFileId is <code>null</code> but required.<p> 2323 * 2324 * @param newLink the new link 2325 * @param referenceResource the reference resource 2326 * @param modelFileId the model file structure id 2327 * @param locale the content locale 2328 * @param mode the content creation mode 2329 * @param postCreateHandler the class name for the post-create handler 2330 * @param editHandlerData the edit handler data, in case the 'new' function is handled by an edit handler 2331 * @param settingPresets the presets for settings 2332 * 2333 * @return the content definition 2334 * 2335 * @throws CmsException if creating the resource failed 2336 */ 2337 private CmsContentDefinition readContentDefinitionForNew( 2338 String newLink, 2339 CmsResource referenceResource, 2340 CmsUUID modelFileId, 2341 Locale locale, 2342 String mode, 2343 String postCreateHandler, 2344 CmsEditHandlerData editHandlerData, 2345 Map<String, String> settingPresets) 2346 throws CmsException { 2347 2348 String sitePath = getCmsObject().getSitePath(referenceResource); 2349 String resourceType; 2350 if (newLink.startsWith(CmsJspTagEdit.NEW_LINK_IDENTIFIER)) { 2351 resourceType = CmsJspTagEdit.getTypeFromNewLink(newLink); 2352 } else { 2353 resourceType = OpenCms.getResourceManager().getResourceType(referenceResource.getTypeId()).getTypeName(); 2354 } 2355 String modelFile = null; 2356 if (modelFileId == null) { 2357 List<CmsResource> modelResources = CmsResourceTypeXmlContent.getModelFiles( 2358 getCmsObject(), 2359 CmsResource.getFolderPath(sitePath), 2360 resourceType); 2361 if (!modelResources.isEmpty()) { 2362 List<CmsModelResourceInfo> modelInfos = CmsContainerpageService.generateModelResourceList( 2363 getCmsObject(), 2364 resourceType, 2365 modelResources, 2366 locale); 2367 return new CmsContentDefinition( 2368 modelInfos, 2369 newLink, 2370 referenceResource.getStructureId(), 2371 locale.toString()); 2372 } 2373 } else if (!modelFileId.isNullUUID()) { 2374 modelFile = getCmsObject().getSitePath( 2375 getCmsObject().readResource(modelFileId, CmsResourceFilter.IGNORE_EXPIRATION)); 2376 } 2377 String newFileName = createResourceToEdit( 2378 newLink, 2379 locale, 2380 sitePath, 2381 modelFile, 2382 mode, 2383 postCreateHandler, 2384 editHandlerData); 2385 CmsResource resource = getCmsObject().readResource(newFileName, CmsResourceFilter.IGNORE_EXPIRATION); 2386 CmsFile file = getCmsObject().readFile(resource); 2387 CmsXmlContent content = getContentDocument(file, false); 2388 CmsContentDefinition contentDefinition = readContentDefinition( 2389 file, 2390 content, 2391 null, 2392 null, 2393 locale, 2394 false, 2395 null, 2396 null, 2397 settingPresets, 2398 null); 2399 contentDefinition.setDeleteOnCancel(true); 2400 return contentDefinition; 2401 } 2402 2403 /** 2404 * Stores the settings attributes to the container element bean persisted in the session cache.<p> 2405 * The container page editor will write the container page XML.<p> 2406 * 2407 * @param entity the entity 2408 * @param containerElement the container element 2409 * @param settingsConfig the settings configuration 2410 * @param nestedFormatters the nested formatters 2411 * 2412 * @return <code>true</code> in case any changed settings where saved 2413 */ 2414 @SuppressWarnings("null") 2415 private boolean saveSettings( 2416 CmsEntity entity, 2417 CmsContainerElementBean containerElement, 2418 Map<String, CmsXmlContentProperty> settingsConfig, 2419 List<I_CmsFormatterBean> nestedFormatters) { 2420 2421 boolean hasChangedSettings = false; 2422 Map<String, String> values = new HashMap<>(containerElement.getIndividualSettings()); 2423 for (Entry<String, CmsXmlContentProperty> settingsEntry : settingsConfig.entrySet()) { 2424 String value = null; 2425 boolean nested = false; 2426 if (nestedFormatters != null) { 2427 for (I_CmsFormatterBean formatter : nestedFormatters) { 2428 if (settingsEntry.getKey().startsWith(formatter.getId())) { 2429 2430 CmsEntity nestedEntity = null; 2431 CmsEntityAttribute attribute = entity.getAttribute(getSettingsAttributeName(formatter.getId())); 2432 if (attribute != null) { 2433 nestedEntity = attribute.getComplexValue(); 2434 CmsEntityAttribute valueAttribute = nestedEntity.getAttribute( 2435 getSettingsAttributeName(settingsEntry.getKey())); 2436 if (valueAttribute != null) { 2437 value = valueAttribute.getSimpleValue(); 2438 } 2439 } 2440 nested = true; 2441 break; 2442 } 2443 } 2444 } 2445 if (!nested) { 2446 CmsEntityAttribute valueAttribute = entity.getAttribute( 2447 getSettingsAttributeName(settingsEntry.getKey())); 2448 if (valueAttribute != null) { 2449 value = valueAttribute.getSimpleValue(); 2450 } 2451 } 2452 if (CmsStringUtil.isEmptyOrWhitespaceOnly(value) 2453 && !HIDDEN_SETTINGS_WIDGET_NAME.equals(settingsEntry.getValue().getWidget()) 2454 && values.containsKey(settingsEntry.getKey())) { 2455 values.remove(settingsEntry.getKey()); 2456 hasChangedSettings = true; 2457 } else if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value) 2458 && !HIDDEN_SETTINGS_WIDGET_NAME.equals(settingsEntry.getValue().getWidget()) 2459 && !value.equals(values.get(settingsEntry.getKey()))) { 2460 values.put(settingsEntry.getKey(), value); 2461 hasChangedSettings = true; 2462 } 2463 } 2464 if (hasChangedSettings) { 2465 containerElement.updateIndividualSettings(values); 2466 getSessionCache().setCacheContainerElement(containerElement.editorHash(), containerElement); 2467 } 2468 return hasChangedSettings; 2469 } 2470 2471 /** 2472 * Synchronizes the locale independent fields for the given entity.<p> 2473 * 2474 * @param file the content file 2475 * @param content the XML content 2476 * @param skipPaths the paths to skip during locale synchronization 2477 * @param entity the entity 2478 * 2479 * @throws CmsXmlException if something goes wrong 2480 */ 2481 private void synchronizeLocaleIndependentForEntity( 2482 CmsFile file, 2483 CmsXmlContent content, 2484 Collection<String> skipPaths, 2485 CmsEntity entity) 2486 throws CmsXmlException { 2487 2488 CmsObject cms = getCmsObject(); 2489 String entityId = entity.getId(); 2490 Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId)); 2491 CmsContentTypeVisitor visitor = null; 2492 CmsEntity originalEntity = null; 2493 if (content.getHandler().hasVisibilityHandlers()) { 2494 visitor = new CmsContentTypeVisitor(cms, file, contentLocale); 2495 visitor.visitTypes(content.getContentDefinition(), getWorkplaceLocale(cms)); 2496 } 2497 if (content.hasLocale(contentLocale)) { 2498 if ((visitor != null) && visitor.hasInvisibleFields()) { 2499 // we need to add invisible content values to the entity before saving 2500 Element element = content.getLocaleNode(contentLocale); 2501 originalEntity = readEntity( 2502 content, 2503 element, 2504 contentLocale, 2505 entityId, 2506 "", 2507 getTypeUri(content.getContentDefinition()), 2508 visitor, 2509 true, 2510 entity); 2511 } 2512 content.removeLocale(contentLocale); 2513 } 2514 content.addLocale(cms, contentLocale); 2515 if ((visitor != null) && visitor.hasInvisibleFields()) { 2516 transferInvisibleValues(originalEntity, entity, visitor); 2517 } 2518 addEntityAttributes(cms, content, "", entity, contentLocale); 2519 content.synchronizeLocaleIndependentValues(cms, skipPaths, contentLocale); 2520 } 2521 2522 /** 2523 * Transfers settings attribute values from one entity to another.<p> 2524 * 2525 * @param source the source entity 2526 * @param target the target entity 2527 */ 2528 private void transferSettingValues(CmsEntity source, CmsEntity target) { 2529 2530 for (CmsEntityAttribute attr : source.getAttributes()) { 2531 if (isSettingsAttribute(attr.getAttributeName())) { 2532 if (attr.isSimpleValue()) { 2533 target.addAttributeValue(attr.getAttributeName(), attr.getSimpleValue()); 2534 } else { 2535 CmsEntity nestedSource = attr.getComplexValue(); 2536 2537 CmsEntity nested = new CmsEntity(nestedSource.getId(), nestedSource.getTypeName()); 2538 for (CmsEntityAttribute nestedAttr : nestedSource.getAttributes()) { 2539 nested.addAttributeValue(nestedAttr.getAttributeName(), nestedAttr.getSimpleValue()); 2540 } 2541 target.addAttributeValue(attr.getAttributeName(), nested); 2542 } 2543 } 2544 } 2545 } 2546 2547 /** 2548 * Validates the given XML content.<p> 2549 * 2550 * @param cms the cms context 2551 * @param structureId the structure id 2552 * @param content the XML content 2553 * 2554 * @return the validation result 2555 */ 2556 private CmsValidationResult validateContent(CmsObject cms, CmsUUID structureId, CmsXmlContent content) { 2557 2558 return validateContent(cms, structureId, content, null); 2559 } 2560 2561 /** 2562 * Validates the given XML content.<p> 2563 * 2564 * @param cms the cms context 2565 * @param structureId the structure id 2566 * @param content the XML content 2567 * @param fieldNames if not null, only validation errors in paths from this set will be added to the validation result 2568 * 2569 * @return the validation result 2570 */ 2571 private CmsValidationResult validateContent( 2572 CmsObject cms, 2573 CmsUUID structureId, 2574 CmsXmlContent content, 2575 Set<String> fieldNames) { 2576 2577 CmsXmlContentErrorHandler errorHandler = content.validate(cms); 2578 Map<String, Map<String[], CmsPair<String, String>>> errorsByEntity = new HashMap<String, Map<String[], CmsPair<String, String>>>(); 2579 2580 if (errorHandler.hasErrors()) { 2581 boolean reallyHasErrors = false; 2582 for (Entry<Locale, Map<String, String>> localeEntry : errorHandler.getErrors().entrySet()) { 2583 Map<String[], CmsPair<String, String>> errors = new HashMap<String[], CmsPair<String, String>>(); 2584 for (Entry<String, String> error : localeEntry.getValue().entrySet()) { 2585 I_CmsXmlContentValue value = content.getValue(error.getKey(), localeEntry.getKey()); 2586 if ((fieldNames == null) || fieldNames.contains(value.getPath())) { 2587 errors.put( 2588 getPathElements(content, value), 2589 new CmsPair<String, String>(error.getValue(), error.getKey())); 2590 reallyHasErrors = true; 2591 } 2592 2593 } 2594 if (reallyHasErrors) { 2595 errorsByEntity.put( 2596 CmsContentDefinition.uuidToEntityId(structureId, localeEntry.getKey().toString()), 2597 errors); 2598 } 2599 } 2600 } 2601 Map<String, Map<String[], CmsPair<String, String>>> warningsByEntity = new HashMap<String, Map<String[], CmsPair<String, String>>>(); 2602 if (errorHandler.hasWarnings()) { 2603 boolean reallyHasErrors = false; 2604 for (Entry<Locale, Map<String, String>> localeEntry : errorHandler.getWarnings().entrySet()) { 2605 Map<String[], CmsPair<String, String>> warnings = new HashMap<String[], CmsPair<String, String>>(); 2606 for (Entry<String, String> warning : localeEntry.getValue().entrySet()) { 2607 I_CmsXmlContentValue value = content.getValue(warning.getKey(), localeEntry.getKey()); 2608 if ((fieldNames == null) || fieldNames.contains(value.getPath())) { 2609 warnings.put( 2610 getPathElements(content, value), 2611 new CmsPair<String, String>(warning.getValue(), warning.getKey())); 2612 reallyHasErrors = true; 2613 } 2614 } 2615 if (reallyHasErrors) { 2616 warningsByEntity.put( 2617 CmsContentDefinition.uuidToEntityId(structureId, localeEntry.getKey().toString()), 2618 warnings); 2619 } 2620 } 2621 } 2622 return new CmsValidationResult(errorsByEntity, warningsByEntity); 2623 } 2624 2625 /** 2626 * Validates the settings attribute values.<p> 2627 * 2628 * @param entity the entity to validate 2629 * @param validationResult the validation result 2630 * @param settingsConfig the settings configuration 2631 */ 2632 private void validateSettings( 2633 CmsEntity entity, 2634 CmsValidationResult validationResult, 2635 Map<String, CmsXmlContentProperty> settingsConfig) { 2636 2637 Map<String, Map<String[], CmsPair<String, String>>> errors = validationResult.getErrors(); 2638 Map<String[], CmsPair<String, String>> entityErrors = errors.get(entity.getId()); 2639 if (entityErrors == null) { 2640 entityErrors = new HashMap<String[], CmsPair<String, String>>(); 2641 } 2642 Map<String, Map<String[], CmsPair<String, String>>> warnings = validationResult.getWarnings(); 2643 Map<String[], CmsPair<String, String>> entityWarnings = warnings.get(entity.getId()); 2644 if (entityWarnings == null) { 2645 entityWarnings = new HashMap<String[], CmsPair<String, String>>(); 2646 } 2647 2648 for (CmsEntityAttribute attribute : entity.getAttributes()) { 2649 if (isSettingsAttribute(attribute.getAttributeName())) { 2650 if (attribute.isSimpleValue()) { 2651 String settingsKey = attribute.getAttributeName().substring( 2652 SETTINGS_ATTRIBUTE_NAME_PREFIX.length() + 1); 2653 CmsXmlContentProperty prop = settingsConfig.get(settingsKey); 2654 if (prop != null) { 2655 String regex = prop.getRuleRegex(); 2656 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(regex)) { 2657 if (!attribute.getSimpleValue().matches(regex)) { 2658 String[] path = new String[] {attribute.getAttributeName()}; 2659 2660 if (SETTINGS_RULE_TYPE_ERROR.equals(prop.getRuleType())) { 2661 entityErrors.put( 2662 path, 2663 new CmsPair<String, String>(prop.getError(), prop.getNiceName())); 2664 } else { 2665 entityWarnings.put( 2666 path, 2667 new CmsPair<String, String>(prop.getError(), prop.getNiceName())); 2668 } 2669 } 2670 } 2671 } 2672 } else { 2673 CmsEntity nested = attribute.getComplexValue(); 2674 for (CmsEntityAttribute nestedAttribute : nested.getAttributes()) { 2675 String settingsKey = nestedAttribute.getAttributeName().substring( 2676 SETTINGS_ATTRIBUTE_NAME_PREFIX.length() + 1); 2677 CmsXmlContentProperty prop = settingsConfig.get(settingsKey); 2678 if (prop != null) { 2679 String regex = prop.getRuleRegex(); 2680 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(regex)) { 2681 if (!nestedAttribute.getSimpleValue().matches(regex)) { 2682 String[] path = new String[] { 2683 attribute.getAttributeName(), 2684 nestedAttribute.getAttributeName()}; 2685 if (SETTINGS_RULE_TYPE_ERROR.equals(prop.getRuleType())) { 2686 entityErrors.put( 2687 path, 2688 new CmsPair<String, String>(prop.getError(), prop.getNiceName())); 2689 } else { 2690 entityWarnings.put( 2691 path, 2692 new CmsPair<String, String>(prop.getError(), prop.getNiceName())); 2693 } 2694 } 2695 } 2696 } 2697 } 2698 } 2699 } 2700 } 2701 if (!entityErrors.isEmpty()) { 2702 errors.put(entity.getId(), entityErrors); 2703 } 2704 if (!entityWarnings.isEmpty()) { 2705 warnings.put(entity.getId(), entityWarnings); 2706 } 2707 2708 } 2709 2710 /** 2711 * Writes the categories that are dynamically read/wrote by the content editor. 2712 * 2713 * @param file the file where the content is stored. 2714 * @param content the content. 2715 * @param lastEditedEntity the last edited entity 2716 */ 2717 private void writeCategories(CmsFile file, CmsXmlContent content, CmsEntity lastEditedEntity) { 2718 2719 // do nothing if one of the arguments is empty. 2720 if ((null == content) || (null == file)) { 2721 return; 2722 } 2723 2724 CmsObject cms = getCmsObject(); 2725 if (!content.getLocales().isEmpty()) { 2726 Locale locale = content.getLocales().iterator().next(); 2727 CmsEntity entity = lastEditedEntity; 2728 List<I_CmsXmlContentValue> values = content.getValues(locale); 2729 for (I_CmsXmlContentValue value : values) { 2730 if (value.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME)) { 2731 I_CmsWidget widget = CmsWidgetUtil.collectWidgetInfo(cms, value).getWidget(); 2732 List<CmsCategory> categories = new ArrayList<CmsCategory>(0); 2733 try { 2734 categories = CmsCategoryService.getInstance().readResourceCategories(cms, file); 2735 } catch (CmsException e) { 2736 LOG.error(Messages.get().getBundle().key(Messages.ERROR_FAILED_READING_CATEGORIES_1), e); 2737 } 2738 if ((null != widget) && (widget instanceof CmsCategoryWidget)) { 2739 String mainCategoryPath = ((CmsCategoryWidget)widget).getStartingCategory( 2740 cms, 2741 cms.getSitePath(file)); 2742 for (CmsCategory category : categories) { 2743 if (category.getPath().startsWith(mainCategoryPath)) { 2744 try { 2745 CmsCategoryService.getInstance().removeResourceFromCategory( 2746 cms, 2747 cms.getSitePath(file), 2748 category); 2749 } catch (CmsException e) { 2750 LOG.error(e.getLocalizedMessage(), e); 2751 } 2752 } 2753 } 2754 if (null == entity) { 2755 try { 2756 CmsContentDefinition definition = readContentDefinition( 2757 file, 2758 content, 2759 "dummy", 2760 null, 2761 locale, 2762 false, 2763 null, 2764 null, 2765 Collections.emptyMap(), 2766 null); 2767 entity = definition.getEntity(); 2768 } catch (CmsException e) { 2769 LOG.error(e.getLocalizedMessage(), e); 2770 } 2771 } 2772 String checkedCategories = ""; 2773 if (null != entity) { 2774 checkedCategories = CmsEntity.getValueForPath(entity, new String[] {value.getPath()}); 2775 } 2776 List<String> checkedCategoryList = Arrays.asList(checkedCategories.split(",")); 2777 for (String category : checkedCategoryList) { 2778 try { 2779 CmsCategoryService.getInstance().addResourceToCategory( 2780 cms, 2781 cms.getSitePath(file), 2782 CmsCategoryService.getInstance().getCategory(cms, category)); 2783 } catch (CmsException e) { 2784 if (LOG.isWarnEnabled()) { 2785 LOG.warn(e.getLocalizedMessage(), e); 2786 } 2787 } 2788 } 2789 } 2790 } 2791 } 2792 } 2793 } 2794 2795 /** 2796 * Writes the xml content to the vfs and re-initializes the member variables.<p> 2797 * 2798 * @param cms the cms context 2799 * @param file the file to write to 2800 * @param content the content 2801 * @param encoding the file encoding 2802 * 2803 * @return the content 2804 * 2805 * @throws CmsException if writing the file fails 2806 */ 2807 private CmsXmlContent writeContent(CmsObject cms, CmsFile file, CmsXmlContent content, String encoding) 2808 throws CmsException { 2809 2810 String decodedContent = content.toString(); 2811 try { 2812 file.setContents(decodedContent.getBytes(encoding)); 2813 } catch (UnsupportedEncodingException e) { 2814 throw new CmsException( 2815 org.opencms.workplace.editors.Messages.get().container( 2816 org.opencms.workplace.editors.Messages.ERR_INVALID_CONTENT_ENC_1, 2817 file.getRootPath()), 2818 e); 2819 } 2820 // the file content might have been modified during the write operation 2821 2822 cms.getRequestContext().setAttribute(ATTR_EDITOR_SAVING, "true"); 2823 try { 2824 file = cms.writeFile(file); 2825 } finally { 2826 cms.getRequestContext().removeAttribute(ATTR_EDITOR_SAVING); 2827 } 2828 return CmsXmlContentFactory.unmarshal(cms, file); 2829 } 2830 2831}