Passed
Push — master ( 48b9a1...94ae61 )
by Timo
20:07
created

SearchResultSetService::getQueryInstance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 1
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 45
    public function __construct(TypoScriptConfiguration $configuration, Search $search, SolrLogManager $solrLogManager = 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
    }
109
110
    /**
111
     * @param bool $useCache
112
     * @return bool
113
     */
114 29
    public function getIsSolrAvailable($useCache = true)
115
    {
116 29
        $this->isSolrAvailable = $this->search->ping($useCache);
117 29
        return $this->isSolrAvailable;
118
    }
119
120
    /**
121
     * @return bool
122
     */
123 29
    public function getHasSearched()
124
    {
125 29
        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 37
    protected function getPreparedQuery($rawQuery, $resultsPerPage)
163
    {
164
        /* @var $query Query */
165 37
        $query = $this->getQueryInstance($rawQuery);
166
167 37
        $this->applyPageSectionsRootLineFilter($query);
168
169 37
        if ($this->typoScriptConfiguration->getLoggingQuerySearchWords()) {
170
            $this->logger->log(
171
                SolrLogManager::INFO,
172
                'Received search query',
173
                [
174
                    $rawQuery
175
                ]
176
            );
177
        }
178
179 37
        $query->setResultsPerPage($resultsPerPage);
180
181 37
        if ($this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) {
182
            // empty main query, but using a "return everything"
183
            // alternative query in q.alt
184 30
            $query->setAlternativeQuery('*:*');
185
        }
186
187 37
        if ($this->typoScriptConfiguration->getSearchInitializeWithQuery()) {
188 3
            $query->setAlternativeQuery($this->typoScriptConfiguration->getSearchInitializeWithQuery());
189
        }
190
191 37
        foreach ($this->getAdditionalFilters() as $additionalFilter) {
192 2
            $query->getFilters()->add($additionalFilter);
193
        }
194
195 37
        return $query;
196
    }
197
198
    /**
199
     * @param Query $query
200
     * @param SearchRequest $searchRequest
201
     */
202 37
    protected function initializeRegisteredSearchComponents(Query $query, SearchRequest $searchRequest)
203
    {
204 37
        $searchComponents = $this->getRegisteredSearchComponents();
205
206 37
        foreach ($searchComponents as $searchComponent) {
207
            /** @var Search\SearchComponent $searchComponent */
208 31
            $searchComponent->setSearchConfiguration($this->typoScriptConfiguration->getSearchConfiguration());
209
210 31
            if ($searchComponent instanceof QueryAware && $this->useQueryAwareComponents) {
211 31
                $searchComponent->setQuery($query);
212
            }
213
214 31
            if ($searchComponent instanceof SearchRequestAware) {
215 30
                $searchComponent->setSearchRequest($searchRequest);
216
            }
217
218 31
            $searchComponent->initializeSearchComponent();
219
        }
220 37
    }
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 37
    protected function getNumberOfResultsPerPage(SearchRequest $searchRequest)
232
    {
233 37
        $requestedPerPage = $searchRequest->getResultsPerPage();
234 37
        $perPageSwitchOptions = $this->typoScriptConfiguration->getSearchResultsPerPageSwitchOptionsAsArray();
235 37
        if (isset($requestedPerPage) && in_array($requestedPerPage, $perPageSwitchOptions)) {
236 4
            $this->setPerPageInSession($requestedPerPage);
237 4
            $this->resultsPerPageChanged = true;
238
        }
239
240 37
        $defaultResultsPerPage = $this->typoScriptConfiguration->getSearchResultsPerPage();
241 37
        $sessionResultPerPage = $this->getPerPageFromSession();
242
243 37
        $currentNumberOfResultsShown = $defaultResultsPerPage;
244 37
        if (!is_null($sessionResultPerPage) && in_array($sessionResultPerPage, $perPageSwitchOptions)) {
245 3
            $currentNumberOfResultsShown = (int)$sessionResultPerPage;
246
        }
247
248 37
        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 37
        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 39
    protected function processResponse(Query $query, \Apache_Solr_Response $response)
265
    {
266 39
        $this->wrapResultDocumentInResultObject($response);
267 39
        $this->addExpandedDocumentsFromVariants($response);
268
269 39
        $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 39
    }
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 39
    private function applySearchResponseProcessors(Query $query, \Apache_Solr_Response $response)
280
    {
281 39
        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 39
    }
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 39
    protected function addExpandedDocumentsFromVariants(\Apache_Solr_Response $response)
300
    {
301 39
        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 34
        if (!$this->typoScriptConfiguration->getSearchVariants()) {
306 31
            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 39
    protected function wrapResultDocumentInResultObject(\Apache_Solr_Response $response)
352
    {
353
        try {
354 39
            $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 39
        if (!is_array($documents)) {
374 5
            return;
375
        }
376
377 34
        foreach ($documents as $key => $originalDocument) {
378 28
            $result = $this->wrapApacheSolrDocumentInResultObject($originalDocument);
379 28
            $documents[$key] = $result;
380
        }
381
382 34
        $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 34
    }
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 28
    protected function wrapApacheSolrDocumentInResultObject(\Apache_Solr_Document $originalDocument)
394
    {
395 28
        $searchResultClassName = $this->getResultClassName();
396 28
        $result = GeneralUtility::makeInstance($searchResultClassName, $originalDocument);
397 28
        if (!$result instanceof SearchResult) {
398
            throw new \InvalidArgumentException('Could not create result object with class: ' . (string)$searchResultClassName, 1470037679);
399
        }
400
401 28
        return $result;
402
    }
403
404
    /**
405
     * @return string
406
     */
407 28
    protected function getResultClassName()
408
    {
409 28
        return isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultClassName ']) ?
410 28
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultClassName '] : SearchResult::class;
411
    }
412
413
    /**
414
     * @return string
415
     */
416 39
    protected function getResultSetClassName()
417
    {
418 39
        return isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultSetClassName ']) ?
419 39
            $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 37
    protected function shouldHideResultsFromInitialSearch(SearchRequest $searchRequest)
429
    {
430 37
        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 37
    protected function applyPageSectionsRootLineFilter(Query $query)
441
    {
442 37
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
443 37
        if (count($searchQueryFilters) <= 0) {
444 35
            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 42
    public function getAdditionalFilters()
460
    {
461
        // when we've build the additionalFilter once, we could return them
462 42
        if (count($this->additionalFilters) > 0) {
463 2
            return $this->additionalFilters;
464
        }
465
466 42
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
467 42
        if (count($searchQueryFilters) <= 0) {
468 40
            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 39
    public function search(SearchRequest $searchRequest)
503
    {
504
        /** @var $resultSet SearchResultSet */
505 39
        $resultSetClass = $this->getResultSetClassName();
506 39
        $resultSet = GeneralUtility::makeInstance($resultSetClass);
507 39
        $resultSet->setUsedSearchRequest($searchRequest);
508 39
        $this->lastResultSet = $resultSet;
509
510 39
        $resultSet = $this->handleSearchHook('beforeSearch', $resultSet);
511
512 39
        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 37
        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 37
        $rawQuery = $searchRequest->getRawUserQuery();
523 37
        $resultsPerPage = $this->getNumberOfResultsPerPage($searchRequest);
524 37
        $query = $this->getPreparedQuery($rawQuery, $resultsPerPage);
525 37
        $this->initializeRegisteredSearchComponents($query, $searchRequest);
526 37
        $resultSet->setUsedQuery($query);
527
528 37
        $currentPage = max(0, $searchRequest->getPage());
529
        // if the number of results per page has been changed by the current request, reset the pagebrowser
530 37
        if ($this->resultsPerPageChanged) {
531 4
            $currentPage = 0;
532
        }
533
534 37
        $offSet = $currentPage * $resultsPerPage;
535
536
        // performing the actual search, sending the query to the Solr server
537 37
        $query = $this->modifyQuery($query, $searchRequest, $this->search);
538 37
        $response = $this->search->search($query, $offSet, null);
539 37
        $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 37
        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 37
        $this->processResponse($query, $response);
548
549 37
        $this->addSearchResultsToResultSet($response, $resultSet);
550
551 37
        $resultSet->setResponse($response);
552 37
        $resultSet->setUsedPage($currentPage);
553 37
        $resultSet->setUsedResultsPerPage($resultsPerPage);
554 37
        $resultSet->setUsedAdditionalFilters($this->getAdditionalFilters());
555 37
        $resultSet->setUsedSearch($this->search);
556
557
        /** @var $searchResultReconstitutionProcessor ResultSetReconstitutionProcessor */
558 37
        $searchResultReconstitutionProcessor = GeneralUtility::makeInstance(ResultSetReconstitutionProcessor::class);
559 37
        $searchResultReconstitutionProcessor->process($resultSet);
560
561 37
        return $this->handleSearchHook('afterSearch', $resultSet);
562
    }
563
564
    /**
565
     * Allows to modify a query before eventually handing it over to Solr.
566
     *
567
     * @param Query $query The current query before it's being handed over to Solr.
568
     * @param SearchRequest $searchRequest The searchRequest, relevant in the current context
569
     * @param Search $search The search, relevant in the current context
570
     * @throws \UnexpectedValueException
571
     * @return Query The modified query that is actually going to be given to Solr.
572
     */
573 37
    protected function modifyQuery(Query $query, SearchRequest $searchRequest, Search $search)
574
    {
575
        // hook to modify the search query
576 37
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'])) {
577 30
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'] as $classReference) {
578 30
                $queryModifier = GeneralUtility::getUserObj($classReference);
579
580 30
                if ($queryModifier instanceof Modifier) {
581 30
                    if ($queryModifier instanceof SearchAware) {
582
                        $queryModifier->setSearch($search);
583
                    }
584
585 30
                    if ($queryModifier instanceof SearchRequestAware) {
586 30
                        $queryModifier->setSearchRequest($searchRequest);
587
                    }
588
589 30
                    $query = $queryModifier->modifyQuery($query);
590
                } else {
591
                    throw new \UnexpectedValueException(
592
                        get_class($queryModifier) . ' must implement interface ' . Modifier::class,
593 30
                        1310387414
594
                    );
595
                }
596
            }
597
        }
598
599 37
        return $query;
600
    }
601
602
    /**
603
     * Allows to modify a response returned from Solr before returning it to
604
     * the rest of the extension.
605
     *
606
     * @deprecated Please use $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['afterSearch'] now to register your SearchResultSetProcessor will be removed in 8.0
607
     * @param \Apache_Solr_Response $response The response as returned by Solr
608
     * @param SearchRequest $searchRequest The searchRequest, relevant in the current context
609
     * @param Search $search The search, relevant in the current context
610
     * @return \Apache_Solr_Response The modified response that is actually going to be returned to the extension.
611
     * @throws \UnexpectedValueException if a response modifier does not implement interface ApacheSolrForTypo3\Solr\Search\ResponseModifier
612
     */
613 37
    protected function modifyResponse(\Apache_Solr_Response $response, SearchRequest $searchRequest, Search $search)
614
    {
615
        // hook to modify the search response
616 37
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchResponse'])) {
617
            GeneralUtility::logDeprecatedFunction();
618
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchResponse'] as $classReference) {
619
                $responseModifier = GeneralUtility::getUserObj($classReference);
620
621
                if ($responseModifier instanceof ResponseModifier) {
622
                    if ($responseModifier instanceof SearchAware) {
623
                        $responseModifier->setSearch($search);
624
                    }
625
626
                    if ($responseModifier instanceof SearchRequestAware) {
627
                        $responseModifier->setSearchRequest($searchRequest);
628
                    }
629
                    $response = $responseModifier->modifyResponse($response);
630
                } else {
631
                    throw new \UnexpectedValueException(
632
                        get_class($responseModifier) . ' must implement interface ' . ResponseModifier::class,
633
                        1343147211
634
                    );
635
                }
636
            }
637
638
            // add modification indicator
639
            $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...
640
        }
641
642 37
        return $response;
643
    }
644
    /**
645
     * Retrieves a single document from solr by document id.
646
     *
647
     * @param string $documentId
648
     * @return SearchResult
649
     */
650 2
    public function getDocumentById($documentId)
651
    {
652
        /* @var $query Query */
653 2
        $query = GeneralUtility::makeInstance(Query::class, $documentId);
654 2
        $query->setQueryFields(QueryFields::fromString('id'));
655
656 2
        $response = $this->search->search($query, 0, 1);
657 2
        $this->processResponse($query, $response);
658
659 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...
660 2
        return $resultDocument;
661
    }
662
663
    /**
664
     * This method is used to call the registered hooks during the search execution.
665
     *
666
     * @param string $eventName
667
     * @param SearchResultSet $resultSet
668
     * @return SearchResultSet
669
     */
670 39
    private function handleSearchHook($eventName, SearchResultSet $resultSet)
671
    {
672 39
        if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr'][$eventName])) {
673 39
            return $resultSet;
674
        }
675
676 26
        foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr'][$eventName] as $classReference) {
677 26
            $afterSearchProcessor = GeneralUtility::getUserObj($classReference);
678 26
            if ($afterSearchProcessor instanceof SearchResultSetProcessor) {
679 26
                $afterSearchProcessor->process($resultSet);
680
            }
681
        }
682
683 26
        return $resultSet;
684
    }
685
686
    /**
687
     * @return SearchResultSet
688
     */
689 28
    public function getLastResultSet()
690
    {
691 28
        return $this->lastResultSet;
692
    }
693
694
    /**
695
     * This method returns true when the last search was executed with an empty query
696
     * string or whitespaces only. When no search was triggered it will return false.
697
     *
698
     * @return bool
699
     */
700
    public function getLastSearchWasExecutedWithEmptyQueryString()
701
    {
702
        $wasEmptyQueryString = false;
703
        if ($this->lastResultSet != null) {
704
            $wasEmptyQueryString = $this->lastResultSet->getUsedSearchRequest()->getRawUserQueryIsEmptyString();
705
        }
706
707
        return $wasEmptyQueryString;
708
    }
709
710
    /**
711
     * @param int $requestedPerPage
712
     */
713 3
    protected function setPerPageInSession($requestedPerPage)
714
    {
715 3
        $GLOBALS['TSFE']->fe_user->setKey('ses', 'tx_solr_resultsPerPage', intval($requestedPerPage));
716 3
    }
717
718
    /**
719
     * @return mixed
720
     */
721 30
    protected function getPerPageFromSession()
722
    {
723 30
        return $GLOBALS['TSFE']->fe_user->getKey('ses', 'tx_solr_resultsPerPage');
724
    }
725
726
    /**
727
     * @return bool
728
     */
729 6
    protected function getInitialSearchIsConfigured()
730
    {
731 6
        return $this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialEmptyQuery() || $this->typoScriptConfiguration->getSearchInitializeWithQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialQuery();
732
    }
733
734
    /**
735
     * @return mixed
736
     */
737 30
    protected function getRegisteredSearchComponents()
738
    {
739 30
        return GeneralUtility::makeInstance(SearchComponentManager::class)->getSearchComponents();
740
    }
741
742
    /**
743
     * This method is used to reference the SearchResult object from the response in the SearchResultSet object.
744
     *
745
     * @param \Apache_Solr_Response $response
746
     * @param SearchResultSet $resultSet
747
     */
748 37
    protected function addSearchResultsToResultSet($response, $resultSet)
749
    {
750 37
        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...
751 5
            return;
752
        }
753
754 32
        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...
755 26
            $resultSet->addSearchResult($searchResult);
756
        }
757 32
    }
758
759
    /**
760
     * @param string $rawQuery
761
     * @return Query|object
762
     */
763 30
    protected function getQueryInstance($rawQuery)
764
    {
765 30
        $query = GeneralUtility::makeInstance(Query::class, $rawQuery, $this->typoScriptConfiguration);
766 30
        return $query;
767
    }
768
}
769