Passed
Push — master ( aa570e...188b42 )
by Timo
22:45 queued 03:50
created

  A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 8
cts 8
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 4
nop 2
crap 4
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 bool
78
     */
79
    protected $useQueryAwareComponents = true;
80
81
    /**
82
     * @var
83
     */
84
    protected $isSolrAvailable = false;
85
86
    /**
87
     * @var TypoScriptConfiguration
88
     */
89
    protected $typoScriptConfiguration;
90
91
    /**
92
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
93
     */
94
    protected $logger = null;
95
96
    /**
97
     * @param TypoScriptConfiguration $configuration
98
     * @param Search $search
99
     * @param SolrLogManager $solrLogManager
100
     */
101
    public function __construct(TypoScriptConfiguration $configuration, Search $search, SolrLogManager $solrLogManager = null)
102
    {
103 46
        $this->search = $search;
104
        $this->typoScriptConfiguration = $configuration;
105 46
        $this->logger = is_null($solrLogManager) ? GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__) : $solrLogManager;
106 46
    }
107 46
108 46
    /**
109
     * @param bool $useCache
110
     * @return bool
111
     */
112
    public function getIsSolrAvailable($useCache = true)
113
    {
114 30
        $this->isSolrAvailable = $this->search->ping($useCache);
115
        return $this->isSolrAvailable;
116 30
    }
117 30
118
    /**
119
     * @return bool
120
     */
121
    public function getHasSearched()
122
    {
123 30
        return $this->search->hasSearched();
124
    }
125 30
126
    /**
127
     * Retrieves the used search instance.
128
     *
129
     * @return Search
130
     */
131
    public function getSearch()
132
    {
133 2
        return $this->search;
134
    }
135 2
136
    /**
137
     * @param bool $useQueryAwareComponents
138
     */
139
    public function setUseQueryAwareComponents($useQueryAwareComponents)
140
    {
141
        $this->useQueryAwareComponents = $useQueryAwareComponents;
142
    }
143
144
    /**
145
     * @return bool
146
     */
147
    public function getUseQueryAwareComponents()
148
    {
149
        return $this->useQueryAwareComponents;
150
    }
151
152
    /**
153
     * Initializes the Query object and SearchComponents and returns
154
     * the initialized query object, when a search should be executed.
155
     *
156
     * @param string|null $rawQuery
157
     * @param int $resultsPerPage
158
     * @return Query
159
     */
160
    protected function getPreparedQuery($rawQuery, $resultsPerPage)
161
    {
162 38
        /* @var $query Query */
163
        $query = $this->getQueryInstance($rawQuery);
164
165 38
        $this->applyPageSectionsRootLineFilter($query);
166
167 38
        if ($this->typoScriptConfiguration->getLoggingQuerySearchWords()) {
168
            $this->logger->log(
169 38
                SolrLogManager::INFO,
170
                'Received search query',
171
                [
172
                    $rawQuery
173
                ]
174
            );
175
        }
176
177
        $query->setResultsPerPage($resultsPerPage);
178
179 38
        if ($this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) {
180
            // empty main query, but using a "return everything"
181 38
            // alternative query in q.alt
182
            $query->setAlternativeQuery('*:*');
183
        }
184 31
185
        if ($this->typoScriptConfiguration->getSearchInitializeWithQuery()) {
186
            $query->setAlternativeQuery($this->typoScriptConfiguration->getSearchInitializeWithQuery());
187 38
        }
188 3
189
        foreach ($this->getAdditionalFilters() as $additionalFilter) {
190
            $query->getFilters()->add($additionalFilter);
191 38
        }
192 2
193
        return $query;
194
    }
195 38
196
    /**
197
     * @param Query $query
198
     * @param SearchRequest $searchRequest
199
     */
200
    protected function initializeRegisteredSearchComponents(Query $query, SearchRequest $searchRequest)
