Passed
Push — task/2976_TYPO3.11_compatibili... ( d7dfd0...4be1cc )
by Rafael
03:40
created

SearchUriBuilder::getResultGroupItemPageUri()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 7
ccs 6
cts 6
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
crap 1
1
<?php
2
namespace ApacheSolrForTypo3\Solr\Domain\Search\Uri;
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
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\Grouping\GroupItem;
18
use ApacheSolrForTypo3\Solr\Domain\Search\SearchRequest;
19
use ApacheSolrForTypo3\Solr\Event\Routing\BeforeProcessCachedVariablesEvent;
20
use ApacheSolrForTypo3\Solr\Event\Routing\BeforeReplaceVariableInCachedUrlEvent;
21
use ApacheSolrForTypo3\Solr\Event\Routing\PostProcessUriEvent;
22
use ApacheSolrForTypo3\Solr\Routing\RoutingService;
23
use ApacheSolrForTypo3\Solr\System\Url\UrlHelper;
24
use Psr\EventDispatcher\EventDispatcherInterface;
25
use TYPO3\CMS\Core\Http\Uri;
26
use ApacheSolrForTypo3\Solr\Utility\ParameterSortingUtility;
27
use TYPO3\CMS\Core\Utility\GeneralUtility;
28
use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
29
30
/**
31
 * SearchUriBuilder
32
 *
33
 * Responsibility:
34
 *
35
 * The SearchUriBuilder is responsible to build uris, that are used in the
36
 * searchContext. It can use the previous request with it's persistent
37
 * arguments to build the url for a search sub request.
38
 *
39
 * @author Frans Saris <[email protected]>
40
 * @author Timo Hund <[email protected]>
41
 */
42
class SearchUriBuilder
43
{
44
45
    /**
46
     * @var UriBuilder
47
     */
48
    protected $uriBuilder;
49
50
    /**
51
     * @var array
52
     */
53
    protected static $preCompiledLinks = [];
54
55
    /**
56
     * @var integer
57
     */
58
    protected static $hitCount;
59
60
    /**
61
     * @var integer
62
     */
63
    protected static $missCount;
64
65
    /**
66
     * @var array
67
     */
68
    protected static $additionalArgumentsCache = [];
69
70
    /**
71
     * @var EventDispatcherInterface
72
     */
73
    protected $eventDispatcher;
74
75
    /**
76
     * @var RoutingService
77
     */
78
    protected $routingService;
79
80
    /**
81
     * @param UriBuilder $uriBuilder
82
     */
83 37
    public function injectUriBuilder(UriBuilder $uriBuilder)
84
    {
85 37
        $this->uriBuilder = $uriBuilder;
86 37
    }
87
88
    /**
89
     * @param RoutingService $routingService
90
     */
91 37
    public function injectRoutingService(RoutingService $routingService)
92
    {
93 37
        $this->routingService = $routingService;
94 37
    }
95
96
    /**
97
     * @param EventDispatcherInterface $eventDispatcher
98
     */
99 37
    public function injectEventDispatcher(EventDispatcherInterface $eventDispatcher)
100
    {
101 37
        $this->eventDispatcher = $eventDispatcher;
102 37
    }
103
104
    /**
105
     * @param SearchRequest $previousSearchRequest
106
     * @param $facetName
107
     * @param $facetValue
108
     * @return string
109
     */
110 18
    public function getAddFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue): string
111
    {
112 18
        $persistentAndFacetArguments = $previousSearchRequest
113 18
            ->getCopyForSubRequest()->removeAllGroupItemPages()->addFacetValue($facetName, $facetValue)
114 18
            ->getAsArray();
115
116 18
        $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
117 18
        $additionalArguments = is_array($additionalArguments) ? $additionalArguments : [];
0 ignored issues
show
introduced by
The condition is_array($additionalArguments) is always true.
Loading history...
118
119 18
        $arguments = $persistentAndFacetArguments + $additionalArguments;
120
121 18
        $this->sortFilterParametersIfNecessary($previousSearchRequest, $arguments);
122
123 18
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
124 18
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
125
    }
126
127
    /**
128
     * Removes all other facet values for this name and only set's the passed value for the facet.
129
     *
130
     * @param SearchRequest $previousSearchRequest
131
     * @param $facetName
132
     * @param $facetValue
133
     * @return string
134
     */
135 1
    public function getSetFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue): string
136
    {
137 1
        $previousSearchRequest = $previousSearchRequest
138 1
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeAllFacetValuesByName($facetName);
139
140 1
        return $this->getAddFacetValueUri($previousSearchRequest, $facetName, $facetValue);
141
    }
142
143
    /**
144
     * @param SearchRequest $previousSearchRequest
145
     * @param $facetName
146
     * @param $facetValue
147
     * @return string
148
     */
149 5
    public function getRemoveFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue): string
