Failed Conditions
Push — master ( 5f60a5...9b80eb )
by Rafael
21:42
created

SearchRequest   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 646
Duplicated Lines 0 %

Test Coverage

Coverage 91.26%

Importance

Changes 0
Metric Value
wmc 61
dl 0
loc 646
ccs 188
cts 206
cp 0.9126
rs 3.681
c 0
b 0
f 0

42 Methods

Rating   Name   Duplication   Size   Complexity  
A getId() 0 3 1
A removeAllFacets() 0 6 1
A getRawUserQueryIsNull() 0 5 1
A getSortingPart() 0 9 3
A addFacetValue() 0 12 2
A getRawUserQueryIsEmptyString() 0 14 3
A getHasSorting() 0 4 1
A getActiveFacetCount() 0 3 1
A getArgumentNameSpace() 0 3 1
A reset() 0 5 1
B getHighestGroupPage() 0 15 5
A setActiveFacets() 0 6 1
A removeSorting() 0 6 1
A __construct() 0 16 2
A getContextTypoScriptConfiguration() 0 3 1
A setGroupItemPage() 0 6 1
A setRawQueryString() 0 6 1
A getRawUserQuery() 0 5 2
A removeFacetValue() 0 18 4
A setResultsPerPage() 0 7 1
A setPage() 0 6 1
A getHasFacetValue() 0 4 1
A getActiveFacets() 0 6 2
A getArguments() 0 2 1
A getAsArray() 0 3 1
A removeAllGroupItemPages() 0 6 1
A getContextSystemLanguageUid() 0 3 1
A prefixWithNamespace() 0 3 1
A getActiveFacetNames() 0 10 1
A getSortingName() 0 3 1
A getSortingDirection() 0 3 1
A getPage() 0 4 1
B getCopyForSubRequest() 0 25 4
A setSorting() 0 7 1
A getActiveFacetValuesByName() 0 13 2
A removeAllFacetValuesByName() 0 11 1
A getResultsPerPage() 0 4 1
A getSorting() 0 4 1
A getContextPageUid() 0 3 1
A getStateChanged() 0 3 1
A mergeArguments() 0 10 1
A getGroupItemPage() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like SearchRequest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SearchRequest, and based on these observations, apply Extract Interface, too.

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 94
    public function __construct(array $argumentsArray = [], $pageUid = 0, $sysLanguageUid = 0, TypoScriptConfiguration $typoScriptConfiguration = null)
107
    {
108 94
        $this->stateChanged = true;
109 94
        $this->persistedArguments = $argumentsArray;
110 94
        $this->contextPageUid = $pageUid;
111 94
        $this->contextSystemLanguageUid = $sysLanguageUid;
112 94
        $this->contextTypoScriptConfiguration = $typoScriptConfiguration;
113 94
        $this->id = spl_object_hash($this);
114
115
        // overwrite the plugin namespace and the persistentArgumentsPaths
116 94
        if (!is_null($typoScriptConfiguration)) {
117 50
            $this->argumentNameSpace = $typoScriptConfiguration->getSearchPluginNamespace();
118
        }
119
120 94
        $this->persistentArgumentsPaths = [$this->argumentNameSpace . ':q', $this->argumentNameSpace . ':filter', $this->argumentNameSpace . ':sort', $this->argumentNameSpace . ':groupPage'];
121 94
        $this->reset();
122 94
    }
123
124
    /**
125
     * @return string
126
     */
127 34
    public function getId()
128
    {
129 34
        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 76
    protected function prefixWithNamespace($path)
157
    {
158 76
        return $this->argumentNameSpace . ':' . $path;
159
    }
160
161
    /**
162
     * @return array
163
     */
164 29
    public function getActiveFacetNames()
165
    {
166 29
        $activeFacets = $this->getActiveFacets();
167 29
        $facetNames = [];
168
169 29
        array_map(function($activeFacet) use (&$facetNames) {
170 3
            $facetNames[] = substr($activeFacet, 0, strpos($activeFacet, ':'));
171 29
        }, $activeFacets);
172
173 29
        return $facetNames;
174
    }
175
176
    /**
177
     * Returns all facet values for a certain facetName
178
     * @param string $facetName
179
     * @return array
180
     */
181 41
    public function getActiveFacetValuesByName($facetName)
182
    {
183 41
        $values = [];
184 41
        $activeFacets = $this->getActiveFacets();
185
186 41
        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 41
        }, $activeFacets);
192
193 41
        return $values;
194
    }
195
196
    /**
197
     * @return array
198
     */
199 49
    public function getActiveFacets()
