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

getTargetPageUidFromRequestConfiguration()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 7
rs 10
ccs 3
cts 3
cp 1
cc 2
nc 2
nop 1
crap 2
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 35
     */
68
    protected static $additionalArgumentsCache = [];
69 35
70 35
    /**
71
     * @var EventDispatcherInterface
72
     */
73
    protected $eventDispatcher;
74
75
    /**
76
     * @var RoutingService
77
     */
78 30
    protected $routingService;
79
80
    /**
81 30
     * @param UriBuilder $uriBuilder
82 30
     */
83
    public function injectUriBuilder(UriBuilder $uriBuilder)
84 30
    {
85 30
        $this->uriBuilder = $uriBuilder;
86
    }
87 30
88
    /**
89 30
     * @param RoutingService $routingService
90
     */
91 30
    public function injectRoutingService(RoutingService $routingService)
92 30
    {
93
        $this->routingService = $routingService;
94
    }
95
96
    /**
97
     * @param EventDispatcherInterface $eventDispatcher
98
     */
99
    public function injectEventDispatcher(EventDispatcherInterface $eventDispatcher)
100
    {
101
        $this->eventDispatcher = $eventDispatcher;
102
    }
103 1
104
    /**
105
     * @param SearchRequest $previousSearchRequest
106 1
     * @param $facetName
107
     * @param $facetValue
108 1
     * @return string
109
     */
110
    public function getAddFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue): string
111
    {
112
        $persistentAndFacetArguments = $previousSearchRequest
113
            ->getCopyForSubRequest()->removeAllGroupItemPages()->addFacetValue($facetName, $facetValue)
114
            ->getAsArray();
115
116
        $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
117 4
        $additionalArguments = is_array($additionalArguments) ? $additionalArguments : [];
0 ignored issues
show
introduced by
The condition is_array($additionalArguments) is always true.
Loading history...
118
119
        $arguments = $persistentAndFacetArguments + $additionalArguments;
120 4
121 4
        $this->sortFilterParametersIfNecessary($previousSearchRequest, $arguments['tx_solr']['filter']);
122
123 4
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
124 4
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
125 4
    }
126
127 4
    /**
128
     * Removes all other facet values for this name and only set's the passed value for the facet.
129 4
     *
130
     * @param SearchRequest $previousSearchRequest
131 4
     * @param $facetName
132 4
     * @param $facetValue
133
     * @return string
134
     */
135
    public function getSetFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue): string
136
    {
137
        $previousSearchRequest = $previousSearchRequest
138
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeAllFacetValuesByName($facetName);
139
140
        return $this->getAddFacetValueUri($previousSearchRequest, $facetName, $facetValue);
141
    }
142
143
    /**
144
     * @param SearchRequest $previousSearchRequest
145
     * @param $facetName
146
     * @param $facetValue
147
     * @return string
148
     */
149
    public function getRemoveFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue): string
150
    {
151
        $persistentAndFacetArguments = $previousSearchRequest
152
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeFacetValue($facetName, $facetValue)
153
            ->getAsArray();
154
155
        $additionalArguments = [];
156
        if ($previousSearchRequest->getContextTypoScriptConfiguration()->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl()) {
157
            $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
158
        }
159
        $arguments = $persistentAndFacetArguments + $additionalArguments;
160
161
        $this->sortFilterParametersIfNecessary($previousSearchRequest, $arguments['tx_solr']['filter']);
162
163 4
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
164
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
165
    }
166 4
167 4
    /**
168
     * @param SearchRequest $previousSearchRequest
169 4
     * @param $facetName
170 4
     * @return string
171 4
     */
172
    public function getRemoveFacetUri(SearchRequest $previousSearchRequest, $facetName): string
173
    {
174 4
        $persistentAndFacetArguments = $previousSearchRequest
175
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeAllFacetValuesByName($facetName)
176 4
            ->getAsArray();
177
178 4
        $additionalArguments = [];
179 4
        if ($previousSearchRequest->getContextTypoScriptConfiguration()->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl()) {
180
            $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
181
        }
182
183
        $arguments = $persistentAndFacetArguments + $additionalArguments;
184
185
        $this->sortFilterParametersIfNecessary($previousSearchRequest, $arguments['tx_solr']['filter']);
186
187 12
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
188
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
189
    }
190 12
191 12
    /**
192
     * @param SearchRequest $previousSearchRequest
193 12
     * @return string
194 12
     */
195
    public function getRemoveAllFacetsUri(SearchRequest $previousSearchRequest): string
196
    {
197
        $persistentAndFacetArguments = $previousSearchRequest
198
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeAllFacets()
199
            ->getAsArray();
200
201
        $additionalArguments = [];
202
        if ($previousSearchRequest->getContextTypoScriptConfiguration()->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl()) {
203
            $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
204
        }
205
206
        $arguments = $persistentAndFacetArguments + $additionalArguments;
207
208
        $this->sortFilterParametersIfNecessary($previousSearchRequest, $arguments['tx_solr']['filter']);
209
210
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
211
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
212
    }