150
    {
151 5
        $persistentAndFacetArguments = $previousSearchRequest
152 5
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeFacetValue($facetName, $facetValue)
153 5
            ->getAsArray();
154
155 5
        $additionalArguments = [];
156 5
        if ($previousSearchRequest->getContextTypoScriptConfiguration()->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl()) {
157 4
            $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
158
        }
159 5
        $arguments = $persistentAndFacetArguments + $additionalArguments;
160
161 5
        $this->sortFilterParametersIfNecessary($previousSearchRequest, $arguments);
162
163 5
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
164 5
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
165
    }
166
167
    /**
168
     * @param SearchRequest $previousSearchRequest
169
     * @param $facetName
170
     * @return string
171
     */
172 1
    public function getRemoveFacetUri(SearchRequest $previousSearchRequest, $facetName): string
173
    {
174 1
        $persistentAndFacetArguments = $previousSearchRequest
175 1
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeAllFacetValuesByName($facetName)
176 1
            ->getAsArray();
177
178 1
        $additionalArguments = [];
179 1
        if ($previousSearchRequest->getContextTypoScriptConfiguration()->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl()) {
180
            $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
181
        }
182
183 1
        $arguments = $persistentAndFacetArguments + $additionalArguments;
184
185 1
        $this->sortFilterParametersIfNecessary($previousSearchRequest, $arguments);
186
187 1
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
188 1
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
189
    }
190
191
    /**
192
     * @param SearchRequest $previousSearchRequest
193
     * @return string
194
     */
195 4
    public function getRemoveAllFacetsUri(SearchRequest $previousSearchRequest): string
196
    {
197 4
        $persistentAndFacetArguments = $previousSearchRequest
198 4
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeAllFacets()
199 4
            ->getAsArray();
200
201 4
        $additionalArguments = [];
202 4
        if ($previousSearchRequest->getContextTypoScriptConfiguration()->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl()) {
203 4
            $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
204
        }
205
206 4
        $arguments = $persistentAndFacetArguments + $additionalArguments;
207
208 4
        $this->sortFilterParametersIfNecessary($previousSearchRequest, $arguments);
209
210 4
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
211 4
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
212
    }
213
214
    /**
215
     * @param SearchRequest $previousSearchRequest
216
     * @param $page
217
     * @return string
218
     */
219 27
    public function getResultPageUri(SearchRequest $previousSearchRequest, $page): string
220
    {
221 27
        $persistentAndFacetArguments = $previousSearchRequest
222 27
            ->getCopyForSubRequest()->setPage($page)
223 27
            ->getAsArray();
224
225 27
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
226 27
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
227
    }
228
229
    /**
230
     * @param SearchRequest $previousSearchRequest
231
     * @param GroupItem $groupItem
232
     * @param int $page
233
     * @return string
234
     */
235 1
    public function getResultGroupItemPageUri(SearchRequest $previousSearchRequest, GroupItem $groupItem, int $page): string
236
    {
237 1
        $persistentAndFacetArguments = $previousSearchRequest
238 1
            ->getCopyForSubRequest()->setGroupItemPage($groupItem->getGroup()->getGroupName(), $groupItem->getGroupValue(), $page)
239 1
            ->getAsArray();
240 1
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
241 1
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
242
    }
243
    /**
244
     * @param SearchRequest $previousSearchRequest
245
     * @param $queryString
246
     * @return string
247
     */
248 1
    public function getNewSearchUri(SearchRequest $previousSearchRequest, $queryString): string
249
    {
250
        /** @var $request SearchRequest */
251 1
        $contextConfiguration = $previousSearchRequest->getContextTypoScriptConfiguration();
252 1
        $contextSystemLanguage = $previousSearchRequest->getContextSystemLanguageUid();
253 1
        $contextPageUid = $previousSearchRequest->getContextPageUid();
254
255 1
        $request = GeneralUtility::makeInstance(
256 1
            SearchRequest::class,
257 1
            [],
258 1
            /** @scrutinizer ignore-type */ $contextPageUid,
259 1
            /** @scrutinizer ignore-type */ $contextSystemLanguage,
260 1
            /** @scrutinizer ignore-type */ $contextConfiguration
261
        );
262 1
        $arguments = $request->setRawQueryString($queryString)->getAsArray();
263
264 1
        $this->sortFilterParametersIfNecessary($previousSearchRequest, $arguments);
265
266 1
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
267 1
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
268
    }
269
270
    /**
271
     * @param SearchRequest $previousSearchRequest
272
     * @param $sortingName
273
     * @param $sortingDirection
274
     * @return string
275
     */
276 1
    public function getSetSortingUri(SearchRequest $previousSearchRequest, $sortingName, $sortingDirection): string
