Passed
Push — master ( ed38f6...30aafa )
by Timo
11:15
created

getTargetPageUidFromRequestConfiguration()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2.1481

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 7
rs 10
c 0
b 0
f 0
ccs 2
cts 3
cp 0.6667
cc 2
nc 2
nop 1
crap 2.1481
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 43
     */
68
    public function injectUriBuilder(UriBuilder $uriBuilder)
69 43
    {
70 43
        $this->uriBuilder = $uriBuilder;
71
    }
72
73
    /**
74
     * @param SearchRequest $previousSearchRequest
75
     * @param $facetName
76
     * @param $facetValue
77
     * @return string
78 33
     */
79
    public function getAddFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue)
80
    {
81 33
        $persistentAndFacetArguments = $previousSearchRequest
82 33
            ->getCopyForSubRequest()->removeAllGroupItemPages()->addFacetValue($facetName, $facetValue)
83
            ->getAsArray();
84 33
85 33
        $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
86
        $additionalArguments = is_array($additionalArguments) ? $additionalArguments : [];
0 ignored issues
show
introduced by
The condition is_array($additionalArguments) is always true.
Loading history...
87 33
88
        $arguments = $persistentAndFacetArguments + $additionalArguments;
89 33
90 33
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
91
        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 1
     */
102
    public function getSetFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue)
103
    {
104 1
        $previousSearchRequest = $previousSearchRequest
105
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeAllFacetValuesByName($facetName);
106 1
107
        return $this->getAddFacetValueUri($previousSearchRequest, $facetName, $facetValue);
108
    }
109
110
    /**
111
     * @param SearchRequest $previousSearchRequest
112
     * @param $facetName
113
     * @param $facetValue
114
     * @return string
115 5
     */
116
    public function getRemoveFacetValueUri(SearchRequest $previousSearchRequest, $facetName, $facetValue)
117
    {
118 5
        $persistentAndFacetArguments = $previousSearchRequest
119 5
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeFacetValue($facetName, $facetValue)
120
            ->getAsArray();
121 5
122 5
        $additionalArguments = [];
123 4
        if ($previousSearchRequest->getContextTypoScriptConfiguration()->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl()) {
124
            $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
125 5
        }
126
        $arguments = $persistentAndFacetArguments + $additionalArguments;
127 5
128 5
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
129
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
130
    }
131
132
    /**
133
     * @param SearchRequest $previousSearchRequest
134
     * @param $facetName
135
     * @return string
136 1
     */
137
    public function getRemoveFacetUri(SearchRequest $previousSearchRequest, $facetName)
138
    {
139 1
        $persistentAndFacetArguments = $previousSearchRequest
140 1
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeAllFacetValuesByName($facetName)
141
            ->getAsArray();
142 1
143 1
        $additionalArguments = [];
144
        if ($previousSearchRequest->getContextTypoScriptConfiguration()->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl()) {
145
            $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
146
        }
147 1
148
        $arguments = $persistentAndFacetArguments + $additionalArguments;
149 1
150 1
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
151
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
152
    }
153
154
    /**
155
     * @param SearchRequest $previousSearchRequest
156
     * @return string
157 4
     */
158
    public function getRemoveAllFacetsUri(SearchRequest $previousSearchRequest)
159
    {
160 4
        $persistentAndFacetArguments = $previousSearchRequest
161 4
            ->getCopyForSubRequest()->removeAllGroupItemPages()->removeAllFacets()
162
            ->getAsArray();
163 4
164 4
        $additionalArguments = [];
165 4
        if ($previousSearchRequest->getContextTypoScriptConfiguration()->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl()) {
166
            $additionalArguments = $this->getAdditionalArgumentsFromRequestConfiguration($previousSearchRequest);
167
        }
168 4
169
        $arguments = $persistentAndFacetArguments + $additionalArguments;
170 4
171 4
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
172
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
173
    }
174
175
    /**
176
     * @param SearchRequest $previousSearchRequest
177
     * @param $page
178
     * @return string
179 11
     */
180
    public function getResultPageUri(SearchRequest $previousSearchRequest, $page)
181
    {
182 11
        $persistentAndFacetArguments = $previousSearchRequest
183 11
            ->getCopyForSubRequest()->setPage($page)
184
            ->getAsArray();
185 11
186 11
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
187
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
188
    }
189
190
    /**
191
     * @param SearchRequest $previousSearchRequest
192
     * @param GroupItem $groupItem
193
     * @param int $page
194
     * @return string
195 1
     */
196
    public function getResultGroupItemPageUri(SearchRequest $previousSearchRequest, GroupItem $groupItem, int $page)
197
    {
198 1
        $persistentAndFacetArguments = $previousSearchRequest
199 1
            ->getCopyForSubRequest()->setGroupItemPage($groupItem->getGroup()->getGroupName(), $groupItem->getGroupValue(), $page)
200 1
            ->getAsArray();
201 1
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
202
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
203
    }
204
    /**
205
     * @param SearchRequest $previousSearchRequest
206
     * @param $queryString
207
     * @return string
208 34
     */
209
    public function getNewSearchUri(SearchRequest $previousSearchRequest, $queryString)
210
    {
211 34
        /** @var $request SearchRequest */
212 34
        $contextConfiguration = $previousSearchRequest->getContextTypoScriptConfiguration();
213 34
        $contextSystemLanguage = $previousSearchRequest->getContextSystemLanguageUid();
214
        $contextPageUid = $previousSearchRequest->getContextPageUid();
215 34
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
        $arguments = $request->setRawQueryString($queryString)->getAsArray();
222 34
223 34
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
224
        return $this->buildLinkWithInMemoryCache($pageUid, $arguments);
225
    }
