Passed
Push — master ( bd2c53...55d092 )
by Timo
22:59 queued 50s
created

getNumberOfResultsPerPage()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 14
cts 14
cp 1
rs 8.439
c 0
b 0
f 0
cc 6
eloc 14
nc 8
nop 1
crap 6
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\Response\Processor\ResponseProcessor;
34
use ApacheSolrForTypo3\Solr\Search;
35
use ApacheSolrForTypo3\Solr\Search\QueryAware;
36
use ApacheSolrForTypo3\Solr\Search\ResponseModifier;
37
use ApacheSolrForTypo3\Solr\Search\SearchAware;
38
use ApacheSolrForTypo3\Solr\Search\SearchComponentManager;
39
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
40
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
41
use TYPO3\CMS\Core\Utility\GeneralUtility;
42
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
43
use Apache_Solr_ParserException;
44
45
/**
46
 * The SearchResultSetService is responsible to build a SearchResultSet from a SearchRequest.
47
 * It encapsulates the logic to trigger a search in order to be able to reuse it in multiple places.
48
 *
49
 * @author Timo Schmidt <[email protected]>
50
 */
51
class SearchResultSetService
52
{
53
    /**
54
     * Additional filters, which will be added to the query, as well as to
55
     * suggest queries.
56
     *
57
     * @var array
58
     */
59
    protected $additionalFilters = [];
60
61
    /**
62
     * Track, if the number of results per page has been changed by the current request
63
     *
64
     * @var bool
65
     */
66
    protected $resultsPerPageChanged = false;
67
68
    /**
69
     * @var \ApacheSolrForTypo3\Solr\Search
70
     */
71
    protected $search;
72
73
    /**
74
     * @var SearchResultSet
75
     */
76
    protected $lastResultSet = null;
77
78
    /**
79
     * @var bool
80
     */
81
    protected $useQueryAwareComponents = true;
82
83
    /**
84
     * @var
85
     */
86
    protected $isSolrAvailable = false;
87
88
    /**
89
     * @var TypoScriptConfiguration
90
     */
91
    protected $typoScriptConfiguration;
92
93
    /**
94
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
95
     */
96
    protected $logger = null;
97
98
    /**
99
     * @param TypoScriptConfiguration $configuration
100
     * @param Search $search
101
     * @param SolrLogManager $solrLogManager
102
     */
103 46
    public function __construct(TypoScriptConfiguration $configuration, Search $search, SolrLogManager $solrLogManager = null)
104
    {
105 46
        $this->search = $search;
106 46
        $this->typoScriptConfiguration = $configuration;
107 46
        $this->logger = is_null($solrLogManager) ? GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__) : $solrLogManager;
108 46
    }
109
110
    /**
111
     * @param bool $useCache
112
     * @return bool
113
     */
114 30
    public function getIsSolrAvailable($useCache = true)
115
    {
116 30
        $this->isSolrAvailable = $this->search->ping($useCache);
117 30
        return $this->isSolrAvailable;
118
    }
119
120
    /**
121
     * @return bool
122
     */
123 30
    public function getHasSearched()
124
    {
125 30
        return $this->search->hasSearched();
126
    }
127
128
    /**
129
     * Retrieves the used search instance.
130
     *
131
     * @return Search
132
     */
133 2
    public function getSearch()
134
    {
135 2
        return $this->search;
136
    }
137
138
    /**
139
     * @param bool $useQueryAwareComponents
140
     */
141
    public function setUseQueryAwareComponents($useQueryAwareComponents)
142
    {
143
        $this->useQueryAwareComponents = $useQueryAwareComponents;
144
    }
145
146
    /**
147
     * @return bool
148
     */
149
    public function getUseQueryAwareComponents()
150
    {
151
        return $this->useQueryAwareComponents;
152
    }
153
154
    /**
155
     * Initializes the Query object and SearchComponents and returns
156
     * the initialized query object, when a search should be executed.
157
     *
158
     * @param string|null $rawQuery
159
     * @param int $resultsPerPage
160
     * @return Query
161
     */
162 38
    protected function getPreparedQuery($rawQuery, $resultsPerPage)
