| Current Path : /var/www/surf/TYPO3/vendor/typo3/cms-extbase/Classes/Routing/ |
| Current File : /var/www/surf/TYPO3/vendor/typo3/cms-extbase/Classes/Routing/ExtbasePluginEnhancer.php |
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Routing;
use TYPO3\CMS\Core\Routing\Enhancer\PluginEnhancer;
use TYPO3\CMS\Core\Routing\Route;
use TYPO3\CMS\Core\Routing\RouteCollection;
/**
* Allows to have a plugin with multiple controllers + actions for one specific plugin that has a namespace.
*
* A typical configuration looks like this:
*
* routeEnhancers:
* BlogExample:
* type: Extbase
* extension: BlogExample
* plugin: Pi1
* routes:
* - { routePath: '/blog/{page}', _controller: 'Blog::list', _arguments: {'page': '@widget_0/currentPage'} }
* - { routePath: '/blog/{slug}', _controller: 'Blog::detail' }
* requirements:
* page: '[0-9]+'
* slug: '.*'
*/
class ExtbasePluginEnhancer extends PluginEnhancer
{
/**
* @var array
*/
protected $routesOfPlugin;
public function __construct(array $configuration)
{
parent::__construct($configuration);
$this->routesOfPlugin = $this->configuration['routes'] ?? [];
// Only set the namespace if the plugin+extension keys are given. This allows to also use "namespace" property
// instead from the parent constructor.
if (
$this->namespace === ''
&& isset($this->configuration['extension'])
&& isset($this->configuration['plugin'])
) {
$extensionName = $this->configuration['extension'];
$pluginName = $this->configuration['plugin'];
$extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionName)));
$pluginSignature = strtolower($extensionName . '_' . $pluginName);
$this->namespace = 'tx_' . $pluginSignature;
}
return;
}
/**
* {@inheritdoc}
*/
public function enhanceForMatching(RouteCollection $collection): void
{
$i = 0;
/** @var Route $defaultPageRoute */
$defaultPageRoute = $collection->get('default');
foreach ($this->routesOfPlugin as $configuration) {
$route = $this->getVariant($defaultPageRoute, $configuration);
$collection->add($this->namespace . '_' . $i++, $route);
}
}
/**
* {@inheritdoc}
*/
protected function getVariant(Route $defaultPageRoute, array $configuration): Route
{
$arguments = $configuration['_arguments'] ?? [];
unset($configuration['_arguments']);
$variableProcessor = $this->getVariableProcessor();
$routePath = $this->modifyRoutePath($configuration['routePath']);
$routePath = $variableProcessor->deflateRoutePath($routePath, $this->namespace, $arguments);
unset($configuration['routePath']);
$options = array_merge($defaultPageRoute->getOptions(), ['_enhancer' => $this, 'utf8' => true, '_arguments' => $arguments]);
$route = new Route(rtrim($defaultPageRoute->getPath(), '/') . '/' . ltrim($routePath, '/'), [], [], $options);
$defaults = array_merge_recursive(
$defaultPageRoute->getDefaults(),
$variableProcessor->deflateKeys($this->configuration['defaults'] ?? [], $this->namespace, $arguments)
);
// only keep `defaults` that are actually used in `routePath`
$defaults = $this->filterValuesByPathVariables(
$route,
$defaults
);
// apply '_controller' to route defaults
$defaults = array_merge_recursive(
$defaults,
array_intersect_key($configuration, ['_controller' => true])
);
$route->setDefaults($defaults);
$this->applyRouteAspects($route, $this->aspects ?? [], $this->namespace);
$this->applyRequirements($route, $this->configuration['requirements'] ?? [], $this->namespace);
return $route;
}
/**
* {@inheritdoc}
*/
public function enhanceForGeneration(RouteCollection $collection, array $originalParameters): void
{
if (!is_array($originalParameters[$this->namespace] ?? null)) {
return;
}
// apply default controller and action names if not set in parameters
if (!$this->hasControllerActionValues($originalParameters[$this->namespace])
&& !empty($this->configuration['defaultController'])
) {
$this->applyControllerActionValues(
$this->configuration['defaultController'],
$originalParameters[$this->namespace],
true
);
}
$i = 0;
/** @var Route $defaultPageRoute */
$defaultPageRoute = $collection->get('default');
foreach ($this->routesOfPlugin as $configuration) {
$variant = $this->getVariant($defaultPageRoute, $configuration);
// The enhancer tells us: This given route does not match the parameters
if (!$this->verifyRequiredParameters($variant, $originalParameters)) {
continue;
}
$parameters = $originalParameters;
unset($parameters[$this->namespace]['action']);
unset($parameters[$this->namespace]['controller']);
$compiledRoute = $variant->compile();
// contains all given parameters, even if not used as variables in route
$deflatedParameters = $this->deflateParameters($variant, $parameters);
$variables = array_flip($compiledRoute->getPathVariables());
$mergedParams = array_replace($variant->getDefaults(), $deflatedParameters);
// all params must be given, otherwise we exclude this variant
// (it is allowed that $variables is empty - in this case variables are
// "given" implicitly through controller-action pair in `_controller`)
if (array_diff_key($variables, $mergedParams)) {
continue;
}
$variant->addOptions(['deflatedParameters' => $deflatedParameters]);
$collection->add($this->namespace . '_' . $i++, $variant);
}
}
/**
* A route has matched the controller/action combination, so ensure that these properties
* are set to tx_blogexample_pi1[controller] and tx_blogexample_pi1[action].
*
* @param array $parameters Actual parameter payload to be used
* @param array $internals Internal instructions (_route, _controller, ...)
*/
public function inflateParameters(array $parameters, array $internals = []): array
{
$parameters = $this->getVariableProcessor()
->inflateNamespaceParameters($parameters, $this->namespace);
$parameters[$this->namespace] = $parameters[$this->namespace] ?? [];
// Invalid if there is no controller given, so this enhancers does not do anything
if (empty($internals['_controller'] ?? null)) {
return $parameters;
}
$this->applyControllerActionValues(
$internals['_controller'],
$parameters[$this->namespace],
false
);
return $parameters;
}
/**
* Check if controller+action combination matches
*/
protected function verifyRequiredParameters(Route $route, array $parameters): bool
{
if (!is_array($parameters[$this->namespace])) {
return false;
}
if (!$route->hasDefault('_controller')) {
return false;
}
$controller = $route->getDefault('_controller');
[$controllerName, $actionName] = explode('::', $controller);
if (!isset($parameters[$this->namespace]['controller']) || $controllerName !== $parameters[$this->namespace]['controller']) {
return false;
}
if (!isset($parameters[$this->namespace]['action']) || $actionName !== $parameters[$this->namespace]['action']) {
return false;
}
return true;
}
/**
* Check if action and controller are not empty.
*/
protected function hasControllerActionValues(array $target): bool
{
return !empty($target['controller']) && !empty($target['action']);
}
/**
* Add controller and action parameters so they can be used later-on.
*
* @param array $target Reference to target array to be modified
* @param bool $tryUpdate Try updating action value - but only if controller value matches
*/
protected function applyControllerActionValues(string $controllerActionValue, array &$target, bool $tryUpdate = false)
{
if (!str_contains($controllerActionValue, '::')) {
return;
}
[$controllerName, $actionName] = explode('::', $controllerActionValue, 2);
// use default action name if controller matches
if ($tryUpdate && empty($target['action']) && $controllerName === ($target['controller'] ?? null)) {
$target['action'] = $actionName;
// use default controller name if action is defined (implies: non-default-controllers must be given)
} elseif ($tryUpdate && empty($target['controller']) && !empty($target['action'])) {
$target['controller'] = $controllerName;
// fallback and override
} else {
$target['controller'] = $controllerName;
$target['action'] = $actionName;
}
}
}