Passed
Push — master ( a3f50d...2cf8ca )
by Timo
06:00
created

SearchRequest::removeAllGroupItemPages()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
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 91
    public function __construct(array $argumentsArray = [], $pageUid = 0, $sysLanguageUid = 0, TypoScriptConfiguration $typoScriptConfiguration = null)
107
    {
108 91
        $this->stateChanged = true;
109 91
        $this->persistedArguments = $argumentsArray;
110 91
        $this->contextPageUid = $pageUid;
111 91
        $this->contextSystemLanguageUid = $sysLanguageUid;
112 91
        $this->contextTypoScriptConfiguration = $typoScriptConfiguration;
113 91
        $this->id = spl_object_hash($this);
114
115
        // overwrite the plugin namespace and the persistentArgumentsPaths
116 91
        if (!is_null($typoScriptConfiguration)) {
117 48
            $this->argumentNameSpace = $typoScriptConfiguration->getSearchPluginNamespace();
118
        }
119
120 91
        $this->persistentArgumentsPaths = [$this->argumentNameSpace . ':q', $this->argumentNameSpace . ':filter', $this->argumentNameSpace . ':sort', $this->argumentNameSpace . ':groupPage'];
121 91
        $this->reset();
122 91
    }
123
124
    /**
125
     * @return string
126
     */
127 32
    public function getId()
