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