Passed
Push — master ( f3b3b6...d83d48 )
by Timo
23:16
created

SuggestService   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 215
Duplicated Lines 0 %

Test Coverage

Coverage 59.61%

Importance

Changes 0
Metric Value
wmc 25
dl 0
loc 215
ccs 62
cts 104
cp 0.5961
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A addDocumentsWhenLimitNotReached() 0 11 3
A getSuggestionArray() 0 15 3
A getSuggestions() 0 21 3
A getSolrSuggestions() 0 15 2
B addTopResultsToSuggestions() 0 30 6
A __construct() 0 6 2
A getDocumentAsArray() 0 9 4
A doASearch() 0 3 1
A getResultArray() 0 3 1
1
<?php
2
3
namespace ApacheSolrForTypo3\Solr\Domain\Search\Suggest;
4
5
/***************************************************************
6
 *  Copyright notice
7
 *
8
 *  (c) 2017 Franz Saris <[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 3 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\ConnectionManager;
29
use ApacheSolrForTypo3\Solr\Domain\Search\Query\QueryBuilder;
30
use ApacheSolrForTypo3\Solr\Domain\Search\Query\SuggestQuery;
31
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\Result\SearchResult;
32
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\Result\SearchResultCollection;
33
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\SearchResultSet;
34
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\SearchResultSetService;
35
use ApacheSolrForTypo3\Solr\Domain\Search\SearchRequest;
36
use ApacheSolrForTypo3\Solr\Search;
37
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
38
use TYPO3\CMS\Core\Utility\GeneralUtility;
39
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
40
41
/**
42
 * Class SuggestService
43
 *
44
 * @author Frans Saris <[email protected]>
45
 * @author Timo Hund <[email protected]>
46
 * @package ApacheSolrForTypo3\Solr\Domain\Search\Suggest
47
 */
