Passed
Push — master ( efbfe4...f2d6e9 )
by Timo
23:10
created

SearchRequest::getContextSystemLanguageUid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
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 2 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 87
    public function __construct(array $argumentsArray = [], $pageUid = 0, $sysLanguageUid = 0, TypoScriptConfiguration $typoScriptConfiguration = null)
107
    {
108 87
        $this->stateChanged = true;
109 87
        $this->persistedArguments = $argumentsArray;
110 87
        $this->contextPageUid = $pageUid;
111 87
        $this->contextSystemLanguageUid = $sysLanguageUid;
112 87
        $this->contextTypoScriptConfiguration = $typoScriptConfiguration;
113 87
        $this->id = spl_object_hash($this);
114
115
        // overwrite the plugin namespace and the persistentArgumentsPaths
116 87
        if (!is_null($typoScriptConfiguration)) {
117 44
            $this->argumentNameSpace = $typoScriptConfiguration->getSearchPluginNamespace();
118
        }
119
120 87
        $this->persistentArgumentsPaths = [$this->argumentNameSpace . ':q', $this->argumentNameSpace . ':filter', $this->argumentNameSpace . ':sort', $this->argumentNameSpace . ':groupPage'];
121 87
        $this->reset();
122 87
    }
123
124
    /**
125
     * @return string
126
     */
127 28
    public function getId()
128
    {
129 28
        return $this->id;
130
    }
131
132
    /**
133
     * Can be used do merge arguments into the request arguments
134
     *
135
     * @param array $argumentsToMerge
136
     * @return SearchRequest
137
     */
138 1
    public function mergeArguments(array $argumentsToMerge)
139
    {
140 1
        ArrayUtility::mergeRecursiveWithOverrule(
141 1
            $this->persistedArguments,
142 1
            $argumentsToMerge
143
        );
144
145 1
        $this->reset();
146
147 1
        return $this;
148
    }
149
150
    /**
151
     * Helper method to prefix an accessor with the arguments namespace.
152
     *
153
     * @param string $path
154
     * @return string
155
     */
156 69
    protected function prefixWithNamespace($path)
157
    {
158 69
        return $this->argumentNameSpace . ':' . $path;
159
    }
160
161
    /**
162
     * @return array
163
     */
164 25
    public function getActiveFacetNames()
165
    {
166 25
        $activeFacets = $this->getActiveFacets();
167 25
        $facetNames = [];
168
169 25
        array_map(function($activeFacet) use (&$facetNames) {
170 2
            $facetNames[] = substr($activeFacet, 0, strpos($activeFacet, ':'));
171 25
        }, $activeFacets);
172
173 25
        return $facetNames;
174
    }
175
176
    /**
177
     * Returns all facet values for a certain facetName
178
     * @param string $facetName
179
     * @return array
180
     */
181 36
    public function getActiveFacetValuesByName($facetName)
182
    {
183 36
        $values = [];
184 36
        $activeFacets = $this->getActiveFacets();
185
186 36
        array_map(function($activeFacet) use (&$values, $facetName) {
187 9
            $parts = explode(':', $activeFacet, 2);
188 9
            if ($parts[0] === $facetName) {
189 9
                $values[] = $parts[1];
190
            }
191 36
        }, $activeFacets);
192
193 36
        return $values;
194
    }
195
196
    /**
197
     * @return array
198
     */
199 43
    public function getActiveFacets()
200
    {
201 43
        $path = $this->prefixWithNamespace('filter');
202 43
        $pathValue = $this->argumentsAccessor->get($path, []);
203
204 43
        return is_array($pathValue) ? $pathValue : [];
205
    }
206
207
    /**
208
     * @return int
209
     */
210 2
    public function getActiveFacetCount()
211
    {
212 2
        return count($this->getActiveFacets());
213
    }
214
215
    /**
216
     * @param $activeFacets
217
     *
218
     * @return SearchRequest
219
     */
220 37
    protected function setActiveFacets($activeFacets = [])
221
    {
222 37
        $path = $this->prefixWithNamespace('filter');
223 37
        $this->argumentsAccessor->set($path, $activeFacets);
224
225 37
        return $this;
226
    }
227
228
    /**
229
     * Adds a facet value to the request.
230
     *
231
     * @param string $facetName
232
     * @param mixed $facetValue
233
     *
234
     * @return SearchRequest
235
     */
236 32
    public function addFacetValue($facetName, $facetValue)