213
214
    /**
215
     * @param SearchRequest $previousSearchRequest
216 34
     * @param $page
217
     * @return string
218
     */
219 34
    public function getResultPageUri(SearchRequest $previousSearchRequest, $page): string
220 34
    {
221 34
        $persistentAndFacetArguments = $previousSearchRequest
222
            ->getCopyForSubRequest()->setPage($page)
223 34
            ->getAsArray();
224 34
225 34
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
226 34
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
227 34
    }
228 34
229
    /**
230 34
     * @param SearchRequest $previousSearchRequest
231
     * @param GroupItem $groupItem
232 34
     * @param int $page
233
     * @return string
234 34
     */
235 34
    public function getResultGroupItemPageUri(SearchRequest $previousSearchRequest, GroupItem $groupItem, int $page): string
236
    {
237
        $persistentAndFacetArguments = $previousSearchRequest
238
            ->getCopyForSubRequest()->setGroupItemPage($groupItem->getGroup()->getGroupName(), $groupItem->getGroupValue(), $page)
239
            ->getAsArray();
240
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
241
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
242
    }
243
    /**
244 32
     * @param SearchRequest $previousSearchRequest
245
     * @param $queryString
246
     * @return string
247 32
     */
248 32
    public function getNewSearchUri(SearchRequest $previousSearchRequest, $queryString): string
249
    {
250 32
        /** @var $request SearchRequest */
251 32
        $contextConfiguration = $previousSearchRequest->getContextTypoScriptConfiguration();
252
        $contextSystemLanguage = $previousSearchRequest->getContextSystemLanguageUid();
253
        $contextPageUid = $previousSearchRequest->getContextPageUid();
254
255
        $request = GeneralUtility::makeInstance(
256
            SearchRequest::class,
257
            [],
258 32
            /** @scrutinizer ignore-type */ $contextPageUid,
259
            /** @scrutinizer ignore-type */ $contextSystemLanguage,
260
            /** @scrutinizer ignore-type */ $contextConfiguration
261 32
        );
262 32
        $arguments = $request->setRawQueryString($queryString)->getAsArray();
263
264 32
        $this->sortFilterParametersIfNecessary($previousSearchRequest, $arguments['tx_solr']['filter']);
265 32
266
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
267
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
268
    }
269
270
    /**
271
     * @param SearchRequest $previousSearchRequest
272 28
     * @param $sortingName
273
     * @param $sortingDirection
274
     * @return string
275 28
     */
276 28
    public function getSetSortingUri(SearchRequest $previousSearchRequest, $sortingName, $sortingDirection): string
277
    {
278
        $persistentAndFacetArguments = $previousSearchRequest
279 28
            ->getCopyForSubRequest()->setSorting($sortingName, $sortingDirection)
280 28
            ->getAsArray();
281
282
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
283
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
284
    }
285
286
    /**
287 31
     * @param SearchRequest $previousSearchRequest
288
     * @return string
289 31
     */
290
    public function getRemoveSortingUri(SearchRequest $previousSearchRequest): string
291
    {
292
        $persistentAndFacetArguments = $previousSearchRequest
293 31
            ->getCopyForSubRequest()->removeSorting()
294 31
            ->getAsArray();
295 31
296
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
297
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
298 31
    }
299 31
300
    /**
301 31
     * @param SearchRequest $previousSearchRequest
302
     * @return string
303
     */
304
    public function getCurrentSearchUri(SearchRequest $previousSearchRequest): string
305
    {
306
        $persistentAndFacetArguments = $previousSearchRequest
307
            ->getCopyForSubRequest()
308 35
            ->getAsArray();
309
310 35
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
311
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
312
    }
313
314 35
    /**
315
     * @param SearchRequest $request
316
     * @return array
317
     */
318
    protected function getAdditionalArgumentsFromRequestConfiguration(SearchRequest $request): array
319
    {
320
        if ($request->getContextTypoScriptConfiguration() == null) {
321
            return [];
322
        }
323
324 35
        $reQuestId = $request->getId();
325
        if (isset(self::$additionalArgumentsCache[$reQuestId])) {
326 35
            return self::$additionalArgumentsCache[$reQuestId];
327 35
        }
328 35
329 35
        self::$additionalArgumentsCache[$reQuestId] = $request->getContextTypoScriptConfiguration()
330 35
            ->getSearchFacetingFacetLinkUrlParametersAsArray();
331 33
332 33
        return self::$additionalArgumentsCache[$reQuestId];
333
    }
334 35
335 35
    /**
336 35
     * @param SearchRequest $request
337
     * @return int|null
338
     */
339
    protected function getTargetPageUidFromRequestConfiguration(SearchRequest $request): ?int
340
    {
341
        if ($request->getContextTypoScriptConfiguration() == null) {
342 35
            return null;
343 35
        }
344
345 35
        return $request->getContextTypoScriptConfiguration()->getSearchTargetPage();
346
    }
347
348
    /**
349 35
     * Build the link with an i memory cache that reduces the amount of required typolink calls.
350 35
     *
351
     * @param int|null $pageUid
352 35
     * @param array $arguments
353 35
     * @return string
354 35
     */
