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