001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (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.detailpage; 029 030import org.opencms.ade.configuration.CmsADEConfigData; 031import org.opencms.ade.configuration.CmsADEManager; 032import org.opencms.configuration.CmsParameterConfiguration; 033import org.opencms.file.CmsObject; 034import org.opencms.file.CmsResource; 035import org.opencms.file.CmsVfsResourceNotFoundException; 036import org.opencms.main.CmsException; 037import org.opencms.main.CmsLog; 038import org.opencms.main.OpenCms; 039import org.opencms.site.CmsSite; 040import org.opencms.util.CmsFileUtil; 041import org.opencms.util.CmsStringUtil; 042import org.opencms.workplace.CmsWorkplace; 043 044import java.util.ArrayList; 045import java.util.Arrays; 046import java.util.Collection; 047import java.util.List; 048import java.util.Set; 049 050import org.apache.commons.lang3.RandomStringUtils; 051import org.apache.commons.logging.Log; 052 053/** 054 * This class uses information from the detail page information stored in the sitemap to find/recognize the detail pages for 055 * a given resource.<p> 056 * 057 * @since 8.0.0 058 */ 059public class CmsDefaultDetailPageHandler implements I_CmsDetailPageHandler { 060 061 /** 062 * Helper class for storing intermediate results when looking up detail pages for a resource. 063 */ 064 private static class DetailPageConfigData { 065 066 /** The link source config. */ 067 private CmsADEConfigData m_sourceConfig; 068 069 /** The configuration whose detail pages are used. */ 070 private CmsADEConfigData m_configForDetailPages; 071 072 /** The link target configuration. */ 073 private CmsADEConfigData m_targetConfig; 074 075 /** The detail pages. */ 076 private List<CmsDetailPageInfo> m_detailPages = new ArrayList<>(); 077 078 /** 079 * Gets the config for detail pages. 080 * 081 * @return the config for detail pages 082 */ 083 public CmsADEConfigData getConfigForDetailPages() { 084 085 return m_configForDetailPages; 086 } 087 088 /** 089 * Gets the detail pages. 090 * 091 * @return the detail pages 092 */ 093 public List<CmsDetailPageInfo> getDetailPages() { 094 095 return m_detailPages; 096 } 097 098 /** 099 * Gets the source config. 100 * 101 * @return the source config 102 */ 103 @SuppressWarnings("unused") 104 public CmsADEConfigData getSourceConfig() { 105 106 return m_sourceConfig; 107 } 108 109 /** 110 * Gets the target config. 111 * 112 * @return the target config 113 */ 114 @SuppressWarnings("unused") 115 public CmsADEConfigData getTargetConfig() { 116 117 return m_targetConfig; 118 } 119 120 /** 121 * Sets the config for detail pages. 122 * 123 * @param configForDetailPages the new config for detail pages 124 */ 125 public void setConfigForDetailPages(CmsADEConfigData configForDetailPages) { 126 127 m_configForDetailPages = configForDetailPages; 128 } 129 130 /** 131 * Sets the detail pages. 132 * 133 * @param detailPages the new detail pages 134 */ 135 public void setDetailPages(List<CmsDetailPageInfo> detailPages) { 136 137 m_detailPages = detailPages; 138 } 139 140 /** 141 * Sets the source config. 142 * 143 * @param sourceConfig the new source config 144 */ 145 public void setSourceConfig(CmsADEConfigData sourceConfig) { 146 147 m_sourceConfig = sourceConfig; 148 } 149 150 /** 151 * Sets the target config. 152 * 153 * @param targetConfig the new target config 154 */ 155 public void setTargetConfig(CmsADEConfigData targetConfig) { 156 157 m_targetConfig = targetConfig; 158 } 159 160 } 161 162 /** Logger instance for this class. */ 163 private static final Log LOG = CmsLog.getLog(CmsDefaultDetailPageHandler.class); 164 165 /** The configuration. */ 166 private CmsParameterConfiguration m_config = new CmsParameterConfiguration(); 167 168 /** 169 * Constructor. 170 */ 171 public CmsDefaultDetailPageHandler() { 172 173 // just for debugging 174 LOG.debug("Created default detail page handler."); 175 } 176 177 /** 178 * Adds the configuration parameter. 179 * 180 * @param paramName the param name 181 * @param paramValue the param value 182 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#addConfigurationParameter(java.lang.String, java.lang.String) 183 */ 184 public void addConfigurationParameter(String paramName, String paramValue) { 185 186 m_config.add(paramName, paramValue); 187 188 } 189 190 /** 191 * Gets the all detail pages. 192 * 193 * @param cms the cms 194 * @param resType the res type 195 * @return the all detail pages 196 * @throws CmsException the cms exception 197 * @see org.opencms.ade.detailpage.I_CmsDetailPageHandler#getAllDetailPages(org.opencms.file.CmsObject, int) 198 */ 199 public Collection<String> getAllDetailPages(CmsObject cms, int resType) throws CmsException { 200 201 if (!OpenCms.getADEManager().isInitialized()) { 202 return new ArrayList<String>(); 203 } 204 String typeName = OpenCms.getResourceManager().getResourceType(resType).getTypeName(); 205 return OpenCms.getADEManager().getDetailPages(cms, typeName); 206 } 207 208 /** 209 * Gets the configuration. 210 * 211 * @return the configuration 212 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#getConfiguration() 213 */ 214 public CmsParameterConfiguration getConfiguration() { 215 216 return m_config; 217 218 } 219 220 /** 221 * Gets the detail page for a content element.<p> 222 * 223 * @param manager the ADE manager instance. 224 * @param cms the CMS context 225 * @param contentRootPath the element's root path 226 * @param originPath the site path from which the detail content is being linked 227 * @param targetDetailPage the target detail page to use 228 * 229 * @return the detail page for the content element 230 */ 231 public String getDetailPage( 232 CmsADEManager manager, 233 CmsObject cms, 234 String contentRootPath, 235 String originPath, 236 String targetDetailPage) { 237 238 boolean online = cms.getRequestContext().getCurrentProject().isOnlineProject(); 239 Set<String> parentFolderTypes = manager.getParentFolderTypes(online, contentRootPath); 240 if (parentFolderTypes.isEmpty()) { 241 return null; 242 } 243 244 if (targetDetailPage != null) { 245 try { 246 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(targetDetailPage); 247 CmsResource targetDetailPageRes = null; 248 if (site != null) { 249 CmsObject rootCms = OpenCms.initCmsObject(cms); 250 rootCms.getRequestContext().setSiteRoot(""); 251 targetDetailPageRes = rootCms.readResource(targetDetailPage); 252 } else { 253 targetDetailPageRes = cms.readResource(targetDetailPage); 254 } 255 if (manager.isDetailPage(cms, targetDetailPageRes)) { 256 return targetDetailPageRes.getRootPath(); 257 } 258 } catch (CmsVfsResourceNotFoundException e) { 259 LOG.debug(e.getLocalizedMessage(), e); 260 } catch (Exception e) { 261 LOG.warn(e.getLocalizedMessage(), e); 262 } 263 } 264 265 String resType = null; 266 try { 267 CmsObject rootCms = OpenCms.initCmsObject(cms); 268 rootCms.getRequestContext().setSiteRoot(""); 269 CmsResource detailResource = rootCms.readResource(contentRootPath); 270 resType = OpenCms.getResourceManager().getResourceType(detailResource).getTypeName(); 271 } catch (CmsVfsResourceNotFoundException e) { 272 LOG.info(e.getLocalizedMessage(), e); 273 } catch (Exception e) { 274 LOG.warn(e.getLocalizedMessage(), e); 275 } 276 if (resType == null) { 277 if (parentFolderTypes.size() > 1) { 278 LOG.warn( 279 "Couldn't determine resource type of detail resource " 280 + contentRootPath 281 + ", picking arbitrarily from configured parent folder types " 282 + parentFolderTypes); 283 } 284 resType = parentFolderTypes.iterator().next(); 285 286 } 287 DetailPageConfigData context = lookupDetailPageConfigData(manager, cms, contentRootPath, originPath, resType); 288 List<CmsDetailPageInfo> relevantPages = context.getDetailPages(); 289 if (context.getConfigForDetailPages() == null) { 290 return null; 291 } 292 LOG.info( 293 "Trying to determine detail page for '" 294 + contentRootPath 295 + "' in context '" 296 + context.getConfigForDetailPages().getBasePath() 297 + "'"); 298 if (!CmsStringUtil.isPrefixPath( 299 context.getConfigForDetailPages().getExternalDetailContentExclusionFolder(), 300 contentRootPath)) { 301 return null; 302 } 303 String result = new CmsDetailPageFilter(cms, contentRootPath).filterDetailPages(relevantPages).map( 304 info -> info.getUri()).findFirst().orElse(null); 305 return result; 306 } 307 308 /** 309 * Gets the detail page to use for a detail resource. 310 * 311 * @param cms the cms 312 * @param rootPath the root path 313 * @param linkSource the link source 314 * @param targetDetailPage the target detail page 315 * @return the detail page 316 * @see org.opencms.ade.detailpage.I_CmsDetailPageHandler#getDetailPage(org.opencms.file.CmsObject, java.lang.String, java.lang.String, java.lang.String) 317 */ 318 public String getDetailPage(CmsObject cms, String rootPath, String linkSource, String targetDetailPage) { 319 320 CmsADEManager manager = OpenCms.getADEManager(); 321 if (!manager.isInitialized()) { 322 return null; 323 } 324 325 if (rootPath.endsWith(".jsp") || rootPath.startsWith(CmsWorkplace.VFS_PATH_WORKPLACE)) { 326 // exclude these for performance reasons 327 return null; 328 } 329 String result = getDetailPage(manager, cms, rootPath, linkSource, targetDetailPage); 330 if (result == null) { 331 return null; 332 } 333 if (!CmsResource.isFolder(result)) { 334 result = CmsResource.getFolderPath(result); 335 } 336 return result; 337 } 338 339 /** 340 * Inits the configuration. 341 * 342 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#initConfiguration() 343 */ 344 public void initConfiguration() { 345 346 m_config = CmsParameterConfiguration.unmodifiableVersion(m_config); 347 348 } 349 350 /** 351 * Initialize. 352 * 353 * @param offlineCms the offline cms 354 * @param onlineCms the online cms 355 * @see org.opencms.ade.detailpage.I_CmsDetailPageHandler#initialize(org.opencms.file.CmsObject, org.opencms.file.CmsObject) 356 */ 357 public void initialize(CmsObject offlineCms, CmsObject onlineCms) { 358 359 // do nothing 360 } 361 362 /** 363 * Checks whether the given detail page is valid for the given resource.<p> 364 * 365 * @param cms the CMS context 366 * @param page the detail page 367 * @param detailRes the detail resource 368 * 369 * @return true if the given detail page is valid 370 */ 371 public boolean isValidDetailPage(CmsObject cms, CmsResource page, CmsResource detailRes) { 372 373 String p = "[" + RandomStringUtils.randomAlphanumeric(6) + "] "; 374 LOG.debug(p + "isValidDetailPage(" + page.getRootPath() + "," + detailRes.getRootPath() + ")"); 375 if (OpenCms.getSystemInfo().isRestrictDetailContents()) { 376 // in 'restrict detail contents mode', do not allow detail contents from a real site on a detail page of a different real site 377 CmsSite pageSite = OpenCms.getSiteManager().getSiteForRootPath(page.getRootPath()); 378 CmsSite detailSite = OpenCms.getSiteManager().getSiteForRootPath(detailRes.getRootPath()); 379 if ((pageSite != null) 380 && (detailSite != null) 381 && !pageSite.getSiteRoot().equals(detailSite.getSiteRoot())) { 382 LOG.debug(p + "returned false because of restrict-detail-contents option"); 383 return false; 384 } 385 } 386 387 if (!OpenCms.getADEManager().isDetailPage(cms, page)) { 388 LOG.debug(p + "returned false because the page is not a detail page."); 389 return false; 390 } 391 392 String typeName = OpenCms.getResourceManager().getResourceType(detailRes).getTypeName(); 393 DetailPageConfigData context = lookupDetailPageConfigData( 394 OpenCms.getADEManager(), 395 cms, 396 detailRes.getRootPath(), 397 cms.getSitePath(page), 398 typeName); 399 String pageFolder = CmsFileUtil.removeTrailingSeparator(CmsResource.getParentFolder(page.getRootPath())); 400 CmsDetailPageFilter detailPageFilter = new CmsDetailPageFilter(cms, detailRes); 401 boolean foundDetailPage = detailPageFilter.filterDetailPages(context.getDetailPages()).anyMatch( 402 info -> pageFolder.equals(CmsFileUtil.removeTrailingSeparator(info.getUri()))); 403 CmsADEConfigData configForPage = context.getConfigForDetailPages(); 404 if (configForPage == null) { 405 LOG.debug(p + "Returned false because no valid sitemap configuration found"); 406 return false; 407 } 408 if (!foundDetailPage) { 409 LOG.debug(p + "Returned false because detail page is not in context " + configForPage.getBasePath()); 410 return false; 411 } 412 if (!CmsStringUtil.isPrefixPath( 413 configForPage.getExternalDetailContentExclusionFolder(), 414 detailRes.getRootPath())) { 415 LOG.debug( 416 p 417 + "returned false because of external detail content exclusion folder " 418 + configForPage.getExternalDetailContentExclusionFolder()); 419 return false; 420 } 421 return true; 422 } 423 424 /** 425 * Gets the context. 426 * 427 * @param manager the manager 428 * @param cms the cms 429 * @param contentPath the content path 430 * @param originPath the origin path 431 * @param resType the res type 432 * @return the context 433 */ 434 private DetailPageConfigData lookupDetailPageConfigData( 435 CmsADEManager manager, 436 CmsObject cms, 437 String contentPath, 438 String originPath, 439 String resType) { 440 441 DetailPageConfigData context = new DetailPageConfigData(); 442 443 CmsADEConfigData configData = manager.lookupConfigurationWithCache( 444 cms, 445 cms.getRequestContext().addSiteRoot(originPath)); 446 context.setSourceConfig(configData); 447 CmsADEConfigData targetConfigData = manager.lookupConfigurationWithCache(cms, contentPath); 448 context.setTargetConfig(targetConfigData); 449 boolean targetFirst = targetConfigData.isPreferDetailPagesForLocalContents(); 450 List<CmsADEConfigData> configs = targetFirst 451 ? Arrays.asList(targetConfigData, configData) 452 : Arrays.asList(configData, targetConfigData); 453 for (CmsADEConfigData config : configs) { 454 List<CmsDetailPageInfo> pageInfo = config.getDetailPagesForType(resType); 455 if ((pageInfo != null) && !pageInfo.isEmpty()) { 456 context.setConfigForDetailPages(config); 457 context.setDetailPages(pageInfo); 458 break; 459 } 460 } 461 return context; 462 } 463 464}