Completed
Push — master ( cefe26...80523f )
by Timo
23:41
created

Query::getQueryParameters()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 15
cts 15
cp 1
rs 8.7972
c 0
b 0
f 0
cc 4
eloc 15
nc 8
nop 0
crap 4
1
<?php
2
namespace ApacheSolrForTypo3\Solr;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2009-2015 Ingo Renner <[email protected]>
8
 *  All rights reserved
9
 *
10
 *  This script is part of the TYPO3 project. The TYPO3 project is
11
 *  free software; you can redistribute it and/or modify
12
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 2 of the License, or
14
 *  (at your option) any later version.
15
 *
16
 *  The GNU General Public License can be found at
17
 *  http://www.gnu.org/copyleft/gpl.html.
18
 *
19
 *  This script is distributed in the hope that it will be useful,
20
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 *  GNU General Public License for more details.
23
 *
24
 *  This copyright notice MUST APPEAR in all copies of the script!
25
 ***************************************************************/
26
27
use ApacheSolrForTypo3\Solr\Domain\Search\Query\Helper\EscapeService;
28
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\BigramPhraseFields;
29
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\Faceting;
30
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\Filters;
31
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\Grouping;
32
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\Highlighting;
33
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\PhraseFields;
34
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\QueryFields;
35
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\ReturnFields;
36
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\TrigramPhraseFields;
37
use ApacheSolrForTypo3\Solr\Domain\Site\SiteHashService;
38
use ApacheSolrForTypo3\Solr\FieldProcessor\PageUidToHierarchy;
39
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
40
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
41
use TYPO3\CMS\Core\Utility\GeneralUtility;
42
43
/**
44
 * A Solr search query
45
 *
46
 * @author Ingo Renner <[email protected]>
47
 * @author Timo Hund <[email protected]>
48
 */
49
class Query
50
{
51
52
    // FIXME extract link building from the query, it's not the query's domain
53
54
    const SORT_ASC = 'ASC';
55
    const SORT_DESC = 'DESC';
56
57
    const OPERATOR_AND = 'AND';
58
    const OPERATOR_OR = 'OR';
59
60
    /**
61
     * Used to identify the queries.
62
     *
63
     * @var int
64
     */
65
    protected static $idCount = 0;
66
67
    /**
68
     * @var int
69
     */
70
    protected $id;
71
72
    /**
73
     * @var TypoScriptConfiguration
74
     */
75
    protected $solrConfiguration;
76
77
    /**
78
     * @var string
79
     */
80
    protected $keywords;
81
82
    /**
83
     * @var string
84
     */
85
    protected $keywordsRaw;
86
87
    /**
88
     * ParameterBuilder for filters.
89
     *
90
     * @var Filters
91
     */
92
    protected $filters = null;
93
94
    /**
95
     * @var string
96
     */
97
    protected $sorting;
98
99
    // TODO check usage of these two variants, especially the check for $rawQueryString in getQueryString()
100
    /**
101
     * @var
102
     */
103
    protected $queryString;
104
105
    /**
106
     * @var array
107
     */
108
    protected $queryParameters = [];
109
110
    /**
111
     * @var int
112
     */
113
    protected $resultsPerPage;
114
115
    /**
116
     * @var int
117
     */
118
    protected $page;
119
120
    /**
121
     * @var int
122
     */
123
    protected $linkTargetPageId;
124
125
    /**
126
     * Holds the query fields with their associated boosts. The key represents
127
     * the field name, value represents the field's boost. These are the fields
128
     * that will actually be searched.
129
     *
130
     * Used in Solr's qf parameter
131
     *
132
     * @var QueryFields
133
     * @see http://wiki.apache.org/solr/DisMaxQParserPlugin#qf_.28Query_Fields.29
134
     */
135
    protected $queryFields = null;
136
137
    /**
138
     * Holds the phrase fields with their associated boosts. The key represents
139
     * the field name, value represents the field's boost. These are the fields
140
     * for those Apache Solr should build phrase quieries and by phrase occurrences should be boosted.
141
     *
142
     * @var PhraseFields
143
     * @see https://lucene.apache.org/solr/guide/7_0/the-dismax-query-parser.html#pf-phrase-fields-parameter
144
     */
145
    protected $phraseFields;
146
147
    /**
148
     * Holds the bigram phrase fields with their associated boosts. The key represents
149
     * the field name, value represents the field's boost. These are the fields
150
     * for those Apache Solr should build the phrases from triplets and sentences.
151
     *
152
     * @var BigramPhraseFields
153
     * @see "pf2" https://lucene.apache.org/solr/guide/7_0/the-extended-dismax-query-parser.html#extended-dismax-parameters
154
     */
155
    protected $bigramPhraseFields;
156
157
    /**
158
     * Holds the trigram phrase fields with their associated boosts. The key represents
159
     * the field name, value represents the field's boost. These are the fields
160
     * for those Apache Solr should build the phrases from triplets and sentences.
161
     *
162
     * @var TrigramPhraseFields
163
     * @see "pf3" https://lucene.apache.org/solr/guide/7_0/the-extended-dismax-query-parser.html#extended-dismax-parameters
164
     */
165
    protected $trigramPhraseFields;
166
167
    /**
168
     * List of fields that will be returned in the result documents.
169
     *
170
     * used in Solr's fl parameter
171
     *
172
     * @var ReturnFields
173
     * @see http://wiki.apache.org/solr/CommonQueryParameters#fl
174
     */
175
    protected $returnFields = null;
176
177
    /**
178
     * ParameterBuilder for the highlighting.
179
     *
180
     * @var Highlighting
181
     */
182
    protected $highlighting = null;
183
184
    /**
185
     * ParameterBuilder for the faceting.
186
     *
187
     * @var Faceting
188
     */
189
    protected $faceting = null;
190
191
    /**
192
     * ParameterBuilder for the grouping.
193
     *
194
     * @var Grouping
195
     */
196
    protected $grouping = null;
197
198
    /**
199
     * @var bool
200
     */
201
    private $rawQueryString = false;
202
203
    /**
204
     * The field by which the result will be collapsed
205
     * @var string
206
     */
207
    protected $variantField = 'variantId';
208
209
    /**
210
     * @var SiteHashService
211
     */
212
    protected $siteHashService = null;
213
214
    /**
215
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
216
     */
217
    protected $logger = null;
218
219
    /**
220
     * @var EscapeService
221
     */
222
    protected $escapeService = null;
223
224
    /**
225
     * Query constructor.
226
     * @param string $keywords
227
     * @param TypoScriptConfiguration $solrConfiguration
228
     * @param SiteHashService|null $siteHashService
229
     * @param EscapeService|null $escapeService
230
     * @param SolrLogManager|null $solrLogManager
231
     */
232 136
    public function __construct($keywords, $solrConfiguration = null, SiteHashService $siteHashService = null, EscapeService $escapeService = null, SolrLogManager $solrLogManager = null)
233
    {
234 136
        $keywords = (string)$keywords;
235
236 136
        $this->logger = is_null($solrLogManager) ? GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__) : $solrLogManager;
237 136
        $this->solrConfiguration = is_null($solrConfiguration) ? Util::getSolrConfiguration() : $solrConfiguration;
238 136
        $this->siteHashService = is_null($siteHashService) ? GeneralUtility::makeInstance(SiteHashService::class) : $siteHashService;
239 136
        $this->escapeService = is_null($escapeService) ? GeneralUtility::makeInstance(EscapeService::class) : $escapeService;
240 136
        $this->setKeywords($keywords);
241 136
        $this->sorting = '';
242
243 136
        $this->linkTargetPageId = $this->solrConfiguration->getSearchTargetPage();
244
245 136
        $this->initializeQuery();
246
247 136
        $this->id = ++self::$idCount;
248 136
    }
