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