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