Passed
Push — master ( 6d83d0...d2efa3 )
by Timo
45:45 queued 16:20
created

SearchRequest::getContextSystemLanguageUid()   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

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace ApacheSolrForTypo3\Solr\Domain\Search;
4
5
/***************************************************************
6
 *  Copyright notice
7
 *
8
 *  (c) 2015-2016 Timo Schmidt <[email protected]>
9
 *  All rights reserved
10
 *
11
 *  This script is part of the TYPO3 project. The TYPO3 project is
12
 *  free software; you can redistribute it and/or modify
13
 *  it under the terms of the GNU General Public License as published by
14
 *  the Free Software Foundation; either version 3 of the License, or
15
 *  (at your option) any later version.
16
 *
17
 *  The GNU General Public License can be found at
18
 *  http://www.gnu.org/copyleft/gpl.html.
19
 *
20
 *  This script is distributed in the hope that it will be useful,
21
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 *  GNU General Public License for more details.
24
 *
25
 *  This copyright notice MUST APPEAR in all copies of the script!
26
 ***************************************************************/
27
28
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
29
use ApacheSolrForTypo3\Solr\System\Util\ArrayAccessor;
30
use TYPO3\CMS\Core\Utility\ArrayUtility;
31
32
/**
33
 * The searchRequest is used to act as an api to the arguments that have been passed
34
 * with GET and POST.
35
 *
36
 * @author Timo Schmidt <[email protected]>
37
 */
