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