249
250
    /**
251
     * @return void
252
     */
253 135
    protected function initializeQuery()
254
    {
255
        // Filters
256 135
        $this->initializeFilters();
257
258
        // What fields to search
259 135
        $queryFields = QueryFields::fromString($this->solrConfiguration->getSearchQueryQueryFields());
260 135
        $this->setQueryFields($queryFields);
261
262
        // What fields to boost by phrase matching
263 135
        $phraseFields = PhraseFields::fromString((string)$this->solrConfiguration->getSearchQueryPhraseFields());
264 135
        $this->setPhraseFields($phraseFields);
265
266
        // For which fields to build bigram phrases and boost by phrase matching
267 135
        $bigramPhraseFields = BigramPhraseFields::fromString((string)$this->solrConfiguration->getSearchQueryBigramPhraseFields());
268 135
        $this->setBigramPhraseFields($bigramPhraseFields);
269
270
        // For which fields to build trigram phrases and boost by phrase matching
271 135
        $trigramPhraseFields = TrigramPhraseFields::fromString((string)$this->solrConfiguration->getSearchQueryTrigramPhraseFields());
272 135
        $this->setTrigramPhraseFields($trigramPhraseFields);
273
274
        // What fields to return from Solr
275 135
        $returnFieldsArray = $this->solrConfiguration->getSearchQueryReturnFieldsAsArray(['*', 'score']);
276 135
        $returnFields = ReturnFields::fromArray($returnFieldsArray);
277 135
        $this->setReturnFields($returnFields);
278
279
        // Configure highlighting
280 135
        $highlighting = Highlighting::fromTypoScriptConfiguration($this->solrConfiguration);
281 135
        $this->setHighlighting($highlighting);
282
283
        // Configure faceting
284 135
        $this->initializeFaceting();
285
286
        // Initialize grouping
287 135
        $this->initializeGrouping();
288
289
        // Configure collapsing
290 135
        $this->initializeCollapsingFromConfiguration();
291 135
    }
292
293
    /**
294
     * @param QueryFields $queryFields
295
     */
296 135
    public function setQueryFields(QueryFields $queryFields)
297
    {
298 135
        $this->queryFields = $queryFields;
299 135
    }
300
301
    /**
302
     * @return QueryFields
303
     */
304 96
    public function getQueryFields()
305
    {
306 96
        return $this->queryFields;
307
    }
