Passed
Push — master ( 158c27...727c92 )
by Timo
04:33
created

mponents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 1
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
3
namespace ApacheSolrForTypo3\Solr\Domain\Search\ResultSet;
4
5
/***************************************************************
6
 *  Copyright notice
7
 *
8
 *  (c) 2015-2016 Timo Schmidt <[email protected]>
9
 *  All rights reserved
10
 *
11
 *  This script is part of the TYPO3 project. The TYPO3 project is
12
 *  free software; you can redistribute it and/or modify
13
 *  it under the terms of the GNU General Public License as published by
14
 *  the Free Software Foundation; either version 2 of the License, or
15
 *  (at your option) any later version.
16
 *
17
 *  The GNU General Public License can be found at
18
 *  http://www.gnu.org/copyleft/gpl.html.
19
 *
20
 *  This script is distributed in the hope that it will be useful,
21
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 *  GNU General Public License for more details.
24
 *
25
 *  This copyright notice MUST APPEAR in all copies of the script!
26
 ***************************************************************/
27
28
use ApacheSolrForTypo3\Solr\Domain\Search\Query\ParameterBuilder\QueryFields;
29
use ApacheSolrForTypo3\Solr\Domain\Search\SearchRequest;
30
use ApacheSolrForTypo3\Solr\Domain\Search\SearchRequestAware;
31
use ApacheSolrForTypo3\Solr\Query;
32
use ApacheSolrForTypo3\Solr\Query\Modifier\Modifier;
33
use ApacheSolrForTypo3\Solr\Search;
34
use ApacheSolrForTypo3\Solr\Search\QueryAware;
35
use ApacheSolrForTypo3\Solr\Search\SearchAware;
36
use ApacheSolrForTypo3\Solr\Search\SearchComponentManager;
37
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
38
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
39
use TYPO3\CMS\Core\Utility\GeneralUtility;
40
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
41
use Apache_Solr_ParserException;
42
43
/**
44
 * The SearchResultSetService is responsible to build a SearchResultSet from a SearchRequest.
45
 * It encapsulates the logic to trigger a search in order to be able to reuse it in multiple places.
46
 *
47
 * @author Timo Schmidt <[email protected]>
48
 */
49
class SearchResultSetService
50
{
51
    /**
52
     * Additional filters, which will be added to the query, as well as to
53
     * suggest queries.
54
     *
55
     * @var array
56
     */
57
    protected $additionalFilters = [];
58
59
    /**
60
     * Track, if the number of results per page has been changed by the current request
61
     *
62
     * @var bool
63
     */
64
    protected $resultsPerPageChanged = false;
65
66
    /**
67
     * @var \ApacheSolrForTypo3\Solr\Search
68
     */
69
    protected $search;
70
71
    /**
72
     * @var SearchResultSet
73
     */
74
    protected $lastResultSet = null;
75
76
    /**
77
     * @var
78
     */
79
    protected $isSolrAvailable = false;
80
81
    /**
82
     * @var TypoScriptConfiguration
83
     */
84
    protected $typoScriptConfiguration;
85
86
    /**
87
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
88
     */
89
    protected $logger = null;
90
91
    /**
92
     * @param TypoScriptConfiguration $configuration
93
     * @param Search $search
94
     * @param SolrLogManager $solrLogManager
95
     */
96
    public function __construct(TypoScriptConfiguration $configuration, Search $search, SolrLogManager $solrLogManager = null)
97
    {
98
        $this->search = $search;
99
        $this->typoScriptConfiguration = $configuration;
100
        $this->logger = is_null($solrLogManager) ? GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__) : $solrLogManager;
101
    }
102
103 46
    /**
104
     * @param bool $useCache
105 46
     * @return bool
106 46
     */
107 46
    public function getIsSolrAvailable($useCache = true)
108 46
    {
109
        $this->isSolrAvailable = $this->search->ping($useCache);
110
        return $this->isSolrAvailable;
111
    }
112
113
    /**
114 30
     * @return bool
115
     */
116 30
    public function getHasSearched()
117 30
    {
118
        return $this->search->hasSearched();
119
    }