277
    {
278 1
        $persistentAndFacetArguments = $previousSearchRequest
279 1
            ->getCopyForSubRequest()->setSorting($sortingName, $sortingDirection)
280 1
            ->getAsArray();
281
282 1
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
283 1
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
284
    }
285
286
    /**
287
     * @param SearchRequest $previousSearchRequest
288
     * @return string
289
     */
290
    public function getRemoveSortingUri(SearchRequest $previousSearchRequest): string
291
    {
292
        $persistentAndFacetArguments = $previousSearchRequest
293
            ->getCopyForSubRequest()->removeSorting()
294
            ->getAsArray();
295
296
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
297
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
298
    }
299
300
    /**
301
     * @param SearchRequest $previousSearchRequest
302
     * @return string
303
     */
304 22
    public function getCurrentSearchUri(SearchRequest $previousSearchRequest): string
305
    {
306 22
        $persistentAndFacetArguments = $previousSearchRequest
307 22
            ->getCopyForSubRequest()
308 22
            ->getAsArray();
309
310 22
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
311 22
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
312
    }
313
314
    /**
315
     * @param SearchRequest $request
316
     * @return array
317
     */
318 19
    protected function getAdditionalArgumentsFromRequestConfiguration(SearchRequest $request): array
319
    {
320 19
        if ($request->getContextTypoScriptConfiguration() == null) {
321
            return [];
322
        }
323
324 19
        $reQuestId = $request->getId();
325 19
        if (isset(self::$additionalArgumentsCache[$reQuestId])) {
326 13
            return self::$additionalArgumentsCache[$reQuestId];
327
        }
328
329 19
        self::$additionalArgumentsCache[$reQuestId] = $request->getContextTypoScriptConfiguration()
330 19
            ->getSearchFacetingFacetLinkUrlParametersAsArray();
331
332 19
        return self::$additionalArgumentsCache[$reQuestId];
333
    }
334
335
    /**
336
     * @param SearchRequest $request
337
     * @return int|null
338
     */
339 35
    protected function getTargetPageUidFromRequestConfiguration(SearchRequest $request): ?int
340
    {
341 35
        if ($request->getContextTypoScriptConfiguration() == null) {
342
            return null;
343
        }
344
345 35
        return $request->getContextTypoScriptConfiguration()->getSearchTargetPage();
346
    }
347
348
    /**
349
     * Build the link with an i memory cache that reduces the amount of required typolink calls.
350
     *
351
     * @param int|null $pageUid
352
     * @param array $arguments
353
     * @return string
354
     */
355 35
    protected function buildLinkWithInMemoryCache(?int $pageUid, array $arguments): string
356
    {
357 35
        $values = [];
358 35
        $structure = $arguments;
359 35
        $this->getSubstitution($structure, $values);
360 35
        $hash = md5($pageUid . json_encode($structure));
361 35
        if (isset(self::$preCompiledLinks[$hash])) {
362 26
            self::$hitCount++;
363 26
            $uriCacheTemplate = self::$preCompiledLinks[$hash];
364
        } else {
365 35
            self::$missCount++;
366 35
            $this->uriBuilder->reset()->setTargetPageUid($pageUid);
0 ignored issues
show
Bug introduced by
It seems like $pageUid can also be of type null; however, parameter $targetPageUid of TYPO3\CMS\Extbase\Mvc\We...der::setTargetPageUid() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

366
            $this->uriBuilder->reset()->setTargetPageUid(/** @scrutinizer ignore-type */ $pageUid);
Loading history...
367 35
            $uriCacheTemplate = $this->uriBuilder->setArguments($structure)->build();
368
369
            /* @var UrlHelper $urlHelper */
370 35
            $urlHelper = GeneralUtility::makeInstance(UrlHelper::class, $uriCacheTemplate);
371 35
            self::$preCompiledLinks[$hash] = (string)$urlHelper;
372
        }
373
374 35
        $keys = array_map(function($value) {
375 33
            return urlencode($value);
376 35
        }, array_keys($values));
377 35
        $values = array_map(function($value) {
378 33
            return urlencode($value);
379 35
        }, $values);
380
381 35
        $routingConfigurations = $this->routingService
382 35
            ->fetchEnhancerByPageUid($pageUid);
0 ignored issues
show
Bug introduced by
It seems like $pageUid can also be of type null; however, parameter $pageUid of ApacheSolrForTypo3\Solr\...etchEnhancerByPageUid() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

382
            ->fetchEnhancerByPageUid(/** @scrutinizer ignore-type */ $pageUid);
Loading history...
383 35
        $enhancedRouting = count($routingConfigurations) > 0;
384 35
        $this->routingService->reset();
385 35
        if ($enhancedRouting && is_array($routingConfigurations[0] ?? null)) {
386
            $this->routingService->fromRoutingConfiguration($routingConfigurations[0]);
387
        }
388
389
        /* @var Uri $uri */
390 35
        $uri = GeneralUtility::makeInstance(
391 35
            Uri::class,
392 35
            $uriCacheTemplate
393
        );
394
395 35
        $urlEvent = new BeforeReplaceVariableInCachedUrlEvent($uri, $enhancedRouting);
396
        /* @var BeforeReplaceVariableInCachedUrlEvent $urlEvent */
397 35
        $urlEvent = $this->eventDispatcher->dispatch($urlEvent);
398 35
        $uriCacheTemplate = (string)$urlEvent->getUri();
399
400 35
        $variableEvent = new BeforeProcessCachedVariablesEvent(
401 35
            $uri,
402
            $routingConfigurations,
403
            $keys,
404
            $values
405
        );
406 35
        $this->eventDispatcher->dispatch($variableEvent);
407
408 35
        $values = $variableEvent->getVariableValues();
409
        // Take care that everything is urlencoded!
410 35
        $keys = array_map(function($value) {
411
            // @TODO: With only PHP 8 support, replace this with str_contains()
412 33
            if (strpos($value, '###') === false) {
413
                return $value;
414
            }
415 33
            return urlencode($value);
416 35
        }, array_keys($values));
417
418 35
        $uri = str_replace($keys, $values, $uriCacheTemplate);
419 35
        $uri = GeneralUtility::makeInstance(
420 35
            Uri::class,
421 35
            $uri
422
        );
423 35
        $uriEvent = new PostProcessUriEvent($uri, $routingConfigurations);
424 35
        $this->eventDispatcher->dispatch($uriEvent);
425 35
        $uri = $uriEvent->getUri();
426 35
        return (string)$uri;
427
    }
