Completed
Push — master ( 9d5568...e20b48 )
by Timo
04:12
created

shouldHideResultsFromInitialSearch()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 2
nc 5
nop 1
crap 30
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 45
        $this->search = $search;
104
        $this->typoScriptConfiguration = $configuration;
105 45
        $this->logger = is_null($solrLogManager) ? GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__) : $solrLogManager;
106 45
    }
107 45
108 45
    /**
109
     * @param bool $useCache
110
     * @return bool
111
     */
112
    public function getIsSolrAvailable($useCache = true)
113
    {
114 29
        $this->isSolrAvailable = $this->search->ping($useCache);
115
        return $this->isSolrAvailable;
116 29
    }
117 29
118
    /**
119
     * @return bool
120
     */
121
    public function getHasSearched()
122
    {
123 29
        return $this->search->hasSearched();
124
    }
125 29
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 37
        /* @var $query Query */
163
        $query = $this->getQueryInstance($rawQuery);
164
165 37
        $this->applyPageSectionsRootLineFilter($query);
166
167 37
        if ($this->typoScriptConfiguration->getLoggingQuerySearchWords()) {
168
            $this->logger->log(
169 37
                SolrLogManager::INFO,
170
                'Received search query',
171
                [
172
                    $rawQuery
173
                ]
174
            );
175
        }
176
177
        $query->setResultsPerPage($resultsPerPage);
178
179 37
        if ($this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) {
180
            // empty main query, but using a "return everything"
181 37
            // alternative query in q.alt
182
            $query->setAlternativeQuery('*:*');
183
        }
184 30
185
        if ($this->typoScriptConfiguration->getSearchInitializeWithQuery()) {
186
            $query->setAlternativeQuery($this->typoScriptConfiguration->getSearchInitializeWithQuery());
187 37
        }
188 3
189
        foreach ($this->getAdditionalFilters() as $additionalFilter) {
190
            $query->getFilters()->add($additionalFilter);
191 37
        }
192 2
193
        return $query;
194
    }
195 37
196
    /**
197
     * @param Query $query
198
     * @param SearchRequest $searchRequest
199
     */
200
    protected function initializeRegisteredSearchComponents(Query $query, SearchRequest $searchRequest)
201
    {
202 37
        $searchComponents = $this->getRegisteredSearchComponents();
203
204 37
        foreach ($searchComponents as $searchComponent) {
205
            /** @var Search\SearchComponent $searchComponent */
206 37
            $searchComponent->setSearchConfiguration($this->typoScriptConfiguration->getSearchConfiguration());
207
208 31
            if ($searchComponent instanceof QueryAware && $this->useQueryAwareComponents) {
209
                $searchComponent->setQuery($query);
210 31
            }
211 31
212
            if ($searchComponent instanceof SearchRequestAware) {
213
                $searchComponent->setSearchRequest($searchRequest);
214 31
            }
215 30
216
            $searchComponent->initializeSearchComponent();
217
        }
218 31
    }
219
220 37
    /**
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 37
        $requestedPerPage = $searchRequest->getResultsPerPage();
232
        $perPageSwitchOptions = $this->typoScriptConfiguration->getSearchResultsPerPageSwitchOptionsAsArray();
233 37
        if (isset($requestedPerPage) && in_array($requestedPerPage, $perPageSwitchOptions)) {
234 37
            $this->setPerPageInSession($requestedPerPage);
0 ignored issues
show
Documentation introduced by
$requestedPerPage is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
235 37
            $this->resultsPerPageChanged = true;
236 4
        }
237 4
238
        $defaultResultsPerPage = $this->typoScriptConfiguration->getSearchResultsPerPage();
239
        $sessionResultPerPage = $this->getPerPageFromSession();
240 37
241 37
        $currentNumberOfResultsShown = $defaultResultsPerPage;
242
        if (!is_null($sessionResultPerPage) && in_array($sessionResultPerPage, $perPageSwitchOptions)) {
243 37
            $currentNumberOfResultsShown = (int)$sessionResultPerPage;
244 37
        }
245 3
246
        if ($this->shouldHideResultsFromInitialSearch($searchRequest)) {
247
            // initialize search with an empty query, which would by default return all documents
248 37
            // 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 37
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 39
        $this->addExpandedDocumentsFromVariants($response);
265
    }
266 39
267 39
    /**
268
     * This method is used to add documents to the expanded documents of the SearchResult
269 39
     * when collapsing is configured.
270 39
     *
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 39
        if (!$this->typoScriptConfiguration->getSearchVariants()) {
280
            return;
281 39
        }
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 39
                continue;
292
            }
293
294
            $variantAccessKey = mb_strtolower($variantId);
295
            if (!isset($response->{'expanded'}) || !isset($response->{'expanded'}->{$variantAccessKey})) {
296
                continue;
297
            }
298
299 39
            foreach ($response->{'expanded'}->{$variantAccessKey}->{'docs'} as $variantDocumentArray) {
300
                $variantDocument = new \Apache_Solr_Document();
301 39
                foreach (get_object_vars($variantDocumentArray) as $propertyName => $propertyValue) {
302 5
                    $variantDocument->{$propertyName} = $propertyValue;
303
                }
304
                $variantSearchResult = $this->wrapApacheSolrDocumentInResultObject($variantDocument);
305 34
                $variantSearchResult->setIsVariant(true);
306 31
                $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 39
        foreach ($documents as $key => $originalDocument) {
352
            $result = $this->wrapApacheSolrDocumentInResultObject($originalDocument);
353
            $documents[$key] = $result;
354 39
        }
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 39
        }
374 5
375
        return $result;
376
    }
377 34
378 28
    /**
379 28
     * @return string
380
     */