201
    {
202 38
        $searchComponents = $this->getRegisteredSearchComponents();
203
204 38
        foreach ($searchComponents as $searchComponent) {
205
            /** @var Search\SearchComponent $searchComponent */
206 38
            $searchComponent->setSearchConfiguration($this->typoScriptConfiguration->getSearchConfiguration());
207
208 32
            if ($searchComponent instanceof QueryAware && $this->useQueryAwareComponents) {
209
                $searchComponent->setQuery($query);
210 32
            }
211 32
212
            if ($searchComponent instanceof SearchRequestAware) {
213
                $searchComponent->setSearchRequest($searchRequest);
214 32
            }
215 31
216
            $searchComponent->initializeSearchComponent();
217
        }
218 32
    }
219
220 38
    /**
221
     * Returns the number of results per Page.
222
     *
223
     * Also influences how many result documents are returned by the Solr
224
     * server as the return value is used in the Solr "rows" GET parameter.
225
     *
226
     * @param SearchRequest $searchRequest
227
     * @return int number of results to show per page
228
     */
229
    protected function getNumberOfResultsPerPage(SearchRequest $searchRequest)
230
    {
231 38
        $requestedPerPage = $searchRequest->getResultsPerPage();
232
        $perPageSwitchOptions = $this->typoScriptConfiguration->getSearchResultsPerPageSwitchOptionsAsArray();
233 38
        if (isset($requestedPerPage) && in_array($requestedPerPage, $perPageSwitchOptions)) {
234 38
            $this->setPerPageInSession($requestedPerPage);
235 38
            $this->resultsPerPageChanged = true;
236 4
        }
237 4
238
        $defaultResultsPerPage = $this->typoScriptConfiguration->getSearchResultsPerPage();
239
        $sessionResultPerPage = $this->getPerPageFromSession();
240 38
241 38
        $currentNumberOfResultsShown = $defaultResultsPerPage;
242
        if (!is_null($sessionResultPerPage) && in_array($sessionResultPerPage, $perPageSwitchOptions)) {
243 38
            $currentNumberOfResultsShown = (int)$sessionResultPerPage;
244 38
        }
245 3
246
        if ($this->shouldHideResultsFromInitialSearch($searchRequest)) {
247
            // initialize search with an empty query, which would by default return all documents
248 38
            // anyway, tell Solr to not return any result documents
249
            // Solr will still return facets though
250
            $currentNumberOfResultsShown = 0;
251
        }
252 2
253
        return $currentNumberOfResultsShown;
254
    }
255 38
256
    /**
257
     * Does post processing of the response.
258
     *
259
     * @param \Apache_Solr_Response $response The search's response.
260
     */
261
    protected function processResponse(\Apache_Solr_Response $response)
262
    {
263
        $this->wrapResultDocumentInResultObject($response);
264 40
        $this->addExpandedDocumentsFromVariants($response);
265
    }
266 40
267 40
    /**
268
     * This method is used to add documents to the expanded documents of the SearchResult
269 40
     * when collapsing is configured.
270 40
     *
271
     * @param \Apache_Solr_Response $response
272
     */
273
    protected function addExpandedDocumentsFromVariants(\Apache_Solr_Response $response)
274
    {
275
        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...
276
            return;
277
        }
278
279 40
        if (!$this->typoScriptConfiguration->getSearchVariants()) {
280
            return;
281 40
        }
282 1
283
        $variantsField = $this->typoScriptConfiguration->getSearchVariantsField();
284 1
        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...
285 1
            /** @var $resultDocument SearchResult */
286 1
            $variantField = $resultDocument->getField($variantsField);
287 1
            $variantId = isset($variantField['value']) ? $variantField['value'] : null;
288
289
                // when there is no value in the collapsing field, we can return
290
            if ($variantId === null) {
291 40
                continue;
292
            }
293
294
            $variantAccessKey = mb_strtolower($variantId);
295
            if (!isset($response->{'expanded'}) || !isset($response->{'expanded'}->{$variantAccessKey})) {
296
                continue;
297
            }
298
299 40
            foreach ($response->{'expanded'}->{$variantAccessKey}->{'docs'} as $variantDocumentArray) {
300
                $variantDocument = new \Apache_Solr_Document();
301 40
                foreach (get_object_vars($variantDocumentArray) as $propertyName => $propertyValue) {
302 5
                    $variantDocument->{$propertyName} = $propertyValue;
303
                }
304
                $variantSearchResult = $this->wrapApacheSolrDocumentInResultObject($variantDocument);
305 35
                $variantSearchResult->setIsVariant(true);
306 32
                $variantSearchResult->setVariantParent($resultDocument);
307
308
                $resultDocument->addVariant($variantSearchResult);
309 3
            }
