Passed
Pull Request — master (#2755)
by Rafael
03:55
created

SolrFacetMaskAndCombineEnhancer::getVariant()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 17
ccs 0
cts 16
cp 0
rs 9.8333
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
1
<?php
2
namespace ApacheSolrForTypo3\Solr\Routing\Enhancer;
3
4
use ApacheSolrForTypo3\Solr\Routing\RoutingService;
5
use ApacheSolrForTypo3\Solr\Utility\RoutingUtility;
6
use TYPO3\CMS\Core\Routing\Enhancer\AbstractEnhancer;
7
use TYPO3\CMS\Core\Routing\Enhancer\RoutingEnhancerInterface;
8
use TYPO3\CMS\Core\Routing\Route;
9
use TYPO3\CMS\Core\Routing\RouteCollection;
10
use TYPO3\CMS\Core\Utility\GeneralUtility;
11
12
/*
13
 * This file is part of the TYPO3 CMS project.
14
 *
15
 * It is free software; you can redistribute it and/or modify it under
16
 * the terms of the GNU General Public License, either version 2
17
 * of the License, or any later version.
18
 *
19
 * For the full copyright and license information, please read the
20
 * LICENSE.txt file that was distributed with this source code.
21
 *
22
 * The TYPO3 project - inspiring people to share!
23
 */
24
25
class SolrFacetMaskAndCombineEnhancer extends AbstractEnhancer implements RoutingEnhancerInterface, SolrRouteEnhancerInterface
26
{
27
    /**
28
     * @var array
29
     */
30
    protected $configuration;
31
32
    /**
33
     * @var string
34
     */
35
    protected $namespace;
36
37
    public function __construct(array $configuration)
38
    {
39
        $this->configuration = $configuration;
40
        $this->namespace = $this->configuration['extensionKey'] ?? 'tx_solr';
41
    }
42
43
    /**
44
     * {@inheritdoc}
45
     */
46
    public function enhanceForMatching(RouteCollection $collection): void
47
    {
48
        /** @var Route $defaultPageRoute */
49
        $defaultPageRoute = $collection->get('default');
50
        $variant = $this->getVariant($defaultPageRoute, $this->configuration);
51
        $collection->add(
52
            'enhancer_' . $this->namespace . spl_object_hash($variant),
53
            $variant
54
        );
55
    }
56
57
    /**
58
     * Builds a variant of a route based on the given configuration.
59
     *
60
     * @param Route $defaultPageRoute
61
     * @param array $configuration
62
     * @return Route
63
     */
64
    protected function getVariant(Route $defaultPageRoute, array $configuration): Route
65
    {
66
        $arguments = $configuration['_arguments'] ?? [];
67
        unset($configuration['_arguments']);
68
69
        $variableProcessor = $this->getVariableProcessor();
70
        $routePath = $this->modifyRoutePath($configuration['routePath']);
71
        $routePath = $variableProcessor->deflateRoutePath($routePath, $this->namespace, $arguments);
72
        $variant = clone $defaultPageRoute;
73
        $variant->setPath(rtrim($variant->getPath(), '/') . '/' . ltrim($routePath, '/'));
74
        $variant->addOptions(['_enhancer' => $this, '_arguments' => $arguments]);
75
        $variant->setDefaults(
76
            $variableProcessor->deflateKeys($this->configuration['defaults'] ?? [], $this->namespace, $arguments)
77
        );
78
        $this->applyRouteAspects($variant, $this->aspects ?? [], $this->namespace);
79
        $this->applyRequirements($variant, $this->configuration['requirements'] ?? [], $this->namespace);
80
        return $variant;
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86
    public function enhanceForGeneration(RouteCollection $collection, array $parameters): void
87
    {
88
        // No parameter for this namespace given, so this route does not fit the requirements
89
        if (!is_array($parameters[$this->namespace])) {
90
            return;
91
        }
92
        /** @var Route $defaultPageRoute */
93
        $defaultPageRoute = $collection->get('default');
94
        $variant = $this->getVariant($defaultPageRoute, $this->configuration);
95
        $compiledRoute = $variant->compile();
96
        $deflatedParameters = $this->deflateParameters($variant, $parameters);
97
        $deflatedParameters = $this->combineArrayParameters($deflatedParameters);
98
        $deflatedParameters = $this->inflateUnprocessedVariables($variant, $deflatedParameters);
99
        // TODO: Implode query parameters if configured!
100
        $deflatedParameters = $this->replaceVariableWithHash($variant, $deflatedParameters);
101
        $variant->setOption('deflatedParameters', $deflatedParameters);
102
        $variables = array_flip($compiledRoute->getPathVariables());
103
        $mergedParams = array_replace($variant->getDefaults(), $deflatedParameters);
104
        // all params must be given, otherwise we exclude this variant
105
        if ($diff = array_diff_key($variables, $mergedParams)) {
0 ignored issues
show
Unused Code introduced by
The assignment to $diff is dead and can be removed.
Loading history...
106
            return;
107
        }
108
109
        $collection->add('enhancer_' . $this->namespace . spl_object_hash($variant), $variant);
110
    }
111
112
    /**
113
     * This method combine arguments into a single string
114
     * It is possible to configure a path segment that contains more than one value.
115
     * This method build one single string out of it.
116
     *
117
     * @param array $parameters
118
     * @return array
119
     */
120
    protected function combineArrayParameters(array $parameters = []): array
121
    {
122
        $parametersCombined = [];
123
        foreach ($this->configuration['_arguments'] as $fieldName => $fieldPath) {
124
            $combinedKey = RoutingUtility::deflateString($fieldPath, $this->namespace);
125
            $parameterPattern = RoutingUtility::deflateString($fieldPath, $this->namespace);
126
            $elements = explode('-', $parameterPattern);
127
            array_pop($elements);
128
            $parameterPattern = implode('-', $elements);
129
            $parameterPattern .= '__\d+';
130
131
            // @TODO: Here the behaviour changed. The whole functionality need to be tested
132
            $pathElements = explode('-', $fieldPath);
133
            $facetTypeToHandle = array_pop($pathElements);
134
135
            if (empty($facetTypeToHandle)) {
136
                continue;
137
            }
138
            foreach ($parameters as $parameterName => $parameterValue) {
139
                // Skip parameters that we don't care for
140
                if (!preg_match('/' . $parameterPattern . '/', $parameterName)) {
141
                    $parametersCombined[$parameterName] = $parameterValue;
142
                    continue;
143
                }
144
145
                if (!array_key_exists($combinedKey, $parametersCombined)) {
146
                    $parametersCombined[$combinedKey] = [];
147
                }
148
149
                $parameterNameNew = $parameterName;
150
                $parameterValueNew = $parameterValue;
151
152
                // Placeholder for cached URIs (type is the last part of the parameter value)
153
                if (substr($parameterValue, 0, 3) === '###') {
154
                    $facetFieldElements = explode(':', $parameterValue);
155
                    $facetField = array_pop($facetFieldElements);
156
                    $facetField = substr($facetField, 0, strlen($facetField) - 3);
157
158
                    $facetValue = null;
159
                    if (strpos($facetField, '%3A') !== false) {
160
                        [$facetField, $facetValue] = explode('%3A', $facetField, 2);
161
                    }
162
163
                    if ($facetField === $facetTypeToHandle) {
164
                        $parameterNameNew = $combinedKey;
165
                        if ($facetValue !== null) {
166
                            $parameterValueNew = $facetValue;
167
                        }
168
                    }
169
                } else {
170
                    [$facetField, $facetValue] = explode(':', $parameterValue, 2);
171
                    if (substr($facetValue, 0, mb_strlen($facetField) + 1) === $facetField . ':') {
172
                        [$facetField, $facetValue] = explode(':', $facetValue, 2);
173
                    }
174
                    if ($facetField === $facetTypeToHandle) {
175
                        $parameterNameNew = $combinedKey;
176
                        $parameterValueNew = $facetValue;
177
                    }
178
                }
179
                if (is_array($parametersCombined[$parameterNameNew])) {
180
                    $parametersCombined[$parameterNameNew][] = $parameterValueNew;
181
                } else {
182
                    $parametersCombined[$parameterNameNew] = $parameterValueNew;
183
                }
184
            }
185
186
            if (isset($parametersCombined[$combinedKey]) && is_array($parametersCombined[$combinedKey])) {
187
                $parametersCombined[$combinedKey] = $this->getRoutingService()->facetsToString(
188
                    $parametersCombined[$combinedKey]
189
                );
190
            }
191
        }
192
193
        return $parametersCombined;
194
    }
195
196
    /**
197
     * We need to convert our internal names by hand into hashes.
198
     *
199
     * This needs to be done, because we not exactly configure a path inside of the site configuration.
200
     * What we are configure is a placeholder contains information, what we should process
201
     *
202
     * @param Route $route
203
     * @param array $parameters
204
     * @return array
205
     */
206
    protected function replaceVariableWithHash(Route $route, array $parameters = []): array
207
    {
208
        $routPath = $this->configuration['routePath'];
209
        $pathElements = explode('/', $routPath);
210
        // Compiles path token are in reversed order
211
        $pathElements = array_reverse($pathElements);
212
        $routeArguments = $route->getArguments();
213
        $pathTokens = $route->compile()->getTokens();
214
215
        $keepVariables = [];
216
217
        for ($i = 0; $i < count($pathTokens); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
218
            // wee only looking for variables
219
            if ($pathTokens[$i][0] !== 'variable') {
220
                continue;
221
            }
222
223
            $pathVariable = $pathElements[$i];
224
            // Remove marker
225
            $pathVariable = substr($pathVariable, 1, strlen($pathVariable) - 1);
226
            $pathVariable = substr($pathVariable, 0, strlen($pathVariable) - 1);
227
228
            // Skip if we could not resolve the variable
229
            if (!isset($routeArguments[$pathVariable])) {
230
                continue;
231
            }
232
233
            $parameterName = RoutingUtility::deflateString(
234
                $routeArguments[$pathVariable],
235
                $this->namespace
236
            );
237
238
            // Skip processing if variable not listed inside of given parameters
239
            if (!isset($parameters[$parameterName])) {
240
                continue;
241
            }
242
243
            $parameters[$pathTokens[$i][3]] = $parameters[$parameterName];
244
            $keepVariables[] = $pathTokens[$i][3];
245
            unset($parameters[$parameterName]);
246
        }
247
248
        return $parameters;
249
    }
250
251
    /**
252
     * This method inflates some variables back, because if a route has deflated variables
253
     * they are used in the upcoming process.
254
     *
255
     * @see \TYPO3\CMS\Core\Routing\PageRouter::generateUri
256
     *
257
     * @param Route $variant
258
     * @param array $deflatedParameters
259
     * @return array
260
     */
261
    protected function inflateUnprocessedVariables(Route $variant, array $deflatedParameters): array
262
    {
263
        $mixedVariables = [];
264
        $variablesToHandle = [];
265
        foreach ($variant->getArguments() as $argumentKey => $argumentPath) {
266
            $variablesToHandle[] = RoutingUtility::deflateString($argumentPath, $this->namespace);
267
        }
268
269
        foreach ($deflatedParameters as $argumentKey => $argumentPath) {
270
            if (in_array($argumentKey, $variablesToHandle)) {
271
                $mixedVariables[$argumentKey] = $deflatedParameters[$argumentKey];
272
            } else {
273
                $elements = explode('__', $argumentKey);
274
                $elements[] = $deflatedParameters[$argumentKey];
275
                $data = $this->inflateQueryParams($elements);
276
277
                $mixedVariables = array_merge_recursive($mixedVariables, $data);
278
            }
279
        }
280
281
        return $mixedVariables;
282
    }
283
284
    /**
285
     * Inflate deflated query parameters
286
     *
287
     * @param array $elements
288
     * @return array
289
     */
290
    protected function inflateQueryParams(array $elements = []): array
291
    {
292
        $result = [];
293
        if (count($elements) > 2) {
294
            $key = array_shift($elements);
295
            $result[$key] = $this->inflateQueryParams($elements);
296
        } elseif (count($elements) == 2) {
297
            $result[$elements[0]] = $elements[1];
298
        }
299
300
        return $result;
301
    }
302
303
    /**
304
     * Deflate query parameters
305
     *
306
     * @param Route $route
307
     * @param array $parameters
308
     * @return array
309
     */
310
    protected function deflateParameters(Route $route, array $parameters): array
311
    {
312
        return $this->getVariableProcessor()->deflateNamespaceParameters(
313
            $parameters,
314
            $this->namespace,
315
            $route->getArguments()
316
        );
317
    }
318
319
    /**
320
     * @return RoutingService
321
     */
322
    protected function getRoutingService(): RoutingService
323
    {
324
        return GeneralUtility::makeInstance(
325
            RoutingService::class,
326
            $this->configuration['solr'],
327
            (string)$this->configuration['extensionKey']
328
        )->withPathArguments($this->configuration['_arguments']);
329
    }
330
}
331