Passed
Pull Request — master (#2705)
by
unknown
39:37 queued 36:20
created

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

564
        if (trim(/** @scrutinizer ignore-type */ $query) === '') {
Loading history...
565
            return true;
566
        }
567
568 38
        return false;
569
    }
570
571
    /**
572
     * This method returns true when no querystring is present at all.
573
     * Which means no search by the user was triggered
574
     *
575
     * @return bool
576
     */
577 43
    public function getRawUserQueryIsNull()
578
    {
579 43
        $path = $this->prefixWithNamespace('q');
580 43
        $query = $this->argumentsAccessor->get($path, null);
581 43
        return $query === null;
582
    }
583
584
    /**
585
     * Sets the results per page that are used during search.
586
     *
587
     * @param int $resultsPerPage
588
     * @return SearchRequest
589
     */
590 43
    public function setResultsPerPage($resultsPerPage)
591
    {
592 43
        $path = $this->prefixWithNamespace('resultsPerPage');
593 43
        $this->argumentsAccessor->set($path, $resultsPerPage);
594 43
        $this->stateChanged = true;
595
596 43
        return $this;
597
    }
598
599
    /**
600
     * @return bool
601
     */
602
    public function getStateChanged()
603
    {
604
        return $this->stateChanged;
605
    }
606
607
    /**
608
     * Returns the passed resultsPerPage value
609
     * @return int|null
610
     */
611 43
    public function getResultsPerPage()
612
    {
613 43
        $path = $this->prefixWithNamespace('resultsPerPage');
614 43
        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...
615
    }
616
617
    /**
618
     * Allows to set additional filters that are used on time and not transported during the request.
619
     *
620
     * @param array $additionalFilters
621
     * @return SearchRequest
622
     */
623 2
    public function setAdditionalFilters($additionalFilters)
624
    {
625 2
        $path = $this->prefixWithNamespace('additionalFilters');
626 2
        $this->argumentsAccessor->set($path, $additionalFilters);
627 2
        $this->stateChanged = true;
628
629 2
        return $this;
630
    }
631
632
    /**
633
     * Retrieves the addtional filters that have been set
634
     *
635
     * @return array
636
     */
637 42
    public function getAdditionalFilters()
638
    {
639 42
        $path = $this->prefixWithNamespace('additionalFilters');
640 42
        return $this->argumentsAccessor->get($path, []);
641
    }
642
643
    /**
644
     * @return int
645
     */
646 34
    public function getContextSystemLanguageUid()
647
    {
648 34
        return $this->contextSystemLanguageUid;
649
    }
650
651
    /**
652
     * @return int
653
     */
654 34
    public function getContextPageUid()
655
    {
656 34
        return $this->contextPageUid;
657
    }
658
659
    /**
660
     * Get contextTypoScriptConfiguration
661
     *
662
     * @return TypoScriptConfiguration
663
     */
664 44
    public function getContextTypoScriptConfiguration()
665
    {
666 44
        return $this->contextTypoScriptConfiguration;
667
    }
668
669
    /**
670
     * Assigns the last known persistedArguments and restores their state.
671
     *
672
     * @return SearchRequest
673
     */
674 44
    public function reset()
675
    {
676 44
        $this->argumentsAccessor = new ArrayAccessor($this->persistedArguments);
677 44
        $this->stateChanged = false;
678 44
        $this->activeFacetDataBag = new UrlFacetDataBag(
679 44
            $this->argumentsAccessor,
680 44
            $this->argumentNameSpace ?? 'tx_solr',
681 44
            $this->contextTypoScriptConfiguration instanceof TypoScriptConfiguration ?
0 ignored issues
show
introduced by
$this->contextTypoScriptConfiguration is always a sub-type of ApacheSolrForTypo3\Solr\...TypoScriptConfiguration.
Loading history...
682 44
            $this->contextTypoScriptConfiguration->getSearchFacetingUrlParameterStyle(
683 44
                UrlFacetDataBag::PARAMETER_STYLE_INDEX
684 44
            ) : UrlFacetDataBag::PARAMETER_STYLE_INDEX
685
        );
686
687
        // If the default of sorting parameter should be true, a modification of this condition is needed.
688
        // If instance of contextTypoScriptConfiguration is not TypoScriptConfiguration the sort should be enabled too
689 44
        if ($this->contextTypoScriptConfiguration instanceof TypoScriptConfiguration &&
690 44
                $this->contextTypoScriptConfiguration->getSearchFacetingUrlParameterSort(false)) {
691 1
            $this->activeFacetDataBag->enableSort();
692
        }
693
694 44
        return $this;
695
    }
696
697
    /**
698
     * This can be used to start a new sub request, e.g. for a faceted search.
699
     *
700
     * @param bool $onlyPersistentArguments
701
     * @return SearchRequest
702
     */
703 35
    public function getCopyForSubRequest($onlyPersistentArguments = true)
704
    {
705 35
        if (!$onlyPersistentArguments) {
706
            // create a new request with all data
707
            $argumentsArray = $this->argumentsAccessor->getData();
708
            return new SearchRequest(
709
                $argumentsArray,
710
                $this->contextPageUid,
711
                $this->contextSystemLanguageUid,
712
                $this->contextTypoScriptConfiguration
713
            );
714
        }
715
716 35
        $arguments = new ArrayAccessor();
717 35
        foreach ($this->persistentArgumentsPaths as $persistentArgumentPath) {
718 35
            if ($this->argumentsAccessor->has($persistentArgumentPath)) {
719 31
                $arguments->set($persistentArgumentPath, $this->argumentsAccessor->get($persistentArgumentPath));
720
            }
721
        }
722
723 35
        return new SearchRequest(
724 35
            $arguments->getData(),
725 35
            $this->contextPageUid,
726 35
            $this->contextSystemLanguageUid,
727 35
            $this->contextTypoScriptConfiguration
728
        );
729
    }
730
731
    /**
732
     * @return string
733
     */
734 28
    public function getArgumentNameSpace()
735
    {
736 28
        return $this->argumentNameSpace;
737
    }
738
739
    /**
740
     * @return array
741
     */
742 35
    public function getAsArray()
743
    {
744 35
        return $this->argumentsAccessor->getData();
745
    }
746
747
    /**
748
     * Returns only the arguments as array.
749
     *
750
     * @return array
751
     */
752 36
    public function getArguments() {
753 36
        return $this->argumentsAccessor->get($this->argumentNameSpace, []);
754
    }
755
}
756