163
    {
164
        /* @var $query Query */
165 38
        $query = $this->getQueryInstance($rawQuery);
166
167 38
        $this->applyPageSectionsRootLineFilter($query);
168
169 38
        if ($this->typoScriptConfiguration->getLoggingQuerySearchWords()) {
170
            $this->logger->log(
171
                SolrLogManager::INFO,
172
                'Received search query',
173
                [
174
                    $rawQuery
175
                ]
176
            );
177
        }
178
179 38
        $query->setResultsPerPage($resultsPerPage);
180
181 38
        if ($this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) {
182
            // empty main query, but using a "return everything"
183
            // alternative query in q.alt
184 31
            $query->setAlternativeQuery('*:*');
185
        }
186
187 38
        if ($this->typoScriptConfiguration->getSearchInitializeWithQuery()) {
188 3
            $query->setAlternativeQuery($this->typoScriptConfiguration->getSearchInitializeWithQuery());
189
        }
190
191 38
        foreach ($this->getAdditionalFilters() as $additionalFilter) {
192 2
            $query->getFilters()->add($additionalFilter);
193
        }
194
195 38
        return $query;
196
    }
197
198
    /**
199
     * @param Query $query
200
     * @param SearchRequest $searchRequest
201
     */
202 38
    protected function initializeRegisteredSearchComponents(Query $query, SearchRequest $searchRequest)
203
    {
204 38
        $searchComponents = $this->getRegisteredSearchComponents();
205
206 38
        foreach ($searchComponents as $searchComponent) {
207
            /** @var Search\SearchComponent $searchComponent */
208 32
            $searchComponent->setSearchConfiguration($this->typoScriptConfiguration->getSearchConfiguration());
209
210 32
            if ($searchComponent instanceof QueryAware && $this->useQueryAwareComponents) {
211 32
                $searchComponent->setQuery($query);
212
            }
213
214 32
            if ($searchComponent instanceof SearchRequestAware) {
215 31
                $searchComponent->setSearchRequest($searchRequest);
216
            }
217
218 32
            $searchComponent->initializeSearchComponent();
219
        }
220 38
    }
221
222
    /**
223
     * Returns the number of results per Page.
224
     *
225
     * Also influences how many result documents are returned by the Solr
226
     * server as the return value is used in the Solr "rows" GET parameter.
227
     *
228
     * @param SearchRequest $searchRequest
229
     * @return int number of results to show per page
230
     */
231 38
    protected function getNumberOfResultsPerPage(SearchRequest $searchRequest)
232
    {
233 38
        $requestedPerPage = $searchRequest->getResultsPerPage();
234 38
        $perPageSwitchOptions = $this->typoScriptConfiguration->getSearchResultsPerPageSwitchOptionsAsArray();
235 38
        if (isset($requestedPerPage) && in_array($requestedPerPage, $perPageSwitchOptions)) {
236 4
            $this->setPerPageInSession($requestedPerPage);
237 4
            $this->resultsPerPageChanged = true;
238
        }
239
240 38
        $defaultResultsPerPage = $this->typoScriptConfiguration->getSearchResultsPerPage();
241 38
        $sessionResultPerPage = $this->getPerPageFromSession();
242
243 38
        $currentNumberOfResultsShown = $defaultResultsPerPage;
244 38
        if (!is_null($sessionResultPerPage) && in_array($sessionResultPerPage, $perPageSwitchOptions)) {
245 3
            $currentNumberOfResultsShown = (int)$sessionResultPerPage;
246
        }
247
248 38
        if ($this->shouldHideResultsFromInitialSearch($searchRequest)) {
249
            // initialize search with an empty query, which would by default return all documents
250
            // anyway, tell Solr to not return any result documents
251
            // Solr will still return facets though
252 2
            $currentNumberOfResultsShown = 0;
253
        }
254
255 38
        return $currentNumberOfResultsShown;
256
    }
257
258
    /**
259
     * Provides a hook for other classes to process the search's response.
260
     *
261
     * @param Query $query The query that has been searched for.
262
     * @param \Apache_Solr_Response $response The search's response.
263
     */
264 40
    protected function processResponse(Query $query, \Apache_Solr_Response $response)
265
    {
266 40
        $this->wrapResultDocumentInResultObject($response);
267 40
        $this->addExpandedDocumentsFromVariants($response);
268
269 40
        $this->applySearchResponseProcessors($query, $response);
0 ignored issues
show
Deprecated Code introduced by
The method ApacheSolrForTypo3\Solr\...rchResponseProcessors() has been deprecated with message: Please use $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['afterSearch'] now to register your SearchResultSetProcessor will be removed in 8.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
270 40
    }
