diff --git a/typo3/sysext/core/Classes/Domain/Page.php b/typo3/sysext/core/Classes/Domain/Page.php new file mode 100644 index 0000000000000000000000000000000000000000..dec1ab0ae2dd74f5420c43fbd6af05a7a642e729 --- /dev/null +++ b/typo3/sysext/core/Classes/Domain/Page.php @@ -0,0 +1,68 @@ +<?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\Domain; + +/** + * @internal not part of public API, as this needs to be streamlined and proven + */ +class Page implements \ArrayAccess +{ + use PropertyTrait; + + protected array $specialPropertyNames = [ + '_language', + '_LOCALIZED_UID', + '_MP_PARAM', + '_ORIG_uid', + '_ORIG_pid', + '_SHORTCUT_ORIGINAL_PAGE_UID', + '_PAGES_OVERLAY', + '_PAGES_OVERLAY_UID', + '_PAGES_OVERLAY_LANGUAGE', + '_PAGES_OVERLAY_REQUESTEDLANGUAGE', + ]; + + protected array $specialProperties = []; + + public function __construct(array $properties) + { + foreach ($properties as $propertyName => $propertyValue) { + if (isset($this->specialPropertyNames[$propertyName])) { + $this->specialProperties[$propertyName] = $propertyValue; + } else { + $this->properties[$propertyName] = $propertyValue; + } + } + } + + public function getLanguageId(): int + { + return $this->specialProperties['_language'] ?? $this->specialProperties['_PAGES_OVERLAY_LANGUAGE'] ?? $this->properties['sys_language_uid']; + } + + public function getPageId(): int + { + $pageId = isset($this->properties['l10n_parent']) && $this->properties['l10n_parent'] > 0 ? $this->properties['l10n_parent'] : $this->properties['uid']; + return (int)$pageId; + } + + public function toArray(): array + { + return $this->properties; + } +} diff --git a/typo3/sysext/core/Classes/Domain/PropertyTrait.php b/typo3/sysext/core/Classes/Domain/PropertyTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..bbf0ab3765d4e81b1afaa1358af421827ec5ed6c --- /dev/null +++ b/typo3/sysext/core/Classes/Domain/PropertyTrait.php @@ -0,0 +1,53 @@ +<?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\Domain; + +/** + * @internal not part of public API, as this needs to be streamlined and proven + */ +trait PropertyTrait +{ + /** + * @var array<string, mixed> + */ + protected array $properties = []; + + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + return isset($this->properties[$offset]); + } + + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->properties[$offset] ?? null; + } + + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + $this->properties[$offset] = $value; + } + + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + unset($this->properties[$offset]); + } +} diff --git a/typo3/sysext/core/Classes/Routing/PageRouter.php b/typo3/sysext/core/Classes/Routing/PageRouter.php index 56ec9d393bb253887a3e8503d86726a57b3b2298..60b1c4b0c09351c16322019d6ddce886e022d86a 100644 --- a/typo3/sysext/core/Classes/Routing/PageRouter.php +++ b/typo3/sysext/core/Classes/Routing/PageRouter.php @@ -24,6 +24,7 @@ use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\RequestContext; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Context\LanguageAspectFactory; +use TYPO3\CMS\Core\Domain\Page; use TYPO3\CMS\Core\Domain\Repository\PageRepository; use TYPO3\CMS\Core\Exception\SiteNotFoundException; use TYPO3\CMS\Core\Http\Uri; @@ -224,9 +225,9 @@ class PageRouter implements RouterInterface } /** - * API for generating a page where the $route parameter is typically an array (page record) or the page ID + * API for generating a page uri where the $route parameter is typically an array (a page record) or the page ID * - * @param array|string|int $route + * @param array|string|int|Page $route * @param array $parameters an array of query parameters which can be built into the URI path, also consider the special handling of "_language" * @param string $fragment additional #my-fragment part * @param string $type see the RouterInterface for possible types @@ -249,7 +250,9 @@ class PageRouter implements RouterInterface } $pageId = 0; - if (is_array($route)) { + if ($route instanceof Page) { + $pageId = $route->getPageId(); + } elseif (is_array($route)) { $pageId = (int)$route['uid']; } elseif (is_scalar($route)) { $pageId = (int)$route; @@ -258,7 +261,19 @@ class PageRouter implements RouterInterface $context = clone $this->context; $context->setAspect('language', LanguageAspectFactory::createFromSiteLanguage($language)); $pageRepository = GeneralUtility::makeInstance(PageRepository::class, $context); - $page = $pageRepository->getPage($pageId, true); + + if ($route instanceof Page) { + $page = $route->toArray(); + } elseif (is_array($route) + // Check 3rd party input $route for basic requirements + && isset($route['uid'], $route['sys_language_uid'], $route['l10n_parent'], $route['slug']) + && (int)$route['sys_language_uid'] === $language->getLanguageId() + && ((int)$route['l10n_parent'] === 0 || ($route['_PAGES_OVERLAY'] ?? false)) + ) { + $page = $route; + } else { + $page = $pageRepository->getPage($pageId, true); + } $pagePath = $page['slug'] ?? ''; if ($parameters['MP'] ?? '') { diff --git a/typo3/sysext/core/Classes/Routing/RouterInterface.php b/typo3/sysext/core/Classes/Routing/RouterInterface.php index 800a115315a60b4b3c938c9e9a375bb80120d19b..fb0ff3f49903a405cf966b7c76ba06228cb86738 100644 --- a/typo3/sysext/core/Classes/Routing/RouterInterface.php +++ b/typo3/sysext/core/Classes/Routing/RouterInterface.php @@ -47,7 +47,7 @@ interface RouterInterface /** * Builds a URI based on the $route and the given parameters. * - * @param string|array|int $route either the route name, or for pages it is usually the array of a page record, or the page ID + * @param string|array|int|\ArrayAccess $route either the route name, or for pages it is usually the array of a page record, or the page ID * @param array $parameters query parameters, specially reserved parameters are usually prefixed with "_" * @param string $fragment the section/fragment www.example.com/page/#fragment, WITHOUT the hash * @param string $type see the constants above. diff --git a/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php b/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php index cf0515deb6b5f3fb4f7e55dfc7719687e0471aaa..9706abda2bac75aff9e0ef28c20479e78cdf08a3 100644 --- a/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php +++ b/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php @@ -26,6 +26,7 @@ use TYPO3\CMS\Core\Context\LanguageAspectFactory; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; +use TYPO3\CMS\Core\Domain\Page; use TYPO3\CMS\Core\Domain\Repository\PageRepository; use TYPO3\CMS\Core\Exception\Page\RootLineException; use TYPO3\CMS\Core\Exception\SiteNotFoundException; @@ -480,6 +481,7 @@ class PageLinkBuilder extends AbstractTypolinkBuilder $targetPageId = (int)($page['l10n_parent'] > 0 ? $page['l10n_parent'] : $page['uid']); $queryParameters['_language'] = $siteLanguageOfTargetPage; + $pageObject = new Page($page); if ($fragment && $useAbsoluteUrl === false @@ -493,7 +495,7 @@ class PageLinkBuilder extends AbstractTypolinkBuilder } else { try { $uri = $siteOfTargetPage->getRouter()->generateUri( - $targetPageId, + $pageObject, $queryParameters, $fragment, $useAbsoluteUrl ? RouterInterface::ABSOLUTE_URL : RouterInterface::ABSOLUTE_PATH