Passed
Push — master ( 4485bc...42e507 )
by Timo
18:39
created

Faceting::getFacetNamesWithConfiguredField()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 0
cts 0
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 1
crap 12
1
<?php
2
namespace ApacheSolrForTypo3\Solr\Query\Modifier;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2009-2015 Ingo Renner <[email protected]>
8
 *  All rights reserved
9
 *
10
 *  This script is part of the TYPO3 project. The TYPO3 project is
11
 *  free software; you can redistribute it and/or modify
12
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 2 of the License, or
14
 *  (at your option) any later version.
15
 *
16
 *  The GNU General Public License can be found at
17
 *  http://www.gnu.org/copyleft/gpl.html.
18
 *
19
 *  This script is distributed in the hope that it will be useful,
20
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 *  GNU General Public License for more details.
23
 *
24
 *  This copyright notice MUST APPEAR in all copies of the script!
25
 ***************************************************************/
26
27
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\Facets\FacetRegistry;
28
use ApacheSolrForTypo3\Solr\Domain\Search\SearchRequest;
29
use ApacheSolrForTypo3\Solr\Domain\Search\SearchRequestAware;
30
use ApacheSolrForTypo3\Solr\Query;
31
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
32
use TYPO3\CMS\Core\Utility\GeneralUtility;
33
use TYPO3\CMS\Extbase\Object\ObjectManager;
34
35
/**
36
 * Modifies a query to add faceting parameters
37
 *
38
 * @author Ingo Renner <[email protected]>
39
 * @author Daniel Poetzinger <[email protected]>
40
 * @author Sebastian Kurfuerst <[email protected]>
41
 */