271
272
    /**
273
     * Executes the register searchResponse processors.
274
     *
275
     * @deprecated Please use $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['afterSearch'] now to register your SearchResultSetProcessor will be removed in 8.0
276
     * @param Query $query
277
     * @param \Apache_Solr_Response $response
278
     */
279 40
    private function applySearchResponseProcessors(Query $query, \Apache_Solr_Response $response)
280
    {
281 40
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['processSearchResponse'])) {
282 1
            GeneralUtility::logDeprecatedFunction();
283
284 1
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['processSearchResponse'] as $classReference) {
285 1
                $responseProcessor = GeneralUtility::getUserObj($classReference);
286 1
                if ($responseProcessor instanceof ResponseProcessor) {
287 1
                    $responseProcessor->processResponse($query, $response);
288
                }
289
            }
290
        }
291 40
    }
292
293
    /**
294
     * This method is used to add documents to the expanded documents of the SearchResult
295
     * when collapsing is configured.
296
     *
297
     * @param \Apache_Solr_Response $response
298
     */
299 40
    protected function addExpandedDocumentsFromVariants(\Apache_Solr_Response $response)
300
    {
301 40
        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...
302 5
            return;
303
        }
304
305 35
        if (!$this->typoScriptConfiguration->getSearchVariants()) {
306 32
            return;
307
        }
308
309 3
        $variantsField = $this->typoScriptConfiguration->getSearchVariantsField();
310 3
        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...
311
            /** @var $resultDocument SearchResult */
312 2
            $variantField = $resultDocument->getField($variantsField);
313 2
            $variantId = isset($variantField['value']) ? $variantField['value'] : null;
314
315
                // when there is no value in the collapsing field, we can return
316 2
            if ($variantId === null) {
317
                continue;
318
            }
319
320 2
            $variantAccessKey = mb_strtolower($variantId);
321 2
            if (!isset($response->{'expanded'}) || !isset($response->{'expanded'}->{$variantAccessKey})) {
322
                continue;
323
            }
324
325 2
            foreach ($response->{'expanded'}->{$variantAccessKey}->{'docs'} as $variantDocumentArray) {
326 2
                $variantDocument = new \Apache_Solr_Document();
327 2
                foreach (get_object_vars($variantDocumentArray) as $propertyName => $propertyValue) {
328 2
                    $variantDocument->{$propertyName} = $propertyValue;
329
                }
330 2
                $variantSearchResult = $this->wrapApacheSolrDocumentInResultObject($variantDocument);
331 2
                $variantSearchResult->setIsVariant(true);
332 2
                $variantSearchResult->setVariantParent($resultDocument);
333
334 2
                $resultDocument->addVariant($variantSearchResult);
335
            }
336
        }
337 3
    }
338
339
    /**
340
     * Wrap all results document it a custom EXT:solr SearchResult object.
341
     *
342
     * Can be overwritten:
343
     *
344
     * $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultClassName '] = ''
345
     *
346
     * to use a custom result object.
347
     *
348
     * @param \Apache_Solr_Response $response
349
     * @throws \Apache_Solr_ParserException
350
     */
351 40
    protected function wrapResultDocumentInResultObject(\Apache_Solr_Response $response)
352
    {
353
        try {
354 40
            $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...
355 1
        } 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...
356
            // when variant are enable and the index is empty, we get a parse exception, because of a
357
            // Apache Solr Bug.
358
            // see: https://github.com/TYPO3-Solr/ext-solr/issues/668
359
            // @todo this try/catch block can be removed after upgrading to Apache Solr 6.4
360 1
            if (!$this->typoScriptConfiguration->getSearchVariants()) {
361
                throw $e;
362
            }
363
364 1
            $response->response = new \stdClass();
365 1
            $response->spellcheck = [];
366 1
            $response->debug = [];
367 1
            $response->responseHeader = [];
368 1
            $response->facet_counts = [];
369
370 1
            $documents = [];
371
        }
372
373 40
        if (!is_array($documents)) {
374 5
            return;
375
        }
376
377 35
        foreach ($documents as $key => $originalDocument) {
378 29
            $result = $this->wrapApacheSolrDocumentInResultObject($originalDocument);
379 29
            $documents[$key] = $result;
380
        }
