Completed
Push — master ( 6ee2a7...5f60a5 )
by Rafael
04:29
created

QueryBuilder::buildSearchQuery()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 9.2
c 0
b 0
f 0
cc 2
eloc 17
nc 2
nop 2
1
<?php
2
namespace ApacheSolrForTypo3\Solr\Domain\Search\Query;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2017 <[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 3 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\ParameterBuilder\BigramPhraseFields;
28
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\Elevation;
29
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\Faceting;
30
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\FieldCollapsing;
31
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\Filters;
32
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\Grouping;
33
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\Highlighting;
34
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\Operator;
35
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\PhraseFields;
36
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\QueryFields;
37
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\ReturnFields;
38
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\Slops;
39
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\Spellchecking;
40
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\TrigramPhraseFields;
41
use ApacheSolrForTypo3\Solr\Domain\Site\SiteHashService;
42
use ApacheSolrForTypo3\Solr\Domain\Site\SiteRepository;
43
use ApacheSolrForTypo3\Solr\FieldProcessor\PageUidToHierarchy;
44
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
45
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
46
use ApacheSolrForTypo3\Solr\Util;
47
use TYPO3\CMS\Core\Utility\GeneralUtility;
48
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
49
50
/**
51
 * The QueryBuilder is responsible to build solr queries, that are used in the extension to query the solr server.
52
 *
53
 * @package ApacheSolrForTypo3\Solr\Domain\Search\Query
54
 */
55
class QueryBuilder {
56
57
    /**
58
     * Additional filters, which will be added to the query, as well as to
59
     * suggest queries.
60
     *
61
     * @var array
62
     */
63
    protected $additionalFilters = [];
64
65
    /**
66
     * @var TypoScriptConfiguration
67
     */
68
    protected $typoScriptConfiguration = null;
69
70
    /**
71
     * @var SolrLogManager;
72
     */
73
    protected $logger = null;
74
75
    /**
76
     * @var SiteHashService
77
     */
78
    protected $siteHashService = null;
79
80
    /**
81
     * @var Query
82
     */
83
    protected $queryToBuild = null;
84
85
    /**
86
     * QueryBuilder constructor.
87
     * @param TypoScriptConfiguration|null $configuration
88
     * @param SolrLogManager|null $solrLogManager
89
     * @param SiteHashService|null $siteHashService
90
     */
91
    public function __construct(TypoScriptConfiguration $configuration = null, SolrLogManager $solrLogManager = null, SiteHashService $siteHashService = null)
92
    {
93
        $this->typoScriptConfiguration = $configuration ?? Util::getSolrConfiguration();
94
        $this->logger = $solrLogManager ?? GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__);
95
        $this->siteHashService = $siteHashService ?? GeneralUtility::makeInstance(SiteHashService::class);
96
    }
97
98
    /**
99
     * @param Query $query
100
     * @return QueryBuilder
101
     */
102
    public function startFrom(Query $query): QueryBuilder
103
    {
104
        $this->queryToBuild = $query;
105
        return $this;
106
    }
107
108
    /**
109
     * @param string $queryString
110
     * @return QueryBuilder
111
     */
112
    public function newSearchQuery($queryString): QueryBuilder
113
    {
114
        $this->queryToBuild = $this->getQueryInstance($queryString);
115
        return $this;
116
    }
117
118
    /**
119
     * @param string $queryString
120
     * @return QueryBuilder
121
     */
122
    public function newSuggestQuery($queryString): QueryBuilder
123
    {
124
        $this->queryToBuild = $this->getSuggestQueryInstance($queryString);
125
        return $this;
126
    }
127
128
    /**
129
     * @return Query
130
     */
131
    public function getQuery(): Query
132
    {
133
        return $this->queryToBuild;
134
    }
135
136
    /**
137
     * Initializes the Query object and SearchComponents and returns
138
     * the initialized query object, when a search should be executed.
139
     *
140
     * @param string|null $rawQuery
141
     * @param int $resultsPerPage
142
     * @return Query
143
     */
144
    public function buildSearchQuery($rawQuery, $resultsPerPage = 10) : Query
145
    {
146
        if ($this->typoScriptConfiguration->getLoggingQuerySearchWords()) {
147
            $this->logger->log(SolrLogManager::INFO, 'Received search query', [$rawQuery]);
148
        }
149
150
        /* @var $query Query */
151
        return $this->newSearchQuery($rawQuery)
152
                ->useResultsPerPage($resultsPerPage)
153
                ->useReturnFieldsFromTypoScript()
154
                ->useQueryFieldsFromTypoScript()
155
                ->useInitialQueryFromTypoScript()
156
                ->useFiltersFromTypoScript()
157
                ->useFacetingFromTypoScript()
158
                ->useVariantsFromTypoScript()
159
                ->useGroupingFromTypoScript()
160
                ->useHighlightingFromTypoScript()
161
                ->usePhraseFieldsFromTypoScript()
162
                ->useBigramPhraseFieldsFromTypoScript()
163
                ->useTrigramPhraseFieldsFromTypoScript()
164
                ->getQuery();
165
    }
166
167
    /**
168
     * Builds a SuggestQuery with all applied filters.
169
     *
170
     * @param string $queryString
171
     * @param string $additionalFilters
172
     * @param integer $requestedPageId
173
     * @param string $groupList
174
     * @return SuggestQuery
175
     */
176
    public function buildSuggestQuery(string $queryString, string $additionalFilters, int $requestedPageId, string $groupList) : SuggestQuery
177
    {
178
        $this->newSuggestQuery($queryString)
179
            ->useFiltersFromTypoScript()
180
            ->useSiteHashFromTypoScript($requestedPageId)
181
            ->useUserAccessGroups(explode(',', $groupList));
182
183
        $this->queryToBuild->setOmitHeader();
184
185
        if (!empty($additionalFilters)) {
186
            $additionalFilters = (array)json_decode($additionalFilters);
187
            $this->useFilterArray($additionalFilters);
188
        }
189
190
191
        return $this->queryToBuild;
192
    }
193
194
    /**
195
     * Uses an array of filters and applies them to the query.
196
     *
197
     * @param array $filterArray
198
     * @return QueryBuilder
199
     */
200
    public function useFilterArray(array $filterArray): QueryBuilder
201
    {
202
        foreach ($filterArray as $additionalFilter) {
203
            $this->useFilter($additionalFilter);
204
        }
205
206
        return $this;
207
    }
208
209
    /**
210
     * Returns Query for Search which finds document for given page.
211
     * Note: The Connection is per language as recommended in ext-solr docs.
212
     *
213
     * @return Query
214
     */
215
    public function buildPageQuery($pageId)
216
    {
217
        $siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
218
        $site = $siteRepository->getSiteByPageId($pageId);
219
220
        return $this->newSearchQuery('')
221
            ->useQueryString('*:*')
222
            ->useFilter('(type:pages AND uid:' . $pageId . ') OR (*:* AND pid:' . $pageId . ' NOT type:pages)', 'type')
223
            ->useFilter('siteHash:' . $site->getSiteHash(), 'siteHash')
224
            ->useReturnFields(ReturnFields::fromString('*'))
225
            ->useSorting('type asc, title asc')
226
            ->useQueryType('standard')
227
            ->useRawQueryString()
228
            ->getQuery();
229
    }
230
231
    /**
232
     * Returns a query for single record
233
     *
234
     * @return Query
235
     */
236
    public function buildRecordQuery($type, $uid, $pageId): Query
237
    {
238
        $siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
239
        $site = $siteRepository->getSiteByPageId($pageId);
240
241
        return $this->newSearchQuery('')
242
            ->useQueryString('*:*')
243
            ->useFilter('type:' . $type . ' AND uid:' . $uid, 'type')
244
            ->useFilter('siteHash:' . $site->getSiteHash(), 'siteHash')
245
            ->useReturnFields(ReturnFields::fromString('*'))
246
            ->useSorting('type asc, title asc')
247
            ->useQueryType('standard')
248
            ->useRawQueryString()
249
            ->getQuery();
250
    }
251
252
    /**
253
     * Applies the queryString that is used to search
254
     *
255
     * @param string $queryString
256
     * @return QueryBuilder
257
     */
258
    public function useQueryString($queryString): QueryBuilder
259
    {
260
        $this->queryToBuild->getQueryStringContainer()->setQueryString($queryString);
261
        return $this;
262
    }
263
264
    /**
265
     * Applies the useRawQueryString flag to the queryString.
266
     *
267
     * @param boolean $boolean
268
     * @return QueryBuilder
269
     */
270
    public function useRawQueryString($boolean = true): QueryBuilder
271
    {
272
        $this->queryToBuild->getQueryStringContainer()->useRawQueryString($boolean);
273
        return $this;
274
    }
275
276
    /**
277
     * Applies the passed queryType to the query.
278
     *
279
     * @param string $queryType
280
     * @return QueryBuilder
281
     */
282
    public function useQueryType($queryType): QueryBuilder
283
    {
284
        $this->queryToBuild->setQueryType($queryType);
285
        return $this;
286
    }
287
288
    /**
289
     * Applies the passed sorting to the query.
290
     *
291
     * @param string $sorting
292
     * @return QueryBuilder
293
     */
294
    public function useSorting($sorting): QueryBuilder
295
    {
296
        $this->queryToBuild->setSorting($sorting);
297
        return $this;
298
    }
299
300
    /**
301
     * @param int $resultsPerPage
302
     * @return QueryBuilder
303
     */
304
    public function useResultsPerPage($resultsPerPage): QueryBuilder
305
    {
306
        $this->queryToBuild->getPagination()->setResultsPerPage($resultsPerPage);
307
        return $this;
308
    }
309
310
    /**
311
     * @param int $page
312
     * @return QueryBuilder
313
     */
314
    public function usePage($page): QueryBuilder
315
    {
316
        $this->queryToBuild->getPagination()->setPage($page);
317
        return $this;
318
    }
319
320
    /**
321
     * @param Operator $operator
322
     * @return QueryBuilder
323
     */
324
    public function useOperator(Operator $operator): QueryBuilder
325
    {
326
        $this->queryToBuild->setOperator($operator);
327
        return $this;
328
    }
329
330
    /**
331
     * @return QueryBuilder
332
     */
333
    public function useSlopsFromTypoScript(): QueryBuilder
334
    {
335
        return $this->useSlops(Slops::fromTypoScriptConfiguration($this->typoScriptConfiguration));
336
    }
337
338
    /**
339
     * @param Slops $slops
340
     * @return QueryBuilder
341
     */
342
    public function useSlops(Slops $slops): QueryBuilder
343
    {
344
        $this->queryToBuild->setSlops($slops);
345
        return $this;
346
    }
347
348
    /**
349
     * Uses the configured boost queries from typoscript
350
     *
351
     * @return QueryBuilder
352
     */
353
    public function useBoostQueriesFromTypoScript(): QueryBuilder
354
    {
355
        $searchConfiguration = $this->typoScriptConfiguration->getSearchConfiguration();
356
357
        if (!empty($searchConfiguration['query.']['boostQuery'])) {
358
            return $this->useBoostQueries($searchConfiguration['query.']['boostQuery']);
359
        }
360
361
        if (!empty($searchConfiguration['query.']['boostQuery.'])) {
362
            $boostQueries = $searchConfiguration['query.']['boostQuery.'];
363
            return $this->useBoostQueries(array_values($boostQueries));
364
        }
365
366
        return $this;
367
    }
368
369
    /**
370
     * Uses the passed boostQuer(y|ies) for the query.
371
     *
372
     * @param string|array $boostQueries
373
     * @return QueryBuilder
374
     */
375
    public function useBoostQueries($boostQueries): QueryBuilder
376
    {
377
        $this->queryToBuild->setBoostQuery($boostQueries);
378
        return $this;
379
    }
380
381
    /**
382
     * Uses the configured boostFunction from the typoscript configuration.
383
     *
384
     * @return QueryBuilder
385
     */
386
    public function useBoostFunctionFromTypoScript(): QueryBuilder
387
    {
388
        $searchConfiguration = $this->typoScriptConfiguration->getSearchConfiguration();
389
        if (!empty($searchConfiguration['query.']['boostFunction'])) {
390
            return $this->useBoostFunction($searchConfiguration['query.']['boostFunction']);
391
        }
392
393
        return $this;
394
    }
395
396
    /**
397
     * Uses the passed boostFunction for the query.
398
     *
399
     * @param string $boostFunction
400
     * @return QueryBuilder
401
     */
402
    public function useBoostFunction($boostFunction): QueryBuilder
403
    {
404
        $this->queryToBuild->setBoostFunction($boostFunction);
405
        return $this;
406
    }
407
408
    /**
409
     * Uses the configured minimumMatch from the typoscript configuration.
410
     *
411
     * @return QueryBuilder
412
     */
413
    public function useMinimumMatchFromTypoScript(): QueryBuilder
414
    {
415
        $searchConfiguration = $this->typoScriptConfiguration->getSearchConfiguration();
416
        if (!empty($searchConfiguration['query.']['minimumMatch'])) {
417
            return $this->useMinimumMatch($searchConfiguration['query.']['minimumMatch']);
418
        }
419
420
        return $this;
421
    }
422
423
    /**
424
     * Uses the passed minimumMatch(mm) for the query.
425
     *
426
     * @param string $boostFunction
427
     * @return QueryBuilder
428
     */
429
    public function useMinimumMatch($boostFunction): QueryBuilder
430
    {
431
        $this->queryToBuild->setMinimumMatch($boostFunction);
432
        return $this;
433
    }
434
435
    /**
436
     * @return QueryBuilder
437
     */
438
    public function useTieParameterFromTypoScript(): QueryBuilder
439
    {
440
        $searchConfiguration = $this->typoScriptConfiguration->getSearchConfiguration();
441
        if (empty($searchConfiguration['query.']['tieParameter'])) {
442
            return $this;
443
        }
444
445
        return $this->useTieParameter($searchConfiguration['query.']['tieParameter']);
446
    }
447
448
    /**
449
     * Applies the tie parameter to the query.
450
     *
451
     * @param mixed $tie
452
     * @return QueryBuilder
453
     */
454
    public function useTieParameter($tie): QueryBuilder
455
    {
456
        $this->queryToBuild->setTieParameter($tie);
457
        return $this;
458
    }
459
460
    /**
461
     * Applies the configured query fields from the typoscript configuration.
462
     *
463
     * @return QueryBuilder
464
     */
465
    public function useQueryFieldsFromTypoScript(): QueryBuilder
466
    {
467
        return $this->useQueryFields(QueryFields::fromString($this->typoScriptConfiguration->getSearchQueryQueryFields()));
468
    }
469
470
    /**
471
     * Applies custom QueryFields to the query.
472
     *
473
     * @param QueryFields $queryFields
474
     * @return QueryBuilder
475
     */
476
    public function useQueryFields(QueryFields $queryFields): QueryBuilder
477
    {
478
        $this->queryToBuild->setQueryFields($queryFields);
479
        return $this;
480
    }
481
482
    /**
483
     * Applies the configured return fields from the typoscript configuration.
484
     *
485
     * @return QueryBuilder
486
     */
487
    public function useReturnFieldsFromTypoScript(): QueryBuilder
488
    {
489
        $returnFieldsArray = (array)$this->typoScriptConfiguration->getSearchQueryReturnFieldsAsArray(['*', 'score']);
490
        return $this->useReturnFields(ReturnFields::fromArray($returnFieldsArray));
491
    }
492
493
    /**
494
     * Applies custom ReturnFields to the query.
495
     *
496
     * @param ReturnFields $returnFields
497
     * @return QueryBuilder
498
     */
499
    public function useReturnFields(ReturnFields $returnFields): QueryBuilder
500
    {
501
        $this->queryToBuild->setReturnFields($returnFields);
502
        return $this;
503
    }
504
505
    /**
506
     * Can be used to apply the allowed sites from plugin.tx_solr.search.query.allowedSites to the query.
507
     *
508
     * @param int $requestedPageId
509
     * @return QueryBuilder
510
     */
511
    public function useSiteHashFromTypoScript(int $requestedPageId): QueryBuilder
512
    {
513
        $queryConfiguration = $this->typoScriptConfiguration->getObjectByPathOrDefault('plugin.tx_solr.search.query.', []);
514
        $allowedSites = $this->siteHashService->getAllowedSitesForPageIdAndAllowedSitesConfiguration($requestedPageId, $queryConfiguration['allowedSites']);
515
        return $this->useSiteHashFromAllowedSites($allowedSites);
516
    }
517
518
    /**
519
     * Can be used to apply a list of allowed sites to the query.
520
     *
521
     * @param string $allowedSites
522
     * @return QueryBuilder
523
     */
524
    public function useSiteHashFromAllowedSites($allowedSites): QueryBuilder
525
    {
526
        $isAnySiteAllowed = trim($allowedSites) === '*';
527
        if ($isAnySiteAllowed) {
528
            // no filter required
529
            return $this;
530
        }
531
532
        $allowedSites = GeneralUtility::trimExplode(',', $allowedSites);
533
        $filters = [];
534
        foreach ($allowedSites as $site) {
535
            $siteHash = $this->siteHashService->getSiteHashForDomain($site);
536
            $filters[] = 'siteHash:"' . $siteHash . '"';
537
        }
538
539
        $siteHashFilterString = implode(' OR ', $filters);
540
        return $this->useFilter($siteHashFilterString, 'siteHash');
541
    }
542
543
    /**
544
     * Can be used to use a specific filter string in the solr query.
545
     *
546
     * @param string $filterString
547
     * @param string $filterName
548
     * @return QueryBuilder
549
     */
550
    public function useFilter($filterString, $filterName = ''): QueryBuilder
551
    {
552
        $this->queryToBuild->getFilters()->add($filterString, $filterName);
553
        return $this;
554
    }
555
556
    /**
557
     * Can be used to filter the result on an applied list of user groups.
558
     *
559
     * @param array $groups
560
     * @return QueryBuilder
561
     */
562
    public function useUserAccessGroups(array $groups): QueryBuilder
563
    {
564
        $groups = array_map('intval', $groups);
565
        $groups[] = 0; // always grant access to public documents
566
        $groups = array_unique($groups);
567
        sort($groups, SORT_NUMERIC);
568
569
        $accessFilter = '{!typo3access}' . implode(',', $groups);
570
        return $this->useFilter($accessFilter, 'accessFilter');
571
    }
572
573
    /**
574
     * Applies the configured initial query settings to set the alternative query for solr as required.
575
     *
576
     * @return QueryBuilder
577
     */
578
    public function useInitialQueryFromTypoScript(): QueryBuilder
579
    {
580
        if ($this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) {
581
            // empty main query, but using a "return everything"
582
            // alternative query in q.alt
583
            $this->queryToBuild->setAlternativeQuery('*:*');
584
        }
585
586
        if ($this->typoScriptConfiguration->getSearchInitializeWithQuery()) {
587
            $this->queryToBuild->setAlternativeQuery($this->typoScriptConfiguration->getSearchInitializeWithQuery());
588
        }
589
590
        return $this;
591
    }
592
593
    /**
594
     * Applies the configured facets from the typoscript configuration on the query.
595
     *
596
     * @return QueryBuilder
597
     */
598
    public function useFacetingFromTypoScript(): QueryBuilder
599
    {
600
        return $this->useFaceting(Faceting::fromTypoScriptConfiguration($this->typoScriptConfiguration));
601
    }
602
603
    /**
604
     * Applies a custom Faceting configuration to the query.
605
     *
606
     * @param Faceting $faceting
607
     * @return QueryBuilder
608
     */
609
    public function useFaceting(Faceting $faceting): QueryBuilder
610
    {
611
        $this->queryToBuild->setFaceting($faceting);
612
        return $this;
613
    }
614
615
    /**
616
     * Applies the configured variants from the typoscript configuration on the query.
617
     *
618
     * @return QueryBuilder
619
     */
620
    public function useVariantsFromTypoScript(): QueryBuilder
621
    {
622
        return $this->useFieldCollapsing(FieldCollapsing::fromTypoScriptConfiguration($this->typoScriptConfiguration));
623
    }
624
625
    /**
626
     * @param FieldCollapsing $fieldCollapsing
627
     * @return QueryBuilder
628
     */
629
    public function useFieldCollapsing(FieldCollapsing $fieldCollapsing): QueryBuilder
630
    {
631
        $this->queryToBuild->setFieldCollapsing($fieldCollapsing);
632
        return $this;
633
    }
634
635
    /**
636
     * Applies the configured groupings from the typoscript configuration to the query.
637
     *
638
     * @return QueryBuilder
639
     */
640
    public function useGroupingFromTypoScript(): QueryBuilder
641
    {
642
        return $this->useGrouping(Grouping::fromTypoScriptConfiguration($this->typoScriptConfiguration));
643
    }
644
645
    /**
646
     * Applies a custom initialized grouping to the query.
647
     *
648
     * @param Grouping $grouping
649
     * @return QueryBuilder
650
     */
651
    public function useGrouping(Grouping $grouping): QueryBuilder
652
    {
653
        $this->queryToBuild->setGrouping($grouping);
654
        return $this;
655
    }
656
657
    /**
658
     * Applies the configured highlighting from the typoscript configuration to the query.
659
     *
660
     * @return QueryBuilder
661
     */
662
    public function useHighlightingFromTypoScript(): QueryBuilder
663
    {
664
        return $this->useHighlighting(Highlighting::fromTypoScriptConfiguration($this->typoScriptConfiguration));
665
    }
666
667
    /**
668
     * @param Highlighting $highlighting
669
     * @return QueryBuilder
670
     */
671
    public function useHighlighting(Highlighting $highlighting): QueryBuilder
672
    {
673
        $this->queryToBuild->setHighlighting($highlighting);
674
        return $this;
675
    }
676
677
    /**
678
     * Applies the configured filters (page section and other from typoscript).
679
     *
680
     * @return QueryBuilder
681
     */
682
    public function useFiltersFromTypoScript(): QueryBuilder
683
    {
684
        $filters = Filters::fromTypoScriptConfiguration($this->typoScriptConfiguration);
685
        $this->queryToBuild->setFilters($filters);
686
687
        $this->useFilterArray($this->getAdditionalFilters());
688
689
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
690
691
        if (count($searchQueryFilters) <= 0) {
692
            return $this;
693
        }
694
695
        // special filter to limit search to specific page tree branches
696
        if (array_key_exists('__pageSections', $searchQueryFilters)) {
697
            $pageIds = GeneralUtility::trimExplode(',', $searchQueryFilters['__pageSections']);
698
            $this->usePageSectionsFromPageIds($pageIds);
699
            $this->typoScriptConfiguration->removeSearchQueryFilterForPageSections();
700
        }
701
702
        return $this;
703
    }
704
705
    /**
706
     * Applies the configured elevation from the typoscript configuration.
707
     *
708
     * @return QueryBuilder
709
     */
710
    public function useElevationFromTypoScript(): QueryBuilder
711
    {
712
        return $this->useElevation(Elevation::fromTypoScriptConfiguration($this->typoScriptConfiguration));
713
    }
714
715
    /**
716
     * @param Elevation $elevation
717
     * @return QueryBuilder
718
     */
719
    public function useElevation(Elevation $elevation): QueryBuilder
720
    {
721
        $this->queryToBuild->setElevation($elevation);
722
        return $this;
723
    }
724
725
    /**
726
     * Applies the configured spellchecking from the typoscript configuration.
727
     *
728
     * @return QueryBuilder
729
     */
730
    public function useSpellcheckingFromTypoScript(): QueryBuilder
731
    {
732
        return $this->useSpellchecking(Spellchecking::fromTypoScriptConfiguration($this->typoScriptConfiguration));
733
    }
734
735
    /**
736
     * @param Spellchecking $spellchecking
737
     * @return QueryBuilder
738
     */
739
    public function useSpellchecking(Spellchecking $spellchecking): QueryBuilder
740
    {
741
        $this->queryToBuild->setSpellchecking($spellchecking);
742
        return $this;
743
    }
744
745
    /**
746
     * Applies the passed pageIds as __pageSection filter.
747
     *
748
     * @param array $pageIds
749
     * @return QueryBuilder
750
     */
751
    public function usePageSectionsFromPageIds(array $pageIds = []): QueryBuilder
752
    {
753
        $filters = [];
754
755
        /** @var $processor PageUidToHierarchy */
756
        $processor = GeneralUtility::makeInstance(PageUidToHierarchy::class);
757
        $hierarchies = $processor->process($pageIds);
758
759
        foreach ($hierarchies as $hierarchy) {
760
            $lastLevel = array_pop($hierarchy);
761
            $filters[] = 'rootline:"' . $lastLevel . '"';
762
        }
763
764
        $pageSectionsFilterString = implode(' OR ', $filters);
765
        return $this->useFilter($pageSectionsFilterString, 'pageSections');
766
    }
767
768
    /**
769
     * Applies the configured phrase fields from the typoscript configuration to the query.
770
     *
771
     * @return QueryBuilder
772
     */
773
    public function usePhraseFieldsFromTypoScript(): QueryBuilder
774
    {
775
        return $this->usePhraseFields(PhraseFields::fromTypoScriptConfiguration($this->typoScriptConfiguration));
776
    }
777
778
    /**
779
     * Applies a custom configured PhraseFields to the query.
780
     *
781
     * @param PhraseFields $phraseFields
782
     * @return QueryBuilder
783
     */
784
    public function usePhraseFields(PhraseFields $phraseFields): QueryBuilder
785
    {
786
        $this->queryToBuild->setPhraseFields($phraseFields);
787
        return $this;
788
    }
789
790
    /**
791
     * Applies the configured bigram phrase fields from the typoscript configuration to the query.
792
     *
793
     * @return QueryBuilder
794
     */
795
    public function useBigramPhraseFieldsFromTypoScript(): QueryBuilder
796
    {
797
        return $this->useBigramPhraseFields(BigramPhraseFields::fromTypoScriptConfiguration($this->typoScriptConfiguration));
798
    }
799
800
    /**
801
     * Applies a custom configured BigramPhraseFields to the query.
802
     *
803
     * @param BigramPhraseFields $bigramPhraseFields
804
     * @return QueryBuilder
805
     */
806
    public function useBigramPhraseFields(BigramPhraseFields $bigramPhraseFields): QueryBuilder
807
    {
808
        $this->queryToBuild->setBigramPhraseFields($bigramPhraseFields);
809
        return $this;
810
    }
811
812
    /**
813
     * Applies the configured trigram phrase fields from the typoscript configuration to the query.
814
     *
815
     * @return QueryBuilder
816
     */
817
    public function useTrigramPhraseFieldsFromTypoScript(): QueryBuilder
818
    {
819
        return $this->useTrigramPhraseFields(TrigramPhraseFields::fromTypoScriptConfiguration($this->typoScriptConfiguration));
820
    }
821
822
    /**
823
     * Applies a custom configured TrigramPhraseFields to the query.
824
     *
825
     * @param TrigramPhraseFields $trigramPhraseFields
826
     * @return QueryBuilder
827
     */
828
    public function useTrigramPhraseFields(TrigramPhraseFields $trigramPhraseFields): QueryBuilder
829
    {
830
        $this->queryToBuild->setTrigramPhraseFields($trigramPhraseFields);
831
        return $this;
832
    }
833
834
    /**
835
     * Retrieves the configuration filters from the TypoScript configuration, except the __pageSections filter.
836
     *
837
     * @return array
838
     */
839
    public function getAdditionalFilters() : array
840
    {
841
        // when we've build the additionalFilter once, we could return them
842
        if (count($this->additionalFilters) > 0) {
843
            return $this->additionalFilters;
844
        }
845
846
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
847
        if (count($searchQueryFilters) <= 0) {
848
            return [];
849
        }
850
851
        $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
852
853
        // all other regular filters
854
        foreach ($searchQueryFilters as $filterKey => $filter) {
855
            // the __pageSections filter should not be handled as additional filter
856
            if ($filterKey === '__pageSections') {
857
                continue;
858
            }
859
860
            $filterIsArray = is_array($searchQueryFilters[$filterKey]);
861
            if ($filterIsArray) {
862
                continue;
863
            }
864
865
            $hasSubConfiguration = is_array($searchQueryFilters[$filterKey . '.']);
866
            if ($hasSubConfiguration) {
867
                $filter = $cObj->stdWrap($searchQueryFilters[$filterKey], $searchQueryFilters[$filterKey . '.']);
868
            }
869
870
            $this->additionalFilters[$filterKey] = $filter;
871
        }
872
873
        return $this->additionalFilters;
874
    }
875
876
    /**
877
     * @param string $rawQuery
878
     * @return Query|object
879
     */
880
    protected function getQueryInstance($rawQuery): Query
881
    {
882
        $query = GeneralUtility::makeInstance(Query::class, $rawQuery);
883
        return $query;
884
    }
885
886
887
    /**
888
     * @param string $rawQuery
889
     * @return SuggestQuery
890
     */
891
    protected function getSuggestQueryInstance($rawQuery): SuggestQuery
892
    {
893
        $query = GeneralUtility::makeInstance(SuggestQuery::class, $rawQuery, $this->typoScriptConfiguration);
894
        return $query;
895
    }
896
}