Passed
Push — master ( 613566...1c0463 )
by Timo
05:04
created

Query::getTrigramPhraseFields()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 1
cts 1
cp 1
cc 1
eloc 2
nc 1
nop 0
crap 1
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 121
     * @var bool
200
     */
201 121
    private $rawQueryString = false;
202
203 121
    /**
204 121
     * The field by which the result will be collapsed
205 121
     * @var string
206 121
     */
207 121
    protected $variantField = 'variantId';
208 121
209
    /**
210 121
     * @var SiteHashService
211
     */
212 121
    protected $siteHashService = null;
213
214 121
    /**
215 121
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
216
     */
217
    protected $logger = null;
218
219
    /**
220 120
     * @var EscapeService
221
     */
222
    protected $escapeService = null;
223 120
224
    /**
225
     * Query constructor.
226 120
     * @param string $keywords
227 120
     * @param TypoScriptConfiguration $solrConfiguration
228
     * @param SiteHashService|null $siteHashService
229
     * @param EscapeService|null $escapeService
230 120
     * @param SolrLogManager|null $solrLogManager
231 120
     */
232 120
    public function __construct($keywords, $solrConfiguration = null, SiteHashService $siteHashService = null, EscapeService $escapeService = null, SolrLogManager $solrLogManager = null)
233
    {
234
        $keywords = (string)$keywords;
235 120
236 120
        $this->logger = is_null($solrLogManager) ? GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__) : $solrLogManager;
237
        $this->solrConfiguration = is_null($solrConfiguration) ? Util::getSolrConfiguration() : $solrConfiguration;
238
        $this->siteHashService = is_null($siteHashService) ? GeneralUtility::makeInstance(SiteHashService::class) : $siteHashService;
239 120
        $this->escapeService = is_null($escapeService) ? GeneralUtility::makeInstance(EscapeService::class) : $escapeService;
240
        $this->setKeywords($keywords);
241
        $this->sorting = '';
242 120
243
        $this->linkTargetPageId = $this->solrConfiguration->getSearchTargetPage();
244
245 120
        $this->initializeQuery();
246 120
247
        $this->id = ++self::$idCount;
248
    }
249
250
    /**
251 120
     * @return void
252
     */
253 120
    protected function initializeQuery()
254 120
    {
255
        // Filters
256
        $this->initializeFilters();
257
258
        // What fields to search
259 83
        $queryFields = QueryFields::fromString($this->solrConfiguration->getSearchQueryQueryFields());
260
        $this->setQueryFields($queryFields);
261 83
262
        // What fields to boost by phrase matching
263
        $phraseFields = PhraseFields::fromString((string)$this->solrConfiguration->getSearchQueryPhraseFields());
264
        $this->setPhraseFields($phraseFields);
265
266
        // For which fields to build bigram phrases and boost by phrase matching
267
        $bigramPhraseFields = BigramPhraseFields::fromString((string)$this->solrConfiguration->getSearchQueryBigramPhraseFields());
268
        $this->setBigramPhraseFields($bigramPhraseFields);
269
270
        // For which fields to build trigram phrases and boost by phrase matching
271
        $trigramPhraseFields = TrigramPhraseFields::fromString((string)$this->solrConfiguration->getSearchQueryTrigramPhraseFields());
272
        $this->setTrigramPhraseFields($trigramPhraseFields);
273
274
        // What fields to return from Solr
275
        $returnFieldsArray = $this->solrConfiguration->getSearchQueryReturnFieldsAsArray(['*', 'score']);
276
        $returnFields = ReturnFields::fromArray($returnFieldsArray);
277
        $this->setReturnFields($returnFields);
278
279
        // Configure highlighting
280 1
        $highlighting = Highlighting::fromTypoScriptConfiguration($this->solrConfiguration);
281
        $this->setHighlighting($highlighting);
282 1
283
        // Configure faceting
284
        $this->initializeFaceting();
285
286
        // Initialize grouping
287
        $this->initializeGrouping();
288
289
        // Configure collapsing
290 41
        $this->initializeCollapsingFromConfiguration();
291
    }