381
382 35
        $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...
383 35
    }
384
385
    /**
386
     * This method is used to wrap the \Apache_Solr_Document instance in an instance of the configured SearchResult
387
     * class.
388
     *
389
     * @param \Apache_Solr_Document $originalDocument
390
     * @throws \InvalidArgumentException
391
     * @return SearchResult
392
     */
393 29
    protected function wrapApacheSolrDocumentInResultObject(\Apache_Solr_Document $originalDocument)
394
    {
395 29
        $searchResultClassName = $this->getResultClassName();
396 29
        $result = GeneralUtility::makeInstance($searchResultClassName, $originalDocument);
397 29
        if (!$result instanceof SearchResult) {
398
            throw new \InvalidArgumentException('Could not create result object with class: ' . (string)$searchResultClassName, 1470037679);
399
        }
400
401 29
        return $result;
402
    }
403
404
    /**
405
     * @return string
406
     */
407 29
    protected function getResultClassName()
408
    {
409 29
        return isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultClassName ']) ?
410 29
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultClassName '] : SearchResult::class;
411
    }
412
413
    /**
414
     * @return string
415
     */
416 40
    protected function getResultSetClassName()
417
    {
418 40
        return isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultSetClassName ']) ?
419 40
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultSetClassName '] : SearchResultSet::class;
420
    }
421
422
    /**
423
     * Checks it the results should be hidden in the response.
424
     *
425
     * @param SearchRequest $searchRequest
426
     * @return bool
427
     */
428 38
    protected function shouldHideResultsFromInitialSearch(SearchRequest $searchRequest)
429
    {
430 38
        return ($this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchInitializeWithQuery()) && !$this->typoScriptConfiguration->getSearchShowResultsOfInitialEmptyQuery() && !$this->typoScriptConfiguration->getSearchShowResultsOfInitialQuery() && $searchRequest->getRawUserQueryIsNull();
431
    }
432
433
    /**
434
     * Initializes additional filters configured through TypoScript and
435
     * Flexforms for use in regular queries and suggest queries.
436
     *
437
     * @param Query $query
438
     * @return void
439
     */
440 38
    protected function applyPageSectionsRootLineFilter(Query $query)
441
    {
442 38
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
443 38
        if (count($searchQueryFilters) <= 0) {
444 36
            return;
445
        }
446
447
        // special filter to limit search to specific page tree branches
448 2
        if (array_key_exists('__pageSections', $searchQueryFilters)) {
449
            $query->setRootlineFilter($searchQueryFilters['__pageSections']);
450
            $this->typoScriptConfiguration->removeSearchQueryFilterForPageSections();
451
        }
452 2
    }
453
454
    /**
455
     * Retrieves the configuration filters from the TypoScript configuration, except the __pageSections filter.
456
     *
457
     * @return array
458
     */
459 43
    public function getAdditionalFilters()
460
    {
461
        // when we've build the additionalFilter once, we could return them
462 43
        if (count($this->additionalFilters) > 0) {
463 2
            return $this->additionalFilters;
464
        }
465
466 43
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
467 43
        if (count($searchQueryFilters) <= 0) {
468 41
            return [];
469
        }
470
471 2
        $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
472
473
        // all other regular filters
474 2
        foreach ($searchQueryFilters as $filterKey => $filter) {
475
            // the __pageSections filter should not be handled as additional filter
476 2
            if ($filterKey === '__pageSections') {
477
                continue;
478
            }
479
480 2
            $filterIsArray = is_array($searchQueryFilters[$filterKey]);
481 2
            if ($filterIsArray) {
482
                continue;
483
            }
484
485 2
            $hasSubConfiguration = is_array($searchQueryFilters[$filterKey . '.']);
486 2
            if ($hasSubConfiguration) {
487
                $filter = $cObj->stdWrap($searchQueryFilters[$filterKey], $searchQueryFilters[$filterKey . '.']);
488
            }
489
490 2
            $this->additionalFilters[$filterKey] = $filter;
491
        }
492
493 2
        return $this->additionalFilters;
494
    }
495
496
    /**
497
     * Performs a search and returns a SearchResultSet.
498
     *
499
     * @param SearchRequest $searchRequest
500
     * @return SearchResultSet
501
     */
502 40
    public function search(SearchRequest $searchRequest)
