Passed
Pull Request — master (#2705)
by
unknown
39:37 queued 36:20
created

SearchUriBuilder::injectUriBuilder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1.037

Importance

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

364
            $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...
365
366
            // even if we call build with disabled cHash in TYPO3 9 a cHash will be generated when site management is active
367
            // to prevent wrong cHashes we remove the cHash here from the cached uri template.
368
            // @todo: This can be removed when https://forge.typo3.org/issues/87120 is resolved and we can ship a proper configuration
369
            /* @var UrlHelper $urlHelper */
370 35
            $urlHelper = GeneralUtility::makeInstance(UrlHelper::class, $uriCacheTemplate);
371 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

371
            /** @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...
372
373 35
            self::$preCompiledLinks[$hash] = (string)$urlHelper;
374
        }
375
376
        $keys = array_map(function($value) {
377 35
            return urlencode($value);
378 35
        }, array_keys($values));
379
        $values = array_map(function($value) {
380 35
            return urlencode($value);
381 35
        }, $values);
382 35
        $uri = str_replace($keys, $values, $uriCacheTemplate);
383 35
        return $uri;
384
    }
385
386
    /**
387
     * Flushes the internal in memory cache.
388
     *
389
     * @return void
390
     */
391
    public function flushInMemoryCache()
392
    {
393
        self::$preCompiledLinks = [];
394
    }
395
396
    /**
397
     * This method is used to build two arrays from a nested array. The first one represents the structure.
398
     * In this structure the values are replaced with the pass to the value. At the same time the values get collected
399
     * in the $values array, with the path as key. This can be used to build a comparable hash from the arguments
400
     * in order to reduce the amount of typolink calls
401
     *
402
     *
403
     * Example input
404
     *
405
     * $data = [
406
     *  'foo' => [
407
     *      'bar' => 111
408
     *   ]
409
     * ]
410
     *
411
     * will return:
412
     *
413
     * $structure = [
414
     *  'foo' => [
415
     *      'bar' => '###foo:bar###'
416
     *   ]
417
     * ]
418
     *
419
     * $values = [
420
     *  '###foo:bar###' => 111
421
     * ]
422
     *
423
     * @param $structure
424
     * @param $values
425
     * @param array $branch
426
     */
427 35
    protected function getSubstitution(array &$structure, array  &$values, array $branch = [])
428
    {
429 35
        foreach ($structure as $key => &$value) {
430 35
            $branch[] = $key;
431 35
            if (is_array($value)) {
432 35
                $this->getSubstitution($value, $values, $branch);
433
            } else {
434 35
                $path = '###' . implode(':', $branch) . '###';
435 35
                $values[$path] = $value;
436 35
                $structure[$key] = $path;
437
            }
438
        }
439 35
    }
440
}
441