parseFacetsIntoObjects()   B
last analyzed

Complexity

Conditions 9
Paths 8

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9.0117

Importance

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