120
121
    /**
122
     * Retrieves the used search instance.
123 30
     *
124
     * @return Search
125 30
     */
126
    public function getSearch()
127
    {
128
        return $this->search;
129
    }
130
131
    /**
132
     * Initializes the Query object and SearchComponents and returns
133 2
     * the initialized query object, when a search should be executed.
134
     *
135 2
     * @param string|null $rawQuery
136
     * @param int $resultsPerPage
137
     * @return Query
138
     */
139
    protected function getPreparedQuery($rawQuery, $resultsPerPage)
140
    {
141
        /* @var $query Query */
142
        $query = $this->getQueryInstance($rawQuery);
143
144
        $this->applyPageSectionsRootLineFilter($query);
145
146
        if ($this->typoScriptConfiguration->getLoggingQuerySearchWords()) {
147
            $this->logger->log(
148
                SolrLogManager::INFO,
149
                'Received search query',
150
                [
151
                    $rawQuery
152
                ]
153
            );
154
        }
155
156
        $query->setResultsPerPage($resultsPerPage);
157
158
        if ($this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) {
159
            // empty main query, but using a "return everything"
160
            // alternative query in q.alt
161
            $query->setAlternativeQuery('*:*');
162 38
        }
163
164
        if ($this->typoScriptConfiguration->getSearchInitializeWithQuery()) {
165 38
            $query->setAlternativeQuery($this->typoScriptConfiguration->getSearchInitializeWithQuery());
166
        }
167 38
168
        foreach ($this->getAdditionalFilters() as $additionalFilter) {
169 38
            $query->getFilters()->add($additionalFilter);
170
        }
171
172
        return $query;
173
    }
174
175
    /**
176
     * @param Query $query
177
     * @param SearchRequest $searchRequest
178
     */
179 38
    protected function initializeRegisteredSearchComponents(Query $query, SearchRequest $searchRequest)
180
    {
181 38
        $searchComponents = $this->getRegisteredSearchComponents();
182
183
        foreach ($searchComponents as $searchComponent) {
184 31
            /** @var Search\SearchComponent $searchComponent */
185
            $searchComponent->setSearchConfiguration($this->typoScriptConfiguration->getSearchConfiguration());
186
187 38
            if ($searchComponent instanceof QueryAware) {
188 3
                $searchComponent->setQuery($query);
189
            }
190
191 38
            if ($searchComponent instanceof SearchRequestAware) {
192 2
                $searchComponent->setSearchRequest($searchRequest);
193
            }
194
195 38
            $searchComponent->initializeSearchComponent();
196
        }
197
    }
198
199
    /**
200
     * Returns the number of results per Page.
201
     *
202 38
     * Also influences how many result documents are returned by the Solr
203
     * server as the return value is used in the Solr "rows" GET parameter.
204 38
     *
205
     * @param SearchRequest $searchRequest
206 38
     * @return int number of results to show per page
207
     */
208 32
    protected function getNumberOfResultsPerPage(SearchRequest $searchRequest)
209
    {
210 32
        $requestedPerPage = $searchRequest->getResultsPerPage();
211 32
        $perPageSwitchOptions = $this->typoScriptConfiguration->getSearchResultsPerPageSwitchOptionsAsArray();
212
        if (isset($requestedPerPage) && in_array($requestedPerPage, $perPageSwitchOptions)) {
213
            $this->setPerPageInSession($requestedPerPage);
214 32
            $this->resultsPerPageChanged = true;
215 31
        }
216
217
        $defaultResultsPerPage = $this->typoScriptConfiguration->getSearchResultsPerPage();
218 32
        $sessionResultPerPage = $this->getPerPageFromSession();
219
220 38
        $currentNumberOfResultsShown = $defaultResultsPerPage;
221
        if (!is_null($sessionResultPerPage) && in_array($sessionResultPerPage, $perPageSwitchOptions)) {
222
            $currentNumberOfResultsShown = (int)$sessionResultPerPage;
223
        }
224
225
        if ($this->shouldHideResultsFromInitialSearch($searchRequest)) {
226
            // initialize search with an empty query, which would by default return all documents
227
            // anyway, tell Solr to not return any result documents
228
            // Solr will still return facets though
229
            $currentNumberOfResultsShown = 0;
230
        }
231 38
232
        return $currentNumberOfResultsShown;
233 38
    }
