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