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 javax.servlet.ServletRequest; 118import javax.servlet.http.HttpServletRequest; 119import javax.servlet.http.HttpServletResponse; 120 121import org.apache.commons.logging.Log; 122 123import com.google.common.collect.Lists; 124import com.google.common.collect.Maps; 125import com.google.common.collect.Multimap; 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 /** 876 * Gets the content element type for the given path's parent folder. 877 * 878 * @param online true if we want to use the Online project's configuration 879 * @param rootPath the root path of a content 880 * 881 * @return the parent folder type name, or null if none is defined 882 */ 883 public String getParentFolderType(boolean online, String rootPath) { 884 885 return getCacheState(online).getParentFolderType(rootPath); 886 887 } 888 889 /** 890 * 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. 891 * 892 * <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. 893 * <p>If the given root path is not part of a site, the list of all containing subsite roots is returned. 894 * 895 * @param cms the current CMS context 896 * @param rootPath the root path for which to get the secret lookup paths 897 * @return 898 */ 899 public List<String> getPathsForSecretLookup(CmsObject cms, String rootPath) { 900 901 CmsADEConfigCacheState state = getCacheState(cms.getRequestContext().getCurrentProject().isOnlineProject()); 902 Set<String> siteConfigPaths = state.getSiteConfigurationPaths(); // has trailing slashes 903 String currentPath = rootPath; 904 currentPath = CmsFileUtil.addTrailingSeparator(currentPath); 905 String siteRoot = OpenCms.getSiteManager().getSiteRoot(rootPath); 906 if (siteRoot != null) { 907 siteRoot = CmsFileUtil.addTrailingSeparator(siteRoot); 908 } 909 List<String> result = new ArrayList<>(); 910 while (currentPath != null) { 911 boolean isSiteRoot = currentPath.equals(siteRoot); 912 if (isSiteRoot || siteConfigPaths.contains(currentPath)) { 913 result.add(CmsFileUtil.removeTrailingSeparator(currentPath)); 914 } 915 if (isSiteRoot) { 916 break; 917 } 918 currentPath = CmsResource.getParentFolder(currentPath); // has trailing slash 919 } 920 return result; 921 } 922 923 /** 924 * Returns the permission info for the given resource.<p> 925 * 926 * @param cms the cms context 927 * @param resource the resource 928 * @param contextPath the context path 929 * 930 * @return the permission info 931 * 932 * @throws CmsException if checking the permissions fails 933 */ 934 public CmsPermissionInfo getPermissionInfo(CmsObject cms, CmsResource resource, String contextPath) 935 throws CmsException { 936 937 boolean hasView = cms.hasPermissions( 938 resource, 939 CmsPermissionSet.ACCESS_VIEW, 940 false, 941 CmsResourceFilter.ALL.addRequireVisible()); 942 boolean hasWrite = false; 943 if (hasView) { 944 try { 945 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(resource.getTypeId()); 946 CmsExplorerTypeSettings settings = OpenCms.getWorkplaceManager().getExplorerTypeSetting( 947 type.getTypeName()); 948 hasView = (settings == null) 949 || settings.getAccess().getPermissions(cms, resource).requiresViewPermission(); 950 if (hasView 951 && CmsResourceTypeXmlContent.isXmlContent(resource) 952 && !CmsResourceTypeXmlContainerPage.isContainerPage(resource)) { 953 if (contextPath == null) { 954 contextPath = resource.getRootPath(); 955 } 956 CmsResourceTypeConfig localConfigData = lookupConfigurationWithCache( 957 cms, 958 contextPath).getResourceType(type.getTypeName()); 959 if (localConfigData != null) { 960 Map<CmsUUID, CmsElementView> elementViews = getElementViews(cms); 961 hasView = elementViews.containsKey(localConfigData.getElementView()) 962 && elementViews.get(localConfigData.getElementView()).hasPermission(cms, resource); 963 } 964 } 965 // the user may only have write permissions if he is allowed to view the resource 966 hasWrite = hasView 967 && cms.hasPermissions( 968 resource, 969 CmsPermissionSet.ACCESS_WRITE, 970 false, 971 CmsResourceFilter.IGNORE_EXPIRATION) 972 && ((settings == null) 973 || settings.getAccess().getPermissions(cms, resource).requiresWritePermission()); 974 } catch (CmsLoaderException e) { 975 LOG.warn(e.getLocalizedMessage(), e); 976 hasWrite = false; 977 } 978 } 979 980 if (hasWrite && isEditorRestricted(cms, resource)) { 981 hasWrite = false; 982 } 983 984 String noEdit = new CmsResourceUtil(cms, resource).getNoEditReason( 985 OpenCms.getWorkplaceManager().getWorkplaceLocale(cms), 986 true); 987 988 boolean isFunction = false; 989 for (String type : new String[] {"function", CmsResourceTypeFunctionConfig.TYPE_NAME}) { 990 if (OpenCms.getResourceManager().matchResourceType(type, resource.getTypeId())) { 991 isFunction = true; 992 break; 993 } 994 } 995 if (isFunction) { 996 Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 997 noEdit = Messages.get().getBundle(locale).key(Messages.GUI_CANT_EDIT_FUNCTIONS_0); 998 } 999 1000 return new CmsPermissionInfo(hasView, hasWrite, noEdit); 1001 } 1002 1003 /** 1004 * Gets a map of plugin wrappers for the given site path. 1005 * 1006 * <p>This *only* includes plugins defined in site plugins active on the given path, not those referenced in formatters. 1007 * 1008 * @param cms the CMS context 1009 * @param path the path for which to get the plugins 1010 * 1011 * @return the map of plugin wrappers, with the plugin groups as keys 1012 */ 1013 public Map<String, List<CmsTemplatePluginWrapper>> getPluginsForPath(CmsObject cms, String path) { 1014 1015 CmsADEConfigData config = lookupConfigurationWithCache(cms, cms.getRequestContext().addSiteRoot(path)); 1016 1017 Multimap<String, CmsTemplatePlugin> plugins = CmsTemplatePluginFinder.getActiveTemplatePluginsFromSitePlugins( 1018 config); 1019 Map<String, List<CmsTemplatePluginWrapper>> result = new HashMap<>(); 1020 for (String key : plugins.keySet()) { 1021 List<CmsTemplatePluginWrapper> wrappers = plugins.get(key).stream().map( 1022 plugin -> new CmsTemplatePluginWrapper(cms, plugin)).collect(Collectors.toList()); 1023 result.put(key, Collections.unmodifiableList(wrappers)); 1024 } 1025 return Collections.unmodifiableMap(result); 1026 1027 } 1028 1029 /** 1030 * Gets the raw configured detail page information, with no existence checks or path correction. 1031 * 1032 * @param cms the CMS context 1033 * @return the list of raw detail page info beans 1034 */ 1035 public List<CmsDetailPageInfo> getRawDetailPages(CmsObject cms) { 1036 1037 return getCache(cms.getRequestContext().getCurrentProject().isOnlineProject()).getRawDetailPages(); 1038 } 1039 1040 /** 1041 * Returns the favorite list, or creates it if not available.<p> 1042 * 1043 * @param cms the cms context 1044 * 1045 * @return the favorite list 1046 * 1047 * @throws CmsException if something goes wrong 1048 */ 1049 public List<CmsContainerElementBean> getRecentList(CmsObject cms) throws CmsException { 1050 1051 CmsUser user = cms.getRequestContext().getCurrentUser(); 1052 Object obj = user.getAdditionalInfo(ADDINFO_ADE_RECENT_LIST); 1053 1054 List<CmsContainerElementBean> recentList = new ArrayList<CmsContainerElementBean>(); 1055 if (obj instanceof String) { 1056 try { 1057 JSONArray array = new JSONArray((String)obj); 1058 for (int i = 0; i < array.length(); i++) { 1059 try { 1060 recentList.add(elementFromJson(array.getJSONObject(i))); 1061 } catch (Throwable e) { 1062 // should never happen, catches wrong or no longer existing values 1063 LOG.warn(e.getLocalizedMessage()); 1064 } 1065 } 1066 } catch (Throwable e) { 1067 // should never happen, catches json parsing 1068 LOG.warn(e.getLocalizedMessage()); 1069 } 1070 } else { 1071 // save to be better next time 1072 saveRecentList(cms, recentList); 1073 } 1074 1075 return recentList; 1076 } 1077 1078 /** 1079 * Gets a map of sitemap attribute values by sitemap path for a given attribute key. 1080 * 1081 * @param cms the current CMS context 1082 * @param attribute the sitemap attribute key 1083 * 1084 * @return the map of attribute values, with the root paths of the corresponding subsitemaps as keys 1085 */ 1086 public Map<String, String> getSitemapAttributeValuesByPath(CmsObject cms, String attribute) { 1087 1088 boolean online = (null == cms) || isOnline(cms); 1089 CmsADEConfigCacheState state = getCacheState(online); 1090 return state.getAttributeValuesByPath(attribute); 1091 } 1092 1093 /** 1094 * Gets the sitemap configuration resource type.<p> 1095 * 1096 * @return the resource type for sitemap configurations 1097 */ 1098 public I_CmsResourceType getSitemapConfigurationType() { 1099 1100 return m_configType; 1101 } 1102 1103 /** 1104 * Gets the registered sitemap extra info providers. 1105 * 1106 * @return the list of providers 1107 */ 1108 public List<I_CmsSitemapExtraInfoProvider> getSitemapExtraInfoProviders() { 1109 1110 return m_sitemapExtraInfoProviders.stream().sorted( 1111 Comparator.comparing(provider -> provider.getOrder())).collect(Collectors.toList()); 1112 } 1113 1114 /** 1115 * Returns all sub sites below the given path.<p> 1116 * 1117 * @param cms the cms context 1118 * @param subSiteRoot the sub site root path 1119 * 1120 * @return the sub site root paths 1121 */ 1122 public List<String> getSubSitePaths(CmsObject cms, String subSiteRoot) { 1123 1124 List<String> result = new ArrayList<String>(); 1125 String normalizedRootPath = CmsStringUtil.joinPaths("/", subSiteRoot, "/"); 1126 CmsADEConfigCacheState state = getCacheState(isOnline(cms)); 1127 Set<String> siteConfigurationPaths = state.getSiteConfigurationPaths(); 1128 for (String path : siteConfigurationPaths) { 1129 if ((path.length() > normalizedRootPath.length()) && path.startsWith(normalizedRootPath)) { 1130 result.add(path); 1131 } 1132 } 1133 return result; 1134 } 1135 1136 /** 1137 * Tries to get the subsite root for a given resource root path.<p> 1138 * 1139 * @param cms the current CMS context 1140 * @param rootPath the root path for which the subsite root should be found 1141 * 1142 * @return the subsite root 1143 */ 1144 public String getSubSiteRoot(CmsObject cms, String rootPath) { 1145 1146 CmsADEConfigData configData = lookupConfiguration(cms, rootPath); 1147 String basePath = configData.getBasePath(); 1148 String siteRoot = OpenCms.getSiteManager().getSiteRoot(rootPath); 1149 if (siteRoot == null) { 1150 siteRoot = ""; 1151 } 1152 if ((basePath == null) || !basePath.startsWith(siteRoot)) { 1153 // the subsite root should always be below the site root 1154 return siteRoot; 1155 } else { 1156 return basePath; 1157 } 1158 } 1159 1160 /** 1161 * Gets the subsites to be displayed in the site selector. 1162 * 1163 * @param online true if we want the subsites for the Online project 1164 * 1165 * @return the subsites to be displayed in the site selector 1166 */ 1167 public List<String> getSubsitesForSiteSelector(boolean online) { 1168 1169 return getCacheState(online).getSubsitesForSiteSelector(); 1170 1171 } 1172 1173 /** 1174 * Gets the table of upload warnings. 1175 * 1176 * @return the table of upload warnings 1177 */ 1178 public CmsUploadWarningTable getUploadWarningTable() { 1179 1180 return m_uploadWarningTable; 1181 } 1182 1183 /** 1184 * Processes a HTML redirect content.<p> 1185 * 1186 * This needs to be in the ADE manager because the user for whom the HTML redirect is being loaded 1187 * does not necessarily have read permissions for the redirect target, so we read the redirect target 1188 * with admin privileges.<p> 1189 * 1190 * @param userCms the CMS context of the current user 1191 * @param request the servlet request 1192 * @param response the servlet response 1193 * @param htmlRedirect the path of the HTML redirect resource 1194 * 1195 * @throws CmsException if something goes wrong 1196 */ 1197 public void handleHtmlRedirect( 1198 CmsObject userCms, 1199 HttpServletRequest request, 1200 HttpServletResponse response, 1201 String htmlRedirect) 1202 throws CmsException { 1203 1204 CmsObject cms = OpenCms.initCmsObject(m_offlineCms); 1205 CmsRequestContext userContext = userCms.getRequestContext(); 1206 CmsRequestContext currentContext = cms.getRequestContext(); 1207 currentContext.setCurrentProject(userContext.getCurrentProject()); 1208 currentContext.setSiteRoot(userContext.getSiteRoot()); 1209 currentContext.setLocale(userContext.getLocale()); 1210 currentContext.setUri(userContext.getUri()); 1211 1212 CmsFile file = cms.readFile(htmlRedirect); 1213 CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file); 1214 1215 // find out the locale to use for reading values from the redirect 1216 List<Locale> candidates = new ArrayList<Locale>(); 1217 candidates.add(currentContext.getLocale()); 1218 candidates.add(CmsLocaleManager.getDefaultLocale()); 1219 candidates.add(Locale.ENGLISH); 1220 candidates.addAll(content.getLocales()); 1221 Locale contentLocale = currentContext.getLocale(); 1222 for (Locale candidateLocale : candidates) { 1223 if (content.hasLocale(candidateLocale)) { 1224 contentLocale = candidateLocale; 1225 break; 1226 } 1227 } 1228 1229 String typeValue = content.getValue(N_TYPE, contentLocale).getStringValue(cms); 1230 String lnkUri = ""; 1231 Integer errorCode; 1232 if ("sublevel".equals(typeValue)) { 1233 // use the nav builder to get the first sub level entry 1234 CmsJspNavBuilder navBuilder = new CmsJspNavBuilder(cms); 1235 if (navBuilder.getNavigationForFolder().size() > 0) { 1236 CmsJspNavElement target = navBuilder.getNavigationForFolder().get(0); 1237 lnkUri = CmsJspTagLink.linkTagAction(target.getResourceName(), request); 1238 errorCode = Integer.valueOf(HttpServletResponse.SC_MOVED_TEMPORARILY); 1239 } else { 1240 // send error 404 if no sub entry available 1241 errorCode = Integer.valueOf(HttpServletResponse.SC_NOT_FOUND); 1242 } 1243 } else { 1244 I_CmsXmlContentValue contentValue = content.getValue(N_LINK, contentLocale); 1245 if (contentValue != null) { 1246 String linkValue = contentValue.getStringValue(cms); 1247 lnkUri = OpenCms.getLinkManager().substituteLinkForUnknownTarget(cms, linkValue); 1248 try { 1249 errorCode = Integer.valueOf(typeValue); 1250 } catch (NumberFormatException e) { 1251 LOG.error(e.getMessage(), e); 1252 // fall back to default 1253 errorCode = Integer.valueOf(307); 1254 } 1255 } else { 1256 // send error 404 if no link value is set 1257 errorCode = Integer.valueOf(HttpServletResponse.SC_NOT_FOUND); 1258 } 1259 } 1260 if (!currentContext.getCurrentProject().isOnlineProject()) { 1261 // permanent redirects are confusing and not useful in the Offline project because they are stored 1262 // by the browser based on the host name, not the site the user is working in. 1263 if (errorCode.intValue() == HttpServletResponse.SC_MOVED_PERMANENTLY) { 1264 errorCode = Integer.valueOf(HttpServletResponse.SC_MOVED_TEMPORARILY); 1265 } 1266 } 1267 request.setAttribute(CmsRequestUtil.ATTRIBUTE_ERRORCODE, errorCode); 1268 response.setHeader("Location", CmsEncoder.convertHostToPunycode(lnkUri)); 1269 response.setHeader("Connection", "close"); 1270 response.setStatus(errorCode.intValue()); 1271 } 1272 1273 /** 1274 * Initializes the configuration by reading all configuration files and caching their data.<p> 1275 */ 1276 public synchronized void initialize() { 1277 1278 // no need to try initialization in case the 'org.opencms.base' is not present and the contained resource types missing 1279 if ((m_initStatus == Status.notInitialized) && OpenCms.getModuleManager().hasModule(MODULE_NAME_ADE_CONFIG)) { 1280 try { 1281 CmsLog.INIT.info(". Initializing the ADE configuration, this may take a while..."); 1282 m_initStatus = Status.initializing; 1283 m_configType = OpenCms.getResourceManager().getResourceType(CONFIG_TYPE); 1284 m_moduleConfigType = OpenCms.getResourceManager().getResourceType(MODULE_CONFIG_TYPE); 1285 m_elementViewType = OpenCms.getResourceManager().getResourceType(ELEMENT_VIEW_TYPE); 1286 CmsProject temp = getTempfileProject(m_onlineCms); 1287 m_offlineCms = OpenCms.initCmsObject(m_onlineCms); 1288 m_offlineCms.getRequestContext().setCurrentProject(temp); 1289 m_onlineCache = new CmsConfigurationCache( 1290 m_onlineCms, 1291 m_configType, 1292 m_moduleConfigType, 1293 m_elementViewType); 1294 m_offlineCache = new CmsConfigurationCache( 1295 m_offlineCms, 1296 m_configType, 1297 m_moduleConfigType, 1298 m_elementViewType); 1299 CmsLog.INIT.info(". Reading online configuration..."); 1300 m_onlineCache.initialize(); 1301 CmsLog.INIT.info(". Reading offline configuration..."); 1302 m_offlineCache.initialize(); 1303 m_onlineContainerConfigurationCache = new CmsContainerConfigurationCache( 1304 m_onlineCms, 1305 "online inheritance groups"); 1306 m_offlineContainerConfigurationCache = new CmsContainerConfigurationCache( 1307 m_offlineCms, 1308 "offline inheritance groups"); 1309 CmsLog.INIT.info(". Reading online inherited container configurations..."); 1310 m_onlineContainerConfigurationCache.initialize(); 1311 CmsLog.INIT.info(". Reading offline inherited container configurations..."); 1312 m_offlineContainerConfigurationCache.initialize(); 1313 m_offlineFormatterCache = new CmsFormatterConfigurationCache(m_offlineCms, "offline formatters"); 1314 m_onlineFormatterCache = new CmsFormatterConfigurationCache(m_onlineCms, "online formatters"); 1315 CmsLog.INIT.info(". Reading online formatter configurations..."); 1316 m_onlineFormatterCache.initialize(); 1317 CmsLog.INIT.info(". Reading offline formatter configurations..."); 1318 m_offlineFormatterCache.initialize(); 1319 1320 m_offlineDetailIdCache = new CmsDetailNameCache(m_offlineCms); 1321 m_onlineDetailIdCache = new CmsDetailNameCache(m_onlineCms); 1322 CmsLog.INIT.info(". Initializing online detail name cache..."); 1323 m_onlineDetailIdCache.initialize(); 1324 CmsLog.INIT.info(". Initializing offline detail name cache..."); 1325 m_offlineDetailIdCache.initialize(); 1326 1327 CmsGlobalConfigurationCacheEventHandler handler = new CmsGlobalConfigurationCacheEventHandler( 1328 m_onlineCms); 1329 handler.addCache(m_offlineCache, m_onlineCache, "ADE configuration cache"); 1330 handler.addCache( 1331 m_offlineContainerConfigurationCache, 1332 m_onlineContainerConfigurationCache, 1333 "Inherited container cache"); 1334 handler.addCache(m_offlineFormatterCache, m_onlineFormatterCache, "formatter configuration cache"); 1335 handler.addCache(m_offlineDetailIdCache, m_onlineDetailIdCache, "Detail ID cache"); 1336 OpenCms.getEventManager().addCmsEventListener(handler); 1337 CmsLog.INIT.info(". Done initializing the ADE configuration."); 1338 m_initStatus = Status.initialized; 1339 } catch (CmsException e) { 1340 m_initStatus = Status.notInitialized; 1341 LOG.error(e.getLocalizedMessage(), e); 1342 } 1343 m_detailPageHandler.initialize(m_offlineCms, m_onlineCms); 1344 } 1345 } 1346 1347 /** 1348 * Checks whether the given resource is configured as a detail page.<p> 1349 * 1350 * @param cms the current CMS context 1351 * @param resource the resource which should be tested 1352 * 1353 * @return true if the resource is configured as a detail page 1354 */ 1355 public boolean isDetailPage(CmsObject cms, CmsResource resource) { 1356 1357 return getCache(isOnline(cms)).isDetailPage(cms, resource); 1358 } 1359 1360 /** 1361 * Checks if the user should be prevented from editing a file. 1362 * 1363 * <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. 1364 * 1365 * @param cms the CMS context 1366 * @param res the resource to check 1367 * @return true if the user should be prevented from editing the file 1368 */ 1369 public boolean isEditorRestricted(CmsObject cms, CmsResource res) { 1370 1371 if (OpenCms.getResourceManager().matchResourceType(CONFIG_TYPE, res.getTypeId())) { 1372 CmsRole role = getRoleForSitemapConfigEditing(); 1373 if ((role != null) && !OpenCms.getRoleManager().hasRoleForResource(cms, role, res)) { 1374 return true; 1375 } 1376 } 1377 return false; 1378 } 1379 1380 /** 1381 * Checks if an element is reused in a page or group that is not excluded by a given predicate. 1382 * 1383 * @param resource the resource to check 1384 * @param exclude predicate used to ignore reuses which match it 1385 * @return true if the element is reused 1386 */ 1387 public boolean isElementReused(CmsResource resource, Predicate<CmsResource> exclude) { 1388 1389 return getOfflineElementUses(resource).anyMatch(source -> !exclude.test(source)); 1390 } 1391 1392 /** 1393 * Checks whether the ADE manager is initialized (this should usually be the case except during the setup).<p> 1394 * 1395 * @return true if the ADE manager is initialized 1396 */ 1397 public boolean isInitialized() { 1398 1399 return m_initStatus == Status.initialized; 1400 } 1401 1402 /** 1403 * Returns the show editor help flag.<p> 1404 * 1405 * @param cms the cms context 1406 * 1407 * @return the show editor help flag 1408 */ 1409 public boolean isShowEditorHelp(CmsObject cms) { 1410 1411 CmsUser user = cms.getRequestContext().getCurrentUser(); 1412 String showHelp = (String)user.getAdditionalInfo(ADDINFO_ADE_SHOW_EDITOR_HELP); 1413 return CmsStringUtil.isEmptyOrWhitespaceOnly(showHelp) || Boolean.parseBoolean(showHelp); 1414 } 1415 1416 /** 1417 * Looks up the configuration data for a given sitemap path.<p> 1418 * 1419 * @param cms the current CMS context 1420 * @param rootPath the root path for which the configuration data should be looked up 1421 * 1422 * @return the configuration data 1423 */ 1424 public CmsADEConfigData lookupConfiguration(CmsObject cms, String rootPath) { 1425 1426 CmsADEConfigData configData = internalLookupConfiguration(cms, rootPath); 1427 return configData; 1428 } 1429 1430 /** 1431 * Looks up the configuration data for a given sitemap path, but uses a thread-local cache for the current request for efficiency. 1432 * 1433 * @param cms the current CMS context 1434 * @param rootPath the root path for which the configuration data should be looked up 1435 * 1436 * @return the configuration data 1437 */ 1438 public CmsADEConfigData lookupConfigurationWithCache(CmsObject cms, String rootPath) { 1439 1440 boolean online = (cms == null) || cms.getRequestContext().getCurrentProject().isOnlineProject(); 1441 String cacheKey = "" + online + ":" + rootPath; 1442 OpenCmsServlet.RequestCache context = OpenCmsServlet.getRequestCache(); 1443 CmsADEConfigData result = null; 1444 if (context != null) { 1445 result = context.getCachedConfig(cacheKey); 1446 } 1447 if (result == null) { 1448 result = internalLookupConfiguration(cms, rootPath); 1449 if (context != null) { 1450 context.setCachedConfig(cacheKey, result); 1451 } 1452 } 1453 return result; 1454 } 1455 1456 /** 1457 * Reloads the configuration.<p> 1458 * 1459 * Normally you shouldn't call this directly since the event handlers take care of updating the configuration. 1460 */ 1461 public void refresh() { 1462 1463 m_onlineCache.initialize(); 1464 m_offlineCache.initialize(); 1465 } 1466 1467 public void removeSitemapExtraInfoProvider(I_CmsSitemapExtraInfoProvider provider) { 1468 1469 m_sitemapExtraInfoProviders.remove(provider); 1470 } 1471 1472 /** 1473 * Saves a list of detail pages.<p> 1474 * @param cms the cms context 1475 * @param rootPath the root path 1476 * @param detailPages the detail pages 1477 * @param newId the id to use for new detail pages without an id 1478 * @return true if the detail pages could be successfully saved 1479 * 1480 * @throws CmsException if something goes wrong 1481 */ 1482 public boolean saveDetailPages(CmsObject cms, String rootPath, List<CmsDetailPageInfo> detailPages, CmsUUID newId) 1483 throws CmsException { 1484 1485 CmsADEConfigData configData = lookupConfiguration(cms, rootPath); 1486 CmsDetailPageConfigurationWriter configWriter; 1487 String originalSiteRoot = cms.getRequestContext().getSiteRoot(); 1488 try { 1489 cms.getRequestContext().setSiteRoot(""); 1490 if (configData.isModuleConfiguration()) { 1491 return false; 1492 } 1493 CmsResource configFile = configData.getResource(); 1494 configWriter = new CmsDetailPageConfigurationWriter(cms, configFile); 1495 configWriter.updateAndSave(detailPages, newId); 1496 return true; 1497 } finally { 1498 cms.getRequestContext().setSiteRoot(originalSiteRoot); 1499 } 1500 } 1501 1502 /** 1503 * Saves the favorite list, user based.<p> 1504 * 1505 * @param cms the cms context 1506 * @param favoriteList the element list 1507 * 1508 * @throws CmsException if something goes wrong 1509 */ 1510 public void saveFavoriteList(CmsObject cms, List<CmsContainerElementBean> favoriteList) throws CmsException { 1511 1512 saveElementList(cms, favoriteList, ADDINFO_ADE_FAVORITE_LIST); 1513 } 1514 1515 /** 1516 * Saves the inheritance container information.<p> 1517 * 1518 * @param cms the current cms context 1519 * @param pageResource the resource or parent folder 1520 * @param name the inheritance name 1521 * @param newOrder if the element have been reordered 1522 * @param elements the elements 1523 * 1524 * @throws CmsException if something goes wrong 1525 */ 1526 public void saveInheritedContainer( 1527 CmsObject cms, 1528 CmsResource pageResource, 1529 String name, 1530 boolean newOrder, 1531 List<CmsContainerElementBean> elements) 1532 throws CmsException { 1533 1534 CmsContainerConfigurationWriter writer = new CmsContainerConfigurationWriter(); 1535 writer.save(cms, name, newOrder, pageResource, elements); 1536 1537 // Inheritance groups are usually reloaded directly after saving them, 1538 // so the cache needs to be up to date after this method is called 1539 m_offlineContainerConfigurationCache.flushUpdates(); 1540 } 1541 1542 /** 1543 * Saves the inheritance container information.<p> 1544 * 1545 * @param cms the current cms context 1546 * @param sitePath the site path of the resource or parent folder 1547 * @param name the inheritance name 1548 * @param newOrder if the element have been reordered 1549 * @param elements the elements 1550 * 1551 * @throws CmsException if something goes wrong 1552 */ 1553 public void saveInheritedContainer( 1554 CmsObject cms, 1555 String sitePath, 1556 String name, 1557 boolean newOrder, 1558 List<CmsContainerElementBean> elements) 1559 throws CmsException { 1560 1561 saveInheritedContainer(cms, cms.readResource(sitePath), name, newOrder, elements); 1562 } 1563 1564 /** 1565 * Saves the favorite list, user based.<p> 1566 * 1567 * @param cms the cms context 1568 * @param recentList the element list 1569 * 1570 * @throws CmsException if something goes wrong 1571 */ 1572 public void saveRecentList(CmsObject cms, List<CmsContainerElementBean> recentList) throws CmsException { 1573 1574 saveElementList(cms, recentList, ADDINFO_ADE_RECENT_LIST); 1575 } 1576 1577 /** 1578 * Sets the show editor help flag.<p> 1579 * 1580 * @param cms the cms context 1581 * @param showHelp the show help flag 1582 * @throws CmsException if writing the user info fails 1583 */ 1584 public void setShowEditorHelp(CmsObject cms, boolean showHelp) throws CmsException { 1585 1586 CmsUser user = cms.getRequestContext().getCurrentUser(); 1587 user.setAdditionalInfo(ADDINFO_ADE_SHOW_EDITOR_HELP, String.valueOf(showHelp)); 1588 cms.writeUser(user); 1589 } 1590 1591 /** 1592 * The method which is called when the OpenCms instance is shut down.<p> 1593 */ 1594 public void shutdown() { 1595 1596 // do nothing 1597 } 1598 1599 /** 1600 * Waits until the next time the cache is updated.<p> 1601 * 1602 * @param online true if we want to wait for the online cache, false for the offline cache 1603 */ 1604 public void waitForCacheUpdate(boolean online) { 1605 1606 getCache(online).getWaitHandleForUpdateTask().enter(2 * CmsConfigurationCache.TASK_DELAY_MILLIS); 1607 } 1608 1609 /** 1610 * Waits until the formatter cache has finished updating itself.<p> 1611 * 1612 * This method is only intended for use in test cases. 1613 * 1614 * @param online true if we should wait for the online formatter cache,false for the offline cache 1615 */ 1616 public void waitForFormatterCache(boolean online) { 1617 1618 CmsFormatterConfigurationCache cache = online ? m_onlineFormatterCache : m_offlineFormatterCache; 1619 cache.waitForUpdate(); 1620 } 1621 1622 /** 1623 * Creates an element from its serialized data.<p> 1624 * 1625 * @param data the serialized data 1626 * 1627 * @return the restored element bean 1628 * 1629 * @throws JSONException if the serialized data got corrupted 1630 */ 1631 protected CmsContainerElementBean elementFromJson(JSONObject data) throws JSONException { 1632 1633 CmsUUID element = new CmsUUID(data.getString(FavListProp.ELEMENT.name().toLowerCase())); 1634 CmsUUID formatter = null; 1635 if (data.has(FavListProp.FORMATTER.name().toLowerCase())) { 1636 formatter = new CmsUUID(data.getString(FavListProp.FORMATTER.name().toLowerCase())); 1637 } 1638 Map<String, String> properties = new HashMap<String, String>(); 1639 1640 JSONObject props = data.getJSONObject(FavListProp.PROPERTIES.name().toLowerCase()); 1641 Iterator<String> keys = props.keys(); 1642 while (keys.hasNext()) { 1643 String key = keys.next(); 1644 properties.put(key, props.getString(key)); 1645 } 1646 1647 return new CmsContainerElementBean(element, formatter, properties, false); 1648 } 1649 1650 /** 1651 * Converts the given element to JSON.<p> 1652 * 1653 * @param element the element to convert 1654 * @param excludeSettings the keys of settings which should not be written to the JSON 1655 * 1656 * @return the JSON representation 1657 */ 1658 protected JSONObject elementToJson(CmsContainerElementBean element, Set<String> excludeSettings) { 1659 1660 JSONObject data = null; 1661 try { 1662 data = new JSONObject(); 1663 data.put(FavListProp.ELEMENT.name().toLowerCase(), element.getId().toString()); 1664 if (element.getFormatterId() != null) { 1665 data.put(FavListProp.FORMATTER.name().toLowerCase(), element.getFormatterId().toString()); 1666 } 1667 JSONObject properties = new JSONObject(); 1668 for (Map.Entry<String, String> entry : element.getIndividualSettings().entrySet()) { 1669 String settingKey = entry.getKey(); 1670 if (!excludeSettings.contains(settingKey)) { 1671 properties.put(entry.getKey(), entry.getValue()); 1672 } 1673 } 1674 data.put(FavListProp.PROPERTIES.name().toLowerCase(), properties); 1675 } catch (JSONException e) { 1676 // should never happen 1677 if (!LOG.isDebugEnabled()) { 1678 LOG.warn(e.getLocalizedMessage()); 1679 } 1680 LOG.debug(e.getLocalizedMessage(), e); 1681 return null; 1682 } 1683 return data; 1684 } 1685 1686 /** 1687 * Gets the configuration cache instance.<p> 1688 * 1689 * @param online true if you want the online cache, false for the offline cache 1690 * 1691 * @return the ADE configuration cache instance 1692 */ 1693 protected CmsConfigurationCache getCache(boolean online) { 1694 1695 return online ? m_onlineCache : m_offlineCache; 1696 } 1697 1698 /** 1699 * Gets the offline cache.<p> 1700 * 1701 * @return the offline configuration cache 1702 */ 1703 protected CmsConfigurationCache getOfflineCache() { 1704 1705 return m_offlineCache; 1706 } 1707 1708 /** 1709 * Gets the online cache.<p> 1710 * 1711 * @return the online configuration cache 1712 */ 1713 protected CmsConfigurationCache getOnlineCache() { 1714 1715 return m_onlineCache; 1716 } 1717 1718 /** 1719 * Gets the role necessary to edit sitemap configuration files. 1720 * 1721 * @return the role needed for editing sitemap configurations 1722 */ 1723 protected CmsRole getRoleForSitemapConfigEditing() { 1724 1725 String roleName = OpenCms.getWorkplaceManager().getSitemapConfigEditRole(); 1726 if (roleName == null) { 1727 return null; 1728 } else { 1729 if (roleName.indexOf("/") == -1) { 1730 return CmsRole.valueOfRoleName(roleName).forOrgUnit(null); 1731 } else { 1732 return CmsRole.valueOfRoleName(roleName); 1733 } 1734 } 1735 } 1736 1737 /** 1738 * Gets the root path for a given resource structure id.<p> 1739 * 1740 * @param structureId the structure id 1741 * @param online if true, the resource will be looked up in the online project ,else in the offline project 1742 * 1743 * @return the root path for the given structure id 1744 * 1745 * @throws CmsException if something goes wrong 1746 */ 1747 protected String getRootPath(CmsUUID structureId, boolean online) throws CmsException { 1748 1749 CmsConfigurationCache cache = online ? m_onlineCache : m_offlineCache; 1750 return cache.getPathForStructureId(structureId); 1751 } 1752 1753 /** 1754 * Gets a tempfile project, creating one if it doesn't exist already.<p> 1755 * 1756 * @param cms the CMS context to use 1757 * @return the tempfile project 1758 * 1759 * @throws CmsException if something goes wrong 1760 */ 1761 protected CmsProject getTempfileProject(CmsObject cms) throws CmsException { 1762 1763 try { 1764 return cms.readProject(I_CmsProjectDriver.TEMP_FILE_PROJECT_NAME); 1765 } catch (CmsException e) { 1766 return cms.createTempfileProject(); 1767 } 1768 } 1769 1770 /** 1771 * Internal configuration lookup method.<p> 1772 * 1773 * @param cms the cms context 1774 * @param rootPath the root path for which to look up the configuration 1775 * 1776 * @return the configuration for the given path 1777 */ 1778 protected CmsADEConfigData internalLookupConfiguration(CmsObject cms, String rootPath) { 1779 1780 boolean online = (null == cms) || isOnline(cms); 1781 CmsADEConfigCacheState state = getCacheState(online); 1782 return state.lookupConfiguration(rootPath); 1783 } 1784 1785 /** 1786 * Returns true if the project set in the CmsObject is the Online project.<p> 1787 * 1788 * @param cms the CMS context to check 1789 * 1790 * @return true if the project set in the CMS context is the Online project 1791 */ 1792 private boolean isOnline(CmsObject cms) { 1793 1794 return cms.getRequestContext().getCurrentProject().isOnlineProject(); 1795 } 1796 1797 /** 1798 * Saves an element list to the user additional infos.<p> 1799 * 1800 * @param cms the cms context 1801 * @param elementList the element list 1802 * @param listKey the list key 1803 * 1804 * @throws CmsException if something goes wrong 1805 */ 1806 private void saveElementList(CmsObject cms, List<CmsContainerElementBean> elementList, String listKey) 1807 throws CmsException { 1808 1809 // limit the favorite list size to avoid the additional info size limit 1810 if (elementList.size() > DEFAULT_ELEMENT_LIST_SIZE) { 1811 elementList = elementList.subList(0, DEFAULT_ELEMENT_LIST_SIZE); 1812 } 1813 1814 JSONArray data = new JSONArray(); 1815 1816 Set<String> excludedSettings = new HashSet<String>(); 1817 // do not store the template contexts, since dragging an element into the page which might be invisible 1818 // doesn't make sense 1819 excludedSettings.add(CmsTemplateContextInfo.SETTING); 1820 1821 for (CmsContainerElementBean element : elementList) { 1822 data.put(elementToJson(element, excludedSettings)); 1823 } 1824 CmsUser user = cms.getRequestContext().getCurrentUser(); 1825 user.setAdditionalInfo(listKey, data.toString()); 1826 cms.writeUser(user); 1827 } 1828}