381
    protected function getResultClassName()
382 34
    {
383 34
        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 28
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultSetClassName '] : SearchResultSet::class;
394
    }
395 28
396 28
    /**
397 28
     * Checks it the results should be hidden in the response.
398
     *
399
     * @param SearchRequest $searchRequest
400
     * @return bool
401 28
     */
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 28
    /**
408
     * Initializes additional filters configured through TypoScript and
409 28
     * Flexforms for use in regular queries and suggest queries.
410 28
     *
411
     * @param Query $query
412
     * @return void
413
     */
414
    protected function applyPageSectionsRootLineFilter(Query $query)
415
    {
416 39
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
417
        if (count($searchQueryFilters) <= 0) {
418 39
            return;
419 39
        }
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 37
    /**
429
     * Retrieves the configuration filters from the TypoScript configuration, except the __pageSections filter.
430 37
     *
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 37
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
441
        if (count($searchQueryFilters) <= 0) {
442 37
            return [];
443 37
        }
444 35
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 42
            $hasSubConfiguration = is_array($searchQueryFilters[$filterKey . '.']);
460
            if ($hasSubConfiguration) {
461
                $filter = $cObj->stdWrap($searchQueryFilters[$filterKey], $searchQueryFilters[$filterKey . '.']);
462 42
            }
463 2
464
            $this->additionalFilters[$filterKey] = $filter;
465
        }
466 42
467 42
        return $this->additionalFilters;
468 40
    }
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 39
        $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 39
            $currentPage = 0;
506 39
        }
507 39
508 39
        $offSet = $currentPage * $resultsPerPage;
509
510 39
        // performing the actual search, sending the query to the Solr server
511
        $query = $this->modifyQuery($query, $searchRequest, $this->search);
512 39
        $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 37
            $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 37
        $this->addSearchResultsToResultSet($response, $resultSet);
523 37
524 37
        $resultSet->setResponse($response);
525 37
        $resultSet->setUsedPage($currentPage);
526 37
        $resultSet->setUsedResultsPerPage($resultsPerPage);
527
        $resultSet->setUsedAdditionalFilters($this->getAdditionalFilters());
528 37
        $resultSet->setUsedSearch($this->search);
529
530 37
        /** @var $searchResultReconstitutionProcessor ResultSetReconstitutionProcessor */
531 4
        $searchResultReconstitutionProcessor = GeneralUtility::makeInstance(ResultSetReconstitutionProcessor::class);
532
        $searchResultReconstitutionProcessor->process($resultSet);
533
534 37
        $resultSet = $this->getAutoCorrection($resultSet);
535
536
        return $this->handleSearchHook('afterSearch', $resultSet);
537 37
    }
538 37
539 37
    /**
540
     * @param SearchResultSet $searchResultSet
541 37
     * @return SearchResultSet
542
     */
543
    protected function getAutoCorrection(SearchResultSet $searchResultSet)
544 2
    {
545
        // no secondary search configured
546
        if (!$this->typoScriptConfiguration->getSearchSpellcheckingSearchUsingSpellCheckerSuggestion()) {
547 37
            return $searchResultSet;
548
        }
549 37
550
        // more then zero results
551 37
        if ($searchResultSet->getAllResultCount() > 0) {
552 37
            return $searchResultSet;
553 37
        }
554 37
555 37
        // no corrections present
556
        if (!$searchResultSet->getHasSpellCheckingSuggestions()) {
557
            return $searchResultSet;
558 37
        }
559 37
560
        $searchResultSet = $this->peformAutoCorrection($searchResultSet);
561 37
562
        return $searchResultSet;
563
    }
564
565
    /**
566
     * @param SearchResultSet $searchResultSet
567
     * @return SearchResultSet
568
     */
569
    protected function peformAutoCorrection(SearchResultSet $searchResultSet)