308
309
    /**
310
     * @param PhraseFields $phraseFields
311
     * @return void
312
     */
313 135
    public function setPhraseFields(PhraseFields $phraseFields)
314
    {
315 135
        $this->phraseFields = $phraseFields;
316 135
    }
317
318
    /**
319
     * @return PhraseFields
320
     */
321 8
    public function getPhraseFields()
322
    {
323 8
        return $this->phraseFields;
324
    }
325
326
    /**
327
     * @return BigramPhraseFields
328
     */
329 3
    public function getBigramPhraseFields()
330
    {
331 3
        return $this->bigramPhraseFields;
332
    }
333
334
    /**
335
     * @param BigramPhraseFields $bigramPhraseFields
336
     * @return void
337
     */
338 135
    public function setBigramPhraseFields(BigramPhraseFields $bigramPhraseFields)
339
    {
340 135
        $this->bigramPhraseFields = $bigramPhraseFields;
341 135
    }
342
343
    /**
344
     * @return TrigramPhraseFields
345
     */
346 3
    public function getTrigramPhraseFields()
347
    {
348 3
        return $this->trigramPhraseFields;
349
    }
350
351
    /**
352
     * @param TrigramPhraseFields $trigramPhraseFields
353
     * @return void
354
     */
355 135
    public function setTrigramPhraseFields(TrigramPhraseFields $trigramPhraseFields)
356
    {
357 135
        $this->trigramPhraseFields = $trigramPhraseFields;
358 135
    }
359
360
    /**
361
     * magic implementation for clone(), makes sure that the id counter is
362
     * incremented
363
     *
364
     * @return void
365
     */
366
    public function __clone()
367
    {
368
        $this->id = ++self::$idCount;
369
    }
370
371
    /**
372
     * returns a string representation of the query
373
     *
374
     * @return string the string representation of the query
375
     */
376 1
    public function __toString()
377
    {
378 1
        return $this->getQueryString();
379
    }
380
381
    /**
382
     * Builds the query string which is then used for Solr's q parameters
383
     *
384
     * @return string Solr query string
385
     */
386 48
    public function getQueryString()
387
    {
388 48
        if (!$this->rawQueryString) {
389 39
            $this->buildQueryString();
390
        }
391
392 48
        return $this->queryString;
393
    }
394
395
    /**
396
     * Sets the query string without any escaping.
397
     *
398
     * Be cautious with this function!
399
     * TODO remove this method as it basically just sets the q parameter / keywords
400
     *
401
     * @param string $queryString The raw query string.
402
     */
403 10
    public function setQueryString($queryString)
404
    {
405 10
        $this->queryString = $queryString;
406 10
    }
407
408
    /**
409
     * Creates the string that is later used as the q parameter in the solr query
410
     *
411
     * @return void
412
     */
413 39
    protected function buildQueryString()
414
    {
415
        // very simple for now
416 39
        $this->queryString = $this->keywords;
417 39
    }
418
419
    /**
420
     * Sets whether a raw query sting should be used, that is, whether the query
421
     * string should be escaped or not.
422
     *
423
     * @param bool $useRawQueryString TRUE to use raw queries (like Lucene Query Language) or FALSE for regular, escaped queries
424
     */
425 10
    public function useRawQueryString($useRawQueryString)
426
    {
427 10
        $this->rawQueryString = (boolean)$useRawQueryString;
428 10
    }
429
430
    /**
431
     * Returns the query's ID.
432
     *
433
     * @return int The query's ID.
434
     */
435
    public function getId()
436
    {
437
        return $this->id;
438
    }
439
440
    /**
441
     * Gets the currently showing page's number
442
     *
443
     * @return int page number currently showing
444
     */
445 1
    public function getPage()
446
    {
447 1
        return $this->page;
448
    }
449
450
    /**
451
     * Sets the page that should be shown
452
     *
453
     * @param int $page page number to show
454
     * @return void
455
     */
456 2
    public function setPage($page)
457
    {
458 2
        $this->page = max(intval($page), 0);
459 2
    }
460
461
    /**
462
     * Gets the index of the first result document we're showing
463
     *
464
     * @return int index of the currently first document showing
465
     */
466
    public function getStartIndex()
467
    {
468
        return ($this->page - 1) * $this->resultsPerPage;
469
    }
470
471
    /**
472
     * Gets the index of the last result document we're showing
473
     *
474
     * @return int index of the currently last document showing
475
     */
476
    public function getEndIndex()
477
    {
478
        return $this->page * $this->resultsPerPage;
479
    }
480
481
    // query elevation
482
483
    /**
484
     * Activates and deactivates query elevation for the current query.
485
     *
486
     * @param bool $elevation True to enable query elevation (default), FALSE to disable query elevation.
487
     * @param bool $forceElevation Optionally force elevation so that the elevated documents are always on top regardless of sorting, default to TRUE.
488
     * @param bool $markElevatedResults Mark elevated results
489
     * @return void
490
     */
491 35
    public function setQueryElevation($elevation = true, $forceElevation = true, $markElevatedResults = true)
