001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (C) Alkacon Software (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.configuration; 029 030import org.opencms.ade.configuration.CmsADEConfigDataInternal.AttributeValue; 031import org.opencms.ade.configuration.CmsADEConfigDataInternal.ConfigReferenceMeta; 032import org.opencms.ade.configuration.formatters.CmsFormatterBeanParser; 033import org.opencms.ade.configuration.formatters.CmsFormatterChangeSet; 034import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCacheState; 035import org.opencms.ade.configuration.formatters.CmsFormatterIndex; 036import org.opencms.ade.configuration.plugins.CmsSitePlugin; 037import org.opencms.ade.containerpage.shared.CmsContainer; 038import org.opencms.ade.containerpage.shared.CmsContainerElement; 039import org.opencms.ade.containerpage.shared.CmsFormatterConfig; 040import org.opencms.ade.detailpage.CmsDetailPageInfo; 041import org.opencms.ade.galleries.CmsAddContentRestriction; 042import org.opencms.file.CmsObject; 043import org.opencms.file.CmsResource; 044import org.opencms.file.CmsResourceFilter; 045import org.opencms.file.types.CmsResourceTypeFolder; 046import org.opencms.file.types.CmsResourceTypeFunctionConfig; 047import org.opencms.file.types.CmsResourceTypeXmlContent; 048import org.opencms.file.types.I_CmsResourceType; 049import org.opencms.gwt.CmsIconUtil; 050import org.opencms.gwt.shared.CmsGwtConstants; 051import org.opencms.jsp.util.CmsFunctionRenderer; 052import org.opencms.loader.CmsLoaderException; 053import org.opencms.main.CmsException; 054import org.opencms.main.CmsLog; 055import org.opencms.main.OpenCms; 056import org.opencms.main.OpenCmsServlet; 057import org.opencms.util.CmsFileUtil; 058import org.opencms.util.CmsStringUtil; 059import org.opencms.util.CmsUUID; 060import org.opencms.workplace.editors.directedit.CmsAdvancedDirectEditProvider.SitemapDirectEditPermissions; 061import org.opencms.xml.CmsLinkFinisher; 062import org.opencms.xml.CmsXmlContentDefinition; 063import org.opencms.xml.containerpage.CmsFormatterConfiguration; 064import org.opencms.xml.containerpage.CmsXmlDynamicFunctionHandler; 065import org.opencms.xml.containerpage.I_CmsFormatterBean; 066import org.opencms.xml.content.CmsXmlContentFactory; 067import org.opencms.xml.content.CmsXmlContentProperty; 068 069import java.util.ArrayList; 070import java.util.Arrays; 071import java.util.Collection; 072import java.util.Collections; 073import java.util.HashMap; 074import java.util.HashSet; 075import java.util.Iterator; 076import java.util.LinkedHashMap; 077import java.util.List; 078import java.util.Map; 079import java.util.Objects; 080import java.util.Set; 081import java.util.concurrent.ExecutionException; 082import java.util.stream.Collectors; 083 084import org.apache.commons.collections4.MapUtils; 085import org.apache.commons.lang3.builder.ReflectionToStringBuilder; 086import org.apache.commons.logging.Log; 087 088import com.google.common.base.Optional; 089import com.google.common.cache.CacheBuilder; 090import com.google.common.cache.CacheLoader; 091import com.google.common.cache.LoadingCache; 092import com.google.common.collect.ArrayListMultimap; 093import com.google.common.collect.ImmutableList; 094import com.google.common.collect.Lists; 095import com.google.common.collect.Maps; 096import com.google.common.collect.Multimap; 097import com.google.common.collect.Sets; 098 099/** 100 * A class which represents the accessible configuration data at a given point in a sitemap.<p> 101 */ 102public class CmsADEConfigData { 103 104 /** 105 * Enum for indicating how sitemap attributes should be read. 106 */ 107 public enum AttributeMode { 108 /** Include all sitemap attributes, including dynamically injected ones. */ 109 all, 110 111 /** Ignore all dynamically injected sitemap attributes. */ 112 excludeInjected, 113 114 /** Ignore dynamically injected sitemap attributes for this sitemap configuration, but include injected values that were inherited from parent sitemap configurations. */ 115 excludeInjectedForThisLevel 116 } 117 118 /** 119 * Bean which contains the detail information for a single sub-sitemap and resource type.<p> 120 * 121 * This includes both information about the detail page itself, as well as the path of the folder 122 * which is used to store that content type in this subsitemap.<p> 123 * 124 */ 125 public class DetailInfo { 126 127 /** The base path of the sitemap configuration where this information originates from. */ 128 private String m_basePath; 129 130 /** The information about the detail page info itself. */ 131 private CmsDetailPageInfo m_detailPageInfo; 132 133 /** The content folder path. */ 134 private String m_folderPath; 135 136 /** The detail type. */ 137 private String m_type; 138 139 /** 140 * Creates a new instance.<p> 141 * 142 * @param folderPath the content folder path 143 * @param detailPageInfo the detail page information 144 * @param type the detail type 145 * @param basePath the base path of the sitemap configuration 146 */ 147 public DetailInfo(String folderPath, CmsDetailPageInfo detailPageInfo, String type, String basePath) { 148 149 m_folderPath = folderPath; 150 m_detailPageInfo = detailPageInfo; 151 m_type = type; 152 m_basePath = basePath; 153 154 } 155 156 /** 157 * Gets the base path of the sitemap configuration from which this information is coming.<p> 158 * 159 * @return the base path 160 */ 161 public String getBasePath() { 162 163 return m_basePath; 164 } 165 166 /** 167 * Gets the detail page information.<p> 168 * 169 * @return the detail page information 170 */ 171 public CmsDetailPageInfo getDetailPageInfo() { 172 173 return m_detailPageInfo; 174 } 175 176 /** 177 * Gets the content folder path.<p> 178 * 179 * @return the content folder path 180 */ 181 public String getFolderPath() { 182 183 return m_folderPath; 184 } 185 186 /** 187 * Gets the detail type.<p> 188 * 189 * @return the detail type 190 */ 191 public String getType() { 192 193 return m_type; 194 } 195 196 /** 197 * Sets the base path.<p> 198 * 199 * @param basePath the new base path value 200 */ 201 public void setBasePath(String basePath) { 202 203 m_basePath = basePath; 204 } 205 206 /** 207 * @see java.lang.Object#toString() 208 */ 209 @Override 210 public String toString() { 211 212 return ReflectionToStringBuilder.toString(this); 213 } 214 } 215 216 /** Sitemap attribute for the upload folder. */ 217 public static final String ATTR_BINARY_UPLOAD_TARGET = "binary.upload.target"; 218 219 /** Sitemap attribute controlling the default files that should be truncated by the link finisher (comma separated). */ 220 public static final String ATTR_TEMPLATE_LINK_DEFAULTFILES = "template.link.defaultfiles"; 221 222 /** Sitemap attribute configuring the link finisher - currently only supports the mode 'foldername', every other value is interpreted as 'disabled'. */ 223 public static final String ATTR_TEMPLATE_LINK_FINISHER = "template.link.finisher"; 224 225 /** Attribute for regex to exclude links from being run through the finisher if they match. */ 226 public static final String ATTRIBUTE_TEMPLATE_LINK_FINISHER_EXCLUDE = "template.link.finisher.exclude"; 227 228 /** Prefix for logging special request log messages. */ 229 public static final String REQ_LOG_PREFIX = "[CmsADEConfigData] "; 230 231 /** Channel for logging special request log messages. */ 232 public static final String REQUEST_LOG_CHANNEL = "org.opencms.ade.configuration.CmsADEConfigData.request"; 233 234 /** The log instance for this class. */ 235 private static final Log LOG = CmsLog.getLog(CmsADEConfigData.class); 236 237 /** Prefixes for internal settings which might be passed as formatter keys to findFormatter(). */ 238 private static final HashSet<String> systemSettingPrefixes = new HashSet<>( 239 Arrays.asList("element", "model", "source", "use", "cms", "is")); 240 241 /** The wrapped configuration bean containing the actual data. */ 242 protected CmsADEConfigDataInternal m_data; 243 244 /** Extra information injected from elsewhere. */ 245 protected List<I_CmsSitemapExtraInfo> m_extraInfo = Collections.emptyList(); 246 247 /** Lazily initialized map of formatters. */ 248 private Map<CmsUUID, I_CmsFormatterBean> m_activeFormatters; 249 250 /** Lazily initialized cache for active formatters by formatter key. */ 251 private Multimap<String, I_CmsFormatterBean> m_activeFormattersByKey; 252 253 /** The sitemap attributes (may be null if not yet computed). */ 254 private Map<String, AttributeValue> m_attributes; 255 256 /** The cache state to which the wrapped configuration bean belongs. */ 257 private CmsADEConfigCacheState m_cache; 258 259 /** Current formatter configuration. */ 260 private CmsFormatterConfigurationCacheState m_cachedFormatters; 261 262 /** The configuration sequence (contains the list of all sitemap configuration data beans to be used for inheritance). */ 263 private CmsADEConfigurationSequence m_configSequence; 264 265 /** Cache for formatters by container type. */ 266 private Map<String, List<I_CmsFormatterBean>> m_formattersByContainerType = new HashMap<>(); 267 268 /** Cache for formatters by display type. */ 269 private Map<String, List<I_CmsFormatterBean>> m_formattersByDisplayType = new HashMap<>(); 270 271 /** Lazily initialized cache for formatters by JSP id. */ 272 private Multimap<CmsUUID, I_CmsFormatterBean> m_formattersByJspId; 273 274 /** Lazily initialized cache for formatters by formatter key. */ 275 private Multimap<String, I_CmsFormatterBean> m_formattersByKey; 276 277 /** Loading cache for for formatters grouped by type. */ 278 private LoadingCache<String, List<I_CmsFormatterBean>> m_formattersByTypeCache = CacheBuilder.newBuilder().build( 279 new CacheLoader<String, List<I_CmsFormatterBean>>() { 280 281 @Override 282 public List<I_CmsFormatterBean> load(String typeName) throws Exception { 283 284 List<I_CmsFormatterBean> result = new ArrayList<>(); 285 for (I_CmsFormatterBean formatter : getActiveFormatters().values()) { 286 if (formatter.getResourceTypeNames().contains(typeName)) { 287 result.add(formatter); 288 } 289 } 290 return result; 291 } 292 }); 293 294 private CmsLinkFinisher m_linkFinisher; 295 296 /** Cached shared setting overrides. */ 297 private volatile ImmutableList<CmsUUID> m_sharedSettingOverrides; 298 299 /** Set of names of active types.*/ 300 private Set<String> m_typesAddable; 301 302 /** Cache of (active) resource type configurations by name. */ 303 private Map<String, CmsResourceTypeConfig> m_typesByName; 304 305 /** Type names configured in this or ancestor sitemap configurations. */ 306 private Set<String> m_typesInAncestors; 307 308 /** 309 * Creates a new configuration data object, based on an internal configuration data bean and a 310 * configuration cache state.<p> 311 * 312 * @param data the internal configuration data bean 313 * @param cache the configuration cache state 314 * @param configSequence the configuration sequence 315 */ 316 public CmsADEConfigData( 317 CmsADEConfigDataInternal data, 318 CmsADEConfigCacheState cache, 319 CmsADEConfigurationSequence configSequence) { 320 321 m_data = data; 322 m_cache = cache; 323 m_configSequence = configSequence; 324 } 325 326 /** 327 * Generic method to merge lists of named configuration objects.<p> 328 * 329 * The lists are merged such that the configuration objects from the child list rise to the front of the result list, 330 * and two configuration objects will be merged themselves if they share the same name.<p> 331 * 332 * For example, if we have two lists of configuration objects:<p> 333 * 334 * parent: A1, B1, C1<p> 335 * child: D2, B2<p> 336 * 337 * then the resulting list will look like:<p> 338 * 339 * D2, B3, A1, C1<p> 340 * 341 * where B3 is the result of merging B1 and B2.<p> 342 * 343 * @param <C> the type of configuration object 344 * @param parentConfigs the parent configurations 345 * @param childConfigs the child configurations 346 * @param preserveDisabled if true, try to merge parents with disabled children instead of discarding them 347 * 348 * @return the merged configuration object list 349 */ 350 public static <C extends I_CmsConfigurationObject<C>> List<C> combineConfigurationElements( 351 List<C> parentConfigs, 352 List<C> childConfigs, 353 boolean preserveDisabled) { 354 355 List<C> result = new ArrayList<C>(); 356 Map<String, C> map = new LinkedHashMap<String, C>(); 357 if (parentConfigs != null) { 358 for (C parent : Lists.reverse(parentConfigs)) { 359 map.put(parent.getKey(), parent); 360 } 361 } 362 if (childConfigs == null) { 363 childConfigs = Collections.emptyList(); 364 } 365 for (C child : Lists.reverse(childConfigs)) { 366 String childKey = child.getKey(); 367 if (child.isDisabled() && !preserveDisabled) { 368 map.remove(childKey); 369 } else { 370 C parent = map.get(childKey); 371 map.remove(childKey); 372 C newValue; 373 if (parent != null) { 374 newValue = parent.merge(child); 375 } else { 376 newValue = child; 377 } 378 map.put(childKey, newValue); 379 } 380 } 381 result = new ArrayList<C>(map.values()); 382 Collections.reverse(result); 383 // those multiple "reverse" calls may a bit confusing. They are there because on the one hand we want to keep the 384 // configuration items from one configuration in the same order as they are defined, on the other hand we want 385 // configuration items from a child configuration to rise to the top of the configuration items. 386 387 // so for example, if the parent configuration has items with the keys A,B,C,E 388 // and the child configuration has items with the keys C,B,D 389 // we want the items of the combined configuration in the order C,B,D,A,E 390 391 return result; 392 } 393 394 /** 395 * If the given formatter key has a sub-formatter suffix, returns the part before it, 396 * otherwise returns null. 397 * 398 * @param key the formatter key 399 * @return the parent formatter key 400 */ 401 public static final String getParentFormatterKey(String key) { 402 403 if (key == null) { 404 return null; 405 } 406 int separatorPos = key.lastIndexOf(CmsGwtConstants.FORMATTER_SUBKEY_SEPARATOR); 407 if (separatorPos == -1) { 408 return null; 409 } 410 return key.substring(0, separatorPos); 411 412 } 413 414 /** 415 * Applies the formatter change sets of this and all parent configurations to a formatter index 416 * 417 * @param formatterIndex the collection of formatters to apply the changes to 418 * 419 * @param formatterCacheState the formatter cache state from which new external formatters should be fetched 420 */ 421 public void applyAllFormatterChanges( 422 CmsFormatterIndex formatterIndex, 423 CmsFormatterConfigurationCacheState formatterCacheState) { 424 425 for (CmsFormatterChangeSet changeSet : getFormatterChangeSets()) { 426 changeSet.applyToFormatters(formatterIndex, formatterCacheState); 427 } 428 } 429 430 /** 431 * Gets the 'best' formatter for the given ID.<p> 432 * 433 * If the formatter with the ID has a key, then the active formatter with the same key is returned. Otherwise, the 434 * formatter matching the ID is returned. So being active and having the same key is prioritized over an exact ID match. 435 * 436 * @param id the formatter ID 437 * @return the best formatter the given ID 438 */ 439 public I_CmsFormatterBean findFormatter(CmsUUID id) { 440 441 return findFormatter(id, false); 442 } 443 444 /** 445 * Gets the 'best' formatter for the given ID.<p> 446 * 447 * If the formatter with the ID has a key, then the active formatter with the same key is returned. Otherwise, the 448 * formatter matching the ID is returned. So being active and having the same key is prioritized over an exact ID match. 449 * 450 * @param id the formatter ID 451 * @param noWarn if true, disables warnings 452 * @return the best formatter the given ID 453 */ 454 public I_CmsFormatterBean findFormatter(CmsUUID id, boolean noWarn) { 455 456 if (id == null) { 457 return null; 458 } 459 460 CmsFormatterConfigurationCacheState formatterState = getCachedFormatters(); 461 I_CmsFormatterBean originalResult = formatterState.getFormatters().get(id); 462 I_CmsFormatterBean result = originalResult; 463 if ((result != null) && (result.getKey() != null)) { 464 String key = result.getKey(); 465 I_CmsFormatterBean resultForKey = getFormatterAndWarnIfAmbiguous(getActiveFormattersByKey(), key, noWarn); 466 if (resultForKey != null) { 467 result = resultForKey; 468 } else { 469 String parentKey = getParentFormatterKey(key); 470 if (parentKey != null) { 471 resultForKey = getFormatterAndWarnIfAmbiguous(getActiveFormattersByKey(), parentKey, noWarn); 472 if (resultForKey != null) { 473 result = resultForKey; 474 } 475 } 476 } 477 } 478 479 if (result != originalResult) { 480 String message = "Using substitute formatter " 481 + getFormatterLabel(result) 482 + " instead of " 483 + getFormatterLabel(originalResult) 484 + " because of matching key."; 485 LOG.debug(message); 486 OpenCmsServlet.withRequestCache( 487 reqCache -> reqCache.addLog(REQUEST_LOG_CHANNEL, "debug", REQ_LOG_PREFIX + message)); 488 } 489 return result; 490 } 491 492 /** 493 * Gets the 'best' formatter for the given name.<p> 494 * 495 * The name can be either a formatter key, or a formatter UUID. If it's a key, an active formatter with that key is returned. 496 * If it's a UUID, and the formatter with that UUID has no key, it will be returned. If it does have a key, the active formatter 497 * with that key is returned (so being active and having the same key is prioritized over an exact ID match). 498 * 499 * @param name a formatter name (key or ID) 500 * @return the best formatter for that name, or null if no formatter could be found 501 */ 502 public I_CmsFormatterBean findFormatter(String name) { 503 504 return findFormatter(name, false); 505 } 506 507 /** 508 * Gets the 'best' formatter for the given name.<p> 509 * 510 * The name can be either a formatter key, or a formatter UUID. If it's a key, an active formatter with that key is returned. 511 * If it's a UUID, and the formatter with that UUID has no key, it will be returned. If it does have a key, the active formatter 512 * with that key is returned (so being active and having the same key is prioritized over an exact ID match). 513 * 514 * @param name a formatter name (key or ID) 515 * @param noWarn if true, disables warnings 516 * @return the best formatter for that name, or null if no formatter could be found 517 */ 518 public I_CmsFormatterBean findFormatter(String name, boolean noWarn) { 519 520 if (name == null) { 521 return null; 522 } 523 524 if (systemSettingPrefixes.contains(name) || name.startsWith(CmsContainerElement.SYSTEM_SETTING_PREFIX)) { 525 if (LOG.isDebugEnabled()) { 526 LOG.debug("System setting prefix used: " + name, new Exception()); 527 } 528 return null; 529 } 530 531 if (CmsUUID.isValidUUID(name)) { 532 return findFormatter(new CmsUUID(name), noWarn); 533 } 534 535 if (name.startsWith(CmsFormatterConfig.SCHEMA_FORMATTER_ID)) { 536 return null; 537 } 538 539 Multimap<String, I_CmsFormatterBean> active = getActiveFormattersByKey(); 540 I_CmsFormatterBean result = getFormatterAndWarnIfAmbiguous(active, name, noWarn); 541 if (result != null) { 542 return result; 543 } 544 545 String parentName = getParentFormatterKey(name); 546 if (parentName != null) { 547 result = getFormatterAndWarnIfAmbiguous(active, parentName, noWarn); 548 if (result != null) { 549 return result; 550 } 551 } 552 553 if (!noWarn) { 554 String message1 = "No local formatter found for key '" 555 + name 556 + "' at '" 557 + getBasePath() 558 + "', trying inactive formatters"; 559 LOG.warn(message1); 560 OpenCmsServlet.withRequestCache(rc -> rc.addLog(REQUEST_LOG_CHANNEL, "warn", REQ_LOG_PREFIX + message1)); 561 } 562 563 Multimap<String, I_CmsFormatterBean> all = getFormattersByKey(); 564 result = getFormatterAndWarnIfAmbiguous(all, name, noWarn); 565 if (result != null) { 566 return result; 567 } 568 569 if (parentName != null) { 570 result = getFormatterAndWarnIfAmbiguous(all, parentName, noWarn); 571 if (result != null) { 572 return result; 573 } 574 } 575 576 if (!noWarn) { 577 OpenCmsServlet.withRequestCache( 578 rc -> rc.addLog( 579 REQUEST_LOG_CHANNEL, 580 "warn", 581 REQ_LOG_PREFIX + "No formatter found for key '" + name + "' at '" + getBasePath() + "'")); 582 } 583 return null; 584 } 585 586 /** 587 * Gets the active external (non-schema) formatters for this sub-sitemap.<p> 588 * 589 * @return the map of active external formatters by structure id 590 */ 591 public Map<CmsUUID, I_CmsFormatterBean> getActiveFormatters() { 592 593 if (m_activeFormatters == null) { 594 CmsFormatterIndex formatterIndex = new CmsFormatterIndex(); 595 for (I_CmsFormatterBean formatter : getCachedFormatters().getAutoEnabledFormatters().values()) { 596 formatterIndex.addFormatter(formatter); 597 } 598 applyAllFormatterChanges(formatterIndex, getCachedFormatters()); 599 m_activeFormatters = Collections.unmodifiableMap(formatterIndex.getFormattersWithAdditionalKeys()); 600 } 601 return m_activeFormatters; 602 } 603 604 /** 605 * Gets the active formatters for a given container type. 606 * 607 * @param containerType a container type 608 * 609 * @return the active formatters for the container type 610 */ 611 public List<I_CmsFormatterBean> getActiveFormattersWithContainerType(String containerType) { 612 613 return m_formattersByContainerType.computeIfAbsent( 614 containerType, 615 type -> Collections.unmodifiableList( 616 getActiveFormatters().values().stream().filter( 617 formatter -> formatter.getContainerTypes().contains(type)).collect(Collectors.toList()))); 618 } 619 620 /** 621 * Gets the active formatters for a given display type. 622 * 623 * @param displayType a display type 624 * @return the active formatters for the display type 625 */ 626 public List<I_CmsFormatterBean> getActiveFormattersWithDisplayType(String displayType) { 627 628 return m_formattersByDisplayType.computeIfAbsent( 629 displayType, 630 type -> Collections.unmodifiableList( 631 getActiveFormatters().values().stream().filter( 632 formatter -> Objects.equals(type, formatter.getDisplayType())).collect(Collectors.toList())) 633 634 ); 635 } 636 637 /** 638 * Gets the set of names of types active in this sitemap configuration. 639 * 640 * @return the set of type names of active types 641 */ 642 public Set<String> getAddableTypeNames() { 643 644 Set<String> result = m_typesAddable; 645 if (result != null) { 646 return result; 647 } else { 648 Set<String> mutableResult = new HashSet<>(); 649 for (CmsResourceTypeConfig typeConfig : internalGetResourceTypes(true)) { 650 if (!typeConfig.isAddDisabled()) { 651 mutableResult.add(typeConfig.getTypeName()); 652 } 653 } 654 result = Collections.unmodifiableSet(mutableResult); 655 m_typesAddable = result; 656 return result; 657 } 658 } 659 660 /** 661 * Gets the 'add content' restriction for this configuration. 662 * 663 * @return the 'add content' restriction 664 */ 665 public CmsAddContentRestriction getAddContentRestriction() { 666 667 getAncestorTypeNames(); 668 669 CmsADEConfigData parentConfig = parent(); 670 if (parentConfig == null) { 671 return m_data.getAddContentRestriction(); 672 } else { 673 return parentConfig.getAddContentRestriction().merge(m_data.getAddContentRestriction()); 674 } 675 } 676 677 /** 678 * Gets the list of all detail pages.<p> 679 * 680 * @return the list of all detail pages 681 */ 682 public List<CmsDetailPageInfo> getAllDetailPages() { 683 684 return getAllDetailPages(true); 685 } 686 687 /** 688 * Gets a list of all detail pages.<p> 689 * 690 * @param update if true, this method will try to correct the root paths in the returned objects if the corresponding resources have been moved 691 * 692 * @return the list of all detail pages 693 */ 694 public List<CmsDetailPageInfo> getAllDetailPages(boolean update) { 695 696 CmsADEConfigData parentData = parent(); 697 List<CmsDetailPageInfo> parentDetailPages; 698 if (parentData != null) { 699 parentDetailPages = parentData.getAllDetailPages(false); 700 } else { 701 parentDetailPages = Collections.emptyList(); 702 } 703 List<CmsDetailPageInfo> result = mergeDetailPages(parentDetailPages, m_data.getOwnDetailPages()); 704 if (update) { 705 result = updateUris(result); 706 } 707 return result; 708 } 709 710 /** 711 * Gets the set of names of types configured in this or any ancestor sitemap configurations. 712 * 713 * @return the set of type names from all ancestor configurations 714 */ 715 public Set<String> getAncestorTypeNames() { 716 717 Set<String> result = m_typesInAncestors; 718 if (result != null) { 719 return result; 720 } else { 721 Set<String> mutableResult = new HashSet<>(); 722 for (CmsResourceTypeConfig typeConfig : internalGetResourceTypes(false)) { 723 mutableResult.add(typeConfig.getTypeName()); 724 } 725 result = Collections.unmodifiableSet(mutableResult); 726 m_typesInAncestors = result; 727 return result; 728 } 729 } 730 731 /** 732 * Gets the value of an attribute, or a default value 733 * 734 * @param key the attribute key 735 * @param defaultValue the value to return if no attribute with the given name is found 736 * 737 * @return the attribute value 738 */ 739 public String getAttribute(String key, String defaultValue) { 740 741 AttributeValue value = getAttributes().get(key); 742 if (value != null) { 743 return value.getValue(); 744 } else { 745 return defaultValue; 746 } 747 748 } 749 750 /** 751 * Gets the active sitemap attribute editor configuration. 752 * 753 * @return the active sitemap attribute editor configuration 754 */ 755 public CmsSitemapAttributeEditorConfiguration getAttributeEditorConfiguration() { 756 757 CmsUUID id = getAttributeEditorConfigurationId(); 758 CmsSitemapAttributeEditorConfiguration result = m_cache.getAttributeEditorConfiguration(id); 759 if (result == null) { 760 result = CmsSitemapAttributeEditorConfiguration.EMPTY; 761 } 762 return result; 763 764 } 765 766 /** 767 * Gets the structure id of the configured sitemap attribute editor configuration. 768 * 769 * @return the structure id of the configured sitemap attribute editor configuration 770 */ 771 public CmsUUID getAttributeEditorConfigurationId() { 772 773 CmsADEConfigData parent = parent(); 774 CmsUUID result = m_data.getAttributeEditorConfigId(); 775 if ((result == null) && (parent != null)) { 776 result = parent.getAttributeEditorConfigurationId(); 777 } 778 return result; 779 780 } 781 782 /** 783 * Gets the map of attributes configured for this sitemap, including values inherited from parent sitemaps. 784 * 785 * @return the map of attributes 786 */ 787 public Map<String, AttributeValue> getAttributes() { 788 789 return getAttributes(AttributeMode.all); 790 } 791 792 /** 793 * Gets the map of attributes configured for this sitemap, including values inherited from parent sitemaps. 794 * 795 * <p>NOTE: If mode != AttributeMode.all, the result is not cached. 796 * 797 * @param mode indicates how injected attributes should be treated 798 * @return the map of attributes 799 */ 800 public Map<String, AttributeValue> getAttributes(AttributeMode mode) { 801 802 if ((m_attributes != null) && (mode == AttributeMode.all)) { 803 return m_attributes; 804 } 805 CmsADEConfigData parentConfig = parent(); 806 Map<String, AttributeValue> result = new HashMap<>(); 807 AttributeMode parentMode = mode; 808 if (mode == AttributeMode.excludeInjectedForThisLevel) { 809 parentMode = AttributeMode.all; 810 } 811 if (parentConfig != null) { 812 result.putAll(parentConfig.getAttributes(parentMode)); 813 } 814 815 for (Map.Entry<String, AttributeValue> entry : m_data.getAttributes().entrySet()) { 816 result.put(entry.getKey(), entry.getValue()); 817 } 818 819 if ((getBasePath() != null) && (mode == AttributeMode.all)) { 820 for (I_CmsSitemapExtraInfo extraInfo : m_extraInfo) { 821 result.putAll(MapUtils.emptyIfNull(extraInfo.getAttributes(getBasePath()))); 822 } 823 } 824 825 Map<String, AttributeValue> immutableResult = Collections.unmodifiableMap(result); 826 if (mode == AttributeMode.all) { 827 m_attributes = immutableResult; 828 } 829 return immutableResult; 830 } 831 832 /** 833 * Gets the configuration base path.<p> 834 * 835 * For example, if the configuration file is located at /sites/default/.content/.config, the base path is /sites/default.<p> 836 * 837 * @return the base path of the configuration 838 */ 839 public String getBasePath() { 840 841 return m_data.getBasePath(); 842 } 843 844 /** 845 * Gets the cached formatters.<p> 846 * 847 * @return the cached formatters 848 */ 849 public CmsFormatterConfigurationCacheState getCachedFormatters() { 850 851 if (m_cachedFormatters == null) { 852 m_cachedFormatters = OpenCms.getADEManager().getCachedFormatters( 853 getCms().getRequestContext().getCurrentProject().isOnlineProject()); 854 } 855 return m_cachedFormatters; 856 } 857 858 /** 859 * Gets an (immutable) list of paths of configuration files in inheritance order. 860 * 861 * @return the list of configuration files 862 */ 863 public List<String> getConfigPaths() { 864 865 return m_configSequence.getConfigPaths(); 866 867 } 868 869 /** 870 * Returns the names of the bundles configured as workplace bundles in any module configuration.<p> 871 * 872 * @return the names of the bundles configured as workplace bundles in any module configuration. 873 */ 874 public Set<String> getConfiguredWorkplaceBundles() { 875 876 Set<String> result = new HashSet<String>(); 877 for (CmsResourceTypeConfig config : internalGetResourceTypes(false)) { 878 String bundlename = config.getConfiguredWorkplaceBundle(); 879 if (null != bundlename) { 880 result.add(bundlename); 881 } 882 } 883 return result; 884 } 885 886 /** 887 * Gets the active content folder configuration. 888 * 889 * @return the active content folder configuration 890 */ 891 public CmsContentFolderOption getContentFolderOption() { 892 893 if (m_data.getContentFolderOption() != null) { 894 return m_data.getContentFolderOption(); 895 } 896 CmsADEConfigData parent = parent(); 897 if (parent != null) { 898 return parent.getContentFolderOption(); 899 } else { 900 return null; 901 } 902 } 903 904 /** 905 * Gets the content folder path.<p> 906 * 907 * For example, if the configuration file is located at /sites/default/.content/.config, the content folder path is /sites/default/.content 908 * 909 * @return the content folder path 910 */ 911 public String getContentFolderPath() { 912 913 return CmsStringUtil.joinPaths(m_data.getBasePath(), CmsADEManager.CONTENT_FOLDER_NAME); 914 915 } 916 917 /** 918 * Returns a list of the creatable resource types.<p> 919 * 920 * @param cms the CMS context used to check whether the resource types are creatable 921 * @param pageFolderRootPath the root path of the current container page 922 * @return the list of creatable resource type 923 * 924 * @throws CmsException if something goes wrong 925 */ 926 public List<CmsResourceTypeConfig> getCreatableTypes(CmsObject cms, String pageFolderRootPath) throws CmsException { 927 928 List<CmsResourceTypeConfig> result = new ArrayList<CmsResourceTypeConfig>(); 929 for (CmsResourceTypeConfig typeConfig : getResourceTypes()) { 930 if (typeConfig.checkCreatable(cms, pageFolderRootPath)) { 931 result.add(typeConfig); 932 } 933 } 934 return result; 935 } 936 937 /** 938 * Returns the default detail page.<p> 939 * 940 * @return the default detail page 941 */ 942 public CmsDetailPageInfo getDefaultDetailPage() { 943 944 for (CmsDetailPageInfo detailpage : getAllDetailPages(true)) { 945 if (CmsADEManager.DEFAULT_DETAILPAGE_TYPE.equals(detailpage.getType())) { 946 return detailpage; 947 } 948 } 949 return null; 950 } 951 952 /** 953 * Returns the default model page.<p> 954 * 955 * @return the default model page 956 */ 957 public CmsModelPageConfig getDefaultModelPage() { 958 959 List<CmsModelPageConfig> modelPages = getModelPages(); 960 for (CmsModelPageConfig modelPageConfig : getModelPages()) { 961 if (modelPageConfig.isDefault()) { 962 return modelPageConfig; 963 } 964 } 965 if (modelPages.isEmpty()) { 966 return null; 967 } 968 return modelPages.get(0); 969 } 970 971 /** 972 * Gets the detail information for this sitemap config data object.<p> 973 * 974 * @param cms the CMS context 975 * @return the list of detail information 976 */ 977 public List<DetailInfo> getDetailInfos(CmsObject cms) { 978 979 List<DetailInfo> result = Lists.newArrayList(); 980 List<CmsDetailPageInfo> detailPages = getAllDetailPages(true); 981 Collections.reverse(detailPages); // make sure primary detail pages come later in the list and override other detail pages for the same type 982 Map<String, CmsDetailPageInfo> primaryDetailPageMapByType = Maps.newHashMap(); 983 for (CmsDetailPageInfo pageInfo : detailPages) { 984 primaryDetailPageMapByType.put(pageInfo.getType(), pageInfo); 985 } 986 for (CmsResourceTypeConfig typeConfig : getResourceTypes()) { 987 String typeName = typeConfig.getTypeName(); 988 if (((typeConfig.getFolderOrName() == null) || !typeConfig.getFolderOrName().isPageRelative()) 989 && primaryDetailPageMapByType.containsKey(typeName)) { 990 String folderPath = typeConfig.getFolderPath(cms, null); 991 CmsDetailPageInfo pageInfo = primaryDetailPageMapByType.get(typeName); 992 result.add(new DetailInfo(folderPath, pageInfo, typeName, getBasePath())); 993 } 994 } 995 return result; 996 } 997 998 /** 999 * Gets the detail pages for a specific type.<p> 1000 * 1001 * @param type the type name 1002 * 1003 * @return the list of detail pages for that type 1004 */ 1005 public List<CmsDetailPageInfo> getDetailPagesForType(String type) { 1006 1007 List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>(); 1008 CmsResourceTypeConfig typeConfig = getResourceType(type); 1009 if (type.startsWith(CmsDetailPageInfo.FUNCTION_PREFIX) 1010 || ((typeConfig != null) && !typeConfig.isDetailPagesDisabled())) { 1011 1012 List<CmsDetailPageInfo> defaultPages = new ArrayList<>(); 1013 for (CmsDetailPageInfo detailpage : getAllDetailPages(true)) { 1014 if (detailpage.getType().equals(type)) { 1015 result.add(detailpage); 1016 } else if (CmsADEManager.DEFAULT_DETAILPAGE_TYPE.equals(detailpage.getType())) { 1017 defaultPages.add(detailpage); 1018 } 1019 } 1020 result.addAll(defaultPages); 1021 } 1022 return result; 1023 } 1024 1025 /** 1026 * Returns the direct edit permissions for e.g. list elements with the given type. 1027 * 1028 * @param type the resource type name 1029 * @return the permissions 1030 */ 1031 public SitemapDirectEditPermissions getDirectEditPermissions(String type) { 1032 1033 if (type == null) { 1034 LOG.error("Null type in checkListEdit"); 1035 return SitemapDirectEditPermissions.all; 1036 } 1037 1038 if (!getAncestorTypeNames().contains(type)) { 1039 // not configured anywhere for ADE 1040 return SitemapDirectEditPermissions.editAndCreate; 1041 } 1042 1043 CmsResourceTypeConfig typeConfig = getResourceType(type); 1044 if (typeConfig == null) { 1045 return SitemapDirectEditPermissions.none; 1046 } 1047 1048 if (typeConfig.isEnabledInLists()) { 1049 return SitemapDirectEditPermissions.editAndCreate; 1050 } 1051 1052 if (typeConfig.isCreateDisabled() || typeConfig.isAddDisabled()) { 1053 return SitemapDirectEditPermissions.editOnly; 1054 } 1055 1056 return SitemapDirectEditPermissions.all; 1057 } 1058 1059 /** 1060 * Gets the display mode for deactivated functions in the gallery dialog. 1061 * 1062 * @param defaultValue the default value to return if it's not set 1063 * @return the display mode for deactivated types 1064 */ 1065 public CmsGalleryDisabledTypesMode getDisabledFunctionsMode(CmsGalleryDisabledTypesMode defaultValue) { 1066 1067 CmsADEConfigData parentData = parent(); 1068 if (m_data.getGalleryDisabledFunctionsMode() != null) { 1069 return m_data.getGalleryDisabledFunctionsMode(); 1070 } else if (parentData != null) { 1071 return parentData.getDisabledFunctionsMode(defaultValue); 1072 } else { 1073 return defaultValue; 1074 } 1075 } 1076 1077 /** 1078 * Gets the display mode for deactivated types in the gallery dialog. 1079 * 1080 * @param defaultValue the default value to return if it's not set 1081 * @return the display mode for deactivated types 1082 */ 1083 public CmsGalleryDisabledTypesMode getDisabledTypeMode(CmsGalleryDisabledTypesMode defaultValue) { 1084 1085 CmsADEConfigData parentData = parent(); 1086 if (m_data.getDisabledTypeMode() != null) { 1087 return m_data.getDisabledTypeMode(); 1088 } else if (parentData != null) { 1089 return parentData.getDisabledTypeMode(defaultValue); 1090 } else { 1091 return defaultValue; 1092 } 1093 } 1094 1095 /** 1096 * Returns all available display formatters.<p> 1097 * 1098 * @param cms the cms context 1099 * 1100 * @return the available display formatters 1101 */ 1102 public List<I_CmsFormatterBean> getDisplayFormatters(CmsObject cms) { 1103 1104 List<I_CmsFormatterBean> result = new ArrayList<I_CmsFormatterBean>(); 1105 for (I_CmsFormatterBean formatter : getCachedFormatters().getFormatters().values()) { 1106 if (formatter.isDisplayFormatter()) { 1107 result.add(formatter); 1108 } 1109 } 1110 return result; 1111 } 1112 1113 /** 1114 * Gets the bean that represents the dynamic function availability. 1115 * 1116 * @param formatterConfig the formatter configuration state 1117 * 1118 * @return the dynamic function availability 1119 */ 1120 public CmsFunctionAvailability getDynamicFunctionAvailability(CmsFormatterConfigurationCacheState formatterConfig) { 1121 1122 CmsADEConfigData parentData = parent(); 1123 CmsFunctionAvailability result; 1124 if (parentData == null) { 1125 result = new CmsFunctionAvailability(formatterConfig); 1126 } else { 1127 result = parentData.getDynamicFunctionAvailability(formatterConfig); 1128 } 1129 Collection<CmsUUID> enabledIds = m_data.getDynamicFunctions(); 1130 Collection<CmsUUID> disabledIds = m_data.getFunctionsToRemove(); 1131 if (m_data.isRemoveAllFunctions() && !m_configSequence.getMeta().isSkipRemovals()) { 1132 result.removeAll(); 1133 } 1134 if (enabledIds != null) { 1135 result.addAll(enabledIds); 1136 } 1137 if (disabledIds != null) { 1138 for (CmsUUID id : disabledIds) { 1139 result.remove(id); 1140 } 1141 } 1142 return result; 1143 } 1144 1145 /** 1146 * Gets the root path of the closest subsite going up the tree which has the 'exclude external detail contents' option enabled, or '/' if no such subsite exists. 1147 * 1148 * @return the root path of the closest subsite with 'external detail contents excluded' 1149 */ 1150 public String getExternalDetailContentExclusionFolder() { 1151 1152 if (m_data.isExcludeExternalDetailContents()) { 1153 String basePath = m_data.getBasePath(); 1154 if (basePath == null) { 1155 return "/"; 1156 } else { 1157 return basePath; 1158 } 1159 } else { 1160 CmsADEConfigData parent = parent(); 1161 if (parent != null) { 1162 return parent.getExternalDetailContentExclusionFolder(); 1163 } else { 1164 return "/"; 1165 } 1166 } 1167 } 1168 1169 /** 1170 * Returns the formatter change sets for this and all parent sitemaps, ordered by increasing folder depth of the sitemap.<p> 1171 * 1172 * @return the formatter change sets for all ancestor sitemaps 1173 */ 1174 public List<CmsFormatterChangeSet> getFormatterChangeSets() { 1175 1176 CmsADEConfigData currentConfig = this; 1177 List<CmsFormatterChangeSet> result = Lists.newArrayList(); 1178 while (currentConfig != null) { 1179 CmsFormatterChangeSet changes = currentConfig.getOwnFormatterChangeSet(); 1180 if (changes != null) { 1181 if (currentConfig.getMeta().isSkipRemovals()) { 1182 changes = changes.cloneWithNoRemovals(); 1183 } 1184 result.add(changes); 1185 } 1186 currentConfig = currentConfig.parent(); 1187 } 1188 Collections.reverse(result); 1189 return result; 1190 } 1191 1192 /** 1193 * Gets the formatter configuration for a resource.<p> 1194 * 1195 * @param cms the current CMS context 1196 * @param res the resource for which the formatter configuration should be retrieved 1197 * 1198 * @return the configuration of formatters for the resource 1199 */ 1200 public CmsFormatterConfiguration getFormatters(CmsObject cms, CmsResource res) { 1201 1202 if (CmsResourceTypeFunctionConfig.isFunction(res)) { 1203 1204 CmsFormatterConfigurationCacheState formatters = getCachedFormatters(); 1205 I_CmsFormatterBean function = findFormatter(res.getStructureId()); 1206 if (function != null) { 1207 return CmsFormatterConfiguration.create(cms, Collections.singletonList(function)); 1208 } else { 1209 if ((!res.getStructureId().isNullUUID()) 1210 && cms.existsResource(res.getStructureId(), CmsResourceFilter.IGNORE_EXPIRATION)) { 1211 // usually if it's just been created, but not added to the configuration cache yet 1212 CmsFormatterBeanParser parser = new CmsFormatterBeanParser(cms, new HashMap<>()); 1213 try { 1214 function = parser.parse( 1215 CmsXmlContentFactory.unmarshal(cms, cms.readFile(res)), 1216 res.getRootPath(), 1217 "" + res.getStructureId()); 1218 return CmsFormatterConfiguration.create(cms, Collections.singletonList(function)); 1219 } catch (Exception e) { 1220 LOG.warn(e.getLocalizedMessage(), e); 1221 return CmsFormatterConfiguration.EMPTY_CONFIGURATION; 1222 } 1223 1224 } else { 1225 // if a new function has been dragged on the page, it doesn't exist in the VFS yet, so we need a different 1226 // instance as a replacement 1227 CmsResource defaultFormatter = CmsFunctionRenderer.getDefaultFunctionInstance(cms); 1228 if (defaultFormatter != null) { 1229 I_CmsFormatterBean defaultFormatterBean = formatters.getFormatters().get( 1230 defaultFormatter.getStructureId()); 1231 return CmsFormatterConfiguration.create(cms, Collections.singletonList(defaultFormatterBean)); 1232 } else { 1233 LOG.warn("Could not read default formatter for functions."); 1234 return CmsFormatterConfiguration.EMPTY_CONFIGURATION; 1235 } 1236 } 1237 } 1238 } else { 1239 try { 1240 int resTypeId = res.getTypeId(); 1241 return getFormatters( 1242 cms, 1243 OpenCms.getResourceManager().getResourceType(resTypeId), 1244 getFormattersFromSchema(cms, res)); 1245 } catch (CmsLoaderException e) { 1246 LOG.warn(e.getLocalizedMessage(), e); 1247 return CmsFormatterConfiguration.EMPTY_CONFIGURATION; 1248 } 1249 } 1250 } 1251 1252 /** 1253 * Gets a named function reference.<p> 1254 * 1255 * @param name the name of the function reference 1256 * 1257 * @return the function reference for the given name 1258 */ 1259 public CmsFunctionReference getFunctionReference(String name) { 1260 1261 List<CmsFunctionReference> functionReferences = getFunctionReferences(); 1262 for (CmsFunctionReference functionRef : functionReferences) { 1263 if (functionRef.getName().equals(name)) { 1264 return functionRef; 1265 } 1266 } 1267 return null; 1268 } 1269 1270 /** 1271 * Gets the list of configured function references.<p> 1272 * 1273 * @return the list of configured function references 1274 */ 1275 public List<CmsFunctionReference> getFunctionReferences() { 1276 1277 return internalGetFunctionReferences(); 1278 } 1279 1280 /** 1281 * Gets the map of external (non-schema) formatters which are inactive in this sub-sitemap.<p> 1282 * 1283 * @return the map inactive external formatters 1284 */ 1285 public Map<CmsUUID, I_CmsFormatterBean> getInactiveFormatters() { 1286 1287 CmsFormatterConfigurationCacheState cacheState = getCachedFormatters(); 1288 Map<CmsUUID, I_CmsFormatterBean> result = Maps.newHashMap(cacheState.getFormatters()); 1289 result.keySet().removeAll(getActiveFormatters().keySet()); 1290 return result; 1291 } 1292 1293 /** 1294 * Creates a link finisher based on the configured sitemap attributes. 1295 * 1296 * @return the link finisher 1297 */ 1298 public CmsLinkFinisher getLinkFinisher() { 1299 1300 if (m_linkFinisher == null) { 1301 1302 boolean enabled = "foldername".equals(getAttribute(ATTR_TEMPLATE_LINK_FINISHER, "")); 1303 String suffixesStr = getAttribute(ATTR_TEMPLATE_LINK_DEFAULTFILES, null); 1304 Collection<String> defaultFileNames; 1305 if (suffixesStr != null) { 1306 defaultFileNames = Collections.unmodifiableList( 1307 Arrays.asList(suffixesStr.split(",")).stream().map(suffix -> suffix.strip()).filter( 1308 suffix -> !CmsStringUtil.isEmptyOrWhitespaceOnly(suffix)).collect(Collectors.toList())); 1309 } else { 1310 defaultFileNames = OpenCms.getDefaultFiles(); 1311 } 1312 String exclude = getAttribute(ATTRIBUTE_TEMPLATE_LINK_FINISHER_EXCLUDE, null); 1313 m_linkFinisher = new CmsLinkFinisher(enabled, defaultFileNames, exclude); 1314 } 1315 return m_linkFinisher; 1316 1317 } 1318 1319 /** 1320 * Gets the list of available model pages.<p> 1321 * 1322 * @return the list of available model pages 1323 */ 1324 public List<CmsModelPageConfig> getModelPages() { 1325 1326 return getModelPages(false); 1327 } 1328 1329 /** 1330 * Gets the list of available model pages.<p> 1331 * 1332 * @param includeDisable <code>true</code> to include disabled model pages 1333 * 1334 * @return the list of available model pages 1335 */ 1336 public List<CmsModelPageConfig> getModelPages(boolean includeDisable) { 1337 1338 CmsADEConfigData parentData = parent(); 1339 List<CmsModelPageConfig> parentModelPages; 1340 if ((parentData != null) && !m_data.isDiscardInheritedModelPages()) { 1341 parentModelPages = parentData.getModelPages(); 1342 } else { 1343 parentModelPages = Collections.emptyList(); 1344 } 1345 1346 List<CmsModelPageConfig> result = combineConfigurationElements( 1347 parentModelPages, 1348 m_data.getOwnModelPageConfig(), 1349 includeDisable); 1350 return result; 1351 } 1352 1353 /** 1354 * Gets the formatter changes for this sitemap configuration.<p> 1355 * 1356 * @return the formatter change set 1357 */ 1358 public CmsFormatterChangeSet getOwnFormatterChangeSet() { 1359 1360 return m_data.getFormatterChangeSet(); 1361 } 1362 1363 /** 1364 * Gets the configuration for the available properties.<p> 1365 * 1366 * @return the configuration for the available properties 1367 */ 1368 public List<CmsPropertyConfig> getPropertyConfiguration() { 1369 1370 CmsADEConfigData parentData = parent(); 1371 List<CmsPropertyConfig> parentProperties; 1372 boolean removeInherited = m_data.isDiscardInheritedProperties() && !getMeta().isSkipRemovals(); 1373 if ((parentData != null) && !removeInherited) { 1374 parentProperties = parentData.getPropertyConfiguration(); 1375 } else { 1376 parentProperties = Collections.emptyList(); 1377 } 1378 LinkedHashMap<String, CmsPropertyConfig> propMap = new LinkedHashMap<>(); 1379 for (CmsPropertyConfig conf : parentProperties) { 1380 if (conf.isDisabled()) { 1381 continue; 1382 } 1383 propMap.put(conf.getName(), conf); 1384 } 1385 for (CmsPropertyConfig conf : m_data.getOwnPropertyConfigurations()) { 1386 if (conf.isDisabled()) { 1387 propMap.remove(conf.getName()); 1388 } else if (propMap.containsKey(conf.getName())) { 1389 propMap.put(conf.getName(), propMap.get(conf.getName()).merge(conf)); 1390 } else { 1391 propMap.put(conf.getName(), conf); 1392 } 1393 } 1394 List<CmsPropertyConfig> result = new ArrayList<>(propMap.values()); 1395 return result; 1396 } 1397 1398 /** 1399 * Computes the ordered map of properties to display in the property dialog, given the map of default property configurations passed as a parameter. 1400 * 1401 * @param defaultProperties the default property configurations 1402 * @return the ordered map of property configurations for the property dialog 1403 */ 1404 public Map<String, CmsXmlContentProperty> getPropertyConfiguration( 1405 Map<String, CmsXmlContentProperty> defaultProperties) { 1406 1407 List<CmsPropertyConfig> myPropConfigs = getPropertyConfiguration(); 1408 Map<String, CmsXmlContentProperty> allProps = new LinkedHashMap<>(defaultProperties); 1409 Map<String, CmsXmlContentProperty> result = new LinkedHashMap<>(); 1410 for (CmsPropertyConfig prop : myPropConfigs) { 1411 allProps.put(prop.getName(), prop.getPropertyData()); 1412 if (prop.isTop()) { 1413 result.put(prop.getName(), prop.getPropertyData()); 1414 } 1415 } 1416 for (Map.Entry<String, CmsXmlContentProperty> entry : allProps.entrySet()) { 1417 if (!result.containsKey(entry.getKey())) { 1418 result.put(entry.getKey(), entry.getValue()); 1419 } 1420 } 1421 return result; 1422 1423 } 1424 1425 /** 1426 * Gets the property configuration as a map of CmsXmlContentProperty instances.<p> 1427 * 1428 * @return the map of property configurations 1429 */ 1430 public Map<String, CmsXmlContentProperty> getPropertyConfigurationAsMap() { 1431 1432 Map<String, CmsXmlContentProperty> result = new LinkedHashMap<String, CmsXmlContentProperty>(); 1433 for (CmsPropertyConfig propConf : getPropertyConfiguration()) { 1434 result.put(propConf.getName(), propConf.getPropertyData()); 1435 } 1436 return result; 1437 } 1438 1439 /** 1440 * Returns the resource from which this configuration was read.<p> 1441 * 1442 * @return the resource from which this configuration was read 1443 */ 1444 public CmsResource getResource() { 1445 1446 return m_data.getResource(); 1447 } 1448 1449 /** 1450 * Returns the configuration for a specific resource type.<p> 1451 * 1452 * @param typeName the name of the type 1453 * 1454 * @return the resource type configuration for that type 1455 */ 1456 public CmsResourceTypeConfig getResourceType(String typeName) { 1457 1458 for (CmsResourceTypeConfig type : getResourceTypes()) { 1459 if (typeName.equals(type.getTypeName())) { 1460 return type; 1461 } 1462 } 1463 return null; 1464 } 1465 1466 /** 1467 * Gets a list of all available resource type configurations.<p> 1468 * 1469 * @return the available resource type configurations 1470 */ 1471 public List<CmsResourceTypeConfig> getResourceTypes() { 1472 1473 List<CmsResourceTypeConfig> result = internalGetResourceTypes(true); 1474 for (CmsResourceTypeConfig config : result) { 1475 config.initialize(getCms()); 1476 } 1477 return result; 1478 } 1479 1480 /** 1481 * Gets the searchable resource type configurations.<p> 1482 * 1483 * @param cms the current CMS context 1484 * @return the searchable resource type configurations 1485 */ 1486 public Collection<CmsResourceTypeConfig> getSearchableTypes(CmsObject cms) { 1487 1488 return getResourceTypes(); 1489 } 1490 1491 /** 1492 * Gets the list of structure ids of the shared setting overrides, ordered by increasing specificity. 1493 * 1494 * @return the list of structure ids of shared setting overrides 1495 */ 1496 public ImmutableList<CmsUUID> getSharedSettingOverrides() { 1497 1498 if (m_sharedSettingOverrides != null) { 1499 return m_sharedSettingOverrides; 1500 } 1501 1502 CmsADEConfigData currentConfig = this; 1503 List<CmsADEConfigData> relevantConfigurations = new ArrayList<>(); 1504 while (currentConfig != null) { 1505 relevantConfigurations.add(currentConfig); 1506 if (currentConfig.m_data.isRemoveSharedSettingOverrides() 1507 && !currentConfig.m_configSequence.getMeta().isSkipRemovals()) { 1508 // once we find a configuration where 'remove all shared setting overrides' is enabled, 1509 // all parent configurations become irrelevant 1510 break; 1511 } 1512 currentConfig = currentConfig.parent(); 1513 } 1514 1515 // order by ascending specificity 1516 Collections.reverse(relevantConfigurations); 1517 1518 List<CmsUUID> ids = new ArrayList<>(); 1519 for (CmsADEConfigData config : relevantConfigurations) { 1520 CmsUUID id = config.m_data.getSharedSettingOverride(); 1521 if (id != null) { 1522 ids.add(id); 1523 } 1524 } 1525 ImmutableList<CmsUUID> result = ImmutableList.copyOf(ids); 1526 m_sharedSettingOverrides = result; 1527 return result; 1528 } 1529 1530 /** 1531 * Gets the ids of site plugins which are active in this sitemap configuration. 1532 * 1533 * @return the ids of active site plugins 1534 */ 1535 public Set<CmsUUID> getSitePluginIds() { 1536 1537 CmsADEConfigData parent = parent(); 1538 Set<CmsUUID> result; 1539 if ((parent == null) || (m_data.isRemoveAllPlugins() && !getMeta().isSkipRemovals())) { 1540 result = new HashSet<>(); 1541 } else { 1542 result = parent.getSitePluginIds(); 1543 } 1544 result.removeAll(m_data.getRemovedPlugins()); 1545 result.addAll(m_data.getAddedPlugins()); 1546 return result; 1547 } 1548 1549 /** 1550 * Gets the list of site plugins active in this sitemap configuration. 1551 * 1552 * @return the list of active site plugins 1553 */ 1554 public List<CmsSitePlugin> getSitePlugins() { 1555 1556 Set<CmsUUID> pluginIds = getSitePluginIds(); 1557 List<CmsSitePlugin> result = new ArrayList<>(); 1558 Map<CmsUUID, CmsSitePlugin> plugins = m_cache.getSitePlugins(); 1559 for (CmsUUID id : pluginIds) { 1560 CmsSitePlugin sitePlugin = plugins.get(id); 1561 if (sitePlugin != null) { 1562 result.add(sitePlugin); 1563 } 1564 } 1565 return result; 1566 } 1567 1568 /** 1569 * Gets the type ordering mode. 1570 * 1571 * @return the type ordering mode 1572 */ 1573 public CmsTypeOrderingMode getTypeOrderingMode() { 1574 1575 CmsTypeOrderingMode ownOrderingMode = m_data.getTypeOrderingMode(); 1576 if (ownOrderingMode != null) { 1577 return ownOrderingMode; 1578 } else { 1579 CmsADEConfigData parentConfig = parent(); 1580 CmsTypeOrderingMode parentMode = null; 1581 if (parentConfig == null) { 1582 parentMode = CmsTypeOrderingMode.latestOnTop; 1583 } else { 1584 parentMode = parentConfig.getTypeOrderingMode(); 1585 } 1586 return parentMode; 1587 } 1588 1589 } 1590 1591 /** 1592 * Gets a map of the active resource type configurations, with type names as keys. 1593 * 1594 * @return the map of active types 1595 */ 1596 public Map<String, CmsResourceTypeConfig> getTypesByName() { 1597 1598 if (m_typesByName != null) { 1599 return m_typesByName; 1600 } 1601 Map<String, CmsResourceTypeConfig> result = new HashMap<>(); 1602 for (CmsResourceTypeConfig type : getResourceTypes()) { 1603 result.put(type.getTypeName(), type); 1604 } 1605 result = Collections.unmodifiableMap(result); 1606 m_typesByName = result; 1607 return result; 1608 } 1609 1610 /** 1611 * Gets the set of resource type names for which schema formatters can be enabled or disabled and which are not disabled in this sub-sitemap.<p> 1612 * 1613 * @return the set of types for which schema formatters are active 1614 */ 1615 public Set<String> getTypesWithActiveSchemaFormatters() { 1616 1617 Set<String> result = Sets.newHashSet(getTypesWithModifiableFormatters()); 1618 for (CmsFormatterChangeSet changeSet : getFormatterChangeSets()) { 1619 changeSet.applyToTypes(result); 1620 } 1621 return result; 1622 } 1623 1624 /** 1625 * Gets the set of names of resource types which have schema-based formatters that can be enabled or disabled.<p> 1626 * 1627 * @return the set of names of resource types which have schema-based formatters that can be enabled or disabled 1628 */ 1629 public Set<String> getTypesWithModifiableFormatters() { 1630 1631 Set<String> result = new HashSet<String>(); 1632 for (I_CmsResourceType type : OpenCms.getResourceManager().getResourceTypes()) { 1633 if (type instanceof CmsResourceTypeXmlContent) { 1634 CmsXmlContentDefinition contentDef = null; 1635 try { 1636 contentDef = CmsXmlContentDefinition.getContentDefinitionForType(getCms(), type.getTypeName()); 1637 if ((contentDef != null) && contentDef.getContentHandler().hasModifiableFormatters()) { 1638 result.add(type.getTypeName()); 1639 } 1640 } catch (Exception e) { 1641 LOG.error(e.getLocalizedMessage(), e); 1642 } 1643 } 1644 } 1645 return result; 1646 1647 } 1648 1649 /** 1650 * Checks if there are any matching formatters for the given set of containers.<p> 1651 * 1652 * @param cms the current CMS context 1653 * @param resType the resource type for which the formatter configuration should be retrieved 1654 * @param containers the page containers 1655 * 1656 * @return if there are any matching formatters 1657 */ 1658 public boolean hasFormatters(CmsObject cms, I_CmsResourceType resType, Collection<CmsContainer> containers) { 1659 1660 try { 1661 if (CmsXmlDynamicFunctionHandler.TYPE_FUNCTION.equals(resType.getTypeName()) 1662 || CmsResourceTypeFunctionConfig.TYPE_NAME.equals(resType.getTypeName())) { 1663 // dynamic function may match any container 1664 return true; 1665 } 1666 CmsXmlContentDefinition def = CmsXmlContentDefinition.getContentDefinitionForType( 1667 cms, 1668 resType.getTypeName()); 1669 CmsFormatterConfiguration schemaFormatters = def.getContentHandler().getFormatterConfiguration(cms, null); 1670 CmsFormatterConfiguration formatters = getFormatters(cms, resType, schemaFormatters); 1671 for (CmsContainer cont : containers) { 1672 if (cont.isEditable() 1673 && (formatters.getAllMatchingFormatters(cont.getType(), cont.getWidth()).size() > 0)) { 1674 return true; 1675 } 1676 } 1677 } catch (CmsException e) { 1678 LOG.warn(e.getLocalizedMessage(), e); 1679 1680 } 1681 return false; 1682 } 1683 1684 /** 1685 * Returns the value of the "create contents locally" flag.<p> 1686 * 1687 * If this flag is set, contents of types configured in a super-sitemap will be created in the sub-sitemap (if the user 1688 * creates them from the sub-sitemap). 1689 * 1690 * @return the "create contents locally" flag 1691 */ 1692 public boolean isCreateContentsLocally() { 1693 1694 return m_data.isCreateContentsLocally(); 1695 } 1696 1697 /** 1698 * Returns the value of the "discard inherited model pages" flag.<p> 1699 * 1700 * If this flag is set, inherited model pages will be discarded for this sitemap.<p> 1701 * 1702 * @return the "discard inherited model pages" flag 1703 */ 1704 public boolean isDiscardInheritedModelPages() { 1705 1706 return m_data.isDiscardInheritedModelPages(); 1707 } 1708 1709 /** 1710 * Returns the value of the "discard inherited properties" flag.<p> 1711 * 1712 * If this is flag is set, inherited property definitions will be discarded for this sitemap.<p> 1713 * 1714 * @return the "discard inherited properties" flag.<p> 1715 */ 1716 public boolean isDiscardInheritedProperties() { 1717 1718 return m_data.isDiscardInheritedProperties(); 1719 } 1720 1721 /** 1722 * Returns the value of the "discard inherited types" flag.<p> 1723 * 1724 * If this flag is set, inherited resource types from a super-sitemap will be discarded for this sitemap.<p> 1725 * 1726 * @return the "discard inherited types" flag 1727 */ 1728 public boolean isDiscardInheritedTypes() { 1729 1730 return m_data.isDiscardInheritedTypes(); 1731 } 1732 1733 /** 1734 * True if detail contents outside this sitemap should not be rendered in detail pages from this sitemap. 1735 * 1736 * @return true if detail contents outside this sitemap should not be rendered in detail pages from this sitemap. 1737 */ 1738 public boolean isExcludeExternalDetailContents() { 1739 1740 return m_data.isExcludeExternalDetailContents(); 1741 } 1742 1743 /** 1744 * Checks if dynamic functions not matching any containers should be hidden. 1745 * 1746 * @return true if dynamic functions not matching any containers should be hidden 1747 */ 1748 public boolean isHideNonMatchingFunctions() { 1749 1750 return getDisabledFunctionsMode(CmsGalleryDisabledTypesMode.hide) == CmsGalleryDisabledTypesMode.hide; 1751 } 1752 1753 /** 1754 * Returns true if the subsite should be included in the site selector. 1755 * 1756 * @return true if the subsite should be included in the site selector 1757 */ 1758 public boolean isIncludeInSiteSelector() { 1759 1760 return m_configSequence.getConfig().isIncludeInSiteSelector(); 1761 } 1762 1763 /** 1764 * Returns true if this is a module configuration instead of a normal sitemap configuration.<p> 1765 * 1766 * @return true if this is a module configuration 1767 */ 1768 public boolean isModuleConfiguration() { 1769 1770 return m_data.isModuleConfig(); 1771 } 1772 1773 /** 1774 * Returns true if detail pages from this sitemap should be preferred for links to contents in this sitemap.<p> 1775 * 1776 * @return true if detail pages from this sitemap should be preferred for links to contents in this sitemap 1777 */ 1778 public boolean isPreferDetailPagesForLocalContents() { 1779 1780 return m_data.isPreferDetailPagesForLocalContents(); 1781 } 1782 1783 /** 1784 * Checks if any formatter with the given JSP id has the 'search content' option set to true. 1785 * 1786 * @param jspId the structure id of a formatter JSP 1787 * @return true if any of the formatters 1788 */ 1789 public boolean isSearchContentFormatter(CmsUUID jspId) { 1790 1791 for (I_CmsFormatterBean formatter : getFormattersByJspId().get(jspId)) { 1792 if (formatter.isSearchContent()) { 1793 return true; 1794 } 1795 } 1796 return false; 1797 } 1798 1799 /** 1800 * Returns true if the new container page format, which uses formatter keys (but also is different in other ways from the new format 1801 * 1802 * @return true if formatter keys should be used 1803 */ 1804 public boolean isUseFormatterKeys() { 1805 1806 Boolean result = m_data.getUseFormatterKeys(); 1807 if (result != null) { 1808 LOG.debug("isUseFormatterKeys - found value " + result + " at " + getBasePath()); 1809 return result.booleanValue(); 1810 } 1811 CmsADEConfigData parent = parent(); 1812 if (parent != null) { 1813 return parent.isUseFormatterKeys(); 1814 } 1815 boolean defaultValue = true; 1816 LOG.debug("isUseFormatterKeys - using defaultValue " + defaultValue); 1817 return defaultValue; 1818 } 1819 1820 /** 1821 * Fetches the parent configuration of this configuration.<p> 1822 * 1823 * If this configuration is a sitemap configuration with no direct parent configuration, 1824 * the module configuration will be returned. If this configuration already is a module configuration, 1825 * null will be returned.<p> 1826 * 1827 * @return the parent configuration 1828 */ 1829 public CmsADEConfigData parent() { 1830 1831 Optional<CmsADEConfigurationSequence> parentPath = m_configSequence.getParent(); 1832 if (parentPath.isPresent()) { 1833 CmsADEConfigDataInternal internalData = parentPath.get().getConfig(); 1834 CmsADEConfigData result = new CmsADEConfigData(internalData, m_cache, parentPath.get()); 1835 result.m_extraInfo = m_extraInfo; 1836 return result; 1837 } else { 1838 return null; 1839 } 1840 } 1841 1842 /** 1843 * Returns true if the sitemap attribute editor should be available in this subsite. 1844 * 1845 * @return true if the sitemap attribute editor dialog should be available 1846 */ 1847 public boolean shouldShowSitemapAttributeDialog() { 1848 1849 return getAttributeEditorConfiguration().getAttributeDefinitions().size() > 0; 1850 } 1851 1852 /** 1853 * Clears the internal formatter caches. 1854 * 1855 * <p>This should only be used for test cases. 1856 */ 1857 protected void clearCaches() { 1858 1859 m_activeFormatters = null; 1860 m_activeFormattersByKey = null; 1861 m_formattersByKey = null; 1862 m_formattersByJspId = null; 1863 m_formattersByTypeCache.invalidateAll(); 1864 } 1865 1866 /** 1867 * Creates the content directory for this configuration node if possible.<p> 1868 * 1869 * @throws CmsException if something goes wrong 1870 */ 1871 protected void createContentDirectory() throws CmsException { 1872 1873 if (!isModuleConfiguration()) { 1874 String contentFolder = getContentFolderPath(); 1875 if (!getCms().existsResource(contentFolder)) { 1876 getCms().createResource( 1877 contentFolder, 1878 OpenCms.getResourceManager().getResourceType(CmsResourceTypeFolder.getStaticTypeName())); 1879 } 1880 } 1881 } 1882 1883 /** 1884 * Gets the CMS object used for VFS operations.<p> 1885 * 1886 * @return the CMS object used for VFS operations 1887 */ 1888 protected CmsObject getCms() { 1889 1890 return m_cache.getCms(); 1891 } 1892 1893 /** 1894 * Gets the CMS object used for VFS operations.<p> 1895 * 1896 * @return the CMS object 1897 */ 1898 protected CmsObject getCmsObject() { 1899 1900 return getCms(); 1901 } 1902 1903 /** 1904 * Helper method to converts a list of detail pages to a map from type names to lists of detail pages for each type.<p> 1905 * 1906 * @param detailPages the list of detail pages 1907 * 1908 * @return the map of detail pages 1909 */ 1910 protected Map<String, List<CmsDetailPageInfo>> getDetailPagesMap(List<CmsDetailPageInfo> detailPages) { 1911 1912 Map<String, List<CmsDetailPageInfo>> result = Maps.newHashMap(); 1913 for (CmsDetailPageInfo detailpage : detailPages) { 1914 String type = detailpage.getType(); 1915 if (!result.containsKey(type)) { 1916 result.put(type, new ArrayList<CmsDetailPageInfo>()); 1917 } 1918 result.get(type).add(detailpage); 1919 } 1920 return result; 1921 } 1922 1923 /** 1924 * Collects the folder types in a map.<p> 1925 * 1926 * @return the map of folder types 1927 * 1928 * @throws CmsException if something goes wrong 1929 */ 1930 protected Map<String, String> getFolderTypes() throws CmsException { 1931 1932 Map<String, String> result = new HashMap<String, String>(); 1933 CmsObject cms = OpenCms.initCmsObject(getCms()); 1934 if (m_data.isModuleConfig()) { 1935 Set<String> siteRoots = OpenCms.getSiteManager().getSiteRoots(); 1936 for (String siteRoot : siteRoots) { 1937 cms.getRequestContext().setSiteRoot(siteRoot); 1938 for (CmsResourceTypeConfig config : getResourceTypes()) { 1939 if (!config.isDetailPagesDisabled()) { 1940 String typeName = config.getTypeName(); 1941 if (!config.isPageRelative()) { // elements stored with container pages can not be used as detail contents 1942 String folderPath = config.getFolderPath(cms, null); 1943 result.put(CmsStringUtil.joinPaths(folderPath, "/"), typeName); 1944 } 1945 } 1946 } 1947 } 1948 } else { 1949 for (CmsResourceTypeConfig config : getResourceTypes()) { 1950 if (!config.isDetailPagesDisabled()) { 1951 String typeName = config.getTypeName(); 1952 if (!config.isPageRelative()) { // elements stored with container pages can not be used as detail contents 1953 String folderPath = config.getFolderPath(getCms(), null); 1954 result.put(CmsStringUtil.joinPaths(folderPath, "/"), typeName); 1955 } 1956 } 1957 } 1958 } 1959 return result; 1960 } 1961 1962 /** 1963 * Gets the formatter configuration for a resource type.<p> 1964 * 1965 * @param cms the current CMS context 1966 * @param resType the resource type 1967 * @param schemaFormatters the resource schema formatters 1968 * 1969 * @return the configuration of formatters for the resource type 1970 */ 1971 protected CmsFormatterConfiguration getFormatters( 1972 CmsObject cms, 1973 I_CmsResourceType resType, 1974 CmsFormatterConfiguration schemaFormatters) { 1975 1976 String typeName = resType.getTypeName(); 1977 List<I_CmsFormatterBean> formatters = new ArrayList<I_CmsFormatterBean>(); 1978 Set<String> types = new HashSet<String>(); 1979 types.add(typeName); 1980 for (CmsFormatterChangeSet changeSet : getFormatterChangeSets()) { 1981 if (changeSet != null) { 1982 changeSet.applyToTypes(types); 1983 } 1984 } 1985 1986 if ((schemaFormatters != null) && types.contains(typeName)) { 1987 for (I_CmsFormatterBean formatter : schemaFormatters.getAllFormatters()) { 1988 formatters.add(formatter); 1989 } 1990 } 1991 1992 try { 1993 List<I_CmsFormatterBean> formattersForType = m_formattersByTypeCache.get(typeName); 1994 formatters.addAll(formattersForType); 1995 } catch (ExecutionException e) { 1996 LOG.error(e.getLocalizedMessage(), e); 1997 1998 } 1999 return CmsFormatterConfiguration.create(cms, formatters); 2000 } 2001 2002 /** 2003 * Gets the formatters from the schema.<p> 2004 * 2005 * @param cms the current CMS context 2006 * @param res the resource for which the formatters should be retrieved 2007 * 2008 * @return the formatters from the schema 2009 */ 2010 protected CmsFormatterConfiguration getFormattersFromSchema(CmsObject cms, CmsResource res) { 2011 2012 try { 2013 return OpenCms.getResourceManager().getResourceType(res.getTypeId()).getFormattersForResource(cms, res); 2014 } catch (CmsException e) { 2015 LOG.error(e.getLocalizedMessage(), e); 2016 return CmsFormatterConfiguration.EMPTY_CONFIGURATION; 2017 } 2018 } 2019 2020 /** 2021 * Gets the metadata about how this configuration was referenced. 2022 * 2023 * @return the metadata 2024 */ 2025 protected ConfigReferenceMeta getMeta() { 2026 2027 return m_configSequence.getMeta(); 2028 } 2029 2030 /** 2031 * Internal method for getting the function references.<p> 2032 * 2033 * @return the function references 2034 */ 2035 protected List<CmsFunctionReference> internalGetFunctionReferences() { 2036 2037 CmsADEConfigData parentData = parent(); 2038 if ((parentData == null)) { 2039 if (m_data.isModuleConfig()) { 2040 return Collections.unmodifiableList(m_data.getFunctionReferences()); 2041 } else { 2042 return Lists.newArrayList(); 2043 } 2044 } else { 2045 return parentData.internalGetFunctionReferences(); 2046 2047 } 2048 } 2049 2050 /** 2051 * Helper method for getting the list of resource types.<p> 2052 * 2053 * @param filterDisabled true if disabled types should be filtered from the result 2054 * 2055 * @return the list of resource types 2056 */ 2057 protected List<CmsResourceTypeConfig> internalGetResourceTypes(boolean filterDisabled) { 2058 2059 CmsADEConfigData parentData = parent(); 2060 List<CmsResourceTypeConfig> parentResourceTypes = null; 2061 if (parentData == null) { 2062 parentResourceTypes = Lists.newArrayList(); 2063 } else { 2064 parentResourceTypes = Lists.newArrayList(); 2065 for (CmsResourceTypeConfig typeConfig : parentData.internalGetResourceTypes(false)) { 2066 CmsResourceTypeConfig copiedType = typeConfig.copy( 2067 m_data.isDiscardInheritedTypes() && !getMeta().isSkipRemovals()); 2068 parentResourceTypes.add(copiedType); 2069 } 2070 } 2071 String template = getMeta().getTemplate(); 2072 List<CmsResourceTypeConfig> result = combineConfigurationElements( 2073 parentResourceTypes, 2074 m_data.getOwnResourceTypes().stream().map(type -> type.markWithTemplate(template)).collect( 2075 Collectors.toList()), 2076 true); 2077 if (m_data.isCreateContentsLocally()) { 2078 CmsContentFolderOption contentFolderOption = getContentFolderOption(); 2079 CmsUUID folderId = null; 2080 if (contentFolderOption != null) { 2081 folderId = contentFolderOption.getFolderId(); 2082 } 2083 String basePath = CmsStringUtil.joinPaths(m_data.getBasePath(), CmsADEManager.CONTENT_FOLDER_NAME); 2084 if (folderId != null) { 2085 try { 2086 CmsResource resource = getCms().readResource(folderId, CmsResourceFilter.ALL); 2087 if (!resource.isFolder()) { 2088 LOG.error( 2089 "Resource configured as content folder isn't a folder: " 2090 + resource.getRootPath() 2091 + " (context: " 2092 + getBasePath() 2093 + ")"); 2094 } else { 2095 basePath = CmsFileUtil.removeTrailingSeparator(resource.getRootPath()); 2096 } 2097 } catch (Exception e) { 2098 LOG.error(e.getLocalizedMessage(), e); 2099 } 2100 } 2101 2102 for (CmsResourceTypeConfig typeConfig : result) { 2103 typeConfig.updateBasePath(basePath); 2104 } 2105 } 2106 if (filterDisabled) { 2107 Iterator<CmsResourceTypeConfig> iter = result.iterator(); 2108 while (iter.hasNext()) { 2109 CmsResourceTypeConfig typeConfig = iter.next(); 2110 if (typeConfig.isDisabled()) { 2111 iter.remove(); 2112 } 2113 } 2114 } 2115 if (getTypeOrderingMode() == CmsTypeOrderingMode.byDisplayOrder) { 2116 Collections.sort(result, (a, b) -> Integer.compare(a.getOrder(), b.getOrder())); 2117 } 2118 return result; 2119 } 2120 2121 /** 2122 * Merges two lists of detail pages, one from a parent configuration and one from a child configuration.<p> 2123 * 2124 * @param parentDetailPages the parent's detail pages 2125 * @param ownDetailPages the child's detail pages 2126 * 2127 * @return the merged detail pages 2128 */ 2129 protected List<CmsDetailPageInfo> mergeDetailPages( 2130 List<CmsDetailPageInfo> parentDetailPages, 2131 List<CmsDetailPageInfo> ownDetailPages) { 2132 2133 List<CmsDetailPageInfo> parentDetailPageCopies = Lists.newArrayList(); 2134 for (CmsDetailPageInfo info : parentDetailPages) { 2135 parentDetailPageCopies.add(info.copyAsInherited()); 2136 } 2137 2138 List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>(); 2139 Map<String, List<CmsDetailPageInfo>> resultDetailPageMap = Maps.newHashMap(); 2140 Map<String, List<CmsDetailPageInfo>> parentPagesGroupedByType = getDetailPagesMap(parentDetailPageCopies); 2141 Map<String, List<CmsDetailPageInfo>> childPagesGroupedByType = getDetailPagesMap(ownDetailPages); 2142 Set<String> allTypes = new HashSet<>(); 2143 allTypes.addAll(parentPagesGroupedByType.keySet()); 2144 allTypes.addAll(childPagesGroupedByType.keySet()); 2145 for (String type : allTypes) { 2146 2147 List<CmsDetailPageInfo> parentPages = parentPagesGroupedByType.get(type); 2148 List<CmsDetailPageInfo> childPages = childPagesGroupedByType.get(type); 2149 List<CmsDetailPageInfo> merged = mergeDetailPagesForType(parentPages, childPages); 2150 resultDetailPageMap.put(type, merged); 2151 } 2152 result = new ArrayList<CmsDetailPageInfo>(); 2153 for (List<CmsDetailPageInfo> pages : resultDetailPageMap.values()) { 2154 result.addAll(pages); 2155 } 2156 return result; 2157 } 2158 2159 /** 2160 * Helper method to correct paths in detail page beans if the corresponding resources have been moved.<p> 2161 * 2162 * @param detailPages the original list of detail pages 2163 * 2164 * @return the corrected list of detail pages 2165 */ 2166 protected List<CmsDetailPageInfo> updateUris(List<CmsDetailPageInfo> detailPages) { 2167 2168 List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>(); 2169 for (CmsDetailPageInfo page : detailPages) { 2170 CmsUUID structureId = page.getId(); 2171 try { 2172 String rootPath = OpenCms.getADEManager().getRootPath( 2173 structureId, 2174 getCms().getRequestContext().getCurrentProject().isOnlineProject()); 2175 String iconClasses; 2176 if (page.getType().startsWith(CmsDetailPageInfo.FUNCTION_PREFIX)) { 2177 iconClasses = CmsIconUtil.getIconClasses(CmsXmlDynamicFunctionHandler.TYPE_FUNCTION, null, false); 2178 } else { 2179 iconClasses = CmsIconUtil.getIconClasses(page.getType(), null, false); 2180 } 2181 CmsDetailPageInfo correctedPage = new CmsDetailPageInfo( 2182 structureId, 2183 rootPath, 2184 page.getType(), 2185 page.getQualifier(), 2186 page.getFolders(), 2187 iconClasses); 2188 result.add(page.isInherited() ? correctedPage.copyAsInherited() : correctedPage); 2189 } catch (CmsException e) { 2190 LOG.warn(e.getLocalizedMessage(), e); 2191 } 2192 } 2193 return result; 2194 } 2195 2196 /** 2197 * Gets a multimap of active formatters for which a formatter key is defined, with the formatter keys as map keys. 2198 * 2199 * @return the map of active formatters by key 2200 */ 2201 private Multimap<String, I_CmsFormatterBean> getActiveFormattersByKey() { 2202 2203 if (m_activeFormattersByKey == null) { 2204 ArrayListMultimap<String, I_CmsFormatterBean> activeFormattersByKey = ArrayListMultimap.create(); 2205 for (I_CmsFormatterBean formatter : getActiveFormatters().values()) { 2206 for (String key : formatter.getAllKeys()) { 2207 activeFormattersByKey.put(key, formatter); 2208 } 2209 } 2210 m_activeFormattersByKey = activeFormattersByKey; 2211 } 2212 return m_activeFormattersByKey; 2213 } 2214 2215 /** 2216 * Gets a formatter with the given key from a multimap, and warns if there are multiple values 2217 * for the key. 2218 * 2219 * @param formatterMap the formatter multimap 2220 * @param name the formatter key 2221 * @param noWarn if true, disables warnings 2222 * @return the formatter for the key (null if none are found, the first one if multiple are found) 2223 */ 2224 private I_CmsFormatterBean getFormatterAndWarnIfAmbiguous( 2225 Multimap<String, I_CmsFormatterBean> formatterMap, 2226 String name, 2227 boolean noWarn) { 2228 2229 I_CmsFormatterBean result; 2230 result = null; 2231 Collection<I_CmsFormatterBean> activeForKey = formatterMap.get(name); 2232 if (activeForKey.size() > 0) { 2233 if (activeForKey.size() > 1) { 2234 if (!noWarn) { 2235 String labels = "" 2236 + activeForKey.stream().map(this::getFormatterLabel).collect(Collectors.toList()); 2237 String message = "Ambiguous formatter for key '" 2238 + name 2239 + "' at '" 2240 + getBasePath() 2241 + "': found " 2242 + labels; 2243 LOG.warn(message); 2244 OpenCmsServlet.withRequestCache( 2245 rc -> rc.addLog(REQUEST_LOG_CHANNEL, "warn", REQ_LOG_PREFIX + message)); 2246 } 2247 } 2248 result = activeForKey.iterator().next(); 2249 } 2250 return result; 2251 } 2252 2253 /** 2254 * Gets a user-friendly formatter label to use for logging. 2255 * 2256 * @param formatter a formatter bean 2257 * @return the formatter label for the log 2258 */ 2259 private String getFormatterLabel(I_CmsFormatterBean formatter) { 2260 2261 return formatter.getLocation() != null ? formatter.getLocation() : formatter.getId(); 2262 } 2263 2264 /** 2265 * Gets formatters by JSP id. 2266 * 2267 * @return the multimap from JSP id to formatter beans 2268 */ 2269 private Multimap<CmsUUID, I_CmsFormatterBean> getFormattersByJspId() { 2270 2271 if (m_formattersByJspId == null) { 2272 ArrayListMultimap<CmsUUID, I_CmsFormatterBean> formattersByJspId = ArrayListMultimap.create(); 2273 for (I_CmsFormatterBean formatter : getCachedFormatters().getFormatters().values()) { 2274 formattersByJspId.put(formatter.getJspStructureId(), formatter); 2275 } 2276 m_formattersByJspId = formattersByJspId; 2277 } 2278 return m_formattersByJspId; 2279 } 2280 2281 /** 2282 * Gets a multimap of the formatters for which a formatter key is defined, with the formatter keys as map keys. 2283 * 2284 * @return the map of formatters by key 2285 */ 2286 private Multimap<String, I_CmsFormatterBean> getFormattersByKey() { 2287 2288 if (m_formattersByKey == null) { 2289 ArrayListMultimap<String, I_CmsFormatterBean> formattersByKey = ArrayListMultimap.create(); 2290 for (I_CmsFormatterBean formatter : getCachedFormatters().getFormatters().values()) { 2291 for (String key : formatter.getAllKeys()) { 2292 formattersByKey.put(key, formatter); 2293 } 2294 } 2295 m_formattersByKey = formattersByKey; 2296 } 2297 return m_formattersByKey; 2298 } 2299 2300 /** 2301 * Merges detail pages for a specific resource type from a parent and child sitemap. 2302 * 2303 * @param parentPages the detail pages from the parent sitemap 2304 * @param childPages the detail pages from the child sitemap 2305 * @return the merged detail pages 2306 */ 2307 private List<CmsDetailPageInfo> mergeDetailPagesForType( 2308 List<CmsDetailPageInfo> parentPages, 2309 List<CmsDetailPageInfo> childPages) { 2310 2311 List<CmsDetailPageInfo> merged = null; 2312 if ((parentPages != null) && (childPages != null)) { 2313 if (childPages.stream().anyMatch(page -> page.getQualifier() == null)) { 2314 // If the child detail pages contain one with an unqualified type, they completely override the parent detail pages. 2315 merged = childPages; 2316 } else { 2317 // Otherwise, all child pages with a specific merge key override all parent pages with the same merge key 2318 Map<String, List<CmsDetailPageInfo>> pagesGroupedByMergeKey = new HashMap<>(); 2319 for (List<CmsDetailPageInfo> pages : Arrays.asList(parentPages, childPages)) { 2320 pagesGroupedByMergeKey.putAll( 2321 pages.stream().collect(Collectors.groupingBy(page -> page.getMergeKey()))); 2322 } 2323 // combine page lists for all merge keys into a single page list 2324 merged = pagesGroupedByMergeKey.entrySet().stream().flatMap(entry -> entry.getValue().stream()).collect( 2325 Collectors.toList()); 2326 } 2327 } else if (parentPages != null) { 2328 merged = parentPages; 2329 } else if (childPages != null) { 2330 merged = childPages; 2331 } else { 2332 merged = new ArrayList<>(); 2333 } 2334 return merged; 2335 } 2336 2337}