Passed
Push — master ( fe103b...7bb24b )
by Timo
23:28
created

QueryBuilder::removeAllBoostQueries()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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\Sorting;
40
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\Sortings;
41
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\Spellchecking;
42
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\TrigramPhraseFields;
43
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\Facets\SortingExpression;
44
use ApacheSolrForTypo3\Solr\Domain\Site\SiteHashService;
45
use ApacheSolrForTypo3\Solr\Domain\Site\SiteRepository;
46
use ApacheSolrForTypo3\Solr\FieldProcessor\PageUidToHierarchy;
47
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
48
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
49
use ApacheSolrForTypo3\Solr\Util;
50
use TYPO3\CMS\Core\Utility\GeneralUtility;
51
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
52
53
/**
54
 * The QueryBuilder is responsible to build solr queries, that are used in the extension to query the solr server.
55
 *
56
 * @package ApacheSolrForTypo3\Solr\Domain\Search\Query
57
 */
58
class QueryBuilder {
59
60
    /**
61
     * Additional filters, which will be added to the query, as well as to
62
     * suggest queries.
63
     *
64
     * @var array
65
     */
66
    protected $additionalFilters = [];
67
68
    /**
69
     * @var TypoScriptConfiguration
70
     */
71
    protected $typoScriptConfiguration = null;
72
73
    /**
74
     * @var SolrLogManager;
75
     */
76
    protected $logger = null;
77
78
    /**
79
     * @var SiteHashService
80
     */
81
    protected $siteHashService = null;
82
83
    /**
84
     * @var Query
85
     */
86
    protected $queryToBuild = null;
87
88
    /**
89
     * QueryBuilder constructor.
90
     * @param TypoScriptConfiguration|null $configuration
91
     * @param SolrLogManager|null $solrLogManager
92
     * @param SiteHashService|null $siteHashService
93
     */
94 182
    public function __construct(TypoScriptConfiguration $configuration = null, SolrLogManager $solrLogManager = null, SiteHashService $siteHashService = null)
95
    {
96 182
        $this->typoScriptConfiguration = $configuration ?? Util::getSolrConfiguration();
97 182
        $this->logger = $solrLogManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
98 182
        $this->siteHashService = $siteHashService ?? GeneralUtility::makeInstance(SiteHashService::class);
99 182
    }
100
101
    /**
102
     * @param Query $query
103
     * @return QueryBuilder
104
     */
105 125
    public function startFrom(Query $query): QueryBuilder
106
    {
107 125
        $this->queryToBuild = $query;
108 125
        return $this;
109
    }
110
111
    /**
112
     * @param string $queryString
113
     * @return QueryBuilder
114
     */
115 148
    public function newSearchQuery($queryString): QueryBuilder
116
    {
117 148
        $this->queryToBuild = $this->getSearchQueryInstance($queryString);
118 148
        return $this;
119
    }
120
121
    /**
122
     * @param string $queryString
123
     * @return QueryBuilder
124
     */
125 4
    public function newSuggestQuery($queryString): QueryBuilder
126
    {
127 4
        $this->queryToBuild = $this->getSuggestQueryInstance($queryString);
128 4
        return $this;
129
    }
130
131
    /**
132
     * @return Query
133
     */
134 167
    public function getQuery(): Query
135
    {
136 167
        return $this->queryToBuild;
137
    }
138
139
    /**
140
     * Initializes the Query object and SearchComponents and returns
141
     * the initialized query object, when a search should be executed.
142
     *
143
     * @param string|null $rawQuery
144
     * @param int $resultsPerPage
145
     * @param array $additionalFiltersFromRequest
146
     * @return SearchQuery
147
     */
148 136
    public function buildSearchQuery($rawQuery, $resultsPerPage = 10, array $additionalFiltersFromRequest = []) : SearchQuery
149
    {
150 136
        if ($this->typoScriptConfiguration->getLoggingQuerySearchWords()) {
151
            $this->logger->log(SolrLogManager::INFO, 'Received search query', [$rawQuery]);
152
        }
153
154
        /* @var $query SearchQuery */
155 136
        return $this->newSearchQuery($rawQuery)
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->newSearchQ...ypoScript()->getQuery() returns the type ApacheSolrForTypo3\Solr\Domain\Search\Query\Query which includes types incompatible with the type-hinted return ApacheSolrForTypo3\Solr\...earch\Query\SearchQuery.
Loading history...
156 136
                ->useResultsPerPage($resultsPerPage)
157 136
                ->useReturnFieldsFromTypoScript()
158 136
                ->useQueryFieldsFromTypoScript()
159 136
                ->useInitialQueryFromTypoScript()
160 136
                ->useFiltersFromTypoScript()
161 136
                ->useFilterArray($additionalFiltersFromRequest)
162 136
                ->useFacetingFromTypoScript()
163 136
                ->useVariantsFromTypoScript()
164 136
                ->useGroupingFromTypoScript()
165 136
                ->useHighlightingFromTypoScript()
166 136
                ->usePhraseFieldsFromTypoScript()
167 136
                ->useBigramPhraseFieldsFromTypoScript()
168 136
                ->useTrigramPhraseFieldsFromTypoScript()
169 136
                ->getQuery();
170
    }
171
172
    /**
173
     * Builds a SuggestQuery with all applied filters.
174
     *
175
     * @param string $queryString
176
     * @param array $additionalFilters
177
     * @param integer $requestedPageId
178
     * @param string $groupList
179
     * @return SuggestQuery
180
     */
181 3
    public function buildSuggestQuery(string $queryString, array $additionalFilters, int $requestedPageId, string $groupList) : SuggestQuery
182
    {
183 3
        $this->newSuggestQuery($queryString)
184 3
            ->useFiltersFromTypoScript()
185 3
            ->useSiteHashFromTypoScript($requestedPageId)
186 3
            ->useUserAccessGroups(explode(',', $groupList))
187 3
            ->useOmitHeader();
188
189
190 3
        if (!empty($additionalFilters)) {
191
            $this->useFilterArray($additionalFilters);
192
        }
193
194 3
        return $this->queryToBuild;
195
    }
196
197
    /**
198
     * @param bool $omitHeader
199
     * @return QueryBuilder
200
     */
201 2
    public function useOmitHeader($omitHeader = true): QueryBuilder
202
    {
203 2
        $this->queryToBuild->setOmitHeader($omitHeader);
204
205 2
        return $this;
206
    }
207
208
    /**
209
     * Uses an array of filters and applies them to the query.
210
     *
211
     * @param array $filterArray
212
     * @return QueryBuilder
213
     */
214 138
    public function useFilterArray(array $filterArray): QueryBuilder
215
    {
216 138
        foreach ($filterArray as $key => $additionalFilter) {
217 10
            $this->useFilter($additionalFilter, $key);
218
        }
219
220 138
        return $this;
221
    }
222
223
    /**
224
     * Returns Query for Search which finds document for given page.
225
     * Note: The Connection is per language as recommended in ext-solr docs.
226
     *
227
     * @return Query
228
     */
229 1
    public function buildPageQuery($pageId)
230
    {
231 1
        $siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
232 1
        $site = $siteRepository->getSiteByPageId($pageId);
233
234 1
        return $this->newSearchQuery('')
235 1
            ->useQueryString('*:*')
236 1
            ->useFilter('(type:pages AND uid:' . $pageId . ') OR (*:* AND pid:' . $pageId . ' NOT type:pages)', 'type')
237 1
            ->useFilter('siteHash:' . $site->getSiteHash(), 'siteHash')
238 1
            ->useReturnFields(ReturnFields::fromString('*'))
239 1
            ->useSortings(Sortings::fromString('type asc, title asc'))
240 1
            ->useQueryType('standard')
241 1
            ->getQuery();
242
    }
243
244
    /**
245
     * Returns a query for single record
246
     *
247
     * @return Query
248
     */
249
    public function buildRecordQuery($type, $uid, $pageId): Query
250
    {
251
        $siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
252
        $site = $siteRepository->getSiteByPageId($pageId);
253
254
        return $this->newSearchQuery('')
255
            ->useQueryString('*:*')
256
            ->useFilter('type:' . $type . ' AND uid:' . $uid, 'type')
257
            ->useFilter('siteHash:' . $site->getSiteHash(), 'siteHash')
258
            ->useReturnFields(ReturnFields::fromString('*'))
259
            ->useSortings(Sortings::fromString('type asc, title asc'))
260
            ->useQueryType('standard')
261
            ->getQuery();
262
    }
263
264
    /**
265
     * Applies the queryString that is used to search
266
     *
267
     * @param string $queryString
268
     * @return QueryBuilder
269
     */
270 5
    public function useQueryString($queryString): QueryBuilder
271
    {
272 5
        $this->queryToBuild->setQuery($queryString);
273 5
        return $this;
274
    }
275
276
    /**
277
     * Applies the passed queryType to the query.
278
     *
279
     * @param string $queryType
280
     * @return QueryBuilder
281
     */
282 2
    public function useQueryType(string $queryType): QueryBuilder
283
    {
284 2
        $this->queryToBuild->addParam('qt', $queryType);
285 2
        return $this;
286
    }
287
288
    /**
289
     * Remove the queryType (qt) from the query.
290
     *
291
     * @return QueryBuilder
292
     */
293 1
    public function removeQueryType(): QueryBuilder
294
    {
295 1
        $this->queryToBuild->addParam('qt', null);
296 1
        return $this;
297
    }
298
299
    /**
300
     * Can be used to remove all sortings from the query.
301
     *
302
     * @return QueryBuilder
303
     */
304 1
    public function removeAllSortings(): QueryBuilder
305
    {
306 1
        $this->queryToBuild->clearSorts();
307 1
        return $this;
308
    }
309
310
    /**
311
     * Applies the passed sorting to the query.
312
     *
313
     * @param Sorting $sorting
314
     * @return QueryBuilder
315
     */
316 7
    public function useSorting(Sorting $sorting): QueryBuilder
317
    {
318 7
        if (strpos($sorting->getFieldName(), 'relevance') !== false) {
319 1
            $this->removeAllSortings();
320 1
            return $this;
321
        }
322
323 7
        $this->queryToBuild->addSort($sorting->getFieldName(), $sorting->getDirection());
324 7
        return $this;
325
    }
326
327
    /**
328
     * Applies the passed sorting to the query.
329
     *
330
     * @param Sortings $sortings
331
     * @return QueryBuilder
332
     */
333 6
    public function useSortings(Sortings $sortings): QueryBuilder
334
    {
335 6
        foreach($sortings->getSortings() as $sorting) {
336 6
            $this->useSorting($sorting);
337
        }
338
339 6
        return $this;
340
    }
341
342
    /**
343
     * @param int $resultsPerPage
344
     * @return QueryBuilder
345
     */
346 136
    public function useResultsPerPage($resultsPerPage): QueryBuilder
347
    {
348 136
        $this->queryToBuild->setRows($resultsPerPage);
349 136
        return $this;
350
    }
351
352
    /**
353
     * @param int $page
354
     * @return QueryBuilder
355
     */
356
    public function usePage($page): QueryBuilder
357
    {
358
        $this->queryToBuild->setStart($page);
359
        return $this;
360
    }
361
362
    /**
363
     * @param Operator $operator
364
     * @return QueryBuilder
365
     */
366 1
    public function useOperator(Operator $operator): QueryBuilder
367
    {
368 1
        $this->queryToBuild->setQueryDefaultOperator( $operator->getOperator());
369 1
        return $this;
370
    }
371
372
    /**
373
     * Remove the default query operator.
374
     *
375
     * @return QueryBuilder
376
     */
377 1
    public function removeOperator(): QueryBuilder
378
    {
379 1
        $this->queryToBuild->setQueryDefaultOperator(null);
380 1
        return $this;
381
    }
382
383
    /**
384
     * @return QueryBuilder
385
     */
386 51
    public function useSlopsFromTypoScript(): QueryBuilder
387
    {
388 51
        return $this->useSlops(Slops::fromTypoScriptConfiguration($this->typoScriptConfiguration));
389
    }
390
391
    /**
392
     * @param Slops $slops
393
     * @return QueryBuilder
394
     */
395 55
    public function useSlops(Slops $slops): QueryBuilder
396
    {
397 55
        return $slops->build($this);
398
    }
399
400
    /**
401
     * Uses the configured boost queries from typoscript
402
     *
403
     * @return QueryBuilder
404
     */
405 51
    public function useBoostQueriesFromTypoScript(): QueryBuilder
406
    {
407 51
        $searchConfiguration = $this->typoScriptConfiguration->getSearchConfiguration();
408
409 51
        if (!empty($searchConfiguration['query.']['boostQuery'])) {
410 1
            return $this->useBoostQueries($searchConfiguration['query.']['boostQuery']);
411
        }
412
413 50
        if (!empty($searchConfiguration['query.']['boostQuery.'])) {
414 1
            $boostQueries = $searchConfiguration['query.']['boostQuery.'];
415 1
            return $this->useBoostQueries(array_values($boostQueries));
416
        }
417
418 49
        return $this;
419
    }
420
421
    /**
422
     * Uses the passed boostQuer(y|ies) for the query.
423
     *
424
     * @param string|array $boostQueries
425
     * @return QueryBuilder
426
     */
427 3
    public function useBoostQueries($boostQueries): QueryBuilder
428
    {
429 3
        $boostQueryArray = [];
430 3
        if(is_array($boostQueries)) {
431 1
            foreach($boostQueries as $boostQuery) {
432 1
                $boostQueryArray[] = ['key' => md5($boostQuery), 'query' => $boostQuery];
433
            }
434
        } else {
435 2
            $boostQueryArray[] = ['key' => md5($boostQueries), 'query' => $boostQueries];
436
        }
437
438 3
        $this->queryToBuild->getEDisMax()->setBoostQueries($boostQueryArray);
439 3
        return $this;
440
    }
441
442
    /**
443
     * Removes all boost queries from the query.
444
     *
445
     * @return QueryBuilder
446
     */
447 1
    public function removeAllBoostQueries(): QueryBuilder
448
    {
449 1
        $this->queryToBuild->getEDisMax()->clearBoostQueries();
450 1
        return $this;
451
    }
452
453
    /**
454
     * Uses the configured boostFunction from the typoscript configuration.
455
     *
456
     * @return QueryBuilder
457
     */
458 51
    public function useBoostFunctionFromTypoScript(): QueryBuilder
459
    {
460 51
        $searchConfiguration = $this->typoScriptConfiguration->getSearchConfiguration();
461 51
        if (!empty($searchConfiguration['query.']['boostFunction'])) {
462 1
            return $this->useBoostFunction($searchConfiguration['query.']['boostFunction']);
463
        }
464
465 50
        return $this;
466
    }
467
468
    /**
469
     * Uses the passed boostFunction for the query.
470
     *
471
     * @param string $boostFunction
472
     * @return QueryBuilder
473
     */
474 2
    public function useBoostFunction(string $boostFunction): QueryBuilder
475
    {
476 2
        $this->queryToBuild->getEDisMax()->setBoostFunctions($boostFunction);
477 2
        return $this;
478
    }
479
480
    /**
481
     * Removes all previously configured boost functions.
482
     *
483
     * @return $this
484
     */
485 1
    public function removeAllBoostFunctions()
486
    {
487 1
        $this->queryToBuild->getEDisMax()->setBoostFunctions(null);
488 1
        return $this;
489
    }
490
491
    /**
492
     * Uses the configured minimumMatch from the typoscript configuration.
493
     *
494
     * @return QueryBuilder
495
     */
496 51
    public function useMinimumMatchFromTypoScript(): QueryBuilder
497
    {
498 51
        $searchConfiguration = $this->typoScriptConfiguration->getSearchConfiguration();
499 51
        if (!empty($searchConfiguration['query.']['minimumMatch'])) {
500 1
            return $this->useMinimumMatch($searchConfiguration['query.']['minimumMatch']);
501
        }
502
503 50
        return $this;
504
    }
505
506
    /**
507
     * Uses the passed minimumMatch(mm) for the query.
508
     *
509
     * @param string $minimumMatch
510
     * @return QueryBuilder
511
     */
512 2
    public function useMinimumMatch(string $minimumMatch): QueryBuilder
513
    {
514 2
        $this->queryToBuild->getEDisMax()->setMinimumMatch($minimumMatch);
515 2
        return $this;
516
    }
517
518
    /**
519
     * Remove any previous passed minimumMatch parameter.
520
     *
521
     * @return QueryBuilder
522
     */
523 1
    public function removeMinimumMatch(): QueryBuilder
524
    {
525 1
        $this->queryToBuild->getEDisMax()->setMinimumMatch(null);
526 1
        return $this;
527
    }
528
529
    /**
530
     * @return QueryBuilder
531
     */
532 51
    public function useTieParameterFromTypoScript(): QueryBuilder
533
    {
534 51
        $searchConfiguration = $this->typoScriptConfiguration->getSearchConfiguration();
535 51
        if (empty($searchConfiguration['query.']['tieParameter'])) {
536 50
            return $this;
537
        }
538
539 1
        return $this->useTieParameter($searchConfiguration['query.']['tieParameter']);
540
    }
541
542
    /**
543
     * Applies the tie parameter to the query.
544
     *
545
     * @param mixed $tie
546
     * @return QueryBuilder
547
     */
548 1
    public function useTieParameter($tie): QueryBuilder
549
    {
550 1
        $this->queryToBuild->getEDisMax()->setTie($tie);
551 1
        return $this;
552
    }
553
554
    /**
555
     * Applies the configured query fields from the typoscript configuration.
556
     *
557
     * @return QueryBuilder
558
     */
559 136
    public function useQueryFieldsFromTypoScript(): QueryBuilder
560
    {
561 136
        return $this->useQueryFields(QueryFields::fromString($this->typoScriptConfiguration->getSearchQueryQueryFields()));
562
    }
563
564
    /**
565
     * Applies custom QueryFields to the query.
566
     *
567
     * @param QueryFields $queryFields
568
     * @return QueryBuilder
569
     */
570 144
    public function useQueryFields(QueryFields $queryFields): QueryBuilder
571
    {
572 144
        return $queryFields->build($this);
573
    }
574
575
    /**
576
     * Applies the configured return fields from the typoscript configuration.
577
     *
578
     * @return QueryBuilder
579
     */
580 136
    public function useReturnFieldsFromTypoScript(): QueryBuilder
581
    {
582 136
        $returnFieldsArray = (array)$this->typoScriptConfiguration->getSearchQueryReturnFieldsAsArray(['*', 'score']);
583 136
        return $this->useReturnFields(ReturnFields::fromArray($returnFieldsArray));
584
    }
585
586
    /**
587
     * Applies custom ReturnFields to the query.
588
     *
589
     * @param ReturnFields $returnFields
590
     * @return QueryBuilder
591
     */
592 138
    public function useReturnFields(ReturnFields $returnFields): QueryBuilder
593
    {
594 138
        return $returnFields->build($this);
595
    }
596
597
    /**
598
     * Can be used to apply the allowed sites from plugin.tx_solr.search.query.allowedSites to the query.
599
     *
600
     * @param int $requestedPageId
601
     * @return QueryBuilder
602
     */
603 40
    public function useSiteHashFromTypoScript(int $requestedPageId): QueryBuilder
604
    {
605 40
        $queryConfiguration = $this->typoScriptConfiguration->getObjectByPathOrDefault('plugin.tx_solr.search.query.', []);
606 40
        $allowedSites = $this->siteHashService->getAllowedSitesForPageIdAndAllowedSitesConfiguration($requestedPageId, $queryConfiguration['allowedSites']);
607 40
        return $this->useSiteHashFromAllowedSites($allowedSites);
608
    }
609
610
    /**
611
     * Can be used to apply a list of allowed sites to the query.
612
     *
613
     * @param string $allowedSites
614
     * @return QueryBuilder
615
     */
616 40
    public function useSiteHashFromAllowedSites($allowedSites): QueryBuilder
617
    {
618 40
        $isAnySiteAllowed = trim($allowedSites) === '*';
619 40
        if ($isAnySiteAllowed) {
620
            // no filter required
621 1
            return $this;
622
        }
623
624 39
        $allowedSites = GeneralUtility::trimExplode(',', $allowedSites);
625 39
        $filters = [];
626 39
        foreach ($allowedSites as $site) {
627 39
            $siteHash = $this->siteHashService->getSiteHashForDomain($site);
628 39
            $filters[] = 'siteHash:"' . $siteHash . '"';
629
        }
630
631 39
        $siteHashFilterString = implode(' OR ', $filters);
632 39
        return $this->useFilter($siteHashFilterString, 'siteHash');
633
    }
634
635
    /**
636
     * Can be used to use a specific filter string in the solr query.
637
     *
638
     * @param string $filterString
639
     * @param string $filterName
640
     * @return QueryBuilder
641
     */
642 58
    public function useFilter($filterString, $filterName = ''): QueryBuilder
643
    {
644 58
        $filterName = $filterName === '' ? $filterString : $filterName;
645 58
        $this->queryToBuild->addFilterQuery(['key' => $filterName, 'query' => $filterString]);
646 58
        return $this;
647
    }
648
649
    /**
650
     * Removes a filter by the fieldName.
651
     *
652
     * @param string $fieldName
653
     * @return QueryBuilder
654
     */
655 1
    public function removeFilterByFieldName($fieldName): QueryBuilder
656
    {
657 1
        return $this->removeFilterByFunction(
658 1
            function($key, $query) use ($fieldName) {
659 1
                $queryString = $query->getQuery();
660 1
                $storedFieldName = substr($queryString,0, strpos($queryString, ":"));
661 1
                return $storedFieldName == $fieldName;
662 1
            }
663
        );
664
    }
665
666
    /**
667
     * Removes a filter by the name of the filter (also known as key).
668
     *
669
     * @param string $name
670
     * @return QueryBuilder
671
     */
672 1
    public function removeFilterByName($name): QueryBuilder
673
    {
674 1
        return $this->removeFilterByFunction(
675 1
            function($key, $query) use ($name) {
676 1
                $key = $query->getKey();
677 1
                return $key == $name;
678 1
            }
679
        );
680
    }
681
682
    /**
683
     * Removes a filter by the filter value.
684
     *
685
     * @param string $value
686
     * @return QueryBuilder
687
     */
688 1
    public function removeFilterByValue($value): QueryBuilder
689
    {
690 1
        return $this->removeFilterByFunction(
691 1
            function($key, $query) use ($value) {
692 1
                $query = $query->getQuery();
693 1
                return $query == $value;
694 1
            }
695
        );
696
    }
697
698
    /**
699
     * @param \Closure $filterFunction
700
     * @return QueryBuilder
701
     */
702 2
    public function removeFilterByFunction($filterFunction) : QueryBuilder
703
    {
704 2
        $queries = $this->queryToBuild->getFilterQueries();
705 2
        foreach($queries as $key =>  $query) {
706 2
            $canBeRemoved = $filterFunction($key, $query);
707 2
            if($canBeRemoved) {
708 2
                unset($queries[$key]);
709
            }
710
        }
711
712 2
        $this->queryToBuild->setFilterQueries($queries);
713 2
        return $this;
714
    }
715
716
    /**
717
     * Can be used to filter the result on an applied list of user groups.
718
     *
719
     * @param array $groups
720
     * @return QueryBuilder
721
     */
722 44
    public function useUserAccessGroups(array $groups): QueryBuilder
723
    {
724 44
        $groups = array_map('intval', $groups);
725 44
        $groups[] = 0; // always grant access to public documents
726 44
        $groups = array_unique($groups);
727 44
        sort($groups, SORT_NUMERIC);
728
729 44
        $accessFilter = '{!typo3access}' . implode(',', $groups);
730 44
        $this->queryToBuild->removeFilterQuery('access');
731 44
        return $this->useFilter($accessFilter, 'access');
732
    }
733
734
    /**
735
     * Applies the configured initial query settings to set the alternative query for solr as required.
736
     *
737
     * @return QueryBuilder
738
     */
739 136
    public function useInitialQueryFromTypoScript(): QueryBuilder
740
    {
741 136
        if ($this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) {
742
            // empty main query, but using a "return everything"
743
            // alternative query in q.alt
744 39
            $this->useAlternativeQuery('*:*');
745
        }
746
747 136
        if ($this->typoScriptConfiguration->getSearchInitializeWithQuery()) {
748 4
            $this->useAlternativeQuery($this->typoScriptConfiguration->getSearchInitializeWithQuery());
749
        }
750
751 136
        return $this;
752
    }
753
754
    /**
755
     * Passes the alternative query to the Query
756
     * @param string $query
757
     * @return QueryBuilder
758
     */
759 41
    public function useAlternativeQuery(string $query): QueryBuilder
760
    {
761 41
        $this->queryToBuild->getEDisMax()->setQueryAlternative($query);
762 41
        return $this;
763
    }
764
765
    /**
766
     * Remove the alternative query from the Query.
767
     *
768
     * @return QueryBuilder
769
     */
770 1
    public function removeAlternativeQuery(): QueryBuilder
771
    {
772 1
        $this->queryToBuild->getEDisMax()->setQueryAlternative(null);
773 1
        return $this;
774
    }
775
776
    /**
777
     * Applies the configured facets from the typoscript configuration on the query.
778
     *
779
     * @return QueryBuilder
780
     */
781 136
    public function useFacetingFromTypoScript(): QueryBuilder
782
    {
783 136
        return $this->useFaceting(Faceting::fromTypoScriptConfiguration($this->typoScriptConfiguration));
784
    }
785
786
    /**
787
     * Applies a custom Faceting configuration to the query.
788
     *
789
     * @param Faceting $faceting
790
     * @return QueryBuilder
791
     */
792 137
    public function useFaceting(Faceting $faceting): QueryBuilder
793
    {
794 137
        return $faceting->build($this);
795
    }
796
797
    /**
798
     * Applies the configured variants from the typoscript configuration on the query.
799
     *
800
     * @return QueryBuilder
801
     */
802 136
    public function useVariantsFromTypoScript(): QueryBuilder
803
    {
804 136
        return $this->useFieldCollapsing(FieldCollapsing::fromTypoScriptConfiguration($this->typoScriptConfiguration));
805
    }
806
807
    /**
808
     * @param FieldCollapsing $fieldCollapsing
809
     * @return QueryBuilder
810
     */
811 137
    public function useFieldCollapsing(FieldCollapsing $fieldCollapsing): QueryBuilder
812
    {
813 137
        return $fieldCollapsing->build($this);
814
    }
815
816
    /**
817
     * Applies the configured groupings from the typoscript configuration to the query.
818
     *
819
     * @return QueryBuilder
820
     */
821 136
    public function useGroupingFromTypoScript(): QueryBuilder
822
    {
823 136
        return $this->useGrouping(Grouping::fromTypoScriptConfiguration($this->typoScriptConfiguration));
824
    }
825
826
    /**
827
     * Applies a custom initialized grouping to the query.
828
     *
829
     * @param Grouping $grouping
830
     * @return QueryBuilder
831
     */
832 137
    public function useGrouping(Grouping $grouping): QueryBuilder
833
    {
834 137
        return $grouping->build($this);
835
    }
836
837
    /**
838
     * @param boolean $debugMode
839
     * @return QueryBuilder
840
     */
841 35
    public function useDebug($debugMode): QueryBuilder
842
    {
843 35
        if (!$debugMode) {
844 1
            $this->queryToBuild->addParam('debugQuery', null);
845 1
            $this->queryToBuild->addParam('echoParams', null);
846 1
            return $this;
847
        }
848
849 35
        $this->queryToBuild->addParam('debugQuery', 'true');
850 35
        $this->queryToBuild->addParam('echoParams', 'all');
851
852 35
        return $this;
853
    }
854
855
    /**
856
     * Applies the configured highlighting from the typoscript configuration to the query.
857
     *
858
     * @return QueryBuilder
859
     */
860 136
    public function useHighlightingFromTypoScript(): QueryBuilder
861
    {
862 136
        return $this->useHighlighting(Highlighting::fromTypoScriptConfiguration($this->typoScriptConfiguration));
863
    }
864
865
    /**
866
     * @param Highlighting $highlighting
867
     * @return QueryBuilder
868
     */
869 136
    public function useHighlighting(Highlighting $highlighting): QueryBuilder
870
    {
871 136
        return $highlighting->build($this);
872
    }
873
874
    /**
875
     * Applies the configured filters (page section and other from typoscript).
876
     *
877
     * @return QueryBuilder
878
     */
879 138
    public function useFiltersFromTypoScript(): QueryBuilder
880
    {
881 138
        $filters = Filters::fromTypoScriptConfiguration($this->typoScriptConfiguration);
882 138
        $this->queryToBuild->setFilterQueries($filters->getValues());
883
884 138
        $this->useFilterArray($this->getAdditionalFilters());
885
886 138
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
887
888 138
        if (!is_array($searchQueryFilters) || count($searchQueryFilters) <= 0) {
0 ignored issues
show
introduced by
The condition is_array($searchQueryFilters) is always true.
Loading history...
889 134
            return $this;
890
        }
891
892
        // special filter to limit search to specific page tree branches
893 4
        if (array_key_exists('__pageSections', $searchQueryFilters)) {
894 1
            $pageIds = GeneralUtility::trimExplode(',', $searchQueryFilters['__pageSections']);
895 1
            $this->usePageSectionsFromPageIds($pageIds);
896 1
            $this->typoScriptConfiguration->removeSearchQueryFilterForPageSections();
897
        }
898
899 4
        return $this;
900
    }
901
902
    /**
903
     * Applies the configured elevation from the typoscript configuration.
904
     *
905
     * @return QueryBuilder
906
     */
907 38
    public function useElevationFromTypoScript(): QueryBuilder
908
    {
909 38
        return $this->useElevation(Elevation::fromTypoScriptConfiguration($this->typoScriptConfiguration));
910
    }
911
912
    /**
913
     * @param Elevation $elevation
914
     * @return QueryBuilder
915
     */
916 40
    public function useElevation(Elevation $elevation): QueryBuilder
917
    {
918 40
        return $elevation->build($this);
919
    }
920
921
    /**
922
     * Applies the configured spellchecking from the typoscript configuration.
923
     *
924
     * @return QueryBuilder
925
     */
926 34
    public function useSpellcheckingFromTypoScript(): QueryBuilder
927
    {
928 34
        return $this->useSpellchecking(Spellchecking::fromTypoScriptConfiguration($this->typoScriptConfiguration));
929
    }
930
931
    /**
932
     * @param Spellchecking $spellchecking
933
     * @return QueryBuilder
934
     */
935 35
    public function useSpellchecking(Spellchecking $spellchecking): QueryBuilder
936
    {
937 35
        return $spellchecking->build($this);
938
    }
939
940
    /**
941
     * Applies the passed pageIds as __pageSection filter.
942
     *
943
     * @param array $pageIds
944
     * @return QueryBuilder
945
     */
946 1
    public function usePageSectionsFromPageIds(array $pageIds = []): QueryBuilder
947
    {
948 1
        $filters = [];
949
950
        /** @var $processor PageUidToHierarchy */
951 1
        $processor = GeneralUtility::makeInstance(PageUidToHierarchy::class);
952 1
        $hierarchies = $processor->process($pageIds);
953
954 1
        foreach ($hierarchies as $hierarchy) {
955 1
            $lastLevel = array_pop($hierarchy);
956 1
            $filters[] = 'rootline:"' . $lastLevel . '"';
957
        }
958
959 1
        $pageSectionsFilterString = implode(' OR ', $filters);
960 1
        return $this->useFilter($pageSectionsFilterString, 'pageSections');
961
    }
962
963
    /**
964
     * Applies the configured phrase fields from the typoscript configuration to the query.
965
     *
966
     * @return QueryBuilder
967
     */
968 136
    public function usePhraseFieldsFromTypoScript(): QueryBuilder
969
    {
970 136
        return $this->usePhraseFields(PhraseFields::fromTypoScriptConfiguration($this->typoScriptConfiguration));
971
    }
972
973
    /**
974
     * Applies a custom configured PhraseFields to the query.
975
     *
976
     * @param PhraseFields $phraseFields
977
     * @return QueryBuilder
978
     */
979 138
    public function usePhraseFields(PhraseFields $phraseFields): QueryBuilder
980
    {
981 138
        return $phraseFields->build($this);
982
    }
983
984
    /**
985
     * Applies the configured bigram phrase fields from the typoscript configuration to the query.
986
     *
987
     * @return QueryBuilder
988
     */
989 136
    public function useBigramPhraseFieldsFromTypoScript(): QueryBuilder
990
    {
991 136
        return $this->useBigramPhraseFields(BigramPhraseFields::fromTypoScriptConfiguration($this->typoScriptConfiguration));
992
    }
993
994
    /**
995
     * Applies a custom configured BigramPhraseFields to the query.
996
     *
997
     * @param BigramPhraseFields $bigramPhraseFields
998
     * @return QueryBuilder
999
     */
1000 137
    public function useBigramPhraseFields(BigramPhraseFields $bigramPhraseFields): QueryBuilder
1001
    {
1002 137
        return $bigramPhraseFields->build($this);
1003
    }
1004
1005
    /**
1006
     * Applies the configured trigram phrase fields from the typoscript configuration to the query.
1007
     *
1008
     * @return QueryBuilder
1009
     */
1010 136
    public function useTrigramPhraseFieldsFromTypoScript(): QueryBuilder
1011
    {
1012 136
        return $this->useTrigramPhraseFields(TrigramPhraseFields::fromTypoScriptConfiguration($this->typoScriptConfiguration));
1013
    }
1014
1015
    /**
1016
     * Applies a custom configured TrigramPhraseFields to the query.
1017
     *
1018
     * @param TrigramPhraseFields $trigramPhraseFields
1019
     * @return QueryBuilder
1020
     */
1021 137
    public function useTrigramPhraseFields(TrigramPhraseFields $trigramPhraseFields): QueryBuilder
1022
    {
1023 137
        return $trigramPhraseFields->build($this);
1024
    }
1025
1026
    /**
1027
     * Retrieves the configuration filters from the TypoScript configuration, except the __pageSections filter.
1028
     *
1029
     * @return array
1030
     */
1031 143
    public function getAdditionalFilters() : array
1032
    {
1033
        // when we've build the additionalFilter once, we could return them
1034 143
        if (count($this->additionalFilters) > 0) {
1035 2
            return $this->additionalFilters;
1036
        }
1037
1038 143
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
1039 143
        if (!is_array($searchQueryFilters) || count($searchQueryFilters) <= 0) {
0 ignored issues
show
introduced by
The condition is_array($searchQueryFilters) is always true.
Loading history...
1040 140
            return [];
1041
        }
1042
1043 4
        $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
1044
1045
        // all other regular filters
1046 4
        foreach ($searchQueryFilters as $filterKey => $filter) {
1047
            // the __pageSections filter should not be handled as additional filter
1048 4
            if ($filterKey === '__pageSections') {
1049 1
                continue;
1050
            }
1051
1052 3
            $filterIsArray = is_array($searchQueryFilters[$filterKey]);
1053 3
            if ($filterIsArray) {
1054
                continue;
1055
            }
1056
1057 3
            $hasSubConfiguration = is_array($searchQueryFilters[$filterKey . '.']);
1058 3
            if ($hasSubConfiguration) {
1059
                $filter = $cObj->stdWrap($searchQueryFilters[$filterKey], $searchQueryFilters[$filterKey . '.']);
1060
            }
1061
1062 3
            $this->additionalFilters[$filterKey] = $filter;
1063
        }
1064
1065 4
        return $this->additionalFilters;
1066
    }
1067
1068
    /**
1069
     * @param string $rawQuery
1070
     * @return SearchQuery
1071
     */
1072 148
    protected function getSearchQueryInstance($rawQuery): SearchQuery
1073
    {
1074 148
        $query = GeneralUtility::makeInstance(SearchQuery::class);
1075 148
        $query->setQuery($rawQuery);
1076 148
        return $query;
1077
    }
1078
1079
1080
    /**
1081
     * @param string $rawQuery
1082
     * @return SuggestQuery
1083
     */
1084 4
    protected function getSuggestQueryInstance($rawQuery): SuggestQuery
1085
    {
1086 4
        $query = GeneralUtility::makeInstance(SuggestQuery::class, /** @scrutinizer ignore-type */ $rawQuery, /** @scrutinizer ignore-type */ $this->typoScriptConfiguration);
1087
1088 4
        return $query;
1089
    }
1090
}