Passed
Push — master ( 5adbd6...5901ba )
by Timo
20:55
created

wrapResultDocumentInResultObject()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 33
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5.0042

Importance

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