128
    {
129 32
        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 73
    protected function prefixWithNamespace($path)
157
    {
158 73
        return $this->argumentNameSpace . ':' . $path;
159
    }
160
161
    /**
162
     * @return array
163
     */
164 28
    public function getActiveFacetNames()
165
    {
166 28
        $activeFacets = $this->getActiveFacets();
167 28
        $facetNames = [];
168
169 28
        array_map(function($activeFacet) use (&$facetNames) {
170 3
            $facetNames[] = substr($activeFacet, 0, strpos($activeFacet, ':'));
171 28
        }, $activeFacets);
172
173 28
        return $facetNames;
174
    }
175
176
    /**
177
     * Returns all facet values for a certain facetName
178
     * @param string $facetName
179
     * @return array
180
     */
181 40
    public function getActiveFacetValuesByName($facetName)
182
    {
183 40
        $values = [];
184 40
        $activeFacets = $this->getActiveFacets();
185
186 40
        array_map(function($activeFacet) use (&$values, $facetName) {
187 11
            $parts = explode(':', $activeFacet, 2);
188 11
            if ($parts[0] === $facetName) {
189 11
                $values[] = $parts[1];
190
            }
191 40
        }, $activeFacets);
192
193 40
        return $values;
194
    }
195
196
    /**
197
     * @return array
198
     */
199 47
    public function getActiveFacets()
200
    {
201 47
        $path = $this->prefixWithNamespace('filter');
202 47
        $pathValue = $this->argumentsAccessor->get($path, []);
203
204 47
        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 41
    protected function setActiveFacets($activeFacets = [])
221
    {
222 41
        $path = $this->prefixWithNamespace('filter');
223 41
        $this->argumentsAccessor->set($path, $activeFacets);
224
225 41
        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 36
    public function addFacetValue($facetName, $facetValue)
237
    {
238 36
        if ($this->getHasFacetValue($facetName, $facetValue)) {
239 3
            return $this;
240
        }
241
242 36
        $facetValues = $this->getActiveFacets();
243 36
        $facetValues[] = $facetName . ':' . $facetValue;
244 36
        $this->setActiveFacets($facetValues);
245
246 36
        $this->stateChanged = true;
247 36
        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 6
    public function removeFacetValue($facetName, $facetValue)
259
    {
260 6
        if (!$this->getHasFacetValue($facetName, $facetValue)) {
261
            return $this;
262
        }
263 6
        $facetValues = $this->getActiveFacets();
264 6
        $facetValueToLookFor = $facetName . ':' . $facetValue;
265
266 6
        foreach ($facetValues as $index => $facetValue) {
267 6
            if ($facetValue === $facetValueToLookFor) {
268 6
                unset($facetValues[$index]);
269 6
                break;
270
            }
271
        }
272
273 6
        $this->setActiveFacets($facetValues);
274 6
        $this->stateChanged = true;
275 6
        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 6
    public function removeAllFacets()
304
    {
305 6
        $path = $this->prefixWithNamespace('filter');
306 6
        $this->argumentsAccessor->reset($path);
307 6
        $this->stateChanged = true;
308 6
        return $this;
309
    }
310
311
    /**
312
     * @param string $facetName
313
     * @param mixed $facetValue
314
     * @return bool
315
     */
316 40
    public function getHasFacetValue($facetName, $facetValue)
317
    {
318 40
        $facetNameAndValueToCheck = $facetName . ':' . $facetValue;
319 40
        return in_array($facetNameAndValueToCheck, $this->getActiveFacets());
320
    }
321
322
    /**
323
     * @return bool
324
     */
325 44
    public function getHasSorting()
326
    {
327 44
        $path = $this->prefixWithNamespace('sort');
328 44
        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 43
    public function getSorting()
337
    {
338 43
        $path = $this->prefixWithNamespace('sort');
339 43
        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 43
    protected function getSortingPart($index)
349
    {
350 43
        $sorting = $this->getSorting();
351 43
        if ($sorting === '') {
352 41
            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 43
    public function getSortingName()
365
    {
366 43
        return $this->getSortingPart(0);
367
    }
368
369
    /**
370
     * Returns the sorting direction that is currently used.
371
     *
372
     * @return string
373
     */
374 42
    public function getSortingDirection()
375
    {
376 42
        return mb_strtolower($this->getSortingPart(1));
377
    }
378
379
    /**
380
     * @return SearchRequest
381
     */
382 30
    public function removeSorting()
383
    {
384 30
        $path = $this->prefixWithNamespace('sort');
385 30
        $this->argumentsAccessor->reset($path);
386 30
        $this->stateChanged = true;
387 30
        return $this;
388
    }
389
390
    /**
391
     * @param string $sortingName
392
     * @param string $direction (asc or desc)
393
     *
394
     * @return SearchRequest
395
     */
396 31
    public function setSorting($sortingName, $direction = 'asc')
397
    {
398 31
        $value = $sortingName . ' ' . $direction;
399 31
        $path = $this->prefixWithNamespace('sort');
400 31
        $this->argumentsAccessor->set($path, $value);
401 31
        $this->stateChanged = true;
402 31
        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 14
    public function setPage($page)
412
    {
413 14
        $this->stateChanged = true;
414 14
        $path = $this->prefixWithNamespace('page');
415 14
        $this->argumentsAccessor->set($path, $page);
416 14
        return $this;
417
    }
418
419
    /**
420
     * Returns the passed page.
421
     *
422
     * @return int|null
423
     */
424 45
    public function getPage()
425
    {
426 45
        $path = $this->prefixWithNamespace('page');
427 45
        return $this->argumentsAccessor->get($path);
428
    }
429
430
    /**
431
     * Can be used to reset all groupPages.
432
     *
433
     * @return SearchRequest
434
     */
435
    public function removeAllGroupItemPages(): SearchRequest
436
    {
437
        $path = $this->prefixWithNamespace('groupPage');
438 1
        $this->argumentsAccessor->reset($path);
439
440 1
        return $this;
441 1
    }
442 1
443 1
    /**
444
     * Can be used to paginate within a groupItem.
445
     *
446
     * @param string $groupName e.g. type
447
     * @param string $groupItemValue e.g. pages
448
     * @param int $page
449
     * @return SearchRequest
450
     */
451
    public function setGroupItemPage(string $groupName, string $groupItemValue, int $page): SearchRequest
452
    {
453 1
        $this->stateChanged = true;
454
        $path = $this->prefixWithNamespace('groupPage:' . $groupName . ':' . $groupItemValue);
455 1
        $this->argumentsAccessor->set($path, $page);
456 1
        return $this;
457
    }
458
459
    /**
460
     * Retrieves the current page for this group item.
461
     *
462
     * @param string $groupName
463
     * @param string $groupItemValue
464
     * @return int
465
     */
466
    public function getGroupItemPage(string $groupName, string $groupItemValue): int
467
    {
468
        $path = $this->prefixWithNamespace('groupPage:' . $groupName . ':' . $groupItemValue);
469
        return max(1, (int)$this->argumentsAccessor->get($path));
470
    }
471
472
    /**
473
     * Retrieves the highest page of the groups.
474
     *
475
     * @return int
476
     */
477
    public function getHighestGroupPage()
478
    {
479
        $max = 1;
480
        $path = $this->prefixWithNamespace('groupPage');
481
        $groupPages = $this->argumentsAccessor->get($path);
482
        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...
483
            if (!is_array($groups)) continue;
484
            foreach ($groups as $groupItemPage) {
485
                if ($groupItemPage > $max) {
486
                    $max = $groupItemPage;
487 41
                }
488
            }
489 41
        }
490 41
491 41
        return $max;
492 41
    }
493
494
    /**
495
     * Method to overwrite the query string.
496
     *
497
     * @param string $rawQueryString
498
     * @return SearchRequest
499
     */
500 45
    public function setRawQueryString($rawQueryString)
501
    {
502 45
        $this->stateChanged = true;
503 45
        $path = $this->prefixWithNamespace('q');
504 45
        $this->argumentsAccessor->set($path, $rawQueryString);
505
        return $this;
506
    }
507
508
    /**
509
     * Returns the passed rawQueryString.
510
     *
511
     * @return string|null
512
     */
513
    public function getRawUserQuery()
514 42
    {
515
        $path = $this->prefixWithNamespace('q');
516 42
        $query = $this->argumentsAccessor->get($path, null);
517 42
        return is_null($query) ? $query : (string)$query;
518
    }
519 42
520 4
    /**
521
     * Method to check if the query string is an empty string
522
     * (also empty string or whitespaces only are handled as empty).
523 38
     *
524
     * When no query string is set (null) the method returns false.
525
     * @return bool
526
     */
527 38
    public function getRawUserQueryIsEmptyString()
528
    {
529
        $path = $this->prefixWithNamespace('q');
530
        $query = $this->argumentsAccessor->get($path, null);
531
532
        if ($query === null) {
533
            return false;
534
        }
535
536 44
        if (trim($query) === '') {
537
            return true;
538 44
        }
539 44
540 44
        return false;
541
    }
542
543
    /**
544
     * This method returns true when no querystring is present at all.
545
     * Which means no search by the user was triggered
546
     *
547
     * @return bool
548
     */
549 47
    public function getRawUserQueryIsNull()
550
    {
551 47
        $path = $this->prefixWithNamespace('q');
552 47
        $query = $this->argumentsAccessor->get($path, null);
553 47
        return $query === null;
554
    }
555 47
556
    /**
557
     * Sets the results per page that are used during search.
558
     *
559
     * @param int $resultsPerPage
560
     * @return SearchRequest
561 1
     */
562
    public function setResultsPerPage($resultsPerPage)
563 1
    {
564
        $path = $this->prefixWithNamespace('resultsPerPage');
565
        $this->argumentsAccessor->set($path, $resultsPerPage);
566
        $this->stateChanged = true;
567
568
        return $this;
569
    }
570 46
571
    /**
572 46
     * @return bool
573 46
     */
574
    public function getStateChanged()
575
    {
576
        return $this->stateChanged;
577
    }
578
579 33
    /**
580
     * Returns the passed resultsPerPage value
581 33
     * @return int|null
582
     */
583
    public function getResultsPerPage()
584
    {
585
        $path = $this->prefixWithNamespace('resultsPerPage');
586
        return $this->argumentsAccessor->get($path);
587 33
    }
588
589 33
    /**
590
     * @return int
591
     */
592
    public function getContextSystemLanguageUid()
593
    {
594
        return $this->contextSystemLanguageUid;
595
    }
596
597 52
    /**
598
     * @return int
599 52
     */
600
    public function getContextPageUid()
601
    {
602
        return $this->contextPageUid;
603
    }
604
605
    /**
606
     * Get contextTypoScriptConfiguration
607 89
     *
608
     * @return TypoScriptConfiguration
609 89
     */
610 89
    public function getContextTypoScriptConfiguration()
611 89
    {
612
        return $this->contextTypoScriptConfiguration;
613
    }
614
615
    /**
616
     * Assigns the last known persistedArguments and restores their state.
617
     *
618
     * @return SearchRequest
619
     */
620 38
    public function reset()
621
    {
622 38
        $this->argumentsAccessor = new ArrayAccessor($this->persistedArguments);
623
        $this->stateChanged = false;
624
        return $this;
625
    }
626
627
    /**
628
     * This can be used to start a new sub request, e.g. for a faceted search.
629
     *
630
     * @param bool $onlyPersistentArguments
631
     * @return SearchRequest
632
     */
633 38
    public function getCopyForSubRequest($onlyPersistentArguments = true)
634 38
    {
635 38
        if (!$onlyPersistentArguments) {
636 38
            // create a new request with all data
637
            $argumentsArray = $this->argumentsAccessor->getData();
638
            return new SearchRequest(
639
                $argumentsArray,
640 38
                $this->contextPageUid,
641 38
                $this->contextSystemLanguageUid,
642 38
                $this->contextTypoScriptConfiguration
643 38
            );
644 38
        }
645
646
        $arguments = new ArrayAccessor();
647
        foreach ($this->persistentArgumentsPaths as $persistentArgumentPath) {
648
            if ($this->argumentsAccessor->has($persistentArgumentPath)) {
649
                $arguments->set($persistentArgumentPath, $this->argumentsAccessor->get($persistentArgumentPath));
650
            }
651 25
        }
652
653 25
        return new SearchRequest(
654
            $arguments->getData(),
655
            $this->contextPageUid,
656
            $this->contextSystemLanguageUid,
657
            $this->contextTypoScriptConfiguration
658
        );
659 47
    }
660
661 47
    /**
662
     * @return string
663
     */
664
    public function getArgumentNameSpace()
665
    {
666
        return $this->argumentNameSpace;
667
    }
668
669 36
    /**
670 36
     * @return array
671
     */
672
    public function getAsArray()
673
    {
674
        return $this->argumentsAccessor->getData();
675
    }
676
677
    /**
678
     * Returns only the arguments as array.
679
     *
680
     * @return array
681
     */
682
    public function getArguments() {
683
        return $this->argumentsAccessor->get($this->argumentNameSpace, []);
684
    }
685
}
686