292 41
293 38
    /**
294
     * @param QueryFields $queryFields
295
     */
296 41
    public function setQueryFields(QueryFields $queryFields)
297
    {
298
        $this->queryFields = $queryFields;
299
    }
300
301
    /**
302
     * @return QueryFields
303
     */
304
    public function getQueryFields()
305
    {
306
        return $this->queryFields;
307 4
    }
308
309 4
    /**
310 4
     * @param PhraseFields $phraseFields
311
     * @return void
312
     */
313
    public function setPhraseFields(PhraseFields $phraseFields)
314
    {
315
        $this->phraseFields = $phraseFields;
316
    }
317 38
318
    /**
319
     * @return PhraseFields
320 38
     */
321 38
    public function getPhraseFields()
322
    {
323
        return $this->phraseFields;
324
    }
325
326
    /**
327
     * @return BigramPhraseFields
328
     */
329 4
    public function getBigramPhraseFields()
330
    {
331 4
        return $this->bigramPhraseFields;
332 4
    }
333
334
    /**
335
     * @param BigramPhraseFields $bigramPhraseFields
336
     * @return void
337
     */
338
    public function setBigramPhraseFields(BigramPhraseFields $bigramPhraseFields)
339
    {
340
        $this->bigramPhraseFields = $bigramPhraseFields;
341
    }
342
343
    /**
344
     * @return TrigramPhraseFields
345
     */
346
    public function getTrigramPhraseFields()
347
    {
348
        return $this->trigramPhraseFields;
349 1
    }
350
351 1
    /**
352
     * @param TrigramPhraseFields $trigramPhraseFields
353
     * @return void
354
     */
355
    public function setTrigramPhraseFields(TrigramPhraseFields $trigramPhraseFields)
356
    {
357
        $this->trigramPhraseFields = $trigramPhraseFields;
358
    }
359
360 2
    /**
361
     * magic implementation for clone(), makes sure that the id counter is
362 2
     * incremented
363 2
     *
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
    public function __toString()
377
    {
378
        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
    public function getQueryString()
387
    {
388
        if (!$this->rawQueryString) {
389
            $this->buildQueryString();
390
        }
391
392
        return $this->queryString;
393
    }
394
395 34
    /**
396
     * Sets the query string without any escaping.
397 34
     *
398 30
     * Be cautious with this function!
399 30
     * TODO remove this method as it basically just sets the q parameter / keywords
400 30
     *
401 30
     * @param string $queryString The raw query string.
402
     */
403
    public function setQueryString($queryString)
404 5
    {
405 5
        $this->queryString = $queryString;
406 5
    }
407 5
408
    /**
409 34
     * Creates the string that is later used as the q parameter in the solr query
410
     *
411
     * @return void
412
     */
413
    protected function buildQueryString()
414
    {
415
        // very simple for now
416 30
        $this->queryString = $this->keywords;
417
    }
418 30
419 29
    /**
420
     * Sets whether a raw query sting should be used, that is, whether the query
421 1
     * string should be escaped or not.
422
     *
423 30
     * @param bool $useRawQueryString TRUE to use raw queries (like Lucene Query Language) or FALSE for regular, escaped queries
424
     */
425
    public function useRawQueryString($useRawQueryString)
426
    {
427
        $this->rawQueryString = (boolean)$useRawQueryString;
428
    }
429
430
    /**
431
     * Returns the query's ID.
432 3
     *
433
     * @return int The query's ID.
434 3
     */
435
    public function getId()
436
    {
437
        return $this->id;
438
    }
439
440 4
    /**
441
     * Gets the currently showing page's number
442 4
     *
443 4
     * @return int page number currently showing
444
     */
445
    public function getPage()
446
    {
447
        return $this->page;
448 1
    }
449
450 1
    /**
451
     * Sets the page that should be shown
452
     *
453
     * @param int $page page number to show
454
     * @return void
455
     */
