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