Passed
Push — task/2976_TYPO3.11_compatibili... ( 6df79b...fdb8e9 )
by Rafael
22:17
created

SuggestService::addTopResultsToSuggestions()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 32
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 20
dl 0
loc 32
ccs 21
cts 21
cp 1
rs 8.9777
c 0
b 0
f 0
cc 6
nc 4
nop 3
crap 6
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\NoSolrConnectionFoundException;
37
use ApacheSolrForTypo3\Solr\Search;
38
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
39
use ApacheSolrForTypo3\Solr\System\Solr\ParsingUtil;
40
use ApacheSolrForTypo3\Solr\Util;
41
use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException;
42
use TYPO3\CMS\Core\Utility\GeneralUtility;
43
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
44
45
/**
46
 * Class SuggestService
47
 *
48
 * @author Frans Saris <[email protected]>
49
 * @author Timo Hund <[email protected]>
50
 */
51
class SuggestService {
52
53
    /**
54
     * @var TypoScriptFrontendController
55
     */
56
    protected $tsfe;
57
58
    /**
59
     * @var SearchResultSetService
60
     */
61
    protected $searchService;
62
63
    /**
64
     * @var TypoScriptConfiguration
65
     */
66
    protected $typoScriptConfiguration;
67
68
    /**
69
     * @var QueryBuilder
70
     */
71
    protected $queryBuilder;
72
73
    /**
74
     * SuggestService constructor.
75
     * @param TypoScriptFrontendController $tsfe
76
     * @param SearchResultSetService $searchResultSetService
77
     * @param QueryBuilder|null $queryBuilder
78
     */
79 7
    public function __construct(TypoScriptFrontendController $tsfe, SearchResultSetService $searchResultSetService, TypoScriptConfiguration $typoScriptConfiguration, QueryBuilder $queryBuilder = null)
80
    {
81 7
        $this->tsfe = $tsfe;
82 7
        $this->searchService = $searchResultSetService;
83 7
        $this->typoScriptConfiguration = $typoScriptConfiguration;
84 7
        $this->queryBuilder = $queryBuilder ?? GeneralUtility::makeInstance(QueryBuilder::class, /** @scrutinizer ignore-type */ $typoScriptConfiguration);
85 7
    }
86
87
    /**
88
     * Build an array structure of the suggestions.
89
     *
90
     * @param SearchRequest $searchRequest
91
     * @param array $additionalFilters
92
     * @return array
93
     */
94 7
    public function getSuggestions(SearchRequest $searchRequest, array $additionalFilters = []) : array
95
    {
96 7
        $requestId = (int)$this->tsfe->getRequestedId();
97 7
        $groupList = Util::getFrontendUserGroupsList();
98
99 7
        $suggestQuery = $this->queryBuilder->buildSuggestQuery($searchRequest->getRawUserQuery(), $additionalFilters, $requestId, $groupList);
100 7
        $solrSuggestions = $this->getSolrSuggestions($suggestQuery);
101
102 7
        if ($solrSuggestions === []) {
103 2
            return ['status' => false];
104
        }
105
106 5
        $maxSuggestions = $this->typoScriptConfiguration->getSuggestNumberOfSuggestions();
107 5
        $showTopResults = $this->typoScriptConfiguration->getSuggestShowTopResults();
108 5
        $suggestions    = $this->getSuggestionArray($suggestQuery, $solrSuggestions, $maxSuggestions);
109
110 5
        if (!$showTopResults) {
111 1
            return $this->getResultArray($searchRequest, $suggestions, [], false);
112
        }
113
114 4
        return $this->addTopResultsToSuggestions($searchRequest, $suggestions, $additionalFilters);
115
    }
116
117
    /**
118
     * Determines the top results and adds them to the suggestions.
119
     *
120
     * @param SearchRequest $searchRequest
121
     * @param array $suggestions
122
     * @param array $additionalFilters
123
     * @return array
124
     */
125 4
    protected function addTopResultsToSuggestions(SearchRequest $searchRequest, $suggestions, array $additionalFilters) : array
126
    {
127 4
        $maxDocuments = $this->typoScriptConfiguration->getSuggestNumberOfTopResults();
128
129
        // perform the current search.
130 4
        $searchRequest->setResultsPerPage($maxDocuments);
131 4
        $searchRequest->setAdditionalFilters($additionalFilters);
132
133 4
        $didASecondSearch = false;
134 4
        $documents = [];
135
136 4
        $searchResultSet = $this->doASearch($searchRequest);
137 4
        $results = $searchResultSet->getSearchResults();
138 4
        if (count($results) > 0) {
139 2
            $documents = $this->addDocumentsWhenLimitNotReached($documents, $results, $maxDocuments);
140
        }
141
142 4
        $suggestionKeys = array_keys($suggestions);
143 4
        $bestSuggestion = reset($suggestionKeys);
144 4
        $bestSuggestionRequest = $searchRequest->getCopyForSubRequest();
145 4
        $bestSuggestionRequest->setRawQueryString($bestSuggestion);
146 4
        $bestSuggestionRequest->setResultsPerPage($maxDocuments);
147 4
        $bestSuggestionRequest->setAdditionalFilters($additionalFilters);
148
149
        // No results found, use first proposed suggestion to perform the search
150 4
        if (count($documents) === 0 && !empty($suggestions) && ($searchResultSet = $this->doASearch($bestSuggestionRequest)) && count($searchResultSet->getSearchResults()) > 0) {
151 2
            $didASecondSearch = true;
152 2
            $documentsToAdd = $searchResultSet->getSearchResults();
153 2
            $documents = $this->addDocumentsWhenLimitNotReached($documents, $documentsToAdd, $maxDocuments);
154
        }
155
156 4
        return $this->getResultArray($searchRequest, $suggestions, $documents, $didASecondSearch);
157
    }
158
159
    /**
160
     * Retrieves the suggestions from the solr server.
161
     *
162
     * @param SuggestQuery $suggestQuery
163
     * @return array
164
     * @throws NoSolrConnectionFoundException
165
     * @throws AspectNotFoundException
166
     */
167 4
    protected function getSolrSuggestions(SuggestQuery $suggestQuery): array
168
    {
169 4
        $pageId = $this->tsfe->getRequestedId();
170 4
        $languageId = Util::getLanguageUid();
171 4
        $solr = GeneralUtility::makeInstance(ConnectionManager::class)->getConnectionByPageId($pageId, $languageId);
172 4
        $search = GeneralUtility::makeInstance(Search::class, /** @scrutinizer ignore-type */ $solr);
173 4
        $response = $search->search($suggestQuery, 0, 0);
174
175 4
        $rawResponse = $response->getRawResponse();
176 4
        if (null === $rawResponse) {
177 1
            return [];
178
        }
179 3
        $results = json_decode($rawResponse);
180 3
        $suggestConfig = $this->typoScriptConfiguration->getObjectByPath('plugin.tx_solr.suggest.');
181 3
        $facetSuggestions = isset($suggestConfig['suggestField']) ? $results->facet_counts->facet_fields->{$suggestConfig['suggestField']} ?? [] : [];
182 3
        $facetSuggestions = ParsingUtil::getMapArrayFromFlatArray($facetSuggestions);
183
184 3
        return $facetSuggestions ?? [];
185
    }
186
187
    /**
188
     * Extracts the suggestions from solr as array.
189
     *
190
     * @param SuggestQuery $suggestQuery
191
     * @param array $solrSuggestions
192
     * @param integer $maxSuggestions
193
     * @return array
194
     */
195 5
    protected function getSuggestionArray(SuggestQuery $suggestQuery, $solrSuggestions, $maxSuggestions) : array
196
    {
197 5
        $queryString = $suggestQuery->getQuery();
198 5
        $suggestionCount = 0;
199 5
        $suggestions = [];
200 5
        foreach ($solrSuggestions as $string => $count) {
201 5
            $suggestion = trim($queryString . ' ' . $string);
202 5
            $suggestions[$suggestion] = $count;
203 5
            $suggestionCount++;
204 5
            if ($suggestionCount === $maxSuggestions) {
205
                return $suggestions;
206
            }
207
        }
208
209 5
        return $suggestions;
210
    }
211
212
    /**
213
     * Adds documents from a collection to the result collection as soon as the limit is not reached.
214
     *
215
     * @param array $documents
216
     * @param SearchResultCollection $documentsToAdd
217
     * @param integer $maxDocuments
218
     * @return array
219
     */
220 4
    protected function addDocumentsWhenLimitNotReached(array $documents, SearchResultCollection $documentsToAdd, int $maxDocuments)  : array
221
    {
222 4
        $additionalTopResultsFields = $this->typoScriptConfiguration->getSuggestAdditionalTopResultsFields();
223
        /** @var SearchResult $document */
224 4
        foreach ($documentsToAdd as $document) {
225 4
            $documents[] = $this->getDocumentAsArray($document, $additionalTopResultsFields);
226 4
            if (count($documents) >= $maxDocuments) {
227 1
                return $documents;
228
            }
229
        }
230
231 3
        return $documents;
232
    }
233
234
    /**
235
     * Creates an array representation of the result and returns it.
236
     *
237
     * @param SearchResult $document
238
     * @param array $additionalTopResultsFields
239
     * @return array
240
     */
241 4
    protected function getDocumentAsArray(SearchResult $document, $additionalTopResultsFields = []) : array
242
    {
243
        $fields = [
244 4
            'link' => $document->getUrl(),
245 4
            'type' => $document['type_stringS'] ? $document['type_stringS'] : $document->getType(),
246 4
            'title' => $document->getTitle(),
247 4
            'content' => $document->getContent(),
248 4
            'group' => $document->getHasGroupItem() ? $document->getGroupItem()->getGroupValue() : '',
249 4
            'previewImage' => $document['image'] ? $document['image'] : '',
250
        ];
251 4
        foreach ($additionalTopResultsFields as $additionalTopResultsField) {
252
            $fields[$additionalTopResultsField] = $document[$additionalTopResultsField] ? $document[$additionalTopResultsField] : '';
253
        }
254 4
        return $fields;
255
    }
256
257
    /**
258
     * Runs a search and returns the results.
259
     *
260
     * @param SearchRequest $searchRequest
261
     * @return \ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\SearchResultSet
262
     */
263 4
    protected function doASearch($searchRequest) : SearchResultSet
264
    {
265 4
        return $this->searchService->search($searchRequest);
266
    }
267
268
    /**
269
     * Creates an result array with the required fields.
270
     *
271
     * @param SearchRequest $searchRequest
272
     * @param array $suggestions
273
     * @param array $documents
274
     * @param boolean $didASecondSearch
275
     * @return array
276
     */
277 5
    protected function getResultArray(SearchRequest $searchRequest, $suggestions, $documents, $didASecondSearch) : array
278
    {
279 5
        return ['suggestions' => $suggestions, 'suggestion' => $searchRequest->getRawUserQuery(), 'documents' => $documents, 'didSecondSearch' => $didASecondSearch];
280
    }
281
282
283
}
284