Passed
Push — master ( 064f0a...ae02be )
by Timo
49s
created

Faceting::buildFacetParameters()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 18
cts 18
cp 1
rs 8.439
c 0
b 0
f 0
cc 5
eloc 19
nc 6
nop 1
crap 5
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\FacetQueryBuilderRegistry;
28
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\Facets\FacetRegistry;
29
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\Facets\FacetUrlDecoderRegistry;
30
use ApacheSolrForTypo3\Solr\Query;
31
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
32
use ApacheSolrForTypo3\Solr\Util;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
use TYPO3\CMS\Extbase\Object\ObjectManager;
35
36
/**
37
 * Modifies a query to add faceting parameters
38
 *
39
 * @author Ingo Renner <[email protected]>
40
 * @author Daniel Poetzinger <[email protected]>
41
 * @author Sebastian Kurfuerst <[email protected]>
42
 */
43
class Faceting implements Modifier
44
{
45
    /**
46
     * @var \ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration
47
     */
48
    protected $configuration;
49
50
    protected $facetParameters = [];
51
52
    protected $facetFilters = [];
53
54
    /**
55
     * @var array
56
     */
57
    protected $allConfiguredFacets = [];
58
59
    /**
60
     * @var FacetRegistry
61
     */
62
    protected $facetRegistry = null;
63
64
    /**
65
     * @param TypoScriptConfiguration $solrConfiguration
66
     * @param FacetRegistry $facetRegistry
67
     */
68 29
    public function __construct($solrConfiguration = null, FacetRegistry $facetRegistry = null)
69
    {
70 29
        if (!is_null($solrConfiguration)) {
71 7
            $this->configuration = $solrConfiguration;
72
        } else {
73 22
            $this->configuration = Util::getSolrConfiguration();
74
        }
75
76 29
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
77 29
        $this->facetRegistry = is_null($facetRegistry) ? $objectManager->get(FacetRegistry::class) : $facetRegistry;
78
79 29
        $this->allConfiguredFacets = $this->configuration->getSearchFacetingFacets();
80 29
    }
81
82
    /**
83
     * Modifies the given query and adds the parameters necessary for faceted
84
     * search.
85
     *
86
     * @param Query $query The query to modify
87
     * @return Query The modified query with faceting parameters
88
     */
89 29
    public function modifyQuery(Query $query)
90
    {
91 29
        $query->setFaceting();
92 29
        $this->buildFacetingParameters();
93 29
        $this->addFacetQueryFilters();
94
95 29
        foreach ($this->facetParameters as $facetParameter => $value) {
96 29
            $query->addQueryParameter($facetParameter, $value);
97
        }
98
99 29
        foreach ($this->facetFilters as $filter) {
100 3
            $query->addFilter($filter);
101
        }
102
103 29
        return $query;
104
    }
105
106
    /**
107
     * Delegates the parameter building to specialized functions depending on
108
     * the type of facet to add.
109
     *
110
     */
111 29
    protected function buildFacetingParameters()
112
    {
113 29
        foreach ($this->allConfiguredFacets as $facetName => $facetConfiguration) {
114 29
            $facetName = substr($facetName, 0, -1);
115 29
            $type = isset($facetConfiguration['type']) ? $facetConfiguration['type'] : 'options';
116 29
            $facetParameterBuilder = $this->facetRegistry->getPackage($type)->getQueryBuilder();
117
118 29
            if (is_null($facetParameterBuilder)) {
119
                throw new \InvalidArgumentException('No query build configured for facet ' . htmlspecialchars($facetName));
120
            }
121
122 29
            $facetParameters = $facetParameterBuilder->build($facetName, $this->configuration);
123 29
            $this->facetParameters = array_merge_recursive($this->facetParameters, $facetParameters);
124
        }
125 29
    }
126
127
    /**
128
     * Adds filters specified through HTTP GET as filter query parameters to
129
     * the Solr query.
130
     *
131
     */
132 29
    protected function addFacetQueryFilters()
133
    {
134
        // todo refactor to use a request object
135 29
        $resultParameters = GeneralUtility::_GET('tx_solr');
136
137
        // format for filter URL parameter:
138
        // tx_solr[filter]=$facetName0:$facetValue0,$facetName1:$facetValue1,$facetName2:$facetValue2
139 29
        if (is_array($resultParameters['filter'])) {
140 3
            $filters = array_map('urldecode', $resultParameters['filter']);
141
            // $filters look like ['name:value1','name:value2','fieldname2:foo']
142 3
            $configuredFacets = $this->getConfiguredFacets();
143
            // first group the filters by facetName - so that we can
144
            // decide later whether we need to do AND or OR for multiple
145
            // filters for a certain facet/field
146
            // $filtersByFacetName look like ['name' =>  ['value1', 'value2'], 'fieldname2' => ['foo']]
147 3
            $filtersByFacetName = [];
148 3
            foreach ($filters as $filter) {
149
                // only split by the first colon to allow using colons in the filter value itself
150 3
                list($filterFacetName, $filterValue) = explode(':', $filter, 2);
151 3
                if (in_array($filterFacetName, $configuredFacets)) {
152 3
                    $filtersByFacetName[$filterFacetName][] = $filterValue;
153
                }
154
            }
155
156 3
            foreach ($filtersByFacetName as $facetName => $filterValues) {
157 3
                $facetConfiguration = $this->allConfiguredFacets[$facetName . '.'];
158 3
                $type = isset($facetConfiguration['type']) ? $facetConfiguration['type'] : 'options';
159 3
                $filterEncoder = $this->facetRegistry->getPackage($type)->getUrlDecoder();
160
161 3
                if (is_null($filterEncoder)) {
162
                    throw new \InvalidArgumentException('No encoder configured for facet ' . htmlspecialchars($facetName));
163
                }
164
165 3
                $tag = '';
166 3
                if ($facetConfiguration['keepAllOptionsOnSelection'] == 1 || $this->configuration->getSearchFacetingKeepAllFacetsOnSelection()) {
167 2
                    $tag = '{!tag=' . addslashes($facetConfiguration['field']) . '}';
168
                }
169
170 3
                $filterParts = [];
171 3
                foreach ($filterValues as $filterValue) {
172 3
                    $filterOptions = $facetConfiguration[$facetConfiguration['type'] . '.'];
173 3
                    if (empty($filterOptions)) {
174 3
                        $filterOptions = [];
175
                    }
176
177 3
                    $filterValue = $filterEncoder->decode($filterValue, $filterOptions);
178 3
                    $filterParts[] = $facetConfiguration['field'] . ':' . $filterValue;
179
                }
180
181 3
                $operator = ($facetConfiguration['operator'] == 'OR') ? ' OR ' : ' AND ';
182 3
                $this->facetFilters[] = $tag . '(' . implode($operator, $filterParts) . ')';
183
            }
184
        }
185 29
    }
186
187
    /**
188
     * Gets the facets as configured through TypoScript
189
     *
190
     * @return array An array of facet names as specified in TypoScript
191
     */
192 3
    protected function getConfiguredFacets()
193
    {
194 3
        $facets = [];
195
196 3
        foreach ($this->allConfiguredFacets as $facetName => $facetConfiguration) {
197 3
            $facetName = substr($facetName, 0, -1);
198
199 3
            if (empty($facetConfiguration['field'])) {
200
                // TODO later check for query and date, too
201
                continue;
202
            }
203
204 3
            $facets[] = $facetName;
205
        }
206
207 3
        return $facets;
208
    }
209
}
210