Failed Conditions
Push — master ( a052b2...ee9c7d )
by Rafael
18:58
created

SuggestService::getSolrSuggestions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 0
cts 14
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 12
nc 2
nop 1
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 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\ConnectionManager;
29
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\SearchResult;
30
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\SearchResultSet;
31
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\SearchResultSetService;
32
use ApacheSolrForTypo3\Solr\Domain\Search\SearchRequest;
33
use ApacheSolrForTypo3\Solr\Domain\Site\SiteHashService;
34
use ApacheSolrForTypo3\Solr\Search;
35
use ApacheSolrForTypo3\Solr\SuggestQuery;
36
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
37
use TYPO3\CMS\Core\Utility\GeneralUtility;
38
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
39
40
/**
41
 * Class SuggestService
42
 *
43
 * @author Frans Saris <[email protected]>
44
 * @author Timo Hund <[email protected]>
45
 * @package ApacheSolrForTypo3\Solr\Domain\Search\Suggest
46
 */
47
class SuggestService {
48
49
    /**
50
     * @var TypoScriptFrontendController
51
     */
52
    protected $tsfe;
53
54
    /**
55
     * @var SearchResultSetService
56
     */
57
    protected $searchService;
58
59
    /**
60
     * @var TypoScriptConfiguration
61
     */
62
    protected $typoScriptConfiguration;
63
64
    /**
65
     * SuggestService constructor.
66
     * @param TypoScriptFrontendController $tsfe
67
     * @param SearchResultSetService $searchResultSetService
68
     */
69 3
    public function __construct(TypoScriptFrontendController $tsfe, SearchResultSetService $searchResultSetService, TypoScriptConfiguration $typoScriptConfiguration)
70
    {
71 3
        $this->tsfe = $tsfe;
72 3
        $this->searchService = $searchResultSetService;
73 3
        $this->typoScriptConfiguration = $typoScriptConfiguration;
74 3
    }
75
76
    /**
77
     * Build an array structure of the suggestions.
78
     *
79
     * @param SearchRequest $searchRequest
80
     * @param string $additionalFilters
81
     * @return array
82
     */
83 3
    public function getSuggestions(SearchRequest $searchRequest, $additionalFilters) : array
84
    {
85 3
        $suggestQuery = $this->buildSuggestQuery($searchRequest->getRawUserQuery(), $additionalFilters);
86 3
        $solrSuggestions = $this->getSolrSuggestions($suggestQuery);
87
88 3
        if ($solrSuggestions === []) {
89 1
            return ['status' => false];
90
        }
91
92 2
        $maxSuggestions = $this->typoScriptConfiguration->getSuggestNumberOfSuggestions();
93 2
        $showTopResults = $this->typoScriptConfiguration->getSuggestShowTopResults();
94 2
        $suggestions    = $this->getSuggestionArray($suggestQuery->getKeywords(), $solrSuggestions, $maxSuggestions);
95
96 2
        if (!$showTopResults) {
97 1
            return $this->getResultArray($searchRequest, $suggestions, [], false);
98
        }
99
100 1
        return $this->addTopResultsToSuggestions($searchRequest, $suggestions);
101
    }
102
103
    /**
104
     * Determines the top results and adds them to the suggestions.
105
     *
106
     * @param SearchRequest $searchRequest
107
     * @param array $suggestions
108
     * @return array
109
     */
110 1
    protected function addTopResultsToSuggestions(SearchRequest $searchRequest, $suggestions) : array
111
    {
112 1
        $maxDocuments = $this->typoScriptConfiguration->getSuggestNumberOfTopResults();
113
114
        // perform the current search.
115 1
        $searchRequest->setResultsPerPage($maxDocuments);
116
117 1
        $didASecondSearch = false;
118 1
        $documents = [];
119
120 1
        $searchResultSet = $this->doASearch($searchRequest);
121 1
        $results = $searchResultSet->getSearchResults();
122 1
        if (count($results) > 0) {
123 1
            $documents = $this->addDocumentsWhenLimitNotReached($documents, $results, $maxDocuments);
124
        }
125
126 1
        $suggestionKeys = array_keys($suggestions);
127 1
        $bestSuggestion = reset($suggestionKeys);
128 1
        $bestSuggestionRequest = $searchRequest->getCopyForSubRequest();
129 1
        $bestSuggestionRequest->setRawQueryString($bestSuggestion);
130 1
        $bestSuggestionRequest->setResultsPerPage($maxDocuments);
131
132
        // No results found, use first proposed suggestion to perform the search
133 1
        if (count($documents) === 0 && !empty($suggestions) && ($searchResultSet = $this->doASearch($bestSuggestionRequest)) && count($searchResultSet->getSearchResults()) > 0) {
134
            $didASecondSearch = true;
135
            $documentsToAdd = $searchResultSet->getSearchResults();
136
            $documents = $this->addDocumentsWhenLimitNotReached($documents, $documentsToAdd, $maxDocuments);
137
        }
138
139 1
        return $this->getResultArray($searchRequest, $suggestions, $documents, $didASecondSearch);
140
    }
141
142
    /**
143
     * Retrieves the suggestions from the solr server.
144
     *
145
     * @param SuggestQuery $suggestQuery
146
     * @return array
147
     */
148
    protected function getSolrSuggestions(SuggestQuery $suggestQuery) : array
149
    {
150
        $pageId = $this->tsfe->getRequestedId();
151
        $languageId = $this->tsfe->sys_language_uid;
152
        $solr = GeneralUtility::makeInstance(ConnectionManager::class)->getConnectionByPageId($pageId, $languageId);
153
        $search = GeneralUtility::makeInstance(Search::class, $solr);
154
        $response = $search->search($suggestQuery, 0, 0);
155
156
        $rawResponse = $response->getRawResponse();
157
        $results = json_decode($rawResponse);
158
        $suggestConfig = $this->typoScriptConfiguration->getObjectByPath('plugin.tx_solr.suggest.');
159
        $facetSuggestions = $results->facet_counts->facet_fields->{$suggestConfig['suggestField']};
160
        $facetSuggestions = get_object_vars($facetSuggestions);
161
162
        return is_null($facetSuggestions) ? [] : $facetSuggestions;
163
    }
164
165
    /**
166
     * Extracts the suggestions from solr as array.
167
     *
168
     * @param string $queryString
169
     * @param array $solrSuggestions
170
     * @param integer $maxSuggestions
171
     * @return array
172
     */
173 2
    protected function getSuggestionArray($queryString, $solrSuggestions, $maxSuggestions) : array
174
    {
175 2
        $suggestionCount = 0;
176 2
        $suggestions = [];
177 2
        foreach ($solrSuggestions as $string => $count) {
178 2
            $suggestion = trim($queryString . ' ' . $string);
179 2
            $suggestions[$suggestion] = $count;
180 2
            $suggestionCount++;
181 2
            if ($suggestionCount === $maxSuggestions) {
182 2
                return $suggestions;
183
            }
184
        }
185
186 2
        return $suggestions;
187
    }
188
189
    /**
190
     * Adds documents from a collection to the result collection as soon as the limit is not reached.
191
     *
192
     * @param array $documents
193
     * @param \Apache_Solr_Document[] $documentsToAdd
194
     * @param integer $maxDocuments
195
     * @return array
196
     */
197 1
    protected function addDocumentsWhenLimitNotReached(array $documents, array $documentsToAdd, int $maxDocuments)  : array
198
    {
199
        /** @var SearchResult $document */
200 1
        foreach ($documentsToAdd as $document) {
201 1
            $documents[] = $this->getDocumentAsArray($document);
0 ignored issues
show
Compatibility introduced by
$document of type object<Apache_Solr_Document> is not a sub-type of object<ApacheSolrForTypo...ResultSet\SearchResult>. It seems like you assume a child class of the class Apache_Solr_Document to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
202 1
            if (count($documents) >= $maxDocuments) {
203 1
                return $documents;
204
            }
205
        }
206
207
        return $documents;
208
    }
209
210
    /**
211
     * Creates an array representation of the result and returns it.
212
     *
213
     * @param SearchResult $document
214
     * @return array
215
     */
216 1
    protected function getDocumentAsArray(SearchResult $document) : array
217
    {
218
        return [
219 1
            'link' => $document->getUrl(),
220 1
            'type' => $document->getField('type_stringS') ? $document->getField('type_stringS')['value'] : $document->getType(),
221 1
            'title' => $document->getTitle(),
222 1
            'content' => $document->getContent(),
223 1
            'previewImage' => $document->getField('previewImage_stringS') ? $document->getField('previewImage_stringS')['value'] : '',
224
        ];
225
    }
226
227
    /**
228
     * Runs a search and returns the results.
229
     *
230
     * @param SearchRequest $searchRequest
231
     * @return \ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\SearchResultSet
232
     */
233 1
    protected function doASearch($searchRequest) : SearchResultSet
234
    {
235 1
        return $this->searchService->search($searchRequest);
236
    }
237
238
    /**
239
     * Creates an result array with the required fields.
240
     *
241
     * @param SearchRequest $searchRequest
242
     * @param array $suggestions
243
     * @param array $documents
244
     * @param boolean $didASecondSearch
245
     * @return array
246
     */
247 2
    protected function getResultArray(SearchRequest $searchRequest, $suggestions, $documents, $didASecondSearch) : array
248
    {
249 2
        return ['suggestions' => $suggestions, 'suggestion' => $searchRequest->getRawUserQuery(), 'documents' => $documents, 'didSecondSearch' => $didASecondSearch];
250
    }
251
252
    /**
253
     * Builds a SuggestQuery with all applied filters.
254
     *
255
     * @param string $queryString
256
     * @param string $additionalFilters
257
     * @return SuggestQuery
258
     */
259
    protected function buildSuggestQuery(string $queryString ,string $additionalFilters) : SuggestQuery
260
    {
261
        $suggestQuery = GeneralUtility::makeInstance(SuggestQuery::class, $queryString);
262
263
        $allowedSitesConfig = $this->typoScriptConfiguration->getObjectByPathOrDefault('plugin.tx_solr.search.query.', []);
264
        $siteService = GeneralUtility::makeInstance(SiteHashService::class);
265
        $allowedSites = $siteService->getAllowedSitesForPageIdAndAllowedSitesConfiguration($this->tsfe->getRequestedId(), $allowedSitesConfig['allowedSites']);
266
        $suggestQuery->setUserAccessGroups(explode(',', $this->tsfe->gr_list));
267
        $suggestQuery->setSiteHashFilter($allowedSites);
268
        $suggestQuery->setOmitHeader();
269
270
        if (!empty($allowedSitesConfig['filter.'])) {
271
            foreach ($allowedSitesConfig['filter.'] as $additionalFilter) {
272
                $suggestQuery->addFilter($additionalFilter);
273
            }
274
        }
275
276
        if (!empty($additionalFilters)) {
277
            $additionalFilters = json_decode($additionalFilters);
278
            foreach ($additionalFilters as $additionalFilter) {
279
                $suggestQuery->addFilter($additionalFilter);
280
            }
281
        }
282
283
        return $suggestQuery;
284
    }
285
}