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