428
429
    /**
430
     * Flushes the internal in memory cache.
431
     *
432
     * @return void
433
     */
434 10
    public function flushInMemoryCache()
435
    {
436 10
        self::$preCompiledLinks = [];
437 10
    }
438
439
    /**
440
     * This method is used to build two arrays from a nested array. The first one represents the structure.
441
     * In this structure the values are replaced with the pass to the value. At the same time the values get collected
442
     * in the $values array, with the path as key. This can be used to build a comparable hash from the arguments
443
     * in order to reduce the amount of typolink calls
444
     *
445
     *
446
     * Example input
447
     *
448
     * $data = [
449
     *  'foo' => [
450
     *      'bar' => 111
451
     *   ]
452
     * ]
453
     *
454
     * will return:
455
     *
456
     * $structure = [
457
     *  'foo' => [
458
     *      'bar' => '###foo:bar###'
459
     *   ]
460
     * ]
461
     *
462
     * $values = [
463
     *  '###foo:bar###' => 111
464
     * ]
465
     *
466
     * @param array $structure
467
     * @param array $values
468
     * @param array $branch
469
     */
470 35
    protected function getSubstitution(array &$structure, array  &$values, array $branch = []): void
471
    {
472
        /*
473
         * Adds information about the filter facet to the placeholder.
474
         *
475
         * This feature allows to handle even placeholder in RouteEnhancer
476
         */
477 35
        $filter = false;
478 35
        if (count($branch) > 0 && $branch[count($branch) - 1] === 'filter') {
479 23
            $filter = true;
480
        }
481 35
        foreach ($structure as $key => &$value) {
482 35
            $branch[] = $key;
483 35
            if (is_array($value)) {
484 35
                $this->getSubstitution($value, $values, $branch);
485
            } else {
486 33
                if ($filter) {
487 21
                    [$facetType, $facetValue] = explode(':', $value);
488 21
                    $branch[] = $facetType;
489
                }
490 33
                $path = '###' . implode(':', $branch) . '###';
491 33
                $values[$path] = $value;
492 33
                $structure[$key] = $path;
493 33
                if ($filter) {
494 21
                    array_pop($branch);
495
                }
496
            }
497 35
            array_pop($branch);
498
        }
499 35
    }
500
501
    /**
502
     * Sorts filter arguments if enabled.
503
     *
504
     *
505
     * @param SearchRequest $searchRequest
506
     * @param array $arguments
507
     */
508 22
    protected function sortFilterParametersIfNecessary(SearchRequest $searchRequest, array &$arguments)
509
    {
510 22
        if (!$searchRequest->isActiveFacetsSorted()) {
511 22
            return;
512
        }
513
514
        $pluginNameSpace = $searchRequest->getContextTypoScriptConfiguration()->getSearchPluginNamespace();
515
        if (!empty($arguments[$pluginNameSpace]['filter']) && is_array($arguments[$pluginNameSpace]['filter'])) {
516
            $arguments[$pluginNameSpace]['filter'] = ParameterSortingUtility::sortByType(
517
                $arguments[$pluginNameSpace]['filter'],
518
                $searchRequest->getActiveFacetsUrlParameterStyle()
519
            );
520
        }
521
    }
522
}
523