492
    {
493 35
        if ($elevation) {
494 31
            $this->queryParameters['enableElevation'] = 'true';
495 31
            $this->setForceElevation($forceElevation);
496 31
            if ($markElevatedResults) {
497 31
                $this->getReturnFields()->add('isElevated:[elevated]');
498
            }
499
        } else {
500 5
            $this->queryParameters['enableElevation'] = 'false';
501 5
            unset($this->queryParameters['forceElevation']);
502 5
            $this->getReturnFields()->remove('isElevated:[elevated]');
503 5
            $this->getReturnFields()->remove('[elevated]'); // fallback
504
        }
505 35
    }
506
507
    /**
508
     * Enables or disables the forceElevation query parameter.
509
     *
510
     * @param bool $forceElevation
511
     */
512 31
    protected function setForceElevation($forceElevation)
513
    {
514 31
        if ($forceElevation) {
515 30
            $this->queryParameters['forceElevation'] = 'true';
516
        } else {
517 1
            $this->queryParameters['forceElevation'] = 'false';
518
        }
519 31
    }
520
521
    // collapsing
522
523
    /**
524
     * Check whether collapsing is active
525
     *
526
     * @return bool
527
     */
528 3
    public function getIsCollapsing()
529
    {
530 3
        return $this->getFilters()->hasWithName('collapsing');
531
    }
532
533
    /**
534
     * @param string $fieldName
535
     */
536 4
    public function setVariantField($fieldName)
537
    {
538 4
        $this->variantField = $fieldName;
539 4
    }
540
541
    /**
542
     * @return string
543
     */
544 1
    public function getVariantField()
545
    {
546 1
        return $this->variantField;
547
    }
548
549
    /**
550
     * @param bool $collapsing
551
     */
552 6
    public function setCollapsing($collapsing = true)
553
    {
554 6
        if ($collapsing) {
555 6
            $this->getFilters()->add('{!collapse field=' . $this->variantField . '}', 'collapsing');
556 6
            if ($this->solrConfiguration->getSearchVariantsExpand()) {
557 2
                $this->queryParameters['expand'] = 'true';
558 6
                $this->queryParameters['expand.rows'] = $this->solrConfiguration->getSearchVariantsLimit();
559
            }
560
        } else {
561 1
            $this->getFilters()->removeByName('collapsing');
562 1
            unset($this->queryParameters['expand']);
563 1
            unset($this->queryParameters['expand.rows']);
564
        }
565 6
    }
566
567
    // grouping
568
569
    /**
570
     * Activates and deactivates grouping for the current query.
571
     *
572
     * @param Grouping $grouping TRUE to enable grouping, FALSE to disable grouping
573
     * @return void
574
     */
575 135
    public function setGrouping(Grouping $grouping)
576
    {
577 135
        $this->grouping = $grouping;
578 135
    }
579
580
    /**
581
     * @return Grouping
582
     */
583 99
    public function getGrouping()
584
    {
585 99
        return $this->grouping;
586
    }
587
588
    /**
589
     * Returns the number of results that should be shown per page
590
     *
591
     * @return int number of results to show per page
592
     */
593 34
    public function getResultsPerPage()
594
    {
595 34
        if ($this->getGrouping() instanceof Grouping && $this->getGrouping()->getIsEnabled()) {
596 1
            return $this->getGrouping()->getNumberOfGroups();
597
        }
598
599 34
        return $this->resultsPerPage;
600
    }
601
602
    /**
603
     * Sets the number of results that should be shown per page
604
     *
605
     * @param int $resultsPerPage Number of results to show per page
606
     * @return void
607
     */
608 40
    public function setResultsPerPage($resultsPerPage)
609
    {
610 40
        $this->resultsPerPage = max(intval($resultsPerPage), 0);
611 40
    }
612
613
    // faceting
614
615
    /**
616
     * Activates and deactivates faceting for the current query.
617
     *
618
     * @param Faceting $faceting TRUE to enable faceting, FALSE to disable faceting
619
     * @return void
620
     */
621 135
    public function setFaceting(Faceting $faceting)
622
    {
623 135
        $this->faceting = $faceting;
624 135
    }
625
626
    /**
627
     * @return Faceting
628
     */
629 93
    public function getFaceting()
630
    {
631 93
        return $this->faceting;
632
    }
633
634
635
    /**
636
     * Gets all currently applied filters.
637
     *
638
     * @return Filters Array of filters
639
     */
640 108
    public function getFilters()
641
    {
642 108
        return $this->filters;
643
    }
644
645
    /**
646
     * Sets the filters to use.
647
     *
648
     * @param Filters $filters
649
     */
650 136
    public function setFilters(Filters $filters)
651
    {
652 136
        $this->filters = $filters;
653 136
    }
654
655
    // sorting
656
657
    /**
658
     * Sets access restrictions for a frontend user.
659
     *
660
     * @param array $groups Array of groups a user has been assigned to
661
     */
662 39
    public function setUserAccessGroups(array $groups)
