Completed
Pull Request — master (#188)
by
unknown
63:03
created

DynamicAggregateFilter::addSubFilterAggregation()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 2 Features 1
Metric Value
c 3
b 2
f 1
dl 0
loc 26
rs 8.8571
cc 3
eloc 17
nc 4
nop 4
1
<?php
2
3
/*
4
 * This file is part of the ONGR package.
5
 *
6
 * (c) NFQ Technologies UAB <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ONGR\FilterManagerBundle\Filter\Widget\Dynamic;
13
14
use ONGR\ElasticsearchBundle\Result\Aggregation\AggregationValue;
15
use ONGR\ElasticsearchDSL\Aggregation\FilterAggregation;
16
use ONGR\ElasticsearchDSL\Aggregation\NestedAggregation;
17
use ONGR\ElasticsearchDSL\Aggregation\TermsAggregation;
18
use ONGR\ElasticsearchDSL\BuilderInterface;
19
use ONGR\ElasticsearchDSL\Query\BoolQuery;
20
use ONGR\ElasticsearchDSL\Query\MatchAllQuery;
21
use ONGR\ElasticsearchDSL\Query\NestedQuery;
22
use ONGR\ElasticsearchDSL\Query\TermQuery;
23
use ONGR\ElasticsearchDSL\Search;
24
use ONGR\ElasticsearchBundle\Result\DocumentIterator;
25
use ONGR\FilterManagerBundle\Filter\FilterState;
26
use ONGR\FilterManagerBundle\Filter\Helper\SizeAwareTrait;
27
use ONGR\FilterManagerBundle\Filter\Helper\ViewDataFactoryInterface;
28
use ONGR\FilterManagerBundle\Filter\ViewData\AggregateViewData;
29
use ONGR\FilterManagerBundle\Filter\ViewData;
30
use ONGR\FilterManagerBundle\Filter\Widget\AbstractSingleRequestValueFilter;
31
use ONGR\FilterManagerBundle\Filter\Helper\FieldAwareInterface;
32
use ONGR\FilterManagerBundle\Filter\Helper\FieldAwareTrait;
33
use ONGR\FilterManagerBundle\Search\SearchRequest;
34
use Symfony\Component\HttpFoundation\Request;
35
36
/**
37
 * This class provides single terms choice.
38
 */
39
class DynamicAggregateFilter extends AbstractSingleRequestValueFilter implements FieldAwareInterface, ViewDataFactoryInterface
40
{
41
    use FieldAwareTrait, SizeAwareTrait;
42
43
    /**
44
     * @var array
45
     */
46
    private $sortType;
47
48
    /**
49
     * @var string
50
     */
51
    private $nameField;
52
53
    /**
54
     * @param array $sortType
55
     */
56
    public function setSortType($sortType)
57
    {
58
        $this->sortType = $sortType;
59
    }
60
61
    /**
62
     * @return array
63
     */
64
    public function getSortType()
65
    {
66
        return $this->sortType;
67
    }
68
69
    /**
70
     * @return string
71
     */
72
    public function getNameField()
73
    {
74
        return $this->nameField;
75
    }
76
77
    /**
78
     * @param string $nameField
79
     */
80
    public function setNameField($nameField)
81
    {
82
        $this->nameField = $nameField;
83
    }
84
85
    /**
86
     * {@inheritdoc}
87
     */
88
    public function modifySearch(Search $search, FilterState $state = null, SearchRequest $request = null)
89
    {
90
        list($path, $field) = explode('>', $this->getField());
91
92
        if ($state && $state->isActive()) {
93
            $boolQuery = new BoolQuery();
94
            foreach ($state->getValue() as $value) {
95
                $boolQuery->add(
96
                    new NestedQuery(
97
                        $path,
98
                        new TermQuery($field, $value)
99
                    )
100
                );
101
            }
102
            $search->addPostFilter($boolQuery);
103
        }
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function preProcessSearch(Search $search, Search $relatedSearch, FilterState $state = null)
110
    {
111
        list($path, $field) = explode('>', $this->getField());
112
        $name = $state->getName();
0 ignored issues
show
Bug introduced by
It seems like $state is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
113
        $aggregation = new NestedAggregation(
114
            $name,
115
            $path
116
        );
117
        $termsAggregation = new TermsAggregation('query', $field);
118
        $termsAggregation->addParameter('size', 0);
119
120
        if ($this->getSortType()) {
121
            $termsAggregation->addParameter('order', [$this->getSortType()['type'] => $this->getSortType()['order']]);
122
        }
123
124
        if ($this->getSize() > 0) {
125
            $termsAggregation->addParameter('size', $this->getSize());
126
        }
127
128
        $termsAggregation->addAggregation(
129
            new TermsAggregation('name', $this->getNameField())
130
        );
131
        $aggregation->addAggregation($termsAggregation);
132
        $filterAggregation = new FilterAggregation($name . '-filter');
133
        $filterAggregation->setFilter($relatedSearch->getPostFilters());
134
135
        if ($state->isActive()) {
136
            foreach ($state->getValue() as $key => $term) {
137
                $terms = $state->getValue();
138
                array_splice($terms, $key, 1);
139
140
                $this->addSubFilterAggregation(
141
                    $filterAggregation,
142
                    $aggregation,
143
                    $terms,
144
                    $term
145
                );
146
            }
147
148
            $this->addSubFilterAggregation(
149
                $filterAggregation,
150
                $aggregation,
151
                $state->getValue(),
152
                'all-selected'
153
            );
154
        } else {
155
            $filterAggregation->addAggregation($aggregation);
156
        }
157
158
        $search->addAggregation($filterAggregation);
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    public function createViewData()
165
    {
166
        return new AggregateViewData();
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    public function getViewData(DocumentIterator $result, ViewData $data)
173
    {
174
        $activeNames = [];
175
        $unsortedChoices = [];
176
177
        /** @var AggregationValue $bucket */
178
        foreach ($this->fetchAggregation($result, $data->getName(), $data->getState()->getValue()) as $aggName => $aggregation) {
179
            foreach ($aggregation as $bucket) {
180
                $active = $this->isChoiceActive($bucket['key'], $data);
181
                $choice = new ViewData\Choice();
182
                $choice->setLabel($bucket->getValue('key'));
183
                $choice->setCount($bucket['doc_count']);
184
                $choice->setActive($active);
185
186
                $choice->setUrlParameters(
187
                    $this->getOptionUrlParameters(
188
                        $data->getState()->getValue(),
189
                        $bucket['key'],
190
                        $data,
191
                        $active
192
                    )
193
                );
194
195
                if ($active && !isset($activeNames[$aggName]) && $aggName == $choice->getLabel()) {
196
                    $activeNames[$aggName] = $bucket->getAggregation('name')->getBuckets()[0]['key'];
197
                }
198
199
                $unsortedChoices[$aggName][$bucket->getAggregation('name')->getBuckets()[0]['key']][] = $choice;
200
            }
201
        }
202
203
        if ($data->getState()->isActive()) {
204
            foreach ($activeNames as $agg => $activeName) {
205
                $unsortedChoices[$activeName] = $unsortedChoices[$agg][$activeName];
206
                unset($unsortedChoices[$agg]);
207
                unset($unsortedChoices['all-selected'][$activeName]);
208
            }
209
210
            foreach ($unsortedChoices['all-selected'] as $name => $buckets) {
211
                $unsortedChoices[$name] = $buckets;
212
            }
213
214
            unset($unsortedChoices['all-selected']);
215
        } else {
216
            $unsortedChoices = $unsortedChoices['unfiltered'];
217
        }
218
219
        /** @var AggregateViewData $data */
220
        foreach ($unsortedChoices as $name => $choices) {
221
            $choiceViewData = new ViewData\ChoicesAwareViewData();
222
            $choiceViewData->setName($name);
223
            $choiceViewData->setChoices($choices);
224
            $choiceViewData->setUrlParameters([]);
225
            $choiceViewData->setResetUrlParameters([]);
226
            $data->addItem($choiceViewData);
227
        }
228
229
        return $data;
230
    }
231
232
    /**
233
     * {@inheritdoc}
234
     */
235
    public function isRelated()
236
    {
237
        return true;
238
    }
239
240
    /**
241
     * Fetches buckets from search results.
242
     *
243
     * @param DocumentIterator $result Search results.
244
     * @param string           $name   Filter name.
245
     * @param array            $values Values from the state object
246
     *
247
     * @return array Buckets.
248
     */
249
    private function fetchAggregation(DocumentIterator $result, $name, $values)
250
    {
251
        $data = [];
252
        $aggregation = $result->getAggregation(sprintf('%s-filter', $name));
253
254
        if ($aggregation->getAggregation($name)) {
255
            $aggregation = $aggregation->find($name.'.query');
256
            $data['unfiltered'] = $aggregation;
257
258
            return $data;
259
        }
260
261
        if (!empty($values)) {
262
            foreach ($values as $value) {
263
                $data[$value] = $aggregation->find(sprintf('%s.%s.query', $value, $name));
264
            }
265
266
            $data['all-selected'] = $aggregation->find(sprintf('all-selected.%s.query', $name));
267
268
            return $data;
269
        }
270
271
        return [];
272
    }
273
274
    /**
275
     * A method used to add an additional filter to the aggregations
276
     * in preProcessSearch
277
     *
278
     * @param FilterAggregation $filterAggregation
279
     * @param NestedAggregation $deepLevelAggregation
280
     * @param array             $terms Terms of additional filter
281
     * @param string            $aggName
282
     *
283
     * @return BuilderInterface
284
     */
285
    private function addSubFilterAggregation(
286
        $filterAggregation,
287
        $deepLevelAggregation,
288
        $terms,
289
        $aggName
290
    ) {
291
        list($path, $field) = explode('>', $this->getField());
292
        $boolQuery = new BoolQuery();
293
294
        foreach ($terms as $term) {
295
            $boolQuery->add(
296
                new NestedQuery($path, new TermQuery($field, $term))
297
            );
298
        }
299
300
        if ($boolQuery->getQueries() == []) {
301
            $boolQuery->add(new MatchAllQuery());
302
        }
303
304
        $innerFilterAggregation = new FilterAggregation(
305
            $aggName,
306
            $boolQuery
307
        );
308
        $innerFilterAggregation->addAggregation($deepLevelAggregation);
309
        $filterAggregation->addAggregation($innerFilterAggregation);
310
    }
311
312
    /**
313
     * @param array    $value State value array
314
     * @param string   $key
315
     * @param ViewData $data
316
     * @param bool     $active True when the choice is active
317
     *
318
     * @return array
319
     */
320
    private function getOptionUrlParameters($value, $key, ViewData $data, $active)
321
    {
322
        $parameters = $data->getResetUrlParameters();
323
324
        if (!empty($value)) {
325
            if ($active) {
326
                unset($value[array_search($key, $value)]);
327
            }
328
329
            $parameters[$this->getRequestField()] = $value;
330
        }
331
332
        $parameters[$this->getRequestField()][] = $key;
333
334
        return $parameters;
335
    }
336
337
    /**
338
     * Returns whether choice with the specified key is active.
339
     *
340
     * @param string   $key
341
     * @param ViewData $data
342
     *
343
     * @return bool
344
     */
345
    private function isChoiceActive($key, ViewData $data)
346
    {
347
        return $data->getState()->isActive() && in_array($key, $data->getState()->getValue());
348
    }
349
}
350