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