663
    {
664 39
        $groups = array_map('intval', $groups);
665 39
        $groups[] = 0; // always grant access to public documents
666 39
        $groups = array_unique($groups);
667 39
        sort($groups, SORT_NUMERIC);
668
669 39
        $accessFilter = '{!typo3access}' . implode(',', $groups);
670 39
        $this->getFilters()->removeByPrefix('{!typo3access}');
671 39
        $this->getFilters()->add($accessFilter);
672 39
    }
673
674
    // query parameters
675
676
    /**
677
     * Limits the query to certain sites
678
     *
679
     * @param string $allowedSites Comma-separated list of domains
680
     */
681 35
    public function setSiteHashFilter($allowedSites)
682
    {
683 35
        if (trim($allowedSites) === '*') {
684 1
            return;
685
        }
686
687 34
        $allowedSites = GeneralUtility::trimExplode(',', $allowedSites);
688 34
        $filters = [];
689
690 34
        foreach ($allowedSites as $site) {
691 34
            $siteHash = $this->siteHashService->getSiteHashForDomain($site);
692 34
            $filters[] = 'siteHash:"' . $siteHash . '"';
693
        }
694
695 34
        $this->getFilters()->add(implode(' OR ', $filters));
696 34
    }
697
698
    /**
699
     * Limits the query to certain page tree branches
700
     *
701
     * @param string $pageIds Comma-separated list of page IDs
702
     */
703
    public function setRootlineFilter($pageIds)
704
    {
705
        $pageIds = GeneralUtility::trimExplode(',', $pageIds);
706
        $filters = [];
707
708
            /** @var $processor PageUidToHierarchy */
709
        $processor = GeneralUtility::makeInstance(PageUidToHierarchy::class);
710
        $hierarchies = $processor->process($pageIds);
711
712
        foreach ($hierarchies as $hierarchy) {
713
            $lastLevel = array_pop($hierarchy);
714
            $filters[] = 'rootline:"' . $lastLevel . '"';
715
        }
716
717
        $this->getFilters()->add(implode(' OR ', $filters));
718
    }
719
720
    /**
721
     * @param ReturnFields $returnFields
722
     */
723 135
    public function setReturnFields(ReturnFields $returnFields)
724
    {
725 135
        $this->returnFields = $returnFields;
726 135
    }
727
728
    /**
729
     * @return ReturnFields
730
     */
731 98
    public function getReturnFields()
732
    {
733 98
        return $this->returnFields;
734
    }
735
736
    /**
737
     * Gets the query type, Solr's qt parameter.
738
     *
739
     * @return string Query type, qt parameter.
740
     */
741 1
    public function getQueryType()
742
    {
743 1
        return $this->queryParameters['qt'];
744
    }
745
746
    /**
747
     * Sets the query type, Solr's qt parameter.
748
     *
749
     * @param string|bool $queryType String query type or boolean FALSE to disable / reset the qt parameter.
750
     * @see http://wiki.apache.org/solr/CoreQueryParameters#qt
751
     */
752 2
    public function setQueryType($queryType)
753
    {
754 2
        $this->setQueryParameterWhenStringOrUnsetWhenEmpty('qt', $queryType);
755 2
    }
756
757
    /**
758
     * Sets the query operator to AND or OR. Unsets the query operator (actually
759
     * sets it back to default) for FALSE.
760
     *
761
     * @param string|bool $operator AND or OR, FALSE to unset
762
     */
763 1
    public function setOperator($operator)
764
    {
765 1
        if (in_array($operator, [self::OPERATOR_AND, self::OPERATOR_OR])) {
766 1
            $this->queryParameters['q.op'] = $operator;
767
        }
768
769 1
        if ($operator === false) {
770 1
            unset($this->queryParameters['q.op']);
771
        }
772 1
    }
773
774
    /**
775
     * Gets the alternative query, Solr's q.alt parameter.
776
     *
777
     * @return string Alternative query, q.alt parameter.
778
     */
779 1
    public function getAlternativeQuery()
780
    {
781 1
        return $this->queryParameters['q.alt'];
782
    }
783
784
    /**
785
     * Sets an alternative query, Solr's q.alt parameter.
786
     *
787
     * This query supports the complete Lucene Query Language.
788
     *
789
     * @param string $alternativeQuery String alternative query or boolean FALSE to disable / reset the q.alt parameter.
790
     * @see http://wiki.apache.org/solr/DisMaxQParserPlugin#q.alt
791
     */
792 35
    public function setAlternativeQuery($alternativeQuery)
793
    {
794 35
        $this->setQueryParameterWhenStringOrUnsetWhenEmpty('q.alt', $alternativeQuery);
795 35
    }
796
797
    // keywords
798
799
    /**
800
     * Set the query to omit the response header
801
     *
802
     * @param bool $omitHeader TRUE (default) to omit response headers, FALSE to re-enable
803
     */
804 1
    public function setOmitHeader($omitHeader = true)
805
    {
806 1
        $omitHeader = ($omitHeader === true) ? 'true' : $omitHeader;
807 1
        $this->setQueryParameterWhenStringOrUnsetWhenEmpty('omitHeader', $omitHeader);
808 1
    }
809
810
    /**
811
     * Get the query keywords, keywords are escaped.
812
     *
813
     * @return string query keywords
814
     */