234 38
235 38
    /**
236 4
     * Does post processing of the response.
237 4
     *
238
     * @param \Apache_Solr_Response $response The search's response.
239
     */
240 38
    protected function processResponse(\Apache_Solr_Response $response)
241 38
    {
242
        $this->wrapResultDocumentInResultObject($response);
243 38
        $this->addExpandedDocumentsFromVariants($response);
244 38
    }
245 3
246
    /**
247
     * This method is used to add documents to the expanded documents of the SearchResult
248 38
     * when collapsing is configured.
249
     *
250
     * @param \Apache_Solr_Response $response
251
     */
252 2
    protected function addExpandedDocumentsFromVariants(\Apache_Solr_Response $response)
253
    {
254
        if (!is_array($response->response->docs)) {
0 ignored issues
show
Bug introduced by
The property response does not seem to exist. Did you mean _response?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
255 38
            return;
256
        }
257
258
        if (!$this->typoScriptConfiguration->getSearchVariants()) {
259
            return;
260
        }
261
262
        $variantsField = $this->typoScriptConfiguration->getSearchVariantsField();
263
        foreach ($response->response->docs as $key => $resultDocument) {
0 ignored issues
show
Bug introduced by
The property response does not seem to exist. Did you mean _response?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
264 40
            /** @var $resultDocument SearchResult */
265
            $variantField = $resultDocument->getField($variantsField);
266 40
            $variantId = isset($variantField['value']) ? $variantField['value'] : null;
267 40
268
                // when there is no value in the collapsing field, we can return
269 40
            if ($variantId === null) {
270 40
                continue;
271
            }
272
273
            $variantAccessKey = mb_strtolower($variantId);
274
            if (!isset($response->{'expanded'}) || !isset($response->{'expanded'}->{$variantAccessKey})) {
275
                continue;
276
            }
277
278
            foreach ($response->{'expanded'}->{$variantAccessKey}->{'docs'} as $variantDocumentArray) {
279 40
                $variantDocument = new \Apache_Solr_Document();
280
                foreach (get_object_vars($variantDocumentArray) as $propertyName => $propertyValue) {
281 40
                    $variantDocument->{$propertyName} = $propertyValue;
282 1
                }
283
                $variantSearchResult = $this->wrapApacheSolrDocumentInResultObject($variantDocument);
284 1
                $variantSearchResult->setIsVariant(true);
285 1
                $variantSearchResult->setVariantParent($resultDocument);
286 1
287 1
                $resultDocument->addVariant($variantSearchResult);
288
            }
289
        }
290
    }
291 40
292
    /**
293
     * Wrap all results document it a custom EXT:solr SearchResult object.
294
     *
295
     * Can be overwritten:
296
     *
297
     * $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultClassName '] = ''
298
     *
299 40
     * to use a custom result object.
300
     *
301 40
     * @param \Apache_Solr_Response $response
302 5
     * @throws \Apache_Solr_ParserException
303
     */
304
    protected function wrapResultDocumentInResultObject(\Apache_Solr_Response $response)
305 35
    {
306 32
        try {
307
            $documents = $response->response->docs;
0 ignored issues
show
Bug introduced by
The property response does not seem to exist. Did you mean _response?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
308
        } catch (Apache_Solr_ParserException $e) {
0 ignored issues
show
Unused Code introduced by
catch (\Apache_Solr_Pars...$documents = array(); } does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
309 3
            // when variant are enable and the index is empty, we get a parse exception, because of a
310 3
            // Apache Solr Bug.
311
            // see: https://github.com/TYPO3-Solr/ext-solr/issues/668
312 2
            // @todo this try/catch block can be removed after upgrading to Apache Solr 6.4
313 2
            if (!$this->typoScriptConfiguration->getSearchVariants()) {
314
                throw $e;
315
            }
316 2
317
            $response->response = new \stdClass();
318
            $response->spellcheck = [];
319
            $response->debug = [];
320 2
            $response->responseHeader = [];
321 2
            $response->facet_counts = [];
322
323
            $documents = [];
324
        }
325 2
326 2
        if (!is_array($documents)) {
327 2
            return;
328 2
        }
329
330 2
        foreach ($documents as $key => $originalDocument) {
331 2
            $result = $this->wrapApacheSolrDocumentInResultObject($originalDocument);
332 2
            $documents[$key] = $result;
333
        }
334 2
335
        $response->response->docs = $documents;
0 ignored issues
show
Bug introduced by
The property response does not seem to exist. Did you mean _response?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
336
    }
