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

DynamicAggregateFilter::fetchAggregation()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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