310 3
        }
311
    }
312 2
313 2
    /**
314
     * Wrap all results document it a custom EXT:solr SearchResult object.
315
     *
316 2
     * Can be overwritten:
317
     *
318
     * $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultClassName '] = ''
319
     *
320 2
     * to use a custom result object.
321 2
     *
322
     * @param \Apache_Solr_Response $response
323
     * @throws \Apache_Solr_ParserException
324
     */
325 2
    protected function wrapResultDocumentInResultObject(\Apache_Solr_Response $response)
326 2
    {
327 2
        try {
328 2
            $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...
329
        } 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...
330 2
            // when variant are enable and the index is empty, we get a parse exception, because of a
331 2
            // Apache Solr Bug.
332 2
            // see: https://github.com/TYPO3-Solr/ext-solr/issues/668
333
            // @todo this try/catch block can be removed after upgrading to Apache Solr 6.4
334 2
            if (!$this->typoScriptConfiguration->getSearchVariants()) {
335
                throw $e;
336
            }
337 3
338
            $response->response = new \stdClass();
339
            $response->spellcheck = [];
340
            $response->debug = [];
341
            $response->responseHeader = [];
342
            $response->facet_counts = [];
343
344
            $documents = [];
345
        }
346
347
        if (!is_array($documents)) {
348
            return;
349
        }
350
351 40
        foreach ($documents as $key => $originalDocument) {
352
            $result = $this->wrapApacheSolrDocumentInResultObject($originalDocument);
353
            $documents[$key] = $result;
354 40
        }
355 1
356
        $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...
357
    }
358
359
    /**
360 1
     * This method is used to wrap the \Apache_Solr_Document instance in an instance of the configured SearchResult
361
     * class.
362
     *
363
     * @param \Apache_Solr_Document $originalDocument
364 1
     * @throws \InvalidArgumentException
365 1
     * @return SearchResult
366 1
     */
367 1
    protected function wrapApacheSolrDocumentInResultObject(\Apache_Solr_Document $originalDocument)
368 1
    {
369
        $searchResultClassName = $this->getResultClassName();
370 1
        $result = GeneralUtility::makeInstance($searchResultClassName, $originalDocument);
371
        if (!$result instanceof SearchResult) {
372
            throw new \InvalidArgumentException('Could not create result object with class: ' . (string)$searchResultClassName, 1470037679);
373 40
        }
374 5
375
        return $result;
376
    }
377 35
378 29
    /**
379 29
     * @return string
380
     */
381
    protected function getResultClassName()
382 35
    {
383 35
        return isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultClassName ']) ?
384
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultClassName '] : SearchResult::class;
385
    }
386
387
    /**
388
     * @return string
389
     */
390
    protected function getResultSetClassName()
391
    {
392
        return isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultSetClassName ']) ?
393 29
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultSetClassName '] : SearchResultSet::class;
394
    }
395 29
396 29
    /**
397 29
     * Checks it the results should be hidden in the response.
398
     *
399
     * @param SearchRequest $searchRequest
400
     * @return bool
401 29
     */
402
    protected function shouldHideResultsFromInitialSearch(SearchRequest $searchRequest)
403
    {
404
        return ($this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchInitializeWithQuery()) && !$this->typoScriptConfiguration->getSearchShowResultsOfInitialEmptyQuery() && !$this->typoScriptConfiguration->getSearchShowResultsOfInitialQuery() && $searchRequest->getRawUserQueryIsNull();
405
    }
406
407 29
    /**
408
     * Initializes additional filters configured through TypoScript and
409 29
     * Flexforms for use in regular queries and suggest queries.
410 29
     *
411
     * @param Query $query
412
     * @return void
413
     */
414
    protected function applyPageSectionsRootLineFilter(Query $query)
415
    {
416 40
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
417
        if (count($searchQueryFilters) <= 0) {
418 40
            return;
419 40
        }
420
421
        // special filter to limit search to specific page tree branches
422
        if (array_key_exists('__pageSections', $searchQueryFilters)) {
423
            $query->setRootlineFilter($searchQueryFilters['__pageSections']);
424
            $this->typoScriptConfiguration->removeSearchQueryFilterForPageSections();
425
        }
426
    }
