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 org.apache.commons.logging.Log; 148 149import org.dom4j.Element; 150 151import com.google.common.base.Suppliers; 152import com.google.common.cache.Cache; 153import com.google.common.cache.CacheBuilder; 154import com.google.common.collect.Sets; 155 156import jakarta.servlet.http.HttpServletRequest; 157import jakarta.servlet.http.HttpSession; 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 /** The content augmentation used. */ 170 private I_CmsXmlContentAugmentation m_augmentation; 171 172 /** The context supplied to the content augmentation job. */ 173 private I_CmsXmlContentAugmentation.Context m_context; 174 175 private volatile Throwable m_exception; 176 177 /** The job id. */ 178 private CmsUUID m_id = new CmsUUID(); 179 180 /** The message to display. */ 181 private volatile String m_message; 182 183 /** The locale to switch to. */ 184 private volatile Locale m_nextLocale; 185 186 /** 187 * The original copy of the XML content object. 188 */ 189 private CmsXmlContent m_originalCopy; 190 191 /** The progress message to display. */ 192 private volatile String m_progress; 193 194 /** The results of the content augmentation. */ 195 private volatile CmsXmlContent m_result; 196 197 /** The job state. */ 198 private volatile AugmentationJobState m_state = AugmentationJobState.running; 199 200 /** The structure id of the content being augmented. */ 201 private CmsUUID m_structureId; 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 aborted, done, running; 401 } 402 403 public static final String ATTR_AUGMENTATION_JOBS = "augmentationJobs"; 404 405 /** Request context attribute to mark a writeFile() triggered by the user saving in the content editor. */ 406 public static final String ATTR_EDITOR_SAVING = "__EDITOR_SAVING"; 407 408 /** The logger for this class. */ 409 protected static final Log LOG = CmsLog.getLog(CmsContentService.class); 410 411 /** The type name prefix. */ 412 static final String TYPE_NAME_PREFIX = "http://opencms.org/types/"; 413 414 private static ExecutorService m_augmentationThreadPool = Executors.newCachedThreadPool(); 415 416 /** The RDFA attributes string. */ 417 private static final String RDFA_ATTRIBUTES = CmsGwtConstants.ATTR_DATA_ID 418 + "=\"%1$s\" " 419 + CmsGwtConstants.ATTR_DATA_FIELD 420 + "=\"%2$s\""; 421 422 /** The serial version id. */ 423 private static final long serialVersionUID = 7873052619331296648L; 424 425 /** The setting type name. */ 426 private static final String SETTING_TYPE_NAME = "###SETTING_TYPE###"; 427 428 /** The settings attribute name prefix. */ 429 private static final String SETTINGS_ATTRIBUTE_NAME_PREFIX = "SETTING:::"; 430 431 /** The attribute name used for the client id. */ 432 private static final String SETTINGS_CLIENT_ID_ATTRIBUTE = "/" + SETTINGS_ATTRIBUTE_NAME_PREFIX + "CLIENT_ID:::"; 433 434 /** The settings validation rule type name. */ 435 private static final String SETTINGS_RULE_TYPE_ERROR = "error"; 436 437 /** Mapping client widget names to server side widget classes. */ 438 private static final Map<String, Class<? extends I_CmsADEWidget>> WIDGET_MAPPINGS = new HashMap<>(); 439 440 static { 441 WIDGET_MAPPINGS.put("string", CmsInputWidget.class); 442 WIDGET_MAPPINGS.put("select", CmsSelectWidget.class); 443 WIDGET_MAPPINGS.put("multicheck", org.opencms.widgets.CmsMultiSelectWidget.class); 444 WIDGET_MAPPINGS.put("selectcombo", CmsSelectComboWidget.class); 445 WIDGET_MAPPINGS.put("checkbox", CmsCheckboxWidget.class); 446 WIDGET_MAPPINGS.put("combo", CmsComboWidget.class); 447 WIDGET_MAPPINGS.put("datebox", CmsCalendarWidget.class); 448 WIDGET_MAPPINGS.put("gallery", CmsVfsFileWidget.class); 449 WIDGET_MAPPINGS.put("multiselectbox", CmsMultiSelectWidget.class); 450 WIDGET_MAPPINGS.put("radio", CmsRadioSelectWidget.class); 451 WIDGET_MAPPINGS.put("groupselection", CmsGroupWidget.class); 452 } 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 * Synchronizes and transforms the edited content.<p> 1354 * 1355 * @param entityId the entity id 1356 * @param clientId the client id 1357 * @param editedEntity the edited entity 1358 * @param deletedEntities the deleted entities 1359 * @param skipPaths the paths to skip 1360 * 1361 * @return the content augmentation details 1362 * 1363 * @throws CmsRpcException if something goes wrong 1364 */ 1365 public CmsContentAugmentationDetails synchronizeAndTransform( 1366 String entityId, 1367 String clientId, 1368 CmsEntity editedEntity, 1369 List<String> deletedEntities, 1370 Collection<String> skipPaths) 1371 throws CmsRpcException { 1372 1373 CmsObject cms = getCmsObject(); 1374 try { 1375 CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entityId); 1376 CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION); 1377 CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration( 1378 cms, 1379 cms.getRequestContext().getRootUri()); 1380 CmsFile file = cms.readFile(resource); 1381 CmsXmlContent content = getContentDocument(file, true); 1382 if (editedEntity != null) { 1383 synchronizeLocaleIndependentForEntity(file, content, skipPaths, editedEntity); 1384 } 1385 for (String deleteId : deletedEntities) { 1386 Locale localeToDelete = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(deleteId)); 1387 if (content.hasLocale(localeToDelete)) { 1388 content.removeLocale(localeToDelete); 1389 } 1390 } 1391 1392 byte[] contentData = content.marshal(); 1393 CmsFile fileCopy = new CmsFile(content.getFile()); 1394 fileCopy.setContents(contentData); 1395 1396 I_CmsXmlContentAugmentation augmentation = getContentAugmentation(cms, resource, config); 1397 Locale currentLocale = CmsLocaleManager.getLocale( 1398 CmsContentDefinition.getLocaleFromId(editedEntity.getId())); 1399 CmsXmlContent originalCopy = CmsXmlContentFactory.unmarshal(cms, fileCopy); 1400 CmsXmlContent[] resultContainer = {content}; // if not set, keeps the original 1401 Locale[] nextLocaleContainer = {null}; 1402 StringBuilder messageBuffer = new StringBuilder(); 1403 if (augmentation != null) { 1404 1405 augmentation.augmentContent(new I_CmsXmlContentAugmentation.Context() { 1406 1407 private boolean m_callingJsp; 1408 1409 @Override 1410 public void callJsp(String path) throws Exception { 1411 1412 if (m_callingJsp) { 1413 throw new IllegalStateException("callJsp method can not be used reentrantly"); 1414 } 1415 m_callingJsp = true; 1416 try { 1417 getRequest().setAttribute(I_CmsXmlContentAugmentation.ATTR_CONTEXT, this); 1418 CmsResource resource = cms.readResource(path); 1419 OpenCms.getResourceManager().getLoader(resource).dump( 1420 cms, 1421 resource, 1422 null, 1423 cms.getRequestContext().getLocale(), 1424 getRequest(), 1425 getResponse()); 1426 } finally { 1427 getRequest().removeAttribute(I_CmsXmlContentAugmentation.ATTR_CONTEXT); 1428 m_callingJsp = false; 1429 } 1430 } 1431 1432 @Override 1433 public CmsADEConfigData getADEConfig() { 1434 1435 return config; 1436 } 1437 1438 @Override 1439 public CmsObject getCmsObject() { 1440 1441 return cms; 1442 } 1443 1444 @Override 1445 public CmsXmlContent getContent() { 1446 1447 return originalCopy; 1448 } 1449 1450 @Override 1451 public Locale getLocale() { 1452 1453 return currentLocale; 1454 } 1455 1456 @Override 1457 public String getParameter(String param) { 1458 1459 throw new UnsupportedOperationException(); 1460 } 1461 1462 public boolean isAborted() { 1463 1464 return false; 1465 } 1466 1467 @Override 1468 public void progress(String progressMessage) { 1469 1470 throw new UnsupportedOperationException(); 1471 } 1472 1473 @Override 1474 public void setHtmlMessage(String message) { 1475 1476 messageBuffer.delete(0, messageBuffer.length()); 1477 if (message != null) { 1478 messageBuffer.append(message); 1479 } 1480 1481 } 1482 1483 @Override 1484 public void setNextLocale(Locale locale) { 1485 1486 nextLocaleContainer[0] = locale; 1487 1488 } 1489 1490 @Override 1491 public void setResult(CmsXmlContent result) { 1492 1493 resultContainer[0] = result; 1494 } 1495 }); 1496 } 1497 CmsXmlContent resultContent = resultContainer[0]; 1498 Locale nextLocale = nextLocaleContainer[0]; 1499 getSessionCache().setCacheXmlContent(structureId, resultContent); 1500 CmsContentAugmentationDetails result = new CmsContentAugmentationDetails(); 1501 result.setLocales( 1502 resultContent.getLocales().stream().map(locale -> locale.toString()).collect(Collectors.toList())); 1503 result.setNextLocale(nextLocale != null ? nextLocale.toString() : null); 1504 if (messageBuffer.length() > 0) { 1505 result.setHtmlMessage(messageBuffer.toString()); 1506 } 1507 return result; 1508 } catch (Exception e) { 1509 error(e); 1510 return null; 1511 } 1512 1513 } 1514 1515 /** 1516 * @see org.opencms.acacia.shared.rpc.I_CmsContentService#updateEntityHtml(org.opencms.acacia.shared.CmsEntity, java.lang.String, java.lang.String) 1517 */ 1518 public CmsEntityHtml updateEntityHtml(CmsEntity entity, String contextUri, String htmlContextInfo) 1519 throws Exception { 1520 1521 CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entity.getId()); 1522 if (structureId != null) { 1523 CmsObject cms = getCmsObject(); 1524 try { 1525 CmsResource resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION); 1526 CmsFile file = cms.readFile(resource); 1527 CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file); 1528 String entityId = entity.getId(); 1529 Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId)); 1530 if (content.hasLocale(contentLocale)) { 1531 content.removeLocale(contentLocale); 1532 } 1533 content.addLocale(cms, contentLocale); 1534 addEntityAttributes(cms, content, "", entity, contentLocale); 1535 CmsValidationResult validationResult = validateContent(cms, structureId, content); 1536 String htmlContent = null; 1537 if (!validationResult.hasErrors()) { 1538 file.setContents(content.marshal()); 1539 1540 JSONObject contextInfo = new JSONObject(htmlContextInfo); 1541 String containerName = contextInfo.getString(CmsCntPageData.JSONKEY_NAME); 1542 String containerType = contextInfo.getString(CmsCntPageData.JSONKEY_TYPE); 1543 int containerWidth = contextInfo.getInt(CmsCntPageData.JSONKEY_WIDTH); 1544 int maxElements = contextInfo.getInt(CmsCntPageData.JSONKEY_MAXELEMENTS); 1545 boolean detailView = contextInfo.getBoolean(CmsCntPageData.JSONKEY_DETAILVIEW); 1546 boolean isDetailViewContainer = contextInfo.getBoolean( 1547 CmsCntPageData.JSONKEY_ISDETAILVIEWCONTAINER); 1548 JSONObject presets = contextInfo.getJSONObject(CmsCntPageData.JSONKEY_PRESETS); 1549 HashMap<String, String> presetsMap = new HashMap<String, String>(); 1550 for (String key : presets.keySet()) { 1551 String val = presets.getString(key); 1552 presetsMap.put(key, val); 1553 } 1554 CmsContainer container = new CmsContainer( 1555 containerName, 1556 containerType, 1557 null, 1558 containerWidth, 1559 maxElements, 1560 isDetailViewContainer, 1561 detailView, 1562 true, 1563 Collections.<CmsContainerElement> emptyList(), 1564 null, 1565 null, 1566 presetsMap); 1567 CmsUUID detailContentId = null; 1568 if (contextInfo.has(CmsCntPageData.JSONKEY_DETAIL_ELEMENT_ID)) { 1569 detailContentId = new CmsUUID(contextInfo.getString(CmsCntPageData.JSONKEY_DETAIL_ELEMENT_ID)); 1570 } 1571 CmsElementUtil elementUtil = new CmsElementUtil( 1572 cms, 1573 contextUri, 1574 detailContentId, 1575 getThreadLocalRequest(), 1576 getThreadLocalResponse(), 1577 contentLocale); 1578 htmlContent = elementUtil.getContentByContainer( 1579 file, 1580 contextInfo.getString(CmsCntPageData.JSONKEY_ELEMENT_ID), 1581 container); 1582 } 1583 return new CmsEntityHtml(htmlContent, validationResult); 1584 1585 } catch (Exception e) { 1586 error(e); 1587 } 1588 } 1589 return null; 1590 } 1591 1592 /** 1593 * @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) 1594 */ 1595 public CmsValidationResult validateEntities( 1596 CmsEntity lastEditedEntity, 1597 String clientId, 1598 List<String> deletedEntities, 1599 Collection<String> skipPaths, 1600 String lastEditedLocale) 1601 throws CmsRpcException { 1602 1603 CmsUUID structureId = null; 1604 if (lastEditedEntity != null) { 1605 structureId = CmsContentDefinition.entityIdToUuid(lastEditedEntity.getId()); 1606 } 1607 if ((structureId == null) && !deletedEntities.isEmpty()) { 1608 structureId = CmsContentDefinition.entityIdToUuid(deletedEntities.get(0)); 1609 } 1610 CmsADEConfigData configData = OpenCms.getADEManager().lookupConfiguration( 1611 getCmsObject(), 1612 getCmsObject().getRequestContext().getRootUri()); 1613 if (structureId != null) { 1614 CmsObject cms = getCmsObject(); 1615 CmsResource resource = null; 1616 try { 1617 resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION); 1618 CmsFile file = cms.readFile(resource); 1619 CmsXmlContent content = getContentDocument(file, true); 1620 checkAutoCorrection(cms, content); 1621 if (lastEditedEntity != null) { 1622 synchronizeLocaleIndependentForEntity(file, content, skipPaths, lastEditedEntity); 1623 } 1624 for (String deleteId : deletedEntities) { 1625 Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(deleteId)); 1626 if (content.hasLocale(contentLocale)) { 1627 content.removeLocale(contentLocale); 1628 } 1629 } 1630 return validateContent(cms, structureId, content); 1631 1632 } catch (Exception e) { 1633 if (resource != null) { 1634 // TODO: What is this for? 1635 getSessionCache().uncacheXmlContent(structureId); 1636 } 1637 error(e); 1638 } 1639 } 1640 return null; 1641 } 1642 1643 /** 1644 * @see org.opencms.acacia.shared.rpc.I_CmsContentService#validateEntity(org.opencms.acacia.shared.CmsEntity) 1645 */ 1646 public CmsValidationResult validateEntity(CmsEntity changedEntity) throws CmsRpcException { 1647 1648 CmsUUID structureId = null; 1649 if (changedEntity == null) { 1650 return new CmsValidationResult(null, null); 1651 } 1652 structureId = CmsContentDefinition.entityIdToUuid(changedEntity.getId()); 1653 1654 if (structureId != null) { 1655 CmsObject cms = getCmsObject(); 1656 CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration( 1657 cms, 1658 cms.getRequestContext().getRootUri()); 1659 Set<String> setFieldNames = Sets.newHashSet(); 1660 try { 1661 CmsResource resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION); 1662 CmsFile file = cms.readFile(resource); 1663 CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file); 1664 String entityId = changedEntity.getId(); 1665 Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId)); 1666 if (content.hasLocale(contentLocale)) { 1667 content.removeLocale(contentLocale); 1668 } 1669 content.addLocale(cms, contentLocale); 1670 setFieldNames.addAll(addEntityAttributes(cms, content, "", changedEntity, contentLocale)); 1671 1672 CmsValidationResult result = validateContent(cms, structureId, content, setFieldNames); 1673 CmsEntityAttribute clientIdAttr = changedEntity.getAttribute(SETTINGS_CLIENT_ID_ATTRIBUTE); 1674 if (clientIdAttr != null) { 1675 String clientId = clientIdAttr.getSimpleValue(); 1676 CmsContainerElementBean containerElement = getSessionCache().getCacheContainerElement(clientId); 1677 I_CmsFormatterBean formatter = getFormatterForElement(config, containerElement); 1678 if ((formatter != null) 1679 && formatter.isAllowsSettingsInEditor() 1680 && !formatter.getSettings(config).isEmpty()) { 1681 Map<String, CmsXmlContentProperty> settingsConfig = OpenCms.getADEManager().getFormatterSettings( 1682 cms, 1683 config, 1684 formatter, 1685 containerElement.getResource(), 1686 contentLocale, 1687 getRequest()); 1688 validateSettings(changedEntity, result, settingsConfig); 1689 } 1690 } 1691 return result; 1692 } catch (Throwable e) { 1693 error(e); 1694 } 1695 } 1696 return new CmsValidationResult(null, null); 1697 } 1698 1699 /** 1700 * Decodes the newlink request parameter if possible.<p> 1701 * 1702 * @param newLink the parameter to decode 1703 * 1704 * @return the decoded value 1705 */ 1706 protected String decodeNewLink(String newLink) { 1707 1708 String result = newLink; 1709 if (result == null) { 1710 return null; 1711 } 1712 try { 1713 result = CmsEncoder.decode(result); 1714 try { 1715 result = CmsEncoder.decode(result); 1716 } catch (Throwable e) { 1717 LOG.info(e.getLocalizedMessage(), e); 1718 } 1719 } catch (Throwable e) { 1720 LOG.info(e.getLocalizedMessage(), e); 1721 } 1722 1723 return result; 1724 } 1725 1726 /** 1727 * Returns the element name to the given element.<p> 1728 * 1729 * @param attributeName the attribute name 1730 * 1731 * @return the element name 1732 */ 1733 protected String getElementName(String attributeName) { 1734 1735 if (attributeName.contains("/")) { 1736 return attributeName.substring(attributeName.lastIndexOf("/") + 1); 1737 } 1738 return attributeName; 1739 } 1740 1741 /** 1742 * Helper method to determine the encoding of the given file in the VFS, 1743 * which must be set using the "content-encoding" property.<p> 1744 * 1745 * @param cms the CmsObject 1746 * @param file the file which is to be checked 1747 * @return the encoding for the file 1748 */ 1749 protected String getFileEncoding(CmsObject cms, CmsResource file) { 1750 1751 String result; 1752 try { 1753 result = cms.readPropertyObject(file, CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, true).getValue( 1754 OpenCms.getSystemInfo().getDefaultEncoding()); 1755 } catch (CmsException e) { 1756 result = OpenCms.getSystemInfo().getDefaultEncoding(); 1757 } 1758 return CmsEncoder.lookupEncoding(result, OpenCms.getSystemInfo().getDefaultEncoding()); 1759 } 1760 1761 /** 1762 * Parses the element into an entity.<p> 1763 * 1764 * @param content the entity content 1765 * @param element the current element 1766 * @param locale the content locale 1767 * @param entityId the entity id 1768 * @param parentPath the parent path 1769 * @param typeName the entity type name 1770 * @param visitor the content type visitor 1771 * @param includeInvisible include invisible attributes 1772 * @param editedLocalEntity the edited locale entity 1773 * 1774 * @return the entity 1775 */ 1776 protected CmsEntity readEntity( 1777 CmsXmlContent content, 1778 Element element, 1779 Locale locale, 1780 String entityId, 1781 String parentPath, 1782 String typeName, 1783 CmsContentTypeVisitor visitor, 1784 boolean includeInvisible, 1785 CmsEntity editedLocalEntity) { 1786 1787 String newEntityId = entityId + (CmsStringUtil.isNotEmptyOrWhitespaceOnly(parentPath) ? "/" + parentPath : ""); 1788 CmsEntity newEntity = new CmsEntity(newEntityId, typeName); 1789 CmsEntity result = newEntity; 1790 1791 List<Element> elements = element.elements(); 1792 CmsType type = visitor.getTypes().get(typeName); 1793 boolean isChoice = type.isChoice(); 1794 String choiceTypeName = null; 1795 // just needed for choice attributes 1796 Map<String, Integer> attributeCounter = null; 1797 if (isChoice) { 1798 choiceTypeName = type.getAttributeTypeName(CmsType.CHOICE_ATTRIBUTE_NAME); 1799 type = visitor.getTypes().get(type.getAttributeTypeName(CmsType.CHOICE_ATTRIBUTE_NAME)); 1800 attributeCounter = new HashMap<String, Integer>(); 1801 } 1802 int counter = 0; 1803 CmsObject cms = getCmsObject(); 1804 String previousName = null; 1805 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(parentPath)) { 1806 parentPath += "/"; 1807 } 1808 for (Element child : elements) { 1809 String attributeName = getAttributeName(child.getName(), typeName); 1810 String subTypeName = type.getAttributeTypeName(attributeName); 1811 if (visitor.getTypes().get(subTypeName) == null) { 1812 // in case there is no type configured for this element, the schema may have changed, skip the element 1813 continue; 1814 } 1815 if (!includeInvisible && !visitor.getAttributeConfigurations().get(attributeName).isVisible()) { 1816 // skip attributes marked as invisible, there content should not be transfered to the client 1817 continue; 1818 } 1819 if (isChoice && (attributeCounter != null)) { 1820 if (!attributeName.equals(previousName)) { 1821 if (attributeCounter.get(attributeName) != null) { 1822 counter = attributeCounter.get(attributeName).intValue(); 1823 } else { 1824 counter = 0; 1825 } 1826 previousName = attributeName; 1827 } 1828 attributeCounter.put(attributeName, Integer.valueOf(counter + 1)); 1829 } else if (!attributeName.equals(previousName)) { 1830 1831 // reset the attribute counter for every attribute name 1832 counter = 0; 1833 1834 previousName = attributeName; 1835 } 1836 if (isChoice) { 1837 result = new CmsEntity( 1838 newEntityId + "/" + CmsType.CHOICE_ATTRIBUTE_NAME + "_" + child.getName() + "[" + counter + "]", 1839 choiceTypeName); 1840 newEntity.addAttributeValue(CmsType.CHOICE_ATTRIBUTE_NAME, result); 1841 } 1842 String path = parentPath + child.getName(); 1843 if (visitor.isDynamicallyLoaded(attributeName)) { 1844 I_CmsXmlContentValue value = content.getValue(path, locale, counter); 1845 String attributeValue = getDynamicAttributeValue( 1846 content.getFile(), 1847 value, 1848 attributeName, 1849 editedLocalEntity); 1850 result.addAttributeValue(attributeName, attributeValue); 1851 } else if (visitor.getTypes().get(subTypeName).isSimpleType()) { 1852 I_CmsXmlContentValue value = content.getValue(path, locale, counter); 1853 result.addAttributeValue(attributeName, value.getStringValue(cms)); 1854 } else { 1855 CmsEntity editedSubEntity = null; 1856 if ((editedLocalEntity != null) && (editedLocalEntity.getAttribute(attributeName) != null)) { 1857 editedSubEntity = editedLocalEntity.getAttribute(attributeName).getComplexValue(); 1858 } 1859 CmsEntity subEntity = readEntity( 1860 content, 1861 child, 1862 locale, 1863 entityId, 1864 path + "[" + (counter + 1) + "]", 1865 subTypeName, 1866 visitor, 1867 includeInvisible, 1868 editedSubEntity); 1869 result.addAttributeValue(attributeName, subEntity); 1870 1871 } 1872 counter++; 1873 } 1874 return newEntity; 1875 } 1876 1877 /** 1878 * Reads the types from the given content definition and adds the to the map of already registered 1879 * types if necessary.<p> 1880 * 1881 * @param xmlContentDefinition the XML content definition 1882 * @param locale the messages locale 1883 * 1884 * @return the types of the given content definition 1885 */ 1886 protected Map<String, CmsType> readTypes(CmsXmlContentDefinition xmlContentDefinition, Locale locale) { 1887 1888 CmsContentTypeVisitor visitor = new CmsContentTypeVisitor(getCmsObject(), null, locale); 1889 visitor.visitTypes(xmlContentDefinition, locale); 1890 return visitor.getTypes(); 1891 } 1892 1893 /** 1894 * Synchronizes the locale independent fields.<p> 1895 * 1896 * @param file the content file 1897 * @param content the XML content 1898 * @param skipPaths the paths to skip during locale synchronization 1899 * @param entities the edited entities 1900 * @param lastEdited the last edited locale 1901 * 1902 * @throws CmsXmlException if something goes wrong 1903 */ 1904 protected void synchronizeLocaleIndependentFields( 1905 CmsFile file, 1906 CmsXmlContent content, 1907 Collection<String> skipPaths, 1908 Collection<CmsEntity> entities, 1909 Locale lastEdited) 1910 throws CmsXmlException { 1911 1912 CmsEntity lastEditedEntity = null; 1913 for (CmsEntity entity : entities) { 1914 if (lastEdited.equals(CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entity.getId())))) { 1915 lastEditedEntity = entity; 1916 } else { 1917 synchronizeLocaleIndependentForEntity(file, content, skipPaths, entity); 1918 } 1919 } 1920 if (lastEditedEntity != null) { 1921 // prepare the last edited last, to sync locale independent fields 1922 synchronizeLocaleIndependentForEntity(file, content, skipPaths, lastEditedEntity); 1923 } 1924 } 1925 1926 /** 1927 * Transfers values marked as invisible from the original entity to the target entity.<p> 1928 * 1929 * @param original the original entity 1930 * @param target the target entiy 1931 * @param visitor the type visitor holding the content type configuration 1932 */ 1933 protected void transferInvisibleValues(CmsEntity original, CmsEntity target, CmsContentTypeVisitor visitor) { 1934 1935 List<String> invisibleAttributes = new ArrayList<String>(); 1936 for (Entry<String, CmsAttributeConfiguration> configEntry : visitor.getAttributeConfigurations().entrySet()) { 1937 if (!configEntry.getValue().isVisible()) { 1938 invisibleAttributes.add(configEntry.getKey()); 1939 } 1940 } 1941 CmsContentDefinition.transferValues( 1942 original, 1943 target, 1944 invisibleAttributes, 1945 visitor.getTypes(), 1946 visitor.getAttributeConfigurations(), 1947 true); 1948 } 1949 1950 /** 1951 * Adds the attribute values of the entity to the given XML content.<p> 1952 * 1953 * @param cms the current cms context 1954 * @param content the XML content 1955 * @param parentPath the parent path 1956 * @param entity the entity 1957 * @param contentLocale the content locale 1958 * 1959 * @return the set of xpaths of simple fields in the XML content which were set by this method 1960 */ 1961 private Set<String> addEntityAttributes( 1962 CmsObject cms, 1963 CmsXmlContent content, 1964 String parentPath, 1965 CmsEntity entity, 1966 Locale contentLocale) { 1967 1968 Set<String> fieldsSet = Sets.newHashSet(); 1969 addEntityAttributes(cms, content, parentPath, entity, contentLocale, fieldsSet); 1970 return fieldsSet; 1971 } 1972 1973 /** 1974 * Adds the attribute values of the entity to the given XML content.<p> 1975 * 1976 * @param cms the current cms context 1977 * @param content the XML content 1978 * @param parentPath the parent path 1979 * @param entity the entity 1980 * @param contentLocale the content locale 1981 * @param fieldsSet set to store which fields were set in the XML content 1982 */ 1983 private void addEntityAttributes( 1984 CmsObject cms, 1985 CmsXmlContent content, 1986 String parentPath, 1987 CmsEntity entity, 1988 Locale contentLocale, 1989 Set<String> fieldsSet) { 1990 1991 for (CmsEntityAttribute attribute : entity.getAttributes()) { 1992 if (!isSettingsAttribute(attribute.getAttributeName())) { 1993 if (CmsType.CHOICE_ATTRIBUTE_NAME.equals(attribute.getAttributeName())) { 1994 List<CmsEntity> choiceEntities = attribute.getComplexValues(); 1995 for (int i = 0; i < choiceEntities.size(); i++) { 1996 List<CmsEntityAttribute> choiceAttributes = choiceEntities.get(i).getAttributes(); 1997 // each choice entity may only have a single attribute with a single value 1998 assert (choiceAttributes.size() == 1) 1999 && choiceAttributes.get( 2000 0).isSingleValue() : "each choice entity may only have a single attribute with a single value"; 2001 CmsEntityAttribute choiceAttribute = choiceAttributes.get(0); 2002 String elementPath = parentPath + getElementName(choiceAttribute.getAttributeName()); 2003 if (choiceAttribute.isSimpleValue()) { 2004 String value = choiceAttribute.getSimpleValue(); 2005 I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i); 2006 if (field == null) { 2007 field = content.addValue(cms, elementPath, contentLocale, i); 2008 } 2009 field.setStringValue(cms, value); 2010 fieldsSet.add(field.getPath()); 2011 } else { 2012 CmsEntity child = choiceAttribute.getComplexValue(); 2013 I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i); 2014 if (field == null) { 2015 field = content.addValue(cms, elementPath, contentLocale, i); 2016 } 2017 addEntityAttributes(cms, content, field.getPath() + "/", child, contentLocale, fieldsSet); 2018 } 2019 } 2020 } else { 2021 String elementPath = parentPath + getElementName(attribute.getAttributeName()); 2022 if (attribute.isSimpleValue()) { 2023 List<String> values = attribute.getSimpleValues(); 2024 for (int i = 0; i < values.size(); i++) { 2025 String value = values.get(i); 2026 I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i); 2027 if (field == null) { 2028 field = content.addValue(cms, elementPath, contentLocale, i); 2029 } 2030 field.setStringValue(cms, value); 2031 fieldsSet.add(field.getPath()); 2032 } 2033 } else { 2034 List<CmsEntity> entities = attribute.getComplexValues(); 2035 for (int i = 0; i < entities.size(); i++) { 2036 CmsEntity child = entities.get(i); 2037 I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i); 2038 if (field == null) { 2039 field = content.addValue(cms, elementPath, contentLocale, i); 2040 } 2041 addEntityAttributes(cms, content, field.getPath() + "/", child, contentLocale, fieldsSet); 2042 } 2043 } 2044 } 2045 } 2046 } 2047 } 2048 2049 /** 2050 * Adds the setting attributes according to the setting configuration.<p> 2051 * 2052 * @param attributeConfiguration the attribute configuration 2053 * @param settingsConfig the setting configuration 2054 * @param nestedFormatters the nested formatters 2055 * @param messages the messages 2056 * @param contentLocale the content locale 2057 * @param settingPresets the setting presets 2058 * 2059 * @return the list of names of added attributes 2060 */ 2061 private List<String> addSettingsAttributes( 2062 Map<String, CmsAttributeConfiguration> attributeConfiguration, 2063 Map<String, CmsXmlContentProperty> settingsConfig, 2064 List<I_CmsFormatterBean> nestedFormatters, 2065 CmsMessages messages, 2066 Locale contentLocale, 2067 Map<String, String> settingPresets) { 2068 2069 String attrName; 2070 List<String> attributes = new ArrayList<String>(); 2071 attributeConfiguration.put( 2072 SETTINGS_CLIENT_ID_ATTRIBUTE, 2073 new CmsAttributeConfiguration( 2074 "internal_client_id", 2075 "", 2076 null, 2077 null, 2078 null, 2079 null, 2080 DisplayType.none.name(), 2081 false, 2082 false, 2083 false)); 2084 for (Entry<String, CmsXmlContentProperty> entry : settingsConfig.entrySet()) { 2085 CmsXmlContentProperty prop = entry.getValue(); 2086 String niceName = prop.getNiceName(); 2087 if (CmsStringUtil.isEmptyOrWhitespaceOnly(niceName)) { 2088 niceName = prop.getName(); 2089 } 2090 attrName = getSettingsAttributeName(entry.getKey()); 2091 boolean visible = !CmsGwtConstants.HIDDEN_SETTINGS_WIDGET_NAME.equals(prop.getWidget()) 2092 && !settingPresets.containsKey(prop.getName()); 2093 if (visible) { 2094 attributes.add(attrName); 2095 } 2096 2097 attributeConfiguration.put( 2098 attrName, 2099 new CmsAttributeConfiguration( 2100 niceName, 2101 prop.getDescription(), 2102 getWidgetName(prop.getWidget()), 2103 getWidgetConfig(prop.getWidget(), prop.getWidgetConfiguration(), messages, contentLocale), 2104 prop.getDefault(), 2105 prop.getPlaceholder(), 2106 DisplayType.singleline.name(), 2107 visible, 2108 false, 2109 false)); 2110 } 2111 if (nestedFormatters != null) { 2112 for (I_CmsFormatterBean formatter : nestedFormatters) { 2113 attrName = getSettingsAttributeName(formatter.getId()); 2114 attributes.add(attrName); 2115 attributeConfiguration.put( 2116 attrName, 2117 new CmsAttributeConfiguration( 2118 formatter.getNiceName(m_workplaceLocale), 2119 "", 2120 null, 2121 null, 2122 null, 2123 null, 2124 DisplayType.none.name(), 2125 true, 2126 false, 2127 false)); 2128 2129 } 2130 } 2131 return attributes; 2132 } 2133 2134 /** 2135 * Adds the entity types according to the setting configuration.<p> 2136 * 2137 * @param entityType the entity base type name 2138 * @param types the types 2139 * @param settingsConfig the setting configuration 2140 * @param nestedFormatters the nested formatters 2141 */ 2142 private void addSettingsTypes( 2143 String entityType, 2144 Map<String, CmsType> types, 2145 Map<String, CmsXmlContentProperty> settingsConfig, 2146 List<I_CmsFormatterBean> nestedFormatters) { 2147 2148 CmsType baseType = types.get(entityType); 2149 CmsType settingType = new CmsType(SETTING_TYPE_NAME); 2150 types.put(settingType.getId(), settingType); 2151 baseType.addAttribute(SETTINGS_CLIENT_ID_ATTRIBUTE, settingType, 1, 1); 2152 // add main settings first 2153 for (Entry<String, CmsXmlContentProperty> entry : settingsConfig.entrySet()) { 2154 boolean nested = false; 2155 if (nestedFormatters != null) { 2156 for (I_CmsFormatterBean formatter : nestedFormatters) { 2157 if (entry.getKey().startsWith(formatter.getId())) { 2158 nested = true; 2159 break; 2160 } 2161 } 2162 } 2163 if (!nested) { 2164 baseType.addAttribute(getSettingsAttributeName(entry.getKey()), settingType, 0, 1); 2165 } 2166 } 2167 // add nested formatter settings after 2168 for (Entry<String, CmsXmlContentProperty> entry : settingsConfig.entrySet()) { 2169 if (nestedFormatters != null) { 2170 for (I_CmsFormatterBean formatter : nestedFormatters) { 2171 if (entry.getKey().startsWith(formatter.getId()) 2172 && !CmsGwtConstants.HIDDEN_SETTINGS_WIDGET_NAME.equals(entry.getValue().getWidget())) { 2173 CmsType parent = types.get(formatter.getId()); 2174 if (parent == null) { 2175 parent = new CmsType(formatter.getId()); 2176 types.put(parent.getId(), parent); 2177 baseType.addAttribute(getSettingsAttributeName(formatter.getId()), parent, 1, 1); 2178 } 2179 parent.addAttribute(getSettingsAttributeName(entry.getKey()), settingType, 0, 1); 2180 break; 2181 } 2182 } 2183 } 2184 } 2185 } 2186 2187 /** 2188 * Adds the settings values to the given entity.<p> 2189 * 2190 * @param entity the entity 2191 * @param containerElement the container element bean providing the values 2192 * @param nestedFormatters the nested formatters 2193 */ 2194 private void addSettingsValues( 2195 CmsEntity entity, 2196 CmsContainerElementBean containerElement, 2197 List<I_CmsFormatterBean> nestedFormatters) { 2198 2199 entity.addAttributeValue(SETTINGS_CLIENT_ID_ATTRIBUTE, containerElement.editorHash()); 2200 for (Entry<String, String> settingEntry : containerElement.getIndividualSettings().entrySet()) { 2201 boolean nested = false; 2202 if (nestedFormatters != null) { 2203 for (I_CmsFormatterBean formatter : nestedFormatters) { 2204 if (settingEntry.getKey().startsWith(formatter.getId())) { 2205 String nestedSettingAttributeName = getSettingsAttributeName(formatter.getId()); 2206 CmsEntity nestedEntity = null; 2207 CmsEntityAttribute attribute = entity.getAttribute(nestedSettingAttributeName); 2208 if (attribute != null) { 2209 nestedEntity = attribute.getComplexValue(); 2210 } else { 2211 nestedEntity = new CmsEntity(nestedSettingAttributeName + "[1]", formatter.getId()); 2212 entity.addAttributeValue(nestedSettingAttributeName, nestedEntity); 2213 } 2214 nestedEntity.addAttributeValue( 2215 getSettingsAttributeName(settingEntry.getKey()), 2216 settingEntry.getValue()); 2217 nested = true; 2218 break; 2219 } 2220 } 2221 } 2222 if (!nested) { 2223 entity.addAttributeValue(getSettingsAttributeName(settingEntry.getKey()), settingEntry.getValue()); 2224 } 2225 2226 } 2227 } 2228 2229 /** 2230 * Check if automatic content correction is required. Returns <code>true</code> if the content was changed.<p> 2231 * 2232 * @param cms the cms context 2233 * @param content the content to check 2234 * 2235 * @return <code>true</code> if the content was changed 2236 * @throws CmsXmlException if the automatic content correction failed 2237 */ 2238 private boolean checkAutoCorrection(CmsObject cms, CmsXmlContent content) throws CmsXmlException { 2239 2240 boolean performedAutoCorrection = content.isTransformedVersion(); 2241 try { 2242 content.validateXmlStructure(new CmsXmlEntityResolver(cms)); 2243 } catch (CmsXmlException eXml) { 2244 // validation failed 2245 content.setAutoCorrectionEnabled(true); 2246 content.correctXmlStructure(cms); 2247 performedAutoCorrection = true; 2248 } 2249 return performedAutoCorrection; 2250 } 2251 2252 /** 2253 * Creates a new resource to edit, delegating to an edit handler if edit handler data is passed in.<p> 2254 * 2255 * @param newLink A string, specifying where which new content should be created. 2256 * @param locale The locale for which the 2257 * @param referenceSitePath site path of the currently edited content. 2258 * @param modelFileSitePath site path of the model file 2259 * @param mode optional creation mode 2260 * @param postCreateHandler optional class name of an {@link I_CmsCollectorPostCreateHandler} which is invoked after the content has been created. 2261 * @param editHandlerData edit handler data (will be null if no edit handler is configured or the edit handler does not handle 'new') 2262 * 2263 * @return The site-path of the newly created resource. 2264 * @throws CmsException if something goes wrong 2265 */ 2266 private String createResourceToEdit( 2267 String newLink, 2268 Locale locale, 2269 String referenceSitePath, 2270 String modelFileSitePath, 2271 String mode, 2272 String postCreateHandler, 2273 CmsEditHandlerData editHandlerData) 2274 throws CmsException { 2275 2276 CmsObject cms = getCmsObject(); 2277 HttpServletRequest request = getRequest(); 2278 if (editHandlerData != null) { 2279 CmsContainerpageService containerpageService = new CmsContainerpageService(); 2280 containerpageService.setCms(cms); 2281 containerpageService.setRequest(request); 2282 CmsResource page = cms.readResource(editHandlerData.getPageContextId(), CmsResourceFilter.ALL); 2283 CmsContainerElementBean elementBean = containerpageService.getCachedElement( 2284 editHandlerData.getClientId(), 2285 page.getRootPath()); 2286 Map<String, String[]> params = CmsRequestUtil.createParameterMap( 2287 CmsEncoder.decode(editHandlerData.getRequestParams()), 2288 true, 2289 CmsEncoder.ENCODING_UTF_8); 2290 elementBean.initResource(cms); 2291 CmsResource elementResource = elementBean.getResource(); 2292 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(elementResource); 2293 if (type instanceof CmsResourceTypeXmlContent) { 2294 CmsResourceTypeXmlContent xmlType = (CmsResourceTypeXmlContent)type; 2295 I_CmsEditHandler handler = xmlType.getEditHandler(cms); 2296 if (handler != null) { 2297 return handler.handleNew( 2298 cms, 2299 newLink, 2300 locale, 2301 referenceSitePath, 2302 modelFileSitePath, 2303 postCreateHandler, 2304 elementBean, 2305 editHandlerData.getPageContextId(), 2306 params, 2307 editHandlerData.getOption()); 2308 2309 } else { 2310 LOG.warn( 2311 "Invalid state: edit handler data passed in, but edit handler is undefined. type = " 2312 + type.getTypeName()); 2313 } 2314 } else { 2315 LOG.warn("Invalid state: edit handler data passed in for non-XML content type."); 2316 } 2317 } 2318 return defaultCreateResourceToEdit( 2319 cms, 2320 newLink, 2321 locale, 2322 referenceSitePath, 2323 modelFileSitePath, 2324 mode, 2325 postCreateHandler); 2326 } 2327 2328 /** 2329 * Evaluates any wildcards in the given scope and returns all allowed permutations of it.<p> 2330 * 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 * @param scope the scope 2335 * @param definition the content definition 2336 * 2337 * @return the evaluate scope permutations 2338 */ 2339 private Set<String> evaluateScope(String scope, CmsXmlContentDefinition definition) { 2340 2341 Set<String> evaluatedScopes = new HashSet<String>(); 2342 if (scope.contains("*")) { 2343 // evaluate wildcards to get all allowed permutations of the scope 2344 // a path like Paragraph*/Image should result in Paragraph[0]/Image, Paragraph[1]/Image and Paragraph[2]/Image 2345 // in case max occurrence for Paragraph is 3 2346 2347 String[] pathElements = scope.split("/"); 2348 String parentPath = ""; 2349 2350 for (int i = 0; i < pathElements.length; i++) { 2351 String elementName = pathElements[i]; 2352 boolean hasWildCard = elementName.endsWith("*"); 2353 2354 if (hasWildCard) { 2355 elementName = elementName.substring(0, elementName.length() - 1); 2356 parentPath = CmsStringUtil.joinPaths(parentPath, elementName); 2357 I_CmsXmlSchemaType type = definition.getSchemaType(parentPath); 2358 Set<String> tempScopes = new HashSet<String>(); 2359 if (type.getMaxOccurs() == Integer.MAX_VALUE) { 2360 throw new IllegalStateException( 2361 "Can not use fields with unbounded maxOccurs in scopes for editor change handler."); 2362 } 2363 for (int j = 0; j < type.getMaxOccurs(); j++) { 2364 if (evaluatedScopes.isEmpty()) { 2365 tempScopes.add(elementName + "[" + (j + 1) + "]"); 2366 } else { 2367 2368 for (String evScope : evaluatedScopes) { 2369 tempScopes.add(CmsStringUtil.joinPaths(evScope, elementName + "[" + (j + 1) + "]")); 2370 } 2371 } 2372 } 2373 evaluatedScopes = tempScopes; 2374 } else { 2375 parentPath = CmsStringUtil.joinPaths(parentPath, elementName); 2376 Set<String> tempScopes = new HashSet<String>(); 2377 if (evaluatedScopes.isEmpty()) { 2378 tempScopes.add(elementName); 2379 } else { 2380 for (String evScope : evaluatedScopes) { 2381 tempScopes.add(CmsStringUtil.joinPaths(evScope, elementName)); 2382 } 2383 } 2384 evaluatedScopes = tempScopes; 2385 } 2386 } 2387 } else { 2388 evaluatedScopes.add(scope); 2389 } 2390 return evaluatedScopes; 2391 } 2392 2393 /** 2394 * Evaluates the values of the locale independent fields and the paths to skip during locale synchronization.<p> 2395 * 2396 * @param content the XML content 2397 * @param syncValues the map of synchronization values 2398 * @param skipPaths the list o paths to skip 2399 */ 2400 private void evaluateSyncLocaleValues( 2401 CmsXmlContent content, 2402 Map<String, String> syncValues, 2403 Collection<String> skipPaths) { 2404 2405 CmsObject cms = getCmsObject(); 2406 for (Locale locale : content.getLocales()) { 2407 for (String elementPath : content.getContentDefinition().getContentHandler().getSynchronizations( 2408 true).getSynchronizationPaths()) { 2409 for (I_CmsXmlContentValue contentValue : content.getSimpleValuesBelowPath(elementPath, locale)) { 2410 String valuePath = contentValue.getPath(); 2411 boolean skip = false; 2412 for (String skipPath : skipPaths) { 2413 if (valuePath.startsWith(skipPath)) { 2414 skip = true; 2415 break; 2416 } 2417 } 2418 if (!skip) { 2419 String value = contentValue.getStringValue(cms); 2420 if (syncValues.containsKey(valuePath)) { 2421 if (!syncValues.get(valuePath).equals(value)) { 2422 // in case the current value does not match the previously stored value, 2423 // remove it and add the parent path to the skipPaths list 2424 syncValues.remove(valuePath); 2425 int pathLevelDiff = (CmsResource.getPathLevel(valuePath) 2426 - CmsResource.getPathLevel(elementPath)) + 1; 2427 for (int i = 0; i < pathLevelDiff; i++) { 2428 valuePath = CmsXmlUtils.removeLastXpathElement(valuePath); 2429 } 2430 skipPaths.add(valuePath); 2431 } 2432 } else { 2433 syncValues.put(valuePath, value); 2434 } 2435 } 2436 } 2437 } 2438 } 2439 } 2440 2441 /** 2442 * Returns the change handler scopes.<p> 2443 * 2444 * @param definition the content definition 2445 * 2446 * @return the scopes 2447 */ 2448 private Set<String> getChangeHandlerScopes(CmsXmlContentDefinition definition) { 2449 2450 List<I_CmsXmlContentEditorChangeHandler> changeHandlers = definition.getContentHandler().getEditorChangeHandlers( 2451 false); 2452 Set<String> scopes = new HashSet<String>(); 2453 for (I_CmsXmlContentEditorChangeHandler handler : changeHandlers) { 2454 String scope = handler.getScope(); 2455 scopes.addAll(evaluateScope(scope, definition)); 2456 } 2457 return scopes; 2458 } 2459 2460 /** 2461 * Returns the active content augmentation, if any - otherwise returns null. 2462 * 2463 * @param cms the CMS context 2464 * @param resource the edited resource 2465 * @param config the active sitemap config 2466 * 2467 * @return the content augmentation, or null if none is active 2468 */ 2469 private I_CmsXmlContentAugmentation getContentAugmentation( 2470 CmsObject cms, 2471 CmsResource resource, 2472 CmsADEConfigData config) { 2473 2474 String jsp = config.getAttribute("xml.content.augmentation.jsp", null); 2475 2476 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(jsp)) { 2477 jsp = jsp.trim(); 2478 return new CmsJspContentAugmentation(jsp); 2479 } else { 2480 String className = config.getAttribute("xml.content.augmentation.class", null); 2481 if (className != null) { 2482 className = className.trim(); 2483 try { 2484 return (I_CmsXmlContentAugmentation)(Class.forName(className).getConstructor().newInstance()); 2485 } catch (Exception e) { 2486 LOG.error(e.getLocalizedMessage(), e); 2487 } 2488 } 2489 return null; 2490 } 2491 } 2492 2493 /** 2494 * Returns the XML content document.<p> 2495 * 2496 * @param file the resource file 2497 * @param fromCache <code>true</code> to use the cached document 2498 * 2499 * @return the content document 2500 * 2501 * @throws CmsXmlException if reading the XML fails 2502 */ 2503 private CmsXmlContent getContentDocument(CmsFile file, boolean fromCache) throws CmsXmlException { 2504 2505 CmsXmlContent content = null; 2506 if (fromCache) { 2507 content = getSessionCache().getCacheXmlContent(file.getStructureId()); 2508 } 2509 if (content == null) { 2510 content = CmsXmlContentFactory.unmarshal(getCmsObject(), file); 2511 getSessionCache().setCacheXmlContent(file.getStructureId(), content); 2512 } 2513 CmsContentTypeVisitor visitor = new CmsContentTypeVisitor(getCmsObject(), file, Locale.ENGLISH); 2514 visitor.visitTypes(content.getContentDefinition(), Locale.ENGLISH); 2515 CmsDynamicCategoryFieldList dynCatFields = visitor.getOptionalDynamicCategoryFields(); 2516 dynCatFields.ensureFields(getCmsObject(), content); 2517 return content; 2518 } 2519 2520 /** 2521 * Returns the value that has to be set for the dynamic attribute. 2522 * 2523 * @param file the file where the current content is stored 2524 * @param value the content value that is represented by the attribute 2525 * @param attributeName the attribute's name 2526 * @param editedLocalEntity the entities that where edited last 2527 * @return the value that has to be set for the dynamic attribute. 2528 */ 2529 private String getDynamicAttributeValue( 2530 CmsFile file, 2531 I_CmsXmlContentValue value, 2532 String attributeName, 2533 CmsEntity editedLocalEntity) { 2534 2535 if ((null != editedLocalEntity) && (editedLocalEntity.getAttribute(attributeName) != null)) { 2536 getSessionCache().setDynamicValue( 2537 attributeName, 2538 editedLocalEntity.getAttribute(attributeName).getSimpleValue()); 2539 } 2540 String currentValue = getSessionCache().getDynamicValue(attributeName); 2541 if (null != currentValue) { 2542 return currentValue; 2543 } 2544 if (null != file) { 2545 if (value.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME)) { 2546 List<CmsCategory> categories = new ArrayList<CmsCategory>(0); 2547 try { 2548 categories = CmsCategoryService.getInstance().readResourceCategories(getCmsObject(), file); 2549 } catch (CmsException e) { 2550 LOG.error(Messages.get().getBundle().key(Messages.ERROR_FAILED_READING_CATEGORIES_1), e); 2551 } 2552 I_CmsWidget widget = null; 2553 widget = CmsWidgetUtil.collectWidgetInfo(getCmsObject(), value).getWidget(); 2554 if ((null != widget) && (widget instanceof CmsCategoryWidget)) { 2555 String mainCategoryPath = ((CmsCategoryWidget)widget).getStartingCategory( 2556 getCmsObject(), 2557 getCmsObject().getSitePath(file)); 2558 StringBuffer pathes = new StringBuffer(); 2559 for (CmsCategory category : categories) { 2560 if (category.getPath().startsWith(mainCategoryPath)) { 2561 String path = category.getBasePath() + category.getPath(); 2562 path = getCmsObject().getRequestContext().removeSiteRoot(path); 2563 pathes.append(path).append(','); 2564 } 2565 } 2566 String dynamicConfigString = pathes.length() > 0 ? pathes.substring(0, pathes.length() - 1) : ""; 2567 getSessionCache().setDynamicValue(attributeName, dynamicConfigString); 2568 return dynamicConfigString; 2569 } 2570 } else if (value.getTypeName().equals(CmsXmlAccessRestrictionValue.TYPE_NAME)) { 2571 CmsAccessRestrictionInfo restrictionInfo = CmsAccessRestrictionInfo.getRestrictionInfo( 2572 getCmsObject(), 2573 value.getDocument().getContentDefinition()); 2574 if (restrictionInfo != null) { 2575 try { 2576 List<CmsAccessControlEntry> aces = getCmsObject().getAccessControlEntries( 2577 getCmsObject().getSitePath(value.getDocument().getFile())); 2578 boolean hasEntry = aces.stream().anyMatch( 2579 ace -> ace.getPrincipal().equals(restrictionInfo.getGroup().getId()) 2580 && ace.isResponsible()); 2581 return "" + hasEntry; 2582 } catch (CmsException e) { 2583 LOG.error(e.getLocalizedMessage(), e); 2584 } 2585 return "false"; 2586 } 2587 } 2588 } 2589 return ""; 2590 2591 } 2592 2593 /** 2594 * Returns the formatter configuration for the given container element bean.<p> 2595 * 2596 * @param containerElement the container element 2597 * 2598 * @return the formatter configuration 2599 */ 2600 private I_CmsFormatterBean getFormatterForElement( 2601 CmsADEConfigData config, 2602 CmsContainerElementBean containerElement) { 2603 2604 if ((containerElement != null) 2605 && (containerElement.getFormatterId() != null) 2606 && !containerElement.getFormatterId().isNullUUID()) { 2607 CmsUUID formatterId = containerElement.getFormatterId(); 2608 2609 // find formatter config setting 2610 for (Entry<String, String> settingEntry : containerElement.getIndividualSettings().entrySet()) { 2611 if (settingEntry.getKey().startsWith(CmsFormatterConfig.FORMATTER_SETTINGS_KEY)) { 2612 String formatterConfigId = settingEntry.getValue(); 2613 I_CmsFormatterBean dynamicFmt = config.findFormatter(formatterConfigId); 2614 if ((dynamicFmt != null) && dynamicFmt.getJspStructureId().equals(formatterId)) { 2615 return dynamicFmt; 2616 } 2617 } 2618 } 2619 } 2620 return null; 2621 } 2622 2623 /** 2624 * Returns the path elements for the given content value.<p> 2625 * 2626 * @param content the XML content 2627 * @param value the content value 2628 * 2629 * @return the path elements 2630 */ 2631 private String[] getPathElements(CmsXmlContent content, I_CmsXmlContentValue value) { 2632 2633 List<String> pathElements = new ArrayList<String>(); 2634 String[] paths = value.getPath().split("/"); 2635 String path = ""; 2636 for (int i = 0; i < paths.length; i++) { 2637 path += paths[i]; 2638 I_CmsXmlContentValue ancestor = content.getValue(path, value.getLocale()); 2639 int valueIndex = ancestor.getXmlIndex(); 2640 if (ancestor.isChoiceOption()) { 2641 Element parent = ancestor.getElement().getParent(); 2642 valueIndex = parent.indexOf(ancestor.getElement()); 2643 } 2644 String pathElement = getAttributeName(ancestor); 2645 pathElements.add(pathElement + "[" + valueIndex + "]"); 2646 if (ancestor.isChoiceType()) { 2647 pathElements.add("ATTRIBUTE_CHOICE"); 2648 } 2649 path += "/"; 2650 } 2651 return pathElements.toArray(new String[pathElements.size()]); 2652 } 2653 2654 /** 2655 * Returns the session cache.<p> 2656 * 2657 * @return the session cache 2658 */ 2659 private CmsADESessionCache getSessionCache() { 2660 2661 if (m_sessionCache == null) { 2662 m_sessionCache = CmsADESessionCache.getCache(getRequest(), getCmsObject()); 2663 } 2664 return m_sessionCache; 2665 } 2666 2667 /** 2668 * Returns the attribute name to use for the given setting name.<p> 2669 * 2670 * @param settingName the setting name 2671 * 2672 * @return the attribute name 2673 */ 2674 private String getSettingsAttributeName(String settingName) { 2675 2676 return "/" + SETTINGS_ATTRIBUTE_NAME_PREFIX + settingName; 2677 } 2678 2679 /** 2680 * Returns information on validation issues, sorted by locale and in the order the issues appear in the editor. 2681 * @param cms the current context 2682 * @param content the content with the issues 2683 * @param validationResult the validation result 2684 * @return information on validation issues, sorted by locale and in the order the issues appear in the editor. 2685 */ 2686 private Map<String, List<CmsPair<List<CmsPair<String, Integer>>, String>>> getValidationIssues( 2687 CmsObject cms, 2688 CmsXmlContent content, 2689 CmsValidationResult validationResult) { 2690 2691 // only if we have warnings or errors, we can display them. 2692 if (validationResult.hasErrors() || validationResult.hasWarnings()) { 2693 // if we have errors, we display errors and warnings mixed. 2694 Map<String, Map<String[], CmsPair<String, String>>> issues = new HashMap<>(); 2695 if (validationResult.hasErrors()) { 2696 validationResult.getErrors().entrySet().forEach( 2697 e -> issues.put(CmsContentDefinition.getLocaleFromId(e.getKey()), e.getValue())); 2698 } 2699 if (validationResult.hasWarnings()) { 2700 validationResult.getWarnings().entrySet().forEach(e -> { 2701 String locale = CmsContentDefinition.getLocaleFromId(e.getKey()); 2702 if (issues.containsKey(locale)) { 2703 // We assume we cannot have a warning and error at the same time, so we can override here. 2704 issues.get(locale).putAll(e.getValue()); 2705 } else { 2706 issues.put(locale, e.getValue()); 2707 } 2708 }); 2709 } 2710 // we use a tree map to sort the locales alphabetically 2711 TreeMap<String, List<CmsPair<List<CmsPair<String, Integer>>, String>>> sortedInfoPerLocale = new TreeMap<>(); 2712 CmsXmlContentDefinition definition = content.getContentDefinition(); 2713 for (Entry<String, Map<String[], CmsPair<String, String>>> e : issues.entrySet()) { 2714 Locale l = CmsLocaleManager.getLocale(e.getKey()); 2715 // map from XML path to attributes as provided by the validation result 2716 Map<String, CmsPair<String[], String>> errorsByPath = new HashMap<>(e.getValue().size()); 2717 // fill the map 2718 e.getValue().entrySet().stream().forEach( 2719 v -> errorsByPath.put( 2720 v.getValue().getSecond(), 2721 new CmsPair<>(v.getKey(), v.getValue().getFirst()))); 2722 // get the paths and sort them 2723 List<String> sortedPaths = new ArrayList<>(errorsByPath.keySet()); 2724 sortedPaths.sort(new CmsXmlDisplayOrderPathComparator(definition)); 2725 // the infos for the locale, a list of issue information, where each issue information 2726 // is a list of the path part attributes combined with the index of the path part. 2727 List<CmsPair<List<CmsPair<String, Integer>>, String>> sortedInfos = new ArrayList<>( 2728 errorsByPath.size()); 2729 // fill the info list in the correct sort order 2730 for (String p : sortedPaths) { 2731 CmsPair<String[], String> v = errorsByPath.get(p); 2732 String[] entities = v.getFirst(); 2733 List<CmsPair<String, Integer>> singleInfo = new ArrayList<>(entities.length); 2734 String attributePrefix = ""; 2735 for (int i = 0; i < entities.length; i++) { 2736 String w = entities[i]; 2737 if (!"ATTRIBUTE_CHOICE".equals(w)) { 2738 String attr = org.opencms.acacia.shared.CmsContentDefinition.removeIndex(w); 2739 int idx = org.opencms.acacia.shared.CmsContentDefinition.extractIndex(w); 2740 if (!attributePrefix.isEmpty()) { 2741 attr = attributePrefix + ":" + attr; 2742 } 2743 singleInfo.add(new CmsPair<>(attr, Integer.valueOf(idx + 1))); 2744 attributePrefix += attr.substring(attr.lastIndexOf('/')); 2745 } 2746 } 2747 sortedInfos.add(new CmsPair<>(singleInfo, v.getSecond())); 2748 } 2749 2750 sortedInfoPerLocale.put(l.getDisplayName(getWorkplaceLocale(cms)), sortedInfos); 2751 } 2752 return sortedInfoPerLocale; 2753 } 2754 return null; 2755 } 2756 2757 /** 2758 * Transforms the widget configuration.<p> 2759 * @param settingsWidget the settings widget name 2760 * @param settingsConfig the setting widget configuration 2761 * @param messages the workplace messages 2762 * @param contentLocale the content locale 2763 * 2764 * @return the client widget configuration string 2765 */ 2766 private String getWidgetConfig( 2767 String settingsWidget, 2768 String settingsConfig, 2769 CmsMessages messages, 2770 Locale contentLocale) { 2771 2772 Class<? extends I_CmsADEWidget> widgetClass = WIDGET_MAPPINGS.get(settingsWidget); 2773 String config = ""; 2774 if (widgetClass == null) { 2775 widgetClass = CmsInputWidget.class; 2776 } 2777 try { 2778 I_CmsADEWidget widget = widgetClass.newInstance(); 2779 widget.setConfiguration(settingsConfig); 2780 config = widget.getConfiguration(getCmsObject(), null, messages, null, contentLocale); 2781 } catch (Exception e) { 2782 LOG.error(e.getLocalizedMessage(), e); 2783 } 2784 return config; 2785 } 2786 2787 /** 2788 * Returns the widget class name.<p> 2789 * 2790 * @param settingsWidget the settings widget name 2791 * 2792 * @return the widget class name 2793 */ 2794 private String getWidgetName(String settingsWidget) { 2795 2796 if (WIDGET_MAPPINGS.containsKey(settingsWidget)) { 2797 return WIDGET_MAPPINGS.get(settingsWidget).getName(); 2798 } else { 2799 return CmsInputWidget.class.getName(); 2800 } 2801 } 2802 2803 /** 2804 * Returns the workplace locale.<p> 2805 * 2806 * @param cms the current OpenCms context 2807 * 2808 * @return the current users workplace locale 2809 */ 2810 private Locale getWorkplaceLocale(CmsObject cms) { 2811 2812 if (m_workplaceLocale == null) { 2813 m_workplaceLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 2814 } 2815 return m_workplaceLocale; 2816 } 2817 2818 /** 2819 * Checks whether the given attribute name indicates it is a settings attribute.<p> 2820 * 2821 * @param attributeName the attribute name 2822 * 2823 * @return <code>true</code> in case of settings attributes 2824 */ 2825 private boolean isSettingsAttribute(String attributeName) { 2826 2827 return attributeName.startsWith("/" + SETTINGS_ATTRIBUTE_NAME_PREFIX); 2828 } 2829 2830 private CmsADEConfigData readConfig(CmsUUID pageId) { 2831 2832 if (pageId == null) { 2833 return null; 2834 } 2835 try { 2836 CmsResource resource = getCmsObject().readResource(pageId, CmsResourceFilter.IGNORE_EXPIRATION); 2837 return OpenCms.getADEManager().lookupConfiguration(getCmsObject(), resource.getRootPath()); 2838 } catch (CmsException e) { 2839 LOG.warn(e.getLocalizedMessage(), e); 2840 return null; 2841 } 2842 2843 } 2844 2845 /** 2846 * Reads the content definition for the given resource and locale.<p> 2847 * 2848 * @param file the resource file 2849 * @param content the XML content 2850 * @param entityId the entity id 2851 * @param clientId the container element client id if available 2852 * @param locale the content locale 2853 * @param newLocale if the locale content should be created as new 2854 * @param mainLocale the main language to copy in case the element language node does not exist yet 2855 * @param editedLocaleEntity the edited locale entity 2856 * @param settingPresets the presets for settings 2857 * @param configData the sitemap configuration to use 2858 * 2859 * @return the content definition 2860 * 2861 * @throws CmsException if something goes wrong 2862 */ 2863 private CmsContentDefinition readContentDefinition( 2864 CmsFile file, 2865 CmsXmlContent content, 2866 String entityId, 2867 String clientId, 2868 Locale locale, 2869 boolean newLocale, 2870 Locale mainLocale, 2871 CmsEntity editedLocaleEntity, 2872 Map<String, String> settingPresets, 2873 CmsADEConfigData configData) 2874 throws CmsException { 2875 2876 long timer = 0; 2877 if (LOG.isDebugEnabled()) { 2878 timer = System.currentTimeMillis(); 2879 } 2880 CmsObject cms = getCmsObject(); 2881 String lookupPath = cms.getRequestContext().getRootUri(); 2882 if (lookupPath.startsWith("/system/workplace/") && (file != null)) { 2883 lookupPath = file.getRootPath(); 2884 } 2885 CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(cms, lookupPath); 2886 List<Locale> availableLocalesList = OpenCms.getLocaleManager().getAvailableLocales(cms, file); 2887 if (!availableLocalesList.contains(locale)) { 2888 availableLocalesList.retainAll(content.getLocales()); 2889 List<Locale> defaultLocales = OpenCms.getLocaleManager().getDefaultLocales(cms, file); 2890 Locale replacementLocale = OpenCms.getLocaleManager().getBestMatchingLocale( 2891 locale, 2892 defaultLocales, 2893 availableLocalesList); 2894 LOG.info( 2895 "Can't edit locale " 2896 + locale 2897 + " of file " 2898 + file.getRootPath() 2899 + " because it is not configured as available locale. Using locale " 2900 + replacementLocale 2901 + " instead."); 2902 locale = replacementLocale; 2903 entityId = CmsContentDefinition.uuidToEntityId(file.getStructureId(), locale.toString()); 2904 } 2905 2906 if (CmsStringUtil.isEmptyOrWhitespaceOnly(entityId)) { 2907 entityId = CmsContentDefinition.uuidToEntityId(file.getStructureId(), locale.toString()); 2908 } 2909 boolean performedAutoCorrection = checkAutoCorrection(cms, content); 2910 if (performedAutoCorrection) { 2911 content.initDocument(); 2912 } 2913 if (LOG.isDebugEnabled()) { 2914 LOG.debug( 2915 Messages.get().getBundle().key( 2916 Messages.LOG_TAKE_UNMARSHALING_TIME_1, 2917 "" + (System.currentTimeMillis() - timer))); 2918 } 2919 CmsContentTypeVisitor visitor = new CmsContentTypeVisitor(cms, file, locale); 2920 if (LOG.isDebugEnabled()) { 2921 timer = System.currentTimeMillis(); 2922 } 2923 visitor.visitTypes(content.getContentDefinition(), getWorkplaceLocale(cms)); 2924 if (LOG.isDebugEnabled()) { 2925 LOG.debug( 2926 Messages.get().getBundle().key( 2927 Messages.LOG_TAKE_VISITING_TYPES_TIME_1, 2928 "" + (System.currentTimeMillis() - timer))); 2929 } 2930 CmsEntity entity = null; 2931 Map<String, String> syncValues = new HashMap<String, String>(); 2932 Collection<String> skipPaths = new HashSet<String>(); 2933 evaluateSyncLocaleValues(content, syncValues, skipPaths); 2934 if (content.hasLocale(locale) && newLocale) { 2935 // a new locale is requested, so remove the present one 2936 content.removeLocale(locale); 2937 } 2938 if (!content.hasLocale(locale)) { 2939 if ((mainLocale != null) && content.hasLocale(mainLocale)) { 2940 content.copyLocale(mainLocale, locale); 2941 } else { 2942 content.addLocale(cms, locale); 2943 } 2944 // sync the locale values 2945 if (!visitor.getLocaleSynchronizations().isEmpty() && (content.getLocales().size() > 1)) { 2946 for (Locale contentLocale : content.getLocales()) { 2947 if (!contentLocale.equals(locale)) { 2948 content.synchronizeLocaleIndependentValues(cms, skipPaths, contentLocale); 2949 } 2950 } 2951 } 2952 } 2953 visitor.getOptionalDynamicCategoryFields().ensureFields(cms, content, locale); 2954 Element element = content.getLocaleNode(locale); 2955 if (LOG.isDebugEnabled()) { 2956 timer = System.currentTimeMillis(); 2957 } 2958 entity = readEntity( 2959 content, 2960 element, 2961 locale, 2962 entityId, 2963 "", 2964 getTypeUri(content.getContentDefinition()), 2965 visitor, 2966 false, 2967 editedLocaleEntity); 2968 2969 if (LOG.isDebugEnabled()) { 2970 LOG.debug( 2971 Messages.get().getBundle().key( 2972 Messages.LOG_TAKE_READING_ENTITY_TIME_1, 2973 "" + (System.currentTimeMillis() - timer))); 2974 } 2975 List<String> contentLocales = new ArrayList<String>(); 2976 for (Locale contentLocale : content.getLocales()) { 2977 contentLocales.add(contentLocale.toString()); 2978 } 2979 Locale workplaceLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 2980 LinkedHashMap<String, String> availableLocales = new LinkedHashMap<String, String>(); 2981 for (Locale availableLocale : OpenCms.getLocaleManager().getAvailableLocales(cms, file)) { 2982 availableLocales.put(availableLocale.toString(), availableLocale.getDisplayName(workplaceLocale)); 2983 } 2984 String title = cms.readPropertyObject(file, CmsPropertyDefinition.PROPERTY_TITLE, false).getValue(); 2985 try { 2986 CmsGallerySearchResult searchResult = CmsGallerySearch.searchById(cms, file.getStructureId(), locale); 2987 title = searchResult.getTitle(); 2988 } catch (CmsException e) { 2989 LOG.warn(e.getLocalizedMessage(), e); 2990 } 2991 String typeName = OpenCms.getResourceManager().getResourceType(file.getTypeId()).getTypeName(); 2992 boolean autoUnlock = OpenCms.getWorkplaceManager().shouldAcaciaUnlock(); 2993 Map<String, CmsEntity> entities = new HashMap<String, CmsEntity>(); 2994 entities.put(entityId, entity); 2995 2996 Map<String, CmsAttributeConfiguration> attrConfig = visitor.getAttributeConfigurations(); 2997 Map<String, CmsType> types = visitor.getTypes(); 2998 List<CmsTabInfo> tabInfos = visitor.getTabInfos(); 2999 3000 if (clientId != null) { 3001 CmsContainerElementBean containerElement = getSessionCache().getCacheContainerElement(clientId); 3002 I_CmsFormatterBean formatter = getFormatterForElement(configData, containerElement); 3003 if ((formatter != null) 3004 && formatter.isAllowsSettingsInEditor() 3005 && !formatter.getSettings(config).isEmpty()) { 3006 Map<String, CmsXmlContentProperty> settingsConfig = OpenCms.getADEManager().getFormatterSettings( 3007 cms, 3008 config, 3009 formatter, 3010 containerElement.getResource(), 3011 locale, 3012 getRequest()); 3013 com.google.common.base.Supplier<CmsXmlContent> contentSupplier = Suppliers.memoize(() -> { 3014 try { 3015 return CmsXmlContentFactory.unmarshal(cms, cms.readFile(containerElement.getResource())); 3016 } catch (CmsException e) { 3017 LOG.error(e.getLocalizedMessage(), e); 3018 return null; 3019 } 3020 }); 3021 settingsConfig = CmsXmlContentPropertyHelper.resolveMacrosForPropertyInfo( 3022 cms, 3023 null, 3024 containerElement.getResource(), 3025 contentSupplier, 3026 CmsElementUtil.createStringTemplateSource(formatter, contentSupplier), 3027 settingsConfig); 3028 CmsMessages messages = OpenCms.getWorkplaceManager().getMessages(m_workplaceLocale); 3029 List<I_CmsFormatterBean> nestedFormatters = formatter.hasNestedFormatterSettings() 3030 ? OpenCms.getADEManager().getNestedFormatters( 3031 cms, 3032 config, 3033 containerElement.getResource(), 3034 locale, 3035 getRequest()) 3036 : Collections.emptyList(); 3037 String firstContentAttributeName = types.get( 3038 entity.getTypeName()).getAttributeNames().iterator().next(); 3039 List<String> addedVisibleAttrs = addSettingsAttributes( 3040 attrConfig, 3041 settingsConfig, 3042 nestedFormatters, 3043 messages, 3044 locale, 3045 settingPresets); 3046 addSettingsTypes(entity.getTypeName(), types, settingsConfig, nestedFormatters); 3047 if (editedLocaleEntity != null) { 3048 transferSettingValues(editedLocaleEntity, entity); 3049 } else { 3050 addSettingsValues(entity, containerElement, nestedFormatters); 3051 } 3052 if (tabInfos.isEmpty()) { 3053 tabInfos.add( 3054 new CmsTabInfo( 3055 Messages.get().getBundle(workplaceLocale).key(Messages.GUI_CONTENT_TAB_LABEL_0), 3056 Messages.GUI_CONTENT_TAB_LABEL_0, 3057 Messages.GUI_CONTENT_TAB_LABEL_0, 3058 "content", 3059 firstContentAttributeName.substring(entity.getTypeName().length() + 1), 3060 false, 3061 null, 3062 null, 3063 null)); 3064 } 3065 if (addedVisibleAttrs.size() > 0) { 3066 tabInfos.add( 3067 new CmsTabInfo( 3068 Messages.get().getBundle(workplaceLocale).key(Messages.GUI_SETTINGS_TAB_LABEL_0), 3069 Messages.GUI_SETTINGS_TAB_LABEL_0, 3070 Messages.GUI_SETTINGS_TAB_LABEL_0, 3071 CmsContentDefinition.SETTINGS_TAB_ID, 3072 CmsFileUtil.removeLeadingSeparator(addedVisibleAttrs.iterator().next()), 3073 false, 3074 Messages.get().getBundle(workplaceLocale).key(Messages.GUI_SETTINGS_TAB_DESCRIPTION_0), 3075 null, 3076 null)); 3077 } 3078 } 3079 3080 } 3081 3082 CmsContentDefinition result = new CmsContentDefinition( 3083 entityId, 3084 entities, 3085 visitor.getAttributeConfigurations(), 3086 visitor.getWidgetConfigurations(), 3087 visitor.getComplexWidgetData(), 3088 visitor.getTypes(), 3089 visitor.getTabInfos(), 3090 locale.toString(), 3091 contentLocales, 3092 availableLocales, 3093 visitor.getLocaleSynchronizations(), 3094 syncValues, 3095 skipPaths, 3096 title, 3097 cms.getSitePath(file), 3098 typeName, 3099 CmsIconUtil.getIconClasses(CmsIconUtil.getDisplayType(cms, file), file.getName(), false), 3100 performedAutoCorrection, 3101 autoUnlock, 3102 getChangeHandlerScopes(content.getContentDefinition())); 3103 I_CmsContentTranslator translator = OpenCms.getWorkplaceManager().getContentTranslation(); 3104 boolean translationEnabled = translator.isEnabled(cms, config, file); 3105 try { 3106 if (OpenCms.getResourceManager().getResourceType(file) instanceof CmsResourceTypeXmlAdeConfiguration) { 3107 translationEnabled = false; 3108 } 3109 } catch (Exception e) { 3110 LOG.warn(e.getLocalizedMessage(), e); 3111 } 3112 result.setTranslationEnabled(translationEnabled); 3113 return result; 3114 } 3115 3116 /** 3117 * Creates a new resource according to the new link, or returns the model file informations 3118 * modelFileId is <code>null</code> but required.<p> 3119 * 3120 * @param newLink the new link 3121 * @param referenceResource the reference resource 3122 * @param modelFileId the model file structure id 3123 * @param locale the content locale 3124 * @param mode the content creation mode 3125 * @param postCreateHandler the class name for the post-create handler 3126 * @param editHandlerData the edit handler data, in case the 'new' function is handled by an edit handler 3127 * @param settingPresets the presets for settings 3128 * 3129 * @return the content definition 3130 * 3131 * @throws CmsException if creating the resource failed 3132 */ 3133 private CmsContentDefinition readContentDefinitionForNew( 3134 String newLink, 3135 CmsResource referenceResource, 3136 CmsUUID modelFileId, 3137 Locale locale, 3138 String mode, 3139 String postCreateHandler, 3140 CmsEditHandlerData editHandlerData, 3141 Map<String, String> settingPresets) 3142 throws CmsException { 3143 3144 String sitePath = getCmsObject().getSitePath(referenceResource); 3145 String resourceType; 3146 if (newLink.startsWith(CmsJspTagEdit.NEW_LINK_IDENTIFIER)) { 3147 resourceType = CmsJspTagEdit.getTypeFromNewLink(newLink); 3148 } else { 3149 resourceType = OpenCms.getResourceManager().getResourceType(referenceResource.getTypeId()).getTypeName(); 3150 } 3151 String modelFile = null; 3152 if (modelFileId == null) { 3153 List<CmsResource> modelResources = CmsResourceTypeXmlContent.getModelFiles( 3154 getCmsObject(), 3155 CmsResource.getFolderPath(sitePath), 3156 resourceType); 3157 if (!modelResources.isEmpty()) { 3158 List<CmsModelResourceInfo> modelInfos = CmsContainerpageService.generateModelResourceList( 3159 getCmsObject(), 3160 resourceType, 3161 modelResources, 3162 locale); 3163 return new CmsContentDefinition( 3164 modelInfos, 3165 newLink, 3166 referenceResource.getStructureId(), 3167 locale.toString()); 3168 } 3169 } else if (!modelFileId.isNullUUID()) { 3170 modelFile = getCmsObject().getSitePath( 3171 getCmsObject().readResource(modelFileId, CmsResourceFilter.IGNORE_EXPIRATION)); 3172 } 3173 String newFileName = createResourceToEdit( 3174 newLink, 3175 locale, 3176 sitePath, 3177 modelFile, 3178 mode, 3179 postCreateHandler, 3180 editHandlerData); 3181 CmsResource resource = getCmsObject().readResource(newFileName, CmsResourceFilter.IGNORE_EXPIRATION); 3182 CmsFile file = getCmsObject().readFile(resource); 3183 CmsXmlContent content = getContentDocument(file, false); 3184 CmsContentDefinition contentDefinition = readContentDefinition( 3185 file, 3186 content, 3187 null, 3188 null, 3189 locale, 3190 false, 3191 null, 3192 null, 3193 settingPresets, 3194 null); 3195 contentDefinition.setDeleteOnCancel(true); 3196 return contentDefinition; 3197 } 3198 3199 /** 3200 * Stores the settings attributes to the container element bean persisted in the session cache.<p> 3201 * The container page editor will write the container page XML.<p> 3202 * 3203 * @param entity the entity 3204 * @param containerElement the container element 3205 * @param settingsConfig the settings configuration 3206 * @param nestedFormatters the nested formatters 3207 * 3208 * @return <code>true</code> in case any changed settings where saved 3209 */ 3210 @SuppressWarnings("null") 3211 private boolean saveSettings( 3212 CmsEntity entity, 3213 CmsContainerElementBean containerElement, 3214 Map<String, CmsXmlContentProperty> settingsConfig, 3215 List<I_CmsFormatterBean> nestedFormatters) { 3216 3217 boolean hasChangedSettings = false; 3218 Map<String, String> values = new HashMap<>(containerElement.getIndividualSettings()); 3219 for (Entry<String, CmsXmlContentProperty> settingsEntry : settingsConfig.entrySet()) { 3220 String value = null; 3221 boolean nested = false; 3222 if (nestedFormatters != null) { 3223 for (I_CmsFormatterBean formatter : nestedFormatters) { 3224 if (settingsEntry.getKey().startsWith(formatter.getId())) { 3225 3226 CmsEntity nestedEntity = null; 3227 CmsEntityAttribute attribute = entity.getAttribute(getSettingsAttributeName(formatter.getId())); 3228 if (attribute != null) { 3229 nestedEntity = attribute.getComplexValue(); 3230 CmsEntityAttribute valueAttribute = nestedEntity.getAttribute( 3231 getSettingsAttributeName(settingsEntry.getKey())); 3232 if (valueAttribute != null) { 3233 value = valueAttribute.getSimpleValue(); 3234 } 3235 } 3236 nested = true; 3237 break; 3238 } 3239 } 3240 } 3241 if (!nested) { 3242 CmsEntityAttribute valueAttribute = entity.getAttribute( 3243 getSettingsAttributeName(settingsEntry.getKey())); 3244 if (valueAttribute != null) { 3245 value = valueAttribute.getSimpleValue(); 3246 } 3247 } 3248 if (CmsStringUtil.isEmptyOrWhitespaceOnly(value) 3249 && !CmsGwtConstants.HIDDEN_SETTINGS_WIDGET_NAME.equals(settingsEntry.getValue().getWidget()) 3250 && values.containsKey(settingsEntry.getKey())) { 3251 values.remove(settingsEntry.getKey()); 3252 hasChangedSettings = true; 3253 } else if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value) 3254 && !CmsGwtConstants.HIDDEN_SETTINGS_WIDGET_NAME.equals(settingsEntry.getValue().getWidget()) 3255 && !value.equals(values.get(settingsEntry.getKey()))) { 3256 values.put(settingsEntry.getKey(), value); 3257 hasChangedSettings = true; 3258 } 3259 } 3260 if (hasChangedSettings) { 3261 containerElement.updateIndividualSettings(values); 3262 getSessionCache().setCacheContainerElement(containerElement.editorHash(), containerElement); 3263 } 3264 return hasChangedSettings; 3265 } 3266 3267 /** 3268 * Synchronizes the locale independent fields for the given entity.<p> 3269 * 3270 * @param file the content file 3271 * @param content the XML content 3272 * @param skipPaths the paths to skip during locale synchronization 3273 * @param entity the entity 3274 * 3275 * @throws CmsXmlException if something goes wrong 3276 */ 3277 private void synchronizeLocaleIndependentForEntity( 3278 CmsFile file, 3279 CmsXmlContent content, 3280 Collection<String> skipPaths, 3281 CmsEntity entity) 3282 throws CmsXmlException { 3283 3284 CmsObject cms = getCmsObject(); 3285 String entityId = entity.getId(); 3286 Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId)); 3287 CmsContentTypeVisitor visitor = null; 3288 CmsEntity originalEntity = null; 3289 if (content.getHandler().hasVisibilityHandlers()) { 3290 visitor = new CmsContentTypeVisitor(cms, file, contentLocale); 3291 visitor.visitTypes(content.getContentDefinition(), getWorkplaceLocale(cms)); 3292 } 3293 if (content.hasLocale(contentLocale)) { 3294 if ((visitor != null) && visitor.hasInvisibleFields()) { 3295 // we need to add invisible content values to the entity before saving 3296 Element element = content.getLocaleNode(contentLocale); 3297 originalEntity = readEntity( 3298 content, 3299 element, 3300 contentLocale, 3301 entityId, 3302 "", 3303 getTypeUri(content.getContentDefinition()), 3304 visitor, 3305 true, 3306 entity); 3307 } 3308 content.removeLocale(contentLocale); 3309 } 3310 content.addLocale(cms, contentLocale); 3311 if ((visitor != null) && visitor.hasInvisibleFields()) { 3312 transferInvisibleValues(originalEntity, entity, visitor); 3313 } 3314 addEntityAttributes(cms, content, "", entity, contentLocale); 3315 content.synchronizeLocaleIndependentValues(cms, skipPaths, contentLocale); 3316 } 3317 3318 /** 3319 * Transfers settings attribute values from one entity to another.<p> 3320 * 3321 * @param source the source entity 3322 * @param target the target entity 3323 */ 3324 private void transferSettingValues(CmsEntity source, CmsEntity target) { 3325 3326 for (CmsEntityAttribute attr : source.getAttributes()) { 3327 if (isSettingsAttribute(attr.getAttributeName())) { 3328 if (attr.isSimpleValue()) { 3329 target.addAttributeValue(attr.getAttributeName(), attr.getSimpleValue()); 3330 } else { 3331 CmsEntity nestedSource = attr.getComplexValue(); 3332 3333 CmsEntity nested = new CmsEntity(nestedSource.getId(), nestedSource.getTypeName()); 3334 for (CmsEntityAttribute nestedAttr : nestedSource.getAttributes()) { 3335 nested.addAttributeValue(nestedAttr.getAttributeName(), nestedAttr.getSimpleValue()); 3336 } 3337 target.addAttributeValue(attr.getAttributeName(), nested); 3338 } 3339 } 3340 } 3341 } 3342 3343 /** 3344 * Validates the given XML content.<p> 3345 * 3346 * @param cms the cms context 3347 * @param structureId the structure id 3348 * @param content the XML content 3349 * 3350 * @return the validation result 3351 */ 3352 private CmsValidationResult validateContent(CmsObject cms, CmsUUID structureId, CmsXmlContent content) { 3353 3354 return validateContent(cms, structureId, content, null); 3355 } 3356 3357 /** 3358 * Validates the given XML content.<p> 3359 * 3360 * @param cms the cms context 3361 * @param structureId the structure id 3362 * @param content the XML content 3363 * @param fieldNames if not null, only validation errors in paths from this set will be added to the validation result 3364 * 3365 * @return the validation result 3366 */ 3367 private CmsValidationResult validateContent( 3368 CmsObject cms, 3369 CmsUUID structureId, 3370 CmsXmlContent content, 3371 Set<String> fieldNames) { 3372 3373 CmsXmlContentErrorHandler errorHandler = content.validate(cms); 3374 Map<String, Map<String[], CmsPair<String, String>>> errorsByEntity = new HashMap<String, Map<String[], CmsPair<String, String>>>(); 3375 3376 if (errorHandler.hasErrors()) { 3377 boolean reallyHasErrors = false; 3378 for (Entry<Locale, Map<String, String>> localeEntry : errorHandler.getErrors().entrySet()) { 3379 Map<String[], CmsPair<String, String>> errors = new HashMap<String[], CmsPair<String, String>>(); 3380 for (Entry<String, String> error : localeEntry.getValue().entrySet()) { 3381 I_CmsXmlContentValue value = content.getValue(error.getKey(), localeEntry.getKey()); 3382 if (content.getHandler().isVisible( 3383 cms, 3384 value, 3385 CmsXmlUtils.removeAllXpathIndices(value.getPath()), 3386 content.getFile(), 3387 localeEntry.getKey()) && ((fieldNames == null) || fieldNames.contains(value.getPath()))) { 3388 errors.put( 3389 getPathElements(content, value), 3390 new CmsPair<String, String>(error.getValue(), error.getKey())); 3391 reallyHasErrors = true; 3392 } 3393 3394 } 3395 if (reallyHasErrors) { 3396 errorsByEntity.put( 3397 CmsContentDefinition.uuidToEntityId(structureId, localeEntry.getKey().toString()), 3398 errors); 3399 } 3400 } 3401 } 3402 Map<String, Map<String[], CmsPair<String, String>>> warningsByEntity = new HashMap<String, Map<String[], CmsPair<String, String>>>(); 3403 if (errorHandler.hasWarnings()) { 3404 boolean reallyHasErrors = false; 3405 for (Entry<Locale, Map<String, String>> localeEntry : errorHandler.getWarnings().entrySet()) { 3406 Map<String[], CmsPair<String, String>> warnings = new HashMap<String[], CmsPair<String, String>>(); 3407 for (Entry<String, String> warning : localeEntry.getValue().entrySet()) { 3408 I_CmsXmlContentValue value = content.getValue(warning.getKey(), localeEntry.getKey()); 3409 if (content.getHandler().isVisible( 3410 cms, 3411 value, 3412 CmsXmlUtils.removeAllXpathIndices(value.getPath()), 3413 content.getFile(), 3414 localeEntry.getKey()) && ((fieldNames == null) || fieldNames.contains(value.getPath()))) { 3415 warnings.put( 3416 getPathElements(content, value), 3417 new CmsPair<String, String>(warning.getValue(), warning.getKey())); 3418 reallyHasErrors = true; 3419 } 3420 } 3421 if (reallyHasErrors) { 3422 warningsByEntity.put( 3423 CmsContentDefinition.uuidToEntityId(structureId, localeEntry.getKey().toString()), 3424 warnings); 3425 } 3426 } 3427 } 3428 return new CmsValidationResult(errorsByEntity, warningsByEntity); 3429 } 3430 3431 /** 3432 * Validates the settings attribute values.<p> 3433 * 3434 * @param entity the entity to validate 3435 * @param validationResult the validation result 3436 * @param settingsConfig the settings configuration 3437 */ 3438 private void validateSettings( 3439 CmsEntity entity, 3440 CmsValidationResult validationResult, 3441 Map<String, CmsXmlContentProperty> settingsConfig) { 3442 3443 Map<String, Map<String[], CmsPair<String, String>>> errors = validationResult.getErrors(); 3444 Map<String[], CmsPair<String, String>> entityErrors = errors.get(entity.getId()); 3445 if (entityErrors == null) { 3446 entityErrors = new HashMap<String[], CmsPair<String, String>>(); 3447 } 3448 Map<String, Map<String[], CmsPair<String, String>>> warnings = validationResult.getWarnings(); 3449 Map<String[], CmsPair<String, String>> entityWarnings = warnings.get(entity.getId()); 3450 if (entityWarnings == null) { 3451 entityWarnings = new HashMap<String[], CmsPair<String, String>>(); 3452 } 3453 3454 for (CmsEntityAttribute attribute : entity.getAttributes()) { 3455 if (isSettingsAttribute(attribute.getAttributeName())) { 3456 if (attribute.isSimpleValue()) { 3457 String settingsKey = attribute.getAttributeName().substring( 3458 SETTINGS_ATTRIBUTE_NAME_PREFIX.length() + 1); 3459 CmsXmlContentProperty prop = settingsConfig.get(settingsKey); 3460 if (prop != null) { 3461 String regex = prop.getRuleRegex(); 3462 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(regex)) { 3463 if (!attribute.getSimpleValue().matches(regex)) { 3464 String[] path = new String[] {attribute.getAttributeName()}; 3465 3466 if (SETTINGS_RULE_TYPE_ERROR.equals(prop.getRuleType())) { 3467 entityErrors.put( 3468 path, 3469 new CmsPair<String, String>(prop.getError(), prop.getNiceName())); 3470 } else { 3471 entityWarnings.put( 3472 path, 3473 new CmsPair<String, String>(prop.getError(), prop.getNiceName())); 3474 } 3475 } 3476 } 3477 } 3478 } else { 3479 CmsEntity nested = attribute.getComplexValue(); 3480 for (CmsEntityAttribute nestedAttribute : nested.getAttributes()) { 3481 String settingsKey = nestedAttribute.getAttributeName().substring( 3482 SETTINGS_ATTRIBUTE_NAME_PREFIX.length() + 1); 3483 CmsXmlContentProperty prop = settingsConfig.get(settingsKey); 3484 if (prop != null) { 3485 String regex = prop.getRuleRegex(); 3486 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(regex)) { 3487 if (!nestedAttribute.getSimpleValue().matches(regex)) { 3488 String[] path = new String[] { 3489 attribute.getAttributeName(), 3490 nestedAttribute.getAttributeName()}; 3491 if (SETTINGS_RULE_TYPE_ERROR.equals(prop.getRuleType())) { 3492 entityErrors.put( 3493 path, 3494 new CmsPair<String, String>(prop.getError(), prop.getNiceName())); 3495 } else { 3496 entityWarnings.put( 3497 path, 3498 new CmsPair<String, String>(prop.getError(), prop.getNiceName())); 3499 } 3500 } 3501 } 3502 } 3503 } 3504 } 3505 } 3506 } 3507 if (!entityErrors.isEmpty()) { 3508 errors.put(entity.getId(), entityErrors); 3509 } 3510 if (!entityWarnings.isEmpty()) { 3511 warnings.put(entity.getId(), entityWarnings); 3512 } 3513 3514 } 3515 3516 /** 3517 * Writes the categories that are dynamically read/wrote by the content editor. 3518 * 3519 * @param file the file where the content is stored. 3520 * @param content the content. 3521 * @param lastEditedEntity the last edited entity 3522 */ 3523 private void writeCategories(CmsFile file, CmsXmlContent content, CmsEntity lastEditedEntity) { 3524 3525 // do nothing if one of the arguments is empty. 3526 if ((null == content) || (null == file)) { 3527 return; 3528 } 3529 3530 CmsObject cms = getCmsObject(); 3531 if (!content.getLocales().isEmpty()) { 3532 Locale locale = content.getLocales().iterator().next(); 3533 CmsEntity entity = lastEditedEntity; 3534 List<I_CmsXmlContentValue> values = content.getValues(locale); 3535 for (I_CmsXmlContentValue value : values) { 3536 if (value.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME)) { 3537 I_CmsWidget widget = CmsWidgetUtil.collectWidgetInfo(cms, value).getWidget(); 3538 List<CmsCategory> categories = new ArrayList<CmsCategory>(0); 3539 try { 3540 categories = CmsCategoryService.getInstance().readResourceCategories(cms, file); 3541 } catch (CmsException e) { 3542 LOG.error(Messages.get().getBundle().key(Messages.ERROR_FAILED_READING_CATEGORIES_1), e); 3543 } 3544 if ((null != widget) && (widget instanceof CmsCategoryWidget)) { 3545 String mainCategoryPath = ((CmsCategoryWidget)widget).getStartingCategory( 3546 cms, 3547 cms.getSitePath(file)); 3548 for (CmsCategory category : categories) { 3549 if (category.getPath().startsWith(mainCategoryPath)) { 3550 try { 3551 CmsCategoryService.getInstance().removeResourceFromCategory( 3552 cms, 3553 cms.getSitePath(file), 3554 category); 3555 } catch (CmsException e) { 3556 LOG.error(e.getLocalizedMessage(), e); 3557 } 3558 } 3559 } 3560 if (null == entity) { 3561 try { 3562 CmsContentDefinition definition = readContentDefinition( 3563 file, 3564 content, 3565 "dummy", 3566 null, 3567 locale, 3568 false, 3569 null, 3570 null, 3571 Collections.emptyMap(), 3572 null); 3573 entity = definition.getEntity(); 3574 } catch (CmsException e) { 3575 LOG.error(e.getLocalizedMessage(), e); 3576 } 3577 } 3578 String checkedCategories = ""; 3579 if (null != entity) { 3580 checkedCategories = CmsEntity.getValueForPath(entity, value.getPath().split("/")); 3581 } 3582 List<String> checkedCategoryList = Arrays.asList(checkedCategories.split(",")); 3583 for (String category : checkedCategoryList) { 3584 try { 3585 CmsCategoryService.getInstance().addResourceToCategory( 3586 cms, 3587 cms.getSitePath(file), 3588 CmsCategoryService.getInstance().getCategory(cms, category)); 3589 } catch (CmsException e) { 3590 if (LOG.isWarnEnabled()) { 3591 LOG.warn(e.getLocalizedMessage(), e); 3592 } 3593 } 3594 } 3595 } 3596 } 3597 } 3598 } 3599 } 3600 3601 /** 3602 * Writes the xml content to the vfs and re-initializes the member variables.<p> 3603 * 3604 * @param cms the cms context 3605 * @param file the file to write to 3606 * @param content the content 3607 * @param encoding the file encoding 3608 * 3609 * @return the content 3610 * 3611 * @throws CmsException if writing the file fails 3612 */ 3613 private CmsXmlContent writeContent(CmsObject cms, CmsFile file, CmsXmlContent content, String encoding) 3614 throws CmsException { 3615 3616 String decodedContent = content.toString(); 3617 try { 3618 file.setContents(decodedContent.getBytes(encoding)); 3619 } catch (UnsupportedEncodingException e) { 3620 throw new CmsException( 3621 org.opencms.workplace.editors.Messages.get().container( 3622 org.opencms.workplace.editors.Messages.ERR_INVALID_CONTENT_ENC_1, 3623 file.getRootPath()), 3624 e); 3625 } 3626 // the file content might have been modified during the write operation 3627 3628 cms.getRequestContext().setAttribute(ATTR_EDITOR_SAVING, "true"); 3629 try { 3630 file = cms.writeFile(file); 3631 } finally { 3632 cms.getRequestContext().removeAttribute(ATTR_EDITOR_SAVING); 3633 } 3634 return CmsXmlContentFactory.unmarshal(cms, file); 3635 } 3636 3637}