Passed
Pull Request — task/3376-TYPO3_12_compatibili... (#3456)
by
unknown
44:40
created

ResultSetReconstitutionProcessor   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 215
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 32
eloc 84
dl 0
loc 215
rs 9.84
c 2
b 0
f 0

8 Methods

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