1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
|
4
|
|
|
/* |
5
|
|
|
* This file is part of the TYPO3 CMS project. |
6
|
|
|
* |
7
|
|
|
* It is free software; you can redistribute it and/or modify it under |
8
|
|
|
* the terms of the GNU General Public License, either version 2 |
9
|
|
|
* of the License, or any later version. |
10
|
|
|
* |
11
|
|
|
* For the full copyright and license information, please read the |
12
|
|
|
* LICENSE.txt file that was distributed with this source code. |
13
|
|
|
* |
14
|
|
|
* The TYPO3 project - inspiring people to share! |
15
|
|
|
*/ |
16
|
|
|
|
17
|
|
|
namespace ApacheSolrForTypo3\Solr\Routing; |
18
|
|
|
|
19
|
|
|
use ApacheSolrForTypo3\Solr\Routing\Enhancer\SolrRouteEnhancerInterface; |
20
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
21
|
|
|
use Psr\Http\Message\UriInterface; |
22
|
|
|
use Psr\Log\LoggerAwareInterface; |
23
|
|
|
use Psr\Log\LoggerAwareTrait; |
24
|
|
|
use TYPO3\CMS\Core\Context\Context; |
25
|
|
|
use TYPO3\CMS\Core\Exception\SiteNotFoundException; |
26
|
|
|
use TYPO3\CMS\Core\Http\Uri; |
27
|
|
|
use TYPO3\CMS\Core\Routing\PageSlugCandidateProvider; |
28
|
|
|
use TYPO3\CMS\Core\Routing\SiteMatcher; |
29
|
|
|
use TYPO3\CMS\Core\Site\Entity\NullSite; |
30
|
|
|
use TYPO3\CMS\Core\Site\Entity\Site; |
31
|
|
|
use TYPO3\CMS\Core\Site\Entity\SiteInterface; |
32
|
|
|
use TYPO3\CMS\Core\Site\Entity\SiteLanguage; |
33
|
|
|
use TYPO3\CMS\Core\Site\SiteFinder; |
34
|
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* This service class bundles method required to process and manipulate routes. |
38
|
|
|
* |
39
|
|
|
* @author Lars Tode <[email protected]> |
40
|
|
|
*/ |
41
|
|
|
class RoutingService implements LoggerAwareInterface |
42
|
|
|
{ |
43
|
|
|
use LoggerAwareTrait; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Default plugin namespace |
47
|
|
|
*/ |
48
|
|
|
const PLUGIN_NAMESPACE = 'tx_solr'; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Settings from routing configuration |
52
|
|
|
* |
53
|
|
|
* @var array |
54
|
|
|
*/ |
55
|
|
|
protected $settings = []; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* List of filter that are placed as path arguments |
59
|
|
|
* |
60
|
|
|
* @var array |
61
|
|
|
*/ |
62
|
|
|
protected $pathArguments = []; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Plugin/extension namespace |
66
|
|
|
* |
67
|
|
|
* @var string |
68
|
|
|
*/ |
69
|
|
|
protected $pluginNamespace = 'tx_solr'; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* List of TYPO3 core parameters, that we should ignore |
73
|
|
|
* |
74
|
|
|
* @see \TYPO3\CMS\Frontend\Page\CacheHashCalculator::isCoreParameter |
75
|
|
|
* @var string[] |
76
|
|
|
*/ |
77
|
|
|
protected $coreParameters = ['no_cache', 'cHash', 'id', 'MP', 'type']; |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @var UrlFacetService |
81
|
|
|
*/ |
82
|
|
|
protected $urlFacetPathService; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @var UrlFacetService |
86
|
|
|
*/ |
87
|
|
|
protected $urlFacetQueryService; |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* RoutingService constructor. |
91
|
|
|
* |
92
|
|
|
* @param array $settings |
93
|
|
|
* @param string $pluginNamespace |
94
|
|
|
*/ |
95
|
|
|
public function __construct(array $settings = [], string $pluginNamespace = self::PLUGIN_NAMESPACE) |
96
|
|
|
{ |
97
|
|
|
$this->settings = $settings; |
98
|
|
|
$this->pluginNamespace = $pluginNamespace; |
99
|
|
|
if (empty($this->pluginNamespace)) { |
100
|
|
|
$this->pluginNamespace = self::PLUGIN_NAMESPACE; |
101
|
|
|
} |
102
|
|
|
$this->initUrlFacetService(); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Creates a clone of the current service and replace the settings inside |
107
|
|
|
* |
108
|
|
|
* @param array $settings |
109
|
|
|
* @return RoutingService |
110
|
|
|
*/ |
111
|
|
|
public function withSettings(array $settings): RoutingService |
112
|
|
|
{ |
113
|
|
|
$service = clone $this; |
114
|
|
|
$service->settings = $settings; |
115
|
|
|
$service->initUrlFacetService(); |
116
|
|
|
return $service; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Creates a clone of the current service and replace the settings inside |
121
|
|
|
* |
122
|
|
|
* @param array $pathArguments |
123
|
|
|
* @return RoutingService |
124
|
|
|
*/ |
125
|
|
|
public function withPathArguments(array $pathArguments): RoutingService |
126
|
|
|
{ |
127
|
|
|
$service = clone $this; |
128
|
|
|
$service->pathArguments = $pathArguments; |
129
|
|
|
$service->initUrlFacetService(); |
130
|
|
|
return $service; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Load configuration from routing configuration |
135
|
|
|
* |
136
|
|
|
* @param array $routingConfiguration |
137
|
|
|
* @return $this |
138
|
|
|
*/ |
139
|
|
|
public function fromRoutingConfiguration(array $routingConfiguration): RoutingService |
140
|
|
|
{ |
141
|
|
|
if (empty($routingConfiguration) || |
142
|
|
|
empty($routingConfiguration['type']) || |
143
|
|
|
!$this->isRouteEnhancerForSolr((string)$routingConfiguration['type'])) { |
144
|
|
|
return $this; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
if (isset($routingConfiguration['solr'])) { |
148
|
|
|
$this->settings = $routingConfiguration['solr']; |
149
|
|
|
$this->initUrlFacetService(); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
if (isset($routingConfiguration['_arguments'])) { |
153
|
|
|
$this->pathArguments = $routingConfiguration['_arguments']; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
return $this; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Reset the routing service |
161
|
|
|
* |
162
|
|
|
* @return $this |
163
|
|
|
*/ |
164
|
|
|
public function reset(): RoutingService |
165
|
|
|
{ |
166
|
|
|
$this->settings = []; |
167
|
|
|
$this->pathArguments = []; |
168
|
|
|
$this->pluginNamespace = self::PLUGIN_NAMESPACE; |
169
|
|
|
return $this; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* Initialize url facet services for different types |
174
|
|
|
* |
175
|
|
|
* @return $this |
176
|
|
|
*/ |
177
|
|
|
protected function initUrlFacetService(): RoutingService |
178
|
|
|
{ |
179
|
|
|
$this->urlFacetPathService = new UrlFacetService('path', $this->settings); |
180
|
|
|
$this->urlFacetQueryService = new UrlFacetService('query', $this->settings); |
181
|
|
|
|
182
|
|
|
return $this; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* @return UrlFacetService |
187
|
|
|
*/ |
188
|
|
|
public function getUrlFacetPathService(): UrlFacetService |
189
|
|
|
{ |
190
|
|
|
return $this->urlFacetPathService; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* @return UrlFacetService |
195
|
|
|
*/ |
196
|
|
|
public function getUrlFacetQueryService(): UrlFacetService |
197
|
|
|
{ |
198
|
|
|
return $this->urlFacetQueryService; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Test if the given parameter is a Core parameter |
203
|
|
|
* |
204
|
|
|
* @see \TYPO3\CMS\Frontend\Page\CacheHashCalculator::isCoreParameter |
205
|
|
|
* @param string $parameterName |
206
|
|
|
* @return bool |
207
|
|
|
*/ |
208
|
|
|
public function isCoreParameter(string $parameterName): bool |
209
|
|
|
{ |
210
|
|
|
return in_array($parameterName, $this->coreParameters); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* This returns the plugin namespace |
215
|
|
|
* @see https://docs.typo3.org/p/apache-solr-for-typo3/solr/master/en-us/Configuration/Reference/TxSolrView.html#pluginnamespace |
216
|
|
|
* |
217
|
|
|
* @return string |
218
|
|
|
*/ |
219
|
|
|
public function getPluginNamespace(): string |
220
|
|
|
{ |
221
|
|
|
return $this->pluginNamespace; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Determine if an enhancer is in use for Solr |
226
|
|
|
* |
227
|
|
|
* @param string $enhancerName |
228
|
|
|
* @return bool |
229
|
|
|
*/ |
230
|
|
|
public function isRouteEnhancerForSolr(string $enhancerName): bool |
231
|
|
|
{ |
232
|
|
|
if (empty($enhancerName)) { |
233
|
|
|
return false; |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
if (!isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['routing']['enhancers'][$enhancerName])) { |
237
|
|
|
return false; |
238
|
|
|
} |
239
|
|
|
$className = $GLOBALS['TYPO3_CONF_VARS']['SYS']['routing']['enhancers'][$enhancerName]; |
240
|
|
|
|
241
|
|
|
if (!class_exists($className)) { |
242
|
|
|
return false; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
$interfaces = class_implements($className); |
246
|
|
|
|
247
|
|
|
return in_array(SolrRouteEnhancerInterface::class, $interfaces); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* Masks Solr filter inside of the query parameters |
252
|
|
|
* |
253
|
|
|
* @param string $uriPath |
254
|
|
|
* @return string |
255
|
|
|
*/ |
256
|
|
|
public function finalizePathQuery(string $uriPath): string |
257
|
|
|
{ |
258
|
|
|
$pathSegments = explode('/', $uriPath); |
259
|
|
|
$query = array_pop($pathSegments); |
260
|
|
|
$queryValues = explode($this->urlFacetPathService->getMultiValueSeparator(), $query); |
261
|
|
|
$queryValues = array_map([$this->urlFacetPathService, 'decodeSingleValue'], $queryValues); |
262
|
|
|
/* |
263
|
|
|
* In some constellations the path query contains the facet type in front. |
264
|
|
|
* This leads to the result, that the query values could contain the same facet value multiple times. |
265
|
|
|
* |
266
|
|
|
* In order to avoid this behaviour, the query values need to be checked and clean up. |
267
|
|
|
* 1. Remove possible prefix information |
268
|
|
|
* 2. Apply character replacements |
269
|
|
|
* 3. Filter duplicate values |
270
|
|
|
*/ |
271
|
|
|
$queryValuesCount = count($queryValues); |
272
|
|
|
for ($i = 0; $i < $queryValuesCount; $i++) { |
273
|
|
|
$queryValues[$i] = urldecode($queryValues[$i]); |
274
|
|
|
if ($this->containsFacetAndValueSeparator((string)$queryValues[$i])) { |
275
|
|
|
[$facetName, $facetValue] = explode( |
276
|
|
|
$this->detectFacetAndValueSeparator((string)$queryValues[$i]), |
277
|
|
|
(string)$queryValues[$i], |
278
|
|
|
2 |
279
|
|
|
); |
280
|
|
|
|
281
|
|
|
if ($this->isPathArgument((string)$facetName)) { |
282
|
|
|
$queryValues[$i] = $facetValue; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
} |
286
|
|
|
$queryValues[$i] = $this->urlFacetPathService->applyCharacterMap($queryValues[$i]); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
$queryValues = array_unique($queryValues); |
290
|
|
|
$queryValues = array_map([$this->urlFacetPathService, 'encodeSingleValue'], $queryValues); |
291
|
|
|
sort($queryValues); |
292
|
|
|
$pathSegments[] = implode( |
293
|
|
|
$this->urlFacetPathService->getMultiValueSeparator(), |
294
|
|
|
$queryValues |
295
|
|
|
); |
296
|
|
|
return implode('/', $pathSegments); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* This method checks if the query parameter should be masked. |
301
|
|
|
* |
302
|
|
|
* @return bool |
303
|
|
|
*/ |
304
|
|
|
public function shouldMaskQueryParameter(): bool |
305
|
|
|
{ |
306
|
|
|
if (!isset($this->settings['query']['mask']) || |
307
|
|
|
!(bool)$this->settings['query']['mask']) { |
308
|
|
|
return false; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
$targetFields = $this->getQueryParameterMap(); |
312
|
|
|
|
313
|
|
|
return !empty($targetFields); |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Masks Solr filter inside of the query parameters |
318
|
|
|
* |
319
|
|
|
* @param array $queryParams |
320
|
|
|
* @return array |
321
|
|
|
*/ |
322
|
1 |
|
public function maskQueryParameters(array $queryParams): array |
323
|
|
|
{ |
324
|
1 |
|
if (!$this->shouldMaskQueryParameter()) { |
325
|
|
|
return $queryParams; |
326
|
|
|
} |
327
|
|
|
|
328
|
1 |
|
if (!isset($queryParams[$this->getPluginNamespace()])) { |
329
|
|
|
$this->logger |
330
|
|
|
->/** @scrutinizer ignore-call */ |
331
|
|
|
error('Mask error: Query parameters has no entry for namespace ' . $this->getPluginNamespace()); |
332
|
|
|
return $queryParams; |
333
|
|
|
} |
334
|
|
|
|
335
|
1 |
|
if (!isset($queryParams[$this->getPluginNamespace()]['filter'])) { |
336
|
|
|
$this->logger |
337
|
|
|
->/** @scrutinizer ignore-call */ |
338
|
|
|
info('Mask info: Query parameters has no filter in namespace ' . $this->getPluginNamespace()); |
339
|
|
|
return $queryParams; |
340
|
|
|
} |
341
|
1 |
|
$queryParameterMap = $this->getQueryParameterMap(); |
342
|
1 |
|
$newQueryParams = $queryParams; |
343
|
|
|
|
344
|
1 |
|
$newFilterArray = []; |
345
|
1 |
|
foreach ($newQueryParams[$this->getPluginNamespace()]['filter'] as $queryParamName => $queryParamValue) { |
346
|
1 |
|
$defaultSeparator = $this->detectFacetAndValueSeparator((string)$queryParamValue); |
347
|
1 |
|
[$facetName, $facetValue] = explode($defaultSeparator, $queryParamValue, 2); |
348
|
1 |
|
$keep = false; |
349
|
1 |
|
if (isset($queryParameterMap[$facetName]) && |
350
|
1 |
|
isset($newQueryParams[$queryParameterMap[$facetName]])) { |
351
|
|
|
$this->logger->/** @scrutinizer ignore-call */error( |
352
|
|
|
'Mask error: Facet "' . $facetName . '" as "' . $queryParameterMap[$facetName] . |
353
|
|
|
'" already in query!' |
354
|
|
|
); |
355
|
|
|
$keep = true; |
356
|
|
|
} |
357
|
1 |
|
if (!isset($queryParameterMap[$facetName]) || $keep) { |
358
|
|
|
$newFilterArray[] = $queryParamValue; |
359
|
|
|
continue; |
360
|
|
|
} |
361
|
|
|
|
362
|
1 |
|
$newQueryParams[$queryParameterMap[$facetName]] = $facetValue; |
363
|
|
|
} |
364
|
|
|
|
365
|
1 |
|
$newQueryParams[$this->getPluginNamespace()]['filter'] = $newFilterArray; |
366
|
|
|
|
367
|
1 |
|
return $this->cleanUpQueryParameters($newQueryParams); |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
/** |
371
|
|
|
* Unmask incoming parameters if needed |
372
|
|
|
* |
373
|
|
|
* @param array $queryParams |
374
|
|
|
* @return array |
375
|
|
|
*/ |
376
|
|
|
public function unmaskQueryParameters(array $queryParams): array |
377
|
|
|
{ |
378
|
|
|
if (!$this->shouldMaskQueryParameter()) { |
379
|
|
|
return $queryParams; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
/* |
383
|
|
|
* The array $queryParameterMap contains the mapping of |
384
|
|
|
* facet name to new url name. In order to unmask we need to switch key and values. |
385
|
|
|
*/ |
386
|
|
|
$queryParameterMap = $this->getQueryParameterMap(); |
387
|
|
|
$queryParameterMapSwitched = []; |
388
|
|
|
foreach ($queryParameterMap as $value => $key) { |
389
|
|
|
$queryParameterMapSwitched[$key] = $value; |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
$newQueryParams = []; |
393
|
|
|
foreach ($queryParams as $queryParamName => $queryParamValue) { |
394
|
|
|
// A merge is needed! |
395
|
|
|
if (!isset($queryParameterMapSwitched[$queryParamName])) { |
396
|
|
|
if (isset($newQueryParams[$queryParamName])) { |
397
|
|
|
$newQueryParams[$queryParamName] = array_merge_recursive( |
398
|
|
|
$newQueryParams[$queryParamName], |
399
|
|
|
$queryParamValue |
400
|
|
|
); |
401
|
|
|
} else { |
402
|
|
|
$newQueryParams[$queryParamName] = $queryParamValue; |
403
|
|
|
} |
404
|
|
|
continue; |
405
|
|
|
} |
406
|
|
|
if (!isset($newQueryParams[$this->getPluginNamespace()])) { |
407
|
|
|
$newQueryParams[$this->getPluginNamespace()] = []; |
408
|
|
|
} |
409
|
|
|
if (!isset($newQueryParams[$this->getPluginNamespace()]['filter'])) { |
410
|
|
|
$newQueryParams[$this->getPluginNamespace()]['filter'] = []; |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
$newQueryParams[$this->getPluginNamespace()]['filter'][] = |
414
|
|
|
$queryParameterMapSwitched[$queryParamName] . ':' . $queryParamValue; |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
return $this->cleanUpQueryParameters($newQueryParams); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* This method check if the query parameters should be touched or not. |
422
|
|
|
* |
423
|
|
|
* There are following requirements: |
424
|
|
|
* - Masking is activated and the mal is valid or |
425
|
|
|
* - Concat is activated |
426
|
|
|
* |
427
|
|
|
* @return bool |
428
|
|
|
*/ |
429
|
|
|
public function shouldConcatQueryParameters(): bool |
430
|
|
|
{ |
431
|
|
|
/* |
432
|
|
|
* The concat will activate automatically if parameters should be masked. |
433
|
|
|
* This solution is less complex since not every mapping parameter needs to be tested |
434
|
|
|
*/ |
435
|
|
|
if ($this->shouldMaskQueryParameter()) { |
436
|
|
|
return true; |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
return isset($this->settings['query']['concat']) && (bool)$this->settings['query']['concat']; |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
/** |
443
|
|
|
* Returns the query parameter map |
444
|
|
|
* |
445
|
|
|
* Note TYPO3 core query arguments removed from the configured map! |
446
|
|
|
* |
447
|
|
|
* @return array |
448
|
|
|
*/ |
449
|
|
|
public function getQueryParameterMap(): array |
450
|
|
|
{ |
451
|
|
|
if (!isset($this->settings['query']['map']) || |
452
|
|
|
!is_array($this->settings['query']['map']) || |
453
|
|
|
empty($this->settings['query']['map'])) { |
454
|
|
|
return []; |
455
|
|
|
} |
456
|
|
|
// TODO: Test if there is more than one value! |
457
|
|
|
$self = $this; |
458
|
|
|
return array_filter( |
459
|
|
|
$this->settings['query']['map'], |
460
|
|
|
function($value) use ($self) { |
461
|
|
|
return !$self->isCoreParameter($value); |
462
|
|
|
} |
463
|
|
|
); |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
/** |
467
|
|
|
* Group all filter values together and concat e |
468
|
|
|
* Note: this will just handle filter values |
469
|
|
|
* |
470
|
|
|
* IN: |
471
|
|
|
* tx_solr => [ |
472
|
|
|
* filter => [ |
473
|
|
|
* color:red |
474
|
|
|
* product:candy |
475
|
|
|
* color:blue |
476
|
|
|
* taste:sour |
477
|
|
|
* ] |
478
|
|
|
* ] |
479
|
|
|
* |
480
|
|
|
* OUT: |
481
|
|
|
* tx_solr => [ |
482
|
|
|
* filter => [ |
483
|
|
|
* color:blue,red |
484
|
|
|
* product:candy |
485
|
|
|
* taste:sour |
486
|
|
|
* ] |
487
|
|
|
* ] |
488
|
|
|
* @param array $queryParams |
489
|
|
|
* @return array |
490
|
|
|
*/ |
491
|
1 |
|
public function concatQueryParameter(array $queryParams = []): array |
492
|
|
|
{ |
493
|
1 |
|
if (!$this->shouldConcatQueryParameters()) { |
494
|
|
|
return $queryParams; |
495
|
|
|
} |
496
|
|
|
|
497
|
1 |
|
if (!isset($queryParams[$this->getPluginNamespace()])) { |
498
|
|
|
$this->logger |
499
|
|
|
->error('Mask error: Query parameters has no entry for namespace ' . $this->getPluginNamespace()); |
|
|
|
|
500
|
|
|
return $queryParams; |
501
|
|
|
} |
502
|
|
|
|
503
|
1 |
|
if (!isset($queryParams[$this->getPluginNamespace()]['filter'])) { |
504
|
|
|
$this->logger |
505
|
|
|
->info('Mask info: Query parameters has no filter in namespace ' . $this->getPluginNamespace()); |
506
|
|
|
return $queryParams; |
507
|
|
|
} |
508
|
|
|
|
509
|
1 |
|
$queryParams[$this->getPluginNamespace()]['filter'] = |
510
|
1 |
|
$this->concatFilterValues($queryParams[$this->getPluginNamespace()]['filter']); |
511
|
|
|
|
512
|
1 |
|
return $this->cleanUpQueryParameters($queryParams); |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
/** |
516
|
|
|
* This method expect a filter array that should be concat instead of the whole query |
517
|
|
|
* |
518
|
|
|
* @param array $filterArray |
519
|
|
|
* @return array |
520
|
|
|
*/ |
521
|
|
|
public function concatFilterValues(array $filterArray): array |
522
|
|
|
{ |
523
|
|
|
if (empty($filterArray) || !$this->shouldConcatQueryParameters()) { |
524
|
|
|
return $filterArray; |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
$queryParameterMap = $this->getQueryParameterMap(); |
528
|
|
|
$newFilterArray = []; |
529
|
|
|
$defaultSeparator = $this->detectFacetAndValueSeparator((string)$filterArray[0]); |
530
|
|
|
// Collect parameter names and rename parameter if required |
531
|
|
|
foreach ($filterArray as $set) { |
532
|
|
|
$separator = $this->detectFacetAndValueSeparator((string)$set); |
533
|
|
|
[$facetName, $facetValue] = explode($separator, $set, 2); |
534
|
|
|
if (isset($queryParameterMap[$facetName])) { |
535
|
|
|
$facetName = $queryParameterMap[$facetName]; |
536
|
|
|
} |
537
|
|
|
if (!isset($newFilterArray[$facetName])) { |
538
|
|
|
$newFilterArray[$facetName] = [$facetValue]; |
539
|
|
|
} else { |
540
|
|
|
$newFilterArray[$facetName][] = $facetValue; |
541
|
|
|
} |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
foreach ($newFilterArray as $facetName => $facetValues) { |
545
|
|
|
$newFilterArray[$facetName] = $facetName . $defaultSeparator . $this->queryParameterFacetsToString($facetValues); |
546
|
|
|
} |
547
|
|
|
|
548
|
|
|
return array_values($newFilterArray); |
549
|
|
|
} |
550
|
|
|
|
551
|
|
|
/** |
552
|
|
|
* Inflate given query parameters if configured |
553
|
|
|
* Note: this will just combine filter values |
554
|
|
|
* |
555
|
|
|
* IN: |
556
|
|
|
* tx_solr => [ |
557
|
|
|
* filter => [ |
558
|
|
|
* color:blue,red |
559
|
|
|
* product:candy |
560
|
|
|
* taste:sour |
561
|
|
|
* ] |
562
|
|
|
* ] |
563
|
|
|
* |
564
|
|
|
* OUT: |
565
|
|
|
* tx_solr => [ |
566
|
|
|
* filter => [ |
567
|
|
|
* color:red |
568
|
|
|
* product:candy |
569
|
|
|
* color:blue |
570
|
|
|
* taste:sour |
571
|
|
|
* ] |
572
|
|
|
* ] |
573
|
|
|
* |
574
|
|
|
* @param array $queryParams |
575
|
|
|
* @return array |
576
|
|
|
*/ |
577
|
1 |
|
public function inflateQueryParameter(array $queryParams = []): array |
578
|
|
|
{ |
579
|
1 |
|
if (!$this->shouldConcatQueryParameters()) { |
580
|
|
|
return $queryParams; |
581
|
|
|
} |
582
|
|
|
|
583
|
1 |
|
if (!isset($queryParams[$this->getPluginNamespace()])) { |
584
|
|
|
$queryParams[$this->getPluginNamespace()] = []; |
585
|
|
|
} |
586
|
|
|
|
587
|
1 |
|
if (!isset($queryParams[$this->getPluginNamespace()]['filter'])) { |
588
|
|
|
$queryParams[$this->getPluginNamespace()]['filter'] = []; |
589
|
|
|
} |
590
|
|
|
|
591
|
1 |
|
$newQueryParams = []; |
592
|
1 |
|
foreach ($queryParams[$this->getPluginNamespace()]['filter'] as $set) { |
593
|
1 |
|
$separator = $this->detectFacetAndValueSeparator((string)$set); |
594
|
1 |
|
[$facetName, $facetValuesString] = explode($separator, $set, 2); |
595
|
1 |
|
$facetValues = explode($this->urlFacetQueryService->getMultiValueSeparator(), $facetValuesString); |
596
|
|
|
|
597
|
|
|
/** |
598
|
|
|
* A facet value could contain the multi value separator. This value is masked in order to |
599
|
|
|
* avoid problems during separation of the values (line above). |
600
|
|
|
* |
601
|
|
|
* After splitting the values, the character inside the value need to be restored |
602
|
|
|
* |
603
|
|
|
* @see RoutingService::queryParameterFacetsToString |
604
|
|
|
*/ |
605
|
1 |
|
$facetValues = array_map([$this->urlFacetQueryService, 'decodeSingleValue'], $facetValues); |
606
|
|
|
|
607
|
1 |
|
foreach ($facetValues as $facetValue) { |
608
|
1 |
|
$newQueryParams[] = $facetName . $separator . $facetValue; |
609
|
|
|
} |
610
|
|
|
} |
611
|
1 |
|
$queryParams[$this->getPluginNamespace()]['filter'] = array_values($newQueryParams); |
612
|
|
|
|
613
|
1 |
|
return $this->cleanUpQueryParameters($queryParams); |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
/** |
617
|
|
|
* Cleanup the query parameters, to avoid empty solr arguments |
618
|
|
|
* |
619
|
|
|
* @param array $queryParams |
620
|
|
|
* @return array |
621
|
|
|
*/ |
622
|
|
|
public function cleanUpQueryParameters(array $queryParams): array |
623
|
|
|
{ |
624
|
|
|
if (empty($queryParams[$this->getPluginNamespace()]['filter'])) { |
625
|
|
|
unset($queryParams[$this->getPluginNamespace()]['filter']); |
626
|
|
|
} |
627
|
|
|
|
628
|
|
|
if (empty($queryParams[$this->getPluginNamespace()])) { |
629
|
|
|
unset($queryParams[$this->getPluginNamespace()]); |
630
|
|
|
} |
631
|
|
|
return $queryParams; |
632
|
|
|
} |
633
|
|
|
|
634
|
|
|
/** |
635
|
|
|
* Builds a string out of multiple facet values |
636
|
|
|
* |
637
|
|
|
* A facet value could contain the multi value separator. This value have to masked in order to |
638
|
|
|
* avoid problems during separation of the values later. |
639
|
|
|
* |
640
|
|
|
* This mask have to apply before contact the values |
641
|
|
|
* |
642
|
|
|
* @param array $facets |
643
|
|
|
* @return string |
644
|
|
|
*/ |
645
|
|
|
public function queryParameterFacetsToString(array $facets): string |
646
|
|
|
{ |
647
|
|
|
$facets = array_map([$this->urlFacetQueryService, 'encodeSingleValue'], $facets); |
648
|
|
|
sort($facets); |
649
|
|
|
return implode($this->urlFacetQueryService->getMultiValueSeparator(), $facets); |
650
|
|
|
} |
651
|
|
|
|
652
|
|
|
/** |
653
|
|
|
* Returns the string which separates the facet from the value |
654
|
|
|
* |
655
|
|
|
* @param string $facetWithValue |
656
|
|
|
* @return string |
657
|
|
|
*/ |
658
|
|
|
public function detectFacetAndValueSeparator(string $facetWithValue): string |
659
|
|
|
{ |
660
|
|
|
$separator = ':'; |
661
|
|
|
if (mb_strpos($facetWithValue, '%3A') !== false) { |
662
|
|
|
$separator = '%3A'; |
663
|
|
|
} |
664
|
|
|
|
665
|
|
|
return $separator; |
666
|
|
|
} |
667
|
|
|
|
668
|
|
|
/** |
669
|
|
|
* Check if given facet value combination contains a separator |
670
|
|
|
* |
671
|
|
|
* @param string $facetWithValue |
672
|
|
|
* @return bool |
673
|
|
|
*/ |
674
|
|
|
public function containsFacetAndValueSeparator(string $facetWithValue): bool |
675
|
|
|
{ |
676
|
|
|
if (mb_strpos($facetWithValue, ':') === false && mb_strpos($facetWithValue, '%3A') === false) { |
677
|
|
|
return false; |
678
|
|
|
} |
679
|
|
|
|
680
|
|
|
return true; |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
/** |
684
|
|
|
* Cleanup facet values (strip type if needed) |
685
|
|
|
* |
686
|
|
|
* @param array $facetValues |
687
|
|
|
* @return array |
688
|
|
|
*/ |
689
|
|
|
public function cleanupFacetValues(array $facetValues): array |
690
|
|
|
{ |
691
|
|
|
$facetValuesCount = count($facetValues); |
692
|
|
|
for ($i = 0; $i < $facetValuesCount; $i++) { |
693
|
|
|
if (!$this->containsFacetAndValueSeparator((string)$facetValues[$i])) { |
694
|
|
|
continue; |
695
|
|
|
} |
696
|
|
|
|
697
|
|
|
$separator = $this->detectFacetAndValueSeparator((string)$facetValues[$i]); |
698
|
|
|
[$type, $value] = explode($separator, $facetValues[$i]); |
699
|
|
|
|
700
|
|
|
if ($this->isMappingArgument($type) || $this->isPathArgument($type)) { |
701
|
|
|
$facetValues[$i] = $value; |
702
|
|
|
} |
703
|
|
|
} |
704
|
|
|
return $facetValues; |
705
|
|
|
} |
706
|
|
|
|
707
|
|
|
/** |
708
|
|
|
* Builds a string out of multiple facet values |
709
|
|
|
* |
710
|
|
|
* @param array $facets |
711
|
|
|
* @return string |
712
|
|
|
*/ |
713
|
|
|
public function pathFacetsToString(array $facets): string |
714
|
|
|
{ |
715
|
|
|
$facets = $this->cleanupFacetValues($facets); |
716
|
|
|
sort($facets); |
717
|
|
|
$facets = array_map([$this->urlFacetPathService, 'applyCharacterMap'], $facets); |
718
|
|
|
$facets = array_map([$this->urlFacetPathService, 'encodeSingleValue'], $facets); |
719
|
|
|
return implode($this->urlFacetPathService->getMultiValueSeparator(), $facets); |
720
|
|
|
} |
721
|
|
|
|
722
|
|
|
/** |
723
|
|
|
* Builds a string out of multiple facet values |
724
|
|
|
* |
725
|
|
|
* @param array $facets |
726
|
|
|
* @return string |
727
|
|
|
*/ |
728
|
2 |
|
public function facetsToString(array $facets): string |
729
|
|
|
{ |
730
|
2 |
|
$facets = $this->cleanupFacetValues($facets); |
731
|
2 |
|
sort($facets); |
732
|
2 |
|
return implode($this->getDefaultMultiValueSeparator(), $facets); |
733
|
|
|
} |
734
|
|
|
|
735
|
|
|
/** |
736
|
|
|
* Builds a string out of multiple facet values |
737
|
|
|
* |
738
|
|
|
* This method is used in two different situation |
739
|
|
|
* 1. Middleware: Here the values should not be decoded |
740
|
|
|
* 2. Within the event listener CachedPathVariableModifier |
741
|
|
|
* |
742
|
|
|
* @param string $facets |
743
|
|
|
* @param bool $decode |
744
|
|
|
* @return array |
745
|
|
|
*/ |
746
|
|
|
public function pathFacetStringToArray(string $facets, bool $decode = true): array |
747
|
|
|
{ |
748
|
|
|
$facetString = $this->urlFacetPathService->applyCharacterMap($facets); |
749
|
|
|
$facets = explode($this->urlFacetPathService->getMultiValueSeparator(), $facetString); |
750
|
|
|
if (!$decode) { |
751
|
|
|
return $facets; |
752
|
|
|
} |
753
|
|
|
return array_map([$this->urlFacetPathService, 'decodeSingleValue'], $facets); |
754
|
|
|
} |
755
|
|
|
|
756
|
|
|
/** |
757
|
|
|
* Returns the multi value separator |
758
|
|
|
* @return string |
759
|
|
|
*/ |
760
|
2 |
|
public function getDefaultMultiValueSeparator(): string |
761
|
|
|
{ |
762
|
2 |
|
return $this->settings['multiValueSeparator'] ?? ','; |
763
|
|
|
} |
764
|
|
|
|
765
|
|
|
/** |
766
|
|
|
* Find a enhancer configuration by a given page id |
767
|
|
|
* |
768
|
|
|
* @param int $pageUid |
769
|
|
|
* @return array |
770
|
|
|
*/ |
771
|
|
|
public function fetchEnhancerByPageUid(int $pageUid): array |
772
|
|
|
{ |
773
|
|
|
$site = $this->findSiteByUid($pageUid); |
774
|
|
|
if ($site instanceof NullSite) { |
775
|
|
|
return []; |
776
|
|
|
} |
777
|
|
|
|
778
|
|
|
return $this->fetchEnhancerInSiteConfigurationByPageUid( |
779
|
|
|
$site, |
780
|
|
|
$pageUid |
781
|
|
|
); |
782
|
|
|
} |
783
|
|
|
|
784
|
|
|
/** |
785
|
|
|
* Returns the route enhancer configuration by given site and page uid |
786
|
|
|
* |
787
|
|
|
* @param Site $site |
788
|
|
|
* @param int $pageUid |
789
|
|
|
* @return array |
790
|
|
|
*/ |
791
|
|
|
public function fetchEnhancerInSiteConfigurationByPageUid(Site $site, int $pageUid): array |
792
|
|
|
{ |
793
|
|
|
$configuration = $site->getConfiguration(); |
794
|
|
|
if (empty($configuration['routeEnhancers']) || !is_array($configuration['routeEnhancers'])) { |
795
|
|
|
return []; |
796
|
|
|
} |
797
|
|
|
$result = []; |
798
|
|
|
foreach ($configuration['routeEnhancers'] as $routing => $settings) { |
799
|
|
|
// Not the page we are looking for |
800
|
|
|
if (isset($settings['limitToPages']) && |
801
|
|
|
is_array($settings['limitToPages']) && |
802
|
|
|
!in_array($pageUid, $settings['limitToPages'])) { |
803
|
|
|
continue; |
804
|
|
|
} |
805
|
|
|
|
806
|
|
|
if (empty($settings) || !isset($settings['type']) || |
807
|
|
|
!$this->isRouteEnhancerForSolr((string)$settings['type']) |
808
|
|
|
) { |
809
|
|
|
continue; |
810
|
|
|
} |
811
|
|
|
$result[] = $settings; |
812
|
|
|
} |
813
|
|
|
|
814
|
|
|
return $result; |
815
|
|
|
} |
816
|
|
|
|
817
|
|
|
/** |
818
|
|
|
* Add heading slash to given slug |
819
|
|
|
* |
820
|
|
|
* @param string $slug |
821
|
|
|
* @return string |
822
|
|
|
*/ |
823
|
|
|
public function cleanupHeadingSlash(string $slug): string |
824
|
|
|
{ |
825
|
|
|
if (mb_substr($slug, 0, 1) !== '/') { |
826
|
|
|
return '/' . $slug; |
827
|
|
|
} else if (mb_substr($slug, 0, 2) === '//') { |
828
|
|
|
return mb_substr($slug, 1, mb_strlen($slug) - 1); |
829
|
|
|
} |
830
|
|
|
|
831
|
|
|
return $slug; |
832
|
|
|
} |
833
|
|
|
|
834
|
|
|
/** |
835
|
|
|
* Add heading slash to given slug |
836
|
|
|
* |
837
|
|
|
* @param string $slug |
838
|
|
|
* @return string |
839
|
|
|
*/ |
840
|
|
|
public function addHeadingSlash(string $slug): string |
841
|
|
|
{ |
842
|
|
|
if (mb_substr($slug, 0, 1) === '/') { |
843
|
|
|
return $slug; |
844
|
|
|
} |
845
|
|
|
|
846
|
|
|
return '/' . $slug; |
847
|
|
|
} |
848
|
|
|
|
849
|
|
|
/** |
850
|
|
|
* Remove heading slash from given slug |
851
|
|
|
* |
852
|
|
|
* @param string $slug |
853
|
|
|
* @return string |
854
|
|
|
*/ |
855
|
|
|
public function removeHeadingSlash(string $slug): string |
856
|
|
|
{ |
857
|
|
|
if (mb_substr($slug, 0, 1) !== '/') { |
858
|
|
|
return $slug; |
859
|
|
|
} |
860
|
|
|
|
861
|
|
|
return mb_substr($slug, 1, mb_strlen($slug) - 1); |
862
|
|
|
} |
863
|
|
|
|
864
|
|
|
/** |
865
|
|
|
* Retrieve the site by given UID |
866
|
|
|
* |
867
|
|
|
* @param int $pageUid |
868
|
|
|
* @return SiteInterface |
869
|
|
|
*/ |
870
|
|
|
public function findSiteByUid(int $pageUid): SiteInterface |
871
|
|
|
{ |
872
|
|
|
try { |
873
|
|
|
$site = $this->getSiteFinder() |
874
|
|
|
->getSiteByPageId($pageUid); |
875
|
|
|
return $site; |
876
|
|
|
} catch (SiteNotFoundException $exception) { |
877
|
|
|
return new NullSite(); |
878
|
|
|
} |
879
|
|
|
} |
880
|
|
|
|
881
|
|
|
/** |
882
|
|
|
* @param Site $site |
883
|
|
|
* @return PageSlugCandidateProvider |
884
|
|
|
*/ |
885
|
|
|
public function getSlugCandidateProvider(Site $site): PageSlugCandidateProvider |
886
|
|
|
{ |
887
|
|
|
$context = GeneralUtility::makeInstance(Context::class); |
888
|
|
|
return GeneralUtility::makeInstance( |
889
|
|
|
PageSlugCandidateProvider::class, |
890
|
|
|
$context, |
891
|
|
|
$site, |
892
|
|
|
null |
893
|
|
|
); |
894
|
|
|
} |
895
|
|
|
|
896
|
|
|
/** |
897
|
|
|
* Convert the base string into a URI object |
898
|
|
|
* |
899
|
|
|
* @param string $base |
900
|
|
|
* @return UriInterface|null |
901
|
|
|
*/ |
902
|
1 |
|
public function convertStringIntoUri(string $base): ?UriInterface |
903
|
|
|
{ |
904
|
|
|
try { |
905
|
|
|
/* @var Uri $uri */ |
906
|
1 |
|
$uri = GeneralUtility::makeInstance( |
907
|
1 |
|
Uri::class, |
908
|
1 |
|
$base |
909
|
|
|
); |
910
|
|
|
|
911
|
1 |
|
return $uri; |
912
|
|
|
} catch (\InvalidArgumentException $argumentException) { |
913
|
|
|
return null; |
914
|
|
|
} |
915
|
|
|
} |
916
|
|
|
|
917
|
|
|
/** |
918
|
|
|
* In order to search for a path, a possible language prefix need to remove |
919
|
|
|
* |
920
|
|
|
* @param SiteLanguage $language |
921
|
|
|
* @param string $path |
922
|
|
|
* @return string |
923
|
|
|
*/ |
924
|
|
|
public function stripLanguagePrefixFromPath(SiteLanguage $language, string $path): string |
925
|
|
|
{ |
926
|
|
|
if ($language->getBase()->getPath() === '/') { |
927
|
|
|
return $path; |
928
|
|
|
} |
929
|
|
|
|
930
|
|
|
$pathLength = mb_strlen($language->getBase()->getPath()); |
931
|
|
|
|
932
|
|
|
$path = mb_substr($path, $pathLength, mb_strlen($path) - $pathLength); |
933
|
|
|
if (mb_substr($path, 0, 1) !== '/') { |
934
|
|
|
$path = '/' . $path; |
935
|
|
|
} |
936
|
|
|
|
937
|
|
|
return $path; |
938
|
|
|
} |
939
|
|
|
|
940
|
|
|
/** |
941
|
|
|
* Enrich the current query Params with data from path information |
942
|
|
|
* |
943
|
|
|
* @param ServerRequestInterface $request |
944
|
|
|
* @param array $arguments |
945
|
|
|
* @param array $parameters |
946
|
|
|
* @return ServerRequestInterface |
947
|
|
|
*/ |
948
|
3 |
|
public function addPathArgumentsToQuery( |
949
|
|
|
ServerRequestInterface $request, |
950
|
|
|
array $arguments, |
951
|
|
|
array $parameters |
952
|
|
|
): ServerRequestInterface { |
953
|
3 |
|
$queryParams = $request->getQueryParams(); |
954
|
3 |
|
foreach ($arguments as $fieldName => $queryPath) { |
955
|
|
|
// Skip if there is no parameter |
956
|
3 |
|
if (!isset($parameters[$fieldName])) { |
957
|
|
|
continue; |
958
|
|
|
} |
959
|
3 |
|
$pathElements = explode('/', $queryPath); |
960
|
|
|
|
961
|
3 |
|
if (!empty($this->pluginNamespace)) { |
962
|
3 |
|
array_unshift($pathElements, $this->pluginNamespace); |
963
|
|
|
} |
964
|
|
|
|
965
|
3 |
|
$queryParams = $this->processUriPathArgument( |
966
|
3 |
|
$queryParams, |
967
|
3 |
|
$fieldName, |
968
|
3 |
|
$parameters, |
969
|
3 |
|
$pathElements |
970
|
|
|
); |
971
|
|
|
} |
972
|
|
|
|
973
|
3 |
|
return $request->withQueryParams($queryParams); |
974
|
|
|
} |
975
|
|
|
|
976
|
|
|
/** |
977
|
|
|
* Check if given argument is a mapping argument |
978
|
|
|
* |
979
|
|
|
* @param string $facetName |
980
|
|
|
* @return bool |
981
|
|
|
*/ |
982
|
|
|
public function isMappingArgument(string $facetName): bool |
983
|
|
|
{ |
984
|
|
|
$map = $this->getQueryParameterMap(); |
985
|
|
|
if (isset($map[$facetName]) && $this->shouldMaskQueryParameter()) { |
986
|
|
|
return true; |
987
|
|
|
} |
988
|
|
|
|
989
|
|
|
return false; |
990
|
|
|
} |
991
|
|
|
|
992
|
|
|
/** |
993
|
|
|
* Check if given facet type is an path argument |
994
|
|
|
* |
995
|
|
|
* @param string $facetName |
996
|
|
|
* @return bool |
997
|
|
|
*/ |
998
|
|
|
public function isPathArgument(string $facetName): bool |
999
|
|
|
{ |
1000
|
|
|
return isset($this->pathArguments[$facetName]); |
1001
|
|
|
} |
1002
|
|
|
|
1003
|
|
|
/** |
1004
|
|
|
* @param string $variable |
1005
|
|
|
* @return string |
1006
|
|
|
*/ |
1007
|
|
|
public function reviewVariable(string $variable): string |
1008
|
|
|
{ |
1009
|
|
|
if (!$this->containsFacetAndValueSeparator((string)$variable)) { |
1010
|
|
|
return $variable; |
1011
|
|
|
} |
1012
|
|
|
|
1013
|
|
|
$separator = $this->detectFacetAndValueSeparator((string)$variable); |
1014
|
|
|
[$type, $value] = explode($separator, $variable, 2); |
1015
|
|
|
|
1016
|
|
|
return $this->isMappingArgument($type) ? $value : $variable; |
1017
|
|
|
} |
1018
|
|
|
|
1019
|
|
|
/** |
1020
|
|
|
* Remove type prefix from filter |
1021
|
|
|
* |
1022
|
|
|
* @param array $variables |
1023
|
|
|
* @return array |
1024
|
|
|
*/ |
1025
|
|
|
public function reviseFilterVariables(array $variables): array |
1026
|
|
|
{ |
1027
|
|
|
$newVariables = []; |
1028
|
|
|
foreach ($variables as $key => $value) { |
1029
|
|
|
$matches = []; |
1030
|
|
|
if (!preg_match('/###' . $this->getPluginNamespace() . ':filter:\d+:(.+?)###/', $key, $matches)) { |
1031
|
|
|
$newVariables[$key] = $value; |
1032
|
|
|
continue; |
1033
|
|
|
} |
1034
|
|
|
if (!$this->isMappingArgument($matches[1]) && !$this->isPathArgument($matches[1])) { |
1035
|
|
|
$newVariables[$key] = $value; |
1036
|
|
|
continue; |
1037
|
|
|
} |
1038
|
|
|
$separator = $this->detectFacetAndValueSeparator((string)$value); |
1039
|
|
|
$parts = explode($separator, $value); |
1040
|
|
|
|
1041
|
|
|
do { |
1042
|
|
|
if ($parts[0] === $matches[1]) { |
1043
|
|
|
array_shift($parts); |
1044
|
|
|
} |
1045
|
|
|
} while ($parts[0] === $matches[1]); |
1046
|
|
|
|
1047
|
|
|
$newVariables[$key] = implode($separator, $parts); |
1048
|
|
|
} |
1049
|
|
|
|
1050
|
|
|
return $newVariables; |
1051
|
|
|
} |
1052
|
|
|
|
1053
|
|
|
/** |
1054
|
|
|
* Converts path segment information into query parameters |
1055
|
|
|
* |
1056
|
|
|
* Example: |
1057
|
|
|
* /products/household |
1058
|
|
|
* |
1059
|
|
|
* tx_solr: |
1060
|
|
|
* filter: |
1061
|
|
|
* - type:household |
1062
|
|
|
* |
1063
|
|
|
* @param array $queryParams |
1064
|
|
|
* @param string $fieldName |
1065
|
|
|
* @param array $parameters |
1066
|
|
|
* @param array $pathElements |
1067
|
|
|
* @return array |
1068
|
|
|
*/ |
1069
|
|
|
protected function processUriPathArgument( |
1070
|
|
|
array $queryParams, |
1071
|
|
|
string $fieldName, |
1072
|
|
|
array $parameters, |
1073
|
|
|
array $pathElements |
1074
|
|
|
): array { |
1075
|
|
|
$queryKey = array_shift($pathElements); |
1076
|
|
|
$queryKey = (string)$queryKey; |
1077
|
|
|
|
1078
|
|
|
$tmpQueryKey = $queryKey; |
1079
|
|
|
if (strpos($queryKey, '-') !== false) { |
1080
|
|
|
[$tmpQueryKey, $filterName] = explode('-', $tmpQueryKey, 2); |
1081
|
|
|
} |
1082
|
|
|
if (!isset($queryParams[$tmpQueryKey]) || $queryParams[$tmpQueryKey] === null) { |
1083
|
|
|
$queryParams[$tmpQueryKey] = []; |
1084
|
|
|
} |
1085
|
|
|
|
1086
|
|
|
if (strpos($queryKey, '-') !== false) { |
1087
|
|
|
[$queryKey, $filterName] = explode('-', $queryKey, 2); |
1088
|
|
|
// explode multiple values |
1089
|
|
|
$values = $this->pathFacetStringToArray($parameters[$fieldName], false); |
1090
|
|
|
sort($values); |
1091
|
|
|
|
1092
|
|
|
// @TODO: Support URL data bag |
1093
|
|
|
foreach ($values as $value) { |
1094
|
|
|
$value = $this->urlFacetPathService->applyCharacterMap($value); |
1095
|
|
|
$queryParams[$queryKey][] = $filterName . ':' . $value; |
1096
|
|
|
} |
1097
|
|
|
} else { |
1098
|
|
|
$queryParams[$queryKey] = $this->processUriPathArgument( |
1099
|
|
|
$queryParams[$queryKey], |
1100
|
|
|
$fieldName, |
1101
|
|
|
$parameters, |
1102
|
|
|
$pathElements |
1103
|
|
|
); |
1104
|
|
|
} |
1105
|
|
|
|
1106
|
|
|
return $queryParams; |
1107
|
|
|
} |
1108
|
|
|
|
1109
|
|
|
/** |
1110
|
|
|
* Return site matcher |
1111
|
|
|
* |
1112
|
|
|
* @return SiteMatcher |
1113
|
|
|
*/ |
1114
|
|
|
public function getSiteMatcher(): SiteMatcher |
1115
|
|
|
{ |
1116
|
|
|
return GeneralUtility::makeInstance(SiteMatcher::class, $this->getSiteFinder()); |
1117
|
|
|
} |
1118
|
|
|
|
1119
|
|
|
/** |
1120
|
|
|
* Returns the site finder |
1121
|
|
|
* |
1122
|
|
|
* @return SiteFinder|null |
1123
|
|
|
*/ |
1124
|
|
|
protected function getSiteFinder(): ?SiteFinder |
1125
|
|
|
{ |
1126
|
|
|
return GeneralUtility::makeInstance(SiteFinder::class); |
1127
|
|
|
} |
1128
|
|
|
} |
1129
|
|
|
|
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.