2 declare(strict_types = 1);
19 use Doctrine\DBAL\Connection;
20 use Psr\Http\Message\ServerRequestInterface;
21 use Psr\Http\Message\UriInterface;
22 use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
23 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
24 use Symfony\Component\Routing\RequestContext;
110 $this->context =
$context ?? GeneralUtility::makeInstance(Context::class);
111 $this->enhancerFactory = GeneralUtility::makeInstance(EnhancerFactory::class);
112 $this->aspectFactory = GeneralUtility::makeInstance(AspectFactory::class, $this->context);
113 $this->cacheHashCalculator = GeneralUtility::makeInstance(CacheHashCalculator::class);
114 $this->context =
$context ?? GeneralUtility::makeInstance(Context::class);
128 throw new RouteNotFoundException(
'No previous result given. Cannot find a page for an empty route part', 1555303496);
130 $urlPath = $previousResult->getTail();
132 if (!empty($urlPath)) {
133 $normalizedParams = $request->getAttribute(
'normalizedParams');
134 if ($normalizedParams instanceof NormalizedParams) {
135 $scriptName = ltrim($normalizedParams->getScriptName(),
'/');
136 if ($scriptName !==
'' && strpos($urlPath, $scriptName) !==
false) {
137 $urlPath = str_replace($scriptName,
'', $urlPath);
142 $prefixedUrlPath =
'/' . trim($urlPath,
'/');
144 $pageCandidates = [];
145 $language = $previousResult->getLanguage();
146 $languages = [$language->getLanguageId()];
147 if (!empty($language->getFallbackLanguageIds())) {
148 $languages = array_merge($languages, $language->getFallbackLanguageIds());
151 foreach ($languages as $languageId) {
155 foreach ($pageCandidatesFromSlugsAndLanguage as $candidate) {
156 $slugCandidate =
'/' . trim($candidate[
'slug'],
'/');
157 if ($slugCandidate ===
'/' || strpos($prefixedUrlPath, $slugCandidate) === 0) {
159 if ($prefixedUrlPath === $slugCandidate) {
162 $pageCandidates = [$candidate];
166 $pageCandidates[] = $candidate;
172 if (empty($pageCandidates)) {
173 throw new RouteNotFoundException(
'No page candidates found for path "' . $urlPath .
'"', 1538389999);
176 $fullCollection =
new RouteCollection();
177 foreach ($pageCandidates ?? [] as $page) {
178 $pageIdForDefaultLanguage = (int)($page[
'l10n_parent'] ?: $page[
'uid']);
179 $pagePath = $page[
'slug'];
180 $pageCollection =
new RouteCollection();
181 $defaultRouteForPage =
new Route(
185 [
'utf8' =>
true,
'_page' => $page]
187 $pageCollection->add(
'default', $defaultRouteForPage);
189 foreach ($enhancers as $enhancer) {
190 if ($enhancer instanceof DecoratingEnhancerInterface) {
191 $enhancer->decorateForMatching($pageCollection, $urlPath);
194 foreach ($enhancers as $enhancer) {
195 if ($enhancer instanceof RoutingEnhancerInterface) {
196 $enhancer->enhanceForMatching($pageCollection);
200 $collectionPrefix =
'page_' . $page[
'uid'];
203 if (isset($page[
'MPvar'])) {
204 $collectionPrefix .=
'_MP_' . str_replace(
',',
'', $page[
'MPvar']);
206 $pageCollection->addNamePrefix($collectionPrefix .
'_');
207 $fullCollection->addCollection($pageCollection);
209 $defaultRouteForPage->setOption(
'_isDefault',
true);
212 $matcher =
new PageUriMatcher($fullCollection);
214 $result = $matcher->match($prefixedUrlPath);
216 $matchedRoute = $fullCollection->get($result[
'_route']);
218 }
catch (ResourceNotFoundException $e) {
221 throw new RouteNotFoundException(
'No route found for path "' . $urlPath .
'"', 1538389998);
234 public function generateUri($route, array $parameters = [],
string $fragment =
'',
string $type =
''): UriInterface
238 $languageOption = $parameters[
'_language'] ??
null;
239 unset($parameters[
'_language']);
240 if ($languageOption instanceof SiteLanguage) {
241 $language = $languageOption;
242 } elseif ($languageOption !==
null) {
243 $language = $this->site->getLanguageById((
int)$languageOption);
245 if ($language ===
null) {
246 $language = $this->site->getDefaultLanguage();
250 if (is_array($route)) {
251 $pageId = (int)$route[
'uid'];
252 } elseif (is_scalar($route)) {
253 $pageId = (int)$route;
258 $pageRepository = GeneralUtility::makeInstance(PageRepository::class,
$context);
259 $page = $pageRepository->getPage($pageId,
true);
260 $pagePath = $page[
'slug'] ??
'';
262 if ($parameters[
'MP'] ??
false) {
263 $mountPointPairs = explode(
',', $parameters[
'MP']);
275 [, $mountPointPage] = explode(
'-', reset($mountPointPairs));
276 $site = GeneralUtility::makeInstance(SiteMatcher::class)
277 ->matchByPageId((
int)$mountPointPage);
279 }
catch (SiteNotFoundException $e) {
283 $page[
'MPvar'] = $parameters[
'MP'];
284 unset($parameters[
'MP']);
287 $originalParameters = $parameters;
288 $collection =
new RouteCollection();
289 $defaultRouteForPage =
new Route(
290 '/' . ltrim($pagePath,
'/'),
293 [
'utf8' =>
true,
'_page' => $page]
295 $collection->add(
'default', $defaultRouteForPage);
298 unset($originalParameters[
'cHash']);
300 foreach ($enhancers as $enhancer) {
301 if ($enhancer instanceof RoutingEnhancerInterface) {
302 $enhancer->enhanceForGeneration($collection, $originalParameters);
305 foreach ($enhancers as $enhancer) {
306 if ($enhancer instanceof DecoratingEnhancerInterface) {
307 $enhancer->decorateForGeneration($collection, $originalParameters);
311 $scheme = $language->getBase()->getScheme();
312 $mappableProcessor =
new MappableProcessor();
315 rtrim($language->getBase()->getPath(),
'/'),
317 $language->getBase()->getHost(),
319 $scheme ===
'http' ? $language->getBase()->getPort() ?? 80 : 80,
320 $scheme ===
'https' ? $language->getBase()->getPort() ?? 443 : 443
322 $generator =
new UrlGenerator($collection,
$context);
323 $generator->injectMappableProcessor($mappableProcessor);
325 $defaultRouteForPage->setOption(
'_isDefault',
true);
326 $allRoutes = GeneralUtility::makeInstance(RouteSorter::class)
327 ->withRoutes($collection->all())
328 ->withOriginalParameters($originalParameters)
329 ->sortRoutesForGeneration()
331 $matchedRoute =
null;
332 $pageRouteResult =
null;
335 $referenceType = $type === static::ABSOLUTE_PATH ? UrlGenerator::ABSOLUTE_PATH : UrlGenerator::ABSOLUTE_URL;
340 foreach ($allRoutes as $routeName => $route) {
342 $parameters = $originalParameters;
343 if ($route->hasOption(
'deflatedParameters')) {
344 $parameters = $route->getOption(
'deflatedParameters');
346 $mappableProcessor->generate($route, $parameters);
348 $urlAsString = $generator->generate($routeName, $parameters, $referenceType);
349 $uri =
new Uri($urlAsString);
351 $matchedRoute = $collection->get($routeName);
354 $appliedDefaults = $matchedRoute->getOption(
'_appliedDefaults') ?? [];
355 parse_str($uri->getQuery() ??
'', $remainingQueryParameters);
356 $enhancer = $route->getEnhancer();
357 if ($enhancer instanceof InflatableEnhancerInterface) {
358 $remainingQueryParameters = $enhancer->inflateParameters($remainingQueryParameters);
360 $pageRouteResult = $this->
buildPageArguments($route, array_merge($appliedDefaults, $parameters), $remainingQueryParameters);
362 }
catch (MissingMandatoryParametersException $e) {
367 if (!$uri instanceof UriInterface) {
368 throw new InvalidRouteArgumentsException(
'Uri could not be built for page "' . $pageId .
'"', 1538390230);
371 if ($pageRouteResult && $pageRouteResult->areDirty()) {
374 throw new InvalidRouteArgumentsException(
'Route arguments are dirty', 1537613247);
377 if ($matchedRoute && $pageRouteResult && !empty($pageRouteResult->getDynamicArguments())) {
380 $queryArguments = $pageRouteResult->getQueryArguments();
381 if (!empty($cacheHash)) {
382 $queryArguments[
'cHash'] = $cacheHash;
384 $uri = $uri->withQuery(http_build_query($queryArguments,
'',
'&', PHP_QUERY_RFC3986));
387 $uri = $uri->withFragment($fragment);
403 $context = GeneralUtility::makeInstance(Context::class);
405 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
406 ->getQueryBuilderForTable(
'pages');
410 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
411 ->add(GeneralUtility::makeInstance(FrontendWorkspaceRestriction::class,
null,
null, $searchLiveRecordsOnly));
413 $statement = $queryBuilder
414 ->select(
'uid',
'l10n_parent',
'pid',
'slug',
'mount_pid',
'mount_pid_ol',
't3ver_state',
'doktype',
't3ver_wsid',
't3ver_oid')
417 $queryBuilder->expr()->eq(
419 $queryBuilder->createNamedParameter($languageId, \PDO::PARAM_INT)
421 $queryBuilder->expr()->in(
423 $queryBuilder->createNamedParameter(
425 Connection::PARAM_STR_ARRAY
430 ->orderBy(
'slug',
'desc')
432 ->addOrderBy(
'mount_pid_ol',
'asc')
433 ->addOrderBy(
'mount_pid',
'asc')
435 $isRecursiveCall = !empty($excludeUids);
438 $siteMatcher = GeneralUtility::makeInstance(SiteMatcher::class);
439 $pageRepository = GeneralUtility::makeInstance(PageRepository::class,
$context);
441 while ($row = $statement->fetch()) {
442 $mountPageInformation =
null;
443 $pageRepository->fixVersioningPid(
'pages', $row);
444 $pageIdInDefaultLanguage = (int)($languageId > 0 ? $row[
'l10n_parent'] : $row[
'uid']);
446 if (in_array($pageIdInDefaultLanguage, $excludeUids,
true)) {
450 $isOnSameSite = $siteMatcher->matchByPageId($pageIdInDefaultLanguage)->getRootPageId() === $this->site->getRootPageId();
451 }
catch (SiteNotFoundException $e) {
453 $isOnSameSite =
false;
458 if (!$isOnSameSite && $isRecursiveCall) {
462 $mountPageInformation = $pageRepository->getMountPointInfo($pageIdInDefaultLanguage, $row);
465 if (!$isOnSameSite && !$isRecursiveCall && $mountPageInformation) {
469 if ($mountPageInformation) {
471 $row[
'MPvar'] = $mountPageInformation[
'MPvar'];
472 $mountedPage = $pageRepository->getPage_noCheck($mountPageInformation[
'mount_pid_rec'][
'uid']);
474 $mountedPage = $pageRepository->getPageOverlay($mountedPage, $languageId);
486 if (in_array((
int)$mountedPage[
'uid'], $excludeUids,
true)) {
489 $pageToAdd = $mountedPage;
491 $pageToAdd[
'MPvar'] = $mountPageInformation[
'MPvar'];
492 $pageToAdd[
'slug'] = $row[
'slug'];
493 $pages[] = $pageToAdd;
494 $excludeUids[] = (int)$pageToAdd[
'uid'];
495 $excludeUids[] = $pageIdInDefaultLanguage;
501 if ($isOnSameSite && !in_array($pageIdInDefaultLanguage, $excludeUids,
true)) {
503 $excludeUids[] = $pageIdInDefaultLanguage;
507 if ($mountPageInformation) {
508 $siteOfMountedPage = $siteMatcher->matchByPageId((
int)$mountedPage[
'uid']);
509 if ($siteOfMountedPage instanceof Site) {
518 foreach ($morePageCandidates as $candidate) {
520 if (in_array((
int)$candidate[
'uid'], $excludeUids,
true)) {
523 $pages[] = $candidate;
551 array $mountPointPage,
553 Site $siteOfMountedPage,
555 array $slugCandidates,
559 $slugOfMountPoint = $mountPointPage[
'slug'] ??
'';
560 $commonSlugPrefixOfMountedPage = rtrim($mountedPage[
'slug'] ??
'',
'/');
561 $narrowedDownSlugPrefixes = [];
562 foreach ($slugCandidates as $slugCandidate) {
564 if (strpos($slugCandidate, $slugOfMountPoint) === 0) {
566 $narrowedDownSlugPrefix =
'/' . trim(substr($slugCandidate, strlen($slugOfMountPoint)),
'/');
567 $narrowedDownSlugPrefixes[] = $narrowedDownSlugPrefix;
568 $narrowedDownSlugPrefixes[] = $narrowedDownSlugPrefix .
'/';
570 if ($commonSlugPrefixOfMountedPage) {
571 $narrowedDownSlugPrefix = $commonSlugPrefixOfMountedPage . $narrowedDownSlugPrefix;
572 $narrowedDownSlugPrefixes[] = $narrowedDownSlugPrefix;
573 $narrowedDownSlugPrefixes[] = $narrowedDownSlugPrefix .
'/';
577 $trimmedSlugPrefixes = [];
578 $narrowedDownSlugPrefixes = array_unique($narrowedDownSlugPrefixes);
579 foreach ($narrowedDownSlugPrefixes as $narrowedDownSlugPrefix) {
580 $narrowedDownSlugPrefix = trim($narrowedDownSlugPrefix,
'/');
581 $trimmedSlugPrefixes[] =
'/' . $narrowedDownSlugPrefix;
582 if (!empty($narrowedDownSlugPrefix)) {
583 $trimmedSlugPrefixes[] =
'/' . $narrowedDownSlugPrefix .
'/';
586 $trimmedSlugPrefixes = array_unique($trimmedSlugPrefixes);
587 rsort($trimmedSlugPrefixes);
588 $routerForSite = GeneralUtility::makeInstance(static::class, $siteOfMountedPage);
590 $excludedPageIds = [(int)$mountPointPage[
'uid']];
591 $pageCandidates = $routerForSite->getPagesFromDatabaseForCandidates(
592 $trimmedSlugPrefixes,
597 $pageWhichMustBeInRootLine = (int)($mountPointPage[
'mount_pid_ol'] ? $mountedPage[
'uid'] : $mountPointPage[
'uid']);
598 foreach ($pageCandidates as $pageCandidate) {
599 if (!$pageCandidate[
'mount_pid_ol']) {
600 $pageCandidate[
'MPvar'] = $mountPointPage[
'MPvar'] . ($pageCandidate[
'MPvar'] ?
',' . $pageCandidate[
'MPvar'] :
'');
604 $pageCandidateIsConnectedInMountPoint =
false;
605 $rootLine = GeneralUtility::makeInstance(
606 RootlineUtility::class,
607 $pageCandidate[
'uid'],
608 $pageCandidate[
'MPvar'],
611 foreach ($rootLine as $pageInRootLine) {
612 if ((
int)$pageInRootLine[
'uid'] === $pageWhichMustBeInRootLine) {
613 $pageCandidateIsConnectedInMountPoint =
true;
617 if ($pageCandidateIsConnectedInMountPoint ===
false) {
623 $slugOfSubpage = $pageCandidate[
'slug'];
624 if ($commonSlugPrefixOfMountedPage && strpos($slugOfSubpage, $commonSlugPrefixOfMountedPage) === 0) {
625 $slugOfSubpage = substr($slugOfSubpage, strlen($commonSlugPrefixOfMountedPage));
627 $pageCandidate[
'slug'] = $slugOfMountPoint . (($slugOfSubpage && $slugOfSubpage !==
'/') ?
'/' . trim($slugOfSubpage,
'/') :
'');
628 $pages[] = $pageCandidate;
650 array $mountPointPairs,
651 PageRepository $pageRepository
654 $prefixesToRemove = [];
655 $slugPrefixesToAdd = [];
656 foreach ($mountPointPairs as $mountPointPair) {
657 [$mountRoot, $mountedPage] = GeneralUtility::intExplode(
'-', $mountPointPair);
658 $mountPageInformation = $pageRepository->getMountPointInfo($mountedPage);
659 if ($mountPageInformation) {
660 if ($pageId === $mountedPage) {
664 $mountedPage = $pageRepository->getPage($mountedPage);
665 $mountRoot = $pageRepository->getPage($mountRoot);
666 $slugPrefix = $mountedPage[
'slug'] ??
'';
667 if ($slugPrefix ===
'/') {
670 $prefixToRemove = $mountRoot[
'slug'] ??
'';
671 if ($prefixToRemove ===
'/') {
672 $prefixToRemove =
'';
674 $prefixesToRemove[] = $prefixToRemove;
675 $slugPrefixesToAdd[] = $slugPrefix;
678 $slugPrefixesToAdd = array_reverse($slugPrefixesToAdd);
679 $prefixesToRemove = array_reverse($prefixesToRemove);
680 foreach ($prefixesToRemove as $prefixToRemove) {
683 $replacement = array_shift($slugPrefixesToAdd);
684 if ($prefixToRemove !==
'' && strpos($pagePath, $prefixToRemove) === 0) {
685 $pagePath = substr($pagePath, strlen($prefixToRemove));
687 $pagePath = $replacement . ($pagePath !==
'/' ?
'/' . ltrim($pagePath,
'/') :
'');
703 foreach ($this->site->getConfiguration()[
'routeEnhancers'] ?? [] as $enhancerConfiguration) {
705 if (is_array($enhancerConfiguration[
'limitToPages'] ??
null) && !in_array($pageId, $enhancerConfiguration[
'limitToPages'])) {
708 $enhancerType = $enhancerConfiguration[
'type'] ??
'';
709 $enhancer = $this->enhancerFactory->create($enhancerType, $enhancerConfiguration);
710 if (!empty($enhancerConfiguration[
'aspects'] ??
null)) {
711 $aspects = $this->aspectFactory->createAspects(
712 $enhancerConfiguration[
'aspects'],
716 $enhancer->setAspects($aspects);
718 $enhancers[] = $enhancer;
733 foreach ($this->site->getConfiguration()[
'routeEnhancers'] ?? [] as $enhancerConfiguration) {
734 $enhancerType = $enhancerConfiguration[
'type'] ??
'';
735 $enhancer = $this->enhancerFactory->create($enhancerType, $enhancerConfiguration);
736 if ($enhancer instanceof DecoratingEnhancerInterface) {
737 $enhancers[] = $enhancer;
752 if (empty($decoratingEnhancers)) {
755 $redecorationPatterns = array_map(
756 function (DecoratingEnhancerInterface $decorationEnhancers) {
757 $pattern = $decorationEnhancers->getRoutePathRedecorationPattern();
758 return '(?:' . $pattern .
')';
762 return '(?P<decoration>' . implode(
'|', $redecorationPatterns) .
')';
784 if (!empty($redecorationPattern) && preg_match(
'#' . $redecorationPattern .
'#', $routePath, $matches)) {
785 $decoration = $matches[
'decoration'];
786 $decorationPattern = preg_quote($decoration,
'#');
787 $routePath = preg_replace(
'#' . $decorationPattern .
'$#',
'', $routePath);
790 $candidatePathParts = [];
791 $pathParts = GeneralUtility::trimExplode(
'/', $routePath,
true);
792 if (empty($pathParts)) {
796 while (!empty($pathParts)) {
797 $prefix =
'/' . implode(
'/', $pathParts);
798 $candidatePathParts[] = $prefix .
'/';
799 $candidatePathParts[] = $prefix;
800 array_pop($pathParts);
802 $candidatePathParts[] =
'/';
803 return $candidatePathParts;
813 return $this->cacheHashCalculator->calculateCacheHash(
825 $hashParameters = $arguments->getDynamicArguments();
826 $hashParameters[
'id'] = $pageId;
827 $uri = http_build_query($hashParameters,
'',
'&', PHP_QUERY_RFC3986);
828 return $this->cacheHashCalculator->getRelevantParameters($uri);
848 protected function buildPageArguments(Route $route, array $results, array $remainingQueryParameters = []): PageArguments
856 $enhancer = $route->getEnhancer();
857 if ($enhancer instanceof ResultingInterface) {
859 return $enhancer->buildResult($route, $results, $remainingQueryParameters);
861 $page = $route->getOption(
'_page');
862 $pageId = (int)($page[
'l10n_parent'] > 0 ? $page[
'l10n_parent'] : $page[
'uid']);
863 $type = $this->
resolveType($route, $remainingQueryParameters);
865 if ($page[
'MPvar'] ??
'') {
866 $routeArguments[
'MP'] = $page[
'MPvar'];
868 return new PageArguments($pageId, $type, $routeArguments, [], $remainingQueryParameters);
878 protected function resolveType(Route $route, array &$remainingQueryParameters): string
880 $type = $remainingQueryParameters[
'type'] ?? 0;
881 $decoratedParameters = $route->getOption(
'_decoratedParameters');
882 if (isset($decoratedParameters[
'type'])) {
883 $type = $decoratedParameters[
'type'];
884 unset($decoratedParameters[
'type']);
885 $remainingQueryParameters = array_replace_recursive(
886 $remainingQueryParameters,
890 return (
string)$type;
905 if (empty($variableNames)) {
908 $mappers = $route->filterAspects(
909 [StaticMappableAspectInterface::class, \Countable::class],
912 if (empty($mappers)) {
916 $multipliers = array_map(
'count', $mappers);
917 $product = array_product($multipliers);
918 if ($product > 10000) {
919 throw new \OverflowException(
920 'Possible range of all mappers is larger than 10000 items',
935 return array_intersect_key(
937 array_flip($route->compile()->getPathVariables())