427
428 38
    /**
429
     * Retrieves the configuration filters from the TypoScript configuration, except the __pageSections filter.
430 38
     *
431
     * @return array
432
     */
433
    public function getAdditionalFilters()
434
    {
435
        // when we've build the additionalFilter once, we could return them
436
        if (count($this->additionalFilters) > 0) {
437
            return $this->additionalFilters;
438
        }
439
440 38
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
441
        if (count($searchQueryFilters) <= 0) {
442 38
            return [];
443 38
        }
444 36
445
        $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
446
447
        // all other regular filters
448 2
        foreach ($searchQueryFilters as $filterKey => $filter) {
449
            // the __pageSections filter should not be handled as additional filter
450
            if ($filterKey === '__pageSections') {
451
                continue;
452 2
            }
453
454
            $filterIsArray = is_array($searchQueryFilters[$filterKey]);
455
            if ($filterIsArray) {
456
                continue;
457
            }
458
459 43
            $hasSubConfiguration = is_array($searchQueryFilters[$filterKey . '.']);
460
            if ($hasSubConfiguration) {
461
                $filter = $cObj->stdWrap($searchQueryFilters[$filterKey], $searchQueryFilters[$filterKey . '.']);
462 43
            }
463 2
464
            $this->additionalFilters[$filterKey] = $filter;
465
        }
466 43
467 43
        return $this->additionalFilters;
468 41
    }
469
470
    /**
471 2
     * Performs a search and returns a SearchResultSet.
472
     *
473
     * @param SearchRequest $searchRequest
474 2
     * @return SearchResultSet
475
     */
476 2
    public function search(SearchRequest $searchRequest)
477
    {
478
        /** @var $resultSet SearchResultSet */
479
        $resultSetClass = $this->getResultSetClassName();
480 2
        $resultSet = GeneralUtility::makeInstance($resultSetClass);
481 2
        $resultSet->setUsedSearchRequest($searchRequest);
482
        $this->lastResultSet = $resultSet;
483
484
        $resultSet = $this->handleSearchHook('beforeSearch', $resultSet);
485 2
486 2
        if ($searchRequest->getRawUserQueryIsNull() && !$this->getInitialSearchIsConfigured()) {
487
            // when no rawQuery was passed or no initialSearch is configured, we pass an empty result set
488
            return $resultSet;
489
        }
490 2
491
        if ($searchRequest->getRawUserQueryIsEmptyString() && !$this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) {
492
            // the user entered an empty query string "" or "  " and empty querystring is not allowed
493 2
            return $resultSet;
494
        }
495
496
        $rawQuery = $searchRequest->getRawUserQuery();
497
        $resultsPerPage = $this->getNumberOfResultsPerPage($searchRequest);
498
        $query = $this->getPreparedQuery($rawQuery, $resultsPerPage);
499
        $this->initializeRegisteredSearchComponents($query, $searchRequest);
500
        $resultSet->setUsedQuery($query);
501
502 40
        $currentPage = max(0, $searchRequest->getPage());
503
        // if the number of results per page has been changed by the current request, reset the pagebrowser
504
        if ($this->resultsPerPageChanged) {
505 40
            $currentPage = 0;
506 40
        }
507 40
508 40
        $offSet = $currentPage * $resultsPerPage;
509
510 40
        // performing the actual search, sending the query to the Solr server
511
        $query = $this->modifyQuery($query, $searchRequest, $this->search);
512 40
        $response = $this->search->search($query, $offSet, null);
513
514 2
        if ($resultsPerPage === 0) {
515
            // when resultPerPage was forced to 0 we also set the numFound to 0 to hide results, e.g.
516
            // when results for the initial search should not be shown.
517 38
            $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...
518
        }
519
520
        $this->processResponse($response);
521
522 38
        $this->addSearchResultsToResultSet($response, $resultSet);
523 38
524 38
        $resultSet->setResponse($response);
525 38
        $resultSet->setUsedPage($currentPage);
526 38
        $resultSet->setUsedResultsPerPage($resultsPerPage);
527
        $resultSet->setUsedAdditionalFilters($this->getAdditionalFilters());
528 38
        $resultSet->setUsedSearch($this->search);
529
530 38
        /** @var $searchResultReconstitutionProcessor ResultSetReconstitutionProcessor */
531 4
        $searchResultReconstitutionProcessor = GeneralUtility::makeInstance(ResultSetReconstitutionProcessor::class);
532
        $searchResultReconstitutionProcessor->process($resultSet);
533
534 38
        $resultSet = $this->getAutoCorrection($resultSet);
535
536
        return $this->handleSearchHook('afterSearch', $resultSet);
537 38
    }