503
    {
504
        /** @var $resultSet SearchResultSet */
505 40
        $resultSetClass = $this->getResultSetClassName();
506 40
        $resultSet = GeneralUtility::makeInstance($resultSetClass);
507 40
        $resultSet->setUsedSearchRequest($searchRequest);
508 40
        $this->lastResultSet = $resultSet;
509
510 40
        $resultSet = $this->handleSearchHook('beforeSearch', $resultSet);
511
512 40
        if ($searchRequest->getRawUserQueryIsNull() && !$this->getInitialSearchIsConfigured()) {
513
            // when no rawQuery was passed or no initialSearch is configured, we pass an empty result set
514 2
            return $resultSet;
515
        }
516
517 38
        if ($searchRequest->getRawUserQueryIsEmptyString() && !$this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) {
518
            // the user entered an empty query string "" or "  " and empty querystring is not allowed
519
            return $resultSet;
520
        }
521
522 38
        $rawQuery = $searchRequest->getRawUserQuery();
523 38
        $resultsPerPage = $this->getNumberOfResultsPerPage($searchRequest);
524 38
        $query = $this->getPreparedQuery($rawQuery, $resultsPerPage);
525 38
        $this->initializeRegisteredSearchComponents($query, $searchRequest);
526 38
        $resultSet->setUsedQuery($query);
527
528 38
        $currentPage = max(0, $searchRequest->getPage());
529
        // if the number of results per page has been changed by the current request, reset the pagebrowser
530 38
        if ($this->resultsPerPageChanged) {
531 4
            $currentPage = 0;
532
        }
533
534 38
        $offSet = $currentPage * $resultsPerPage;
535
536
        // performing the actual search, sending the query to the Solr server
537 38
        $query = $this->modifyQuery($query, $searchRequest, $this->search);
538 38
        $response = $this->search->search($query, $offSet, null);
539 38
        $response = $this->modifyResponse($response, $searchRequest, $this->search);
0 ignored issues
show
Deprecated Code introduced by
The method ApacheSolrForTypo3\Solr\...rvice::modifyResponse() has been deprecated with message: Please use $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['afterSearch'] now to register your SearchResultSetProcessor will be removed in 8.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
540
541 38
        if ($resultsPerPage === 0) {
542
            // when resultPerPage was forced to 0 we also set the numFound to 0 to hide results, e.g.
543
            // when results for the initial search should not be shown.
544 2
            $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...
545
        }
546
547 38
        $this->processResponse($query, $response);
548
549 38
        $this->addSearchResultsToResultSet($response, $resultSet);
550
551 38
        $resultSet->setResponse($response);
552 38
        $resultSet->setUsedPage($currentPage);
553 38
        $resultSet->setUsedResultsPerPage($resultsPerPage);
554 38
        $resultSet->setUsedAdditionalFilters($this->getAdditionalFilters());
555 38
        $resultSet->setUsedSearch($this->search);
556
557
        /** @var $searchResultReconstitutionProcessor ResultSetReconstitutionProcessor */
558 38
        $searchResultReconstitutionProcessor = GeneralUtility::makeInstance(ResultSetReconstitutionProcessor::class);
559 38
        $searchResultReconstitutionProcessor->process($resultSet);
560
561 38
        $resultSet = $this->getAutoCorrection($resultSet);
562
563 38
        return $this->handleSearchHook('afterSearch', $resultSet);
564
    }
565
566
    /**
567
     * @param SearchResultSet $searchResultSet
568
     * @return SearchResultSet
569
     */
570 38
    protected function getAutoCorrection(SearchResultSet $searchResultSet)
571
    {
572
        // no secondary search configured
573 38
        if (!$this->typoScriptConfiguration->getSearchSpellcheckingSearchUsingSpellCheckerSuggestion()) {
574 37
            return $searchResultSet;
575
        }
576
577
        // more then zero results
578 1
        if ($searchResultSet->getAllResultCount() > 0) {
579 1
            return $searchResultSet;
580
        }
581
582
        // no corrections present
583 1
        if (!$searchResultSet->getHasSpellCheckingSuggestions()) {
584
            return $searchResultSet;
585
        }
586
587 1
        $searchResultSet = $this->peformAutoCorrection($searchResultSet);
588
589 1
        return $searchResultSet;
590
    }
