Completed
Push — master ( dedf8a...55d092 )
by Timo
33:39
created

wrapApacheSolrDocumentInResultObject()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 5
cts 6
cp 0.8333
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 1
crap 2.0185
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);
0 ignored issues
show
Documentation introduced by
$requestedPerPage is of type array, but the function expects a integer.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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
        $resultSet = $this->getAutoCorrection($resultSet);
562
563
        return $this->handleSearchHook('afterSearch', $resultSet);
564
    }
565
566
    /**
567
     * @param SearchResultSet $searchResultSet
568
     * @return SearchResultSet
569
     */
570
    protected function getAutoCorrection(SearchResultSet $searchResultSet)
571
    {
572
        // no secondary search configured
573 37
        if (!$this->typoScriptConfiguration->getSearchSpellcheckingSearchUsingSpellCheckerSuggestion()) {
574
            return $searchResultSet;
575
        }
576 37
577 30
        // more then zero results
578 30
        if ($searchResultSet->getAllResultCount() > 0) {
579
            return $searchResultSet;
580 30
        }
581 30
582
        // no corrections present
583
        if (!$searchResultSet->getHasSpellCheckingSuggestions()) {
584
            return $searchResultSet;
585 30
        }
586 30
587
        $searchResultSet = $this->peformAutoCorrection($searchResultSet);
588
589 30
        return $searchResultSet;
590
    }
591
592
    /**
593 30
     * @param SearchResultSet $searchResultSet
594
     * @return SearchResultSet
595
     */
596
    protected function peformAutoCorrection(SearchResultSet $searchResultSet)
597
    {
598
        $searchRequest = $searchResultSet->getUsedSearchRequest();
599 37
        $suggestions = $searchResultSet->getSpellCheckingSuggestions();
600
601
        $maximumRuns = $this->typoScriptConfiguration->getSearchSpellcheckingNumberOfSuggestionsToTry(1);
602
        $runs = 0;
603
604
        foreach ($suggestions as $suggestion) {
605
            $runs++;
606
607
            $correction = $suggestion->getSuggestion();
608
            $initialQuery = $searchRequest->getRawUserQuery();
609
610
            $searchRequest->setRawQueryString($correction);
611
            $searchResultSet = $this->search($searchRequest);
612
            if ($searchResultSet->getAllResultCount() > 0) {
613 37
                $searchResultSet->setIsAutoCorrected(true);
614
                $searchResultSet->setCorrectedQueryString($correction);
615
                $searchResultSet->setInitialQueryString($initialQuery);
616 37
                break;
617
            }
618
619
            if ($runs > $maximumRuns) {
620
                break;
621
            }
622
        }
623
        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
    protected function modifyQuery(Query $query, SearchRequest $searchRequest, Search $search)
636
    {
637
        // hook to modify the search query
638
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'])) {
639
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'] as $classReference) {
640
                $queryModifier = GeneralUtility::getUserObj($classReference);
641
642 37
                if ($queryModifier instanceof Modifier) {
643
                    if ($queryModifier instanceof SearchAware) {
644
                        $queryModifier->setSearch($search);
645
                    }
646
647
                    if ($queryModifier instanceof SearchRequestAware) {
648
                        $queryModifier->setSearchRequest($searchRequest);
649
                    }
650 2
651
                    $query = $queryModifier->modifyQuery($query);
652
                } else {
653 2
                    throw new \UnexpectedValueException(
654 2
                        get_class($queryModifier) . ' must implement interface ' . Modifier::class,
655
                        1310387414
656 2
                    );
657 2
                }
658
            }
659 2
        }
660 2
661
        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 39
     * @param SearchRequest $searchRequest The searchRequest, relevant in the current context
671
     * @param Search $search The search, relevant in the current context
672 39
     * @return \Apache_Solr_Response The modified response that is actually going to be returned to the extension.
673 39
     * @throws \UnexpectedValueException if a response modifier does not implement interface ApacheSolrForTypo3\Solr\Search\ResponseModifier
674
     */
675
    protected function modifyResponse(\Apache_Solr_Response $response, SearchRequest $searchRequest, Search $search)
