Passed
Push — master ( 276c4d...f776da )
by Timo
24:49
created

SearchUriBuilder   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 361
Duplicated Lines 0 %

Test Coverage

Coverage 97.6%

Importance

Changes 0
Metric Value
wmc 27
eloc 124
dl 0
loc 361
ccs 122
cts 125
cp 0.976
rs 10
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A injectUriBuilder() 0 3 1
A getTargetPageUidFromRequestConfiguration() 0 7 2
A getAdditionalArgumentsFromRequestConfiguration() 0 15 3
A getResultPageUri() 0 8 1
A getResultGroupItemPageUri() 0 7 1
A getSetSortingUri() 0 8 1
A getCurrentSearchUri() 0 9 1
A getNewSearchUri() 0 16 1
A getSetFacetValueUri() 0 6 1
A getRemoveAllFacetsUri() 0 15 2
A getRemoveFacetValueUri() 0 14 2
A getAddFacetValueUri() 0 13 2
A getRemoveFacetUri() 0 15 2
A getRemoveSortingUri() 0 8 1
A buildLinkWithInMemoryCache() 0 32 2
A flushInMemoryCache() 0 3 1
A getSubstitution() 0 10 3
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
 * @package ApacheSolrForTypo3\Solr\Domain\Search\Uri
35
 */
36
37
class SearchUriBuilder
38
{
39
40
    /**
41
     * @var UriBuilder
42
     */
43
    protected $uriBuilder;
44
45
    /**
46
     * @var array
47
     */
48
    protected static $preCompiledLinks = [];
49
50
    /**
51
     * @var integer
52
     */
53
    protected static $hitCount;
54
55
    /**
56
     * @var integer
57
     */
58
    protected static $missCount;
59
60
    /**
61
     * @var array
62
     */
63
    protected static $additionalArgumentsCache = [];
64
65
    /**
66
     * @param UriBuilder $uriBuilder
67
     */
68 43
    public function injectUriBuilder(UriBuilder $uriBuilder)
69
    {
70 43
        $this->uriBuilder = $uriBuilder;
71 43
    }
72
73
    /**
74
     * @param SearchRequest $previousSearchRequest
75
     * @param $facetName
76
     * @param $facetValue
77
     * @return string
78
     */
79 33
    public function getAddFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue)
80
    {
81
        $persistentAndFacetArguments = $previousSearchRequest
82 33
            ->getCopyForSubRequest()->removeAllGroupItemPages()->addFacetValue($facetName, $facetValue)
83 33
            ->getAsArray();
84
85 33
        $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
86 33
        $additionalArguments = is_array($additionalArguments) ? $additionalArguments : [];
0 ignored issues
show
introduced by
The condition is_array($additionalArguments) is always true.
Loading history...
87
88 33
        $arguments = $persistentAndFacetArguments + $additionalArguments;
89
90 33
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
91 33
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
92
    }
93
94
    /**
95
     * Removes all other facet values for this name and only set's the passed value for the facet.
96
     *
97
     * @param SearchRequest $previousSearchRequest
98
     * @param $facetName
99
     * @param $facetValue
100
     * @return string
101
     */
102 1
    public function getSetFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue)
103
    {
104
        $previousSearchRequest = $previousSearchRequest
105 1
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeAllFacetValuesByName($facetName);
106
107 1
        return $this->getAddFacetValueUri($previousSearchRequest, $facetName, $facetValue);
108
    }
109
110
    /**
111
     * @param SearchRequest $previousSearchRequest
112
     * @param $facetName
113
     * @param $facetValue
114
     * @return string
115
     */
116 5
    public function getRemoveFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue)
117
    {
118
        $persistentAndFacetArguments = $previousSearchRequest
119 5
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeFacetValue($facetName, $facetValue)
120 5
            ->getAsArray();
121
122 5
        $additionalArguments = [];
123 5
        if ($previousSearchRequest->getContextTypoScriptConfiguration()->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl()) {
124 4
            $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
125
        }
126 5
        $arguments = $persistentAndFacetArguments + $additionalArguments;
127
128 5
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
129 5
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
130
    }
131
132
    /**
133
     * @param SearchRequest $previousSearchRequest
134
     * @param $facetName
135
     * @return string
136
     */
137 1
    public function getRemoveFacetUri(SearchRequest $previousSearchRequest, $facetName)
138
    {
139
        $persistentAndFacetArguments = $previousSearchRequest
140 1
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeAllFacetValuesByName($facetName)
141 1
            ->getAsArray();
142
143 1
        $additionalArguments = [];
144 1
        if ($previousSearchRequest->getContextTypoScriptConfiguration()->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl()) {
145
            $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
146
        }
147
148 1
        $arguments = $persistentAndFacetArguments + $additionalArguments;
149
150 1
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
151 1
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
152
    }