538 38
539 38
    /**
540
     * @param SearchResultSet $searchResultSet
541 38
     * @return SearchResultSet
542
     */
543
    protected function getAutoCorrection(SearchResultSet $searchResultSet)
544 2
    {
545
        // no secondary search configured
546
        if (!$this->typoScriptConfiguration->getSearchSpellcheckingSearchUsingSpellCheckerSuggestion()) {
547 38
            return $searchResultSet;
548
        }
549 38
550
        // more then zero results
551 38
        if ($searchResultSet->getAllResultCount() > 0) {
552 38
            return $searchResultSet;
553 38
        }
554 38
555 38
        // no corrections present
556
        if (!$searchResultSet->getHasSpellCheckingSuggestions()) {
557
            return $searchResultSet;
558 38
        }
559 38
560
        $searchResultSet = $this->peformAutoCorrection($searchResultSet);
561 38
562
        return $searchResultSet;
563 38
    }
564
565
    /**
566
     * @param SearchResultSet $searchResultSet
567
     * @return SearchResultSet
568
     */
569
    protected function peformAutoCorrection(SearchResultSet $searchResultSet)
570 38
    {
571
        $searchRequest = $searchResultSet->getUsedSearchRequest();
572
        $suggestions = $searchResultSet->getSpellCheckingSuggestions();
573 38
574 37
        $maximumRuns = $this->typoScriptConfiguration->getSearchSpellcheckingNumberOfSuggestionsToTry(1);
575
        $runs = 0;
576
577
        foreach ($suggestions as $suggestion) {
578 1
            $runs++;
579 1
580
            $correction = $suggestion->getSuggestion();
581
            $initialQuery = $searchRequest->getRawUserQuery();
582
583 1
            $searchRequest->setRawQueryString($correction);
584
            $searchResultSet = $this->search($searchRequest);
585
            if ($searchResultSet->getAllResultCount() > 0) {
586
                $searchResultSet->setIsAutoCorrected(true);
587 1
                $searchResultSet->setCorrectedQueryString($correction);
588
                $searchResultSet->setInitialQueryString($initialQuery);
589 1
                break;
590
            }
591
592
            if ($runs > $maximumRuns) {
593
                break;
594
            }
595
        }
596 1
        return $searchResultSet;
597
    }
598 1
599 1
    /**
600
     * Allows to modify a query before eventually handing it over to Solr.
601 1
     *
602 1
     * @param Query $query The current query before it's being handed over to Solr.
603
     * @param SearchRequest $searchRequest The searchRequest, relevant in the current context
604 1
     * @param Search $search The search, relevant in the current context
605 1
     * @throws \UnexpectedValueException
606
     * @return Query The modified query that is actually going to be given to Solr.
607 1
     */
608 1
    protected function modifyQuery(Query $query, SearchRequest $searchRequest, Search $search)
609
    {
610 1
        // hook to modify the search query
611 1
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'])) {
612 1
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'] as $classReference) {
613 1
                $queryModifier = GeneralUtility::getUserObj($classReference);
614 1
615 1
                if ($queryModifier instanceof Modifier) {
616 1
                    if ($queryModifier instanceof SearchAware) {
617
                        $queryModifier->setSearch($search);
618
                    }
619
620
                    if ($queryModifier instanceof SearchRequestAware) {
621
                        $queryModifier->setSearchRequest($searchRequest);
622
                    }
623 1
624
                    $query = $queryModifier->modifyQuery($query);
625
                } else {
626
                    throw new \UnexpectedValueException(
627
                        get_class($queryModifier) . ' must implement interface ' . Modifier::class,
628
                        1310387414
629
                    );
630
                }
631
            }
