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