153
154
    /**
155
     * @param SearchRequest $previousSearchRequest
156
     * @return string
157
     */
158 4
    public function getRemoveAllFacetsUri(SearchRequest $previousSearchRequest)
159
    {
160
        $persistentAndFacetArguments = $previousSearchRequest
161 4
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeAllFacets()
162 4
            ->getAsArray();
163
164 4
        $additionalArguments = [];
165 4
        if ($previousSearchRequest->getContextTypoScriptConfiguration()->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl()) {
166 4
            $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
167
        }
168
169 4
        $arguments = $persistentAndFacetArguments + $additionalArguments;
170
171 4
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
172 4
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
173
    }
174
175
    /**
176
     * @param SearchRequest $previousSearchRequest
177
     * @param $page
178
     * @return string
179
     */
180 11
    public function getResultPageUri(SearchRequest $previousSearchRequest, $page)
181
    {
182
        $persistentAndFacetArguments = $previousSearchRequest
183 11
            ->getCopyForSubRequest()->setPage($page)
184 11
            ->getAsArray();
185
186 11
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
187 11
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
188
    }
189
190
    /**
191
     * @param SearchRequest $previousSearchRequest
192
     * @param GroupItem $groupItem
193
     * @param int $page
194
     * @return string
195
     */
196 1
    public function getResultGroupItemPageUri(SearchRequest $previousSearchRequest, GroupItem $groupItem, int $page)
197
    {
198
        $persistentAndFacetArguments = $previousSearchRequest
199 1
            ->getCopyForSubRequest()->setGroupItemPage($groupItem->getGroup()->getGroupName(), $groupItem->getGroupValue(), $page)
200 1
            ->getAsArray();
201 1
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
202 1
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
203
    }
204
    /**
205
     * @param SearchRequest $previousSearchRequest
206
     * @param $queryString
207
     * @return string
208
     */
209 34
    public function getNewSearchUri(SearchRequest $previousSearchRequest, $queryString)
210
    {
211
        /** @var $request SearchRequest */
212 34
        $contextConfiguration = $previousSearchRequest->getContextTypoScriptConfiguration();
213 34
        $contextSystemLanguage = $previousSearchRequest->getContextSystemLanguageUid();
214 34
        $contextPageUid = $previousSearchRequest->getContextPageUid();
215
216 34
        $request = GeneralUtility::makeInstance(
217 34
            SearchRequest::class, [],
218 34
            /** @scrutinizer ignore-type */ $contextPageUid,
219 34
            /** @scrutinizer ignore-type */ $contextSystemLanguage,
220 34
            /** @scrutinizer ignore-type */ $contextConfiguration);
221 34
        $arguments = $request->setRawQueryString($queryString)->getAsArray();
222
223 34
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
224 34
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
225
    }
226
227
    /**
228
     * @param SearchRequest $previousSearchRequest
229
     * @param $sortingName
230
     * @param $sortingDirection
231
     * @return string
232
     */
233 32
    public function getSetSortingUri(SearchRequest $previousSearchRequest, $sortingName, $sortingDirection)
234
    {
235
        $persistentAndFacetArguments = $previousSearchRequest
236 32
            ->getCopyForSubRequest()->setSorting($sortingName, $sortingDirection)
237 32
            ->getAsArray();
238
239 32
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
240 32
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
241
    }
242
243
    /**
244
     * @param SearchRequest $previousSearchRequest
245
     * @return string
246
     */
247 31
    public function getRemoveSortingUri(SearchRequest $previousSearchRequest)
248
    {
249
        $persistentAndFacetArguments = $previousSearchRequest
250 31
            ->getCopyForSubRequest()->removeSorting()
251 31
            ->getAsArray();
252
253 31
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
254 31
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
255
    }
256
257
    /**
258
     * @param SearchRequest $previousSearchRequest
259
     * @return string
260
     */
261 27
    public function getCurrentSearchUri(SearchRequest $previousSearchRequest)
262
    {
263
        $persistentAndFacetArguments = $previousSearchRequest
264 27
            ->getCopyForSubRequest()
265 27
            ->getAsArray();
266
267
268 27
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
269 27
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
270
    }
271
272
    /**
273
     * @param SearchRequest $request
274
     * @return array
275
     */
276 34
    protected function getAdditionalArgumentsFromRequestConfiguration(SearchRequest $request)