815 34
    public function getKeywords()
816
    {
817 34
        return $this->keywords;
818
    }
819
820
    /**
821
     * Sets the query keywords, escapes them as needed for Solr/Lucene.
822
     *
823
     * @param string $keywords user search terms/keywords
824
     */
825 136
    public function setKeywords($keywords)
826
    {
827 136
        $this->keywords = $this->escapeService->escape($keywords);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->escapeService->escape($keywords) can also be of type integer or double. However, the property $keywords is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
828 136
        $this->keywordsRaw = $keywords;
829 136
    }
830
831
    /**
832
     * Gets the cleaned keywords so that it can be used in templates f.e.
833
     *
834
     * @return string The cleaned keywords.
835
     */
836 26
    public function getKeywordsCleaned()
837
    {
838 26
        return $this->cleanKeywords($this->keywordsRaw);
839
    }
840
841
    /**
842
     * Helper method to escape/encode keywords for use in HTML
843
     *
844
     * @param string $keywords Keywords to prepare for use in HTML
845
     * @return string Encoded keywords
846
     */
847 26
    public static function cleanKeywords($keywords)
848
    {
849 26
        $keywords = trim($keywords);
850 26
        $keywords = htmlspecialchars($keywords);
851 26
        return $keywords;
852
    }
853
854
    // relevance, matching
855
856
    /**
857
     * Gets the raw, unescaped, unencoded keywords.
858
     *
859
     * USE WITH CAUTION!
860
     *
861
     * @return string raw keywords
862
     */
863
    public function getKeywordsRaw()
864
    {
865
        return $this->keywordsRaw;
866
    }
867
868
    /**
869
     * Sets the minimum match (mm) parameter
870
     *
871
     * @param mixed $minimumMatch Minimum match parameter as string or boolean FALSE to disable / reset the mm parameter
872
     * @see http://wiki.apache.org/solr/DisMaxRequestHandler#mm_.28Minimum_.27Should.27_Match.29
873
     */
874 1
    public function setMinimumMatch($minimumMatch)
875
    {
876 1
        $this->setQueryParameterWhenStringOrUnsetWhenEmpty('mm', $minimumMatch);
877 1
    }
878
879
    /**
880
     * Sets the boost function (bf) parameter
881
     *
882
     * @param mixed $boostFunction boost function parameter as string or boolean FALSE to disable / reset the bf parameter
883
     * @see http://wiki.apache.org/solr/DisMaxRequestHandler#bf_.28Boost_Functions.29
884
     */
885 1
    public function setBoostFunction($boostFunction)
886
    {
887 1
        $this->setQueryParameterWhenStringOrUnsetWhenEmpty('bf', $boostFunction);
888 1
    }
889
890
    // query fields
891
    // TODO move up to field list methods
892
893
    /**
894
     * Sets the boost query (bq) parameter
895
     *
896
     * @param mixed $boostQuery boost query parameter as string or array to set a boost query or boolean FALSE to disable / reset the bq parameter
897
     * @see http://wiki.apache.org/solr/DisMaxQParserPlugin#bq_.28Boost_Query.29
898
     */
899 1
    public function setBoostQuery($boostQuery)
900
    {
901 1
        if (is_array($boostQuery)) {
902
            $this->queryParameters['bq'] = $boostQuery;
903
            return;
904
        }
905 1
        $this->setQueryParameterWhenStringOrUnsetWhenEmpty('bq', $boostQuery);
906 1
    }
907
908
    /**
909
     * Set the tie breaker (tie) parameter
910
     *
911
     * @param mixed $tieParameter tie breaker parameter as string or boolean FALSE to disable / reset the tie parameter
912
     * @return void
913
     */
914
    public function setTieParameter($tieParameter)
915
    {
916
        $this->setQueryParameterWhenStringOrUnsetWhenEmpty('tie', $tieParameter);
917
    }
918
919
    /**
920
     * Set the phrase slop (ps) parameter
921
     *
922
     * @param int $phraseSlop Phrase Slop parameter as int or null to unset this parameter
923
     * @return void
924
     */
925 1
    public function setPhraseSlopParameter(int $phraseSlop = null)
926
    {
927 1
        $this->setQueryParameterWhenIntOrUnsetWhenNull('ps', $phraseSlop);
928 1
    }
929
930
    /**
931
     * Set the Query Phrase Slop (qs) parameter
932
     *
933
     * @param int $queryPhraseSlop Query Phrase Slop parameter as int or null to unset this parameter
934
     * @return void
935
     */
936 1
    public function setQueryPhraseSlopParameter(int $queryPhraseSlop = null)
937
    {
938 1
        $this->setQueryParameterWhenIntOrUnsetWhenNull('qs', $queryPhraseSlop);
939 1
    }
940
941
    /**
942
     * Set the bigram phrase slop (ps2) parameter
943
     *
944
     * @param int $bigramPhraseSlop Bigram Phrase Slop parameter as int or null to unset this parameter
945
     * @return void
946
     */
947 1
    public function setBigramPhraseSlopParameter(int $bigramPhraseSlop = null)