456 6
    public function setPage($page)
457
    {
458 6
        $this->page = max(intval($page), 0);
459 6
    }
460 6
461 2
    /**
462 6
     * Gets the index of the first result document we're showing
463
     *
464
     * @return int index of the currently first document showing
465 1
     */
466 1
    public function getStartIndex()
467 1
    {
468
        return ($this->page - 1) * $this->resultsPerPage;
469 6
    }
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 120
    }
480
481 120
    // query elevation
482 120
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 86
     * @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 86
     * @return void
490
     */
491
    public function setQueryElevation($elevation = true, $forceElevation = true, $markElevatedResults = true)
492
    {
493
        if ($elevation) {
494
            $this->queryParameters['enableElevation'] = 'true';
495
            $this->setForceElevation($forceElevation);
496
            if ($markElevatedResults) {
497 33
                $this->getReturnFields()->add('isElevated:[elevated]');
498
            }
499 33
        } else {
500 1
            $this->queryParameters['enableElevation'] = 'false';
501
            unset($this->queryParameters['forceElevation']);
502
            $this->getReturnFields()->remove('isElevated:[elevated]');
503 33
            $this->getReturnFields()->remove('[elevated]'); // fallback
504
        }
505
    }
506
507
    /**
508
     * Enables or disables the forceElevation query parameter.
509
     *
510
     * @param bool $forceElevation
511
     */
512 39
    protected function setForceElevation($forceElevation)
513
    {
514 39
        if ($forceElevation) {
515 39
            $this->queryParameters['forceElevation'] = 'true';
516
        } else {
517
            $this->queryParameters['forceElevation'] = 'false';
518
        }
519
    }
520
521
    // collapsing
522
523
    /**
524
     * Check whether collapsing is active
525 120
     *
526
     * @return bool
527 120
     */
528 120
    public function getIsCollapsing()
529
    {
530
        return $this->getFilters()->hasWithName('collapsing');
531
    }
532
533 80
    /**
534
     * @param string $fieldName
535 80
     */
536
    public function setVariantField($fieldName)
537
    {
538
        $this->variantField = $fieldName;
539
    }
540
541
    /**
542
     * @return string
543
     */
544 95
    public function getVariantField()
545
    {
546 95
        return $this->variantField;
547
    }
548
549
    /**
550
     * @param bool $collapsing
551
     */
552
    public function setCollapsing($collapsing = true)
553
    {
554 121
        if ($collapsing) {
555
            $this->getFilters()->add('{!collapse field=' . $this->variantField . '}', 'collapsing');
556 121
            if ($this->solrConfiguration->getSearchVariantsExpand()) {
557 121
                $this->queryParameters['expand'] = 'true';
558
                $this->queryParameters['expand.rows'] = $this->solrConfiguration->getSearchVariantsLimit();
559
            }
560
        } else {
561
            $this->getFilters()->removeByName('collapsing');
562
            unset($this->queryParameters['expand']);
563
            unset($this->queryParameters['expand.rows']);
564
        }
565
    }
566 38
567
    // grouping
568 38
569 38
    /**
570 38
     * Activates and deactivates grouping for the current query.
571 38
     *
572
     * @param Grouping $grouping TRUE to enable grouping, FALSE to disable grouping
573 38
     * @return void
574 38
     */
575 38
    public function setGrouping(Grouping $grouping)
576 38
    {
577
        $this->grouping = $grouping;
578
    }
579
580
    /**
581
     * @return Grouping
582
     */
583
    public function getGrouping()
584
    {
585 34
        return $this->grouping;
586
    }
587 34
588 1
    /**
589
     * Returns the number of results that should be shown per page
590
     *
591 33
     * @return int number of results to show per page
592 33
     */
593
    public function getResultsPerPage()
594 33
    {
595 33
        if ($this->getGrouping() instanceof Grouping && $this->getGrouping()->getIsEnabled()) {
596 33
            return $this->getGrouping()->getNumberOfGroups();
597
        }
598
599 33
        return $this->resultsPerPage;
600 33
    }
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
    public function setResultsPerPage($resultsPerPage)