48
class SuggestService {
49
50
    /**
51
     * @var TypoScriptFrontendController
52
     */
53
    protected $tsfe;
54
55
    /**
56
     * @var SearchResultSetService
57
     */
58
    protected $searchService;
59
60
    /**
61
     * @var TypoScriptConfiguration
62
     */
63
    protected $typoScriptConfiguration;
64
65
    /**
66
     * @var QueryBuilder
67
     */
68
    protected $queryBuilder;
69
70
    /**
71
     * SuggestService constructor.
72
     * @param TypoScriptFrontendController $tsfe
73
     * @param SearchResultSetService $searchResultSetService
74
     * @param QueryBuilder|null $queryBuilder
75
     */
76 3
    public function __construct(TypoScriptFrontendController $tsfe, SearchResultSetService $searchResultSetService, TypoScriptConfiguration $typoScriptConfiguration, QueryBuilder $queryBuilder = null)
77
    {
78 3
        $this->tsfe = $tsfe;
79 3
        $this->searchService = $searchResultSetService;
80 3
        $this->typoScriptConfiguration = $typoScriptConfiguration;
81 3
        $this->queryBuilder = is_null($queryBuilder) ? GeneralUtility::makeInstance(QueryBuilder::class, $typoScriptConfiguration) : $queryBuilder;
0 ignored issues
show
Bug introduced by
$typoScriptConfiguration of type ApacheSolrForTypo3\Solr\...TypoScriptConfiguration is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

81
        $this->queryBuilder = is_null($queryBuilder) ? GeneralUtility::makeInstance(QueryBuilder::class, /** @scrutinizer ignore-type */ $typoScriptConfiguration) : $queryBuilder;
Loading history...
82 3
    }
83
84
    /**
85
     * Build an array structure of the suggestions.
86
     *
87
     * @param SearchRequest $searchRequest
88
     * @param string $additionalFilters
89
     * @return array
90
     */
91 3
    public function getSuggestions(SearchRequest $searchRequest, $additionalFilters) : array
92
    {
93 3
        $requestId = (int)$this->tsfe->getRequestedId();
94 3
        $groupList = (string)$this->tsfe->gr_list;
95
96 3
        $suggestQuery = $this->queryBuilder->buildSuggestQuery($searchRequest->getRawUserQuery(), $additionalFilters, $requestId, $groupList);
97 3
        $solrSuggestions = $this->getSolrSuggestions($suggestQuery);
98
99 3
        if ($solrSuggestions === []) {
100 1
            return ['status' => false];
101
        }
102
103 2
        $maxSuggestions = $this->typoScriptConfiguration->getSuggestNumberOfSuggestions();
104 2
        $showTopResults = $this->typoScriptConfiguration->getSuggestShowTopResults();
105 2
        $suggestions    = $this->getSuggestionArray($suggestQuery, $solrSuggestions, $maxSuggestions);
106
107 2
        if (!$showTopResults) {
108 1
            return $this->getResultArray($searchRequest, $suggestions, [], false);
109
        }
110
111 1
        return $this->addTopResultsToSuggestions($searchRequest, $suggestions);
112
    }
113
114
    /**
115
     * Determines the top results and adds them to the suggestions.
116
     *
117
     * @param SearchRequest $searchRequest
118
     * @param array $suggestions
119
     * @return array
120
     */
121 1
    protected function addTopResultsToSuggestions(SearchRequest $searchRequest, $suggestions) : array
122
    {
123 1
        $maxDocuments = $this->typoScriptConfiguration->getSuggestNumberOfTopResults();
124
125
        // perform the current search.
126 1
        $searchRequest->setResultsPerPage($maxDocuments);
127
128 1
        $didASecondSearch = false;
129 1
        $documents = [];
130
131 1
        $searchResultSet = $this->doASearch($searchRequest);
132 1
        $results = $searchResultSet->getSearchResults();
133 1
        if (count($results) > 0) {
134 1
            $documents = $this->addDocumentsWhenLimitNotReached($documents, $results, $maxDocuments);
135
        }
136
137 1
        $suggestionKeys = array_keys($suggestions);
138 1
        $bestSuggestion = reset($suggestionKeys);
139 1
        $bestSuggestionRequest = $searchRequest->getCopyForSubRequest();
140 1
        $bestSuggestionRequest->setRawQueryString($bestSuggestion);
141 1
        $bestSuggestionRequest->setResultsPerPage($maxDocuments);
142
143
        // No results found, use first proposed suggestion to perform the search
144 1
        if (count($documents) === 0 && !empty($suggestions) && ($searchResultSet = $this->doASearch($bestSuggestionRequest)) && count($searchResultSet->getSearchResults()) > 0) {
145
            $didASecondSearch = true;
146
            $documentsToAdd = $searchResultSet->getSearchResults();
147
            $documents = $this->addDocumentsWhenLimitNotReached($documents, $documentsToAdd, $maxDocuments);
148
        }
149
150 1
        return $this->getResultArray($searchRequest, $suggestions, $documents, $didASecondSearch);
151
    }
152
153
    /**
154
     * Retrieves the suggestions from the solr server.
155
     *
156
     * @param SuggestQuery $suggestQuery
157
     * @return array
158
     */
159
    protected function getSolrSuggestions(SuggestQuery $suggestQuery) : array
160
    {
161
        $pageId = $this->tsfe->getRequestedId();
162
        $languageId = $this->tsfe->sys_language_uid;
163
        $solr = GeneralUtility::makeInstance(ConnectionManager::class)->getConnectionByPageId($pageId, $languageId);
164
        $search = GeneralUtility::makeInstance(Search::class, $solr);
165
        $response = $search->search($suggestQuery, 0, 0);
166
167
        $rawResponse = $response->getRawResponse();
168
        $results = json_decode($rawResponse);
169
        $suggestConfig = $this->typoScriptConfiguration->getObjectByPath('plugin.tx_solr.suggest.');
170
        $facetSuggestions = $results->facet_counts->facet_fields->{$suggestConfig['suggestField']};
171
        $facetSuggestions = get_object_vars($facetSuggestions);
172
173
        return is_null($facetSuggestions) ? [] : $facetSuggestions;
0 ignored issues
show
introduced by
The condition is_null($facetSuggestions) can never be true.
Loading history...
174
    }
175
176
    /**
177
     * Extracts the suggestions from solr as array.
178
     *
179
     * @param SuggestQuery $suggestQuery
180
     * @param array $solrSuggestions
181
     * @param integer $maxSuggestions
182
     * @return array
183
     */
184 2
    protected function getSuggestionArray(SuggestQuery $suggestQuery, $solrSuggestions, $maxSuggestions) : array
185
    {
186 2
        $queryString = $suggestQuery->getQueryStringContainer()->getKeywords();
187 2
        $suggestionCount = 0;
188 2
        $suggestions = [];
189 2
        foreach ($solrSuggestions as $string => $count) {
190 2
            $suggestion = trim($queryString . ' ' . $string);
191 2
            $suggestions[$suggestion] = $count;
192 2
            $suggestionCount++;
193 2
            if ($suggestionCount === $maxSuggestions) {
194 2
                return $suggestions;
195
            }
196
        }
197
198 2
        return $suggestions;
199
    }
200
201
    /**
202
     * Adds documents from a collection to the result collection as soon as the limit is not reached.
203
     *
204
     * @param array $documents
205
     * @param SearchResultCollection $documentsToAdd
206
     * @param integer $maxDocuments
207
     * @return array
208
     */
209 1
    protected function addDocumentsWhenLimitNotReached(array $documents, SearchResultCollection $documentsToAdd, int $maxDocuments)  : array
210
    {
211
        /** @var SearchResult $document */
212 1
        foreach ($documentsToAdd as $document) {
213 1
            $documents[] = $this->getDocumentAsArray($document);
214 1
            if (count($documents) >= $maxDocuments) {
215 1
                return $documents;
216
            }
217
        }
218
219
        return $documents;
220
    }
221
222
    /**
223
     * Creates an array representation of the result and returns it.
224
     *
225
     * @param SearchResult $document
226
     * @return array
227
     */
228 1
    protected function getDocumentAsArray(SearchResult $document) : array
229
    {
230
        return [
231 1
            'link' => $document->getUrl(),
232 1
            'type' => $document->getField('type_stringS') ? $document->getField('type_stringS')['value'] : $document->getType(),
233 1
            'title' => $document->getTitle(),
234 1
            'content' => $document->getContent(),
235 1
            'group' => $document->getHasGroupItem() ? $document->getGroupItem()->getGroupValue() : '',
236 1
            'previewImage' => $document->getField('previewImage_stringS') ? $document->getField('previewImage_stringS')['value'] : '',
237
        ];
238
    }
239
240
    /**
241
     * Runs a search and returns the results.
242
     *
243
     * @param SearchRequest $searchRequest
244
     * @return \ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\SearchResultSet
245
     */
246 1
    protected function doASearch($searchRequest) : SearchResultSet
247
    {
248 1
        return $this->searchService->search($searchRequest);
249
    }
250
251
    /**
252
     * Creates an result array with the required fields.
253
     *
254
     * @param SearchRequest $searchRequest
255
     * @param array $suggestions
256
     * @param array $documents
257
     * @param boolean $didASecondSearch
258
     * @return array
259
     */
260 2
    protected function getResultArray(SearchRequest $searchRequest, $suggestions, $documents, $didASecondSearch) : array
261
    {
262 2
        return ['suggestions' => $suggestions, 'suggestion' => $searchRequest->getRawUserQuery(), 'documents' => $documents, 'didSecondSearch' => $didASecondSearch];
263
    }
264
265
266
}