200
    {
201 49
        $path = $this->prefixWithNamespace('filter');
202 49
        $pathValue = $this->argumentsAccessor->get($path, []);
203
204 49
        return is_array($pathValue) ? $pathValue : [];
0 ignored issues
show
introduced by
The condition is_array($pathValue) can never be false.
Loading history...
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 43
    protected function setActiveFacets($activeFacets = [])
221
    {
222 43
        $path = $this->prefixWithNamespace('filter');
223 43
        $this->argumentsAccessor->set($path, $activeFacets);
224
225 43
        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 38
    public function addFacetValue($facetName, $facetValue)
237
    {
238 38
        if ($this->getHasFacetValue($facetName, $facetValue)) {
239 3
            return $this;
240
        }
241
242 38
        $facetValues = $this->getActiveFacets();
243 38
        $facetValues[] = $facetName . ':' . $facetValue;
244 38
        $this->setActiveFacets($facetValues);
245
246 38
        $this->stateChanged = true;
247 38
        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) {
0 ignored issues
show
introduced by
$facetValue is overwriting one of the parameters of this function.
Loading history...
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 42
    public function getHasFacetValue($facetName, $facetValue)
317
    {
318 42
        $facetNameAndValueToCheck = $facetName . ':' . $facetValue;
319 42
        return in_array($facetNameAndValueToCheck, $this->getActiveFacets());
320
    }
321
322
    /**
323
     * @return bool
324
     */
325 45
    public function getHasSorting()
326
    {
327 45
        $path = $this->prefixWithNamespace('sort');
328 45
        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 44
    public function getSorting()
337
    {
338 44
        $path = $this->prefixWithNamespace('sort');
339 44
        return $this->argumentsAccessor->get($path, '');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->argumentsAccessor->get($path, '') returns the type array which is incompatible with the documented return type string.
Loading history...
340
    }
341
342
    /**
343
     * Helper function to get the sorting configuration name or direction.
344
     *
345
     * @param int $index
346
     * @return string
347
     */
348 44
    protected function getSortingPart($index)
349
    {
350 44
        $sorting = $this->getSorting();
351 44
        if ($sorting === '') {
352 42
            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 44
    public function getSortingName()
365
    {
366 44
        return $this->getSortingPart(0);
367
    }
368
369
    /**
370
     * Returns the sorting direction that is currently used.
371
     *
372
     * @return string
373
     */
374 43
    public function getSortingDirection()
375
    {
376 43
        return mb_strtolower($this->getSortingPart(1));
377
    }
378
379
    /**
380
     * @return SearchRequest
381
     */
382 31
    public function removeSorting()
383
    {
384 31
        $path = $this->prefixWithNamespace('sort');
385 31
        $this->argumentsAccessor->reset($path);
386 31
        $this->stateChanged = true;
387 31
        return $this;
388
    }
389
390
    /**
391
     * @param string $sortingName
392
     * @param string $direction (asc or desc)
393
     *
394
     * @return SearchRequest
395
     */
396 32
    public function setSorting($sortingName, $direction = 'asc')
397
    {
398 32
        $value = $sortingName . ' ' . $direction;
399 32
        $path = $this->prefixWithNamespace('sort');
400 32
        $this->argumentsAccessor->set($path, $value);
401 32
        $this->stateChanged = true;
402 32
        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 46
    public function getPage()
425
    {
426 46
        $path = $this->prefixWithNamespace('page');
427 46
        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 null|integer.
Loading history...
428
    }
429
430
    /**
431
     * Can be used to reset all groupPages.
432
     *
433
     * @return SearchRequest
434
     */
435 36
    public function removeAllGroupItemPages(): SearchRequest
436
    {
437 36
        $path = $this->prefixWithNamespace('groupPage');
438 36
        $this->argumentsAccessor->reset($path);
439
440 36
        return $this;
441
    }
442
443
    /**
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 2
    public function setGroupItemPage(string $groupName, string $groupItemValue, int $page): SearchRequest
452
    {
453 2
        $this->stateChanged = true;
454 2
        $path = $this->prefixWithNamespace('groupPage:' . $groupName . ':' . $groupItemValue);
455 2
        $this->argumentsAccessor->set($path, $page);
456 2
        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 1
    public function getGroupItemPage(string $groupName, string $groupItemValue): int
467
    {
468 1
        $path = $this->prefixWithNamespace('groupPage:' . $groupName . ':' . $groupItemValue);
469 1
        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) {
483
            if (!is_array($groups)) continue;
484
            foreach ($groups as $groupItemPage) {
485
                if ($groupItemPage > $max) {
486
                    $max = $groupItemPage;
487
                }
488
            }
489
        }
490
491
        return $max;
492
    }
493
494
    /**
495
     * Method to overwrite the query string.
496
     *
497
     * @param string $rawQueryString
498
     * @return SearchRequest
499
     */
500 42
    public function setRawQueryString($rawQueryString)
501
    {
502 42
        $this->stateChanged = true;
503 42
        $path = $this->prefixWithNamespace('q');
504 42
        $this->argumentsAccessor->set($path, $rawQueryString);
505 42
        return $this;
506
    }
507
508
    /**
509
     * Returns the passed rawQueryString.
510
     *
511
     * @return string|null
512
     */
513 46
    public function getRawUserQuery()
514
    {
515 46
        $path = $this->prefixWithNamespace('q');
516 46
        $query = $this->argumentsAccessor->get($path, null);
517 46
        return is_null($query) ? $query : (string)$query;
518
    }
519
520
    /**
521
     * Method to check if the query string is an empty string
522
     * (also empty string or whitespaces only are handled as empty).
523
     *
524
     * When no query string is set (null) the method returns false.
525
     * @return bool
526
     */
527 43
    public function getRawUserQueryIsEmptyString()
528
    {
529 43
        $path = $this->prefixWithNamespace('q');
530 43
        $query = $this->argumentsAccessor->get($path, null);
531
532 43
        if ($query === null) {
533 4
            return false;
534
        }
535
536 39
        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

536
        if (trim(/** @scrutinizer ignore-type */ $query) === '') {
Loading history...
537
            return true;
538
        }
539
540 39
        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 45
    public function getRawUserQueryIsNull()
550
    {
551 45
        $path = $this->prefixWithNamespace('q');
552 45
        $query = $this->argumentsAccessor->get($path, null);
553 45
        return $query === null;
554
    }
555
556
    /**
557
     * Sets the results per page that are used during search.
558
     *
559
     * @param int $resultsPerPage
560
     * @return SearchRequest
561
     */
562 48
    public function setResultsPerPage($resultsPerPage)
563
    {
564 48
        $path = $this->prefixWithNamespace('resultsPerPage');
565 48
        $this->argumentsAccessor->set($path, $resultsPerPage);
566 48
        $this->stateChanged = true;
567
568 48
        return $this;
569
    }
570
571
    /**
572
     * @return bool
573
     */
574 1
    public function getStateChanged()
575
    {
576 1
        return $this->stateChanged;
577
    }
578
579
    /**
580
     * Returns the passed resultsPerPage value
581
     * @return int|null
582
     */
583 47
    public function getResultsPerPage()
584
    {
585 47
        $path = $this->prefixWithNamespace('resultsPerPage');
586 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 null|integer.
Loading history...
587
    }
588
589
    /**
590
     * @return int
591
     */
592 34
    public function getContextSystemLanguageUid()
593
    {
594 34
        return $this->contextSystemLanguageUid;
595
    }
596
597
    /**
598
     * @return int
599
     */
600 34
    public function getContextPageUid()
601
    {
602 34
        return $this->contextPageUid;
603
    }
604
605
    /**
606
     * Get contextTypoScriptConfiguration
607
     *
608
     * @return TypoScriptConfiguration
609
     */
610 54
    public function getContextTypoScriptConfiguration()
611
    {
612 54
        return $this->contextTypoScriptConfiguration;
613
    }
614
615
    /**
616
     * Assigns the last known persistedArguments and restores their state.
617
     *
618
     * @return SearchRequest
619
     */
620 92
    public function reset()
621
    {
622 92
        $this->argumentsAccessor = new ArrayAccessor($this->persistedArguments);
623 92
        $this->stateChanged = false;
624 92
        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 40
    public function getCopyForSubRequest($onlyPersistentArguments = true)
634
    {
635 40
        if (!$onlyPersistentArguments) {
636
            // create a new request with all data
637
            $argumentsArray = $this->argumentsAccessor->getData();
638
            return new SearchRequest(
639
                $argumentsArray,
640
                $this->contextPageUid,
641
                $this->contextSystemLanguageUid,
642
                $this->contextTypoScriptConfiguration
643
            );
644
        }
645
646 40
        $arguments = new ArrayAccessor();
647 40
        foreach ($this->persistentArgumentsPaths as $persistentArgumentPath) {
648 40
            if ($this->argumentsAccessor->has($persistentArgumentPath)) {
649 40
                $arguments->set($persistentArgumentPath, $this->argumentsAccessor->get($persistentArgumentPath));
650
            }
651
        }
652
653 40
        return new SearchRequest(
654 40
            $arguments->getData(),
655 40
            $this->contextPageUid,
656 40
            $this->contextSystemLanguageUid,
657 40
            $this->contextTypoScriptConfiguration
658
        );
659
    }
660
661
    /**
662
     * @return string
663
     */
664 26
    public function getArgumentNameSpace()
665
    {
666 26
        return $this->argumentNameSpace;
667
    }
668
669
    /**
670
     * @return array
671
     */
672 50
    public function getAsArray()
673
    {
674 50
        return $this->argumentsAccessor->getData();
675
    }
676
677
    /**
678
     * Returns only the arguments as array.
679
     *
680
     * @return array
681
     */
682 37
    public function getArguments() {
683 37
        return $this->argumentsAccessor->get($this->argumentNameSpace, []);
684
    }
685
}
686