277
    {
278 34
        if ($request->getContextTypoScriptConfiguration() == null) {
279
            return [];
280
        }
281
282 34
        $reQuestId = $request->getId();
283 34
        if (isset(self::$additionalArgumentsCache[$reQuestId])) {
284 31
            return self::$additionalArgumentsCache[$reQuestId];
285
        }
286
287 34
        self::$additionalArgumentsCache[$reQuestId] = $request->getContextTypoScriptConfiguration()
288 34
            ->getSearchFacetingFacetLinkUrlParametersAsArray();
289
290 34
        return self::$additionalArgumentsCache[$reQuestId];
291
    }
292
293
    /**
294
     * @param SearchRequest $request
295
     * @return integer|null
296
     */
297 43
    protected function getTargetPageUidFromRequestConfiguration(SearchRequest $request)
298
    {
299 43
        if ($request->getContextTypoScriptConfiguration() == null) {
300
            return null;
301
        }
302
303 43
        return $request->getContextTypoScriptConfiguration()->getSearchTargetPage();
304
    }
305
306
    /**
307
     * Build the link with an i memory cache that reduces the amount of required typolink calls.
308
     *
309
     * @param integer $pageUid
310
     * @param array $arguments
311
     * @return string
312
     */
313 43
    protected function buildLinkWithInMemoryCache($pageUid, array $arguments)
314
    {
315 43
        $values = [];
316 43
        $structure = $arguments;
317 43
        $this->getSubstitution($structure, $values);
318 43
        $hash = md5($pageUid . json_encode($structure));
319 43
        if (isset(self::$preCompiledLinks[$hash])) {
320 33
            self::$hitCount++;
321 33
            $uriCacheTemplate = self::$preCompiledLinks[$hash];
322
        } else {
323 43
            self::$missCount++;
324 43
            $this->uriBuilder->setTargetPageUid($pageUid);
325 43
            $uriCacheTemplate = $this->uriBuilder->setArguments($structure)->setUseCacheHash(false)->build();
326
327
            // even if we call build with disabled cHash in TYPO3 9 a cHash will be generated when site management is active
328
            // to prevent wrong cHashes we remove the cHash here from the cached uri template.
329
            // @todo: This can be removed when https://forge.typo3.org/issues/87120 is resolved and we can ship a proper configuration
330 43
            $urlHelper = GeneralUtility::makeInstance(UrlHelper::class, $uriCacheTemplate);
331 43
            $urlHelper->removeQueryParameter('cHash');
332 43
            $uriCacheTemplate = $urlHelper->getUrl();
333
334 43
            self::$preCompiledLinks[$hash] = $uriCacheTemplate;
335
        }
336
337 43
        $keys = array_map(function($value) {
338 41
            return urlencode($value);
339 43
        }, array_keys($values));
340 43
        $values = array_map(function($value) {
341 41
            return urlencode($value);
342 43
        }, $values);
343 43
        $uri = str_replace($keys, $values, $uriCacheTemplate);
344 43
        return $uri;
345
    }
346
347
    /**
348
     * Flushes the internal in memory cache.
349
     *
350
     * @return void
351
     */
352 8
    public function flushInMemoryCache()
353
    {
354 8
        self::$preCompiledLinks = [];
355 8
    }
356
357
    /**
358
     * This method is used to build two arrays from a nested array. The first one represents the structure.
359
     * In this structure the values are replaced with the pass to the value. At the same time the values get collected
360
     * in the $values array, with the path as key. This can be used to build a comparable hash from the arguments
361
     * in order to reduce the amount of typolink calls
362
     *
363
     *
364
     * Example input
365
     *
366
     * $data = [
367
     *  'foo' => [
368
     *      'bar' => 111
369
     *   ]
370
     * ]
371
     *
372
     * will return:
373
     *
374
     * $structure = [
375
     *  'foo' => [
376
     *      'bar' => '###foo:bar###'
377
     *   ]
378
     * ]
379
     *
380
     * $values = [
381
     *  '###foo:bar###' => 111
382
     * ]
383
     *
384
     * @param $structure
385
     * @param $values
386
     * @param array $branch
387
     */
388 43
    protected function getSubstitution(array &$structure, array  &$values, array $branch = [])
389
    {
390 43
        foreach ($structure as $key => &$value) {
391 43
            $branch[] = $key;
392 43
            if (is_array($value)) {
393 43
                $this->getSubstitution($value, $values, $branch);
394
            } else {
395 41
                $path = '###' . implode(':', $branch) . '###';
396 41
                $values[$path] = $value;
397 43
                $structure[$key] = $path;
398
            }
399
        }
400 43
    }
401
}
402