337 3
338
    /**
339
     * This method is used to wrap the \Apache_Solr_Document instance in an instance of the configured SearchResult
340
     * class.
341
     *
342
     * @param \Apache_Solr_Document $originalDocument
343
     * @throws \InvalidArgumentException
344
     * @return SearchResult
345
     */
346
    protected function wrapApacheSolrDocumentInResultObject(\Apache_Solr_Document $originalDocument)
347
    {
348
        $searchResultClassName = $this->getResultClassName();
349
        $result = GeneralUtility::makeInstance($searchResultClassName, $originalDocument);
350
        if (!$result instanceof SearchResult) {
351 40
            throw new \InvalidArgumentException('Could not create result object with class: ' . (string)$searchResultClassName, 1470037679);
352
        }
353
354 40
        return $result;
355 1
    }
356
357
    /**
358
     * @return string
359
     */
360 1
    protected function getResultClassName()
361
    {
362
        return isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultClassName ']) ?
363
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultClassName '] : SearchResult::class;
364 1
    }
365 1
366 1
    /**
367 1
     * @return string
368 1
     */
369
    protected function getResultSetClassName()
370 1
    {
371
        return isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultSetClassName ']) ?
372
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultSetClassName '] : SearchResultSet::class;
373 40
    }
374 5
375
    /**
376
     * Checks it the results should be hidden in the response.
377 35
     *
378 29
     * @param SearchRequest $searchRequest
379 29
     * @return bool
380
     */
381
    protected function shouldHideResultsFromInitialSearch(SearchRequest $searchRequest)
382 35
    {
383 35
        return ($this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchInitializeWithQuery()) && !$this->typoScriptConfiguration->getSearchShowResultsOfInitialEmptyQuery() && !$this->typoScriptConfiguration->getSearchShowResultsOfInitialQuery() && $searchRequest->getRawUserQueryIsNull();
384
    }
385
386
    /**
387
     * Initializes additional filters configured through TypoScript and
388
     * Flexforms for use in regular queries and suggest queries.
389
     *
390
     * @param Query $query
391
     * @return void
392
     */
393 29
    protected function applyPageSectionsRootLineFilter(Query $query)
394
    {
395 29
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
396 29
        if (count($searchQueryFilters) <= 0) {
397 29
            return;
398
        }
399
400
        // special filter to limit search to specific page tree branches
401 29
        if (array_key_exists('__pageSections', $searchQueryFilters)) {
402
            $query->setRootlineFilter($searchQueryFilters['__pageSections']);
403
            $this->typoScriptConfiguration->removeSearchQueryFilterForPageSections();
404
        }
405
    }
406
407 29
    /**
408
     * Retrieves the configuration filters from the TypoScript configuration, except the __pageSections filter.
409 29
     *
410 29
     * @return array
411
     */
412
    public function getAdditionalFilters()
