Passed
Push — master ( d4f966...dddccf )
by Rafael
34:04 queued 25:52
created

SearchUriBuilder::getNewSearchUri()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 16
ccs 12
cts 12
cp 1
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 2
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\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 43
    public function injectUriBuilder(UriBuilder $uriBuilder)
67
    {
68 43
        $this->uriBuilder = $uriBuilder;
69 43
    }
70
71
    /**
72
     * @param SearchRequest $previousSearchRequest
73
     * @param $facetName
74
     * @param $facetValue
75
     * @return string
76
     */
77 34
    public function getAddFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue)
78
    {
79
        $persistentAndFacetArguments = $previousSearchRequest
80 34
            ->getCopyForSubRequest()->removeAllGroupItemPages()->addFacetValue($facetName, $facetValue)
81 34
            ->getAsArray();
82
83 34
        $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
84 34
        $additionalArguments = is_array($additionalArguments) ? $additionalArguments : [];
0 ignored issues
show
introduced by
The condition is_array($additionalArguments) is always true.
Loading history...
85
86 34
        $arguments = $persistentAndFacetArguments + $additionalArguments;
87
88 34
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
89 34
        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 1
    public function getSetFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue)
101
    {
102
        $previousSearchRequest = $previousSearchRequest
103 1
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeAllFacetValuesByName($facetName);
104
105 1
        return $this->getAddFacetValueUri($previousSearchRequest, $facetName, $facetValue);
106
    }
107
108
    /**
109
     * @param SearchRequest $previousSearchRequest
110
     * @param $facetName
111
     * @param $facetValue
112
     * @return string
113
     */
114 5
    public function getRemoveFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue)
