Completed
Push — master ( 4105ae...3a2e0f )
by Simonas
198:38 queued 133:44
created

DynamicAggregate::getShowZeroChoices()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
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 DynamicAggregate extends AbstractSingleRequestValueFilter implements
40
    FieldAwareInterface,
41
    ViewDataFactoryInterface
42
{
43
    use FieldAwareTrait;
44
45
    /**
46
     * @var array
47
     */
48
    private $sortType;
49
50
    /**
51
     * @var string
52
     */
53
    private $nameField;
54
55
    /**
56
     * @var bool
57
     */
58
    private $showZeroChoices;
59
60
    /**
61
     * @param array $sortType
62
     */
63
    public function setSortType($sortType)
64
    {
65
        $this->sortType = $sortType;
66
    }
67
68
    /**
69
     * @return array
70
     */
71
    public function getSortType()
72
    {
73
        return $this->sortType;
74
    }
75
76
    /**
77
     * @return string
78
     */
79
    public function getNameField()
80
    {
81
        return $this->nameField;
82
    }
83
84
    /**
85
     * @param string $nameField
86
     */
87
    public function setNameField($nameField)
88
    {
89
        $this->nameField = $nameField;
90
    }
91
92
    /**
93
     * @return bool
94
     */
95
    public function getShowZeroChoices()
96
    {
97
        return $this->showZeroChoices;
98
    }
99
100
    /**
101
     * @param bool $showZeroChoices
102
     */
103
    public function setShowZeroChoices($showZeroChoices)
104
    {
105
        $this->showZeroChoices = $showZeroChoices;
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111
    public function getState(Request $request)
112
    {
113
        $state = new FilterState();
114
        $value = $request->get($this->getRequestField());
115
116
        if (isset($value) && is_array($value)) {
117
            $state->setActive(true);
118
            $state->setValue($value);
119
            $state->setUrlParameters([$this->getRequestField() => $value]);
120
        }
121
122
        return $state;
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128
    public function modifySearch(Search $search, FilterState $state = null, SearchRequest $request = null)
129
    {
130
        list($path, $field) = explode('>', $this->getField());
131
132
        if ($state && $state->isActive()) {
133
            $boolQuery = new BoolQuery();
134
            foreach ($state->getValue() as $value) {
135
                $boolQuery->add(
136
                    new NestedQuery(
137
                        $path,
138
                        new TermQuery($field, $value)
139
                    )
140
                );
141
            }
142
            $search->addPostFilter($boolQuery);
143
        }
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function preProcessSearch(Search $search, Search $relatedSearch, FilterState $state = null)
150
    {
151
        list($path, $field) = explode('>', $this->getField());
152
        $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...
153
        $aggregation = new NestedAggregation(
154
            $name,
155
            $path
156
        );
157
        $termsAggregation = new TermsAggregation('query', $field);
158
        $termsAggregation->addParameter('size', 0);
159
160
        if ($this->getSortType()) {
161
            $termsAggregation->addParameter('order', [$this->getSortType()['type'] => $this->getSortType()['order']]);
162
        }
163
164
        $termsAggregation->addAggregation(
165
            new TermsAggregation('name', $this->getNameField())
166
        );
167
        $aggregation->addAggregation($termsAggregation);
168
        $filterAggregation = new FilterAggregation($name . '-filter');
169
170
        if (!empty($relatedSearch->getPostFilters())) {
171
            $filterAggregation->setFilter($relatedSearch->getPostFilters());
172
        } else {
173
            $filterAggregation->setFilter(new MatchAllQuery());
174
        }
175
176
        if ($state->isActive()) {
177
            foreach ($state->getValue() as $key => $term) {
178
                $terms = $state->getValue();
179
                unset($terms[$key]);
180
181
                $this->addSubFilterAggregation(
182
                    $filterAggregation,
183
                    $aggregation,
184
                    $terms,
185
                    $key
186
                );
187
            }
188
189
            $this->addSubFilterAggregation(
190
                $filterAggregation,
191
                $aggregation,
192
                $state->getValue(),
193
                'all-selected'
194
            );
195
        } else {
196
            $filterAggregation->addAggregation($aggregation);
197
        }
198
199
        $search->addAggregation($filterAggregation);
200
201
        if ($this->getShowZeroChoices()) {
202
            $search->addAggregation($aggregation);
203
        }
204
    }
205
206
    /**
207
     * {@inheritdoc}
208
     */
209
    public function createViewData()
210
    {
211
        return new AggregateViewData();
212
    }
213
214
    /**
215
     * {@inheritdoc}
216
     */
217
    public function getViewData(DocumentIterator $result, ViewData $data)
218
    {
219
        $unsortedChoices = [];
220
        $activeNames = $data->getState()->isActive() ? array_keys($data->getState()->getValue()) : [];
221
        $filterAggregations = $this->fetchAggregation($result, $data->getName(), $data->getState()->getValue());
222
223
        if ($this->getShowZeroChoices() && $data->getState()->isActive()) {
224
            $unsortedChoices = $this->formInitialUnsortedChoices($result, $data);
225
        }
226
227
        /** @var AggregationValue $bucket */
228
        foreach ($filterAggregations as $activeName => $aggregation) {
229
            foreach ($aggregation as $bucket) {
230
                $name = $bucket->getAggregation('name')->getBuckets()[0]['key'];
231
232
                if ($name != $activeName && $activeName != 'all-selected') {
233
                    continue;
234
                }
235
236
                $active = $this->isChoiceActive($bucket['key'], $data, $activeName);
237
                $choice = new ViewData\Choice();
238
                $choice->setLabel($bucket->getValue('key'));
239
                $choice->setCount($bucket['doc_count']);
240
                $choice->setActive($active);
241
242
                $choice->setUrlParameters(
243
                    $this->getOptionUrlParameters($bucket['key'], $name, $data, $active)
244
                );
245
246
                if ($activeName == 'all-selected') {
247
                    $unsortedChoices[$activeName][$name][$bucket['key']] = $choice;
248
                } else {
249
                    $unsortedChoices[$activeName][$bucket['key']] = $choice;
250
                }
251
            }
252
        }
253
254
        if (isset($unsortedChoices['all-selected'])) {
255
            foreach ($unsortedChoices['all-selected'] as $name => $buckets) {
256
                if (in_array($name, $activeNames)) {
257
                    continue;
258
                }
259
260
                $unsortedChoices[$name] = $buckets;
261
            }
262
263
            unset($unsortedChoices['all-selected']);
264
        }
265
266
        ksort($unsortedChoices);
267
268
        /** @var AggregateViewData $data */
269
        foreach ($unsortedChoices as $name => $choices) {
270
            $choiceViewData = new ViewData\ChoicesAwareViewData();
271
            $choiceViewData->setName($name);
272
            $choiceViewData->setChoices($choices);
273
            $choiceViewData->setUrlParameters([]);
274
            $choiceViewData->setResetUrlParameters([]);
275
            $data->addItem($choiceViewData);
276
        }
277
278
        return $data;
279
    }
280
281
    /**
282
     * {@inheritdoc}
283
     */
284
    public function isRelated()
285
    {
286
        return true;
287
    }
288
289
    /**
290
     * Fetches buckets from search results.
291
     *
292
     * @param DocumentIterator $result     Search results.
293
     * @param string           $filterName Filter name.
294
     * @param array            $values     Values from the state object
295
     *
296
     * @return array Buckets.
297
     */
298 View Code Duplication
    protected function fetchAggregation(DocumentIterator $result, $filterName, $values)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
299
    {
300
        $data = [];
301
        $aggregation = $result->getAggregation(sprintf('%s-filter', $filterName));
302
303
        if ($aggregation->getAggregation($filterName)) {
304
            $aggregation = $aggregation->find($filterName.'.query');
305
            $data['all-selected'] = $aggregation;
306
307
            return $data;
308
        }
309
310
        if (!empty($values)) {
311
            foreach ($values as $name => $value) {
312
                $data[$name] = $aggregation->find(sprintf('%s.%s.query', $name, $filterName));
313
            }
314
315
            $data['all-selected'] = $aggregation->find(sprintf('all-selected.%s.query', $filterName));
316
317
            return $data;
318
        }
319
320
        return [];
321
    }
322
323
    /**
324
     * A method used to add an additional filter to the aggregations
325
     * in preProcessSearch
326
     *
327
     * @param FilterAggregation $filterAggregation
328
     * @param NestedAggregation $deepLevelAggregation
329
     * @param array             $terms Terms of additional filter
330
     * @param string            $aggName
331
     *
332
     * @return BuilderInterface
333
     */
334 View Code Duplication
    protected function addSubFilterAggregation(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
335
        $filterAggregation,
336
        $deepLevelAggregation,
337
        $terms,
338
        $aggName
339
    ) {
340
        list($path, $field) = explode('>', $this->getField());
341
        $boolQuery = new BoolQuery();
342
343
        foreach ($terms as $term) {
344
            $boolQuery->add(
345
                new NestedQuery($path, new TermQuery($field, $term))
346
            );
347
        }
348
349
        if ($boolQuery->getQueries() == []) {
350
            $boolQuery->add(new MatchAllQuery());
351
        }
352
353
        $innerFilterAggregation = new FilterAggregation(
354
            $aggName,
355
            $boolQuery
356
        );
357
        $innerFilterAggregation->addAggregation($deepLevelAggregation);
358
        $filterAggregation->addAggregation($innerFilterAggregation);
359
    }
360
361
    /**
362
     * @param string   $key
363
     * @param string   $name
364
     * @param ViewData $data
365
     * @param bool     $active True when the choice is active
366
     *
367
     * @return array
368
     */
369
    protected function getOptionUrlParameters($key, $name, ViewData $data, $active)
370
    {
371
        $value = $data->getState()->getValue();
372
        $parameters = $data->getResetUrlParameters();
373
374 View Code Duplication
        if (!empty($value)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
375
            if ($active) {
376
                unset($value[array_search($key, $value)]);
377
                $parameters[$this->getRequestField()] = $value;
378
379
                return $parameters;
380
            }
381
382
            $parameters[$this->getRequestField()] = $value;
383
        }
384
385
        $parameters[$this->getRequestField()][$name] = $key;
386
387
        return $parameters;
388
    }
389
390
    /**
391
     * Returns whether choice with the specified key is active.
392
     *
393
     * @param string   $key
394
     * @param ViewData $data
395
     * @param string   $activeName
396
     *
397
     * @return bool
398
     */
399
    protected function isChoiceActive($key, ViewData $data, $activeName)
400
    {
401
        return $data->getState()->isActive() && in_array($key, $data->getState()->getValue());
402
    }
403
404
    /**
405
     * Forms $unsortedChoices array with all possible choices.
406
     * 0 is assigned to the document count of the choices.
407
     *
408
     * @param DocumentIterator $result
409
     * @param ViewData         $data
410
     *
411
     * @return array
412
     */
413
    private function formInitialUnsortedChoices($result, $data)
414
    {
415
        $unsortedChoices = [];
416
        $urlParameters = array_merge(
417
            $data->getResetUrlParameters(),
418
            $data->getState()->getUrlParameters()
419
        );
420
421
        foreach ($result->getAggregation($data->getName())->getAggregation('query') as $bucket) {
422
            $groupName = $bucket->getAggregation('name')->getBuckets()[0]['key'];
423
            $choice = new ViewData\Choice();
424
            $choice->setActive(false);
425
            $choice->setUrlParameters($urlParameters);
426
            $choice->setLabel($bucket['key']);
427
            $choice->setCount(0);
428
            $unsortedChoices[$groupName][$bucket['key']] = $choice;
429
            $unsortedChoices['all-selected'][$groupName][$bucket['key']] = $choice;
430
        }
431
432
        return $unsortedChoices;
433
    }
434
}
435