591
592
    /**
593
     * @param SearchResultSet $searchResultSet
594
     * @return SearchResultSet
595
     */
596 1
    protected function peformAutoCorrection(SearchResultSet $searchResultSet)
597
    {
598 1
        $searchRequest = $searchResultSet->getUsedSearchRequest();
599 1
        $suggestions = $searchResultSet->getSpellCheckingSuggestions();
600
601 1
        $maximumRuns = $this->typoScriptConfiguration->getSearchSpellcheckingNumberOfSuggestionsToTry(1);
602 1
        $runs = 0;
603
604 1
        foreach ($suggestions as $suggestion) {
605 1
            $runs++;
606
607 1
            $correction = $suggestion->getSuggestion();
608 1
            $initialQuery = $searchRequest->getRawUserQuery();
609
610 1
            $searchRequest->setRawQueryString($correction);
611 1
            $searchResultSet = $this->search($searchRequest);
612 1
            if ($searchResultSet->getAllResultCount() > 0) {
613 1
                $searchResultSet->setIsAutoCorrected(true);
614 1
                $searchResultSet->setCorrectedQueryString($correction);
615 1
                $searchResultSet->setInitialQueryString($initialQuery);
616 1
                break;
617
            }
618
619
            if ($runs > $maximumRuns) {
620
                break;
621
            }
622
        }
623 1
        return $searchResultSet;
624
    }
625
626
    /**
627
     * Allows to modify a query before eventually handing it over to Solr.
628
     *
629
     * @param Query $query The current query before it's being handed over to Solr.
630
     * @param SearchRequest $searchRequest The searchRequest, relevant in the current context
631
     * @param Search $search The search, relevant in the current context
632
     * @throws \UnexpectedValueException
633
     * @return Query The modified query that is actually going to be given to Solr.
634
     */
635 38
    protected function modifyQuery(Query $query, SearchRequest $searchRequest, Search $search)
636
    {
637
        // hook to modify the search query
638 38
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'])) {
639 31
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'] as $classReference) {
640 31
                $queryModifier = GeneralUtility::getUserObj($classReference);
641
642 31
                if ($queryModifier instanceof Modifier) {
643 31
                    if ($queryModifier instanceof SearchAware) {
644
                        $queryModifier->setSearch($search);
645
                    }
646
647 31
                    if ($queryModifier instanceof SearchRequestAware) {
648 31
                        $queryModifier->setSearchRequest($searchRequest);
649
                    }
650
651 31
                    $query = $queryModifier->modifyQuery($query);
652
                } else {
653
                    throw new \UnexpectedValueException(
654
                        get_class($queryModifier) . ' must implement interface ' . Modifier::class,
655 31
                        1310387414
656
                    );
657
                }
658
            }
659
        }
660
661 38
        return $query;
662
    }
663
664
    /**
665
     * Allows to modify a response returned from Solr before returning it to
666
     * the rest of the extension.
667
     *
668
     * @deprecated Please use $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['afterSearch'] now to register your SearchResultSetProcessor will be removed in 8.0
669
     * @param \Apache_Solr_Response $response The response as returned by Solr
670
     * @param SearchRequest $searchRequest The searchRequest, relevant in the current context
671
     * @param Search $search The search, relevant in the current context
672
     * @return \Apache_Solr_Response The modified response that is actually going to be returned to the extension.
673
     * @throws \UnexpectedValueException if a response modifier does not implement interface ApacheSolrForTypo3\Solr\Search\ResponseModifier
674
     */
675 38
    protected function modifyResponse(\Apache_Solr_Response $response, SearchRequest $searchRequest, Search $search)
676
    {
677
        // hook to modify the search response
678 38
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchResponse'])) {
679
            GeneralUtility::logDeprecatedFunction();
680
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchResponse'] as $classReference) {
681
                $responseModifier = GeneralUtility::getUserObj($classReference);
682
683
                if ($responseModifier instanceof ResponseModifier) {
684
                    if ($responseModifier instanceof SearchAware) {
685
                        $responseModifier->setSearch($search);
686
                    }
687
688
                    if ($responseModifier instanceof SearchRequestAware) {
689
                        $responseModifier->setSearchRequest($searchRequest);
690
                    }
691
                    $response = $responseModifier->modifyResponse($response);
692
                } else {
693
                    throw new \UnexpectedValueException(
694
                        get_class($responseModifier) . ' must implement interface ' . ResponseModifier::class,
695
                        1343147211
696
                    );
697
                }
698
            }