226
227
    /**
228
     * @param SearchRequest $previousSearchRequest
229
     * @param $sortingName
230
     * @param $sortingDirection
231
     * @return string
232 32
     */
233
    public function getSetSortingUri(SearchRequest $previousSearchRequest, $sortingName, $sortingDirection)
234
    {
235 32
        $persistentAndFacetArguments = $previousSearchRequest
236 32
            ->getCopyForSubRequest()->setSorting($sortingName, $sortingDirection)
237
            ->getAsArray();
238 32
239 32
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
240
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
241
    }
242
243
    /**
244
     * @param SearchRequest $previousSearchRequest
245
     * @return string
246 31
     */
247
    public function getRemoveSortingUri(SearchRequest $previousSearchRequest)
248
    {
249 31
        $persistentAndFacetArguments = $previousSearchRequest
250 31
            ->getCopyForSubRequest()->removeSorting()
251
            ->getAsArray();
252 31
253 31
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
254
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
255
    }
256
257
    /**
258
     * @param SearchRequest $previousSearchRequest
259
     * @return string
260 27
     */
261
    public function getCurrentSearchUri(SearchRequest $previousSearchRequest)
262
    {
263 27
        $persistentAndFacetArguments = $previousSearchRequest
264 27
            ->getCopyForSubRequest()
265
            ->getAsArray();
266
267 27
268 27
        $pageUid = $this->getTargetPageUidFromRequestConfiguration($previousSearchRequest);
269
        return $this->buildLinkWithInMemoryCache($pageUid, $persistentAndFacetArguments);
270
    }
271
272
    /**
273
     * @param SearchRequest $request
274
     * @return array
275 34
     */
276
    protected function getAdditionalArgumentsFromRequestConfiguration(SearchRequest $request)
277 34
    {
278
        if ($request->getContextTypoScriptConfiguration() == null) {
279
            return [];
280
        }
281 34
282 34
        $reQuestId = $request->getId();
283 31
        if (isset(self::$additionalArgumentsCache[$reQuestId])) {
284
            return self::$additionalArgumentsCache[$reQuestId];
285
        }
286 34
287 34
        self::$additionalArgumentsCache[$reQuestId] = $request->getContextTypoScriptConfiguration()
288
            ->getSearchFacetingFacetLinkUrlParametersAsArray();
289 34
290
        return self::$additionalArgumentsCache[$reQuestId];
291
    }
292
293
    /**
294
     * @param SearchRequest $request
295
     * @return integer|null
296 43
     */
297
    protected function getTargetPageUidFromRequestConfiguration(SearchRequest $request)
298 43
    {
299
        if ($request->getContextTypoScriptConfiguration() == null) {
300
            return null;
301
        }
302 43
303
        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 43
     * @param array $arguments
311
     * @return string
312 43
     */
313 43
    protected function buildLinkWithInMemoryCache($pageUid, array $arguments)
314 43
    {
315 43
        $values = [];
316
        $structure = $arguments;
317 43
        $this->getSubstitution($structure, $values);
318 34
        $hash = md5($pageUid . json_encode($structure));
319 34
        if (isset(self::$preCompiledLinks[$hash])) {
320
            self::$hitCount++;
321 42
            $uriCacheTemplate = self::$preCompiledLinks[$hash];
322 42
        } else {
323 42
            self::$missCount++;
324 42
            $this->uriBuilder->setTargetPageUid($pageUid);
325
            $uriCacheTemplate = $this->uriBuilder->setArguments($structure)->setUseCacheHash(false)->build();
326
327 43
            // even if we call build with disabled cHash in TYPO3 9 a cHash will be generated when site management is active
328 41
            // to prevent wrong cHashes we remove the cHash here from the cached uri template.
329 43
            // @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 41
            $urlHelper->removeQueryParameter('cHash');
332 43
            $uriCacheTemplate = $urlHelper->getUrl();
333 43
334 43
            self::$preCompiledLinks[$hash] = $uriCacheTemplate;
335
        }
336
337
        $keys = array_map(function($value) {
338
            return urlencode($value);
339
        }, array_keys($values));
340
        $values = array_map(function($value) {
341
            return urlencode($value);
342
        }, $values);
343
        $uri = str_replace($keys, $values, $uriCacheTemplate);
344
        return $uri;
345
    }
346
347
    /**
348
     * Flushes the internal in memory cache.
349
     *
350
     * @return void
351
     */
352
    public function flushInMemoryCache()
353
    {
354
        self::$preCompiledLinks = [];
355
    }
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 43
     *      'bar' => 111
369
     *   ]
370 43
     * ]
371 43
     *
372 43
     * will return:
373 43
     *
374
     * $structure = [
375 41
     *  'foo' => [
376 41
     *      'bar' => '###foo:bar###'
377 43
     *   ]
378
     * ]
379
     *
380 43
     * $values = [
381
     *  '###foo:bar###' => 111
382
     * ]
383
     *
384
     * @param $structure
385
     * @param $values
386
     * @param array $branch
387
     */
388
    protected function getSubstitution(array &$structure, array  &$values, array $branch = [])
389
    {
390
        foreach ($structure as $key => &$value) {
391
            $branch[] = $key;
392
            if (is_array($value)) {
393
                $this->getSubstitution($value, $values, $branch);
394
            } else {
395
                $path = '###' . implode(':', $branch) . '###';
396
                $values[$path] = $value;
397
                $structure[$key] = $path;
398
            }
399
        }
400
    }
401
}
402