632
        }
633
634
        return $query;
635 38
    }
636
637
    /**
638 38
     * Retrieves a single document from solr by document id.
639 31
     *
640 31
     * @param string $documentId
641
     * @return SearchResult
642 31
     */
643 31
    public function getDocumentById($documentId)
644
    {
645
        /* @var $query Query */
646
        $query = GeneralUtility::makeInstance(Query::class, $documentId);
647 31
        $query->setQueryFields(QueryFields::fromString('id'));
648 31
649
        $response = $this->search->search($query, 0, 1);
650
        $this->processResponse($response);
651 31
652
        $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...
653
        return $resultDocument;
654
    }
655 31
656
    /**
657
     * This method is used to call the registered hooks during the search execution.
658
     *
659
     * @param string $eventName
660
     * @param SearchResultSet $resultSet
661 38
     * @return SearchResultSet
662
     */
663
    private function handleSearchHook($eventName, SearchResultSet $resultSet)
664
    {
665
        if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr'][$eventName])) {
666
            return $resultSet;
667
        }
668
669
        foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr'][$eventName] as $classReference) {
670
            $afterSearchProcessor = GeneralUtility::getUserObj($classReference);
671
            if ($afterSearchProcessor instanceof SearchResultSetProcessor) {
672
                $afterSearchProcessor->process($resultSet);
673
            }
674
        }
675 38
676
        return $resultSet;
677
    }
678 38
679
    /**
680
     * @return SearchResultSet
681
     */
682
    public function getLastResultSet()
683
    {
684
        return $this->lastResultSet;
685
    }
686
687
    /**
688
     * This method returns true when the last search was executed with an empty query
689
     * string or whitespaces only. When no search was triggered it will return false.
690
     *
691
     * @return bool
692
     */
693
    public function getLastSearchWasExecutedWithEmptyQueryString()
694
    {
695
        $wasEmptyQueryString = false;
696
        if ($this->lastResultSet != null) {
697
            $wasEmptyQueryString = $this->lastResultSet->getUsedSearchRequest()->getRawUserQueryIsEmptyString();
698
        }
699
700
        return $wasEmptyQueryString;
701
    }
702
703
    /**
704 38
     * @param int $requestedPerPage
705
     */
706
    protected function setPerPageInSession($requestedPerPage)
707
    {
708
        $GLOBALS['TSFE']->fe_user->setKey('ses', 'tx_solr_resultsPerPage', intval($requestedPerPage));
709
    }
710
711
    /**
712 2
     * @return mixed
713
     */
714
    protected function getPerPageFromSession()
715 2
    {
716 2
        return $GLOBALS['TSFE']->fe_user->getKey('ses', 'tx_solr_resultsPerPage');
717
    }
718 2
719 2
    /**
720
     * @return bool
721 2
     */
722 2
    protected function getInitialSearchIsConfigured()
723
    {
724
        return $this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialEmptyQuery() || $this->typoScriptConfiguration->getSearchInitializeWithQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialQuery();
725
    }
726
727
    /**
728
     * @return mixed
729
     */
730
    protected function getRegisteredSearchComponents()
731
    {
732 40
        return GeneralUtility::makeInstance(SearchComponentManager::class)->getSearchComponents();
733
    }
734 40
735 40
    /**
736
     * This method is used to reference the SearchResult object from the response in the SearchResultSet object.
737
     *
738 27
     * @param \Apache_Solr_Response $response
739 27
     * @param SearchResultSet $resultSet
740 27
     */
741 27
    protected function addSearchResultsToResultSet($response, $resultSet)
742
    {
743
        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...
744
            return;
745 27
        }
746
747
        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...
748
            $resultSet->addSearchResult($searchResult);
749
        }
750
    }
751 29
752
    /**
753 29
     * @param string $rawQuery
754
     * @return Query|object
755
     */
756
    protected function getQueryInstance($rawQuery)
757
    {
758
        $query = GeneralUtility::makeInstance(Query::class, $rawQuery, $this->typoScriptConfiguration);
759
        return $query;
760
    }
761
}
762