Passed
Push — master ( 39eae3...2e34de )
by Timo
05:33
created

SuggestService::buildSuggestQuery()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

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