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.CmsFunctionReference; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsResource; 034import org.opencms.file.CmsResourceFilter; 035import org.opencms.file.CmsVfsResourceNotFoundException; 036import org.opencms.main.CmsException; 037import org.opencms.main.CmsLog; 038import org.opencms.main.OpenCms; 039import org.opencms.security.CmsPermissionViolationException; 040import org.opencms.security.CmsSecurityException; 041import org.opencms.util.CmsFileUtil; 042import org.opencms.util.CmsStringUtil; 043import org.opencms.util.CmsUUID; 044 045import java.util.ArrayList; 046import java.util.Collection; 047import java.util.Collections; 048import java.util.List; 049import java.util.Locale; 050import java.util.function.BiPredicate; 051 052import org.apache.commons.logging.Log; 053 054/** 055 * This is a utility class which provides convenience methods for finding detail page names for resources which include 056 * the URL names of the resources themselves.<p> 057 * 058 * @see I_CmsDetailPageHandler 059 * 060 * @since 8.0.0 061 */ 062public final class CmsDetailPageUtil { 063 064 /** The log object for this class. */ 065 private static final Log LOG = CmsLog.getLog(CmsDetailPageUtil.class); 066 067 /** 068 * The hidden default constructor.<p> 069 */ 070 private CmsDetailPageUtil() { 071 072 // do nothing 073 } 074 075 /** 076 * Gets a list of detail page URIs for the given resource, with its URL name appended.<p> 077 * 078 * @param cms the current CMS context 079 * @param res the resource for which the detail pages should be retrieved 080 * 081 * @return the list of detail page URIs 082 * 083 * @throws CmsException if something goes wrong 084 */ 085 public static List<String> getAllDetailPagesWithUrlName(CmsObject cms, CmsResource res) throws CmsException { 086 087 List<String> result = new ArrayList<String>(); 088 Collection<String> detailPages = OpenCms.getADEManager().getDetailPageHandler().getAllDetailPages( 089 cms, 090 res.getTypeId()); 091 if (detailPages.isEmpty()) { 092 return Collections.<String> emptyList(); 093 } 094 List<String> detailNames = cms.readUrlNamesForAllLocales(res.getStructureId()); 095 for (String urlName : detailNames) { 096 for (String detailPage : detailPages) { 097 String rootPath = CmsStringUtil.joinPaths(detailPage, urlName, "/"); 098 result.add(rootPath); 099 } 100 } 101 return result; 102 } 103 104 /** 105 * Returns either the newest URL name for a structure id, or the structure id as a string if there is no URL name.<p> 106 * 107 * @param cms the current CMS context 108 * @param id the structure id of a resource 109 * 110 * @return the best URL name for the structure id 111 * 112 * @throws CmsException if something goes wrong 113 */ 114 public static String getBestUrlName(CmsObject cms, CmsUUID id) throws CmsException { 115 116 // this is currently only used for static export 117 Locale locale = cms.getRequestContext().getLocale(); 118 List<Locale> defaultLocales = OpenCms.getLocaleManager().getDefaultLocales(); 119 String urlName = cms.readBestUrlName(id, locale, defaultLocales); 120 if (urlName != null) { 121 return urlName; 122 } 123 return id.toString(); 124 } 125 126 /** 127 * Looks up a page by URI (which may be a detail page URI, or a normal VFS uri).<p> 128 * 129 * @param cms the current CMS context 130 * @param uri the detail page or VFS uri 131 * 132 * @return the resource with the given uri 133 * 134 * @throws CmsException if something goes wrong 135 */ 136 public static CmsResource lookupPage(CmsObject cms, String uri) throws CmsException { 137 138 try { 139 CmsResource res = cms.readResource(uri); 140 return res; 141 } catch (CmsVfsResourceNotFoundException e) { 142 String detailName = CmsResource.getName(uri).replaceAll("/$", ""); 143 CmsUUID detailId = cms.readIdForUrlName(detailName); 144 if (detailId != null) { 145 return cms.readResource(detailId); 146 } 147 throw new CmsVfsResourceNotFoundException( 148 org.opencms.db.generic.Messages.get().container( 149 org.opencms.db.generic.Messages.ERR_READ_RESOURCE_1, 150 uri)); 151 } 152 } 153 154 /** 155 * Resolves a URI to the detail page that renders it and to the detail content it shows.<p> 156 * 157 * This is the resolution {@link CmsDetailPageResourceHandler} performs for a browser request, but 158 * without the servlet request and without any side effect: neither the request context nor any 159 * request attribute is changed, so the same lookup can be used outside the request cycle.<p> 160 * 161 * @param cms the CMS context, initialized with the site root the URI belongs to 162 * @param uri the site relative URI to resolve 163 * 164 * @return the detail resolution, or <code>null</code> if the URI is not a detail page URI 165 * 166 * @throws CmsException if something goes wrong 167 */ 168 public static CmsDetailResolution resolveDetail(CmsObject cms, String uri) throws CmsException { 169 170 return resolveDetail( 171 cms, 172 uri, 173 ( 174 page, 175 detailRes) -> OpenCms.getADEManager().getDetailPageHandler().isValidDetailPage(cms, page, detailRes)); 176 } 177 178 /** 179 * Resolves a URI to the detail page that renders it and to the detail content it shows, using the 180 * given check for whether a detail page is valid for a detail content.<p> 181 * 182 * The check is a parameter because {@link CmsDetailPageResourceHandler#isValidDetailPage(CmsObject, CmsResource, CmsResource)} 183 * is an extension point of that handler; use {@link #resolveDetail(CmsObject, String)} to resolve with 184 * the standard check.<p> 185 * 186 * @param cms the CMS context, initialized with the site root the URI belongs to 187 * @param uri the site relative URI to resolve 188 * @param detailPageValidator checks whether a detail page (first argument) is valid for a detail content (second argument) 189 * 190 * @return the detail resolution, or <code>null</code> if the URI is not a detail page URI 191 * 192 * @throws CmsException if something goes wrong 193 */ 194 public static CmsDetailResolution resolveDetail( 195 CmsObject cms, 196 String uri, 197 BiPredicate<CmsResource, CmsResource> detailPageValidator) 198 throws CmsException { 199 200 String path = CmsFileUtil.removeTrailingSeparator(uri); 201 try { 202 cms.readResource(path, CmsResourceFilter.IGNORE_EXPIRATION); 203 } catch (CmsSecurityException e) { 204 // It may happen that a path is both an existing VFS path and a valid detail page link. 205 // If this is the case, and the user has insufficient permissions to read the resource at the path, 206 // no resource should be displayed, even if the user would have access to the detail page. 207 return null; 208 } catch (CmsException e) { 209 // ignore 210 } 211 String detailName = CmsResource.getName(path); 212 try { 213 CmsUUID detailId = cms.readIdForUrlName(detailName); 214 215 if (detailId != null) { 216 // check existence / permissions 217 CmsResource detailRes = null; 218 CmsPermissionViolationException permissionDenied = null; 219 try { 220 detailRes = cms.readResource(detailId, CmsResourceFilter.ignoreExpirationOffline(cms)); 221 } catch (CmsPermissionViolationException e) { 222 // we postpone the decision what to do with a permission violation until later (see below) 223 permissionDenied = e; 224 } 225 String detailPagePath = CmsResource.getFolderPath(path); 226 CmsResource detailPage = cms.readDefaultFile(detailPagePath); 227 if (permissionDenied != null) { 228 // If we got a permission violation while reading the detail content, we only want to rethrow it if the rest 229 // of the URL is actually plausibly a detail page. Otherwise, we return null, which will usually cause a HTTP 230 // 404 response status. This is to prevent broken links which accidentally end with a restricted detail content's 231 // mapped URL name from triggering a HTTP 401 status. E.g. https://server.com/nonexistent-page/secret, where 232 // there is no "nonexistent-page" folder and "secret" is the mapped URL name of a restricted content. 233 if ((detailPage != null) && OpenCms.getADEManager().isDetailPage(cms, detailPage)) { 234 throw permissionDenied; 235 } else { 236 LOG.debug( 237 "Swallowing CmsPermissionViolationException for detail content because the page [" 238 + detailPagePath 239 + "] is not a detail page.\nDefault file: " 240 + detailPage 241 + "\n", 242 permissionDenied); 243 return null; 244 } 245 } 246 // the page may be null when the folder of the URI has no default file; the detail page 247 // check dereferences the page, so this is checked here and reported as "no detail page" 248 if ((detailPage == null) || !detailPageValidator.test(detailPage, detailRes)) { 249 return null; 250 } 251 return CmsDetailResolution.forContent(detailPage, detailRes, detailName); 252 } 253 CmsADEConfigData configData = OpenCms.getADEManager().lookupConfiguration( 254 cms, 255 cms.getRequestContext().addSiteRoot(path)); 256 // check if the detail name matches any named function 257 for (CmsFunctionReference ref : configData.getFunctionReferences()) { 258 if (detailName.equals(ref.getName()) && (ref.getFunctionDefaultPageId() != null)) { 259 CmsResource detailPage = cms.readDefaultFile(CmsResource.getFolderPath(path)); 260 if ((detailPage != null) && OpenCms.getADEManager().isDetailPage(cms, detailPage)) { 261 return CmsDetailResolution.forFunction( 262 detailPage, 263 cms.readResource(ref.getFunctionDefaultPageId()), 264 detailName); 265 } 266 return null; 267 } 268 } 269 } catch (CmsVfsResourceNotFoundException e) { 270 return null; 271 } 272 return null; 273 } 274}