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