Passed
Pull Request — master (#2925)
by Rafael
35:25
created

SuggestService   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 224
Duplicated Lines 0 %

Test Coverage

Coverage 71.17%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 25
eloc 80
c 2
b 0
f 0
dl 0
loc 224
ccs 79
cts 111
cp 0.7117
rs 10

9 Methods

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