676 26
    {
677 26
        // hook to modify the search response
678 26
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchResponse'])) {
679 26
            GeneralUtility::logDeprecatedFunction();
680
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchResponse'] as $classReference) {
681
                $responseModifier = GeneralUtility::getUserObj($classReference);
682
683 26
                if ($responseModifier instanceof ResponseModifier) {
684
                    if ($responseModifier instanceof SearchAware) {
685
                        $responseModifier->setSearch($search);
686
                    }
687
688
                    if ($responseModifier instanceof SearchRequestAware) {
689 28
                        $responseModifier->setSearchRequest($searchRequest);
690
                    }
691 28
                    $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
        return $response;
705
    }
706
    /**
707
     * Retrieves a single document from solr by document id.
708
     *
709
     * @param string $documentId
710
     * @return SearchResult
711
     */
712
    public function getDocumentById($documentId)
713 3
    {
714
        /* @var $query Query */
715 3
        $query = GeneralUtility::makeInstance(Query::class, $documentId);
716 3
        $query->setQueryFields(QueryFields::fromString('id'));
717
718
        $response = $this->search->search($query, 0, 1);
719
        $this->processResponse($query, $response);
720
721 30
        $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
        return $resultDocument;
723 30
    }
724
725
    /**
726
     * This method is used to call the registered hooks during the search execution.
727
     *
728
     * @param string $eventName
729 6
     * @param SearchResultSet $resultSet
730
     * @return SearchResultSet
731 6
     */
732
    private function handleSearchHook($eventName, SearchResultSet $resultSet)
733
    {
734
        if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr'][$eventName])) {
735
            return $resultSet;
736
        }
737 30
738
        foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr'][$eventName] as $classReference) {
739 30
            $afterSearchProcessor = GeneralUtility::getUserObj($classReference);
740
            if ($afterSearchProcessor instanceof SearchResultSetProcessor) {
741
                $afterSearchProcessor->process($resultSet);
742
            }
743
        }
744
745
        return $resultSet;
746
    }
747
748 37
    /**
749
     * @return SearchResultSet
750 37
     */
751 5
    public function getLastResultSet()
752
    {
753
        return $this->lastResultSet;
754 32
    }
755 26
756
    /**
757 32
     * 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 30
    {
764
        $wasEmptyQueryString = false;
765 30
        if ($this->lastResultSet != null) {
766 30
            $wasEmptyQueryString = $this->lastResultSet->getUsedSearchRequest()->getRawUserQueryIsEmptyString();
767
        }
768
769
        return $wasEmptyQueryString;
770
    }
771
772
    /**
773
     * @param int $requestedPerPage
774
     */
775
    protected function setPerPageInSession($requestedPerPage)
776
    {
777
        $GLOBALS['TSFE']->fe_user->setKey('ses', 'tx_solr_resultsPerPage', intval($requestedPerPage));
778
    }
779
780
    /**
781
     * @return mixed
782
     */
783
    protected function getPerPageFromSession()
784
    {
785
        return $GLOBALS['TSFE']->fe_user->getKey('ses', 'tx_solr_resultsPerPage');
786
    }
787
788
    /**
789
     * @return bool
790
     */
791
    protected function getInitialSearchIsConfigured()
792
    {
793
        return $this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialEmptyQuery() || $this->typoScriptConfiguration->getSearchInitializeWithQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialQuery();
794
    }
795
796
    /**
797
     * @return mixed
798
     */
799
    protected function getRegisteredSearchComponents()
800
    {
801
        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
    protected function addSearchResultsToResultSet($response, $resultSet)
811
    {
812
        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
            return;
814
        }
815
816
        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
            $resultSet->addSearchResult($searchResult);
818
        }
819
    }
820
821
    /**
822
     * @param string $rawQuery
823
     * @return Query|object
824
     */
825
    protected function getQueryInstance($rawQuery)
826
    {
827
        $query = GeneralUtility::makeInstance(Query::class, $rawQuery, $this->typoScriptConfiguration);
828
        return $query;
829
    }
830
}
831