42
class Faceting implements Modifier, SearchRequestAware
43
{
44
45
    /**
46
     * @var FacetRegistry
47
     */
48
    protected $facetRegistry = null;
49
50
    /**
51
     * @var SearchRequest
52
     */
53
    protected $searchRequest;
54
55
    /**
56
     * @param FacetRegistry $facetRegistry
57
     */
58
    public function __construct(FacetRegistry $facetRegistry = null)
59
    {
60
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
61
        $this->facetRegistry = is_null($facetRegistry) ? $objectManager->get(FacetRegistry::class) : $facetRegistry;
62
    }
63
64
    /**
65
     * @param SearchRequest $searchRequest
66
     */
67
    public function setSearchRequest(SearchRequest $searchRequest)
68 29
    {
69
        $this->searchRequest = $searchRequest;
70 29
    }
71 7
72
    /**
73 22
     * Modifies the given query and adds the parameters necessary for faceted
74
     * search.
75
     *
76 29
     * @param Query $query The query to modify
77 29
     * @return Query The modified query with faceting parameters
78
     */
79 29
    public function modifyQuery(Query $query)
80 29
    {
81
        $query->setFaceting();
82
        $typoScriptConfiguration = $this->searchRequest->getContextTypoScriptConfiguration();
83
        $allFacets = $typoScriptConfiguration->getSearchFacetingFacets();
84
85
        $facetParameters = $this->buildFacetingParameters($allFacets, $typoScriptConfiguration);
86
        foreach ($facetParameters as $facetParameter => $value) {
87
            $query->addQueryParameter($facetParameter, $value);
88
        }
89 29
90
        $searchArguments = $this->searchRequest->getArguments();
91 29
        if (is_array($searchArguments)) {
92 29
            $keepAllOptionsOnSelection = $typoScriptConfiguration->getSearchFacetingKeepAllFacetsOnSelection();
93 29
            $facetFilters = $this->addFacetQueryFilters($searchArguments, $allFacets, $keepAllOptionsOnSelection);
94
            foreach ($facetFilters as $filter) {
95 29
                $query->addFilter($filter);
96 29
            }
97
        }
98
99 29
        return $query;
100 3
    }
101
102
    /**
103 29
     * Delegates the parameter building to specialized functions depending on
104
     * the type of facet to add.
105
     *
106
     */
107
    protected function buildFacetingParameters($allFacets, TypoScriptConfiguration $typoScriptConfiguration)
108
    {
109
        $facetParameters = [];
110
111 29
        foreach ($allFacets as $facetName => $facetConfiguration) {
112
            $facetName = substr($facetName, 0, -1);
113 29
            $type = isset($facetConfiguration['type']) ? $facetConfiguration['type'] : 'options';
114 29
            $facetParameterBuilder = $this->facetRegistry->getPackage($type)->getQueryBuilder();
115 29
116 29
            if (is_null($facetParameterBuilder)) {
117
                throw new \InvalidArgumentException('No query build configured for facet ' . htmlspecialchars($facetName));
118 29
            }
119
120
            $facetParameters = array_merge_recursive($facetParameters, $facetParameterBuilder->build($facetName, $typoScriptConfiguration));
121
        }
122 29
123 29
        return $facetParameters;
124
    }
125 29
126
    /**
127
     * Adds filters specified through HTTP GET as filter query parameters to
128
     * the Solr query.
129
     *
130
     * @param array $resultParameters
131
     * @param array $allFacets
132 29
     * @param bool $keepAllOptionsOnSelection
133
     * @return array
134
     */
135 29
    protected function addFacetQueryFilters($resultParameters, $allFacets, $keepAllOptionsOnSelection)
136
    {
137
        $facetFilters = [];
138
139 29
        if (!is_array($resultParameters['filter'])) {
140 3
            return $facetFilters;
141
        }
142 3
143
        $filtersByFacetName = $this->getFiltersByFacetName($resultParameters, $allFacets);
144
        foreach ($filtersByFacetName as $facetName => $filterValues) {
145
            $facetConfiguration = $allFacets[$facetName . '.'];
146
            $type = isset($facetConfiguration['type']) ? $facetConfiguration['type'] : 'options';
147 3
            $filterEncoder = $this->facetRegistry->getPackage($type)->getUrlDecoder();
148 3
149
            if (is_null($filterEncoder)) {
150 3
                throw new \InvalidArgumentException('No encoder configured for facet ' . htmlspecialchars($facetName));
151 3
            }
152 3
153
            $tag = '';
154
            if ($facetConfiguration['keepAllOptionsOnSelection'] == 1 || $keepAllOptionsOnSelection) {
155
                $tag = '{!tag=' . addslashes($facetConfiguration['field']) . '}';
156 3
            }
157 3
158 3
            $filterParts = [];
159 3
            foreach ($filterValues as $filterValue) {
160
                $filterOptions = $facetConfiguration[$facetConfiguration['type'] . '.'];
161 3
                if (empty($filterOptions)) {
162
                    $filterOptions = [];
163
                }
164
165 3
                $filterValue = $filterEncoder->decode($filterValue, $filterOptions);
166 3
                $filterParts[] = $facetConfiguration['field'] . ':' . $filterValue;
167 2
            }
168
169
            $operator = ($facetConfiguration['operator'] == 'OR') ? ' OR ' : ' AND ';
170 3
            $facetFilters[] = $tag . '(' . implode($operator, $filterParts) . ')';
171 3
        }
172 3
        return $facetFilters;
173 3
    }
174 3
175
    /**
176
     * Groups facet values by facet name.
177 3
     *
178 3
     * @param array $resultParameters
179
     * @param array $allFacets
180
     * @return array
181 3
     */
182 3
    protected function getFiltersByFacetName($resultParameters, $allFacets)
183
    {
184
        // format for filter URL parameter:
185 29
        // tx_solr[filter]=$facetName0:$facetValue0,$facetName1:$facetValue1,$facetName2:$facetValue2
186
        $filters = array_map('urldecode', $resultParameters['filter']);
187
        // $filters look like ['name:value1','name:value2','fieldname2:foo']
188
        $configuredFacets = $this->getFacetNamesWithConfiguredField($allFacets);
189
        // first group the filters by facetName - so that we can
190
        // decide later whether we need to do AND or OR for multiple
191
        // filters for a certain facet/field
192 3
        // $filtersByFacetName look like ['name' =>  ['value1', 'value2'], 'fieldname2' => ['foo']]
193
        $filtersByFacetName = [];
194 3
        foreach ($filters as $filter) {
195
            // only split by the first colon to allow using colons in the filter value itself
196 3
            list($filterFacetName, $filterValue) = explode(':', $filter, 2);
197 3
            if (in_array($filterFacetName, $configuredFacets)) {
198
                $filtersByFacetName[$filterFacetName][] = $filterValue;
199 3
            }
200
        }
201
202
        return $filtersByFacetName;
203
    }
204 3
205
    /**
206
     * Gets the facets as configured through TypoScript
207 3
     *
208
     * @param array $allFacets
209
     * @return array An array of facet names as specified in TypoScript
210
     */
211
    protected function getFacetNamesWithConfiguredField(array $allFacets)
212
    {
213
        $facets = [];
214
215
        foreach ($allFacets as $facetName => $facetConfiguration) {
216
            $facetName = substr($facetName, 0, -1);
217
218
            if (empty($facetConfiguration['field'])) {
219
                // TODO later check for query and date, too
220
                continue;
221
            }
222
223
            $facets[] = $facetName;
224
        }
225
226
        return $facets;
227
    }
228
}
229