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 GmbH & Co. KG, 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.staticexport;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsProject;
032import org.opencms.file.CmsResource;
033import org.opencms.file.CmsResourceFilter;
034import org.opencms.main.CmsException;
035import org.opencms.main.CmsLog;
036import org.opencms.main.CmsPermalinkResourceHandler;
037import org.opencms.main.OpenCms;
038import org.opencms.relations.CmsExternalLinksValidationResult;
039import org.opencms.security.CmsRole;
040import org.opencms.security.CmsRoleViolationException;
041import org.opencms.site.CmsSite;
042import org.opencms.util.CmsFileUtil;
043import org.opencms.util.CmsStringUtil;
044import org.opencms.util.CmsUUID;
045
046import java.net.MalformedURLException;
047import java.net.URI;
048import java.net.URISyntaxException;
049import java.net.URL;
050
051import org.apache.commons.logging.Log;
052
053import com.google.common.base.Optional;
054
055/**
056 * Does the link replacement for the &lg;link&gt; tags.<p>
057 *
058 * Since this functionality is closely related to the static export,
059 * this class resides in the static export package.<p>
060 *
061 * @since 6.0.0
062 */
063public class CmsLinkManager {
064
065    /** The log object for this class. */
066    private static final Log LOG = CmsLog.getLog(CmsLinkManager.class);
067
068    /** Base URL to calculate absolute links. */
069    private static URL m_baseUrl;
070
071    /** The configured link substitution handler. */
072    private I_CmsLinkSubstitutionHandler m_linkSubstitutionHandler;
073
074    /** Stores the results of a external link validation. */
075    private CmsExternalLinksValidationResult m_pointerLinkValidationResult;
076
077    /**
078     * Public constructor.<p>
079     *
080     * @param linkSubstitutionHandler the link substitution handler to use
081     */
082    public CmsLinkManager(I_CmsLinkSubstitutionHandler linkSubstitutionHandler) {
083
084        m_linkSubstitutionHandler = linkSubstitutionHandler;
085        if (m_linkSubstitutionHandler == null) {
086            // just make very sure that this is not null
087            m_linkSubstitutionHandler = new CmsDefaultLinkSubstitutionHandler();
088        }
089    }
090
091    /**
092     * Static initializer for the base URL.<p>
093     */
094    static {
095        m_baseUrl = null;
096        try {
097            m_baseUrl = new URL("http://127.0.0.1");
098        } catch (MalformedURLException e) {
099            // this won't happen
100            LOG.error(e.getLocalizedMessage(), e);
101        }
102    }
103
104    /**
105     * Calculates the absolute URI for the "relativeUri" with the given absolute "baseUri" as start. <p>
106     *
107     * If "relativeUri" is already absolute, it is returned unchanged.
108     * This method also returns "relativeUri" unchanged if it is not well-formed.<p>
109     *
110     * @param relativeUri the relative URI to calculate an absolute URI for
111     * @param baseUri the base URI, this must be an absolute URI
112     *
113     * @return an absolute URI calculated from "relativeUri" and "baseUri"
114     */
115    public static String getAbsoluteUri(String relativeUri, String baseUri) {
116
117        if (isAbsoluteUri(relativeUri)) {
118            // URI is null or already absolute
119            return relativeUri;
120        }
121        try {
122            URL url = new URL(new URL(m_baseUrl, baseUri), relativeUri);
123            StringBuffer result = new StringBuffer(100);
124            result.append(url.getPath());
125            if (url.getQuery() != null) {
126                result.append('?');
127                result.append(url.getQuery());
128            }
129            if (url.getRef() != null) {
130                result.append('#');
131                result.append(url.getRef());
132            }
133            return result.toString();
134        } catch (MalformedURLException e) {
135            LOG.debug(e.getLocalizedMessage(), e);
136            return relativeUri;
137        }
138    }
139
140
141    /**
142     * Gets the absolute path for the subsite a link links to.
143     *
144     * <p>For detail links, the subsite of the detail page is returned, not the subsite of the detail content
145     * <p>If the link is not internal, null will be returned.
146     *
147     * @param cms a CMS context
148     * @param link the link to check
149     * @return the subsite path for the link target, or null if not applicable
150     */
151    public static  String getLinkSubsite(CmsObject cms, String link) {
152
153        try {
154
155            URI uri = new URI(link);
156            String path = uri.getPath();
157            String name = CmsResource.getName(path);
158            name = CmsFileUtil.removeTrailingSeparator(name);
159            String rootPath = OpenCms.getLinkManager().getRootPath(cms, link);
160            if (rootPath == null) {
161                return null;
162            }
163            String parentRootPath = null;
164            try {
165                CmsUUID detailId = cms.readIdForUrlName(name);
166                if (detailId != null) {
167                    CmsResource detailRes = cms.readResource(detailId, CmsResourceFilter.IGNORE_EXPIRATION);
168                    // When the last part of the path, interpreted as a detail name, resolves to the same root path returned by CmsLinkManager.getRootPath(), it is a detail page URL
169                    if (detailRes.getRootPath().equals(rootPath)) {
170                        URI parentUri = new URI(
171                            uri.getScheme(),
172                            uri.getAuthority(),
173                            CmsResource.getParentFolder(uri.getPath()),
174                            null,
175                            null);
176                        parentRootPath = OpenCms.getLinkManager().getRootPath(cms, parentUri.toASCIIString());
177                    }
178                }
179            } catch (CmsException e) {
180                LOG.info(e.getLocalizedMessage(), e);
181            }
182            if (parentRootPath != null) {
183                return OpenCms.getADEManager().getSubSiteRoot(cms, parentRootPath);
184            } else {
185                return OpenCms.getADEManager().getSubSiteRoot(cms, rootPath);
186            }
187        } catch (URISyntaxException  e) {
188            LOG.warn(e.getLocalizedMessage(), e);
189            return null;
190        }
191
192    }
193
194    /**
195     * Calculates a relative URI from "fromUri" to "toUri",
196     * both URI must be absolute.<p>
197     *
198     * @param fromUri the URI to start
199     * @param toUri the URI to calculate a relative path to
200     * @return a relative URI from "fromUri" to "toUri"
201     */
202    public static String getRelativeUri(String fromUri, String toUri) {
203
204        StringBuffer result = new StringBuffer();
205        int pos = 0;
206
207        while (true) {
208            int i = fromUri.indexOf('/', pos);
209            int j = toUri.indexOf('/', pos);
210            if ((i == -1) || (i != j) || !fromUri.regionMatches(pos, toUri, pos, i - pos)) {
211                break;
212            }
213            pos = i + 1;
214        }
215
216        // count hops up from here to the common ancestor
217        for (int i = fromUri.indexOf('/', pos); i > 0; i = fromUri.indexOf('/', i + 1)) {
218            result.append("../");
219        }
220
221        // append path down from common ancestor to there
222        result.append(toUri.substring(pos));
223
224        if (result.length() == 0) {
225            // special case: relative link to the parent folder from a file in that folder
226            result.append("./");
227        }
228
229        return result.toString();
230    }
231
232    /**
233     * Returns the resource root path for the given target URI in the OpenCms VFS, or <code>null</code> in
234     * case the target URI points to an external site.<p>
235     *
236     * @param cms the current users OpenCms context
237     * @param basePath path to use as base site for the target URI (can be <code>null</code>)
238     * @param targetUri the target URI
239     *
240     * @return the resource root path for the given target URI in the OpenCms VFS, or <code>null</code> in
241     *      case the target URI points to an external site
242     *
243     * @deprecated use {@link #getRootPath(CmsObject, String, String)} instead, obtain the link manager
244     *      with {@link OpenCms#getLinkManager()}
245     */
246    @Deprecated
247    public static String getSitePath(CmsObject cms, String basePath, String targetUri) {
248
249        return OpenCms.getLinkManager().getRootPath(cms, targetUri, basePath);
250    }
251
252    /**
253     * Tests if the given URI starts with a scheme component.<p>
254     *
255     * The scheme component is something like <code>http:</code> or <code>ftp:</code>.<p>
256     *
257     * @param uri the URI to test
258     *
259     * @return <code>true</code> if the given URI starts with a scheme component
260     */
261    public static boolean hasScheme(String uri) {
262
263        int pos = uri.indexOf(':');
264        // don't want to be misguided by a potential ':' in the query section of the URI (is this possible / allowed?)
265        // so consider only a ':' in the first 10 chars as a scheme
266        return (pos > -1) && (pos < 10);
267    }
268
269    /**
270     * Returns <code>true</code> in case the given URI is absolute.<p>
271     *
272     * An URI is considered absolute if one of the following is true:<ul>
273     * <li>The URI starts with a <code>'/'</code> char.
274     * <li>The URI contains a <code>':'</code> in the first 10 chars.
275     * <li>The URI is <code>null</code>
276     * </ul>
277     *
278     * @param uri the URI to test
279     *
280     * @return <code>true</code> in case the given URI is absolute
281     */
282    public static boolean isAbsoluteUri(String uri) {
283
284        return (uri == null) || ((uri.length() >= 1) && ((uri.charAt(0) == '/') || hasScheme(uri)));
285    }
286
287    /**
288     * Returns if the given link points to the OpenCms workplace UI.<p>
289     *
290     * @param link the link to test
291     *
292     * @return <code>true</code> in case the given URI points to the OpenCms workplace UI
293     */
294    public static boolean isWorkplaceLink(String link) {
295
296        boolean result = false;
297        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(link)) {
298            result = link.startsWith(OpenCms.getSystemInfo().getWorkplaceContext());
299            if (!result) {
300                try {
301                    URI uri = new URI(link);
302                    result = isWorkplaceUri(uri);
303                } catch (URISyntaxException e) {
304                    LOG.debug(e.getLocalizedMessage(), e);
305                }
306            }
307        }
308
309        return result;
310    }
311
312    /**
313     * Returns if the given URI is pointing to the OpenCms workplace UI.<p>
314     *
315     * @param uri the URI
316     *
317     * @return <code>true</code> if the given URI is pointing to the OpenCms workplace UI
318     */
319    public static boolean isWorkplaceUri(URI uri) {
320
321        return (uri != null) && uri.getPath().startsWith(OpenCms.getSystemInfo().getWorkplaceContext());
322    }
323
324
325    /**
326     * Given a path to a VFS resource, the method removes the OpenCms context,
327     * in case the path is prefixed by that context.
328     * @param path the path where the OpenCms context should be removed
329     * @return the adjusted path
330     */
331    public static String removeOpenCmsContext(final String path) {
332
333        String context = OpenCms.getSystemInfo().getOpenCmsContext();
334        if (path.startsWith(context + "/")) {
335            return path.substring(context.length());
336        }
337        String renderPrefix = OpenCms.getStaticExportManager().getVfsPrefix();
338        if (path.startsWith(renderPrefix + "/")) {
339            return path.substring(renderPrefix.length());
340        }
341        return path;
342    }
343
344    /**
345     * Returns the online link for the given resource, with full server prefix.<p>
346     *
347     * Like <code>http://site.enterprise.com:8080/index.html</code>.<p>
348     *
349     * In case the resource name is a full root path, the site from the root path will be used.
350     * Otherwise the resource is assumed to be in the current site set be the OpenCms user context.<p>
351     *
352     * Please note that this method will always return the link as it will appear in the "Online"
353     * project, that is after the resource has been published. In case you need a method that
354     * just returns the link with the full server prefix, use {@link #getServerLink(CmsObject, String)}.<p>
355     *
356     * @param cms the current OpenCms user context
357     * @param resourceName the resource to generate the online link for
358     *
359     * @return the online link for the given resource, with full server prefix
360     *
361     * @see #getServerLink(CmsObject, String)
362     */
363    public String getOnlineLink(CmsObject cms, String resourceName) {
364
365        return getOnlineLink(cms, resourceName, false);
366    }
367
368    /**
369     * Returns the online link for the given resource, with full server prefix.<p>
370     *
371     * Like <code>http://site.enterprise.com:8080/index.html</code>.<p>
372     *
373     * In case the resource name is a full root path, the site from the root path will be used.
374     * Otherwise the resource is assumed to be in the current site set be the OpenCms user context.<p>
375     *
376     * Please note that this method will always return the link as it will appear in the "Online"
377     * project, that is after the resource has been published. In case you need a method that
378     * just returns the link with the full server prefix, use {@link #getServerLink(CmsObject, String)}.<p>
379     *
380     * @param cms the current OpenCms user context
381     * @param resourceName the resource to generate the online link for
382     * @param forceSecure forces the secure server prefix if the target is secure
383     *
384     * @return the online link for the given resource, with full server prefix
385     *
386     * @see #getServerLink(CmsObject, String)
387     */
388    public String getOnlineLink(CmsObject cms, String resourceName, boolean forceSecure) {
389
390        String result = "";
391        try {
392            CmsProject currentProject = cms.getRequestContext().getCurrentProject();
393            try {
394                cms.getRequestContext().setCurrentProject(cms.readProject(CmsProject.ONLINE_PROJECT_ID));
395                result = substituteLinkForUnknownTarget(cms, resourceName, forceSecure);
396                result = appendServerPrefix(cms, result, resourceName, false);
397            } finally {
398                cms.getRequestContext().setCurrentProject(currentProject);
399            }
400        } catch (CmsException e) {
401            // should never happen
402            result = e.getLocalizedMessage();
403            if (LOG.isErrorEnabled()) {
404                LOG.error(e.getLocalizedMessage(), e);
405            }
406        }
407        return result;
408    }
409
410    /**
411     * Returns the online link for the given resource, with full server prefix.<p>
412     *
413     * Like <code>http://site.enterprise.com:8080/index.html</code>.<p>
414     *
415     * In case the resource name is a full root path, the site from the root path will be used.
416     * Otherwise the resource is assumed to be in the current site set be the OpenCms user context.<p>
417     *
418     * Please note that this method will always return the link as it will appear in the "Online"
419     * project, that is after the resource has been published. In case you need a method that
420     * just returns the link with the full server prefix, use {@link #getServerLink(CmsObject, String)}.<p>
421     *
422     * @param cms the current OpenCms user context
423     * @param resourceName the resource to generate the online link for
424     * @param targetDetailPage the target detail page, in case of linking to a specific detail page
425     * @param forceSecure forces the secure server prefix if the target is secure
426     *
427     * @return the online link for the given resource, with full server prefix
428     *
429     * @see #getServerLink(CmsObject, String)
430     */
431    public String getOnlineLink(CmsObject cms, String resourceName, String targetDetailPage, boolean forceSecure) {
432
433        String result = "";
434        try {
435            CmsProject currentProject = cms.getRequestContext().getCurrentProject();
436            try {
437                cms.getRequestContext().setCurrentProject(cms.readProject(CmsProject.ONLINE_PROJECT_ID));
438                result = substituteLinkForUnknownTarget(cms, resourceName, targetDetailPage, forceSecure);
439                result = appendServerPrefix(cms, result, resourceName, false);
440            } finally {
441                cms.getRequestContext().setCurrentProject(currentProject);
442            }
443        } catch (CmsException e) {
444            // should never happen
445            result = e.getLocalizedMessage();
446            if (LOG.isErrorEnabled()) {
447                LOG.error(e.getLocalizedMessage(), e);
448            }
449        }
450        return result;
451    }
452
453    /**
454     * Returns the perma link for the given resource.<p>
455     *
456     * Like
457     * <code>http://site.enterprise.com:8080/permalink/4b65369f-1266-11db-8360-bf0f6fbae1f8.html</code>.<p>
458     *
459     * @param cms the cms context
460     * @param resourceName the resource to generate the perma link for
461     *
462     * @return the perma link
463     */
464    public String getPermalink(CmsObject cms, String resourceName) {
465
466        return getPermalink(cms, resourceName, null);
467    }
468
469    /**
470     * Returns the perma link for the given resource and optional detail content.<p<
471     *
472     * @param cms the CMS context to use
473     * @param resourceName the page to generate the perma link for
474     * @param detailContentId the structure id of the detail content (may be null)
475     *
476     * @return the perma link
477     */
478    public String getPermalink(CmsObject cms, String resourceName, CmsUUID detailContentId) {
479
480        String permalink = "";
481        try {
482            permalink = substituteLink(cms, CmsPermalinkResourceHandler.PERMALINK_HANDLER);
483            String id = cms.readResource(resourceName, CmsResourceFilter.ALL).getStructureId().toString();
484            permalink += id;
485            if (detailContentId != null) {
486                permalink += ":" + detailContentId;
487            }
488            String ext = CmsFileUtil.getExtension(resourceName);
489            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(ext)) {
490                permalink += ext;
491            }
492            CmsSite currentSite = OpenCms.getSiteManager().getCurrentSite(cms);
493            String serverPrefix = null;
494            if (currentSite == OpenCms.getSiteManager().getDefaultSite()) {
495                Optional<CmsSite> siteForDefaultUri = OpenCms.getSiteManager().getSiteForDefaultUri();
496                if (siteForDefaultUri.isPresent()) {
497                    serverPrefix = siteForDefaultUri.get().getServerPrefix(cms, resourceName);
498                } else {
499                    serverPrefix = OpenCms.getSiteManager().getWorkplaceServer();
500                }
501            } else {
502                serverPrefix = currentSite.getServerPrefix(cms, resourceName);
503            }
504
505            if (!permalink.startsWith(serverPrefix)) {
506                permalink = serverPrefix + permalink;
507            }
508        } catch (CmsException e) {
509            // if something wrong
510            permalink = e.getLocalizedMessage();
511            if (LOG.isErrorEnabled()) {
512                LOG.error(e.getLocalizedMessage(), e);
513            }
514        }
515        return permalink;
516    }
517
518    /**
519     * Returns the perma link for the current page based on the URI and detail content id stored in the CmsObject passed as a parameter.<p<
520     *
521     * @param cms the CMS context to use to generate the permalink
522     *
523     * @return the permalink
524     */
525    public String getPermalinkForCurrentPage(CmsObject cms) {
526
527        return getPermalink(cms, cms.getRequestContext().getUri(), cms.getRequestContext().getDetailContentId());
528    }
529
530    /**
531     * Returns the result of the last extern link validation.<p>
532     *
533     * @return the result of the last extern link validation
534     */
535    public CmsExternalLinksValidationResult getPointerLinkValidationResult() {
536
537        return m_pointerLinkValidationResult;
538    }
539
540    /**
541     * Returns the resource root path in the OpenCms VFS for the given target URI link, or <code>null</code> in
542     * case the link points to an external site.<p>
543     *
544     * This methods does not support relative target URI links, so the given URI must be an absolute link.<p>
545     *
546     * See {@link #getRootPath(CmsObject, String)} for a full explanation of this method.<p>
547     *
548     * @param cms the current users OpenCms context
549     * @param targetUri the target URI link
550     *
551     * @return the resource root path in the OpenCms VFS for the given target URI link, or <code>null</code> in
552     *      case the link points to an external site
553     *
554     * @see #getRootPath(CmsObject, String, String)
555     *
556     * @since 7.0.2
557     */
558    public String getRootPath(CmsObject cms, String targetUri) {
559
560        return getRootPath(cms, targetUri, null);
561    }
562
563    /**
564     * Returns the resource root path in the OpenCms VFS for the given target URI link, or <code>null</code> in
565     * case the link points to an external site.<p>
566     *
567     * The default implementation applies the following transformations to the link:<ul>
568     * <li>In case the link starts with a VFS prefix (for example <code>/opencms/opencms</code>,
569     *      this prefix is removed from the result
570     * <li>In case the link is not a root path, the current site root is appended to the result.<p>
571     * <li>In case the link is relative, it will be made absolute using the given absolute <code>basePath</code>
572     *      as starting point.<p>
573     * <li>In case the link contains a server schema (for example <code>http://www.mysite.de/</code>),
574     *      which points to a configured site in OpenCms, the server schema is replaced with
575     *      the root path of the site.<p>
576     * <li>In case the link points to an external site, or in case it is not a valid URI,
577     *      then <code>null</code> is returned.<p>
578     * </ul>
579     *
580     * Please note the above text describes the default behavior as implemented by
581     * {@link CmsDefaultLinkSubstitutionHandler}, which can be fully customized using
582     * the {@link I_CmsLinkSubstitutionHandler} interface.<p>
583     *
584     * @param cms the current users OpenCms context
585     * @param targetUri the target URI link
586     * @param basePath path to use as base in case the target URI is relative (can be <code>null</code>)
587     *
588     * @return the resource root path in the OpenCms VFS for the given target URI link, or <code>null</code> in
589     *      case the link points to an external site
590     *
591     * @see I_CmsLinkSubstitutionHandler for the interface that can be used to fully customize the link substitution
592     * @see CmsDefaultLinkSubstitutionHandler for the default link substitution handler
593     *
594     * @since 7.0.2
595     */
596    public String getRootPath(CmsObject cms, String targetUri, String basePath) {
597
598        return m_linkSubstitutionHandler.getRootPath(cms, targetUri, basePath);
599    }
600
601    /**
602     * Returns the link for the given resource in the current project, with full server prefix.<p>
603     *
604     * Like <code>http://site.enterprise.com:8080/index.html</code>.<p>
605     *
606     * In case the resource name is a full root path, the site from the root path will be used.
607     * Otherwise the resource is assumed to be in the current site set be the OpenCms user context.<p>
608     *
609     * @param cms the current OpenCms user context
610     * @param resourceName the resource to generate the online link for
611     *
612     * @return the link for the given resource in the current project, with full server prefix
613     *
614     * @see #getOnlineLink(CmsObject, String)
615     */
616    public String getServerLink(CmsObject cms, String resourceName) {
617
618        return getServerLink(cms, resourceName, false);
619    }
620
621    /**
622     * Returns the link for the given resource in the current project, with full server prefix.<p>
623     *
624     * Like <code>http://site.enterprise.com:8080/index.html</code>.<p>
625     *
626     * In case the resource name is a full root path, the site from the root path will be used.
627     * Otherwise the resource is assumed to be in the current site set be the OpenCms user context.<p>
628     *
629     * @param cms the current OpenCms user context
630     * @param resourceName the resource to generate the online link for
631     * @param forceSecure forces the secure server prefix
632     *
633     * @return the link for the given resource in the current project, with full server prefix
634     *
635     * @see #getOnlineLink(CmsObject, String)
636     */
637    public String getServerLink(CmsObject cms, String resourceName, boolean forceSecure) {
638
639        String result = substituteLinkForUnknownTarget(cms, resourceName, forceSecure);
640        return appendServerPrefix(cms, result, resourceName, false);
641    }
642
643    /**
644     * Returns the link for the given workplace resource.
645     *
646     * This should only be used for resources under /system or /shared.<p<
647     *
648     * @param cms the current OpenCms user context
649     * @param resourceName the resource to generate the online link for
650     * @param forceSecure forces the secure server prefix
651     *
652     * @return the link for the given resource
653     */
654    public String getWorkplaceLink(CmsObject cms, String resourceName, boolean forceSecure) {
655
656        String result = substituteLinkForUnknownTarget(cms, resourceName, forceSecure);
657        return appendServerPrefix(cms, result, resourceName, true);
658
659    }
660
661    /**
662     * Sets the internal link substitution handler.<p>
663     *
664     * @param cms an OpenCms user context that must have the permissions for role {@link CmsRole#ROOT_ADMIN}.<p>
665     * @param linkSubstitutionHandler the handler to set
666     *
667     * @throws CmsRoleViolationException in case the provided OpenCms user context does not have the required permissions
668     */
669    public void setLinkSubstitutionHandler(CmsObject cms, I_CmsLinkSubstitutionHandler linkSubstitutionHandler)
670    throws CmsRoleViolationException {
671
672        OpenCms.getRoleManager().checkRole(cms, CmsRole.ROOT_ADMIN);
673        m_linkSubstitutionHandler = linkSubstitutionHandler;
674    }
675
676    /**
677     * Sets the result of an external link validation.<p>
678     *
679     * @param externLinkValidationResult the result an external link validation
680     */
681    public void setPointerLinkValidationResult(CmsExternalLinksValidationResult externLinkValidationResult) {
682
683        m_pointerLinkValidationResult = externLinkValidationResult;
684    }
685
686    /**
687     * Returns a link <i>from</i> the URI stored in the provided OpenCms user context
688     * <i>to</i> the given VFS resource, for use on web pages.<p>
689     *
690     * The result will contain the configured context path and
691     * servlet name, and in the case of the "online" project it will also be rewritten according to
692     * to the configured static export settings.<p>
693     *
694     * Should the current site of the given OpenCms user context <code>cms</code> be different from the
695     * site root of the given resource, the result will contain the full server URL to the target resource.<p>
696     *
697     * Please note the above text describes the default behavior as implemented by
698     * {@link CmsDefaultLinkSubstitutionHandler}, which can be fully customized using the
699     * {@link I_CmsLinkSubstitutionHandler} interface.<p>
700     *
701     * @param cms the current OpenCms user context
702     * @param resource the VFS resource the link should point to
703     *
704     * @return a link <i>from</i> the URI stored in the provided OpenCms user context
705     *      <i>to</i> the given VFS resource, for use on web pages
706     */
707    public String substituteLink(CmsObject cms, CmsResource resource) {
708
709        return substituteLinkForRootPath(cms, resource.getRootPath());
710    }
711
712    /**
713     * Returns a link <i>from</i> the URI stored in the provided OpenCms user context
714     * <i>to</i> the VFS resource indicated by the given <code>link</code> in the current site,
715     * for use on web pages.<p>
716     *
717     * The provided <code>link</code> is assumed to be the contained in the site currently
718     * set in the provided OpenCms user context <code>cms</code>.<p>
719     *
720     * The result will be an absolute link that contains the configured context path and
721     * servlet name, and in the case of the "online" project it will also be rewritten according to
722     * to the configured static export settings.<p>
723     *
724     * In case <code>link</code> is a relative URI, the current URI contained in the provided
725     * OpenCms user context <code>cms</code> is used to make the relative <code>link</code> absolute.<p>
726     *
727     * Please note the above text describes the default behavior as implemented by
728     * {@link CmsDefaultLinkSubstitutionHandler}, which can be fully customized using the
729     * {@link I_CmsLinkSubstitutionHandler} interface.<p>
730     *
731     * @param cms the current OpenCms user context
732     * @param link the link to process which is assumed to point to a VFS resource, with optional parameters
733
734     * @return a link <i>from</i> the URI stored in the provided OpenCms user context
735     *      <i>to</i> the VFS resource indicated by the given <code>link</code> in the current site
736     */
737    public String substituteLink(CmsObject cms, String link) {
738
739        return substituteLink(cms, link, null, false);
740    }
741
742    /**
743     * Returns a link <i>from</i> the URI stored in the provided OpenCms user context
744     * <i>to</i> the VFS resource indicated by the given <code>link</code> and <code>siteRoot</code>,
745     * for use on web pages.<p>
746     *
747     * The result will be an absolute link that contains the configured context path and
748     * servlet name, and in the case of the "online" project it will also be rewritten according to
749     * to the configured static export settings.<p>
750     *
751     * In case <code>link</code> is a relative URI, the current URI contained in the provided
752     * OpenCms user context <code>cms</code> is used to make the relative <code>link</code> absolute.<p>
753     *
754     * The provided <code>siteRoot</code> is assumed to be the "home" of the link.
755     * In case the current site of the given OpenCms user context <code>cms</code> is different from the
756     * provided <code>siteRoot</code>, the full server prefix is appended to the result link.<p>
757     *
758     * Please note the above text describes the default behavior as implemented by
759     * {@link CmsDefaultLinkSubstitutionHandler}, which can be fully customized using the
760     * {@link I_CmsLinkSubstitutionHandler} interface.<p>
761     *
762     * @param cms the current OpenCms user context
763     * @param link the link to process which is assumed to point to a VFS resource, with optional parameters
764     * @param siteRoot the site root of the <code>link</code>
765     *
766     * @return the substituted link
767     */
768    public String substituteLink(CmsObject cms, String link, String siteRoot) {
769
770        return substituteLink(cms, link, siteRoot, false);
771    }
772
773    /**
774     * Returns a link <i>from</i> the URI stored in the provided OpenCms user context
775     * <i>to</i> the VFS resource indicated by the given <code>link</code> and <code>siteRoot</code>,
776     * for use on web pages, using the configured link substitution handler.<p>
777     *
778     * The result will be an absolute link that contains the configured context path and
779     * servlet name, and in the case of the "online" project it will also be rewritten according to
780     * to the configured static export settings.<p>
781     *
782     * In case <code>link</code> is a relative URI, the current URI contained in the provided
783     * OpenCms user context <code>cms</code> is used to make the relative <code>link</code> absolute.<p>
784     *
785     * The provided <code>siteRoot</code> is assumed to be the "home" of the link.
786     * In case the current site of the given OpenCms user context <code>cms</code> is different from the
787     * provided <code>siteRoot</code>, the full server prefix is appended to the result link.<p>
788     *
789     * A server prefix is also added if
790     * <ul>
791     *   <li>the link is contained in a normal document and the link references a secure document</li>
792     *   <li>the link is contained in a secure document and the link references a normal document</li>
793     * </ul>
794     *
795     * Please note the above text describes the default behavior as implemented by
796     * {@link CmsDefaultLinkSubstitutionHandler}, which can be fully customized using the
797     * {@link I_CmsLinkSubstitutionHandler} interface.<p>
798     *
799     * @param cms the current OpenCms user context
800     * @param link the link to process which is assumed to point to a VFS resource, with optional parameters
801     * @param siteRoot the site root of the <code>link</code>
802     * @param forceSecure if <code>true</code> generates always an absolute URL (with protocol and server name) for secure links
803     *
804     * @return a link <i>from</i> the URI stored in the provided OpenCms user context
805     *      <i>to</i> the VFS resource indicated by the given <code>link</code> and <code>siteRoot</code>
806     *
807     * @see I_CmsLinkSubstitutionHandler for the interface that can be used to fully customize the link substitution
808     * @see CmsDefaultLinkSubstitutionHandler for the default link substitution handler
809     */
810    public String substituteLink(CmsObject cms, String link, String siteRoot, boolean forceSecure) {
811
812        return substituteLink(cms, link, siteRoot, null, forceSecure);
813    }
814
815    /**
816     * Returns a link <i>from</i> the URI stored in the provided OpenCms user context
817     * <i>to</i> the VFS resource indicated by the given <code>link</code> and <code>siteRoot</code>,
818     * for use on web pages, using the configured link substitution handler.<p>
819     *
820     * The result will be an absolute link that contains the configured context path and
821     * servlet name, and in the case of the "online" project it will also be rewritten according to
822     * to the configured static export settings.<p>
823     *
824     * In case <code>link</code> is a relative URI, the current URI contained in the provided
825     * OpenCms user context <code>cms</code> is used to make the relative <code>link</code> absolute.<p>
826     *
827     * The provided <code>siteRoot</code> is assumed to be the "home" of the link.
828     * In case the current site of the given OpenCms user context <code>cms</code> is different from the
829     * provided <code>siteRoot</code>, the full server prefix is appended to the result link.<p>
830     *
831     * A server prefix is also added if
832     * <ul>
833     *   <li>the link is contained in a normal document and the link references a secure document</li>
834     *   <li>the link is contained in a secure document and the link references a normal document</li>
835     * </ul>
836     *
837     * Please note the above text describes the default behavior as implemented by
838     * {@link CmsDefaultLinkSubstitutionHandler}, which can be fully customized using the
839     * {@link I_CmsLinkSubstitutionHandler} interface.<p>
840     *
841     * @param cms the current OpenCms user context
842     * @param link the link to process which is assumed to point to a VFS resource, with optional parameters
843     * @param siteRoot the site root of the <code>link</code>
844     * @param targetDetailPage the target detail page, in case of linking to a specific detail page
845     * @param forceSecure if <code>true</code> generates always an absolute URL (with protocol and server name) for secure links
846     *
847     * @return a link <i>from</i> the URI stored in the provided OpenCms user context
848     *      <i>to</i> the VFS resource indicated by the given <code>link</code> and <code>siteRoot</code>
849     *
850     * @see I_CmsLinkSubstitutionHandler for the interface that can be used to fully customize the link substitution
851     * @see CmsDefaultLinkSubstitutionHandler for the default link substitution handler
852     */
853    public String substituteLink(
854        CmsObject cms,
855        String link,
856        String siteRoot,
857        String targetDetailPage,
858        boolean forceSecure) {
859
860        if (targetDetailPage != null) {
861            return m_linkSubstitutionHandler.getLink(cms, link, siteRoot, targetDetailPage, forceSecure);
862        } else {
863            return m_linkSubstitutionHandler.getLink(cms, link, siteRoot, forceSecure);
864        }
865
866    }
867
868    /**
869     * Returns a link <i>from</i> the URI stored in the provided OpenCms user context
870     * <i>to</i> the VFS resource indicated by the given root path, for use on web pages.<p>
871     *
872     * The result will contain the configured context path and
873     * servlet name, and in the case of the "online" project it will also be rewritten according to
874     * to the configured static export settings.<p>
875     *
876     * Should the current site of the given OpenCms user context <code>cms</code> be different from the
877     * site root of the given resource root path, the result will contain the full server URL to the target resource.<p>
878     *
879     * @param cms the current OpenCms user context
880     * @param rootPath the VFS resource root path the link should point to
881     *
882     * @return a link <i>from</i> the URI stored in the provided OpenCms user context
883     *      <i>to</i> the VFS resource indicated by the given root path
884     */
885    public String substituteLinkForRootPath(CmsObject cms, String rootPath) {
886
887        String siteRoot = OpenCms.getSiteManager().getSiteRoot(rootPath);
888        if (siteRoot == null) {
889            // use current site root in case no valid site root is available
890            // this will also be the case if a "/system" link is used
891            siteRoot = cms.getRequestContext().getSiteRoot();
892        }
893        String sitePath;
894        if (rootPath.startsWith(siteRoot)) {
895            // only cut the site root if the root part really has this prefix
896            sitePath = rootPath.substring(siteRoot.length());
897        } else {
898            sitePath = rootPath;
899        }
900        return substituteLink(cms, sitePath, siteRoot, false);
901    }
902
903    /**
904     * Returns a link <i>from</i> the URI stored in the provided OpenCms user context
905     * <i>to</i> the given <code>link</code>, for use on web pages.<p>
906     *
907     * A number of tests are performed with the <code>link</code> in order to find out how to create the link:<ul>
908     * <li>If <code>link</code> is empty, an empty String is returned.
909     * <li>If <code>link</code> starts with an URI scheme component, for example <code>http://</code>,
910     * and does not point to an internal OpenCms site, it is returned unchanged.
911     * <li>If <code>link</code> is an absolute URI that starts with a configured site root,
912     * the site root is cut from the link and
913     * the same result as {@link #substituteLink(CmsObject, String, String)} is returned.
914     * <li>Otherwise the same result as {@link #substituteLink(CmsObject, String)} is returned.
915     * </ul>
916     *
917     * @param cms the current OpenCms user context
918     * @param link the link to process
919     *
920     * @return a link <i>from</i> the URI stored in the provided OpenCms user context
921     *      <i>to</i> the given <code>link</code>
922     */
923    public String substituteLinkForUnknownTarget(CmsObject cms, String link) {
924
925        return substituteLinkForUnknownTarget(cms, link, false);
926
927    }
928
929    /**
930     * Returns a link <i>from</i> the URI stored in the provided OpenCms user context
931     * <i>to</i> the given <code>link</code>, for use on web pages.<p>
932     *
933     * A number of tests are performed with the <code>link</code> in order to find out how to create the link:<ul>
934     * <li>If <code>link</code> is empty, an empty String is returned.
935     * <li>If <code>link</code> starts with an URI scheme component, for example <code>http://</code>,
936     * and does not point to an internal OpenCms site, it is returned unchanged.
937     * <li>If <code>link</code> is an absolute URI that starts with a configured site root,
938     * the site root is cut from the link and
939     * the same result as {@link #substituteLink(CmsObject, String, String)} is returned.
940     * <li>Otherwise the same result as {@link #substituteLink(CmsObject, String)} is returned.
941     * </ul>
942     *
943     * @param cms the current OpenCms user context
944     * @param link the link to process
945     * @param forceSecure forces the secure server prefix if the link target is secure
946     *
947     * @return a link <i>from</i> the URI stored in the provided OpenCms user context
948     *      <i>to</i> the given <code>link</code>
949     */
950    public String substituteLinkForUnknownTarget(CmsObject cms, String link, boolean forceSecure) {
951
952        return substituteLinkForUnknownTarget(cms, link, null, forceSecure);
953    }
954
955    /**
956     * Returns a link <i>from</i> the URI stored in the provided OpenCms user context
957     * <i>to</i> the given <code>link</code>, for use on web pages.<p>
958     *
959     * A number of tests are performed with the <code>link</code> in order to find out how to create the link:<ul>
960     * <li>If <code>link</code> is empty, an empty String is returned.
961     * <li>If <code>link</code> starts with an URI scheme component, for example <code>http://</code>,
962     * and does not point to an internal OpenCms site, it is returned unchanged.
963     * <li>If <code>link</code> is an absolute URI that starts with a configured site root,
964     * the site root is cut from the link and
965     * the same result as {@link #substituteLink(CmsObject, String, String)} is returned.
966     * <li>Otherwise the same result as {@link #substituteLink(CmsObject, String)} is returned.
967     * </ul>
968     *
969     * @param cms the current OpenCms user context
970     * @param link the link to process
971     * @param targetDetailPage the target detail page, in case of linking to a specific detail page
972     * @param forceSecure forces the secure server prefix if the link target is secure
973     *
974     * @return a link <i>from</i> the URI stored in the provided OpenCms user context
975     *      <i>to</i> the given <code>link</code>
976     */
977    public String substituteLinkForUnknownTarget(
978        CmsObject cms,
979        String link,
980        String targetDetailPage,
981        boolean forceSecure) {
982
983        if (CmsStringUtil.isEmpty(link)) {
984            return "";
985        }
986        String sitePath = link;
987        String siteRoot = null;
988        if (hasScheme(link)) {
989            // the link has a scheme, that is starts with something like "http://"
990            // usually this should be a link to an external resource, but check anyway
991            sitePath = getRootPath(cms, link);
992            if (sitePath == null) {
993                // probably an external link, don't touch this
994                return link;
995            }
996        }
997        // check if we can find a site from the link
998        siteRoot = OpenCms.getSiteManager().getSiteRoot(sitePath);
999        if (siteRoot == null) {
1000            // use current site root in case no valid site root is available
1001            // this will also be the case if a "/system" link is used
1002            siteRoot = cms.getRequestContext().getSiteRoot();
1003        } else {
1004            // we found a site root, cut this from the resource path
1005            sitePath = sitePath.substring(siteRoot.length());
1006        }
1007        return substituteLink(cms, sitePath, siteRoot, targetDetailPage, forceSecure);
1008    }
1009
1010    /**
1011     * Returns the link for the given resource in the current project, with full server prefix.<p>
1012     *
1013     * The input link must already have been processed according to the link substitution rules.
1014     * This method does just append the server prefix in case this is requires.<p>
1015     *
1016     * @param cms the current OpenCms user context
1017     * @param link the resource to generate the online link for
1018     * @param pathWithOptionalParameters the resource name
1019     * @param workplaceLink if this is set, use the workplace server prefix even if we are in the Online project
1020     *
1021     * @return the link for the given resource in the current project, with full server prefix
1022     */
1023    private String appendServerPrefix(
1024        CmsObject cms,
1025        String link,
1026        String pathWithOptionalParameters,
1027        boolean workplaceLink) {
1028
1029        int paramPos = pathWithOptionalParameters.indexOf("?");
1030        String resourceName = paramPos > -1
1031        ? pathWithOptionalParameters.substring(0, paramPos)
1032        : pathWithOptionalParameters;
1033
1034        if (isAbsoluteUri(link) && !hasScheme(link)) {
1035            // URI is absolute and contains no schema
1036            // this indicates source and target link are in the same site
1037            String serverPrefix;
1038            if (cms.getRequestContext().getCurrentProject().isOnlineProject() && !workplaceLink) {
1039                String overrideSiteRoot = (String)(cms.getRequestContext().getAttribute(
1040                    CmsDefaultLinkSubstitutionHandler.OVERRIDE_SITEROOT_PREFIX + link));
1041                // on online project, get the real site name from the site manager
1042                CmsSite currentSite = OpenCms.getSiteManager().getSite(
1043                    overrideSiteRoot != null ? overrideSiteRoot : resourceName,
1044                    cms.getRequestContext().getSiteRoot());
1045                serverPrefix = currentSite.getServerPrefix(cms, resourceName);
1046            } else {
1047                // in offline mode, source must be the workplace
1048                // so append the workplace server so links can still be clicked
1049                serverPrefix = OpenCms.getSiteManager().getWorkplaceServer(cms);
1050            }
1051            link = serverPrefix + link;
1052        }
1053        return link;
1054    }
1055}