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