355 35
    protected function buildLinkWithInMemoryCache(?int $pageUid, array $arguments): string
356
    {
357
        $values = [];
358
        $structure = $arguments;
359
        $this->getSubstitution($structure, $values);
360
        $hash = md5($pageUid . json_encode($structure));
361
        if (isset(self::$preCompiledLinks[$hash])) {
362
            self::$hitCount++;
363
            $uriCacheTemplate = self::$preCompiledLinks[$hash];
364
        } else {
365
            self::$missCount++;
366
            $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
            $uriCacheTemplate = $this->uriBuilder->setArguments($structure)->setUseCacheHash(false)->build();
0 ignored issues
show
Unused Code introduced by
The call to TYPO3\CMS\Extbase\Mvc\We...lder::setUseCacheHash() has too many arguments starting with false. ( Ignorable by Annotation )

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

367
            $uriCacheTemplate = $this->uriBuilder->setArguments($structure)->/** @scrutinizer ignore-call */ setUseCacheHash(false)->build();

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
368
369
            /* @var UrlHelper $urlHelper */
370
            $urlHelper = GeneralUtility::makeInstance(UrlHelper::class, $uriCacheTemplate);
371
            self::$preCompiledLinks[$hash] = (string)$urlHelper;
372
        }
373
374
        $keys = array_map(function($value) {
375
            return urlencode($value);
376
        }, array_keys($values));
377
        $values = array_map(function($value) {
378
            return urlencode($value);
379
        }, $values);
380
381
        $routingConfigurations = $this->routingService
382
            ->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
        $enhancedRouting = count($routingConfigurations) > 0;
384
        $this->routingService->reset();
385
        if ($enhancedRouting && is_array($routingConfigurations[0])) {
386
            $this->routingService->fromRoutingConfiguration($routingConfigurations[0]);
387
        }
388
389
        /* @var Uri $uri */
390
        $uri = GeneralUtility::makeInstance(
391
            Uri::class,
392
            $uriCacheTemplate
393
        );
394
395
        $urlEvent = new BeforeReplaceVariableInCachedUrlEvent($uri, $enhancedRouting);
396
        /* @var BeforeReplaceVariableInCachedUrlEvent $urlEvent */
397
        $urlEvent = $this->eventDispatcher->dispatch($urlEvent);
398
        $uriCacheTemplate = (string)$urlEvent->getUri();
399 35
400
        $variableEvent = new BeforeProcessCachedVariablesEvent(
401 35
            $uri,
402 35
            $routingConfigurations,
403 35
            $keys,
404 35
            $values
405
        );
406 35
        $this->eventDispatcher->dispatch($variableEvent);
407 35
408 35
        $values = $variableEvent->getVariableValues();
409
        // Take care that everything is urlencoded!
410
        $keys = array_map(function($value) {
411 35
            // @TODO: With only PHP 8 support, replace this with str_contains()
412
            if (strpos($value, '###') === false) {
413
                return $value;
414
            }
415
            return urlencode($value);
416
        }, array_keys($values));
417
418
        $uri = str_replace($keys, $values, $uriCacheTemplate);
419
        $uri = GeneralUtility::makeInstance(
420 35
            Uri::class,
421
            $uri
422 35
        );
423
        $uriEvent = new PostProcessUriEvent($uri, $routingConfigurations);
424
        $this->eventDispatcher->dispatch($uriEvent);
425
        $uri = $uriEvent->getUri();
426
        return (string)$uri;
427
    }
428 35
429
    /**
430
     * Flushes the internal in memory cache.
431
     *
432
     * @return void
433
     */
434
    public function flushInMemoryCache()
435
    {
436
        self::$preCompiledLinks = [];
437
    }
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
    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
        $filter = false;
478
        if (count($branch) > 0 && $branch[count($branch) - 1] === 'filter') {
479
            $filter = true;
480
        }
481
        foreach ($structure as $key => &$value) {
482
            $branch[] = $key;
483
            if (is_array($value)) {
484
                $this->getSubstitution($value, $values, $branch);
485
            } else {
486
                if ($filter) {
487
                    [$facetType, $facetValue] = explode(':', $value);
488
                    $branch[] = $facetType;
489
                }
490
                $path = '###' . implode(':', $branch) . '###';
491
                $values[$path] = $value;
492
                $structure[$key] = $path;
493
                if ($filter) {
494
                    array_pop($branch);
495
                }
496
            }
497
            array_pop($branch);
498
        }
499
    }
500
501
    /**
502
     * Sorts filter arguments if enabled.
503
     *
504
     *
505
     * @param SearchRequest $searchRequest
506
     * @param array|null $filterArguments
507
     */
508
    protected function sortFilterParametersIfNecessary(SearchRequest $searchRequest, ?array &$filterArguments)
509
    {
510
        if (is_array($filterArguments) && !empty($filterArguments) && $searchRequest->isActiveFacetsSorted()) {
511
            ParameterSortingUtility::sortByType(
512
                $filterArguments,
513
                $searchRequest->getActiveFacetsUrlParameterStyle()
514
            );
515
        }
516
    }
517
}
518