413
    {
414
        // when we've build the additionalFilter once, we could return them
415
        if (count($this->additionalFilters) > 0) {
416 40
            return $this->additionalFilters;
417
        }
418 40
419 40
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
420
        if (count($searchQueryFilters) <= 0) {
421
            return [];
422
        }
423
424
        $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
425
426
        // all other regular filters
427
        foreach ($searchQueryFilters as $filterKey => $filter) {
428 38
            // the __pageSections filter should not be handled as additional filter
429
            if ($filterKey === '__pageSections') {
430 38
                continue;
431
            }
432
433
            $filterIsArray = is_array($searchQueryFilters[$filterKey]);
434
            if ($filterIsArray) {
435
                continue;
436
            }
437
438
            $hasSubConfiguration = is_array($searchQueryFilters[$filterKey . '.']);
439
            if ($hasSubConfiguration) {
440 38
                $filter = $cObj->stdWrap($searchQueryFilters[$filterKey], $searchQueryFilters[$filterKey . '.']);
441
            }
442 38
443 38
            $this->additionalFilters[$filterKey] = $filter;
444 36
        }
445
446
        return $this->additionalFilters;
447
    }
448 2
449
    /**
450
     * Performs a search and returns a SearchResultSet.
451
     *
452 2
     * @param SearchRequest $searchRequest
453
     * @return SearchResultSet
454
     */
455
    public function search(SearchRequest $searchRequest)
456
    {
457
        /** @var $resultSet SearchResultSet */
458
        $resultSetClass = $this->getResultSetClassName();
459 43
        $resultSet = GeneralUtility::makeInstance($resultSetClass);
460
        $resultSet->setUsedSearchRequest($searchRequest);
461
        $this->lastResultSet = $resultSet;
462 43
463 2
        $resultSet = $this->handleSearchHook('beforeSearch', $resultSet);
464
465
        if ($searchRequest->getRawUserQueryIsNull() && !$this->getInitialSearchIsConfigured()) {
466 43
            // when no rawQuery was passed or no initialSearch is configured, we pass an empty result set
467 43
            return $resultSet;
468 41
        }
469
470
        if ($searchRequest->getRawUserQueryIsEmptyString() && !$this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) {
471 2
            // the user entered an empty query string "" or "  " and empty querystring is not allowed
472
            return $resultSet;
473
        }
474 2
475
        $rawQuery = $searchRequest->getRawUserQuery();
476 2
        $resultsPerPage = $this->getNumberOfResultsPerPage($searchRequest);
477
        $query = $this->getPreparedQuery($rawQuery, $resultsPerPage);
478
        $this->initializeRegisteredSearchComponents($query, $searchRequest);
479
        $resultSet->setUsedQuery($query);
480 2
481 2
        $currentPage = max(0, $searchRequest->getPage());
482
        // if the number of results per page has been changed by the current request, reset the pagebrowser
483
        if ($this->resultsPerPageChanged) {
484
            $currentPage = 0;
485 2
        }
486 2
487
        $offSet = $currentPage * $resultsPerPage;
488
489
        // performing the actual search, sending the query to the Solr server
490 2
        $query = $this->modifyQuery($query, $searchRequest, $this->search);
491
        $response = $this->search->search($query, $offSet, null);
492
493 2
        if ($resultsPerPage === 0) {
494
            // when resultPerPage was forced to 0 we also set the numFound to 0 to hide results, e.g.
495
            // when results for the initial search should not be shown.
496
            $response->response->numFound = 0;
0 ignored issues
show
Bug introduced by
The property response does not seem to exist. Did you mean _response?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
497
        }
498
499
        $this->processResponse($response);
500
501
        $this->addSearchResultsToResultSet($response, $resultSet);
502 40
503
        $resultSet->setResponse($response);
504
        $resultSet->setUsedPage($currentPage);
505 40
        $resultSet->setUsedResultsPerPage($resultsPerPage);
506 40
        $resultSet->setUsedAdditionalFilters($this->getAdditionalFilters());
507 40
        $resultSet->setUsedSearch($this->search);
508 40
509
        /** @var $searchResultReconstitutionProcessor ResultSetReconstitutionProcessor */
510 40
        $searchResultReconstitutionProcessor = GeneralUtility::makeInstance(ResultSetReconstitutionProcessor::class);
511
        $searchResultReconstitutionProcessor->process($resultSet);
512 40
513
        $resultSet = $this->getAutoCorrection($resultSet);
514 2
515
        return $this->handleSearchHook('afterSearch', $resultSet);
516
    }
