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