/** * Convert the link to a relative link by substracting a base URI * * This is the opposite of resolving a relative link - i.e. creating a * relative reference link from an original URI and a base URI. * * If the two URIs do not intersect (e.g. the original URI is not in any * way related to the base URI) the URI will not be modified. * * @param Uri|string $baseUri * @return Uri */ public function makeRelative($baseUri) { // Copy base URI, we should not modify it $baseUri = new static($baseUri); $this->normalize(); $baseUri->normalize(); $host = $this->getHost(); $baseHost = $baseUri->getHost(); if ($host && $baseHost && $host != $baseHost) { // Not the same hostname return $this; } $port = $this->getPort(); $basePort = $baseUri->getPort(); if ($port && $basePort && $port != $basePort) { // Not the same port return $this; } $scheme = $this->getScheme(); $baseScheme = $baseUri->getScheme(); if ($scheme && $baseScheme && $scheme != $baseScheme) { // Not the same scheme (e.g. HTTP vs. HTTPS) return $this; } // Remove host, port and scheme $this->setHost(null)->setPort(null)->setScheme(null); // Is path the same? if ($this->getPath() == $baseUri->getPath()) { $this->setPath(''); return $this; } $pathParts = preg_split('|(/)|', $this->getPath(), null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $baseParts = preg_split('|(/)|', $baseUri->getPath(), null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); // Get the intersection of existing path parts and those from the // provided URI $matchingParts = array_intersect_assoc($pathParts, $baseParts); // Loop through the matches foreach ($matchingParts as $index => $segment) { // If we skip an index at any point, we have parent traversal, and // need to prepend the path accordingly if ($index && !isset($matchingParts[$index - 1])) { array_unshift($pathParts, '../'); continue; } // Otherwise, we simply unset the given path segment unset($pathParts[$index]); } // Reset the path by imploding path segments $this->setPath(implode($pathParts)); return $this; }
/** * Transform References * * This function implements the "transform references" algorithm from * the RFC3986 specification for URIs. * * @see http://tools.ietf.org/html/rfc3986#page-31 * * @param string $relative * @param bool $strict * * @return static */ public function transformReference($relative, $strict = false) { $base = $this; $relative = new static((string) $relative); $transformed = new static(); if (!$strict && $relative->getScheme() == $this->getScheme()) { $relative->setScheme(null); } if ($relative->getScheme() !== null) { $transformed->setScheme($relative->getScheme()); $transformed->setAuthority($relative->getAuthority()); $transformed->setPath(static::remove_dot_segments($relative->getPath())); $transformed->setQuery($relative->getQuery()); } else { if ($relative->getAuthority() !== null) { $transformed->setAuthority($relative->getAuthority()); $transformed->setPath(static::remove_dot_segments($relative->getPath())); $transformed->setQuery($relative->getQuery()); } else { if ($relative->getPath() == '') { $transformed->setPath($base->getPath()); if ($relative->getQuery() !== null) { $transformed->setQuery($relative->getQuery()); } else { $transformed->setQuery($base->getQuery()); } } else { if ('/' == substr($relative->getPath(), 0, 1)) { $transformed->setPath(static::remove_dot_segments($relative->getPath())); } else { $transformed->setPath(static::merge($base->getPath(), $relative->getPath())); $transformed->setPath(static::remove_dot_segments($transformed->getPath())); } $transformed->setQuery($relative->getQuery()); } $transformed->setAuthority($base->getAuthority()); } $transformed->setScheme($base->getScheme()); } $transformed->setFragment($relative->getFragment()); return $transformed; }