237
    {
238 32
        if ($this->getHasFacetValue($facetName, $facetValue)) {
239 1
            return $this;
240
        }
241
242 32
        $facetValues = $this->getActiveFacets();
243 32
        $facetValues[] = $facetName . ':' . $facetValue;
244 32
        $this->setActiveFacets($facetValues);
245
246 32
        $this->stateChanged = true;
247 32
        return $this;
248
    }
249
250
    /**
251
     * Removes a facet value from the request.
252
     *
253
     * @param string $facetName
254
     * @param mixed $facetValue
255
     *
256
     * @return SearchRequest
257
     */
258 4
    public function removeFacetValue($facetName, $facetValue)
259
    {
260 4
        if (!$this->getHasFacetValue($facetName, $facetValue)) {
261
            return $this;
262
        }
263 4
        $facetValues = $this->getActiveFacets();
264 4
        $facetValueToLookFor = $facetName . ':' . $facetValue;
265
266 4
        foreach ($facetValues as $index => $facetValue) {
267 4
            if ($facetValue === $facetValueToLookFor) {
268 4
                unset($facetValues[$index]);
269 4
                break;
270
            }
271
        }
272
273 4
        $this->setActiveFacets($facetValues);
274 4
        $this->stateChanged = true;
275 4
        return $this;
276
    }
277
278
    /**
279
     * Removes all facet values from the request by a certain facet name
280
     *
281
     * @param string $facetName
282
     *
283
     * @return SearchRequest
284
     */
285 3
    public function removeAllFacetValuesByName($facetName)
286
    {
287 3
        $facetValues = $this->getActiveFacets();
288 3
        $facetValues = array_filter($facetValues, function($facetValue) use ($facetName) {
289 2
            $parts = explode(':', $facetValue, 2);
290 2
            return $parts[0] !== $facetName;
291 3
        });
292
293 3
        $this->setActiveFacets($facetValues);
294 3
        $this->stateChanged = true;
295 3
        return $this;
296
    }
297
298
    /**
299
     * Removes all active facets from the request.
300
     *
301
     * @return SearchRequest
302
     */
303 4
    public function removeAllFacets()
304
    {
305 4
        $path = $this->prefixWithNamespace('filter');
306 4
        $this->argumentsAccessor->reset($path);
307 4
        $this->stateChanged = true;
308 4
        return $this;
309
    }
310
311
    /**
312
     * @param string $facetName
313
     * @param mixed $facetValue
314
     * @return bool
315
     */
316 36
    public function getHasFacetValue($facetName, $facetValue)
317
    {
318 36
        $facetNameAndValueToCheck = $facetName . ':' . $facetValue;
319 36
        return in_array($facetNameAndValueToCheck, $this->getActiveFacets());
320
    }
321
322
    /**
323
     * @return bool
324
     */
325 40
    public function getHasSorting()
326
    {
327 40
        $path = $this->prefixWithNamespace('sort');
328 40
        return $this->argumentsAccessor->has($path);
329
    }
330
331
    /**
332
     * Returns the sorting string in the url e.g. title asc.
333
     *
334
     * @return string
335
     */
336 39
    public function getSorting()
337
    {
338 39
        $path = $this->prefixWithNamespace('sort');
339 39
        return $this->argumentsAccessor->get($path, '');
340
    }
341
342
    /**
343
     * Helper function to get the sorting configuration name or direction.
344
     *
345
     * @param int $index
346
     * @return string
347
     */
348 39
    protected function getSortingPart($index)
349
    {
350 39
        $sorting = $this->getSorting();
351 39
        if ($sorting === '') {
352 37
            return null;
353
        }
354
355 2
        $parts = explode(' ', $sorting);
356 2
        return isset($parts[$index]) ? $parts[$index] : null;
357
    }
358
359
    /**
360
     * Returns the sorting configuration name that is currently used.
361
     *
362
     * @return string
363
     */
364 39
    public function getSortingName()
365
    {
366 39
        return $this->getSortingPart(0);
367
    }
368
369
    /**
370
     * Returns the sorting direction that is currently used.
371
     *
372
     * @return string
373
     */
374 38
    public function getSortingDirection()
375
    {
376 38
        return mb_strtolower($this->getSortingPart(1));
377
    }
378
379
    /**
380
     * @return SearchRequest
381
     */
382 28
    public function removeSorting()