115
    {
116
        $persistentAndFacetArguments = $previousSearchRequest
117 5
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeFacetValue($facetName, $facetValue)
118 5
            ->getAsArray();
119
120 5
        $additionalArguments = [];
121 5
        if ($previousSearchRequest->getContextTypoScriptConfiguration()->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl()) {
122 4
            $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
123
        }
124 5
        $arguments = $persistentAndFacetArguments + $additionalArguments;
125
126 5
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
127 5
        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 4
    public function getRemoveAllFacetsUri(SearchRequest $previousSearchRequest)
157
    {
158
        $persistentAndFacetArguments = $previousSearchRequest
159 4
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeAllFacets()
160 4
            ->getAsArray();
161
162 4
        $additionalArguments = [];
163 4
        if ($previousSearchRequest->getContextTypoScriptConfiguration()->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl()) {
164 4
            $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
165
        }
166
167 4
        $arguments = $persistentAndFacetArguments + $additionalArguments;
168
169 4
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
170 4
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
171
    }
172
173
    /**
174
     * @param SearchRequest $previousSearchRequest
175
     * @param $page
176
     * @return string
177
     */
178 12
    public function getResultPageUri(SearchRequest $previousSearchRequest, $page)
179
    {
180
        $persistentAndFacetArguments = $previousSearchRequest
181 12
            ->getCopyForSubRequest()->setPage($page)
182 12
            ->getAsArray();
183
184 12
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
185 12
        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 34
    public function getNewSearchUri(SearchRequest $previousSearchRequest, $queryString)
208
    {
209
        /** @var $request SearchRequest */
210 34
        $contextConfiguration = $previousSearchRequest->getContextTypoScriptConfiguration();
211 34
        $contextSystemLanguage = $previousSearchRequest->getContextSystemLanguageUid();
212 34
        $contextPageUid = $previousSearchRequest->getContextPageUid();
213
214 34
        $request = GeneralUtility::makeInstance(
215 34
            SearchRequest::class, [],
216 34
            /** @scrutinizer ignore-type */ $contextPageUid,
217 34
            /** @scrutinizer ignore-type */ $contextSystemLanguage,
218 34
            /** @scrutinizer ignore-type */ $contextConfiguration);
219 34
        $arguments = $request->setRawQueryString($queryString)->getAsArray();
220
221 34
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
222 34
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
223
    }
224
225
    /**
226
     * @param SearchRequest $previousSearchRequest
227
     * @param $sortingName
228
     * @param $sortingDirection
229
     * @return string
230
     */
231 33
    public function getSetSortingUri(SearchRequest $previousSearchRequest, $sortingName, $sortingDirection)
232
    {
233
        $persistentAndFacetArguments = $previousSearchRequest
234 33
            ->getCopyForSubRequest()->setSorting($sortingName, $sortingDirection)
235 33
            ->getAsArray();
236
237 33
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
238 33
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
239
    }
240
241
    /**
242
     * @param SearchRequest $previousSearchRequest
243
     * @return string
244
     */
245 32
    public function getRemoveSortingUri(SearchRequest $previousSearchRequest)
246
    {
247
        $persistentAndFacetArguments = $previousSearchRequest
248 32
            ->getCopyForSubRequest()->removeSorting()
249 32
            ->getAsArray();
250
251 32
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
252 32
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
253
    }
254
255
    /**
256
     * @param SearchRequest $previousSearchRequest
257
     * @return string
258
     */
259 28
    public function getCurrentSearchUri(SearchRequest $previousSearchRequest)
260
    {
261
        $persistentAndFacetArguments = $previousSearchRequest
262 28
            ->getCopyForSubRequest()
263 28
            ->getAsArray();
264
265
266 28
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
267 28
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
268
    }
269
270
    /**
271
     * @param SearchRequest $request
272
     * @return array
273
     */
274 35
    protected function getAdditionalArgumentsFromRequestConfiguration(SearchRequest $request)
275
    {
276 35
        if ($request->getContextTypoScriptConfiguration() == null) {
277
            return [];
278
        }
279
280 35
        $reQuestId = $request->getId();
281 35
        if (isset(self::$additionalArgumentsCache[$reQuestId])) {
282 32
            return self::$additionalArgumentsCache[$reQuestId];
283
        }
284
285 35
        self::$additionalArgumentsCache[$reQuestId] = $request->getContextTypoScriptConfiguration()
286 35
            ->getSearchFacetingFacetLinkUrlParametersAsArray();
287
288 35
        return self::$additionalArgumentsCache[$reQuestId];
289
    }
290
291
    /**
292
     * @param SearchRequest $request
293
     * @return integer|null
294
     */
295 43
    protected function getTargetPageUidFromRequestConfiguration(SearchRequest $request)
296
    {
297 43
        if ($request->getContextTypoScriptConfiguration() == null) {
298
            return null;
299
        }
300
301 43
        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 43
    protected function buildLinkWithInMemoryCache($pageUid, array $arguments)
312
    {
313 43
        $values = [];
314 43
        $structure = $arguments;
315 43
        $this->getSubstitution($structure, $values);
316 43
        $hash = md5($pageUid . json_encode($structure));
317 43
        if (isset(self::$preCompiledLinks[$hash])) {
318 34
            self::$hitCount++;
319 34
            $uriCacheTemplate = self::$preCompiledLinks[$hash];
320
        } else {
321 43
            self::$missCount++;
322 43
            $this->uriBuilder->reset()->setTargetPageUid($pageUid);
323 43
            $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 43
            $urlHelper = GeneralUtility::makeInstance(UrlHelper::class, $uriCacheTemplate);
329 43
            $urlHelper->removeQueryParameter('cHash');
330 43
            $uriCacheTemplate = $urlHelper->getUrl();
331
332 43
            self::$preCompiledLinks[$hash] = $uriCacheTemplate;
333
        }
334
335
        $keys = array_map(function($value) {
336 41
            return urlencode($value);
337 43
        }, array_keys($values));
338
        $values = array_map(function($value) {
339 41
            return urlencode($value);
340 43
        }, $values);
341 43
        $uri = str_replace($keys, $values, $uriCacheTemplate);
342 43
        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 43
    protected function getSubstitution(array &$structure, array  &$values, array $branch = [])
387
    {
388 43
        foreach ($structure as $key => &$value) {
389 43
            $branch[] = $key;
390 43
            if (is_array($value)) {
391 43
                $this->getSubstitution($value, $values, $branch);
392
            } else {
393 41
                $path = '###' . implode(':', $branch) . '###';
394 41
                $values[$path] = $value;
395 41
                $structure[$key] = $path;
396
            }
397
        }
398 43
    }
399
}
400