Chris@0: getScheme() != '') { Chris@0: return $rel->withPath(self::removeDotSegments($rel->getPath())); Chris@0: } Chris@0: Chris@0: if ($rel->getAuthority() != '') { Chris@0: $targetAuthority = $rel->getAuthority(); Chris@0: $targetPath = self::removeDotSegments($rel->getPath()); Chris@0: $targetQuery = $rel->getQuery(); Chris@0: } else { Chris@0: $targetAuthority = $base->getAuthority(); Chris@0: if ($rel->getPath() === '') { Chris@0: $targetPath = $base->getPath(); Chris@0: $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); Chris@0: } else { Chris@0: if ($rel->getPath()[0] === '/') { Chris@0: $targetPath = $rel->getPath(); Chris@0: } else { Chris@0: if ($targetAuthority != '' && $base->getPath() === '') { Chris@0: $targetPath = '/' . $rel->getPath(); Chris@0: } else { Chris@0: $lastSlashPos = strrpos($base->getPath(), '/'); Chris@0: if ($lastSlashPos === false) { Chris@0: $targetPath = $rel->getPath(); Chris@0: } else { Chris@0: $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath(); Chris@0: } Chris@0: } Chris@0: } Chris@0: $targetPath = self::removeDotSegments($targetPath); Chris@0: $targetQuery = $rel->getQuery(); Chris@0: } Chris@0: } Chris@0: Chris@0: return new Uri(Uri::composeComponents( Chris@0: $base->getScheme(), Chris@0: $targetAuthority, Chris@0: $targetPath, Chris@0: $targetQuery, Chris@0: $rel->getFragment() Chris@0: )); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the target URI as a relative reference from the base URI. Chris@0: * Chris@0: * This method is the counterpart to resolve(): Chris@0: * Chris@0: * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) Chris@0: * Chris@0: * One use-case is to use the current request URI as base URI and then generate relative links in your documents Chris@0: * to reduce the document size or offer self-contained downloadable document archives. Chris@0: * Chris@0: * $base = new Uri('http://example.com/a/b/'); Chris@0: * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. Chris@0: * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. Chris@0: * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. Chris@0: * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. Chris@0: * Chris@0: * This method also accepts a target that is already relative and will try to relativize it further. Only a Chris@0: * relative-path reference will be returned as-is. Chris@0: * Chris@0: * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well Chris@0: * Chris@0: * @param UriInterface $base Base URI Chris@0: * @param UriInterface $target Target URI Chris@0: * Chris@0: * @return UriInterface The relative URI reference Chris@0: */ Chris@0: public static function relativize(UriInterface $base, UriInterface $target) Chris@0: { Chris@0: if ($target->getScheme() !== '' && Chris@0: ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') Chris@0: ) { Chris@0: return $target; Chris@0: } Chris@0: Chris@0: if (Uri::isRelativePathReference($target)) { Chris@0: // As the target is already highly relative we return it as-is. It would be possible to resolve Chris@0: // the target with `$target = self::resolve($base, $target);` and then try make it more relative Chris@0: // by removing a duplicate query. But let's not do that automatically. Chris@0: return $target; Chris@0: } Chris@0: Chris@0: if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { Chris@0: return $target->withScheme(''); Chris@0: } Chris@0: Chris@0: // We must remove the path before removing the authority because if the path starts with two slashes, the URI Chris@0: // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also Chris@0: // invalid. Chris@0: $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); Chris@0: Chris@0: if ($base->getPath() !== $target->getPath()) { Chris@0: return $emptyPathUri->withPath(self::getRelativePath($base, $target)); Chris@0: } Chris@0: Chris@0: if ($base->getQuery() === $target->getQuery()) { Chris@0: // Only the target fragment is left. And it must be returned even if base and target fragment are the same. Chris@0: return $emptyPathUri->withQuery(''); Chris@0: } Chris@0: Chris@0: // If the base URI has a query but the target has none, we cannot return an empty path reference as it would Chris@0: // inherit the base query component when resolving. Chris@0: if ($target->getQuery() === '') { Chris@0: $segments = explode('/', $target->getPath()); Chris@0: $lastSegment = end($segments); Chris@0: Chris@0: return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); Chris@0: } Chris@0: Chris@0: return $emptyPathUri; Chris@0: } Chris@0: Chris@0: private static function getRelativePath(UriInterface $base, UriInterface $target) Chris@0: { Chris@0: $sourceSegments = explode('/', $base->getPath()); Chris@0: $targetSegments = explode('/', $target->getPath()); Chris@0: array_pop($sourceSegments); Chris@0: $targetLastSegment = array_pop($targetSegments); Chris@0: foreach ($sourceSegments as $i => $segment) { Chris@0: if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { Chris@0: unset($sourceSegments[$i], $targetSegments[$i]); Chris@0: } else { Chris@0: break; Chris@0: } Chris@0: } Chris@0: $targetSegments[] = $targetLastSegment; Chris@0: $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments); Chris@0: Chris@0: // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./". Chris@0: // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used Chris@0: // as the first segment of a relative-path reference, as it would be mistaken for a scheme name. Chris@0: if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { Chris@0: $relativePath = "./$relativePath"; Chris@0: } elseif ('/' === $relativePath[0]) { Chris@0: if ($base->getAuthority() != '' && $base->getPath() === '') { Chris@0: // In this case an extra slash is added by resolve() automatically. So we must not add one here. Chris@0: $relativePath = ".$relativePath"; Chris@0: } else { Chris@0: $relativePath = "./$relativePath"; Chris@0: } Chris@0: } Chris@0: Chris@0: return $relativePath; Chris@0: } Chris@0: Chris@0: private function __construct() Chris@0: { Chris@0: // cannot be instantiated Chris@0: } Chris@0: }