38
class SearchRequest
39
{
40
    /**
41
     * @var string
42
     */
43
    protected $id;
44
45
    /**
46
     * Default namespace overwritten with the configured plugin namespace.
47
     *
48
     * @var string
49
     */
50
    protected $argumentNameSpace = 'tx_solr';
51
52
    /**
53
     * Arguments that should be kept for sub requests.
54
     *
55
     * Default values, overwritten in the constructor with the namespaced arguments
56
     *
57
     * @var array
58
     */
59
    protected $persistentArgumentsPaths = ['tx_solr:q', 'tx_solr:filter', 'tx_solr:sort'];
60
61
    /**
62
     * @var bool
63
     */
64
    protected $stateChanged = false;
65
66
    /**
67
     * @var ArrayAccessor
68
     */
69
    protected $argumentsAccessor;
70
71
    /**
72
     * The sys_language_uid that was used in the context where the request was build.
73
     * This could be different from the "L" parameter and and not relevant for urls,
74
     * because typolink itself will handle it.
75
     *
76
     * @var int
77
     */
78
    protected $contextSystemLanguageUid;
79
80
    /**
81
     * The page_uid that was used in the context where the request was build.
82
     *
83
     * The pageUid is not relevant for the typolink additionalArguments and therefore
84
     * a separate property.
85
     *
86
     * @var int
87
     */
88
    protected $contextPageUid;
89
90
    /**
91
     * @var TypoScriptConfiguration
92
     */
93
    protected $contextTypoScriptConfiguration;
94
95
    /**
96
     * @var array
97
     */
98
    protected $persistedArguments = [];
99
100
    /**
101
     * @param array $argumentsArray
102
     * @param int $pageUid
103
     * @param int $sysLanguageUid
104
     * @param TypoScriptConfiguration $typoScriptConfiguration
105
     */
106 105
    public function __construct(array $argumentsArray = [], $pageUid = 0, $sysLanguageUid = 0, TypoScriptConfiguration $typoScriptConfiguration = null)
107
    {
108 105
        $this->stateChanged = true;
109 105
        $this->persistedArguments = $argumentsArray;
110 105
        $this->contextPageUid = $pageUid;
111 105
        $this->contextSystemLanguageUid = $sysLanguageUid;
112 105
        $this->contextTypoScriptConfiguration = $typoScriptConfiguration;
113 105
        $this->id = spl_object_hash($this);
114
115
        // overwrite the plugin namespace and the persistentArgumentsPaths
116 105
        if (!is_null($typoScriptConfiguration)) {
117 53
            $this->argumentNameSpace = $typoScriptConfiguration->getSearchPluginNamespace();
118
        }
119
120 105
        $this->persistentArgumentsPaths = [$this->argumentNameSpace . ':q', $this->argumentNameSpace . ':filter', $this->argumentNameSpace . ':sort', $this->argumentNameSpace . ':groupPage'];
121
122 105
        if (!is_null($typoScriptConfiguration)) {
123 53
            $additionalPersistentArgumentsNames = $typoScriptConfiguration->getSearchAdditionalPersistentArgumentNames();
124 53
            foreach ($additionalPersistentArgumentsNames ?? [] as $additionalPersistentArgumentsName) {
125
                $this->persistentArgumentsPaths[] = $this->argumentNameSpace . ':' . $additionalPersistentArgumentsName;
126
            }
127 53
            $this->persistentArgumentsPaths = array_unique($this->persistentArgumentsPaths);
128
        }
129
130 105
        $this->reset();
131 105
    }
132
133
    /**
134
     * @return string
135
     */
136 36
    public function getId()
137
    {
138 36
        return $this->id;
139
    }
140
141
    /**
142
     * Can be used do merge arguments into the request arguments
143
     *
144
     * @param array $argumentsToMerge
145
     * @return SearchRequest
146
     */
147 1
    public function mergeArguments(array $argumentsToMerge)
148
    {
149 1
        ArrayUtility::mergeRecursiveWithOverrule(
150 1
            $this->persistedArguments,
151
            $argumentsToMerge
152
        );
153
154 1
        $this->reset();
155
156 1
        return $this;
157
    }
158
159
    /**
160
     * Helper method to prefix an accessor with the arguments namespace.
161
     *
162
     * @param string $path
163
     * @return string
164
     */
165 84
    protected function prefixWithNamespace($path)
166
    {
167 84
        return $this->argumentNameSpace . ':' . $path;
168
    }
169
170
    /**
171
     * @return array
172
     */
173 31
    public function getActiveFacetNames()
174
    {
175 31
        $activeFacets = $this->getActiveFacets();
176 31
        $facetNames = [];
177
178
        array_map(function($activeFacet) use (&$facetNames) {
179 3
            $facetNames[] = substr($activeFacet, 0, strpos($activeFacet, ':'));
180 31
        }, $activeFacets);
181
182 31
        return $facetNames;
183
    }
184
185
    /**
186
     * Returns all facet values for a certain facetName
187
     * @param string $facetName
188
     * @return array
189
     */
190 47
    public function getActiveFacetValuesByName($facetName)
191
    {
192 47
        $values = [];
193 47
        $activeFacets = $this->getActiveFacets();
194
195
        array_map(function($activeFacet) use (&$values, $facetName) {
196 12
            $parts = explode(':', $activeFacet, 2);
197 12
            if ($parts[0] === $facetName) {
198 12
                $values[] = $parts[1];
199
            }
200 47
        }, $activeFacets);
201
202 47
        return $values;
203
    }
204
205
    /**
206
     * @return array
207
     */
208 52
    public function getActiveFacets()
209
    {
210 52
        $path = $this->prefixWithNamespace('filter');
211 52
        $pathValue = $this->argumentsAccessor->get($path, []);
212
213 52
        return is_array($pathValue) ? $pathValue : [];
214
    }
215
216
    /**
217
     * @return int
218
     */
219 2
    public function getActiveFacetCount()
220
    {
221 2
        return count($this->getActiveFacets());
222
    }
223
224
    /**
225
     * @param $activeFacets
226
     *
227
     * @return SearchRequest
228
     */
229 45
    protected function setActiveFacets($activeFacets = [])
230
    {
231 45
        $path = $this->prefixWithNamespace('filter');
232 45
        $this->argumentsAccessor->set($path, $activeFacets);
233
234 45
        return $this;
235
    }
236
237
    /**
238
     * Adds a facet value to the request.
239
     *
240
     * @param string $facetName
241
     * @param mixed $facetValue
242
     *
243
     * @return SearchRequest
244
     */
245 40
    public function addFacetValue($facetName, $facetValue)
246
    {
247 40
        if ($this->getHasFacetValue($facetName, $facetValue)) {
248 3
            return $this;
249
        }
250
251 40
        $facetValues = $this->getActiveFacets();
252 40
        $facetValues[] = $facetName . ':' . $facetValue;
253 40
        $this->setActiveFacets($facetValues);
254
255 40
        $this->stateChanged = true;
256 40
        return $this;
257
    }
258
259
    /**
260
     * Removes a facet value from the request.
261
     *
262
     * @param string $facetName
263
     * @param mixed $facetValue
264
     *
265
     * @return SearchRequest
266
     */
267 6
    public function removeFacetValue($facetName, $facetValue)
268
    {
269 6
        if (!$this->getHasFacetValue($facetName, $facetValue)) {
270
            return $this;
271
        }
272 6
        $facetValues = $this->getActiveFacets();
273 6
        $facetValueToLookFor = $facetName . ':' . $facetValue;
274
275 6
        foreach ($facetValues as $index => $facetValue) {
0 ignored issues
show
introduced by
$facetValue is overwriting one of the parameters of this function.
Loading history...
276 6
            if ($facetValue === $facetValueToLookFor) {
277 6
                unset($facetValues[$index]);
278 6
                break;
279
            }
280
        }
281
282 6
        $this->setActiveFacets($facetValues);
283 6
        $this->stateChanged = true;
284 6
        return $this;
285
    }
286
287
    /**
288
     * Removes all facet values from the request by a certain facet name
289
     *
290
     * @param string $facetName
291
     *
292
     * @return SearchRequest
293
     */
294 3
    public function removeAllFacetValuesByName($facetName)
295
    {
296 3
        $facetValues = $this->getActiveFacets();
297
        $facetValues = array_filter($facetValues, function($facetValue) use ($facetName) {
298 2
            $parts = explode(':', $facetValue, 2);
299 2
            return $parts[0] !== $facetName;
300 3
        });
301
302 3
        $this->setActiveFacets($facetValues);
303 3
        $this->stateChanged = true;
304 3
        return $this;
305
    }
306
307
    /**
308
     * Removes all active facets from the request.
309
     *
310
     * @return SearchRequest
311
     */
312 6
    public function removeAllFacets()
313
    {
314 6
        $path = $this->prefixWithNamespace('filter');
315 6
        $this->argumentsAccessor->reset($path);
316 6
        $this->stateChanged = true;
317 6
        return $this;
318
    }
319
320
    /**
321
     * @param string $facetName
322
     * @param mixed $facetValue
323
     * @return bool
324
     */
325 45
    public function getHasFacetValue($facetName, $facetValue)
326
    {
327 45
        $facetNameAndValueToCheck = $facetName . ':' . $facetValue;
328 45
        return in_array($facetNameAndValueToCheck, $this->getActiveFacets());
329
    }
330
331
    /**
332
     * @return bool
333
     */
334 48
    public function getHasSorting()
335
    {
336 48
        $path = $this->prefixWithNamespace('sort');
337 48
        return $this->argumentsAccessor->has($path);
338
    }
339
340
    /**
341
     * Returns the sorting string in the url e.g. title asc.
342
     *
343
     * @return string
344
     */
345 47
    public function getSorting()
346
    {
347 47
        $path = $this->prefixWithNamespace('sort');
348 47
        return $this->argumentsAccessor->get($path, '');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->argumentsAccessor->get($path, '') also could return the type array which is incompatible with the documented return type string.
Loading history...
349
    }
350
351
    /**
352
     * Helper function to get the sorting configuration name or direction.
353
     *
354
     * @param int $index
355
     * @return string
356
     */
357 47
    protected function getSortingPart($index)
358
    {
359 47
        $sorting = $this->getSorting();
360 47
        if ($sorting === '') {
361 45
            return null;
362
        }
363
364 2
        $parts = explode(' ', $sorting);
365 2
        return isset($parts[$index]) ? $parts[$index] : null;
366
    }
367
368
    /**
369
     * Returns the sorting configuration name that is currently used.
370
     *
371
     * @return string
372
     */
373 47
    public function getSortingName()
374
    {
375 47
        return $this->getSortingPart(0);
376
    }
377
378
    /**
379
     * Returns the sorting direction that is currently used.
380
     *
381
     * @return string
382
     */
383 46
    public function getSortingDirection()
384
    {
385 46
        return mb_strtolower($this->getSortingPart(1));
386
    }
387
388
    /**
389
     * @return SearchRequest
390
     */
391 33
    public function removeSorting()
392
    {
393 33
        $path = $this->prefixWithNamespace('sort');
394 33
        $this->argumentsAccessor->reset($path);
395 33
        $this->stateChanged = true;
396 33
        return $this;
397
    }
398
399
    /**
400
     * @param string $sortingName
401
     * @param string $direction (asc or desc)
402
     *
403
     * @return SearchRequest
404
     */
405 34
    public function setSorting($sortingName, $direction = 'asc')
406
    {
407 34
        $value = $sortingName . ' ' . $direction;
408 34
        $path = $this->prefixWithNamespace('sort');
409 34
        $this->argumentsAccessor->set($path, $value);
410 34
        $this->stateChanged = true;
411 34
        return $this;
412
    }
413
414
    /**
415
     * Method to set the paginated page of the search
416
     *
417
     * @param int $page
418
     * @return SearchRequest
419
     */
420 15
    public function setPage($page)
421
    {
422 15
        $this->stateChanged = true;
423 15
        $path = $this->prefixWithNamespace('page');
424 15
        $this->argumentsAccessor->set($path, $page);
425 15
        return $this;
426
    }
427
428
    /**
429
     * Returns the passed page.
430
     *
431
     * @return int|null
432
     */
433 52
    public function getPage()
434
    {
435 52
        $path = $this->prefixWithNamespace('page');
436 52
        return $this->argumentsAccessor->get($path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->argumentsAccessor->get($path) also could return the type array which is incompatible with the documented return type integer|null.
Loading history...
437
    }
438
439
    /**
440
     * Can be used to reset all groupPages.
441
     *
442
     * @return SearchRequest
443
     */
444 38
    public function removeAllGroupItemPages(): SearchRequest
445
    {
446 38
        $path = $this->prefixWithNamespace('groupPage');
447 38
        $this->argumentsAccessor->reset($path);
448
449 38
        return $this;
450
    }
451
452
    /**
453
     * Can be used to paginate within a groupItem.
454
     *
455
     * @param string $groupName e.g. type
456
     * @param string $groupItemValue e.g. pages
457
     * @param int $page
458
     * @return SearchRequest
459
     */
460 4
    public function setGroupItemPage(string $groupName, string $groupItemValue, int $page): SearchRequest
461
    {
462 4
        $this->stateChanged = true;
463 4
        $escapedValue = $this->getEscapedGroupItemValue($groupItemValue);
464 4
        $path = $this->prefixWithNamespace('groupPage:' . $groupName . ':' . $escapedValue);
465 4
        $this->argumentsAccessor->set($path, $page);
466 4
        return $this;
467
    }
468
469
    /**
470
     * Retrieves the current page for this group item.
471
     *
472
     * @param string $groupName
473
     * @param string $groupItemValue
474
     * @return int
475
     */
476 3
    public function getGroupItemPage(string $groupName, string $groupItemValue): int
477
    {
478 3
        $escapedValue = $this->getEscapedGroupItemValue($groupItemValue);
479 3
        $path = $this->prefixWithNamespace('groupPage:' . $groupName . ':' . $escapedValue);
480 3
        return max(1, (int)$this->argumentsAccessor->get($path));
481
    }
482
483
    /**
484
     * Removes all non alphanumeric values from the groupItem value to have a valid array key.
485
     *
486
     * @param string $groupItemValue
487
     * @return string
488
     */
489 5
    protected function getEscapedGroupItemValue(string $groupItemValue)
490
    {
491 5
        return preg_replace("/[^A-Za-z0-9]/", '', $groupItemValue);
492
    }
493
494
    /**
495
     * Retrieves the highest page of the groups.
496
     *
497
     * @return int
498
     */
499 1
    public function getHighestGroupPage()
500
    {
501 1
        $max = 1;
502 1
        $path = $this->prefixWithNamespace('groupPage');
503 1
        $groupPages = $this->argumentsAccessor->get($path, []);
504 1
        foreach ($groupPages as $groups) {
505
            if (!is_array($groups)) continue;
506
            foreach ($groups as $groupItemPage) {
507
                if ($groupItemPage > $max) {
508
                    $max = $groupItemPage;
509
                }
510
            }
511
        }
512
513 1
        return $max;
514
    }
515
516
    /**
517
     * Method to overwrite the query string.
518
     *
519
     * @param string $rawQueryString
520
     * @return SearchRequest
521
     */
522 46
    public function setRawQueryString($rawQueryString)
523
    {
524 46
        $this->stateChanged = true;
525 46
        $path = $this->prefixWithNamespace('q');
526 46
        $this->argumentsAccessor->set($path, $rawQueryString);
527 46
        return $this;
528
    }
529
530
    /**
531
     * Returns the passed rawQueryString.
532
     *
533
     * @return string|null
534
     */
535 48
    public function getRawUserQuery()
536
    {
537 48
        $path = $this->prefixWithNamespace('q');
538 48
        $query = $this->argumentsAccessor->get($path, null);
539 48
        return is_null($query) ? $query : (string)$query;
540
    }
541
542
    /**
543
     * Method to check if the query string is an empty string
544
     * (also empty string or whitespaces only are handled as empty).
545
     *
546
     * When no query string is set (null) the method returns false.
547
     * @return bool
548
     */
549 47
    public function getRawUserQueryIsEmptyString()
550
    {
551 47
        $path = $this->prefixWithNamespace('q');
552 47
        $query = $this->argumentsAccessor->get($path, null);
553
554 47
        if ($query === null) {
555 4
            return false;
556
        }
557
558 43
        if (trim($query) === '') {
0 ignored issues
show
Bug introduced by
$query of type array is incompatible with the type string expected by parameter $str of trim(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

558
        if (trim(/** @scrutinizer ignore-type */ $query) === '') {
Loading history...
559 1
            return true;
560
        }
561
562 42
        return false;
563
    }
564
565
    /**
566
     * This method returns true when no querystring is present at all.
567
     * Which means no search by the user was triggered
568
     *
569
     * @return bool
570
     */
571 49
    public function getRawUserQueryIsNull()
572
    {
573 49
        $path = $this->prefixWithNamespace('q');
574 49
        $query = $this->argumentsAccessor->get($path, null);
575 49
        return $query === null;
576
    }
577
578
    /**
579
     * Sets the results per page that are used during search.
580
     *
581
     * @param int $resultsPerPage
582
     * @return SearchRequest
583
     */
584 50
    public function setResultsPerPage($resultsPerPage)
585
    {
586 50
        $path = $this->prefixWithNamespace('resultsPerPage');
587 50
        $this->argumentsAccessor->set($path, $resultsPerPage);
588 50
        $this->stateChanged = true;
589
590 50
        return $this;
591
    }
592
593
    /**
594
     * @return bool
595
     */
596 1
    public function getStateChanged()
597
    {
598 1
        return $this->stateChanged;
599
    }
600
601
    /**
602
     * Returns the passed resultsPerPage value
603
     * @return int|null
604
     */
605 51
    public function getResultsPerPage()
606
    {
607 51
        $path = $this->prefixWithNamespace('resultsPerPage');
608 51
        return $this->argumentsAccessor->get($path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->argumentsAccessor->get($path) also could return the type array which is incompatible with the documented return type integer|null.
Loading history...
609
    }
610
611
    /**
612
     * Allows to set additional filters that are used on time and not transported during the request.
613
     *
614
     * @param array $additionalFilters
615
     * @return SearchRequest
616
     */
617 1
    public function setAdditionalFilters($additionalFilters)
618
    {
619 1
        $path = $this->prefixWithNamespace('additionalFilters');
620 1
        $this->argumentsAccessor->set($path, $additionalFilters);
621 1
        $this->stateChanged = true;
622
623 1
        return $this;
624
    }
625
626
    /**
627
     * Retrieves the addtional filters that have been set
628
     *
629
     * @return array
630
     */
631 46
    public function getAdditionalFilters()
632
    {
633 46
        $path = $this->prefixWithNamespace('additionalFilters');
634 46
        return $this->argumentsAccessor->get($path, []);
635
    }
636
637
    /**
638
     * @return int
639
     */
640 35
    public function getContextSystemLanguageUid()
641
    {
642 35
        return $this->contextSystemLanguageUid;
643
    }
644
645
    /**
646
     * @return int
647
     */
648 35
    public function getContextPageUid()
649
    {
650 35
        return $this->contextPageUid;
651
    }
652
653
    /**
654
     * Get contextTypoScriptConfiguration
655
     *
656
     * @return TypoScriptConfiguration
657
     */
658 57
    public function getContextTypoScriptConfiguration()
659
    {
660 57
        return $this->contextTypoScriptConfiguration;
661
    }
662
663
    /**
664
     * Assigns the last known persistedArguments and restores their state.
665
     *
666
     * @return SearchRequest
667
     */
668 103
    public function reset()
669
    {
670 103
        $this->argumentsAccessor = new ArrayAccessor($this->persistedArguments);
671 103
        $this->stateChanged = false;
672 103
        return $this;
673
    }
674
675
    /**
676
     * This can be used to start a new sub request, e.g. for a faceted search.
677
     *
678
     * @param bool $onlyPersistentArguments
679
     * @return SearchRequest
680
     */
681 44
    public function getCopyForSubRequest($onlyPersistentArguments = true)
682
    {
683 44
        if (!$onlyPersistentArguments) {
684
            // create a new request with all data
685
            $argumentsArray = $this->argumentsAccessor->getData();
686
            return new SearchRequest(
687
                $argumentsArray,
688
                $this->contextPageUid,
689
                $this->contextSystemLanguageUid,
690
                $this->contextTypoScriptConfiguration
691
            );
692
        }
693
694 44
        $arguments = new ArrayAccessor();
695 44
        foreach ($this->persistentArgumentsPaths as $persistentArgumentPath) {
696 44
            if ($this->argumentsAccessor->has($persistentArgumentPath)) {
697 35
                $arguments->set($persistentArgumentPath, $this->argumentsAccessor->get($persistentArgumentPath));
698
            }
699
        }
700
701 44
        return new SearchRequest(
702 44
            $arguments->getData(),
703 44
            $this->contextPageUid,
704 44
            $this->contextSystemLanguageUid,
705 44
            $this->contextTypoScriptConfiguration
706
        );
707
    }
708
709
    /**
710
     * @return string
711
     */
712 28
    public function getArgumentNameSpace()
713
    {
714 28
        return $this->argumentNameSpace;
715
    }
716
717
    /**
718
     * @return array
719
     */
720 52
    public function getAsArray()
721
    {
722 52
        return $this->argumentsAccessor->getData();
723
    }
724
725
    /**
726
     * Returns only the arguments as array.
727
     *
728
     * @return array
729
     */
730 36
    public function getArguments() {
731 36
        return $this->argumentsAccessor->get($this->argumentNameSpace, []);
732
    }
733
}
734