383
    {
384 28
        $path = $this->prefixWithNamespace('sort');
385 28
        $this->argumentsAccessor->reset($path);
386 28
        $this->stateChanged = true;
387 28
        return $this;
388
    }
389
390
    /**
391
     * @param string $sortingName
392
     * @param string $direction (asc or desc)
393
     *
394
     * @return SearchRequest
395
     */
396 29
    public function setSorting($sortingName, $direction = 'asc')
397
    {
398 29
        $value = $sortingName . ' ' . $direction;
399 29
        $path = $this->prefixWithNamespace('sort');
400 29
        $this->argumentsAccessor->set($path, $value);
401 29
        $this->stateChanged = true;
402 29
        return $this;
403
    }
404
405
    /**
406
     * Method to set the paginated page of the search
407
     *
408
     * @param int $page
409
     * @return SearchRequest
410
     */
411 13
    public function setPage($page)
412
    {
413 13
        $this->stateChanged = true;
414 13
        $path = $this->prefixWithNamespace('page');
415 13
        $this->argumentsAccessor->set($path, $page);
416 13
        return $this;
417
    }
418
419
    /**
420
     * Returns the passed page.
421
     *
422
     * @return int|null
423
     */
424 41
    public function getPage()
425
    {
426 41
        $path = $this->prefixWithNamespace('page');
427 41
        return $this->argumentsAccessor->get($path);
428
    }
429
430
    /**
431
     * Can be used to paginate within a groupItem.
432
     *
433
     * @param string $groupName e.g. type
434
     * @param string $groupItemValue e.g. pages
435
     * @param int $page
436
     * @return SearchRequest
437
     */
438 1
    public function setGroupItemPage(string $groupName, string $groupItemValue, int $page): SearchRequest
439
    {
440 1
        $this->stateChanged = true;
441 1
        $path = $this->prefixWithNamespace('groupPage:' . $groupName . ':' . $groupItemValue);
442 1
        $this->argumentsAccessor->set($path, $page);
443 1
        return $this;
444
    }
445
446
    /**
447
     * Retrieves the current page for this group item.
448
     *
449
     * @param string $groupName
450
     * @param string $groupItemValue
451
     * @return int
452
     */
453 1
    public function getGroupItemPage(string $groupName, string $groupItemValue): int
454
    {
455 1
        $path = $this->prefixWithNamespace('groupPage:' . $groupName . ':' . $groupItemValue);
456 1
        return max(1, (int)$this->argumentsAccessor->get($path));
457
    }
458
459
    /**
460
     * Retrieves the highest page of the groups.
461
     *
462
     * @return int
463
     */
464
    public function getHighestGroupPage()
465
    {
466
        $max = 1;
467
        $path = $this->prefixWithNamespace('groupPage');
468
        $groupPages = $this->argumentsAccessor->get($path);
469
        foreach ($groupPages as $groups) {
0 ignored issues
show
Bug introduced by
The expression $groupPages of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
470
            if (!is_array($groups)) continue;
471
            foreach ($groups as $groupItemPage) {
472
                if ($groupItemPage > $max) {
473
                    $max = $groupItemPage;
474
                }
475
            }
476
        }
477
478
        return $max;
479
    }
480
481
    /**
482
     * Method to overwrite the query string.
483
     *
484
     * @param string $rawQueryString
485
     * @return SearchRequest
486
     */
487 38
    public function setRawQueryString($rawQueryString)
488
    {
489 38
        $this->stateChanged = true;
490 38
        $path = $this->prefixWithNamespace('q');
491 38
        $this->argumentsAccessor->set($path, $rawQueryString);
492 38
        return $this;
493
    }
494
495
    /**
496
     * Returns the passed rawQueryString.
497
     *
498
     * @return string|null
499
     */
500 41
    public function getRawUserQuery()
501
    {
502 41
        $path = $this->prefixWithNamespace('q');
503 41
        $query = $this->argumentsAccessor->get($path, null);
504 41
        return is_null($query) ? $query : (string)$query;
505
    }
506
507
    /**
508
     * Method to check if the query string is an empty string
509
     * (also empty string or whitespaces only are handled as empty).
510
     *
511
     * When no query string is set (null) the method returns false.
512
     * @return bool
513
     */
514 38
    public function getRawUserQueryIsEmptyString()