609
    {
610
        $this->resultsPerPage = max(intval($resultsPerPage), 0);
611
    }
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
    public function setFaceting(Faceting $faceting)
622
    {
623
        $this->faceting = $faceting;
624
    }
625
626
    /**
627 120
     * @return Faceting
628
     */
629 120
    public function getFaceting()
630 120
    {
631
        return $this->faceting;
632
    }
633
634
635 85
    /**
636
     * Gets all currently applied filters.
637 85
     *
638
     * @return Filters Array of filters
639
     */
640
    public function getFilters()
641
    {
642
        return $this->filters;
643
    }
644
645 1
    /**
646
     * Sets the filters to use.
647 1
     *
648
     * @param Filters $filters
649
     */
650
    public function setFilters(Filters $filters)
651
    {
652
        $this->filters = $filters;
653
    }
654
655
    // sorting
656 2
657
    /**
658 2
     * Sets access restrictions for a frontend user.
659 2
     *
660
     * @param array $groups Array of groups a user has been assigned to
661
     */
662
    public function setUserAccessGroups(array $groups)
663
    {
664
        $groups = array_map('intval', $groups);
665
        $groups[] = 0; // always grant access to public documents
666
        $groups = array_unique($groups);
667 1
        sort($groups, SORT_NUMERIC);
668
669 1
        $accessFilter = '{!typo3access}' . implode(',', $groups);
670 1
        $this->getFilters()->removeByPrefix('{!typo3access}');
671
        $this->getFilters()->add($accessFilter);
672
    }
673 1
674 1
    // query parameters
675
676 1
    /**
677
     * Limits the query to certain sites
678
     *
679
     * @param string $allowedSites Comma-separated list of domains
680
     */
681
    public function setSiteHashFilter($allowedSites)
682
    {
683 1
        if (trim($allowedSites) === '*') {
684
            return;
685 1
        }
686
687
        $allowedSites = GeneralUtility::trimExplode(',', $allowedSites);
688
        $filters = [];
689
690
        foreach ($allowedSites as $site) {
691
            $siteHash = $this->siteHashService->getSiteHashForDomain($site);
692
            $filters[] = 'siteHash:"' . $siteHash . '"';
693
        }
694
695
        $this->getFilters()->add(implode(' OR ', $filters));
696 34
    }
697
698 34
    /**
699 34
     * 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 1
            /** @var $processor PageUidToHierarchy */
709
        $processor = GeneralUtility::makeInstance(PageUidToHierarchy::class);
710 1
        $hierarchies = $processor->process($pageIds);
711 1
712 1
        foreach ($hierarchies as $hierarchy) {
713
            $lastLevel = array_pop($hierarchy);
714
            $filters[] = 'rootline:"' . $lastLevel . '"';
715
        }
716
717
        $this->getFilters()->add(implode(' OR ', $filters));
718
    }
719 33
720
    /**
721 33
     * @param ReturnFields $returnFields
722
     */
723
    public function setReturnFields(ReturnFields $returnFields)
724
    {
725
        $this->returnFields = $returnFields;
726
    }
727
728
    /**
729 121
     * @return ReturnFields
730
     */
731 121
    public function getReturnFields()
732 121
    {
733 121
        return $this->returnFields;
734
    }
735
736
    /**
737
     * Gets the query type, Solr's qt parameter.
738
     *
739
     * @return string Query type, qt parameter.
740 26
     */
741
    public function getQueryType()