517 38
518
    /**
519
     * @param SearchResultSet $searchResultSet
520
     * @return SearchResultSet
521
     */
522 38
    protected function getAutoCorrection(SearchResultSet $searchResultSet)
523 38
    {
524 38
        // no secondary search configured
525 38
        if (!$this->typoScriptConfiguration->getSearchSpellcheckingSearchUsingSpellCheckerSuggestion()) {
526 38
            return $searchResultSet;
527
        }
528 38
529
        // more then zero results
530 38
        if ($searchResultSet->getAllResultCount() > 0) {
531 4
            return $searchResultSet;
532
        }
533
534 38
        // no corrections present
535
        if (!$searchResultSet->getHasSpellCheckingSuggestions()) {
536
            return $searchResultSet;
537 38
        }
538 38
539 38
        $searchResultSet = $this->peformAutoCorrection($searchResultSet);
540
541 38
        return $searchResultSet;
542
    }
543
544 2
    /**
545
     * @param SearchResultSet $searchResultSet
546
     * @return SearchResultSet
547 38
     */
548
    protected function peformAutoCorrection(SearchResultSet $searchResultSet)
549 38
    {
550
        $searchRequest = $searchResultSet->getUsedSearchRequest();
551 38
        $suggestions = $searchResultSet->getSpellCheckingSuggestions();
552 38
553 38
        $maximumRuns = $this->typoScriptConfiguration->getSearchSpellcheckingNumberOfSuggestionsToTry(1);
554 38
        $runs = 0;
555 38
556
        foreach ($suggestions as $suggestion) {
557
            $runs++;
558 38
559 38
            $correction = $suggestion->getSuggestion();
560
            $initialQuery = $searchRequest->getRawUserQuery();
561 38
562
            $searchRequest->setRawQueryString($correction);
563 38
            $searchResultSet = $this->search($searchRequest);
564
            if ($searchResultSet->getAllResultCount() > 0) {
565
                $searchResultSet->setIsAutoCorrected(true);
566
                $searchResultSet->setCorrectedQueryString($correction);
567
                $searchResultSet->setInitialQueryString($initialQuery);
568
                break;
569
            }
570 38
571
            if ($runs > $maximumRuns) {
572
                break;
573 38
            }
574 37
        }
575
        return $searchResultSet;
576
    }
577
578 1
    /**
579 1
     * Allows to modify a query before eventually handing it over to Solr.
580
     *
581
     * @param Query $query The current query before it's being handed over to Solr.
582
     * @param SearchRequest $searchRequest The searchRequest, relevant in the current context
583 1
     * @param Search $search The search, relevant in the current context
584
     * @throws \UnexpectedValueException
585
     * @return Query The modified query that is actually going to be given to Solr.
586
     */
587 1
    protected function modifyQuery(Query $query, SearchRequest $searchRequest, Search $search)
588
    {
589 1
        // hook to modify the search query
590
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'])) {
591
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'] as $classReference) {
592
                $queryModifier = GeneralUtility::getUserObj($classReference);
593
594
                if ($queryModifier instanceof Modifier) {
595
                    if ($queryModifier instanceof SearchAware) {
596 1
                        $queryModifier->setSearch($search);
597
                    }
598 1
599 1
                    if ($queryModifier instanceof SearchRequestAware) {
600
                        $queryModifier->setSearchRequest($searchRequest);
601 1
                    }
602 1
603
                    $query = $queryModifier->modifyQuery($query);
604 1
                } else {
605 1
                    throw new \UnexpectedValueException(
606
                        get_class($queryModifier) . ' must implement interface ' . Modifier::class,
607 1
                        1310387414
608 1
                    );
609
                }
610 1
            }
611 1
        }
612 1
613 1
        return $query;
614 1
    }
615 1
616 1
    /**
617
     * Retrieves a single document from solr by document id.
618
     *
619
     * @param string $documentId
620
     * @return SearchResult
621
     */
622
    public function getDocumentById($documentId)