515
    {
516 38
        $path = $this->prefixWithNamespace('q');
517 38
        $query = $this->argumentsAccessor->get($path, null);
518
519 38
        if ($query === null) {
520 4
            return false;
521
        }
522
523 34
        if (trim($query) === '') {
524
            return true;
525
        }
526
527 34
        return false;
528
    }
529
530
    /**
531
     * This method returns true when no querystring is present at all.
532
     * Which means no search by the user was triggered
533
     *
534
     * @return bool
535
     */
536 40
    public function getRawUserQueryIsNull()
537
    {
538 40
        $path = $this->prefixWithNamespace('q');
539 40
        $query = $this->argumentsAccessor->get($path, null);
540 40
        return $query === null;
541
    }
542
543
    /**
544
     * Sets the results per page that are used during search.
545
     *
546
     * @param int $resultsPerPage
547
     * @return SearchRequest
548
     */
549 43
    public function setResultsPerPage($resultsPerPage)
550
    {
551 43
        $path = $this->prefixWithNamespace('resultsPerPage');
552 43
        $this->argumentsAccessor->set($path, $resultsPerPage);
553 43
        $this->stateChanged = true;
554
555 43
        return $this;
556
    }
557
558
    /**
559
     * @return bool
560
     */
561 1
    public function getStateChanged()
562
    {
563 1
        return $this->stateChanged;
564
    }
565
566
    /**
567
     * Returns the passed resultsPerPage value
568
     * @return int|null
569
     */
570 42
    public function getResultsPerPage()
571
    {
572 42
        $path = $this->prefixWithNamespace('resultsPerPage');
573 42
        return $this->argumentsAccessor->get($path);
574
    }
575
576
    /**
577
     * @return int
578
     */
579 30
    public function getContextSystemLanguageUid()
580
    {
581 30
        return $this->contextSystemLanguageUid;
582
    }
583
584
    /**
585
     * @return int
586
     */
587 30
    public function getContextPageUid()
588
    {
589 30
        return $this->contextPageUid;
590
    }
591
592
    /**
593
     * Get contextTypoScriptConfiguration
594
     *
595
     * @return TypoScriptConfiguration
596
     */
597 48
    public function getContextTypoScriptConfiguration()
598
    {
599 48
        return $this->contextTypoScriptConfiguration;
600
    }
601
602
    /**
603
     * Assigns the last known persistedArguments and restores their state.
604
     *
605
     * @return SearchRequest
606
     */
607 85
    public function reset()
608
    {
609 85
        $this->argumentsAccessor = new ArrayAccessor($this->persistedArguments);
610 85
        $this->stateChanged = false;
611 85
        return $this;
612
    }
613
614
    /**
615
     * This can be used to start a new sub request, e.g. for a faceted search.
616
     *
617
     * @param bool $onlyPersistentArguments
618
     * @return SearchRequest
619
     */
620 35
    public function getCopyForSubRequest($onlyPersistentArguments = true)
621
    {
622 35
        if (!$onlyPersistentArguments) {
623
            // create a new request with all data
624
            $argumentsArray = $this->argumentsAccessor->getData();
625
            return new SearchRequest(
626
                $argumentsArray,
627
                $this->contextPageUid,
628
                $this->contextSystemLanguageUid,
629
                $this->contextTypoScriptConfiguration
630
            );
631
        }
632
633 35
        $arguments = new ArrayAccessor();
634 35
        foreach ($this->persistentArgumentsPaths as $persistentArgumentPath) {
635 35
            if ($this->argumentsAccessor->has($persistentArgumentPath)) {
636 35
                $arguments->set($persistentArgumentPath, $this->argumentsAccessor->get($persistentArgumentPath));
637
            }
638
        }
639
640 35
        return new SearchRequest(
641 35
            $arguments->getData(),
642 35
            $this->contextPageUid,
643 35
            $this->contextSystemLanguageUid,
644 35
            $this->contextTypoScriptConfiguration
645
        );
646
    }
647
648
    /**
649
     * @return string
650
     */
651 22
    public function getArgumentNameSpace()
652
    {
653 22
        return $this->argumentNameSpace;
654
    }
655
656
    /**
657
     * @return array
658
     */
659 44
    public function getAsArray()
660
    {
661 44
        return $this->argumentsAccessor->getData();
662
    }
663
664
    /**
665
     * Returns only the arguments as array.
666
     *
667
     * @return array
668
     */
669 32
    public function getArguments() {
670 32
        return $this->argumentsAccessor->get($this->argumentNameSpace, []);
671
    }
672
}
673