570
    {
571
        $searchRequest = $searchResultSet->getUsedSearchRequest();
572
        $suggestions = $searchResultSet->getSpellCheckingSuggestions();
573 37
574
        $maximumRuns = $this->typoScriptConfiguration->getSearchSpellcheckingNumberOfSuggestionsToTry(1);
575
        $runs = 0;
576 37
577 30
        foreach ($suggestions as $suggestion) {
578 30
            $runs++;
579
580 30
            $correction = $suggestion->getSuggestion();
581 30
            $initialQuery = $searchRequest->getRawUserQuery();
582
583
            $searchRequest->setRawQueryString($correction);
584
            $searchResultSet = $this->search($searchRequest);
585 30
            if ($searchResultSet->getAllResultCount() > 0) {
586 30
                $searchResultSet->setIsAutoCorrected(true);
587
                $searchResultSet->setCorrectedQueryString($correction);
588
                $searchResultSet->setInitialQueryString($initialQuery);
589 30
                break;
590
            }
591
592
            if ($runs > $maximumRuns) {
593 30
                break;
594
            }
595
        }
596
        return $searchResultSet;
597
    }
598
599 37
    /**
600
     * Allows to modify a query before eventually handing it over to Solr.
601
     *
602
     * @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
     * @param Search $search The search, relevant in the current context
605
     * @throws \UnexpectedValueException
606
     * @return Query The modified query that is actually going to be given to Solr.
607
     */
608
    protected function modifyQuery(Query $query, SearchRequest $searchRequest, Search $search)
609
    {
610
        // hook to modify the search query
611
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'])) {
612
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'] as $classReference) {
613 37
                $queryModifier = GeneralUtility::getUserObj($classReference);
614
615
                if ($queryModifier instanceof Modifier) {
616 37
                    if ($queryModifier instanceof SearchAware) {
617
                        $queryModifier->setSearch($search);
618
                    }
619
620
                    if ($queryModifier instanceof SearchRequestAware) {
621
                        $queryModifier->setSearchRequest($searchRequest);
622
                    }
623
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
    }
636
637
    /**
638
     * Retrieves a single document from solr by document id.
639
     *
640
     * @param string $documentId
641
     * @return SearchResult
642 37
     */
643
    public function getDocumentById($documentId)
644
    {
645
        /* @var $query Query */
646
        $query = GeneralUtility::makeInstance(Query::class, $documentId);
647
        $query->setQueryFields(QueryFields::fromString('id'));
648
649
        $response = $this->search->search($query, 0, 1);
650 2
        $this->processResponse($response);
651
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 2
        return $resultDocument;
654 2
    }
655
656 2
    /**
657 2
     * This method is used to call the registered hooks during the search execution.
658
     *
659 2
     * @param string $eventName
660 2
     * @param SearchResultSet $resultSet
661
     * @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 39
            $afterSearchProcessor = GeneralUtility::getUserObj($classReference);
671
            if ($afterSearchProcessor instanceof SearchResultSetProcessor) {
672 39
                $afterSearchProcessor->process($resultSet);
673 39
            }
674
        }
675
676 26
        return $resultSet;
677 26
    }
678 26
679 26
    /**
680
     * @return SearchResultSet
681
     */
682
    public function getLastResultSet()
683 26
    {
684
        return $this->lastResultSet;
685
    }
686
687
    /**
688
     * This method returns true when the last search was executed with an empty query
689 28
     * string or whitespaces only. When no search was triggered it will return false.
690
     *
691 28
     * @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
     * @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
     * @return mixed
713 3
     */
714
    protected function getPerPageFromSession()
715 3
    {
716 3
        return $GLOBALS['TSFE']->fe_user->getKey('ses', 'tx_solr_resultsPerPage');
717
    }
718
719
    /**
720
     * @return bool
721 30
     */
722
    protected function getInitialSearchIsConfigured()
723 30
    {
724
        return $this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialEmptyQuery() || $this->typoScriptConfiguration->getSearchInitializeWithQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialQuery();
725
    }
726
727
    /**
728
     * @return mixed
729 6
     */
730
    protected function getRegisteredSearchComponents()
731 6
    {
732
        return GeneralUtility::makeInstance(SearchComponentManager::class)->getSearchComponents();
733
    }
734
735
    /**
736
     * This method is used to reference the SearchResult object from the response in the SearchResultSet object.
737 30
     *
738
     * @param \Apache_Solr_Response $response
739 30
     * @param SearchResultSet $resultSet
740
     */
741
    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
        }
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 37
            $resultSet->addSearchResult($searchResult);
749
        }
750 37
    }
751 5
752
    /**
753
     * @param string $rawQuery
754 32
     * @return Query|object
755 26
     */
756
    protected function getQueryInstance($rawQuery)
757 32
    {
758
        $query = GeneralUtility::makeInstance(Query::class, $rawQuery, $this->typoScriptConfiguration);
759
        return $query;
760
    }
761
}
762