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