TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
<?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\Backend\Routing\Event;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
|
||||
/**
|
||||
* Listeners to this event will be able to modify the page preview
|
||||
* URI, which had been generated for a page in the frontend.
|
||||
*/
|
||||
final class AfterPagePreviewUriGeneratedEvent
|
||||
{
|
||||
public function __construct(
|
||||
private UriInterface $previewUri,
|
||||
private readonly int $pageId,
|
||||
private readonly int $languageId,
|
||||
private readonly array $rootline,
|
||||
private readonly string $section,
|
||||
private readonly array $additionalQueryParameters,
|
||||
private readonly Context $context,
|
||||
private readonly array $options
|
||||
) {}
|
||||
|
||||
public function setPreviewUri(UriInterface $previewUri): void
|
||||
{
|
||||
$this->previewUri = $previewUri;
|
||||
}
|
||||
|
||||
public function getPreviewUri(): UriInterface
|
||||
{
|
||||
return $this->previewUri;
|
||||
}
|
||||
|
||||
public function getPageId(): int
|
||||
{
|
||||
return $this->pageId;
|
||||
}
|
||||
|
||||
public function getLanguageId(): int
|
||||
{
|
||||
return $this->languageId;
|
||||
}
|
||||
|
||||
public function getRootline(): array
|
||||
{
|
||||
return $this->rootline;
|
||||
}
|
||||
|
||||
public function getSection(): string
|
||||
{
|
||||
return $this->section;
|
||||
}
|
||||
|
||||
public function getAdditionalQueryParameters(): array
|
||||
{
|
||||
return $this->additionalQueryParameters;
|
||||
}
|
||||
|
||||
public function getContext(): Context
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?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\Backend\Routing\Event;
|
||||
|
||||
use Psr\EventDispatcher\StoppableEventInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
|
||||
/**
|
||||
* Listeners to this event will be able to modify the corresponding parameters, before
|
||||
* the page preview URI is being generated, when linking to a page in the frontend.
|
||||
*/
|
||||
final class BeforePagePreviewUriGeneratedEvent implements StoppableEventInterface
|
||||
{
|
||||
private ?UriInterface $uri = null;
|
||||
|
||||
public function __construct(
|
||||
private int $pageId,
|
||||
private int $languageId,
|
||||
private array $rootline,
|
||||
private string $section,
|
||||
private array $additionalQueryParameters,
|
||||
private readonly Context $context,
|
||||
private readonly array $options
|
||||
) {}
|
||||
|
||||
public function setPreviewUri(UriInterface $uri): void
|
||||
{
|
||||
$this->uri = $uri;
|
||||
}
|
||||
|
||||
public function getPreviewUri(): ?UriInterface
|
||||
{
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
public function isPropagationStopped(): bool
|
||||
{
|
||||
return $this->uri !== null;
|
||||
}
|
||||
|
||||
public function getPageId(): int
|
||||
{
|
||||
return $this->pageId;
|
||||
}
|
||||
|
||||
public function setPageId(int $pageId): void
|
||||
{
|
||||
$this->pageId = $pageId;
|
||||
}
|
||||
|
||||
public function getLanguageId(): int
|
||||
{
|
||||
return $this->languageId;
|
||||
}
|
||||
|
||||
public function setLanguageId(int $languageId): void
|
||||
{
|
||||
$this->languageId = $languageId;
|
||||
}
|
||||
|
||||
public function getRootline(): array
|
||||
{
|
||||
return $this->rootline;
|
||||
}
|
||||
|
||||
public function setRootline(array $rootline): void
|
||||
{
|
||||
$this->rootline = $rootline;
|
||||
}
|
||||
|
||||
public function getSection(): string
|
||||
{
|
||||
return $this->section;
|
||||
}
|
||||
|
||||
public function setSection(string $section): void
|
||||
{
|
||||
$this->section = $section;
|
||||
}
|
||||
|
||||
public function getAdditionalQueryParameters(): array
|
||||
{
|
||||
return $this->additionalQueryParameters;
|
||||
}
|
||||
|
||||
public function setAdditionalQueryParameters(array $additionalQueryParameters): void
|
||||
{
|
||||
$this->additionalQueryParameters = $additionalQueryParameters;
|
||||
}
|
||||
|
||||
public function getContext(): Context
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Backend\Routing\Exception;
|
||||
|
||||
use TYPO3\CMS\Core\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when request token was invalid
|
||||
*/
|
||||
class InvalidRequestTokenException extends Exception {}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Backend\Routing\Exception;
|
||||
|
||||
use TYPO3\CMS\Core\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when request token was missing.
|
||||
*/
|
||||
class MissingRequestTokenException extends Exception {}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Backend\Routing\Exception;
|
||||
|
||||
use TYPO3\CMS\Core\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a resource was not found.
|
||||
*/
|
||||
class ResourceNotFoundException extends Exception {}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Backend\Routing\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a route does not exist
|
||||
*/
|
||||
class RouteNotFoundException extends \TYPO3\CMS\Core\Routing\RouteNotFoundException {}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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\Backend\Routing\Exception;
|
||||
|
||||
use TYPO3\CMS\Core\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a resource was found but the type (e.g. AJAX routes) did not match.
|
||||
*/
|
||||
class RouteTypeNotAllowedException extends Exception {}
|
||||
@@ -0,0 +1,654 @@
|
||||
<?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\Backend\Routing;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use TYPO3\CMS\Backend\Routing\Event\AfterPagePreviewUriGeneratedEvent;
|
||||
use TYPO3\CMS\Backend\Routing\Event\BeforePagePreviewUriGeneratedEvent;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Context\DateTimeAspect;
|
||||
use TYPO3\CMS\Core\Context\VisibilityAspect;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
|
||||
use TYPO3\CMS\Core\DataHandling\PageDoktypeRegistry;
|
||||
use TYPO3\CMS\Core\Domain\DateTimeFactory;
|
||||
use TYPO3\CMS\Core\Domain\RecordInterface;
|
||||
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
|
||||
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
|
||||
use TYPO3\CMS\Core\Routing\RouterInterface;
|
||||
use TYPO3\CMS\Core\Routing\UnableToLinkToPageException;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
use TYPO3\CMS\Core\Versioning\VersionState;
|
||||
|
||||
/**
|
||||
* Generate links to Frontend URLs with a modified scope.
|
||||
*/
|
||||
class PreviewUriBuilder
|
||||
{
|
||||
public const OPTION_SWITCH_FOCUS = 'switchFocus';
|
||||
public const OPTION_WINDOW_NAME = 'windowName';
|
||||
public const OPTION_WINDOW_FEATURES = 'windowFeatures';
|
||||
public const OPTION_WINDOW_SCOPE = 'windowScope';
|
||||
|
||||
public const OPTION_WINDOW_SCOPE_LOCAL = 'local';
|
||||
public const OPTION_WINDOW_SCOPE_GLOBAL = 'global';
|
||||
|
||||
protected array $record = [];
|
||||
protected string $table = 'pages';
|
||||
protected int $pageId;
|
||||
protected int $languageId = 0;
|
||||
protected array $rootLine = [];
|
||||
protected string $section = '';
|
||||
protected array $additionalQueryParameters = [];
|
||||
protected bool $moduleLoading = true;
|
||||
protected Context $context;
|
||||
|
||||
/**
|
||||
* @param int|array $page Page ID to be previewed
|
||||
*/
|
||||
public static function create(int|array $page): self
|
||||
{
|
||||
$pageId = 0;
|
||||
$schema = GeneralUtility::makeInstance(TcaSchemaFactory::class)->get('pages');
|
||||
$laguageFieldName = $schema->getCapability(TcaSchemaCapability::Language)->getLanguageField()->getName();
|
||||
$transOrigPointerFieldName = $schema->getCapability(TcaSchemaCapability::Language)->getTranslationOriginPointerField()->getName();
|
||||
if (!is_array($page)) {
|
||||
$pageId = $page;
|
||||
if ($pageId > 0) {
|
||||
// If a page ID is given, we fetch the record from the database
|
||||
$page = BackendUtility::getRecord('pages', $pageId) ?? [];
|
||||
} else {
|
||||
// If no valid page ID is given, we use an empty array
|
||||
$page = [];
|
||||
}
|
||||
}
|
||||
if ($page !== []) {
|
||||
if (($page[$transOrigPointerFieldName] ?? 0) > 0) {
|
||||
// If the page is a translation, we need to use the parent page's ID
|
||||
$pageId = (int)$page[$transOrigPointerFieldName];
|
||||
} else {
|
||||
// Otherwise, use the current page's ID
|
||||
$pageId = (int)($page['uid'] ?? 0);
|
||||
}
|
||||
}
|
||||
$obj = new self($pageId);
|
||||
$obj->pageId = $pageId;
|
||||
$obj->record = $page;
|
||||
$obj->languageId = (int)($page[$laguageFieldName] ?? 0);
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Only to be used by TYPO3 core - for now
|
||||
*/
|
||||
public static function createForRecordPreview(string $table, int|array|RecordInterface $record, int $pageId): self
|
||||
{
|
||||
if ($record instanceof RecordInterface) {
|
||||
$record = $record->getRawRecord()->toArray();
|
||||
}
|
||||
$recordId = is_int($record) ? $record : (int)($record['uid'] ?? 0);
|
||||
$previewPageId = self::getPreviewPageId($table, $recordId, $pageId);
|
||||
$obj = self::create($previewPageId);
|
||||
$obj->table = $table;
|
||||
$obj = $obj->withRootLine(BackendUtility::BEgetRootLine($previewPageId));
|
||||
$obj = $obj->withSection($table === 'tt_content' ? '#c' . $recordId : '');
|
||||
$obj = $obj->withAdditionalQueryParameters(self::getPreviewUrlParameters($previewPageId, $table, $record));
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $pageId Page ID to be previewed
|
||||
*/
|
||||
public function __construct(int $pageId)
|
||||
{
|
||||
$this->pageId = $pageId;
|
||||
$this->context = clone GeneralUtility::makeInstance(Context::class);
|
||||
$this->context->setAspect('visibility', new VisibilityAspect(true, false, false, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $moduleLoading whether to enable JavaScript module loading
|
||||
* @return static
|
||||
*/
|
||||
public function withModuleLoading(bool $moduleLoading): self
|
||||
{
|
||||
if ($this->moduleLoading === $moduleLoading) {
|
||||
return $this;
|
||||
}
|
||||
$target = clone $this;
|
||||
$target->moduleLoading = $moduleLoading;
|
||||
return $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $rootLine (alternative) root-line of pages
|
||||
* @return static
|
||||
*/
|
||||
public function withRootLine(array $rootLine): self
|
||||
{
|
||||
if ($this->rootLine === $rootLine) {
|
||||
return $this;
|
||||
}
|
||||
$target = clone $this;
|
||||
$target->rootLine = $rootLine;
|
||||
return $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $language particular language
|
||||
* @return static
|
||||
*/
|
||||
public function withLanguage(int $language): self
|
||||
{
|
||||
if ($this->languageId === $language) {
|
||||
return $this;
|
||||
}
|
||||
$target = clone $this;
|
||||
$target->languageId = $language;
|
||||
return $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $section particular section (anchor element)
|
||||
* @return static
|
||||
*/
|
||||
public function withSection(string $section): self
|
||||
{
|
||||
if ($this->section === $section) {
|
||||
return $this;
|
||||
}
|
||||
$target = clone $this;
|
||||
$target->section = $section;
|
||||
return $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $additionalQueryParameters additional URI query parameters
|
||||
* @return static
|
||||
*/
|
||||
public function withAdditionalQueryParameters(array|string $additionalQueryParameters): self
|
||||
{
|
||||
if (is_array($additionalQueryParameters)) {
|
||||
$additionalQueryParams = $additionalQueryParameters;
|
||||
} else {
|
||||
$additionalQueryParams = [];
|
||||
parse_str($additionalQueryParameters, $additionalQueryParams);
|
||||
}
|
||||
$languageId = $this->languageId;
|
||||
if (isset($additionalQueryParams['_language'])) {
|
||||
$languageId = (int)$additionalQueryParams['_language'];
|
||||
unset($additionalQueryParams['_language']);
|
||||
}
|
||||
// No change
|
||||
if ($this->languageId === $languageId && $additionalQueryParams === $this->additionalQueryParameters) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$target = clone $this;
|
||||
$target->additionalQueryParameters = $additionalQueryParams;
|
||||
$target->languageId = $languageId;
|
||||
return $target;
|
||||
}
|
||||
|
||||
public function isPreviewable(): bool
|
||||
{
|
||||
if ($this->pageId === 0 || !isset($this->record['doktype'])) {
|
||||
return false;
|
||||
}
|
||||
$isDeletePlaceholder = VersionState::tryFrom($this->record['t3ver_state'] ?? 0) === VersionState::DELETE_PLACEHOLDER;
|
||||
if ($isDeletePlaceholder) {
|
||||
return false;
|
||||
}
|
||||
// Custom records need to be configured via pages TSconfig to allow previews
|
||||
if ($this->table !== 'pages' && $this->table !== 'tt_content') {
|
||||
$previewConfiguration = BackendUtility::getPagesTSconfig($this->pageId)['TCEMAIN.']['preview.'][$this->table . '.'] ?? null;
|
||||
if (!is_array($previewConfiguration)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return self::isPreviewableDoktype($this->pageId, (int)$this->record['doktype']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds preview URI.
|
||||
*/
|
||||
public function buildUri(?array $options = null, ?Context $context = null): ?UriInterface
|
||||
{
|
||||
$eventDispatcher = GeneralUtility::makeInstance(EventDispatcherInterface::class);
|
||||
try {
|
||||
$event = new BeforePagePreviewUriGeneratedEvent(
|
||||
$this->pageId,
|
||||
$this->languageId,
|
||||
$this->rootLine,
|
||||
$this->section,
|
||||
$this->additionalQueryParameters,
|
||||
$context ?? $this->context,
|
||||
$this->enrichOptions($options)
|
||||
);
|
||||
$eventDispatcher->dispatch($event);
|
||||
|
||||
// If there hasn't been a custom preview URI set by an event listener, generate it.
|
||||
if ($event->getPreviewUri() === null) {
|
||||
if (!$this->isPreviewable()) {
|
||||
return null;
|
||||
}
|
||||
$permissionClause = $GLOBALS['BE_USER']->getPagePermsClause(Permission::PAGE_SHOW);
|
||||
$pageInfo = BackendUtility::readPageAccess($event->getPageId(), $permissionClause) ?: [];
|
||||
// Check if the page (= its rootline) has a site attached, otherwise just keep the URI as is
|
||||
if ($event->getRootline() === []) {
|
||||
$event->setRootline(BackendUtility::BEgetRootLine($event->getPageId()));
|
||||
}
|
||||
// prepare custom context for link generation (to allow for example time based previews)
|
||||
$event->setAdditionalQueryParameters(
|
||||
array_replace_recursive(
|
||||
$event->getAdditionalQueryParameters(),
|
||||
self::getAdditionalQueryParametersForAccessRestrictedPages($pageInfo, $event->getContext(), $event->getRootline())
|
||||
)
|
||||
);
|
||||
|
||||
// Build the URI with a site as prefix, if configured
|
||||
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
|
||||
try {
|
||||
$site = $siteFinder->getSiteByPageId($event->getPageId(), $event->getRootline());
|
||||
} catch (SiteNotFoundException $e) {
|
||||
throw new UnableToLinkToPageException('The page ' . $event->getPageId() . ' had no proper connection to a site, no link could be built.', 1651499353);
|
||||
}
|
||||
try {
|
||||
$previewRouteParameters = $event->getAdditionalQueryParameters();
|
||||
// Reassemble encapsulated language id into route parameters to get proper localized page preview
|
||||
// uri for non-default languages.
|
||||
if ($event->getLanguageId() > 0) {
|
||||
$previewRouteParameters['_language'] = $site->getLanguageById($event->getLanguageId());
|
||||
}
|
||||
$event->setPreviewUri(
|
||||
$site->getRouter($event->getContext())->generateUri(
|
||||
$event->getPageId(),
|
||||
$previewRouteParameters,
|
||||
$event->getSection(),
|
||||
RouterInterface::ABSOLUTE_URL
|
||||
)
|
||||
);
|
||||
} catch (\InvalidArgumentException|InvalidRouteArgumentsException $e) {
|
||||
throw new UnableToLinkToPageException(sprintf('The link to the page with ID "%d" could not be generated: %s', $event->getPageId(), $e->getMessage()), 1651499354, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$event = new AfterPagePreviewUriGeneratedEvent(
|
||||
$event->getPreviewUri(),
|
||||
$event->getPageId(),
|
||||
$event->getLanguageId(),
|
||||
$event->getRootline(),
|
||||
$event->getSection(),
|
||||
$event->getAdditionalQueryParameters(),
|
||||
$event->getContext(),
|
||||
$event->getOptions(),
|
||||
);
|
||||
$eventDispatcher->dispatch($event);
|
||||
|
||||
return $event->getPreviewUri();
|
||||
} catch (UnableToLinkToPageException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds attributes array (e.g. `['dispatch-action' => ...]`).
|
||||
* CAVE: Attributes are NOT XSS-protected and need to be put through `htmlspecialchars`
|
||||
*
|
||||
* @param array|null $options
|
||||
*/
|
||||
public function buildDispatcherDataAttributes(?array $options = null): ?array
|
||||
{
|
||||
if (null === ($attributes = $this->buildAttributes($options))) {
|
||||
return null;
|
||||
}
|
||||
$this->loadActionDispatcher();
|
||||
return $this->prefixAttributeNames('dispatch-', $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds attributes array (e.g. `['data-dispatch-action' => ...]`).
|
||||
* CAVE: Attributes are NOT XSS-protected and need to be put through `htmlspecialchars`
|
||||
*
|
||||
* @param array|null $options
|
||||
*/
|
||||
public function buildDispatcherAttributes(?array $options = null): ?array
|
||||
{
|
||||
if (null === ($attributes = $this->buildAttributes($options))) {
|
||||
return null;
|
||||
}
|
||||
$this->loadActionDispatcher();
|
||||
return $this->prefixAttributeNames('data-dispatch-', $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialized attributes are processed with `htmlspecialchars` and ready to be used.
|
||||
*
|
||||
* @param array|null $options
|
||||
*/
|
||||
public function serializeDispatcherAttributes(?array $options = null): ?string
|
||||
{
|
||||
if (null === ($attributes = $this->buildDispatcherAttributes($options))) {
|
||||
return null;
|
||||
}
|
||||
return ' ' . GeneralUtility::implodeAttributes($attributes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* `<typo3-immediate-action>` does not have a specific meaning and is used to
|
||||
* expose `data` attributes, see custom element in `ImmediateActionElement.ts`.
|
||||
*
|
||||
* @param array|null $options
|
||||
*/
|
||||
public function buildImmediateActionElement(?array $options = null): ?string
|
||||
{
|
||||
if (null === ($attributes = $this->buildAttributes($options))) {
|
||||
return null;
|
||||
}
|
||||
$this->loadImmediateActionElement();
|
||||
return sprintf(
|
||||
// `<typo3-immediate-action action="TYPO3.WindowManager.localOpen" args="[...]">`
|
||||
'<typo3-immediate-action %s></typo3-immediate-action>',
|
||||
GeneralUtility::implodeAttributes($attributes, true)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the preview page id, based on the given input, by checking
|
||||
* preview configuration and alternatively looking up the rootline.
|
||||
*
|
||||
* @param string $table The table of the record to be previewed - might be empty for direct page preview ($pageId > 0)
|
||||
* @param int $recordId The id of the record to be previewed - might be empty for direct page preview ($pageId > 0)
|
||||
* @param int $pageId The page to preview the record on, also used to preview a page directly
|
||||
* @internal Only to be used by TYPO3 core
|
||||
*/
|
||||
public static function getPreviewPageId(string $table, int $recordId, int $pageId): int
|
||||
{
|
||||
if ($table === 'pages') {
|
||||
$rootPageId = $recordId;
|
||||
if ($rootPageId) {
|
||||
$l10nPointer = GeneralUtility::makeInstance(TcaSchemaFactory::class)->get('pages')->getCapability(TcaSchemaCapability::Language)->getTranslationOriginPointerField()->getName();
|
||||
$page = BackendUtility::getRecord('pages', $rootPageId, 'is_siteroot,' . $l10nPointer);
|
||||
if ($page['is_siteroot'] && $page[$l10nPointer]) {
|
||||
$rootPageId = $page[$l10nPointer];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$rootPageId = max(0, $pageId);
|
||||
}
|
||||
|
||||
$previewConfiguration = BackendUtility::getPagesTSconfig($rootPageId)['TCEMAIN.']['preview.'][$table . '.'] ?? [];
|
||||
|
||||
if (isset($previewConfiguration['previewPageId'])) {
|
||||
return (int)$previewConfiguration['previewPageId'];
|
||||
}
|
||||
|
||||
$rootPageData = null;
|
||||
$rootLine = BackendUtility::BEgetRootLine($rootPageId);
|
||||
$currentPage = (array)(reset($rootLine) ?: []);
|
||||
if (VersionState::tryFrom($currentPage['t3ver_state'] ?? 0) !== VersionState::DELETE_PLACEHOLDER
|
||||
&& self::isPreviewableDoktype((int)($currentPage['uid'] ?? 0), (int)($currentPage['doktype'] ?? 0))
|
||||
) {
|
||||
// try the current page
|
||||
$previewPageId = $rootPageId;
|
||||
} else {
|
||||
// or search for the root page
|
||||
foreach ($rootLine as $page) {
|
||||
if ($page['is_siteroot'] ?? false) {
|
||||
$rootPageData = $page;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$previewPageId = isset($rootPageData)
|
||||
? (int)$rootPageData['uid']
|
||||
: $rootPageId;
|
||||
}
|
||||
return $previewPageId;
|
||||
}
|
||||
|
||||
protected function buildAttributes(?array $options = null): ?array
|
||||
{
|
||||
$options = $this->enrichOptions($options);
|
||||
if (null === ($uri = $this->buildUri($options))) {
|
||||
return null;
|
||||
}
|
||||
$args = [
|
||||
// target URI
|
||||
(string)$uri,
|
||||
// whether to switch focus to that window
|
||||
$options[self::OPTION_SWITCH_FOCUS],
|
||||
// name of the window instance for JavaScript references
|
||||
$options[self::OPTION_WINDOW_NAME],
|
||||
];
|
||||
if (isset($options[self::OPTION_WINDOW_FEATURES])) {
|
||||
// optional window features (e.g. 'width=500,height=300')
|
||||
$args[] = $options[self::OPTION_WINDOW_FEATURES];
|
||||
}
|
||||
return [
|
||||
'action' => $options[self::OPTION_WINDOW_SCOPE] === self::OPTION_WINDOW_SCOPE_GLOBAL
|
||||
? 'TYPO3.WindowManager.globalOpen'
|
||||
: 'TYPO3.WindowManager.localOpen',
|
||||
'args' => json_encode($args),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles options to used for opening preview URI in a new window/tab.
|
||||
* + `switchFocus` (bool): whether to focus new window in browser
|
||||
* + `windowName` (string): name of window for internal reference
|
||||
* + `windowScope` (string): `local` (current document) `global` (whole backend)
|
||||
*
|
||||
* @param array|null $options
|
||||
*/
|
||||
protected function enrichOptions(?array $options = null): array
|
||||
{
|
||||
return array_merge(
|
||||
[
|
||||
self::OPTION_SWITCH_FOCUS => null,
|
||||
// 'newTYPO3frontendWindow' was used in BackendUtility::viewOnClick
|
||||
self::OPTION_WINDOW_NAME => 'newTYPO3frontendWindow',
|
||||
self::OPTION_WINDOW_SCOPE => self::OPTION_WINDOW_SCOPE_LOCAL,
|
||||
],
|
||||
$options ?? []
|
||||
);
|
||||
}
|
||||
|
||||
protected function loadActionDispatcher(): void
|
||||
{
|
||||
if (!$this->moduleLoading) {
|
||||
return;
|
||||
}
|
||||
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
|
||||
$pageRenderer->loadJavaScriptModule('@typo3/backend/action-dispatcher.js');
|
||||
}
|
||||
|
||||
protected function loadImmediateActionElement(): void
|
||||
{
|
||||
if (!$this->moduleLoading) {
|
||||
return;
|
||||
}
|
||||
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
|
||||
$pageRenderer->loadJavaScriptModule('@typo3/backend/element/immediate-action-element.js');
|
||||
}
|
||||
|
||||
protected function prefixAttributeNames(string $prefix, array $attributes): array
|
||||
{
|
||||
$attributeNames = array_map(
|
||||
static function (string $name) use ($prefix): string {
|
||||
return $prefix . $name;
|
||||
},
|
||||
array_keys($attributes)
|
||||
);
|
||||
return array_combine(
|
||||
$attributeNames,
|
||||
array_values($attributes)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates ADMCMD parameters for the "viewpage" extension / frontend
|
||||
* @internal not part of TYPO3 Core API
|
||||
*/
|
||||
public static function getAdditionalQueryParametersForAccessRestrictedPages(array $pageInfo, Context $context, array $rootLine): array
|
||||
{
|
||||
if ($pageInfo === []) {
|
||||
return [];
|
||||
}
|
||||
// Initialize access restriction values from current page
|
||||
$access = [
|
||||
'fe_group' => (string)($pageInfo['fe_group'] ?? ''),
|
||||
'starttime' => (int)($pageInfo['starttime'] ?? 0),
|
||||
'endtime' => (int)($pageInfo['endtime'] ?? 0),
|
||||
];
|
||||
// Only check rootline if the current page has not set extendToSubpages itself
|
||||
if (!(bool)($pageInfo['extendToSubpages'] ?? false)) {
|
||||
// remove the current page from the rootline
|
||||
array_shift($rootLine);
|
||||
foreach ($rootLine as $page) {
|
||||
// Skip root node and pages which do not define extendToSubpages
|
||||
if ((int)($page['uid'] ?? 0) === 0 || !(bool)($page['extendToSubpages'] ?? false)) {
|
||||
continue;
|
||||
}
|
||||
$access['fe_group'] = (string)($page['fe_group'] ?? '');
|
||||
$access['starttime'] = (int)($page['starttime'] ?? 0);
|
||||
$access['endtime'] = (int)($page['endtime'] ?? 0);
|
||||
// Stop as soon as a page in the rootline has extendToSubpages set
|
||||
break;
|
||||
}
|
||||
}
|
||||
$additionalQueryParameters = [];
|
||||
if ((int)$access['fe_group'] === -2) {
|
||||
// -2 means "show at any login". We simulate first available fe_group.
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('fe_groups');
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
||||
->add(GeneralUtility::makeInstance(HiddenRestriction::class));
|
||||
|
||||
$activeFeGroupId = $queryBuilder->select('uid')
|
||||
->from('fe_groups')
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
|
||||
if ($activeFeGroupId) {
|
||||
$additionalQueryParameters['ADMCMD_simUser'] = $activeFeGroupId;
|
||||
}
|
||||
} elseif (!empty($access['fe_group'])) {
|
||||
$additionalQueryParameters['ADMCMD_simUser'] = $access['fe_group'];
|
||||
}
|
||||
if ($access['starttime'] > $GLOBALS['EXEC_TIME']) {
|
||||
// simulate access time to ensure PageRepository will find the page and in turn PageRouter will generate
|
||||
// a URL for it
|
||||
$dateAspect = new DateTimeAspect(DateTimeFactory::createFromTimestamp($access['starttime']));
|
||||
$context->setAspect('date', $dateAspect);
|
||||
$additionalQueryParameters['ADMCMD_simTime'] = $access['starttime'];
|
||||
}
|
||||
if ($access['endtime'] < $GLOBALS['EXEC_TIME'] && $access['endtime'] !== 0) {
|
||||
// Set access time to page's endtime subtracted one second to ensure PageRepository will find the page and
|
||||
// in turn PageRouter will generate a URL for it
|
||||
$dateAspect = new DateTimeAspect(DateTimeFactory::createFromTimestamp($access['endtime'] - 1));
|
||||
$context->setAspect('date', $dateAspect);
|
||||
$additionalQueryParameters['ADMCMD_simTime'] = ($access['endtime'] - 1);
|
||||
}
|
||||
return $additionalQueryParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameters for the preview URL by evaluating language overlays and preview configuration
|
||||
*/
|
||||
protected static function getPreviewUrlParameters(int $previewPageId, string $table, int|array $record): string
|
||||
{
|
||||
if (is_array($record)) {
|
||||
$recordId = (int)($record['uid'] ?? 0);
|
||||
} else {
|
||||
$recordId = $record;
|
||||
$record = BackendUtility::getRecord($table, $record) ?? [];
|
||||
}
|
||||
|
||||
$linkParameters = [];
|
||||
$previewConfiguration = BackendUtility::getPagesTSconfig($previewPageId)['TCEMAIN.']['preview.'][$table . '.'] ?? [];
|
||||
|
||||
// language handling
|
||||
$schema = GeneralUtility::makeInstance(TcaSchemaFactory::class)->get($table);
|
||||
if ($schema->isLanguageAware()
|
||||
&& ($languageField = $schema->getCapability(TcaSchemaCapability::Language)->getLanguageField()->getName())
|
||||
&& !empty($record[$languageField])
|
||||
) {
|
||||
$l10nPointer = $schema->getCapability(TcaSchemaCapability::Language)->getTranslationOriginPointerField()->getName();
|
||||
if (!empty($record[$l10nPointer]) && ($previewConfiguration['useDefaultLanguageRecord'] ?? true) === false) {
|
||||
$recordId = (int)$record[$l10nPointer];
|
||||
} else {
|
||||
$recordId = (int)$record['uid'];
|
||||
}
|
||||
$language = $record[$languageField];
|
||||
if ($language > 0) {
|
||||
$linkParameters['_language'] = $language;
|
||||
}
|
||||
}
|
||||
|
||||
// Always use live workspace record uid for the preview
|
||||
if ($schema->isWorkspaceAware() && ($record['t3ver_oid'] ?? 0) > 0) {
|
||||
$recordId = $record['t3ver_oid'];
|
||||
}
|
||||
|
||||
// map record data to GET parameters
|
||||
if (isset($previewConfiguration['fieldToParameterMap.'])) {
|
||||
foreach ($previewConfiguration['fieldToParameterMap.'] as $field => $parameterName) {
|
||||
$value = $record[$field] ?? '';
|
||||
if ($field === 'uid') {
|
||||
$value = $recordId;
|
||||
}
|
||||
$linkParameters[$parameterName] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// add/override parameters by configuration
|
||||
if (isset($previewConfiguration['additionalGetParameters.'])) {
|
||||
$linkParameters = array_replace(
|
||||
$linkParameters,
|
||||
GeneralUtility::removeDotsFromTS($previewConfiguration['additionalGetParameters.'])
|
||||
);
|
||||
}
|
||||
|
||||
return HttpUtility::buildQueryString($linkParameters, '&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current page has a doktype, which can be previewed
|
||||
*/
|
||||
protected static function isPreviewableDoktype(int $pageId, int $doktype): bool
|
||||
{
|
||||
if ($pageId <= 0 || $doktype <= 0) {
|
||||
return false;
|
||||
}
|
||||
$doktypeRegistry = GeneralUtility::makeInstance(PageDoktypeRegistry::class);
|
||||
return $doktypeRegistry->isPageViewable($doktype, $pageId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Backend\Routing;
|
||||
|
||||
use Symfony\Component\Routing\Route as SymfonyRoute;
|
||||
|
||||
/**
|
||||
* This is a single entity for a Route.
|
||||
*
|
||||
* The architecture is highly inspired by the Symfony Routing Component.
|
||||
*/
|
||||
class Route
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $path = '/';
|
||||
|
||||
protected array $methods = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
public static function fromSymfonyRoute(SymfonyRoute $source, string $identifier): self
|
||||
{
|
||||
$options = $source->getOptions();
|
||||
// Store the name of the Route in the _identifier option so the token can be checked against that
|
||||
$options['_identifier'] = $identifier;
|
||||
$methods = $options['methods'] ?? [];
|
||||
unset($options['methods']);
|
||||
|
||||
$target = new self($source->getPath(), $options);
|
||||
if (is_array($methods) && $methods !== []) {
|
||||
$target->setMethods($methods);
|
||||
}
|
||||
return $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor setting up the required path and options
|
||||
*
|
||||
* @param string $path The path pattern to match
|
||||
* @param array $options An array of options
|
||||
*/
|
||||
public function __construct($path, $options)
|
||||
{
|
||||
$this->setPath($path)->setOptions($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path
|
||||
*
|
||||
* @return string The path pattern
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the pattern for the path
|
||||
* A pattern must start with a slash and must not have multiple slashes at the beginning because the
|
||||
* generated path for this route would be confused with a network path, e.g. '//domain.com/path'.
|
||||
*
|
||||
* This method implements a fluent interface.
|
||||
*
|
||||
* @param string $pattern The path pattern
|
||||
* @return Route The current Route instance
|
||||
*/
|
||||
public function setPath($pattern)
|
||||
{
|
||||
$this->path = '/' . ltrim(trim($pattern), '/');
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the uppercased HTTP methods this route is restricted to.
|
||||
* An empty array means that any method is allowed.
|
||||
*
|
||||
* @return string[] The methods
|
||||
*/
|
||||
public function getMethods(): array
|
||||
{
|
||||
return $this->methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HTTP methods (e.g. ['POST']) this route is restricted to.
|
||||
* An empty array means that any method is allowed.
|
||||
*
|
||||
* This method implements a fluent interface.
|
||||
*
|
||||
* @param string[] $methods The array of allowed methods
|
||||
*/
|
||||
public function setMethods(array $methods): self
|
||||
{
|
||||
$this->methods = array_map(strtoupper(...), $methods);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the options set
|
||||
*
|
||||
* @return array The options
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the options
|
||||
*
|
||||
* This method implements a fluent interface.
|
||||
*
|
||||
* @param array $options The options
|
||||
* @return Route The current Route instance
|
||||
*/
|
||||
public function setOptions(array $options)
|
||||
{
|
||||
$this->options = $options;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an option value
|
||||
*
|
||||
* This method implements a fluent interface.
|
||||
*
|
||||
* @param string $name An option name
|
||||
* @param mixed $value The option value
|
||||
* @return Route The current Route instance
|
||||
*/
|
||||
public function setOption($name, $value)
|
||||
{
|
||||
$this->options[$name] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an option value
|
||||
*
|
||||
* @param string $name An option name
|
||||
* @return mixed The option value or NULL when not given
|
||||
*/
|
||||
public function getOption($name)
|
||||
{
|
||||
return $this->options[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an option has been set
|
||||
*
|
||||
* @param string $name An option name
|
||||
* @return bool TRUE if the option is set, FALSE otherwise
|
||||
*/
|
||||
public function hasOption($name)
|
||||
{
|
||||
return array_key_exists($name, $this->options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?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\Backend\Routing;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException;
|
||||
use TYPO3\CMS\Backend\Routing\Exception\RouteTypeNotAllowedException;
|
||||
use TYPO3\CMS\Core\Http\Error\MethodNotAllowedException;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
|
||||
/**
|
||||
* A value object representing redirects within Backend routing.
|
||||
*/
|
||||
class RouteRedirect
|
||||
{
|
||||
/**
|
||||
* Name of route to be redirected to
|
||||
*/
|
||||
private string $name;
|
||||
|
||||
/**
|
||||
* Multi-dimensional query params array
|
||||
* e.g. `['level1' => ['level2' => 'value']]`
|
||||
*/
|
||||
private array $parameters;
|
||||
|
||||
public static function create(string $name, $params): self
|
||||
{
|
||||
if (is_string($params)) {
|
||||
parse_str($params, $parsedParameters);
|
||||
$params = $parsedParameters;
|
||||
} elseif (!is_array($params)) {
|
||||
throw new \LogicException('Params must be array or string', 1627907107);
|
||||
}
|
||||
return new self($name, $params);
|
||||
}
|
||||
|
||||
public static function createFromRoute(Route $route, array $parameters): self
|
||||
{
|
||||
return new self($route->getOption('_identifier'), $parameters);
|
||||
}
|
||||
|
||||
public static function createFromRequest(ServerRequestInterface $request): ?self
|
||||
{
|
||||
$name = $request->getQueryParams()['redirect'] ?? null;
|
||||
if (empty($name)) {
|
||||
return null;
|
||||
}
|
||||
return self::create($name, $request->getQueryParams()['redirectParams'] ?? []);
|
||||
}
|
||||
|
||||
private function __construct(string $name, array $params)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->parameters = $this->sanitizeParameters($params);
|
||||
}
|
||||
|
||||
private function sanitizeParameters(array $redirectParameters): array
|
||||
{
|
||||
unset($redirectParameters['token']);
|
||||
unset($redirectParameters['route']);
|
||||
unset($redirectParameters['redirect']);
|
||||
unset($redirectParameters['redirectParams']);
|
||||
return $redirectParameters;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getParameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
public function getFormattedParameters(): string
|
||||
{
|
||||
$redirectParameters = http_build_query($this->parameters, '', '&', PHP_QUERY_RFC3986);
|
||||
return ltrim($redirectParameters, '&?');
|
||||
}
|
||||
|
||||
public function hasParameters(): bool
|
||||
{
|
||||
return !empty($this->parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the route can be resolved as a redirect.
|
||||
*
|
||||
* @throws RouteNotFoundException
|
||||
* @throws MethodNotAllowedException
|
||||
* @throws RouteTypeNotAllowedException
|
||||
*/
|
||||
public function resolve(Router $router): void
|
||||
{
|
||||
$route = $router->getRoute($this->name);
|
||||
if ($route === null) {
|
||||
throw new RouteNotFoundException(
|
||||
sprintf('Route "%s" was not found', $this->name),
|
||||
1627907587
|
||||
);
|
||||
}
|
||||
if ($route->getOption('ajax')) {
|
||||
throw new RouteTypeNotAllowedException(
|
||||
sprintf('AJAX route "%s" cannot be redirected', $this->name),
|
||||
1627407451
|
||||
);
|
||||
}
|
||||
// Links to modules are always allowed, no problem here
|
||||
// Check for the AJAX option should be handled before
|
||||
if ($route->getOption('module')) {
|
||||
return;
|
||||
}
|
||||
if ($route->getMethods() !== [] && !in_array('GET', $route->getMethods(), true)) {
|
||||
throw new MethodNotAllowedException(
|
||||
$route->getMethods(),
|
||||
1627407452
|
||||
);
|
||||
}
|
||||
$settings = $route->getOption('redirect');
|
||||
if (($settings['enable'] ?? false) !== true) {
|
||||
throw new RouteNotFoundException(
|
||||
sprintf('Route "%s" cannot be redirected', $this->name),
|
||||
1627407511
|
||||
);
|
||||
}
|
||||
// Only use allowed arguments, if set, otherwise no parameters are allowed
|
||||
if (!empty($settings['parameters'])) {
|
||||
$this->parameters = ArrayUtility::intersectRecursive($this->parameters, (array)$settings['parameters']);
|
||||
} else {
|
||||
$this->parameters = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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\Backend\Routing;
|
||||
|
||||
use TYPO3\CMS\Core\Routing\RouteResultInterface;
|
||||
|
||||
/**
|
||||
* A route result for the TYPO3 Backend Routing,
|
||||
* containing the matched Route and the related arguments found in the URL
|
||||
*/
|
||||
class RouteResult implements RouteResultInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected Route $route,
|
||||
protected array $arguments = [],
|
||||
) {}
|
||||
|
||||
public function getRoute(): Route
|
||||
{
|
||||
return $this->route;
|
||||
}
|
||||
|
||||
public function getRouteName(): string
|
||||
{
|
||||
return $this->route->getOption('_identifier');
|
||||
}
|
||||
|
||||
public function getArguments(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function offsetExists($offset): bool
|
||||
{
|
||||
return $offset === 'route' || isset($this->arguments[$offset]);
|
||||
}
|
||||
|
||||
public function offsetGet(mixed $offset): mixed
|
||||
{
|
||||
return match ($offset) {
|
||||
'route' => $this->route,
|
||||
default => $this->arguments[$offset],
|
||||
};
|
||||
}
|
||||
|
||||
public function offsetSet(mixed $offset = '', mixed $value = ''): void
|
||||
{
|
||||
switch ($offset) {
|
||||
case 'route':
|
||||
$this->route = $value;
|
||||
break;
|
||||
default:
|
||||
$this->arguments[$offset] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
switch ($offset) {
|
||||
case 'route':
|
||||
throw new \InvalidArgumentException('You can never unset the Route in a route result', 1669839336);
|
||||
default:
|
||||
unset($this->arguments[$offset]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Backend\Routing;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use Symfony\Component\Routing\Matcher\UrlMatcher;
|
||||
use Symfony\Component\Routing\Route as SymfonyRoute;
|
||||
use TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException;
|
||||
use TYPO3\CMS\Core\Http\Error\MethodNotAllowedException;
|
||||
use TYPO3\CMS\Core\Routing\BackendEntryPointResolver;
|
||||
use TYPO3\CMS\Core\Routing\RequestContextFactory;
|
||||
use TYPO3\CMS\Core\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Implementation of a class for adding routes, collecting throughout the Bootstrap
|
||||
* to register all sorts of Backend Routes, and to fetch the main Collection in order
|
||||
* to resolve a route (see ->match() and ->matchRequest()).
|
||||
*
|
||||
* Ideally, the Router is solely instantiated and accessed via the Bootstrap, the RequestHandler and the UriBuilder.
|
||||
*
|
||||
* See \TYPO3\CMS\Backend\Http\RequestHandler for more details on route matching() and Bootstrap->initializeBackendRouting().
|
||||
*
|
||||
* The architecture is inspired by the Symfony Routing Component.
|
||||
*/
|
||||
#[Autoconfigure(
|
||||
public: true,
|
||||
configurator: '@' . RouterConfigurator::class,
|
||||
)]
|
||||
class Router
|
||||
{
|
||||
/**
|
||||
* All routes used in the TYPO3 Backend
|
||||
*/
|
||||
protected RouteCollection $routeCollection;
|
||||
|
||||
public function __construct(
|
||||
protected readonly RequestContextFactory $requestContextFactory,
|
||||
protected readonly BackendEntryPointResolver $backendEntryPointResolver,
|
||||
) {
|
||||
$this->routeCollection = new RouteCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new route with a given identifier
|
||||
*/
|
||||
public function addRoute(string $routeIdentifier, Route $route, array $aliases = []): void
|
||||
{
|
||||
$symfonyRoute = new SymfonyRoute($route->getPath(), [], [], $route->getOptions());
|
||||
$symfonyRoute->setMethods($route->getMethods());
|
||||
$this->routeCollection->add($routeIdentifier, $symfonyRoute);
|
||||
foreach ($aliases as $aliasName) {
|
||||
$this->routeCollection->addAlias($aliasName, $routeIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
public function addRouteCollection(RouteCollection $routeCollection): void
|
||||
{
|
||||
$this->routeCollection->addCollection($routeCollection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all registered routes, only use in UriBuilder. Does not care about aliases,
|
||||
* so be careful with using this method.
|
||||
*/
|
||||
public function getRoutes(): \Iterator
|
||||
{
|
||||
return $this->routeCollection->getIterator();
|
||||
}
|
||||
|
||||
public function hasRoute(string $routeName): bool
|
||||
{
|
||||
return $this->routeCollection->get($routeName) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a route by its identifier, or null if nothing found.
|
||||
*
|
||||
* @return SymfonyRoute|Route|null
|
||||
*/
|
||||
public function getRoute(string $routeName)
|
||||
{
|
||||
return $this->routeCollection->get($routeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal only use in Core, this should not be exposed
|
||||
*/
|
||||
public function getRouteCollection(): RouteCollection
|
||||
{
|
||||
return $this->routeCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to match a URL path with a set of routes.
|
||||
*
|
||||
* @param string $pathInfo The path info to be parsed
|
||||
* @return Route the first Route object found
|
||||
* @throws ResourceNotFoundException If the resource could not be found
|
||||
*/
|
||||
public function match($pathInfo): Route
|
||||
{
|
||||
foreach ($this->routeCollection->getIterator() as $routeIdentifier => $route) {
|
||||
// This check is done in a simple way as there are no parameters yet (get parameters only)
|
||||
if ($route->getPath() === $pathInfo) {
|
||||
return Route::fromSymfonyRoute($route, $routeIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
throw new ResourceNotFoundException('The requested resource "' . $pathInfo . '" was not found.', 1425389240);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches a PSR-7 Request and returns a RouteResult with parameters and the resolved route.
|
||||
*/
|
||||
public function matchResult(ServerRequestInterface $request): RouteResult
|
||||
{
|
||||
$path = $this->backendEntryPointResolver->getBackendRoutePath($request);
|
||||
if ($path === null) {
|
||||
throw new ResourceNotFoundException('The requested resource "' . $request->getUri()->getPath() . '" does not contain a known backend route prefix.', 1704787661);
|
||||
}
|
||||
|
||||
if ($path === '' || $path === '/' || $path === '/index.php') {
|
||||
// Allow the login page to be displayed if routing is not used and on index.php
|
||||
// (consolidate RouteDispatcher::evaluateReferrer() when changing 'login' to something different)
|
||||
$path = '/login';
|
||||
}
|
||||
$requestContext = $this->requestContextFactory->fromBackendRequest($request);
|
||||
try {
|
||||
$result = (new UrlMatcher($this->routeCollection, $requestContext))->match($path);
|
||||
$matchedSymfonyRoute = $this->routeCollection->get($result['_route']);
|
||||
if ($matchedSymfonyRoute === null) {
|
||||
throw new ResourceNotFoundException('The requested resource "' . $path . '" was not found.', 1607596900);
|
||||
}
|
||||
} catch (\Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
|
||||
throw new MethodNotAllowedException($e->getAllowedMethods(), 1612649842, $e);
|
||||
} catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
|
||||
throw new ResourceNotFoundException('The requested resource "' . $path . '" was not found.', 1612649840);
|
||||
}
|
||||
$route = Route::fromSymfonyRoute($matchedSymfonyRoute, $result['_route']);
|
||||
unset($result['_route']);
|
||||
return new RouteResult($route, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to match a URI against the registered routes.
|
||||
* Use ->matchResult() instead, as this method will be deprecated in the future.
|
||||
*
|
||||
* @return Route the first Route object found
|
||||
*/
|
||||
public function matchRequest(ServerRequestInterface $request): Route
|
||||
{
|
||||
return $this->matchResult($request)->getRoute();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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\Backend\Routing;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class RouterConfigurationEvent
|
||||
{
|
||||
public function __construct(
|
||||
public Router $router,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?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\Backend\Routing;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class RouterConfigurator
|
||||
{
|
||||
public function __construct(
|
||||
#[Autowire(service: 'backend.routes', lazy: true)]
|
||||
private \ArrayObject $backendRoutes,
|
||||
#[Autowire(service: 'cache.core')]
|
||||
private PhpFrontend $coreCache,
|
||||
#[Autowire(expression: 'service("package-dependent-cache-identifier").withPrefix("BackendRoutes").toString()')]
|
||||
private string $cacheIdentifier,
|
||||
private EventDispatcherInterface $eventDispatcher,
|
||||
) {}
|
||||
|
||||
public function __invoke(Router $router): void
|
||||
{
|
||||
$routesFromPackages = $this->coreCache->require($this->cacheIdentifier);
|
||||
if ($routesFromPackages === false) {
|
||||
$routesFromPackages = $this->backendRoutes->getArrayCopy();
|
||||
$this->coreCache->set($this->cacheIdentifier, 'return ' . var_export($routesFromPackages, true) . ';');
|
||||
}
|
||||
|
||||
foreach ($routesFromPackages as $name => $options) {
|
||||
$path = $options['path'];
|
||||
$methods = $options['methods'] ?? [];
|
||||
$aliases = $options['aliases'] ?? [];
|
||||
unset($options['path'], $options['methods'], $options['aliases']);
|
||||
$route = new Route($path, $options);
|
||||
if ($methods !== []) {
|
||||
$route->setMethods($methods);
|
||||
}
|
||||
$router->addRoute($name, $route, $aliases);
|
||||
}
|
||||
|
||||
// Add routes from all modules
|
||||
$this->eventDispatcher->dispatch(new RouterConfigurationEvent($router));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Backend\Routing;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGenerator;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Routing\RequestContext;
|
||||
use Symfony\Component\Routing\Route as SymfonyRoute;
|
||||
use TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException;
|
||||
use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException;
|
||||
use TYPO3\CMS\Backend\Routing\Exception\RouteTypeNotAllowedException;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
|
||||
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
|
||||
use TYPO3\CMS\Core\Http\Error\MethodNotAllowedException;
|
||||
use TYPO3\CMS\Core\Http\ServerRequestFactory;
|
||||
use TYPO3\CMS\Core\Http\Uri;
|
||||
use TYPO3\CMS\Core\Routing\RequestContextFactory;
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
|
||||
/**
|
||||
* Main UrlGenerator for creating URLs for the Backend. Generates a URL based on
|
||||
* an identifier defined by Configuration/Backend/Routes.php of an extension,
|
||||
* and adds some more parameters to the URL.
|
||||
*
|
||||
* Currently only available and useful when called from Router->generate() as the information
|
||||
* about possible routes needs to be handed over.
|
||||
*/
|
||||
class UriBuilder implements SingletonInterface
|
||||
{
|
||||
/**
|
||||
* Generates an absolute URL
|
||||
*/
|
||||
public const ABSOLUTE_URL = 'url';
|
||||
|
||||
/**
|
||||
* Generates an absolute path
|
||||
*/
|
||||
public const ABSOLUTE_PATH = 'absolute';
|
||||
|
||||
/**
|
||||
* Generates an absolute url for URL sharing
|
||||
*/
|
||||
public const SHAREABLE_URL = 'share';
|
||||
|
||||
/**
|
||||
* @var array<non-empty-string, UriInterface>
|
||||
*/
|
||||
protected array $generated = [];
|
||||
|
||||
protected ?RequestContext $requestContext = null;
|
||||
|
||||
/**
|
||||
* Loads the router to fetch the available routes from the Router to be used for generating routes
|
||||
*/
|
||||
public function __construct(
|
||||
protected readonly Router $router,
|
||||
protected readonly FormProtectionFactory $formProtectionFactory,
|
||||
protected readonly RequestContextFactory $requestContextFactory
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function setRequestContext(RequestContext $requestContext): void
|
||||
{
|
||||
$this->requestContext = $requestContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a URL or path for a specific route based on the given route.
|
||||
* Currently used to link to the current script, it is encouraged to use "buildUriFromRoute" if possible.
|
||||
*
|
||||
* If there is no route with the given name, the generator throws the RouteNotFoundException.
|
||||
*
|
||||
* @param string $pathInfo The path to the route
|
||||
* @param array $parameters An array of parameters
|
||||
* @param string $referenceType The type of reference to be generated (one of the constants)
|
||||
* @return UriInterface The generated Uri
|
||||
* @throws RouteNotFoundException If the named route doesn't exist
|
||||
* @throws ResourceNotFoundException If no route matches the path info
|
||||
*/
|
||||
public function buildUriFromRoutePath($pathInfo, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
|
||||
{
|
||||
$route = $this->router->match($pathInfo);
|
||||
return $this->buildUriFromRoute($route->getOption('_identifier'), $parameters, $referenceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a link to a page with a route targetted as a redirect, if a "deep link" is possible.
|
||||
* Currently works just fine for URLs built for "main" and "login" pages.
|
||||
*
|
||||
* @param RouteRedirect|null $redirect
|
||||
* @throws RouteNotFoundException
|
||||
* @internal this is experimental API used for creating logins to redirect to a different route
|
||||
*/
|
||||
public function buildUriWithRedirect(string $name, array $parameters = [], ?RouteRedirect $redirect = null, string $referenceType = self::ABSOLUTE_PATH): UriInterface
|
||||
{
|
||||
if ($redirect === null) {
|
||||
return $this->buildUriFromRoute($name, $parameters, $referenceType);
|
||||
}
|
||||
|
||||
try {
|
||||
$redirect->resolve($this->router);
|
||||
} catch (RouteNotFoundException|RouteTypeNotAllowedException|MethodNotAllowedException $e) {
|
||||
return $this->buildUriFromRoute($name, $parameters, $referenceType);
|
||||
}
|
||||
$parameters['redirect'] = $redirect->getName();
|
||||
if ($redirect->hasParameters()) {
|
||||
$parameters['redirectParams'] = $redirect->getFormattedParameters();
|
||||
}
|
||||
return $this->buildUriFromRoute($name, $parameters, $referenceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* A shorthand function to link to e.g. the same as in the current request, so the internal attribute "route"
|
||||
* is properly covered and does not need to be exposed all the time.
|
||||
*/
|
||||
public function buildUriFromRequest(ServerRequestInterface $request, array $parameters = [], string $referenceType = self::ABSOLUTE_PATH): UriInterface
|
||||
{
|
||||
$route = $request->getAttribute('route');
|
||||
if (!$route instanceof Route && !$route instanceof SymfonyRoute) {
|
||||
throw new RouteNotFoundException('No route object was given inside the request object', 1691423325);
|
||||
}
|
||||
return $this->buildUriFromRoute($route->getOption('_identifier'), $parameters, $referenceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a URL or path for a specific route based on the given parameters.
|
||||
* When the route is configured with "access=public" then the token generation is left out.
|
||||
*
|
||||
* If there is no route with the given name, the generator throws the RouteNotFoundException.
|
||||
*
|
||||
* @param string $name The name of the route
|
||||
* @param array $parameters An array of parameters
|
||||
* @param string $referenceType The type of reference to be generated (one of the constants)
|
||||
* @return UriInterface The generated Uri
|
||||
* @throws RouteNotFoundException If the named route doesn't exist
|
||||
*/
|
||||
public function buildUriFromRoute($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
|
||||
{
|
||||
$cacheIdentifier = 'route' . $name . serialize($parameters) . $referenceType;
|
||||
if (isset($this->generated[$cacheIdentifier])) {
|
||||
return $this->generated[$cacheIdentifier];
|
||||
}
|
||||
|
||||
$route = $this->router->getRoute((string)$name);
|
||||
if ($route === null) {
|
||||
throw new RouteNotFoundException('Unable to generate a URL for the named route "' . $name . '" because this route was not found.', 1476050190);
|
||||
}
|
||||
|
||||
$parameters = array_merge(
|
||||
$route->getOption('parameters') ?? [],
|
||||
$parameters
|
||||
);
|
||||
|
||||
// If the route is not shareable and doesn't have the "public" option set, a token must be generated.
|
||||
if ($referenceType !== self::SHAREABLE_URL && (!$route->hasOption('access') || $route->getOption('access') !== 'public')) {
|
||||
$parameters = [
|
||||
'token' => $this->formProtectionFactory->createForType('backend')->generateToken('route', $name),
|
||||
] + $parameters;
|
||||
}
|
||||
|
||||
$this->generated[$cacheIdentifier] = $this->buildUri($name, $parameters, (string)$referenceType);
|
||||
return $this->generated[$cacheIdentifier];
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method building a Uri object, merging the GET parameters array into a flat queryString
|
||||
*
|
||||
* @param string $routeName The route name in the collection
|
||||
* @param array $parameters An array of GET parameters
|
||||
* @param string $referenceType The type of reference to be generated (one of the constants)
|
||||
*/
|
||||
protected function buildUri(string $routeName, array $parameters, string $referenceType): UriInterface
|
||||
{
|
||||
if (isset($this->requestContext)) {
|
||||
$requestContext = $this->requestContext;
|
||||
} elseif (isset($GLOBALS['TYPO3_REQUEST'])) {
|
||||
$requestContext = $this->requestContextFactory->fromBackendRequest($GLOBALS['TYPO3_REQUEST']);
|
||||
} elseif (!Environment::isCli()) {
|
||||
$requestContext = $this->requestContextFactory->fromBackendRequest(ServerRequestFactory::fromGlobals()->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE));
|
||||
} else {
|
||||
$requestContext = new RequestContext('/typo3/');
|
||||
}
|
||||
$urlGenerator = new UrlGenerator($this->router->getRouteCollection(), $requestContext);
|
||||
$url = $urlGenerator->generate($routeName, $parameters, $referenceType === self::ABSOLUTE_PATH ? UrlGeneratorInterface::ABSOLUTE_PATH : UrlGeneratorInterface::ABSOLUTE_URL);
|
||||
return new Uri($url);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user