Passed
Pull Request — task/3376-TYPO3_12_compatibili... (#3456)
by Rafael
43:06
created

setObjectManager()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 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
     * @return FacetRegistry
40
     */
41
    protected function getFacetRegistry(): FacetRegistry
42
    {
43
        // @extensionScannerIgnoreLine
44
        return GeneralUtility::makeInstance(FacetRegistry::class);
45
    }
46
47
    /**
48
     * The implementation can be used to influence a SearchResultSet that is
49
     * created and processed in the SearchResultSetService.
50
     *
51
     * @param SearchResultSet $resultSet
52
     * @return SearchResultSet
53
     * @throws InvalidFacetPackageException
54
     */
55
    public function process(SearchResultSet $resultSet): SearchResultSet
56
    {
57
        $resultSet = $this->parseSpellCheckingResponseIntoObjects($resultSet);
58
        $resultSet = $this->parseSortingIntoObjects($resultSet);
59
60
        // here we can reconstitute other domain objects from the solr response
61
        return $this->parseFacetsIntoObjects($resultSet);
62
    }
63
64
    /**
65
     * @param SearchResultSet $resultSet
66
     * @return SearchResultSet
67
     */
68
    protected function parseSortingIntoObjects(SearchResultSet $resultSet): SearchResultSet
69
    {
70
        $configuration = $resultSet->getUsedSearchRequest()->getContextTypoScriptConfiguration();
71
        $hasSorting = $resultSet->getUsedSearchRequest()->getHasSorting();
72
        $activeSortingName = $resultSet->getUsedSearchRequest()->getSortingName();
73
        $activeSortingDirection = $resultSet->getUsedSearchRequest()->getSortingDirection();
74
75
        // no configuration available
76
        if (!isset($configuration)) {
77
            return $resultSet;
78
        }
79
80
        // no sorting enabled
81
        if (!$configuration->getSearchSorting()) {
82
            return $resultSet;
83
        }
84
        foreach ($configuration->getSearchSortingOptionsConfiguration() as $sortingKeyName => $sortingOptions) {
85
            $sortingName = rtrim($sortingKeyName, '.');
86
            $selected = false;
87
            $direction = $configuration->getSearchSortingDefaultOrderBySortOptionName($sortingName);
88
89
            // when we have an active sorting in the request we compare the sortingName and mark is as active and
90
            // use the direction from the request
91
            if ($hasSorting && $activeSortingName == $sortingName) {
92
                $selected = true;
93
                $direction = $activeSortingDirection;
94
            }
95
96
            $field = $sortingOptions['field'];
97
            $label = $sortingOptions['label'];
98
99
            $isResetOption = $field === 'relevance';
100
101
            // Allow stdWrap on label:
102
            $labelHasSubConfiguration = is_array($sortingOptions['label.'] ?? null);
103
            if ($labelHasSubConfiguration) {
104
                $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
105
                $label = $cObj->stdWrap($label, $sortingOptions['label.']);
106
            }
107
108
            if ($isResetOption && !$hasSorting) {
109
                $selected = true;
110
            }
111
112
            /** @noinspection PhpParamsInspection */
113
            $sorting = GeneralUtility::makeInstance(
114
                Sorting::class,
115
                $resultSet,
116
                $sortingName,
117
                $field,
118
                $direction,
119
                $label,
120
                $selected,
121
                $isResetOption
122
            );
123
            $resultSet->addSorting($sorting);
124
        }
125
126
        return $resultSet;
127
    }
128
129
    /**
130
     * @param SearchResultSet $resultSet
131
     * @return SearchResultSet
132
     */
133
    private function parseSpellCheckingResponseIntoObjects(SearchResultSet $resultSet): SearchResultSet
134
    {
135
        //read the response
136
        $response = $resultSet->getResponse();
137
138
        if (!is_array($response->spellcheck->suggestions ?? null)) {
139
            return $resultSet;
140
        }
141
142
        $misspelledTerm = '';
143
        foreach ($response->spellcheck->suggestions as $key => $suggestionData) {
144
            if (is_string($suggestionData)) {
145
                $misspelledTerm = $key;
146
                continue;
147
            }
148
149
            if ($misspelledTerm === '') {
150
                throw new UnexpectedValueException('No misspelled term before suggestion');
151
            }
152
153
            if (!is_object($suggestionData) && !is_array($suggestionData->suggestion)) {
154
                continue;
155
            }
156
157
            foreach ($suggestionData->suggestion as $suggestedTerm) {
158
                $suggestion = $this->createSuggestionFromResponseFragment($suggestionData, $suggestedTerm, $misspelledTerm);
159
                //add it to the resultSet
160
                $resultSet->addSpellCheckingSuggestion($suggestion);
161
            }
162
        }
163
164
        return $resultSet;
165
    }
166
167
    /**
168
     * @param stdClass $suggestionData
169
     * @param string $suggestedTerm
170
     * @param string $misspelledTerm
171
     * @return Suggestion
172
     */
173
    private function createSuggestionFromResponseFragment(
174
        stdClass $suggestionData,
175
        string $suggestedTerm,
176
        string $misspelledTerm
177
    ): Suggestion {
178
        $numFound = $suggestionData->numFound ?? 0;
179
        $startOffset = $suggestionData->startOffset ?? 0;
180
        $endOffset = $suggestionData->endOffset ?? 0;
181
182
        // by now we avoid to use GeneralUtility::makeInstance, since we only create a value object
183
        // and the usage might be an overhead.
184
        return new Suggestion($suggestedTerm, $misspelledTerm, $numFound, $startOffset, $endOffset);
185
    }
186
187
    /**
188
     * Parse available facets into objects
189
     *
190
     * @param SearchResultSet $resultSet
191
     * @return SearchResultSet
192
     * @throws InvalidFacetPackageException
193
     */
194
    private function parseFacetsIntoObjects(SearchResultSet $resultSet): SearchResultSet
195
    {
196
        // Make sure we can access the facet configuration
197
        if (!$resultSet->getUsedSearchRequest() || !$resultSet->getUsedSearchRequest()->getContextTypoScriptConfiguration()) {
198
            return $resultSet;
199
        }
200
201
        // Read the response
202
        $response = $resultSet->getResponse();
203
        if (!is_object($response->facet_counts) && !is_object($response->facets)) {
204
            return $resultSet;
205
        }
206
207
        $facetRegistry = $this->getFacetRegistry();
208
        $facetsConfiguration = $resultSet->getUsedSearchRequest()->getContextTypoScriptConfiguration()->getSearchFacetingFacets();
209
210
        foreach ($facetsConfiguration as $name => $options) {
211
            if (!is_array($options)) {
212
                continue;
213
            }
214
            $facetName = rtrim($name, '.');
215
            $type = !empty($options['type']) ? $options['type'] : '';
216
217
            $parser = $facetRegistry->getPackage($type)->getParser();
218
            $facet = $parser->parse($resultSet, $facetName, $options);
219
            if ($facet !== null) {
220
                $resultSet->addFacet($facet);
221
            }
222
        }
223
224
        $this->applyRequirements($resultSet);
225
226
        return $resultSet;
227
    }
228
229
    /**
230
     * @param SearchResultSet $resultSet
231
     */
232
    protected function applyRequirements(SearchResultSet $resultSet)
233
    {
234
        $requirementsService = $this->getRequirementsService();
235
        $facets = $resultSet->getFacets();
236
        foreach ($facets as $facet) {
237
            /** @var $facet AbstractFacet */
238
            $requirementsMet = $requirementsService->getAllRequirementsMet($facet);
239
            $facet->setAllRequirementsMet($requirementsMet);
240
        }
241
    }
242
243
    /**
244
     * @return RequirementsService
245
     */
246
    protected function getRequirementsService(): RequirementsService
247
    {
248
        // @extensionScannerIgnoreLine
249
        return GeneralUtility::makeInstance(RequirementsService::class);
250
    }
251
}
252