699
700
            // add modification indicator
701
            $response->response->isModified = true;
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...
702
        }
703
704 38
        return $response;
705
    }
706
    /**
707
     * Retrieves a single document from solr by document id.
708
     *
709
     * @param string $documentId
710
     * @return SearchResult
711
     */
712 2
    public function getDocumentById($documentId)
713
    {
714
        /* @var $query Query */
715 2
        $query = GeneralUtility::makeInstance(Query::class, $documentId);
716 2
        $query->setQueryFields(QueryFields::fromString('id'));
717
718 2
        $response = $this->search->search($query, 0, 1);
719 2
        $this->processResponse($query, $response);
720
721 2
        $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...
722 2
        return $resultDocument;
723
    }
724
725
    /**
726
     * This method is used to call the registered hooks during the search execution.
727
     *
728
     * @param string $eventName
729
     * @param SearchResultSet $resultSet
730
     * @return SearchResultSet
731
     */
732 40
    private function handleSearchHook($eventName, SearchResultSet $resultSet)
733
    {
734 40
        if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr'][$eventName])) {
735 40
            return $resultSet;
736
        }
737
738 27
        foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr'][$eventName] as $classReference) {
739 27
            $afterSearchProcessor = GeneralUtility::getUserObj($classReference);
740 27
            if ($afterSearchProcessor instanceof SearchResultSetProcessor) {
741 27
                $afterSearchProcessor->process($resultSet);
742
            }
743
        }
744
745 27
        return $resultSet;
746
    }
747
748
    /**
749
     * @return SearchResultSet
750
     */
751 29
    public function getLastResultSet()
752
    {
753 29
        return $this->lastResultSet;
754
    }
755
756
    /**
757
     * This method returns true when the last search was executed with an empty query
758
     * string or whitespaces only. When no search was triggered it will return false.
759
     *
760
     * @return bool
761
     */
762
    public function getLastSearchWasExecutedWithEmptyQueryString()
763
    {
764
        $wasEmptyQueryString = false;
765
        if ($this->lastResultSet != null) {
766
            $wasEmptyQueryString = $this->lastResultSet->getUsedSearchRequest()->getRawUserQueryIsEmptyString();
767
        }
768
769
        return $wasEmptyQueryString;
770
    }
771
772
    /**
773
     * @param int $requestedPerPage
774
     */
775 3
    protected function setPerPageInSession($requestedPerPage)
776
    {
777 3
        $GLOBALS['TSFE']->fe_user->setKey('ses', 'tx_solr_resultsPerPage', intval($requestedPerPage));
778 3
    }
779
780
    /**
781
     * @return mixed
782
     */
783 31
    protected function getPerPageFromSession()
784
    {
785 31
        return $GLOBALS['TSFE']->fe_user->getKey('ses', 'tx_solr_resultsPerPage');
786
    }
787
788
    /**
789
     * @return bool
790
     */
791 6
    protected function getInitialSearchIsConfigured()
792
    {
793 6
        return $this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialEmptyQuery() || $this->typoScriptConfiguration->getSearchInitializeWithQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialQuery();
794
    }
795
796
    /**
797
     * @return mixed
798
     */
799 31
    protected function getRegisteredSearchComponents()
800
    {
801 31
        return GeneralUtility::makeInstance(SearchComponentManager::class)->getSearchComponents();
802
    }
803
804
    /**
805
     * This method is used to reference the SearchResult object from the response in the SearchResultSet object.
806
     *
807
     * @param \Apache_Solr_Response $response
808
     * @param SearchResultSet $resultSet
809
     */
810 38
    protected function addSearchResultsToResultSet($response, $resultSet)
811
    {
812 38
        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...
813 5
            return;
814
        }
815
816 33
        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...
817 27
            $resultSet->addSearchResult($searchResult);
818
        }
819 33
    }
820
821
    /**
822
     * @param string $rawQuery
823
     * @return Query|object
824
     */
825 31
    protected function getQueryInstance($rawQuery)
826
    {
827 31
        $query = GeneralUtility::makeInstance(Query::class, $rawQuery, $this->typoScriptConfiguration);
828 31
        return $query;
829
    }
830
}
831