|
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 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
|
|
|
|
|
71
|
|
|
/** |
|
72
|
|
|
* SuggestService constructor. |
|
73
|
|
|
* @param TypoScriptFrontendController $tsfe |
|
74
|
|
|
* @param SearchResultSetService $searchResultSetService |
|
75
|
|
|
* @param QueryBuilder|null $queryBuilder |
|
76
|
|
|
*/ |
|
77
|
4 |
|
public function __construct(TypoScriptFrontendController $tsfe, SearchResultSetService $searchResultSetService, TypoScriptConfiguration $typoScriptConfiguration, QueryBuilder $queryBuilder = null) |
|
78
|
|
|
{ |
|
79
|
4 |
|
$this->tsfe = $tsfe; |
|
80
|
4 |
|
$this->searchService = $searchResultSetService; |
|
81
|
4 |
|
$this->typoScriptConfiguration = $typoScriptConfiguration; |
|
82
|
4 |
|
$this->queryBuilder = $queryBuilder ?? GeneralUtility::makeInstance(QueryBuilder::class, /** @scrutinizer ignore-type */ $typoScriptConfiguration); |
|
83
|
4 |
|
} |
|
84
|
|
|
|
|
85
|
|
|
/** |
|
86
|
|
|
* Build an array structure of the suggestions. |
|
87
|
|
|
* |
|
88
|
|
|
* @param SearchRequest $searchRequest |
|
89
|
|
|
* @param array $additionalFilters |
|
90
|
|
|
* @return array |
|
91
|
|
|
*/ |
|
92
|
4 |
|
public function getSuggestions(SearchRequest $searchRequest, array $additionalFilters = []) : array |
|
93
|
|
|
{ |
|
94
|
4 |
|
$requestId = (int)$this->tsfe->getRequestedId(); |
|
95
|
4 |
|
$groupList = (string)$this->tsfe->gr_list; |
|
|
|
|
|
|
96
|
|
|
|
|
97
|
4 |
|
$suggestQuery = $this->queryBuilder->buildSuggestQuery($searchRequest->getRawUserQuery(), $additionalFilters, $requestId, $groupList); |
|
98
|
4 |
|
$solrSuggestions = $this->getSolrSuggestions($suggestQuery); |
|
99
|
|
|
|
|
100
|
4 |
|
if ($solrSuggestions === []) { |
|
101
|
1 |
|
return ['status' => false]; |
|
102
|
|
|
} |
|
103
|
|
|
|
|
104
|
3 |
|
$maxSuggestions = $this->typoScriptConfiguration->getSuggestNumberOfSuggestions(); |
|
105
|
3 |
|
$showTopResults = $this->typoScriptConfiguration->getSuggestShowTopResults(); |
|
106
|
3 |
|
$suggestions = $this->getSuggestionArray($suggestQuery, $solrSuggestions, $maxSuggestions); |
|
107
|
|
|
|
|
108
|
3 |
|
if (!$showTopResults) { |
|
109
|
1 |
|
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
|
1 |
|
protected function getSolrSuggestions(SuggestQuery $suggestQuery) : array |
|
164
|
|
|
{ |
|
165
|
1 |
|
$pageId = $this->tsfe->getRequestedId(); |
|
166
|
1 |
|
$languageId = $this->tsfe->sys_language_uid; |
|
|
|
|
|
|
167
|
1 |
|
$solr = GeneralUtility::makeInstance(ConnectionManager::class)->getConnectionByPageId($pageId, $languageId); |
|
168
|
1 |
|
$search = GeneralUtility::makeInstance(Search::class, /** @scrutinizer ignore-type */ $solr); |
|
169
|
1 |
|
$response = $search->search($suggestQuery, 0, 0); |
|
170
|
|
|
|
|
171
|
1 |
|
$rawResponse = $response->getRawResponse(); |
|
172
|
1 |
|
$results = json_decode($rawResponse); |
|
173
|
1 |
|
$suggestConfig = $this->typoScriptConfiguration->getObjectByPath('plugin.tx_solr.suggest.'); |
|
174
|
1 |
|
$facetSuggestions = $results->facet_counts->facet_fields->{$suggestConfig['suggestField']}; |
|
175
|
1 |
|
$facetSuggestions = ParsingUtil::getMapArrayFromFlatArray($facetSuggestions); |
|
176
|
|
|
|
|
177
|
1 |
|
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
|
3 |
|
protected function getSuggestionArray(SuggestQuery $suggestQuery, $solrSuggestions, $maxSuggestions) : array |
|
189
|
|
|
{ |
|
190
|
3 |
|
$queryString = $suggestQuery->getQuery(); |
|
191
|
3 |
|
$suggestionCount = 0; |
|
192
|
3 |
|
$suggestions = []; |
|
193
|
3 |
|
foreach ($solrSuggestions as $string => $count) { |
|
194
|
3 |
|
$suggestion = trim($queryString . ' ' . $string); |
|
195
|
3 |
|
$suggestions[$suggestion] = $count; |
|
196
|
3 |
|
$suggestionCount++; |
|
197
|
3 |
|
if ($suggestionCount === $maxSuggestions) { |
|
198
|
3 |
|
return $suggestions; |
|
199
|
|
|
} |
|
200
|
|
|
} |
|
201
|
|
|
|
|
202
|
3 |
|
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
|
2 |
|
return $documents; |
|
221
|
|
|
} |
|
222
|
|
|
} |
|
223
|
|
|
|
|
224
|
1 |
|
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['previewImage_stringS'] ? $document['previewImage_stringS'] : '', |
|
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
|
3 |
|
protected function getResultArray(SearchRequest $searchRequest, $suggestions, $documents, $didASecondSearch) : array |
|
271
|
|
|
{ |
|
272
|
3 |
|
return ['suggestions' => $suggestions, 'suggestion' => $searchRequest->getRawUserQuery(), 'documents' => $documents, 'didSecondSearch' => $didASecondSearch]; |
|
273
|
|
|
} |
|
274
|
|
|
|
|
275
|
|
|
|
|
276
|
|
|
} |
|
277
|
|
|
|
This property has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.