Passed
Push — master ( 4751e9...ec72de )
by Rafael
38:16
created

combineArrayParameters()   C

Complexity

Conditions 15
Paths 86

Size

Total Lines 74
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 240

Importance

Changes 0
Metric Value
eloc 46
dl 0
loc 74
ccs 0
cts 62
cp 0
rs 5.9166
c 0
b 0
f 0
cc 15
nc 86
nop 1
crap 240

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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