Passed
Push — master ( 2081ab...b6ff0f )
by Rafael
36:21
created

getAdditionalArgumentsFromRequestConfiguration()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3.3332

Importance

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

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

330
            /** @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...
331
332 8
            self::$preCompiledLinks[$hash] = (string)$urlHelper;
333
        }
334
335
        $keys = array_map(function($value) {
336 6
            return urlencode($value);
337 8
        }, array_keys($values));
338
        $values = array_map(function($value) {
339 6
            return urlencode($value);
340 8
        }, $values);
341 8
        $uri = str_replace($keys, $values, $uriCacheTemplate);
342 8
        return $uri;
343
    }
344
345
    /**
346
     * Flushes the internal in memory cache.
347
     *
348
     * @return void
349
     */
350 8
    public function flushInMemoryCache()
351
    {
352 8
        self::$preCompiledLinks = [];
353 8
    }
354
355
    /**
356
     * This method is used to build two arrays from a nested array. The first one represents the structure.
357
     * In this structure the values are replaced with the pass to the value. At the same time the values get collected
358
     * in the $values array, with the path as key. This can be used to build a comparable hash from the arguments
359
     * in order to reduce the amount of typolink calls
360
     *
361
     *
362
     * Example input
363
     *
364
     * $data = [
365
     *  'foo' => [
366
     *      'bar' => 111
367
     *   ]
368
     * ]
369
     *
370
     * will return:
371
     *
372
     * $structure = [
373
     *  'foo' => [
374
     *      'bar' => '###foo:bar###'
375
     *   ]
376
     * ]
377
     *
378
     * $values = [
379
     *  '###foo:bar###' => 111
380
     * ]
381
     *
382
     * @param $structure
383
     * @param $values
384
     * @param array $branch
385
     */
386 8
    protected function getSubstitution(array &$structure, array  &$values, array $branch = [])
387
    {
388 8
        foreach ($structure as $key => &$value) {
389 8
            $branch[] = $key;
390 8
            if (is_array($value)) {
391 8
                $this->getSubstitution($value, $values, $branch);
392
            } else {
393 6
                $path = '###' . implode(':', $branch) . '###';
394 6
                $values[$path] = $value;
395 6
                $structure[$key] = $path;
396
            }
397
        }
398 8
    }
399
}
400