TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Routing\Enhancer;
|
||||
|
||||
use TYPO3\CMS\Core\Routing\Aspect\AspectInterface;
|
||||
use TYPO3\CMS\Core\Routing\Aspect\ModifiableAspectInterface;
|
||||
use TYPO3\CMS\Core\Routing\Route;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Abstract Enhancer, useful for custom enhancers
|
||||
*/
|
||||
abstract class AbstractEnhancer implements EnhancerInterface
|
||||
{
|
||||
/**
|
||||
* @var AspectInterface[]
|
||||
*/
|
||||
protected array $aspects = [];
|
||||
|
||||
/**
|
||||
* @var VariableProcessor|null
|
||||
*/
|
||||
protected $variableProcessor;
|
||||
|
||||
/**
|
||||
* @param AspectInterface[] $aspects
|
||||
* @param string|null $namespace
|
||||
*/
|
||||
protected function applyRouteAspects(Route $route, array $aspects, ?string $namespace = null)
|
||||
{
|
||||
if (empty($aspects)) {
|
||||
return;
|
||||
}
|
||||
$aspects = $this->getVariableProcessor()
|
||||
->deflateKeys($aspects, $namespace, $route->getArguments());
|
||||
$route->setAspects($aspects);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $namespace
|
||||
*/
|
||||
protected function applyRequirements(Route $route, array $requirements, ?string $namespace = null)
|
||||
{
|
||||
$requirements = $this->getVariableProcessor()
|
||||
->deflateKeys($requirements, $namespace, $route->getArguments());
|
||||
// only keep requirements that are actually part of the current route path
|
||||
$requirements = $this->filterValuesByPathVariables($route, $requirements);
|
||||
// Symfony's behavior on applying pattern for parameters just concerns values
|
||||
// to be passed either to URL or to internal parameters - they are always the
|
||||
// same, without any transformation.
|
||||
//
|
||||
// TYPO3 extends ("enhances") this behavior by making a difference between values
|
||||
// for generation (resulting in a URL) and matching (resulting in query parameters)
|
||||
// having the following implications and meaning:
|
||||
//
|
||||
// + since requirements in classic Symfony focus on parameters in URLs
|
||||
// and aspects define a mapping between URL part (e.g. 'some-example-news')
|
||||
// and the corresponding internal argument (e.g. 'tx_news_pi1[news]=123')
|
||||
// + thus, the requirement definition cannot be used for resolving and generating
|
||||
// a route at the same time (it would have to be e.g. `[\w_._]+` AND `\d+`)
|
||||
//
|
||||
// Symfony's default regular expression pattern `[^/]+` (see
|
||||
// `RouteCompiler::compilePattern()`) has to be overridden with `.+` to
|
||||
// allow URI parameters like `some-example-news/january` as well.
|
||||
//
|
||||
// Existing `requirements` for TYPO3 route enhancers are not modified, only those
|
||||
// that are not defined and would use Symfony's default pattern.
|
||||
$requirements = $this->defineValuesByAspect($route, $requirements, '.+');
|
||||
$route->setRequirements($requirements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies variables that are considered static (not having `&cHash=...` applied),
|
||||
* without having the demand to define a custom `StaticMappableAspectInterface`
|
||||
* to fake the behavior.
|
||||
*
|
||||
* However:
|
||||
* + in case there's an aspect defined for a variable, it will be skipped (aspects take precedence)
|
||||
* + in case not requirement is defined for a variable, it will be skipped (avoiding weak definitions)
|
||||
*
|
||||
* @param array<non-empty-string, bool> $staticVariables option values
|
||||
*/
|
||||
protected function applyStaticVariables(Route $route, array $staticVariables, ?string $namespace = null): void
|
||||
{
|
||||
// skip definitions that are not explicitly set to `true`
|
||||
$staticVariables = array_filter($staticVariables, static fn($definition) => $definition === true);
|
||||
$staticVariables = $this->getVariableProcessor()
|
||||
->deflateKeys($staticVariables, $namespace, $route->getArguments());
|
||||
// only keep static variables that are actually part of the current route path
|
||||
$staticVariables = $this->filterValuesByPathVariables($route, $staticVariables);
|
||||
// skip definitions that already have an aspect defined (aspects take precedence)
|
||||
$staticVariables = array_diff_key($staticVariables, $route->getAspects());
|
||||
// skip definitions that not have any requirement defined (avoiding weak definitions)
|
||||
$staticVariables = array_intersect_key($staticVariables, $route->getRequirements());
|
||||
$route->setOption('_static', $staticVariables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only keeps values that actually have been used as variables in route path.
|
||||
*
|
||||
* + routePath: '/list/{page}' ('page' used as variable in route path)
|
||||
* + values: ['entity' => 'entity...', 'page' => 'page...', 'other' => 'other...']
|
||||
* + result: ['page' => 'page...']
|
||||
*
|
||||
* @param Route $route
|
||||
* @param array $values
|
||||
*/
|
||||
protected function filterValuesByPathVariables(Route $route, array $values): array
|
||||
{
|
||||
return array_intersect_key(
|
||||
$values,
|
||||
array_flip($route->compile()->getPathVariables())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides items having an aspect definition with a given
|
||||
* $overrideValue in target $targetValue array.
|
||||
*/
|
||||
protected function overrideValuesByAspect(Route $route, array $values, string $targetValue): array
|
||||
{
|
||||
foreach (array_keys($route->getAspects()) as $variableName) {
|
||||
$values[$variableName] = $targetValue;
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define items having an aspect definition in case they are not defined
|
||||
* with a given $targetValue in target $targetValue array.
|
||||
*/
|
||||
protected function defineValuesByAspect(Route $route, array $values, string $targetValue): array
|
||||
{
|
||||
foreach (array_keys($route->getAspects()) as $variableName) {
|
||||
if (isset($values[$variableName])) {
|
||||
continue;
|
||||
}
|
||||
$values[$variableName] = $targetValue;
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the route path to add the variable names with the aspects, e.g.
|
||||
*
|
||||
* + `/{locale_modifier}/{product_title}` -> `/products/{product_title}`
|
||||
* + `/{!locale_modifier}/{product_title}` -> `/products/{product_title}`
|
||||
*
|
||||
* @param string $routePath
|
||||
*/
|
||||
protected function modifyRoutePath(string $routePath): string
|
||||
{
|
||||
$substitutes = [];
|
||||
foreach ($this->aspects as $variableName => $aspect) {
|
||||
if (!$aspect instanceof ModifiableAspectInterface) {
|
||||
continue;
|
||||
}
|
||||
$value = $aspect->modify();
|
||||
if ($value !== null) {
|
||||
$substitutes['{' . $variableName . '}'] = $value;
|
||||
$substitutes['{!' . $variableName . '}'] = $value;
|
||||
}
|
||||
}
|
||||
return str_replace(
|
||||
array_keys($substitutes),
|
||||
array_values($substitutes),
|
||||
$routePath
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves type from processed route and modifies remaining query parameters.
|
||||
*
|
||||
* @param array $remainingQueryParameters reference to remaining query parameters
|
||||
*/
|
||||
protected function resolveType(Route $route, array &$remainingQueryParameters): string
|
||||
{
|
||||
$type = $remainingQueryParameters['type'] ?? 0;
|
||||
$decoratedParameters = $route->getOption('_decoratedParameters');
|
||||
if (isset($decoratedParameters['type'])) {
|
||||
$type = $decoratedParameters['type'];
|
||||
unset($decoratedParameters['type']);
|
||||
$remainingQueryParameters = array_replace_recursive(
|
||||
$remainingQueryParameters,
|
||||
$decoratedParameters
|
||||
);
|
||||
}
|
||||
return (string)$type;
|
||||
}
|
||||
|
||||
protected function getVariableProcessor(): VariableProcessor
|
||||
{
|
||||
if (isset($this->variableProcessor)) {
|
||||
return $this->variableProcessor;
|
||||
}
|
||||
return $this->variableProcessor = GeneralUtility::makeInstance(VariableProcessor::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAspects(array $aspects): void
|
||||
{
|
||||
$this->aspects = $aspects;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAspects(): array
|
||||
{
|
||||
return $this->aspects;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user