| Current Path : /proc/thread-self/root/var/www/surf/TYPO3/vendor/typo3/cms-core/Classes/Routing/ |
| Current File : //proc/thread-self/root/var/www/surf/TYPO3/vendor/typo3/cms-core/Classes/Routing/BestUrlMatcher.php |
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Routing;
use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RouteCollection as SymfonyRouteCollection;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* @internal
*/
class BestUrlMatcher extends UrlMatcher
{
protected function matchCollection(string $pathinfo, SymfonyRouteCollection $routes): array
{
$matchedRoutes = $this->preMatchCollection($pathinfo, $routes);
$matches = count($matchedRoutes);
if ($matches === 0) {
return [];
}
if ($matches === 1) {
return $matchedRoutes[0]->getRouteResult();
}
usort($matchedRoutes, [$this, 'sortMatchedRoutes']);
return array_shift($matchedRoutes)->getRouteResult();
}
/**
* Tries to match a URL with a set of routes.
* Basically all code has been duplicated from `UrlMatcher::matchCollection`, the difference is
* it does not just return the first match, but return all possible matches for further reduction.
*
* @param string $pathinfo The path info to be parsed
* @return list<MatchedRoute>
*/
protected function preMatchCollection(string $pathinfo, SymfonyRouteCollection $routes): array
{
$matchedRoutes = [];
// HEAD and GET are equivalent as per RFC
$method = $this->context->getMethod();
if ($method === 'HEAD') {
$method = 'GET';
}
$supportsTrailingSlash = $method === 'GET' && $this instanceof RedirectableUrlMatcherInterface;
$trimmedPathinfo = rtrim($pathinfo, '/') ?: '/';
foreach ($routes as $name => $route) {
$compiledRoute = $route->compile();
$staticPrefix = rtrim($compiledRoute->getStaticPrefix(), '/');
$requiredMethods = $route->getMethods();
// check the static prefix of the URL first. Only use the more expensive preg_match when it matches
if ($staticPrefix !== '' && !str_starts_with($trimmedPathinfo, $staticPrefix)) {
continue;
}
$regex = $compiledRoute->getRegex();
$pos = strrpos($regex, '$');
$hasTrailingSlash = $regex[$pos - 1] === '/';
$regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash);
if (!preg_match($regex, $pathinfo, $matches)) {
continue;
}
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{[\w\x80-\xFF]+\}/?$#', $route->getPath());
if ($hasTrailingVar && ($hasTrailingSlash || (null === $m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && preg_match($regex, $trimmedPathinfo, $m)) {
if ($hasTrailingSlash) {
$matches = $m;
} else {
$hasTrailingVar = false;
}
}
$hostMatches = [];
if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) {
continue;
}
$attributes = $this->getAttributes($route, $name, array_replace($matches, $hostMatches));
$status = $this->handleRouteRequirements($pathinfo, $name, $route, $attributes);
if ($status[0] === self::REQUIREMENT_MISMATCH) {
continue;
}
if ($pathinfo !== '/' && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
if ($supportsTrailingSlash && (!$requiredMethods || \in_array('GET', $requiredMethods))) {
return $this->allow = $this->allowSchemes = [];
}
continue;
}
if ($route->getSchemes() && !$route->hasScheme($this->context->getScheme())) {
$this->allowSchemes = array_merge($this->allowSchemes, $route->getSchemes());
continue;
}
if ($requiredMethods && !\in_array($method, $requiredMethods)) {
$this->allow = array_merge($this->allow, $requiredMethods);
continue;
}
$matchedRoute = GeneralUtility::makeInstance(
MatchedRoute::class,
$route,
array_replace($attributes, $status[1] ?? [])
);
$matchedRoutes[] = $matchedRoute->withPathMatches($matches)->withHostMatches($hostMatches);
}
return $matchedRoutes;
}
/**
* Sorts the best matching route result to the beginning
*/
protected function sortMatchedRoutes(MatchedRoute $a, MatchedRoute $b): int
{
if ($a->getFallbackScore() !== $b->getFallbackScore()) {
// sort fallbacks to the end
return $a->getFallbackScore() <=> $b->getFallbackScore();
}
if ($b->getHostMatchScore() !== $a->getHostMatchScore()) {
// sort more specific host matches to the beginning
return $b->getHostMatchScore() <=> $a->getHostMatchScore();
}
// index `1` refers to the array index containing the corresponding `tail` match
// @todo not sure, whether `tail` can be defined generic, it's hard coded in `SiteMatcher`
if ($b->getPathMatchScore(1) !== $a->getPathMatchScore(1)) {
return $b->getPathMatchScore(1) <=> $a->getPathMatchScore(1);
}
// fallback for behavior prior to issue #93240, using reverse sorted site identifier
// (side note: site identifier did not contain any URL relevant information)
return $b->getSiteIdentifier() <=> $a->getSiteIdentifier();
}
}