742 26
    {
743
        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 26
     */
752
    public function setQueryType($queryType)
753 26
    {
754 26
        $this->setQueryParameterWhenStringOrUnsetWhenEmpty('qt', $queryType);
755 26
    }
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
    public function setOperator($operator)
764
    {
765
        if (in_array($operator, [self::OPERATOR_AND, self::OPERATOR_OR])) {
766
            $this->queryParameters['q.op'] = $operator;
767
        }
768
769
        if ($operator === false) {
770
            unset($this->queryParameters['q.op']);
771
        }
772
    }
773
774
    /**
775
     * Gets the alternative query, Solr's q.alt parameter.
776
     *
777
     * @return string Alternative query, q.alt parameter.
778 1
     */
779
    public function getAlternativeQuery()
780 1
    {
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 1
     * @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 1
     */
792 1
    public function setAlternativeQuery($alternativeQuery)
793
    {
794
        $this->setQueryParameterWhenStringOrUnsetWhenEmpty('q.alt', $alternativeQuery);
795
    }
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 1
     */
804
    public function setOmitHeader($omitHeader = true)
805 1
    {
806
        $omitHeader = ($omitHeader === true) ? 'true' : $omitHeader;
807
        $this->setQueryParameterWhenStringOrUnsetWhenEmpty('omitHeader', $omitHeader);
808
    }
809 1
810 1
    /**
811
     * Get the query keywords, keywords are escaped.
812
     *
813
     * @return string query keywords
814
     */
815
    public function getKeywords()
816
    {
817
        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
    public function setKeywords($keywords)
826
    {
827
        $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
        $this->keywordsRaw = $keywords;
829
    }
830 7
831
    /**
832 7
     * Gets the cleaned keywords so that it can be used in templates f.e.
833 7
     *
834
     * @return string The cleaned keywords.
835
     */
836
    public function getKeywordsCleaned()
837
    {
838
        return $this->cleanKeywords($this->keywordsRaw);
839
    }
840
841 80
    /**
842
     * Helper method to escape/encode keywords for use in HTML
843 80
     *
844 80
     * @param string $keywords Keywords to prepare for use in HTML
845 80
     * @return string Encoded keywords
846 80
     */
847 80
    public static function cleanKeywords($keywords)
848 80
    {
849 80
        $keywords = trim($keywords);
850
        $keywords = htmlspecialchars($keywords);
851 80
        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 120
    public function getKeywordsRaw()
864
    {
865 120
        return $this->keywordsRaw;
866 120
    }
867
868
    /**
869
     * Sets the minimum match (mm) parameter
870
     *
871 80
     * @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 80
     */
874
    public function setMinimumMatch($minimumMatch)
875
    {
876
        $this->setQueryParameterWhenStringOrUnsetWhenEmpty('mm', $minimumMatch);
877
    }
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 30
     * @see http://wiki.apache.org/solr/DisMaxRequestHandler#bf_.28Boost_Functions.29
884
     */
885 30
    public function setBoostFunction($boostFunction)
886 30
    {
887 30
        $this->setQueryParameterWhenStringOrUnsetWhenEmpty('bf', $boostFunction);
888 30
    }
889 30
890
    // query fields
891 1
    // TODO move up to field list methods
892 1
893 1
    /**
894
     * Sets the boost query (bq) parameter
895 30
     *
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
    public function setBoostQuery($boostQuery)
900
    {
901
        if (is_array($boostQuery)) {
902
            $this->queryParameters['bq'] = $boostQuery;
903
            return;
904 40
        }
905
        $this->setQueryParameterWhenStringOrUnsetWhenEmpty('bq', $boostQuery);
906 40
    }
907 40
908
    /**
909 6
     * Set the tie breaker (tie) parameter
910
     *
911 40
     * @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 42
    /**
920
     * Set the phrase slop (ps) parameter
921 42
     *
922 42
     * @param int $phraseSlop Phrase Slop parameter as int or null to unset this parameter
923
     * @return void
924
     */
925
    public function setPhraseSlopParameter(int $phraseSlop = null)
926
    {
927
        $this->setQueryParameterWhenIntOrUnsetWhenNull('ps', $phraseSlop);
928
    }
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
    public function setQueryPhraseSlopParameter(int $queryPhraseSlop = null)
937 2
    {
938
        $this->setQueryParameterWhenIntOrUnsetWhenNull('qs', $queryPhraseSlop);
939 2
    }
940 2
941
    /**
942
     * Set the bigram phrase slop (ps2) parameter
943 2
     *
944 2
     * @param int $bigramPhraseSlop Bigram Phrase Slop parameter as int or null to unset this parameter
945
     * @return void
946 1
     */
947
    public function setBigramPhraseSlopParameter(int $bigramPhraseSlop = null)
948 2
    {
949
        $this->setQueryParameterWhenIntOrUnsetWhenNull('ps2', $bigramPhraseSlop);
950
    }
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 2
     * @return void
957
     */
958 2
    public function setTrigramPhraseSlopParameter(int $trigramPhraseSlop = null)
959 2
    {
960 2
        $this->setQueryParameterWhenIntOrUnsetWhenNull('ps3', $trigramPhraseSlop);
961 1
    }
962 1
963
964
    /**
965 2
     * 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
    public function getQueryParameter($parameterName, $defaultIfEmpty = null)
972
    {
973 29
        $parameters = $this->getQueryParameters();
974
        return isset($parameters[$parameterName]) ? $parameters[$parameterName] : $defaultIfEmpty;
975 29
    }
976 29
977 29
    /**
978
     * Builds an array of query parameters to use for the search query.
979 1
     *
980 1
     * @return array An array ready to use with query parameters
981
     */
982 29
    public function getQueryParameters()
983
    {
984
        $queryParameters = $this->getReturnFields()->build();
985
        $queryParameters = array_merge($queryParameters, $this->getFilters()->build());
986
        $queryParameters = array_merge($queryParameters, $this->queryParameters);
987
        $queryParameters = array_merge($queryParameters, $this->getQueryFields()->build());
988
989 2
        if ($this->solrConfiguration->getPhraseSearchIsEnabled()) {
990
            $queryParameters = array_merge($queryParameters, $this->getPhraseFields()->build());
991 2
        }
992
        if ($this->solrConfiguration->getBigramPhraseSearchIsEnabled()) {
993
            $queryParameters = array_merge($queryParameters, $this->getBigramPhraseFields()->build());
994
        }
995
        if ($this->solrConfiguration->getTrigramPhraseSearchIsEnabled()) {
996
            $queryParameters = array_merge($queryParameters, $this->getTrigramPhraseFields()->build());
997
        }
998
999 120
        $queryParameters = array_merge($queryParameters, $this->getHighlighting()->build());
1000
        $queryParameters = array_merge($queryParameters, $this->getFaceting()->build());
1001
        $queryParameters = array_merge($queryParameters, $this->getGrouping()->build());
1002 120
1003 4
        return $queryParameters;
1004 4
    }
1005 4
1006
    // general query parameters
1007 4
1008
    /**
1009
     * Enables or disables highlighting of search terms in result teasers.
1010 116
     *
1011
     * @param Highlighting $highlighting
1012
     * @see http://wiki.apache.org/solr/HighlightingParameters
1013
     * @return void
1014
     */
1015
    public function setHighlighting(Highlighting $highlighting)
1016 120
    {
1017
        $this->highlighting = $highlighting;
1018 120
    }
1019 120
1020 120
    /**
1021
     * @return Highlighting
1022
     */
1023
    public function getHighlighting()
1024
    {
1025 120
        return $this->highlighting;
1026
    }
1027 120
1028 120
    // misc
1029 120
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 121
     */
1035
    public function setSpellchecking($spellchecking = true)
1036 121
    {
1037 121
        if ($spellchecking) {
1038 121
            $this->queryParameters['spellcheck'] = 'true';
1039
            $this->queryParameters['spellcheck.collate'] = 'true';
1040
            $maxCollationTries = $this->solrConfiguration->getSearchSpellcheckingNumberOfSuggestionsToTry();
1041
            $this->addQueryParameter('spellcheck.maxCollationTries', $maxCollationTries);
1042
        } else {
1043
            unset($this->queryParameters['spellcheck']);
1044
            unset($this->queryParameters['spellcheck.collate']);
1045
            unset($this->queryParameters['spellcheck.maxCollationTries']);
1046
        }
1047
    }
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
    private function setQueryParameterWhenStringOrUnsetWhenEmpty($parameterName, $value)
1057
    {
1058
        if (is_string($value) && !empty($value)) {
1059
            $this->addQueryParameter($parameterName, $value);
1060
        } else {
1061
            unset($this->queryParameters[$parameterName]);
1062
        }
1063
    }
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
    private function setQueryParameterWhenIntOrUnsetWhenNull(string $parameterName, int $value = null)
1073
    {
1074
        if (null === $value) {
1075
            unset($this->queryParameters[$parameterName]);
1076
            return;
1077
        }
1078
        $this->addQueryParameter($parameterName, $value);
1079
    }
1080
1081
    /**
1082
     * Adds any generic query parameter.
1083
     *
1084
     * @param string $parameterName Query parameter name
1085
     * @param string $parameterValue Parameter value
1086
     */
1087
    public function addQueryParameter($parameterName, $parameterValue)
1088
    {
1089
        $this->queryParameters[$parameterName] = $parameterValue;
1090
    }
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
    public function setSorting($sorting)
1106
    {
1107
        if ($sorting) {
1108
            if (!is_string($sorting)) {
1109
                throw new \InvalidArgumentException('Sorting needs to be a string!');
1110
            }
1111
            $sortParameter = $this->removeRelevanceSortField($sorting);
1112
            $this->queryParameters['sort'] = $sortParameter;
1113
        } else {
1114
            unset($this->queryParameters['sort']);
1115
        }
1116
    }
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
    protected function removeRelevanceSortField($sorting)
1125
    {
1126
        $sortParameter = $sorting;
1127
        list($sortField) = explode(' ', $sorting);
1128
        if ($sortField === 'relevance') {
1129
            $sortParameter = '';
1130
            return $sortParameter;
1131
        }
1132
1133
        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
    public function setDebugMode($debugMode = true)
1142
    {
1143
        if ($debugMode) {
1144
            $this->queryParameters['debugQuery'] = 'true';
1145
            $this->queryParameters['echoParams'] = 'all';
1146
        } else {
1147
            unset($this->queryParameters['debugQuery']);
1148
            unset($this->queryParameters['echoParams']);
1149
        }
1150
    }
1151
1152
    /**
1153
     * Returns the link target page id.
1154
     *
1155
     * @return int
1156
     */
1157
    public function getLinkTargetPageId()
1158
    {
1159
        return $this->linkTargetPageId;
1160
    }
1161
1162
    /**
1163
     * Activates the collapsing on the configured field, if collapsing was enabled.
1164
     *
1165
     * @return bool
1166
     */
1167
    protected function initializeCollapsingFromConfiguration()
1168
    {
1169
        // check collapsing
1170
        if ($this->solrConfiguration->getSearchVariants()) {
1171
            $collapseField = $this->solrConfiguration->getSearchVariantsField();
1172
            $this->setVariantField($collapseField);
1173
            $this->setCollapsing(true);
1174
1175
            return true;
1176
        }
1177
1178
        return false;
1179
    }
1180
1181
    /**
1182
     * @return void
1183
     */
1184
    protected function initializeFaceting()
1185
    {
1186
        $faceting = Faceting::fromTypoScriptConfiguration($this->solrConfiguration);
1187
        $this->setFaceting($faceting);
1188
    }
1189
1190
    /**
1191
     * @return void
1192
     */
1193
    protected function initializeGrouping()
1194
    {
1195
        $grouping = Grouping::fromTypoScriptConfiguration($this->solrConfiguration);
1196
        $this->setGrouping($grouping);
1197
    }
1198
1199
    /**
1200
     * @return void
1201
     */
1202
    protected function initializeFilters()
1203
    {
1204
        $filters = Filters::fromTypoScriptConfiguration($this->solrConfiguration);
1205
        $this->setFilters($filters);
1206
    }
1207
}
1208