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