Completed
Push — master ( 02d379...1ee726 )
by
unknown
15:15
created

AbstractEnhancer::overrideValuesByAspect()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types = 1);
3
4
namespace TYPO3\CMS\Core\Routing\Enhancer;
5
6
/*
7
 * This file is part of the TYPO3 CMS project.
8
 *
9
 * It is free software; you can redistribute it and/or modify it under
10
 * the terms of the GNU General Public License, either version 2
11
 * of the License, or any later version.
12
 *
13
 * For the full copyright and license information, please read the
14
 * LICENSE.txt file that was distributed with this source code.
15
 *
16
 * The TYPO3 project - inspiring people to share!
17
 */
18
19
use TYPO3\CMS\Core\Routing\Aspect\AspectInterface;
20
use TYPO3\CMS\Core\Routing\Aspect\ModifiableAspectInterface;
21
use TYPO3\CMS\Core\Routing\Route;
22
23
/**
24
 * Abstract Enhancer, useful for custom enhancers
25
 */
26
abstract class AbstractEnhancer implements EnhancerInterface
27
{
28
    /**
29
     * @var AspectInterface[]
30
     */
31
    protected $aspects = [];
32
33
    /**
34
     * @var VariableProcessor
35
     */
36
    protected $variableProcessor;
37
38
    /**
39
     * @param Route $route
40
     * @param AspectInterface[] $aspects
41
     * @param string|null $namespace
42
     */
43
    protected function applyRouteAspects(Route $route, array $aspects, string $namespace = null)
44
    {
45
        if (empty($aspects)) {
46
            return;
47
        }
48
        $aspects = $this->getVariableProcessor()
49
            ->deflateKeys($aspects, $namespace, $route->getArguments());
50
        $route->setAspects($aspects);
51
    }
52
53
    /**
54
     * @param Route $route
55
     * @param array $requirements
56
     * @param string|null $namespace
57
     */
58
    protected function applyRequirements(Route $route, array $requirements, string $namespace = null)
59
    {
60
        if (empty($requirements)) {
61
            return;
62
        }
63
64
        $requirements = $this->getVariableProcessor()
65
            ->deflateKeys($requirements, $namespace, $route->getArguments());
66
        // only keep requirements that are actually part of the current route path
67
        $requirements = $this->filterValuesByPathVariables($route, $requirements);
68
        // In general requirements are not considered when having aspects defined for a
69
        // given variable name and thus aspects are more specific and take precedence:
70
        // + since requirements in classic Symfony focus on internal arguments
71
        //   and aspects define a mapping between URL part (e.g. 'some-example-news')
72
        //   and the corresponding internal argument (e.g. 'tx_news_pi1[news]=123')
73
        // + thus, the requirement definition cannot be used for resolving and generating
74
        //   a route at the same time (it would have to be e.g. `[\w_._]+` AND `\d+`)
75
        // This call overrides default Symfony regular expression pattern `[^/]+` (see
76
        // `RouteCompiler::compilePattern()`) with `.+` to allow URI parameters like
77
        // `some-example-news/january` as well.
78
        $requirements = $this->overrideValuesByAspect($route, $requirements, '.+');
79
80
        if (!empty($requirements)) {
81
            $route->setRequirements($requirements);
82
        }
83
    }
84
85
    /**
86
     * Only keeps values that actually have been used as variables in route path.
87
     *
88
     * + routePath: '/list/{page}' ('page' used as variable in route path)
89
     * + values: ['entity' => 'entity...', 'page' => 'page...', 'other' => 'other...']
90
     * + result: ['page' => 'page...']
91
     *
92
     * @param Route $route
93
     * @param array $values
94
     * @return array
95
     */
96
    protected function filterValuesByPathVariables(Route $route, array $values): array
97
    {
98
        return array_intersect_key(
99
            $values,
100
            array_flip($route->compile()->getPathVariables())
101
        );
102
    }
103
104
    /**
105
     * Overrides items having an aspect definition with a given
106
     * $overrideValue in target $values array.
107
     *
108
     * @param Route $route
109
     * @param array $values
110
     * @param string $overrideValue
111
     * @return array
112
     */
113
    protected function overrideValuesByAspect(Route $route, array $values, string $overrideValue): array
114
    {
115
        foreach (array_keys($route->getAspects()) as $variableName) {
116
            $values[$variableName] = $overrideValue;
117
        }
118
        return $values;
119
    }
120
121
    /**
122
     * Modify the route path to add the variable names with the aspects, e.g.
123
     *
124
     * + `/{locale_modifier}/{product_title}`  -> `/products/{product_title}`
125
     * + `/{!locale_modifier}/{product_title}` -> `/products/{product_title}`
126
     *
127
     * @param string $routePath
128
     * @return string
129
     */
130
    protected function modifyRoutePath(string $routePath): string
131
    {
132
        $substitutes = [];
133
        foreach ($this->aspects as $variableName => $aspect) {
134
            if (!$aspect instanceof ModifiableAspectInterface) {
135
                continue;
136
            }
137
            $value = $aspect->modify();
138
            if ($value !== null) {
139
                $substitutes['{' . $variableName . '}'] = $value;
140
                $substitutes['{!' . $variableName . '}'] = $value;
141
            }
142
        }
143
        return str_replace(
144
            array_keys($substitutes),
145
            array_values($substitutes),
146
            $routePath
147
        );
148
    }
149
150
    /**
151
     * Retrieves type from processed route and modifies remaining query parameters.
152
     *
153
     * @param Route $route
154
     * @param array $remainingQueryParameters reference to remaining query parameters
155
     * @return string
156
     */
157
    protected function resolveType(Route $route, array &$remainingQueryParameters): string
158
    {
159
        $type = $remainingQueryParameters['type'] ?? 0;
160
        $decoratedParameters = $route->getOption('_decoratedParameters');
161
        if (isset($decoratedParameters['type'])) {
162
            $type = $decoratedParameters['type'];
163
            unset($decoratedParameters['type']);
164
            $remainingQueryParameters = array_replace_recursive(
165
                $remainingQueryParameters,
166
                $decoratedParameters
0 ignored issues
show
Bug introduced by
$decoratedParameters of type string is incompatible with the type array expected by parameter $array1 of array_replace_recursive(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

166
                /** @scrutinizer ignore-type */ $decoratedParameters
Loading history...
167
            );
168
        }
169
        return (string)$type;
170
    }
171
172
    /**
173
     * @return VariableProcessor
174
     */
175
    protected function getVariableProcessor(): VariableProcessor
176
    {
177
        if (isset($this->variableProcessor)) {
178
            return $this->variableProcessor;
179
        }
180
        return $this->variableProcessor = new VariableProcessor();
181
    }
182
183
    /**
184
     * {@inheritdoc}
185
     */
186
    public function setAspects(array $aspects): void
187
    {
188
        $this->aspects = $aspects;
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194
    public function getAspects(): array
195
    {
196
        return $this->aspects;
197
    }
198
}
199