>bRZ慰'r9B#̙Qu@~~W189@O#Ms=vlCF9 CTcE+1u#߰wyK?Ү^6$߁F'ftgl;s" 0z|d0<!<:HWgwM|L\ %VQ[v}L&KI |k v@O*g՛ R;594ǃI$"}p3WnCXf_̝C݃ŞUF3|H9h#n/vG7\†\Bld /Mm/FݼS:ʤ$B{nwK7h;AԒsj ƌ  a|k)]=o@:- WMSggRŰ.#hSC>hG|⸟OoXb>~>岕ZM^@HM>XHAX}$ 郔2(,n9Ǘo۝bknp3/ 7f76% !uJ ]9{f;l<<]oszmG0"JlmR%iyw 4 UcMvg2t?Z )+#dZ};V ͗o'-+r?7SI}PLnA_8NŚ^\ηÒ!b4MC0slt6&h/v$cI&m{$JazrxQ@0vwMoqt  ]l@ uKCL⮎ N<1FZ =yV3IUJa sHO@_NVlq(vޝ@h;Z<,4[U_ w#vah+tp-ypX`]ַYNy'-+r?,z30MaR\jn6fキuB+ '¼VuVT ?yܣѺQ D]7l:7[qA4֊[ݑ8n)8btC|O1fNsٰvVJ/zL6t[kV32F~΃Ѷ1 ҜM}nx_qCr\U 2l)BZr-ڠ4C`gCtŪiB.ܰo+*ds ;$6khClUwDgyi7~<>:M%<0μn>׌s8~ՄeZCD_4-Siܪ):/)G,GĒ4&ݶ.QV]!`ps84:]h(%bìou".s8~ՄbʂpGRkBk2Ez T?on`W3AЧ fQd$~F,fNa˧Ý"g=}FQQ*bsD*ޯ%Zt'8 5DN$ω겙ЫKZ, b KcLv1UWnL2N2dHԾj;AAjX8rkFI@-dఐ#5xa,)#L+tܩo)Q*7hYL%3xNHL0(w?rT)Z# +6ą{3R90sKlLIo98aW ,jI]*9%Ctdcmy_)?wJK^fpB ~(X!wfGv@lQ/_!YtB_<3uIe&K$S J[NKe 3,6&3\㚯^lPF+ӿWVXd?Ao (0TGkր3}/}% ֚}IN˜Tdz||_2 #K>E֡C0pz1כz/,Jvk{<ڝ9 &[+Fbp8!-)1DQ=/F$G+J_)\\m=썷اgyA=rO &pjl-3_qN!mir%;DF R_5QpEO;Ӹ7r6MN@h1v S&y}BbF55j ߋ/8XG.*6}XcM 12 6rJvҡϟCg~c Y٤Q3[^_8Q{S/!su54=\}8` {>@@f | O懐4p;X'kyB%/%,R,il]g))ws9!?F{T}|('http://example.com/a/b/c')); // prints 'c'. * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. * * This method also accepts a target that is already relative and will try to relativize it further. Only a * relative-path reference will be returned as-is. * * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well */ public static function relativize(UriInterface $base, UriInterface $target): UriInterface { if ($target->getScheme() !== '' && ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') ) { return $target; } if (Uri::isRelativePathReference($target)) { // As the target is already highly relative we return it as-is. It would be possible to resolve // the target with `$target = self::resolve($base, $target);` and then try make it more relative // by removing a duplicate query. But let's not do that automatically. return $target; } if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { return $target->withScheme(''); } // We must remove the path before removing the authority because if the path starts with two slashes, the URI // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also // invalid. $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); if ($base->getPath() !== $target->getPath()) { return $emptyPathUri->withPath(self::getRelativePath($base, $target)); } if ($base->getQuery() === $target->getQuery()) { // Only the target fragment is left. And it must be returned even if base and target fragment are the same. return $emptyPathUri->withQuery(''); } // If the base URI has a query but the target has none, we cannot return an empty path reference as it would // inherit the base query component when resolving. if ($target->getQuery() === '') { $segments = explode('/', $target->getPath()); /** @var string $lastSegment */ $lastSegment = end($segments); return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); } return $emptyPathUri; } private static function getRelativePath(UriInterface $base, UriInterface $target): string { $sourceSegments = explode('/', $base->getPath()); $targetSegments = explode('/', $target->getPath()); array_pop($sourceSegments); $targetLastSegment = array_pop($targetSegments); foreach ($sourceSegments as $i => $segment) { if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { unset($sourceSegments[$i], $targetSegments[$i]); } else { break; } } $targetSegments[] = $targetLastSegment; $relativePath = str_repeat('../', count($sourceSegments)).implode('/', $targetSegments); // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./". // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used // as the first segment of a relative-path reference, as it would be mistaken for a scheme name. if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { $relativePath = "./$relativePath"; } elseif ('/' === $relativePath[0]) { if ($base->getAuthority() != '' && $base->getPath() === '') { // In this case an extra slash is added by resolve() automatically. So we must not add one here. $relativePath = ".$relativePath"; } else { $relativePath = "./$relativePath"; } } return $relativePath; } private function __construct() { // cannot be instantiated } }