948
    {
949 1
        $this->setQueryParameterWhenIntOrUnsetWhenNull('ps2', $bigramPhraseSlop);
950 1
    }
951
952
    /**
953
     * Set the trigram phrase slop (ps3) parameter
954
     *
955
     * @param int $trigramPhraseSlop Trigram Phrase Slop parameter as int or null to unset this parameter
956
     * @return void
957
     */
958 1
    public function setTrigramPhraseSlopParameter(int $trigramPhraseSlop = null)
959
    {
960 1
        $this->setQueryParameterWhenIntOrUnsetWhenNull('ps3', $trigramPhraseSlop);
961 1
    }
962
963
964
    /**
965
     * Gets a specific query parameter by its name.
966
     *
967
     * @param string $parameterName The parameter to return
968
     * @param mixed $defaultIfEmpty
969
     * @return mixed The parameter's value or $defaultIfEmpty if not set
970
     */
971 7
    public function getQueryParameter($parameterName, $defaultIfEmpty = null)
972
    {
973 7
        $parameters = $this->getQueryParameters();
974 7
        return isset($parameters[$parameterName]) ? $parameters[$parameterName] : $defaultIfEmpty;
975
    }
976
977
    /**
978
     * Builds an array of query parameters to use for the search query.
979
     *
980
     * @return array An array ready to use with query parameters
981
     */
982 93
    public function getQueryParameters()
983
    {
984 93
        $queryParameters = $this->getReturnFields()->build();
985 93
        $queryParameters = array_merge($queryParameters, $this->getFilters()->build());
986 93
        $queryParameters = array_merge($queryParameters, $this->queryParameters);
987 93
        $queryParameters = array_merge($queryParameters, $this->getQueryFields()->build());
988
989 93
        if ($this->solrConfiguration->getPhraseSearchIsEnabled()) {
990 5
            $queryParameters = array_merge($queryParameters, $this->getPhraseFields()->build());
991
        }
992 93
        if ($this->solrConfiguration->getBigramPhraseSearchIsEnabled()) {
993 2
            $queryParameters = array_merge($queryParameters, $this->getBigramPhraseFields()->build());
994
        }
995 93
        if ($this->solrConfiguration->getTrigramPhraseSearchIsEnabled()) {
996 2
            $queryParameters = array_merge($queryParameters, $this->getTrigramPhraseFields()->build());
997
        }
998
999 93
        $queryParameters = array_merge($queryParameters, $this->getHighlighting()->build());
1000 93
        $queryParameters = array_merge($queryParameters, $this->getFaceting()->build());
1001 93
        $queryParameters = array_merge($queryParameters, $this->getGrouping()->build());
1002
1003 93
        return $queryParameters;
1004
    }
1005
1006
    // general query parameters
1007
1008
    /**
1009
     * Enables or disables highlighting of search terms in result teasers.
1010
     *
1011
     * @param Highlighting $highlighting
1012
     * @see http://wiki.apache.org/solr/HighlightingParameters
1013
     * @return void
1014
     */
1015 135
    public function setHighlighting(Highlighting $highlighting)
1016
    {
1017 135
        $this->highlighting = $highlighting;
1018 135
    }
1019
1020
    /**
1021
     * @return Highlighting
1022
     */
1023 93
    public function getHighlighting()
1024
    {
1025 93
        return $this->highlighting;
1026
    }
1027
1028
    // misc
1029
1030
    /**
1031
     * Enables or disables spellchecking for the query.
1032
     *
1033
     * @param bool $spellchecking Enables spellchecking when set to TRUE, deactivates spellchecking when set to FALSE, defaults to TRUE.
1034
     */
1035 31
    public function setSpellchecking($spellchecking = true)
1036
    {
1037 31
        if ($spellchecking) {
1038 31
            $this->queryParameters['spellcheck'] = 'true';
1039 31
            $this->queryParameters['spellcheck.collate'] = 'true';
1040 31
            $maxCollationTries = $this->solrConfiguration->getSearchSpellcheckingNumberOfSuggestionsToTry();
1041 31
            $this->addQueryParameter('spellcheck.maxCollationTries', $maxCollationTries);
1042
        } else {
1043 1
            unset($this->queryParameters['spellcheck']);
1044 1
            unset($this->queryParameters['spellcheck.collate']);
1045 1
            unset($this->queryParameters['spellcheck.maxCollationTries']);
1046
        }
1047 31
    }
1048
1049
    /**
1050
     * This method can be used to set a query parameter when the value is a string and not empty or unset it
1051
     * in any other case. Extracted to avoid duplicate code.
1052
     *
1053
     * @param string $parameterName
1054
     * @param mixed $value
1055
     */
1056 41
    private function setQueryParameterWhenStringOrUnsetWhenEmpty($parameterName, $value)
1057
    {
1058 41
        if (is_string($value) && !empty($value)) {
1059 41
            $this->addQueryParameter($parameterName, $value);
1060
        } else {
1061 6
            unset($this->queryParameters[$parameterName]);
1062
        }
1063 41
    }
