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