1
|
|
|
<?php |
2
|
|
|
namespace ApacheSolrForTypo3\Solr\Domain\Search\ResultSet; |
3
|
|
|
|
4
|
|
|
/* |
5
|
|
|
* This file is part of the TYPO3 CMS project. |
6
|
|
|
* |
7
|
|
|
* It is free software; you can redistribute it and/or modify it under |
8
|
|
|
* the terms of the GNU General Public License, either version 2 |
9
|
|
|
* of the License, or any later version. |
10
|
|
|
* |
11
|
|
|
* For the full copyright and license information, please read the |
12
|
|
|
* LICENSE.txt file that was distributed with this source code. |
13
|
|
|
* |
14
|
|
|
* The TYPO3 project - inspiring people to share! |
15
|
|
|
*/ |
16
|
|
|
|
17
|
|
|
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\Facets\AbstractFacet; |
18
|
|
|
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\Facets\FacetRegistry; |
19
|
|
|
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\Facets\RequirementsService; |
20
|
|
|
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\Sorting\Sorting; |
21
|
|
|
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\Spellchecking\Suggestion; |
22
|
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility; |
23
|
|
|
use TYPO3\CMS\Extbase\Object\ObjectManager; |
24
|
|
|
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* This processor is used to transform the solr response into a |
28
|
|
|
* domain object hierarchy that can be used in the application (controller and view). |
29
|
|
|
* |
30
|
|
|
* @author Frans Saris <[email protected]> |
31
|
|
|
* @author Timo Hund <[email protected]> |
32
|
|
|
* @package ApacheSolrForTypo3\Solr\Domain\Search\ResultSet |
33
|
|
|
*/ |
34
|
|
|
class ResultSetReconstitutionProcessor implements SearchResultSetProcessor |
35
|
|
|
{ |
36
|
|
|
/** |
37
|
|
|
* @var ObjectManagerInterface |
38
|
|
|
*/ |
39
|
|
|
protected $objectManager; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @return ObjectManagerInterface |
43
|
|
|
*/ |
44
|
53 |
|
public function getObjectManager() |
45
|
|
|
{ |
46
|
53 |
|
if ($this->objectManager === null) { |
47
|
26 |
|
$this->objectManager = GeneralUtility::makeInstance(ObjectManager::class); |
48
|
|
|
} |
49
|
53 |
|
return $this->objectManager; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @param ObjectManagerInterface $objectManager |
54
|
|
|
*/ |
55
|
27 |
|
public function setObjectManager($objectManager) |
56
|
|
|
{ |
57
|
27 |
|
$this->objectManager = $objectManager; |
58
|
27 |
|
} |
59
|
|
|
|
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* @return FacetRegistry |
63
|
|
|
*/ |
64
|
53 |
|
protected function getFacetRegistry() |
65
|
|
|
{ |
66
|
53 |
|
return $this->getObjectManager()->get(FacetRegistry::class); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* The implementation can be used to influence a SearchResultSet that is |
71
|
|
|
* created and processed in the SearchResultSetService. |
72
|
|
|
* |
73
|
|
|
* @param SearchResultSet $resultSet |
74
|
|
|
* @return SearchResultSet |
75
|
|
|
*/ |
76
|
65 |
|
public function process(SearchResultSet $resultSet) |
77
|
|
|
{ |
78
|
65 |
|
if (!$resultSet instanceof SearchResultSet) { |
79
|
|
|
return $resultSet; |
80
|
|
|
} |
81
|
|
|
|
82
|
65 |
|
$resultSet = $this->parseResultCount($resultSet); |
83
|
65 |
|
$resultSet = $this->parseSpellCheckingResponseIntoObjects($resultSet); |
84
|
65 |
|
$resultSet = $this->parseSortingIntoObjects($resultSet); |
85
|
|
|
|
86
|
|
|
// here we can reconstitute other domain objects from the solr response |
87
|
65 |
|
$resultSet = $this->parseFacetsIntoObjects($resultSet); |
88
|
|
|
|
89
|
65 |
|
return $resultSet; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* @param SearchResultSet $resultSet |
94
|
|
|
* @return SearchResultSet |
95
|
|
|
*/ |
96
|
65 |
|
protected function parseResultCount(SearchResultSet $resultSet) |
97
|
|
|
{ |
98
|
65 |
|
$response = $resultSet->getResponse(); |
99
|
65 |
|
if (!isset($response->response->numFound)) { |
|
|
|
|
100
|
6 |
|
return $resultSet; |
101
|
|
|
} |
102
|
|
|
|
103
|
59 |
|
$resultSet->setAllResultCount($response->response->numFound); |
|
|
|
|
104
|
59 |
|
return $resultSet; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* @param SearchResultSet $resultSet |
109
|
|
|
* @return SearchResultSet |
110
|
|
|
*/ |
111
|
65 |
|
protected function parseSortingIntoObjects(SearchResultSet $resultSet) |
112
|
|
|
{ |
113
|
65 |
|
$configuration = $resultSet->getUsedSearchRequest()->getContextTypoScriptConfiguration(); |
114
|
65 |
|
$hasSorting = $resultSet->getUsedSearchRequest()->getHasSorting(); |
115
|
65 |
|
$activeSortingName = $resultSet->getUsedSearchRequest()->getSortingName(); |
116
|
65 |
|
$activeSortingDirection = $resultSet->getUsedSearchRequest()->getSortingDirection(); |
117
|
|
|
|
118
|
|
|
// no configuration available |
119
|
65 |
|
if (!isset($configuration)) { |
120
|
8 |
|
return $resultSet; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
// no sorting enabled |
124
|
57 |
|
if (!$configuration->getSearchSorting()) { |
125
|
29 |
|
return $resultSet; |
126
|
|
|
} |
127
|
28 |
|
foreach ($configuration->getSearchSortingOptionsConfiguration() as $sortingKeyName => $sortingOptions) { |
128
|
28 |
|
$sortingName = rtrim($sortingKeyName, '.'); |
129
|
28 |
|
$selected = false; |
130
|
28 |
|
$direction = $configuration->getSearchSortingDefaultOrderBySortOptionName($sortingName); |
131
|
|
|
|
132
|
|
|
// when we have an active sorting in the request we compare the sortingName and mark is as active and |
133
|
|
|
// use the direction from the request |
134
|
28 |
|
if ($hasSorting && $activeSortingName == $sortingName) { |
135
|
1 |
|
$selected = true; |
136
|
1 |
|
$direction = $activeSortingDirection; |
137
|
|
|
} |
138
|
|
|
|
139
|
28 |
|
$field = $sortingOptions['field']; |
140
|
28 |
|
$label = $sortingOptions['label']; |
141
|
|
|
|
142
|
28 |
|
$isResetOption = $field === 'relevance'; |
143
|
|
|
// @todo allow stdWrap on label |
144
|
28 |
|
$sorting = new Sorting($resultSet, $sortingName, $field, $direction, $label, $selected, $isResetOption); |
145
|
28 |
|
$resultSet->addSorting($sorting); |
146
|
|
|
} |
147
|
|
|
|
148
|
28 |
|
return $resultSet; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* @param SearchResultSet $resultSet |
153
|
|
|
* @return SearchResultSet |
154
|
|
|
*/ |
155
|
65 |
|
private function parseSpellCheckingResponseIntoObjects(SearchResultSet $resultSet) |
156
|
|
|
{ |
157
|
|
|
//read the response |
158
|
65 |
|
$response = $resultSet->getResponse(); |
159
|
65 |
|
if (!is_object($response->spellcheck->suggestions)) { |
160
|
58 |
|
return $resultSet; |
161
|
|
|
} |
162
|
|
|
|
163
|
7 |
|
foreach ($response->spellcheck->suggestions as $key => $suggestionData) { |
164
|
4 |
|
if (!isset($suggestionData->suggestion) && !is_array($suggestionData->suggestion)) { |
165
|
1 |
|
continue; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
// the key contains the misspelled word expect the internal key "collation" |
169
|
4 |
|
if ($key == 'collation') { |
170
|
|
|
continue; |
171
|
|
|
} |
172
|
|
|
//create the spellchecking object structure |
173
|
4 |
|
$misspelledTerm = $key; |
174
|
4 |
|
foreach ($suggestionData->suggestion as $suggestedTerm) { |
175
|
4 |
|
$suggestion = $this->createSuggestionFromResponseFragment($suggestionData, $suggestedTerm, $misspelledTerm); |
176
|
|
|
|
177
|
|
|
//add it to the resultSet |
178
|
4 |
|
$resultSet->addSpellCheckingSuggestion($suggestion); |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
|
182
|
7 |
|
return $resultSet; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* @param \stdClass $suggestionData |
187
|
|
|
* @param string $suggestedTerm |
188
|
|
|
* @param string $misspelledTerm |
189
|
|
|
* @return Suggestion |
190
|
|
|
*/ |
191
|
4 |
|
private function createSuggestionFromResponseFragment($suggestionData, $suggestedTerm, $misspelledTerm) |
192
|
|
|
{ |
193
|
4 |
|
$numFound = isset($suggestionData->numFound) ? $suggestionData->numFound : 0; |
194
|
4 |
|
$startOffset = isset($suggestionData->startOffset) ? $suggestionData->startOffset : 0; |
195
|
4 |
|
$endOffset = isset($suggestionData->endOffset) ? $suggestionData->endOffset : 0; |
196
|
|
|
|
197
|
|
|
// by now we avoid to use GeneralUtility::makeInstance, since we only create a value object |
198
|
|
|
// and the usage might be a overhead. |
199
|
4 |
|
$suggestion = new Suggestion($suggestedTerm, $misspelledTerm, $numFound, $startOffset, $endOffset); |
200
|
4 |
|
return $suggestion; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Parse available facets into objects |
205
|
|
|
* |
206
|
|
|
* @param SearchResultSet $resultSet |
207
|
|
|
* @return SearchResultSet |
208
|
|
|
*/ |
209
|
65 |
|
private function parseFacetsIntoObjects(SearchResultSet $resultSet) |
210
|
|
|
{ |
211
|
|
|
// Make sure we can access the facet configuration |
212
|
65 |
|
if (!$resultSet->getUsedSearchRequest() || !$resultSet->getUsedSearchRequest()->getContextTypoScriptConfiguration()) { |
213
|
8 |
|
return $resultSet; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
// Read the response |
217
|
57 |
|
$response = $resultSet->getResponse(); |
218
|
57 |
|
if (!is_object($response->facet_counts)) { |
219
|
4 |
|
return $resultSet; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** @var FacetRegistry $facetRegistry */ |
223
|
53 |
|
$facetRegistry = $this->getFacetRegistry(); |
224
|
53 |
|
$facetsConfiguration = $resultSet->getUsedSearchRequest()->getContextTypoScriptConfiguration()->getSearchFacetingFacets(); |
225
|
|
|
|
226
|
53 |
|
foreach ($facetsConfiguration as $name => $options) { |
227
|
51 |
|
if (!is_array($options)) { |
228
|
|
|
continue; |
229
|
|
|
} |
230
|
51 |
|
$facetName = rtrim($name, '.'); |
231
|
51 |
|
$type = !empty($options['type']) ? $options['type'] : ''; |
232
|
|
|
|
233
|
51 |
|
$parser = $facetRegistry->getPackage($type)->getParser(); |
234
|
51 |
|
$facet = $parser->parse($resultSet, $facetName, $options); |
235
|
51 |
|
if ($facet !== null) { |
236
|
51 |
|
$resultSet->addFacet($facet); |
237
|
|
|
} |
238
|
|
|
} |
239
|
|
|
|
240
|
53 |
|
$this->applyRequirements($resultSet); |
241
|
|
|
|
242
|
53 |
|
return $resultSet; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* @param SearchResultSet $resultSet |
247
|
|
|
*/ |
248
|
53 |
|
protected function applyRequirements(SearchResultSet $resultSet) |
249
|
|
|
{ |
250
|
53 |
|
$requirementsService = $this->getRequirementsService(); |
251
|
53 |
|
$facets = $resultSet->getFacets(); |
252
|
53 |
|
foreach ($facets as $facet) { |
253
|
|
|
/** @var $facet AbstractFacet */ |
254
|
49 |
|
$requirementsMet = $requirementsService->getAllRequirementsMet($facet); |
255
|
49 |
|
$facet->setAllRequirementsMet($requirementsMet); |
256
|
|
|
} |
257
|
53 |
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* @return RequirementsService |
261
|
|
|
*/ |
262
|
53 |
|
protected function getRequirementsService() |
263
|
|
|
{ |
264
|
53 |
|
return $this->getObjectManager()->get(RequirementsService::class); |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.