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.CmsADEConfigData.DetailInfo; 031import org.opencms.ade.configuration.CmsElementView.ElementViewComparator; 032import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCache; 033import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCacheState; 034import org.opencms.ade.configuration.plugins.CmsTemplatePlugin; 035import org.opencms.ade.configuration.plugins.CmsTemplatePluginFinder; 036import org.opencms.ade.containerpage.inherited.CmsContainerConfigurationCache; 037import org.opencms.ade.containerpage.inherited.CmsContainerConfigurationWriter; 038import org.opencms.ade.containerpage.inherited.CmsInheritedContainerState; 039import org.opencms.ade.detailpage.CmsDetailPageConfigurationWriter; 040import org.opencms.ade.detailpage.CmsDetailPageInfo; 041import org.opencms.ade.detailpage.I_CmsDetailPageHandler; 042import org.opencms.ade.upload.CmsUploadWarningTable; 043import org.opencms.configuration.CmsSystemConfiguration; 044import org.opencms.db.I_CmsProjectDriver; 045import org.opencms.file.CmsFile; 046import org.opencms.file.CmsObject; 047import org.opencms.file.CmsProject; 048import org.opencms.file.CmsRequestContext; 049import org.opencms.file.CmsResource; 050import org.opencms.file.CmsResourceFilter; 051import org.opencms.file.CmsUser; 052import org.opencms.file.types.CmsResourceTypeFunctionConfig; 053import org.opencms.file.types.CmsResourceTypeXmlContainerPage; 054import org.opencms.file.types.CmsResourceTypeXmlContent; 055import org.opencms.file.types.I_CmsResourceType; 056import org.opencms.gwt.shared.CmsGwtConstants; 057import org.opencms.gwt.shared.CmsPermissionInfo; 058import org.opencms.gwt.shared.CmsTemplateContextInfo; 059import org.opencms.i18n.CmsEncoder; 060import org.opencms.i18n.CmsLocaleManager; 061import org.opencms.json.JSONArray; 062import org.opencms.json.JSONException; 063import org.opencms.json.JSONObject; 064import org.opencms.jsp.CmsJspNavBuilder; 065import org.opencms.jsp.CmsJspNavElement; 066import org.opencms.jsp.CmsJspTagLink; 067import org.opencms.jsp.util.CmsJspStandardContextBean; 068import org.opencms.jsp.util.CmsTemplatePluginWrapper; 069import org.opencms.loader.CmsLoaderException; 070import org.opencms.main.CmsException; 071import org.opencms.main.CmsLog; 072import org.opencms.main.OpenCms; 073import org.opencms.main.OpenCmsServlet; 074import org.opencms.monitor.CmsMemoryMonitor; 075import org.opencms.relations.CmsRelation; 076import org.opencms.relations.CmsRelationFilter; 077import org.opencms.security.CmsPermissionSet; 078import org.opencms.security.CmsRole; 079import org.opencms.util.CmsFileUtil; 080import org.opencms.util.CmsRequestUtil; 081import org.opencms.util.CmsStringUtil; 082import org.opencms.util.CmsUUID; 083import org.opencms.util.CmsWaitHandle; 084import org.opencms.workplace.explorer.CmsExplorerTypeSettings; 085import org.opencms.workplace.explorer.CmsResourceUtil; 086import org.opencms.xml.CmsXmlContentDefinition; 087import org.opencms.xml.CmsXmlException; 088import org.opencms.xml.containerpage.CmsADECache; 089import org.opencms.xml.containerpage.CmsADECacheSettings; 090import org.opencms.xml.containerpage.CmsContainerElementBean; 091import org.opencms.xml.containerpage.I_CmsFormatterBean; 092import org.opencms.xml.containerpage.Messages; 093import org.opencms.xml.content.CmsXmlContent; 094import org.opencms.xml.content.CmsXmlContentFactory; 095import org.opencms.xml.content.CmsXmlContentProperty; 096import org.opencms.xml.content.CmsXmlContentProperty.Visibility; 097import org.opencms.xml.content.CmsXmlContentPropertyHelper; 098import org.opencms.xml.content.I_CmsXmlContentHandler; 099import org.opencms.xml.types.I_CmsXmlContentValue; 100 101import java.util.ArrayList; 102import java.util.Collections; 103import java.util.Comparator; 104import java.util.HashMap; 105import java.util.HashSet; 106import java.util.Iterator; 107import java.util.LinkedHashMap; 108import java.util.List; 109import java.util.Locale; 110import java.util.Map; 111import java.util.Map.Entry; 112import java.util.Set; 113import java.util.function.Predicate; 114import java.util.stream.Collectors; 115import java.util.stream.Stream; 116 117import org.apache.commons.logging.Log; 118 119import com.google.common.collect.Lists; 120import com.google.common.collect.Maps; 121import com.google.common.collect.Multimap; 122 123import jakarta.servlet.ServletRequest; 124import jakarta.servlet.http.HttpServletRequest; 125import jakarta.servlet.http.HttpServletResponse; 126 127/** 128 * This is the main class used to access the ADE configuration and also accomplish some other related tasks 129 * like loading/saving favorite and recent lists.<p> 130 */ 131public class CmsADEManager { 132 133 /** JSON property name constant. */ 134 protected enum FavListProp { 135 /** element property. */ 136 ELEMENT, 137 /** formatter property. */ 138 FORMATTER, 139 /** properties property. */ 140 PROPERTIES; 141 } 142 143 /** 144 * A status enum for the initialization status.<p> 145 */ 146 protected enum Status { 147 /** already initialized. */ 148 initialized, 149 /** currently initializing. */ 150 initializing, 151 /** not initialized. */ 152 notInitialized 153 } 154 155 /** The client id separator. */ 156 public static final String CLIENT_ID_SEPERATOR = "#"; 157 158 /** The configuration file name. */ 159 public static final String CONFIG_FILE_NAME = ".config"; 160 161 /** The name of the sitemap configuration file type. */ 162 public static final String CONFIG_FOLDER_TYPE = "content_folder"; 163 164 /** The path for sitemap configuration files relative from the base path. */ 165 public static final String CONFIG_SUFFIX = "/" 166 + CmsADEManager.CONTENT_FOLDER_NAME 167 + "/" 168 + CmsADEManager.CONFIG_FILE_NAME; 169 170 /** The name of the sitemap configuration file type. */ 171 public static final String CONFIG_TYPE = "sitemap_config"; 172 173 /** The content folder name. */ 174 public static final String CONTENT_FOLDER_NAME = ".content"; 175 176 /** The default detail page type name. */ 177 public static final String DEFAULT_DETAILPAGE_TYPE = CmsGwtConstants.DEFAULT_DETAILPAGE_TYPE; 178 179 /** Default favorite/recent list size constant. */ 180 public static final int DEFAULT_ELEMENT_LIST_SIZE = 10; 181 182 /** The name of the element view configuration file type. */ 183 public static final String ELEMENT_VIEW_TYPE = "elementview"; 184 185 /** The name of the module configuration file type. */ 186 public static final String MODULE_CONFIG_TYPE = "module_config"; 187 188 /** The aADE configuration module name. */ 189 public static final String MODULE_NAME_ADE_CONFIG = "org.opencms.base"; 190 191 /** Node name for the nav level link value. */ 192 public static final String N_LINK = "Link"; 193 194 /** Node name for the nav level type value. */ 195 public static final String N_TYPE = "Type"; 196 197 /** The path to the sitemap editor JSP. */ 198 public static final String PATH_SITEMAP_EDITOR_JSP = "/system/workplace/commons/sitemap.jsp"; 199 200 /** User additional info key constant. */ 201 protected static final String ADDINFO_ADE_FAVORITE_LIST = "ADE_FAVORITE_LIST"; 202 203 /** User additional info key constant. */ 204 protected static final String ADDINFO_ADE_RECENT_LIST = "ADE_RECENT_LIST"; 205 206 /** User additional info key constant. */ 207 protected static final String ADDINFO_ADE_SHOW_EDITOR_HELP = "ADE_SHOW_EDITOR_HELP"; 208 209 /** The logger instance for this class. */ 210 private static final Log LOG = CmsLog.getLog(CmsADEManager.class); 211 212 /** The cache instance. */ 213 private CmsADECache m_cache; 214 215 /** The sitemap configuration file type. */ 216 private I_CmsResourceType m_configType; 217 218 /** The detail page handler. */ 219 private I_CmsDetailPageHandler m_detailPageHandler; 220 221 /** The element view configuration file type. */ 222 private I_CmsResourceType m_elementViewType; 223 224 /** The initialization status. */ 225 private Status m_initStatus = Status.notInitialized; 226 227 /** The module configuration file type. */ 228 private I_CmsResourceType m_moduleConfigType; 229 230 /** The online cache instance. */ 231 private CmsConfigurationCache m_offlineCache; 232 233 /** The offline CMS context. */ 234 private CmsObject m_offlineCms; 235 236 /** The offline inherited container configuration cache. */ 237 private CmsContainerConfigurationCache m_offlineContainerConfigurationCache; 238 239 /** The detail id cache for the Offline project. */ 240 private CmsDetailNameCache m_offlineDetailIdCache; 241 242 /** The offline formatter bean cache. */ 243 private CmsFormatterConfigurationCache m_offlineFormatterCache; 244 245 /** The offline cache instance. */ 246 private CmsConfigurationCache m_onlineCache; 247 248 /** The online CMS context. */ 249 private CmsObject m_onlineCms; 250 251 /** The online inherited container configuration cache. */ 252 private CmsContainerConfigurationCache m_onlineContainerConfigurationCache; 253 254 /** The Online project detail id cache. */ 255 private CmsDetailNameCache m_onlineDetailIdCache; 256 257 /** The online formatter bean cache. */ 258 private CmsFormatterConfigurationCache m_onlineFormatterCache; 259 260 /** ADE parameters. */ 261 private Map<String, String> m_parameters; 262 263 /** The table of upload warnings. */ 264 private CmsUploadWarningTable m_uploadWarningTable = new CmsUploadWarningTable(); 265 266 /** The providers used to inject additional configuration details into the sitemap configuration. */ 267 private List<I_CmsSitemapExtraInfoProvider> m_sitemapExtraInfoProviders = new ArrayList<>(); 268 269 /** 270 * Creates a new ADE manager.<p> 271 * 272 * @param adminCms a CMS context with admin privileges 273 * @param memoryMonitor the memory monitor instance 274 * @param systemConfiguration the system configuration 275 */ 276 public CmsADEManager( 277 CmsObject adminCms, 278 CmsMemoryMonitor memoryMonitor, 279 CmsSystemConfiguration systemConfiguration) { 280 281 // initialize the ade cache 282 CmsADECacheSettings cacheSettings = systemConfiguration.getAdeCacheSettings(); 283 if (cacheSettings == null) { 284 cacheSettings = new CmsADECacheSettings(); 285 } 286 m_onlineCms = adminCms; 287 m_cache = new CmsADECache(memoryMonitor, cacheSettings); 288 m_parameters = new LinkedHashMap<String, String>(systemConfiguration.getAdeParameters()); 289 m_detailPageHandler = systemConfiguration.getDetailPageHandler(); 290 // further initialization is done by the initialize() method. We don't do that in the constructor, 291 // because during the setup the configuration resource types don't exist yet. 292 } 293 294 /** 295 * Adds a wait handle for the next cache update to a formatter configuration.<p> 296 * 297 * @param online true if we want to add a wait handle to the online cache, else the offline cache 298 * @return the wait handle that has been added 299 */ 300 public CmsWaitHandle addFormatterCacheWaitHandle(boolean online) { 301 302 CmsWaitHandle handle = new CmsWaitHandle(true); // single use wait handle 303 CmsFormatterConfigurationCache cache = online ? m_onlineFormatterCache : m_offlineFormatterCache; 304 cache.addWaitHandle(handle); 305 return handle; 306 } 307 308 /** 309 * Adds a sitemap extra info provider. 310 * 311 * @param provider the provider to add 312 */ 313 public void addSitemapExtraInfoProvider(I_CmsSitemapExtraInfoProvider provider) { 314 315 m_sitemapExtraInfoProviders.add(provider); 316 } 317 318 /** 319 * Checks if the sitemap config can be edited by the user in the given CMS context. 320 * 321 * <p>Note: Even if this returns true, there may be other reasons preventing the sitemap configuration from being edited by the user. 322 * 323 * @param cms the CMS context to check 324 * @return false if the user should not be able to edit the sitemap configuration 325 */ 326 public boolean canEditSitemapConfiguration(CmsObject cms) { 327 328 CmsRole role = getRoleForSitemapConfigEditing(); 329 if (role == null) { 330 return true; 331 } 332 return OpenCms.getRoleManager().hasRole(cms, role); 333 } 334 335 /** 336 * Finds the entry point to a sitemap.<p> 337 * 338 * @param cms the CMS context 339 * @param openPath the resource path to find the sitemap to 340 * 341 * @return the sitemap entry point 342 */ 343 public String findEntryPoint(CmsObject cms, String openPath) { 344 345 CmsADEConfigData configData = lookupConfiguration(cms, openPath); 346 String result = configData.getBasePath(); 347 if (result == null) { 348 return cms.getRequestContext().addSiteRoot("/"); 349 } 350 return result; 351 } 352 353 /** 354 * Flushes inheritance group changes so the cache is updated.<p> 355 * 356 * This is useful for test cases. 357 */ 358 public void flushInheritanceGroupChanges() { 359 360 m_offlineContainerConfigurationCache.flushUpdates(); 361 m_onlineContainerConfigurationCache.flushUpdates(); 362 } 363 364 /** 365 * Gets the complete list of beans for the currently configured detail pages.<p> 366 * 367 * @param cms the CMS context to use 368 * 369 * @return the list of detail page infos 370 */ 371 public List<CmsDetailPageInfo> getAllDetailPages(CmsObject cms) { 372 373 return getCacheState(isOnline(cms)).getAllDetailPages(); 374 } 375 376 /** 377 * Gets the containerpage cache instance.<p> 378 * 379 * @return the containerpage cache instance 380 */ 381 public CmsADECache getCache() { 382 383 return m_cache; 384 } 385 386 /** 387 * Gets the cached formatter beans.<p> 388 * 389 * @param online true if the Online project formatters should be returned, false for the Offline formatters 390 * 391 * @return the formatter configuration cache state 392 */ 393 public CmsFormatterConfigurationCacheState getCachedFormatters(boolean online) { 394 395 CmsFormatterConfigurationCache cache = online ? m_onlineFormatterCache : m_offlineFormatterCache; 396 return cache.getState(); 397 } 398 399 /** 400 * Gets the current ADE configuration cache state.<p> 401 * 402 * @param online true if you want the online state, false for the offline state 403 * 404 * @return the configuration cache state 405 */ 406 public CmsADEConfigCacheState getCacheState(boolean online) { 407 408 return (online ? m_onlineCache : m_offlineCache).getState(); 409 } 410 411 /** 412 * Gets the configuration file type.<p> 413 * 414 * @return the configuration file type 415 */ 416 public I_CmsResourceType getConfigurationType() { 417 418 return m_configType; 419 } 420 421 /** 422 * Returns the names of the bundles configured as workplace bundles in any module configuration. 423 * @return the names of the bundles configured as workplace bundles in any module configuration. 424 */ 425 public Set<String> getConfiguredWorkplaceBundles() { 426 427 CmsADEConfigData configData = internalLookupConfiguration(null, null); 428 return configData.getConfiguredWorkplaceBundles(); 429 } 430 431 /** 432 * Gets the content types configured in any sitemap configuations. 433 * 434 * @param online true if the types for the Online project should be fetched 435 * @return the set of content types 436 */ 437 public Set<String> getContentTypeNames(boolean online) { 438 439 CmsConfigurationCache cache = online ? m_onlineCache : m_offlineCache; 440 return cache.getState().getContentTypes(); 441 442 } 443 444 /** 445 * Reads the current element bean from the request.<p> 446 * 447 * @param req the servlet request 448 * 449 * @return the element bean 450 * 451 * @throws CmsException if no current element is set 452 */ 453 public CmsContainerElementBean getCurrentElement(ServletRequest req) throws CmsException { 454 455 CmsJspStandardContextBean sCBean = CmsJspStandardContextBean.getInstance(req); 456 CmsContainerElementBean element = sCBean.getElement(); 457 if (element == null) { 458 throw new CmsException( 459 Messages.get().container( 460 Messages.ERR_READING_ELEMENT_FROM_REQUEST_1, 461 sCBean.getRequestContext().getUri())); 462 } 463 return element; 464 } 465 466 /** 467 * Gets the detail id cache for the Online or Offline projects.<p> 468 * 469 * @param online if true, gets the Online project detail id 470 * 471 * @return the detail name cache 472 */ 473 public CmsDetailNameCache getDetailIdCache(boolean online) { 474 475 return online ? m_onlineDetailIdCache : m_offlineDetailIdCache; 476 } 477 478 /** 479 * Gets the detail page information for everything.<p> 480 * 481 * @param cms the current CMS context 482 * 483 * @return the list with all the detail page information 484 */ 485 public List<DetailInfo> getDetailInfo(CmsObject cms) { 486 487 return getCacheState(isOnline(cms)).getDetailInfosForSubsites(cms); 488 } 489 490 /** 491 * Gets the detail page for a content element.<p> 492 * 493 * @param cms the CMS context 494 * @param pageRootPath the element's root path 495 * @param originPath the path in which the the detail page is being requested 496 * 497 * @return the detail page for the content element 498 */ 499 public String getDetailPage(CmsObject cms, String pageRootPath, String originPath) { 500 501 return getDetailPage(cms, pageRootPath, originPath, null); 502 } 503 504 /** 505 * Gets the detail page for a content element.<p> 506 * 507 * @param cms the CMS context 508 * @param rootPath the element's root path 509 * @param linkSource the path in which the the detail page is being requested 510 * @param targetDetailPage the target detail page to use 511 * 512 * @return the detail page for the content element 513 */ 514 public String getDetailPage(CmsObject cms, String rootPath, String linkSource, String targetDetailPage) { 515 516 return getDetailPageHandler().getDetailPage(cms, rootPath, linkSource, targetDetailPage); 517 } 518 519 /** 520 * Gets the detail page finder.<p> 521 * 522 * @return the detail page finder 523 */ 524 public I_CmsDetailPageHandler getDetailPageHandler() { 525 526 return m_detailPageHandler; 527 } 528 529 /** 530 * Returns the main detail pages for a type in all of the VFS tree.<p> 531 * 532 * @param cms the current CMS context 533 * @param type the resource type name 534 * @return a list of detail page root paths 535 */ 536 public List<String> getDetailPages(CmsObject cms, String type) { 537 538 CmsConfigurationCache cache = isOnline(cms) ? m_onlineCache : m_offlineCache; 539 return cache.getState().getDetailPages(type); 540 } 541 542 /** 543 * Gets the set of types for which detail pages are defined.<p> 544 * 545 * @param cms the current CMS context 546 * 547 * @return the set of types for which detail pages are defined 548 */ 549 public Set<String> getDetailPageTypes(CmsObject cms) { 550 551 return getCacheState(isOnline(cms)).getDetailPageTypes(); 552 } 553 554 /** 555 * Returns the element settings for a given resource.<p> 556 * 557 * @param cms the current cms context 558 * @param resource the resource 559 * 560 * @return the element settings for a given resource 561 * 562 * @throws CmsException if something goes wrong 563 */ 564 public Map<String, CmsXmlContentProperty> getElementSettings(CmsObject cms, CmsResource resource) 565 throws CmsException { 566 567 if (CmsResourceTypeXmlContent.isXmlContent(resource)) { 568 Map<String, CmsXmlContentProperty> result = new LinkedHashMap<String, CmsXmlContentProperty>(); 569 Map<String, CmsXmlContentProperty> settings = CmsXmlContentDefinition.getContentHandlerForResource( 570 cms, 571 resource).getSettings(cms, resource); 572 result.putAll(settings); 573 return CmsXmlContentPropertyHelper.copyPropertyConfiguration(result); 574 } 575 return Collections.<String, CmsXmlContentProperty> emptyMap(); 576 } 577 578 /** 579 * Returns the available element views.<p> 580 * 581 * @param cms the cms context 582 * 583 * @return the element views 584 */ 585 public Map<CmsUUID, CmsElementView> getElementViews(CmsObject cms) { 586 587 CmsConfigurationCache cache = getCache(isOnline(cms)); 588 List<CmsElementView> viewList = Lists.newArrayList(); 589 viewList.addAll(cache.getState().getElementViews().values()); 590 viewList.addAll(OpenCms.getWorkplaceManager().getExplorerTypeViews().values()); 591 Collections.sort(viewList, new ElementViewComparator()); 592 Map<CmsUUID, CmsElementView> result = Maps.newLinkedHashMap(); 593 for (CmsElementView viewValue : viewList) { 594 result.put(viewValue.getId(), viewValue); 595 } 596 return result; 597 } 598 599 /** 600 * Gets the element view configuration resource type.<p> 601 * 602 * @return the element view configuration resource type 603 */ 604 public I_CmsResourceType getElementViewType() { 605 606 return m_elementViewType; 607 } 608 609 /** 610 * Returns the favorite list, or creates it if not available.<p> 611 * 612 * @param cms the cms context 613 * 614 * @return the favorite list 615 * 616 * @throws CmsException if something goes wrong 617 */ 618 public List<CmsContainerElementBean> getFavoriteList(CmsObject cms) throws CmsException { 619 620 CmsUser user = cms.getRequestContext().getCurrentUser(); 621 Object obj = user.getAdditionalInfo(ADDINFO_ADE_FAVORITE_LIST); 622 623 List<CmsContainerElementBean> favList = new ArrayList<CmsContainerElementBean>(); 624 if (obj instanceof String) { 625 try { 626 JSONArray array = new JSONArray((String)obj); 627 for (int i = 0; i < array.length(); i++) { 628 try { 629 favList.add(elementFromJson(array.getJSONObject(i))); 630 } catch (Throwable e) { 631 // should never happen, catches wrong or no longer existing values 632 LOG.warn(e.getLocalizedMessage()); 633 } 634 } 635 } catch (Throwable e) { 636 // should never happen, catches json parsing 637 LOG.warn(e.getLocalizedMessage()); 638 } 639 } else { 640 // save to be better next time 641 saveFavoriteList(cms, favList); 642 } 643 644 return favList; 645 } 646 647 /** 648 * Returns the settings configured for the given formatter which should be editable via ADE.<p> 649 * 650 * @param cms the cms context 651 * @param config the sitemap configuration 652 * @param mainFormatter the formatter 653 * @param res the element resource 654 * @param locale the content locale 655 * @param req the current request, if available 656 * 657 * @return the settings configured for the given formatter 658 */ 659 public Map<String, CmsXmlContentProperty> getFormatterSettings( 660 CmsObject cms, 661 CmsADEConfigData config, 662 I_CmsFormatterBean mainFormatter, 663 CmsResource res, 664 Locale locale, 665 ServletRequest req) { 666 667 Map<String, CmsXmlContentProperty> result = new LinkedHashMap<String, CmsXmlContentProperty>(); 668 Visibility defaultVisibility = Visibility.elementAndParentIndividual; 669 if (mainFormatter != null) { 670 for (Entry<String, CmsXmlContentProperty> entry : mainFormatter.getSettings(config).entrySet()) { 671 Visibility visibility = entry.getValue().getVisibility(defaultVisibility); 672 if (visibility.isVisibleOnElement()) { 673 result.put(entry.getKey(), entry.getValue()); 674 } 675 } 676 if (mainFormatter.hasNestedFormatterSettings()) { 677 List<I_CmsFormatterBean> nestedFormatters = getNestedFormatters(cms, config, res, locale, req); 678 if (nestedFormatters != null) { 679 for (I_CmsFormatterBean formatter : nestedFormatters) { 680 for (Entry<String, CmsXmlContentProperty> entry : formatter.getSettings(config).entrySet()) { 681 Visibility visibility = entry.getValue().getVisibility(defaultVisibility); 682 switch (visibility) { 683 case parentShared: 684 case elementAndParentShared: 685 result.put(entry.getKey(), entry.getValue()); 686 break; 687 case elementAndParentIndividual: 688 case parentIndividual: 689 String settingName = formatter.getKeyOrId() + "_" + entry.getKey(); 690 CmsXmlContentProperty settingConf = entry.getValue().withName(settingName); 691 result.put(settingName, settingConf); 692 break; 693 default: 694 break; 695 } 696 } 697 } 698 } 699 } 700 } 701 return result; 702 } 703 704 /** 705 * Returns the inheritance state for the given inheritance name and resource.<p> 706 * 707 * @param cms the current cms context 708 * @param resource the resource 709 * @param name the inheritance name 710 * 711 * @return the inheritance state 712 */ 713 public CmsInheritedContainerState getInheritedContainerState(CmsObject cms, CmsResource resource, String name) { 714 715 String rootPath = resource.getRootPath(); 716 if (!resource.isFolder()) { 717 rootPath = CmsResource.getParentFolder(rootPath); 718 } 719 CmsInheritedContainerState result = new CmsInheritedContainerState(); 720 boolean online = isOnline(cms); 721 CmsContainerConfigurationCache cache = online 722 ? m_onlineContainerConfigurationCache 723 : m_offlineContainerConfigurationCache; 724 result.addConfigurations(cache, rootPath, name); 725 return result; 726 727 } 728 729 /** 730 * Returns the inheritance state for the given inheritance name and root path.<p> 731 * 732 * @param cms the current cms context 733 * @param rootPath the root path 734 * @param name the inheritance name 735 * 736 * @return the inheritance state 737 * 738 * @throws CmsException if something goes wrong 739 */ 740 public CmsInheritedContainerState getInheritedContainerState(CmsObject cms, String rootPath, String name) 741 throws CmsException { 742 743 String oldSiteRoot = cms.getRequestContext().getSiteRoot(); 744 try { 745 cms.getRequestContext().setSiteRoot(""); 746 CmsResource resource = cms.readResource(rootPath); 747 return getInheritedContainerState(cms, resource, name); 748 } finally { 749 cms.getRequestContext().setSiteRoot(oldSiteRoot); 750 } 751 } 752 753 /** 754 * Gets the maximum sitemap depth.<p> 755 * 756 * @return the maximum sitemap depth 757 */ 758 public int getMaxSitemapDepth() { 759 760 return 20; 761 } 762 763 /** 764 * Gets the module configuration resource type.<p> 765 * 766 * @return the module configuration resource type 767 */ 768 public I_CmsResourceType getModuleConfigurationType() { 769 770 return m_moduleConfigType; 771 } 772 773 /** 774 * Returns the nested formatters of the given resource.<p> 775 * 776 * @param cms the cms context 777 * @param config the sitemap configuration 778 * @param res the resource 779 * @param locale the content locale 780 * @param req the request, if available 781 * 782 * @return the nested formatters 783 */ 784 public List<I_CmsFormatterBean> getNestedFormatters( 785 CmsObject cms, 786 CmsADEConfigData config, 787 CmsResource res, 788 Locale locale, 789 ServletRequest req) { 790 791 List<I_CmsFormatterBean> result = null; 792 if (CmsResourceTypeXmlContent.isXmlContent(res)) { 793 CmsResourceTypeXmlContent type = (CmsResourceTypeXmlContent)OpenCms.getResourceManager().getResourceType( 794 res); 795 String schema = type.getSchema(); 796 try { 797 CmsXmlContentDefinition contentDefinition = CmsXmlContentDefinition.unmarshal(cms, schema); 798 // get the content handler for the resource type to create 799 I_CmsXmlContentHandler handler = contentDefinition.getContentHandler(); 800 if (handler.hasNestedFormatters()) { 801 result = new ArrayList<I_CmsFormatterBean>(); 802 for (String formatterId : handler.getNestedFormatters(cms, res, locale, req)) { 803 I_CmsFormatterBean formatter = config.findFormatter(formatterId); 804 if (formatter != null) { 805 result.add(formatter); 806 } 807 } 808 } 809 } catch (CmsXmlException e) { 810 LOG.error(e.getMessage(), e); 811 } 812 } 813 return result; 814 } 815 816 /** 817 * Creates a stream that produces the pages/groups referencing a given element. 818 * 819 * <p>Note that this method doesn't take a CmsObject and just generates all resources referencing the element regardless of whether 820 * the current user can read them. So readResource calls for the ids of these resources may fail. 821 * 822 * @param resource the element resource 823 * @return the stream of resources which use the element 824 */ 825 public Stream<CmsResource> getOfflineElementUses(CmsResource resource) { 826 827 if ((resource == null) || resource.getStructureId().isNullUUID()) { 828 return Stream.of(); 829 } 830 try { 831 List<CmsRelation> relations = m_offlineCms.readRelations( 832 CmsRelationFilter.relationsToStructureId(resource.getStructureId())); 833 834 return relations.stream().flatMap(rel -> { 835 try { 836 CmsResource source = rel.getSource(m_offlineCms, CmsResourceFilter.ALL); 837 return Stream.of(source); 838 } catch (Exception e) { 839 LOG.debug(e.getLocalizedMessage(), e); 840 return Stream.of(); 841 } 842 }).filter(source -> { 843 return (CmsResourceTypeXmlContainerPage.isContainerPage(source) 844 || CmsResourceTypeXmlContainerPage.isModelGroup(source) 845 || OpenCms.getResourceManager().matchResourceType( 846 CmsResourceTypeXmlContainerPage.GROUP_CONTAINER_TYPE_NAME, 847 source.getTypeId())); 848 }); 849 } catch (CmsException e) { 850 LOG.error(e.getLocalizedMessage(), e); 851 return Stream.of(); 852 } 853 } 854 855 /** 856 * Gets ADE parameters.<p> 857 * 858 * @param cms the current CMS context 859 * @return the ADE parameters for the current user 860 */ 861 public Map<String, String> getParameters(CmsObject cms) { 862 863 Map<String, String> result = new LinkedHashMap<String, String>(m_parameters); 864 if (cms != null) { 865 String userParamsStr = (String)(cms.getRequestContext().getCurrentUser().getAdditionalInfo().get( 866 "ADE_PARAMS")); 867 if (userParamsStr != null) { 868 Map<String, String> userParams = CmsStringUtil.splitAsMap(userParamsStr, "|", ":"); 869 result.putAll(userParams); 870 } 871 } 872 return result; 873 } 874 875 public Set<String> getParentFolderTypes(boolean online, String contentRootPath) { 876 877 return getCacheState(online).getParentFolderTypes(contentRootPath); 878 } 879 880 /** 881 * Gets the root paths to use for secret lookup in the current project and based on the given root path, ordered from most deeply nested to least deeply nested. 882 * 883 * <p>These consist of all subsite paths, up to and including the site root, even if the site root does not have a .content/.config file. 884 * <p>If the given root path is not part of a site, the list of all containing subsite roots is returned. 885 * 886 * @param cms the current CMS context 887 * @param rootPath the root path for which to get the secret lookup paths 888 * @return 889 */ 890 public List<String> getPathsForSecretLookup(CmsObject cms, String rootPath) { 891 892 CmsADEConfigCacheState state = getCacheState(cms.getRequestContext().getCurrentProject().isOnlineProject()); 893 Set<String> siteConfigPaths = state.getSiteConfigurationPaths(); // has trailing slashes 894 String currentPath = rootPath; 895 currentPath = CmsFileUtil.addTrailingSeparator(currentPath); 896 String siteRoot = OpenCms.getSiteManager().getSiteRoot(rootPath); 897 if (siteRoot != null) { 898 siteRoot = CmsFileUtil.addTrailingSeparator(siteRoot); 899 } 900 List<String> result = new ArrayList<>(); 901 while (currentPath != null) { 902 boolean isSiteRoot = currentPath.equals(siteRoot); 903 if (isSiteRoot || siteConfigPaths.contains(currentPath)) { 904 result.add(CmsFileUtil.removeTrailingSeparator(currentPath)); 905 } 906 if (isSiteRoot) { 907 break; 908 } 909 currentPath = CmsResource.getParentFolder(currentPath); // has trailing slash 910 } 911 return result; 912 } 913 914 /** 915 * Returns the permission info for the given resource.<p> 916 * 917 * @param cms the cms context 918 * @param resource the resource 919 * @param contextPath the context path 920 * 921 * @return the permission info 922 * 923 * @throws CmsException if checking the permissions fails 924 */ 925 public CmsPermissionInfo getPermissionInfo(CmsObject cms, CmsResource resource, String contextPath) 926 throws CmsException { 927 928 boolean hasView = cms.hasPermissions( 929 resource, 930 CmsPermissionSet.ACCESS_VIEW, 931 false, 932 CmsResourceFilter.ALL.addRequireVisible()); 933 boolean hasWrite = false; 934 if (hasView) { 935 try { 936 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(resource.getTypeId()); 937 CmsExplorerTypeSettings settings = OpenCms.getWorkplaceManager().getExplorerTypeSetting( 938 type.getTypeName()); 939 hasView = (settings == null) 940 || settings.getAccess().getPermissions(cms, resource).requiresViewPermission(); 941 if (hasView 942 && CmsResourceTypeXmlContent.isXmlContent(resource) 943 && !CmsResourceTypeXmlContainerPage.isContainerPage(resource)) { 944 if (contextPath == null) { 945 contextPath = resource.getRootPath(); 946 } 947 CmsResourceTypeConfig localConfigData = lookupConfigurationWithCache( 948 cms, 949 contextPath).getResourceType(type.getTypeName()); 950 if (localConfigData != null) { 951 Map<CmsUUID, CmsElementView> elementViews = getElementViews(cms); 952 hasView = elementViews.containsKey(localConfigData.getElementView()) 953 && elementViews.get(localConfigData.getElementView()).hasPermission(cms, resource); 954 } 955 } 956 // the user may only have write permissions if he is allowed to view the resource 957 hasWrite = hasView 958 && cms.hasPermissions( 959 resource, 960 CmsPermissionSet.ACCESS_WRITE, 961 false, 962 CmsResourceFilter.IGNORE_EXPIRATION) 963 && ((settings == null) 964 || settings.getAccess().getPermissions(cms, resource).requiresWritePermission()); 965 } catch (CmsLoaderException e) { 966 LOG.warn(e.getLocalizedMessage(), e); 967 hasWrite = false; 968 } 969 } 970 971 if (hasWrite && isEditorRestricted(cms, resource)) { 972 hasWrite = false; 973 } 974 975 String noEdit = new CmsResourceUtil(cms, resource).getNoEditReason( 976 OpenCms.getWorkplaceManager().getWorkplaceLocale(cms), 977 true); 978 979 boolean isFunction = false; 980 for (String type : new String[] {"function", CmsResourceTypeFunctionConfig.TYPE_NAME}) { 981 if (OpenCms.getResourceManager().matchResourceType(type, resource.getTypeId())) { 982 isFunction = true; 983 break; 984 } 985 } 986 if (isFunction) { 987 Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 988 noEdit = Messages.get().getBundle(locale).key(Messages.GUI_CANT_EDIT_FUNCTIONS_0); 989 } 990 991 return new CmsPermissionInfo(hasView, hasWrite, noEdit); 992 } 993 994 /** 995 * Gets a map of plugin wrappers for the given site path. 996 * 997 * <p>This *only* includes plugins defined in site plugins active on the given path, not those referenced in formatters. 998 * 999 * @param cms the CMS context 1000 * @param path the path for which to get the plugins 1001 * 1002 * @return the map of plugin wrappers, with the plugin groups as keys 1003 */ 1004 public Map<String, List<CmsTemplatePluginWrapper>> getPluginsForPath(CmsObject cms, String path) { 1005 1006 CmsADEConfigData config = lookupConfigurationWithCache(cms, cms.getRequestContext().addSiteRoot(path)); 1007 1008 Multimap<String, CmsTemplatePlugin> plugins = CmsTemplatePluginFinder.getActiveTemplatePluginsFromSitePlugins( 1009 config); 1010 Map<String, List<CmsTemplatePluginWrapper>> result = new HashMap<>(); 1011 for (String key : plugins.keySet()) { 1012 List<CmsTemplatePluginWrapper> wrappers = plugins.get(key).stream().map( 1013 plugin -> new CmsTemplatePluginWrapper(cms, plugin)).collect(Collectors.toList()); 1014 result.put(key, Collections.unmodifiableList(wrappers)); 1015 } 1016 return Collections.unmodifiableMap(result); 1017 1018 } 1019 1020 /** 1021 * Gets the raw configured detail page information, with no existence checks or path correction. 1022 * 1023 * @param cms the CMS context 1024 * @return the list of raw detail page info beans 1025 */ 1026 public List<CmsDetailPageInfo> getRawDetailPages(CmsObject cms) { 1027 1028 return getCache(cms.getRequestContext().getCurrentProject().isOnlineProject()).getRawDetailPages(); 1029 } 1030 1031 /** 1032 * Returns the favorite list, or creates it if not available.<p> 1033 * 1034 * @param cms the cms context 1035 * 1036 * @return the favorite list 1037 * 1038 * @throws CmsException if something goes wrong 1039 */ 1040 public List<CmsContainerElementBean> getRecentList(CmsObject cms) throws CmsException { 1041 1042 CmsUser user = cms.getRequestContext().getCurrentUser(); 1043 Object obj = user.getAdditionalInfo(ADDINFO_ADE_RECENT_LIST); 1044 1045 List<CmsContainerElementBean> recentList = new ArrayList<CmsContainerElementBean>(); 1046 if (obj instanceof String) { 1047 try { 1048 JSONArray array = new JSONArray((String)obj); 1049 for (int i = 0; i < array.length(); i++) { 1050 try { 1051 recentList.add(elementFromJson(array.getJSONObject(i))); 1052 } catch (Throwable e) { 1053 // should never happen, catches wrong or no longer existing values 1054 LOG.warn(e.getLocalizedMessage()); 1055 } 1056 } 1057 } catch (Throwable e) { 1058 // should never happen, catches json parsing 1059 LOG.warn(e.getLocalizedMessage()); 1060 } 1061 } else { 1062 // save to be better next time 1063 saveRecentList(cms, recentList); 1064 } 1065 1066 return recentList; 1067 } 1068 1069 /** 1070 * Gets a map of sitemap attribute values by sitemap path for a given attribute key. 1071 * 1072 * @param cms the current CMS context 1073 * @param attribute the sitemap attribute key 1074 * 1075 * @return the map of attribute values, with the root paths of the corresponding subsitemaps as keys 1076 */ 1077 public Map<String, String> getSitemapAttributeValuesByPath(CmsObject cms, String attribute) { 1078 1079 boolean online = (null == cms) || isOnline(cms); 1080 CmsADEConfigCacheState state = getCacheState(online); 1081 return state.getAttributeValuesByPath(attribute); 1082 } 1083 1084 /** 1085 * Gets the sitemap configuration resource type.<p> 1086 * 1087 * @return the resource type for sitemap configurations 1088 */ 1089 public I_CmsResourceType getSitemapConfigurationType() { 1090 1091 return m_configType; 1092 } 1093 1094 /** 1095 * Gets the registered sitemap extra info providers. 1096 * 1097 * @return the list of providers 1098 */ 1099 public List<I_CmsSitemapExtraInfoProvider> getSitemapExtraInfoProviders() { 1100 1101 return m_sitemapExtraInfoProviders.stream().sorted( 1102 Comparator.comparing(provider -> provider.getOrder())).collect(Collectors.toList()); 1103 } 1104 1105 /** 1106 * Returns all sub sites below the given path.<p> 1107 * 1108 * @param cms the cms context 1109 * @param subSiteRoot the sub site root path 1110 * 1111 * @return the sub site root paths 1112 */ 1113 public List<String> getSubSitePaths(CmsObject cms, String subSiteRoot) { 1114 1115 List<String> result = new ArrayList<String>(); 1116 String normalizedRootPath = CmsStringUtil.joinPaths("/", subSiteRoot, "/"); 1117 CmsADEConfigCacheState state = getCacheState(isOnline(cms)); 1118 Set<String> siteConfigurationPaths = state.getSiteConfigurationPaths(); 1119 for (String path : siteConfigurationPaths) { 1120 if ((path.length() > normalizedRootPath.length()) && path.startsWith(normalizedRootPath)) { 1121 result.add(path); 1122 } 1123 } 1124 return result; 1125 } 1126 1127 /** 1128 * Tries to get the subsite root for a given resource root path.<p> 1129 * 1130 * @param cms the current CMS context 1131 * @param rootPath the root path for which the subsite root should be found 1132 * 1133 * @return the subsite root 1134 */ 1135 public String getSubSiteRoot(CmsObject cms, String rootPath) { 1136 1137 CmsADEConfigData configData = lookupConfiguration(cms, rootPath); 1138 String basePath = configData.getBasePath(); 1139 String siteRoot = OpenCms.getSiteManager().getSiteRoot(rootPath); 1140 if (siteRoot == null) { 1141 siteRoot = ""; 1142 } 1143 if ((basePath == null) || !basePath.startsWith(siteRoot)) { 1144 // the subsite root should always be below the site root 1145 return siteRoot; 1146 } else { 1147 return basePath; 1148 } 1149 } 1150 1151 /** 1152 * Gets the subsites to be displayed in the site selector. 1153 * 1154 * @param online true if we want the subsites for the Online project 1155 * 1156 * @return the subsites to be displayed in the site selector 1157 */ 1158 public List<String> getSubsitesForSiteSelector(boolean online) { 1159 1160 return getCacheState(online).getSubsitesForSiteSelector(); 1161 1162 } 1163 1164 /** 1165 * Gets the table of upload warnings. 1166 * 1167 * @return the table of upload warnings 1168 */ 1169 public CmsUploadWarningTable getUploadWarningTable() { 1170 1171 return m_uploadWarningTable; 1172 } 1173 1174 /** 1175 * Processes a HTML redirect content.<p> 1176 * 1177 * This needs to be in the ADE manager because the user for whom the HTML redirect is being loaded 1178 * does not necessarily have read permissions for the redirect target, so we read the redirect target 1179 * with admin privileges.<p> 1180 * 1181 * @param userCms the CMS context of the current user 1182 * @param request the servlet request 1183 * @param response the servlet response 1184 * @param htmlRedirect the path of the HTML redirect resource 1185 * 1186 * @throws CmsException if something goes wrong 1187 */ 1188 public void handleHtmlRedirect( 1189 CmsObject userCms, 1190 HttpServletRequest request, 1191 HttpServletResponse response, 1192 String htmlRedirect) 1193 throws CmsException { 1194 1195 CmsObject cms = OpenCms.initCmsObject(m_offlineCms); 1196 CmsRequestContext userContext = userCms.getRequestContext(); 1197 CmsRequestContext currentContext = cms.getRequestContext(); 1198 currentContext.setCurrentProject(userContext.getCurrentProject()); 1199 currentContext.setSiteRoot(userContext.getSiteRoot()); 1200 currentContext.setLocale(userContext.getLocale()); 1201 currentContext.setUri(userContext.getUri()); 1202 1203 CmsFile file = cms.readFile(htmlRedirect); 1204 CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file); 1205 1206 // find out the locale to use for reading values from the redirect 1207 List<Locale> candidates = new ArrayList<Locale>(); 1208 candidates.add(currentContext.getLocale()); 1209 candidates.add(CmsLocaleManager.getDefaultLocale()); 1210 candidates.add(Locale.ENGLISH); 1211 candidates.addAll(content.getLocales()); 1212 Locale contentLocale = currentContext.getLocale(); 1213 for (Locale candidateLocale : candidates) { 1214 if (content.hasLocale(candidateLocale)) { 1215 contentLocale = candidateLocale; 1216 break; 1217 } 1218 } 1219 1220 String typeValue = content.getValue(N_TYPE, contentLocale).getStringValue(cms); 1221 String lnkUri = ""; 1222 Integer errorCode; 1223 if ("sublevel".equals(typeValue)) { 1224 // use the nav builder to get the first sub level entry 1225 CmsJspNavBuilder navBuilder = new CmsJspNavBuilder(cms); 1226 if (navBuilder.getNavigationForFolder().size() > 0) { 1227 CmsJspNavElement target = navBuilder.getNavigationForFolder().get(0); 1228 lnkUri = CmsJspTagLink.linkTagAction(target.getResourceName(), request); 1229 errorCode = Integer.valueOf(HttpServletResponse.SC_MOVED_TEMPORARILY); 1230 } else { 1231 // send error 404 if no sub entry available 1232 errorCode = Integer.valueOf(HttpServletResponse.SC_NOT_FOUND); 1233 } 1234 } else { 1235 I_CmsXmlContentValue contentValue = content.getValue(N_LINK, contentLocale); 1236 if (contentValue != null) { 1237 String linkValue = contentValue.getStringValue(cms); 1238 lnkUri = OpenCms.getLinkManager().substituteLinkForUnknownTarget(cms, linkValue); 1239 try { 1240 errorCode = Integer.valueOf(typeValue); 1241 } catch (NumberFormatException e) { 1242 LOG.error(e.getMessage(), e); 1243 // fall back to default 1244 errorCode = Integer.valueOf(307); 1245 } 1246 } else { 1247 // send error 404 if no link value is set 1248 errorCode = Integer.valueOf(HttpServletResponse.SC_NOT_FOUND); 1249 } 1250 } 1251 if (!currentContext.getCurrentProject().isOnlineProject()) { 1252 // permanent redirects are confusing and not useful in the Offline project because they are stored 1253 // by the browser based on the host name, not the site the user is working in. 1254 if (errorCode.intValue() == HttpServletResponse.SC_MOVED_PERMANENTLY) { 1255 errorCode = Integer.valueOf(HttpServletResponse.SC_MOVED_TEMPORARILY); 1256 } 1257 } 1258 request.setAttribute(CmsRequestUtil.ATTRIBUTE_ERRORCODE, errorCode); 1259 response.setHeader("Location", CmsEncoder.convertHostToPunycode(lnkUri)); 1260 response.setHeader("Connection", "close"); 1261 response.setStatus(errorCode.intValue()); 1262 } 1263 1264 /** 1265 * Initializes the configuration by reading all configuration files and caching their data.<p> 1266 */ 1267 public synchronized void initialize() { 1268 1269 // no need to try initialization in case the 'org.opencms.base' is not present and the contained resource types missing 1270 if ((m_initStatus == Status.notInitialized) && OpenCms.getModuleManager().hasModule(MODULE_NAME_ADE_CONFIG)) { 1271 try { 1272 CmsLog.INIT.info(". Initializing the ADE configuration, this may take a while..."); 1273 m_initStatus = Status.initializing; 1274 m_configType = OpenCms.getResourceManager().getResourceType(CONFIG_TYPE); 1275 m_moduleConfigType = OpenCms.getResourceManager().getResourceType(MODULE_CONFIG_TYPE); 1276 m_elementViewType = OpenCms.getResourceManager().getResourceType(ELEMENT_VIEW_TYPE); 1277 CmsProject temp = getTempfileProject(m_onlineCms); 1278 m_offlineCms = OpenCms.initCmsObject(m_onlineCms); 1279 m_offlineCms.getRequestContext().setCurrentProject(temp); 1280 m_onlineCache = new CmsConfigurationCache( 1281 m_onlineCms, 1282 m_configType, 1283 m_moduleConfigType, 1284 m_elementViewType); 1285 m_offlineCache = new CmsConfigurationCache( 1286 m_offlineCms, 1287 m_configType, 1288 m_moduleConfigType, 1289 m_elementViewType); 1290 CmsLog.INIT.info(". Reading online configuration..."); 1291 m_onlineCache.initialize(); 1292 CmsLog.INIT.info(". Reading offline configuration..."); 1293 m_offlineCache.initialize(); 1294 m_onlineContainerConfigurationCache = new CmsContainerConfigurationCache( 1295 m_onlineCms, 1296 "online inheritance groups"); 1297 m_offlineContainerConfigurationCache = new CmsContainerConfigurationCache( 1298 m_offlineCms, 1299 "offline inheritance groups"); 1300 CmsLog.INIT.info(". Reading online inherited container configurations..."); 1301 m_onlineContainerConfigurationCache.initialize(); 1302 CmsLog.INIT.info(". Reading offline inherited container configurations..."); 1303 m_offlineContainerConfigurationCache.initialize(); 1304 m_offlineFormatterCache = new CmsFormatterConfigurationCache(m_offlineCms, "offline formatters"); 1305 m_onlineFormatterCache = new CmsFormatterConfigurationCache(m_onlineCms, "online formatters"); 1306 CmsLog.INIT.info(". Reading online formatter configurations..."); 1307 m_onlineFormatterCache.initialize(); 1308 CmsLog.INIT.info(". Reading offline formatter configurations..."); 1309 m_offlineFormatterCache.initialize(); 1310 1311 m_offlineDetailIdCache = new CmsDetailNameCache(m_offlineCms); 1312 m_onlineDetailIdCache = new CmsDetailNameCache(m_onlineCms); 1313 CmsLog.INIT.info(". Initializing online detail name cache..."); 1314 m_onlineDetailIdCache.initialize(); 1315 CmsLog.INIT.info(". Initializing offline detail name cache..."); 1316 m_offlineDetailIdCache.initialize(); 1317 1318 CmsGlobalConfigurationCacheEventHandler handler = new CmsGlobalConfigurationCacheEventHandler( 1319 m_onlineCms); 1320 handler.addCache(m_offlineCache, m_onlineCache, "ADE configuration cache"); 1321 handler.addCache( 1322 m_offlineContainerConfigurationCache, 1323 m_onlineContainerConfigurationCache, 1324 "Inherited container cache"); 1325 handler.addCache(m_offlineFormatterCache, m_onlineFormatterCache, "formatter configuration cache"); 1326 handler.addCache(m_offlineDetailIdCache, m_onlineDetailIdCache, "Detail ID cache"); 1327 OpenCms.getEventManager().addCmsEventListener(handler); 1328 CmsLog.INIT.info(". Done initializing the ADE configuration."); 1329 m_initStatus = Status.initialized; 1330 } catch (CmsException e) { 1331 m_initStatus = Status.notInitialized; 1332 LOG.error(e.getLocalizedMessage(), e); 1333 } 1334 m_detailPageHandler.initialize(m_offlineCms, m_onlineCms); 1335 } 1336 } 1337 1338 /** 1339 * Checks whether the given resource is configured as a detail page.<p> 1340 * 1341 * @param cms the current CMS context 1342 * @param resource the resource which should be tested 1343 * 1344 * @return true if the resource is configured as a detail page 1345 */ 1346 public boolean isDetailPage(CmsObject cms, CmsResource resource) { 1347 1348 return getCache(isOnline(cms)).isDetailPage(cms, resource); 1349 } 1350 1351 /** 1352 * Checks if the user should be prevented from editing a file. 1353 * 1354 * <p>This is not a permission check, but an additional mechanism to prevent users from editing configuration files even if they technically need or have write permissions for these files. 1355 * 1356 * @param cms the CMS context 1357 * @param res the resource to check 1358 * @return true if the user should be prevented from editing the file 1359 */ 1360 public boolean isEditorRestricted(CmsObject cms, CmsResource res) { 1361 1362 if (OpenCms.getResourceManager().matchResourceType(CONFIG_TYPE, res.getTypeId())) { 1363 CmsRole role = getRoleForSitemapConfigEditing(); 1364 if ((role != null) && !OpenCms.getRoleManager().hasRoleForResource(cms, role, res)) { 1365 return true; 1366 } 1367 } 1368 return false; 1369 } 1370 1371 /** 1372 * Checks if an element is reused in a page or group that is not excluded by a given predicate. 1373 * 1374 * @param resource the resource to check 1375 * @param exclude predicate used to ignore reuses which match it 1376 * @return true if the element is reused 1377 */ 1378 public boolean isElementReused(CmsResource resource, Predicate<CmsResource> exclude) { 1379 1380 return getOfflineElementUses(resource).anyMatch(source -> !exclude.test(source)); 1381 } 1382 1383 /** 1384 * Checks whether the ADE manager is initialized (this should usually be the case except during the setup).<p> 1385 * 1386 * @return true if the ADE manager is initialized 1387 */ 1388 public boolean isInitialized() { 1389 1390 return m_initStatus == Status.initialized; 1391 } 1392 1393 /** 1394 * Returns the show editor help flag.<p> 1395 * 1396 * @param cms the cms context 1397 * 1398 * @return the show editor help flag 1399 */ 1400 public boolean isShowEditorHelp(CmsObject cms) { 1401 1402 CmsUser user = cms.getRequestContext().getCurrentUser(); 1403 String showHelp = (String)user.getAdditionalInfo(ADDINFO_ADE_SHOW_EDITOR_HELP); 1404 return CmsStringUtil.isEmptyOrWhitespaceOnly(showHelp) || Boolean.parseBoolean(showHelp); 1405 } 1406 1407 /** 1408 * Looks up the configuration data for a given sitemap path.<p> 1409 * 1410 * @param cms the current CMS context 1411 * @param rootPath the root path for which the configuration data should be looked up 1412 * 1413 * @return the configuration data 1414 */ 1415 public CmsADEConfigData lookupConfiguration(CmsObject cms, String rootPath) { 1416 1417 CmsADEConfigData configData = internalLookupConfiguration(cms, rootPath); 1418 return configData; 1419 } 1420 1421 /** 1422 * Looks up the configuration data for a given sitemap path, but uses a thread-local cache for the current request for efficiency. 1423 * 1424 * @param cms the current CMS context 1425 * @param rootPath the root path for which the configuration data should be looked up 1426 * 1427 * @return the configuration data 1428 */ 1429 public CmsADEConfigData lookupConfigurationWithCache(CmsObject cms, String rootPath) { 1430 1431 boolean online = (cms == null) || cms.getRequestContext().getCurrentProject().isOnlineProject(); 1432 String cacheKey = "" + online + ":" + rootPath; 1433 OpenCmsServlet.RequestCache context = OpenCmsServlet.getRequestCache(); 1434 CmsADEConfigData result = null; 1435 if (context != null) { 1436 result = context.getCachedConfig(cacheKey); 1437 } 1438 if (result == null) { 1439 result = internalLookupConfiguration(cms, rootPath); 1440 if (context != null) { 1441 context.setCachedConfig(cacheKey, result); 1442 } 1443 } 1444 return result; 1445 } 1446 1447 /** 1448 * Reloads the configuration.<p> 1449 * 1450 * Normally you shouldn't call this directly since the event handlers take care of updating the configuration. 1451 */ 1452 public void refresh() { 1453 1454 m_onlineCache.initialize(); 1455 m_offlineCache.initialize(); 1456 } 1457 1458 public void removeSitemapExtraInfoProvider(I_CmsSitemapExtraInfoProvider provider) { 1459 1460 m_sitemapExtraInfoProviders.remove(provider); 1461 } 1462 1463 /** 1464 * Saves a list of detail pages.<p> 1465 * @param cms the cms context 1466 * @param rootPath the root path 1467 * @param detailPages the detail pages 1468 * @param newId the id to use for new detail pages without an id 1469 * @return true if the detail pages could be successfully saved 1470 * 1471 * @throws CmsException if something goes wrong 1472 */ 1473 public boolean saveDetailPages(CmsObject cms, String rootPath, List<CmsDetailPageInfo> detailPages, CmsUUID newId) 1474 throws CmsException { 1475 1476 CmsADEConfigData configData = lookupConfiguration(cms, rootPath); 1477 CmsDetailPageConfigurationWriter configWriter; 1478 String originalSiteRoot = cms.getRequestContext().getSiteRoot(); 1479 try { 1480 cms.getRequestContext().setSiteRoot(""); 1481 if (configData.isModuleConfiguration()) { 1482 return false; 1483 } 1484 CmsResource configFile = configData.getResource(); 1485 configWriter = new CmsDetailPageConfigurationWriter(cms, configFile); 1486 configWriter.updateAndSave(detailPages, newId); 1487 return true; 1488 } finally { 1489 cms.getRequestContext().setSiteRoot(originalSiteRoot); 1490 } 1491 } 1492 1493 /** 1494 * Saves the favorite list, user based.<p> 1495 * 1496 * @param cms the cms context 1497 * @param favoriteList the element list 1498 * 1499 * @throws CmsException if something goes wrong 1500 */ 1501 public void saveFavoriteList(CmsObject cms, List<CmsContainerElementBean> favoriteList) throws CmsException { 1502 1503 saveElementList(cms, favoriteList, ADDINFO_ADE_FAVORITE_LIST); 1504 } 1505 1506 /** 1507 * Saves the inheritance container information.<p> 1508 * 1509 * @param cms the current cms context 1510 * @param pageResource the resource or parent folder 1511 * @param name the inheritance name 1512 * @param newOrder if the element have been reordered 1513 * @param elements the elements 1514 * 1515 * @throws CmsException if something goes wrong 1516 */ 1517 public void saveInheritedContainer( 1518 CmsObject cms, 1519 CmsResource pageResource, 1520 String name, 1521 boolean newOrder, 1522 List<CmsContainerElementBean> elements) 1523 throws CmsException { 1524 1525 CmsContainerConfigurationWriter writer = new CmsContainerConfigurationWriter(); 1526 writer.save(cms, name, newOrder, pageResource, elements); 1527 1528 // Inheritance groups are usually reloaded directly after saving them, 1529 // so the cache needs to be up to date after this method is called 1530 m_offlineContainerConfigurationCache.flushUpdates(); 1531 } 1532 1533 /** 1534 * Saves the inheritance container information.<p> 1535 * 1536 * @param cms the current cms context 1537 * @param sitePath the site path of the resource or parent folder 1538 * @param name the inheritance name 1539 * @param newOrder if the element have been reordered 1540 * @param elements the elements 1541 * 1542 * @throws CmsException if something goes wrong 1543 */ 1544 public void saveInheritedContainer( 1545 CmsObject cms, 1546 String sitePath, 1547 String name, 1548 boolean newOrder, 1549 List<CmsContainerElementBean> elements) 1550 throws CmsException { 1551 1552 saveInheritedContainer(cms, cms.readResource(sitePath), name, newOrder, elements); 1553 } 1554 1555 /** 1556 * Saves the favorite list, user based.<p> 1557 * 1558 * @param cms the cms context 1559 * @param recentList the element list 1560 * 1561 * @throws CmsException if something goes wrong 1562 */ 1563 public void saveRecentList(CmsObject cms, List<CmsContainerElementBean> recentList) throws CmsException { 1564 1565 saveElementList(cms, recentList, ADDINFO_ADE_RECENT_LIST); 1566 } 1567 1568 /** 1569 * Sets the show editor help flag.<p> 1570 * 1571 * @param cms the cms context 1572 * @param showHelp the show help flag 1573 * @throws CmsException if writing the user info fails 1574 */ 1575 public void setShowEditorHelp(CmsObject cms, boolean showHelp) throws CmsException { 1576 1577 CmsUser user = cms.getRequestContext().getCurrentUser(); 1578 user.setAdditionalInfo(ADDINFO_ADE_SHOW_EDITOR_HELP, String.valueOf(showHelp)); 1579 cms.writeUser(user); 1580 } 1581 1582 /** 1583 * The method which is called when the OpenCms instance is shut down.<p> 1584 */ 1585 public void shutdown() { 1586 1587 // do nothing 1588 } 1589 1590 /** 1591 * Waits until the next time the cache is updated.<p> 1592 * 1593 * @param online true if we want to wait for the online cache, false for the offline cache 1594 */ 1595 public void waitForCacheUpdate(boolean online) { 1596 1597 getCache(online).getWaitHandleForUpdateTask().enter(2 * CmsConfigurationCache.TASK_DELAY_MILLIS); 1598 } 1599 1600 /** 1601 * Waits until the formatter cache has finished updating itself.<p> 1602 * 1603 * This method is only intended for use in test cases. 1604 * 1605 * @param online true if we should wait for the online formatter cache,false for the offline cache 1606 */ 1607 public void waitForFormatterCache(boolean online) { 1608 1609 CmsFormatterConfigurationCache cache = online ? m_onlineFormatterCache : m_offlineFormatterCache; 1610 cache.waitForUpdate(); 1611 } 1612 1613 /** 1614 * Creates an element from its serialized data.<p> 1615 * 1616 * @param data the serialized data 1617 * 1618 * @return the restored element bean 1619 * 1620 * @throws JSONException if the serialized data got corrupted 1621 */ 1622 protected CmsContainerElementBean elementFromJson(JSONObject data) throws JSONException { 1623 1624 CmsUUID element = new CmsUUID(data.getString(FavListProp.ELEMENT.name().toLowerCase())); 1625 CmsUUID formatter = null; 1626 if (data.has(FavListProp.FORMATTER.name().toLowerCase())) { 1627 formatter = new CmsUUID(data.getString(FavListProp.FORMATTER.name().toLowerCase())); 1628 } 1629 Map<String, String> properties = new HashMap<String, String>(); 1630 1631 JSONObject props = data.getJSONObject(FavListProp.PROPERTIES.name().toLowerCase()); 1632 Iterator<String> keys = props.keys(); 1633 while (keys.hasNext()) { 1634 String key = keys.next(); 1635 properties.put(key, props.getString(key)); 1636 } 1637 1638 return new CmsContainerElementBean(element, formatter, properties, false); 1639 } 1640 1641 /** 1642 * Converts the given element to JSON.<p> 1643 * 1644 * @param element the element to convert 1645 * @param excludeSettings the keys of settings which should not be written to the JSON 1646 * 1647 * @return the JSON representation 1648 */ 1649 protected JSONObject elementToJson(CmsContainerElementBean element, Set<String> excludeSettings) { 1650 1651 JSONObject data = null; 1652 try { 1653 data = new JSONObject(); 1654 data.put(FavListProp.ELEMENT.name().toLowerCase(), element.getId().toString()); 1655 if (element.getFormatterId() != null) { 1656 data.put(FavListProp.FORMATTER.name().toLowerCase(), element.getFormatterId().toString()); 1657 } 1658 JSONObject properties = new JSONObject(); 1659 for (Map.Entry<String, String> entry : element.getIndividualSettings().entrySet()) { 1660 String settingKey = entry.getKey(); 1661 if (!excludeSettings.contains(settingKey)) { 1662 properties.put(entry.getKey(), entry.getValue()); 1663 } 1664 } 1665 data.put(FavListProp.PROPERTIES.name().toLowerCase(), properties); 1666 } catch (JSONException e) { 1667 // should never happen 1668 if (!LOG.isDebugEnabled()) { 1669 LOG.warn(e.getLocalizedMessage()); 1670 } 1671 LOG.debug(e.getLocalizedMessage(), e); 1672 return null; 1673 } 1674 return data; 1675 } 1676 1677 /** 1678 * Gets the configuration cache instance.<p> 1679 * 1680 * @param online true if you want the online cache, false for the offline cache 1681 * 1682 * @return the ADE configuration cache instance 1683 */ 1684 protected CmsConfigurationCache getCache(boolean online) { 1685 1686 return online ? m_onlineCache : m_offlineCache; 1687 } 1688 1689 /** 1690 * Gets the offline cache.<p> 1691 * 1692 * @return the offline configuration cache 1693 */ 1694 protected CmsConfigurationCache getOfflineCache() { 1695 1696 return m_offlineCache; 1697 } 1698 1699 /** 1700 * Gets the online cache.<p> 1701 * 1702 * @return the online configuration cache 1703 */ 1704 protected CmsConfigurationCache getOnlineCache() { 1705 1706 return m_onlineCache; 1707 } 1708 1709 /** 1710 * Gets the role necessary to edit sitemap configuration files. 1711 * 1712 * @return the role needed for editing sitemap configurations 1713 */ 1714 protected CmsRole getRoleForSitemapConfigEditing() { 1715 1716 String roleName = OpenCms.getWorkplaceManager().getSitemapConfigEditRole(); 1717 if (roleName == null) { 1718 return null; 1719 } else { 1720 if (roleName.indexOf("/") == -1) { 1721 return CmsRole.valueOfRoleName(roleName).forOrgUnit(null); 1722 } else { 1723 return CmsRole.valueOfRoleName(roleName); 1724 } 1725 } 1726 } 1727 1728 /** 1729 * Gets the root path for a given resource structure id.<p> 1730 * 1731 * @param structureId the structure id 1732 * @param online if true, the resource will be looked up in the online project ,else in the offline project 1733 * 1734 * @return the root path for the given structure id 1735 * 1736 * @throws CmsException if something goes wrong 1737 */ 1738 protected String getRootPath(CmsUUID structureId, boolean online) throws CmsException { 1739 1740 CmsConfigurationCache cache = online ? m_onlineCache : m_offlineCache; 1741 return cache.getPathForStructureId(structureId); 1742 } 1743 1744 /** 1745 * Gets a tempfile project, creating one if it doesn't exist already.<p> 1746 * 1747 * @param cms the CMS context to use 1748 * @return the tempfile project 1749 * 1750 * @throws CmsException if something goes wrong 1751 */ 1752 protected CmsProject getTempfileProject(CmsObject cms) throws CmsException { 1753 1754 try { 1755 return cms.readProject(I_CmsProjectDriver.TEMP_FILE_PROJECT_NAME); 1756 } catch (CmsException e) { 1757 return cms.createTempfileProject(); 1758 } 1759 } 1760 1761 /** 1762 * Internal configuration lookup method.<p> 1763 * 1764 * @param cms the cms context 1765 * @param rootPath the root path for which to look up the configuration 1766 * 1767 * @return the configuration for the given path 1768 */ 1769 protected CmsADEConfigData internalLookupConfiguration(CmsObject cms, String rootPath) { 1770 1771 boolean online = (null == cms) || isOnline(cms); 1772 CmsADEConfigCacheState state = getCacheState(online); 1773 return state.lookupConfiguration(rootPath); 1774 } 1775 1776 /** 1777 * Returns true if the project set in the CmsObject is the Online project.<p> 1778 * 1779 * @param cms the CMS context to check 1780 * 1781 * @return true if the project set in the CMS context is the Online project 1782 */ 1783 private boolean isOnline(CmsObject cms) { 1784 1785 return cms.getRequestContext().getCurrentProject().isOnlineProject(); 1786 } 1787 1788 /** 1789 * Saves an element list to the user additional infos.<p> 1790 * 1791 * @param cms the cms context 1792 * @param elementList the element list 1793 * @param listKey the list key 1794 * 1795 * @throws CmsException if something goes wrong 1796 */ 1797 private void saveElementList(CmsObject cms, List<CmsContainerElementBean> elementList, String listKey) 1798 throws CmsException { 1799 1800 // limit the favorite list size to avoid the additional info size limit 1801 if (elementList.size() > DEFAULT_ELEMENT_LIST_SIZE) { 1802 elementList = elementList.subList(0, DEFAULT_ELEMENT_LIST_SIZE); 1803 } 1804 1805 JSONArray data = new JSONArray(); 1806 1807 Set<String> excludedSettings = new HashSet<String>(); 1808 // do not store the template contexts, since dragging an element into the page which might be invisible 1809 // doesn't make sense 1810 excludedSettings.add(CmsTemplateContextInfo.SETTING); 1811 1812 for (CmsContainerElementBean element : elementList) { 1813 data.put(elementToJson(element, excludedSettings)); 1814 } 1815 CmsUser user = cms.getRequestContext().getCurrentUser(); 1816 user.setAdditionalInfo(listKey, data.toString()); 1817 cms.writeUser(user); 1818 } 1819}