Passed
Push — master ( 498335...52b5ef )
by Rafael
42:19
created

SearchUriBuilder::getNewSearchUri()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 1.0019

Importance

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

336
            $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...
337
338
            // even if we call build with disabled cHash in TYPO3 9 a cHash will be generated when site management is active
339
            // to prevent wrong cHashes we remove the cHash here from the cached uri template.
340
            // @todo: This can be removed when https://forge.typo3.org/issues/87120 is resolved and we can ship a proper configuration
341
            /* @var UrlHelper $urlHelper */
342 35
            $urlHelper = GeneralUtility::makeInstance(UrlHelper::class, $uriCacheTemplate);
343 35
            $urlHelper->removeQueryParameter('cHash');
0 ignored issues
show
Deprecated Code introduced by
The function ApacheSolrForTypo3\Solr\...:removeQueryParameter() has been deprecated: Will be removed with v12. Use withoutQueryParameter instead. ( Ignorable by Annotation )

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

343
            /** @scrutinizer ignore-deprecated */ $urlHelper->removeQueryParameter('cHash');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
344
345 35
            self::$preCompiledLinks[$hash] = (string)$urlHelper;
346
        }
347
348
        $keys = array_map(function($value) {
349 35
            return urlencode($value);
350 35
        }, array_keys($values));
351
        $values = array_map(function($value) {
352 35
            return urlencode($value);
353 35
        }, $values);
354 35
        $uri = str_replace($keys, $values, $uriCacheTemplate);
355 35
        return $uri;
356
    }
357
358
    /**
359
     * Flushes the internal in memory cache.
360
     *
361
     * @return void
362
     */
363
    public function flushInMemoryCache()
364
    {
365
        self::$preCompiledLinks = [];
366
    }
367
368
    /**
369
     * This method is used to build two arrays from a nested array. The first one represents the structure.
370
     * In this structure the values are replaced with the pass to the value. At the same time the values get collected
371
     * in the $values array, with the path as key. This can be used to build a comparable hash from the arguments
372
     * in order to reduce the amount of typolink calls
373
     *
374
     *
375
     * Example input
376
     *
377
     * $data = [
378
     *  'foo' => [
379
     *      'bar' => 111
380
     *   ]
381
     * ]
382
     *
383
     * will return:
384
     *
385
     * $structure = [
386
     *  'foo' => [
387
     *      'bar' => '###foo:bar###'
388
     *   ]
389
     * ]
390
     *
391
     * $values = [
392
     *  '###foo:bar###' => 111
393
     * ]
394
     *
395
     * @param array $structure
396
     * @param array $values
397
     * @param array $branch
398
     */
399 35
    protected function getSubstitution(array &$structure, array  &$values, array $branch = [])
400
    {
401 35
        foreach ($structure as $key => &$value) {
402 35
            $branch[] = $key;
403 35
            if (is_array($value)) {
404 35
                $this->getSubstitution($value, $values, $branch);
405
            } else {
406 35
                $path = '###' . implode(':', $branch) . '###';
407 35
                $values[$path] = $value;
408 35
                $structure[$key] = $path;
409
            }
410
        }
411 35
    }
412
413
    /**
414
     * Sorts filter arguments if enabled.
415
     *
416
     *
417
     * @param SearchRequest $searchRequest
418
     * @param array|null $filterArguments
419
     */
420 35
    protected function sortFilterParametersIfNecessary(SearchRequest $searchRequest, ?array &$filterArguments)
421
    {
422 35
        if (is_array($filterArguments) && !empty($filterArguments) && $searchRequest->isActiveFacetsSorted()) {
423
            ParameterSortingUtility::sortByType(
424
                $filterArguments,
425
                $searchRequest->getActiveFacetsUrlParameterStyle()
426
            );
427
        }
428 35
    }
429
}
430