1064
1065
    /**
1066
     * This method can be used to set a query parameter when the value is a int and not empty or unset it
1067
     * in any other case. Extracted to avoid duplicate code.
1068
     *
1069
     * @param string $parameterName
1070
     * @param int $value
1071
     */
1072 4
    private function setQueryParameterWhenIntOrUnsetWhenNull(string $parameterName, int $value = null)
1073
    {
1074 4
        if (null === $value) {
1075
            unset($this->queryParameters[$parameterName]);
1076
            return;
1077
        }
1078 4
        $this->addQueryParameter($parameterName, $value);
1079 4
    }
1080
1081
    /**
1082
     * Adds any generic query parameter.
1083
     *
1084
     * @param string $parameterName Query parameter name
1085
     * @param string $parameterValue Parameter value
1086
     */
1087 47
    public function addQueryParameter($parameterName, $parameterValue)
1088
    {
1089 47
        $this->queryParameters[$parameterName] = $parameterValue;
1090 47
    }
1091
1092
    /**
1093
     * Sets the sort parameter.
1094
     *
1095
     * $sorting must include a field name (or the pseudo-field score),
1096
     * followed by a space,
1097
     * followed by a sort direction (asc or desc).
1098
     *
1099
     * Multiple fallback sortings can be separated by comma,
1100
     * ie: <field name> <direction>[,<field name> <direction>]...
1101
     *
1102
     * @param string|bool $sorting Either a comma-separated list of sort fields and directions or FALSE to reset sorting to the default behavior (sort by score / relevance)
1103
     * @see http://wiki.apache.org/solr/CommonQueryParameters#sort
1104
     */
1105 2
    public function setSorting($sorting)
1106
    {
1107 2
        if ($sorting) {
1108 2
            if (!is_string($sorting)) {
1109
                throw new \InvalidArgumentException('Sorting needs to be a string!');
1110
            }
1111 2
            $sortParameter = $this->removeRelevanceSortField($sorting);
1112 2
            $this->queryParameters['sort'] = $sortParameter;
1113
        } else {
1114 1
            unset($this->queryParameters['sort']);
1115
        }
1116 2
    }
1117
1118
    /**
1119
     * Removes the relevance sort field if present in the sorting field definition.
1120
     *
1121
     * @param string $sorting
1122
     * @return string
1123
     */
1124 2
    protected function removeRelevanceSortField($sorting)
1125
    {
1126 2
        $sortParameter = $sorting;
1127 2
        list($sortField) = explode(' ', $sorting);
1128 2
        if ($sortField === 'relevance') {
1129 1
            $sortParameter = '';
1130 1
            return $sortParameter;
1131
        }
1132
1133 2
        return $sortParameter;
1134
    }
1135
1136
    /**
1137
     * Enables or disables the debug parameter for the query.
1138
     *
1139
     * @param bool $debugMode Enables debugging when set to TRUE, deactivates debugging when set to FALSE, defaults to TRUE.
1140
     */
1141 30
    public function setDebugMode($debugMode = true)
1142
    {
1143 30
        if ($debugMode) {
1144 30
            $this->queryParameters['debugQuery'] = 'true';
1145 30
            $this->queryParameters['echoParams'] = 'all';
1146
        } else {
1147 1
            unset($this->queryParameters['debugQuery']);
1148 1
            unset($this->queryParameters['echoParams']);
1149
        }
1150 30
    }
1151
1152
    /**
1153
     * Returns the link target page id.
1154
     *
1155
     * @return int
1156
     */
1157 2
    public function getLinkTargetPageId()
1158
    {
1159 2
        return $this->linkTargetPageId;
1160
    }
1161
1162
    /**
1163
     * Activates the collapsing on the configured field, if collapsing was enabled.
1164
     *
1165
     * @return bool
1166
     */
1167 135
    protected function initializeCollapsingFromConfiguration()
1168
    {
1169
        // check collapsing
1170 135
        if ($this->solrConfiguration->getSearchVariants()) {
1171 4
            $collapseField = $this->solrConfiguration->getSearchVariantsField();
1172 4
            $this->setVariantField($collapseField);
1173 4
            $this->setCollapsing(true);
1174
1175 4
            return true;
1176
        }
1177
1178 131
        return false;
1179
    }
1180
1181
    /**
1182
     * @return void
1183
     */
1184 135
    protected function initializeFaceting()
1185
    {
1186 135
        $faceting = Faceting::fromTypoScriptConfiguration($this->solrConfiguration);
1187 135
        $this->setFaceting($faceting);
1188 135
    }
1189
1190
    /**
1191
     * @return void
1192
     */
1193 135
    protected function initializeGrouping()
1194
    {
1195 135
        $grouping = Grouping::fromTypoScriptConfiguration($this->solrConfiguration);
1196 135
        $this->setGrouping($grouping);
1197 135
    }
1198
1199
    /**
1200
     * @return void
1201
     */
1202 136
    protected function initializeFilters()
1203
    {
1204 136
        $filters = Filters::fromTypoScriptConfiguration($this->solrConfiguration);
1205 136
        $this->setFilters($filters);
1206 136
    }
1207
}
1208