623 1
    {
624
        /* @var $query Query */
625
        $query = GeneralUtility::makeInstance(Query::class, $documentId);
626
        $query->setQueryFields(QueryFields::fromString('id'));
627
628
        $response = $this->search->search($query, 0, 1);
629
        $this->processResponse($response);
630
631
        $resultDocument = isset($response->response->docs[0]) ? $response->response->docs[0] : null;
0 ignored issues
show
Bug introduced by
The property response does not seem to exist. Did you mean _response?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
632
        return $resultDocument;
633
    }
634
635 38
    /**
636
     * This method is used to call the registered hooks during the search execution.
637
     *
638 38
     * @param string $eventName
639 31
     * @param SearchResultSet $resultSet
640 31
     * @return SearchResultSet
641
     */
642 31
    private function handleSearchHook($eventName, SearchResultSet $resultSet)
643 31
    {
644
        if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr'][$eventName])) {
645
            return $resultSet;
646
        }
647 31
648 31
        foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr'][$eventName] as $classReference) {
649
            $afterSearchProcessor = GeneralUtility::getUserObj($classReference);
650
            if ($afterSearchProcessor instanceof SearchResultSetProcessor) {
651 31
                $afterSearchProcessor->process($resultSet);
652
            }
653
        }
654
655 31
        return $resultSet;
656
    }
657
658
    /**
659
     * @return SearchResultSet
660
     */
661 38
    public function getLastResultSet()
662
    {
663
        return $this->lastResultSet;
664
    }
665
666
    /**
667
     * This method returns true when the last search was executed with an empty query
668
     * string or whitespaces only. When no search was triggered it will return false.
669
     *
670
     * @return bool
671
     */
672
    public function getLastSearchWasExecutedWithEmptyQueryString()
673
    {
674
        $wasEmptyQueryString = false;
675 38
        if ($this->lastResultSet != null) {
676
            $wasEmptyQueryString = $this->lastResultSet->getUsedSearchRequest()->getRawUserQueryIsEmptyString();
677
        }
678 38
679
        return $wasEmptyQueryString;
680
    }
681
682
    /**
683
     * @param int $requestedPerPage
684
     */
685
    protected function setPerPageInSession($requestedPerPage)
686
    {
687
        $GLOBALS['TSFE']->fe_user->setKey('ses', 'tx_solr_resultsPerPage', intval($requestedPerPage));
688
    }
689
690
    /**
691
     * @return mixed
692
     */
693
    protected function getPerPageFromSession()
694
    {
695
        return $GLOBALS['TSFE']->fe_user->getKey('ses', 'tx_solr_resultsPerPage');
696
    }
697
698
    /**
699
     * @return bool
700
     */
701
    protected function getInitialSearchIsConfigured()
702
    {
703
        return $this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialEmptyQuery() || $this->typoScriptConfiguration->getSearchInitializeWithQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialQuery();
704 38
    }
705
706
    /**
707
     * @return mixed
708
     */
709
    protected function getRegisteredSearchComponents()
710
    {
711
        return GeneralUtility::makeInstance(SearchComponentManager::class)->getSearchComponents();
712 2
    }
713
714
    /**
715 2
     * This method is used to reference the SearchResult object from the response in the SearchResultSet object.
716 2
     *
717
     * @param \Apache_Solr_Response $response
718 2
     * @param SearchResultSet $resultSet
719 2
     */
720
    protected function addSearchResultsToResultSet($response, $resultSet)
721 2
    {
722 2
        if (!is_array($response->response->docs)) {
0 ignored issues
show
Bug introduced by
The property response does not seem to exist. Did you mean _response?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
723
            return;
724
        }
725
726
        foreach ($response->response->docs as $searchResult) {
0 ignored issues
show
Bug introduced by
The property response does not seem to exist. Did you mean _response?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
727
            $resultSet->addSearchResult($searchResult);
728
        }
729
    }
730
731
    /**
732 40
     * @param string $rawQuery
733
     * @return Query|object
734 40
     */
735 40
    protected function getQueryInstance($rawQuery)
736
    {
737
        $query = GeneralUtility::makeInstance(Query::class, $rawQuery, $this->typoScriptConfiguration);
738 27
        return $query;
739 27
    }
740
}
741