Completed
Pull Request — master (#193)
by
unknown
192:45 queued 127:52
created

DynamicAggregate::getShowZeroChoices()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
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
        $search->addAggregation($aggregation);
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206
    public function createViewData()
207
    {
208
        return new AggregateViewData();
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function getViewData(DocumentIterator $result, ViewData $data)
215
    {
216
        $unsortedChoices = [];
217
        $activeNames = $data->getState()->isActive() ? array_keys($data->getState()->getValue()) : [];
218
        $filterAggregations = $this->fetchAggregation($result, $data->getName(), $data->getState()->getValue());
219
220
        if ($this->getShowZeroChoices() && $data->getState()->isActive()) {
221
            $unsortedChoices = $this->formInitialUnsortedChoices($result, $data);
222
        }
223
224
        /** @var AggregationValue $bucket */
225
        foreach ($filterAggregations as $activeName => $aggregation) {
226
            foreach ($aggregation as $bucket) {
227
                $name = $bucket->getAggregation('name')->getBuckets()[0]['key'];
228
229
                if ($name != $activeName && $activeName != 'all-selected') {
230
                    continue;
231
                }
232
233
                $active = $this->isChoiceActive($bucket['key'], $data, $activeName);
234
                $choice = new ViewData\Choice();
235
                $choice->setLabel($bucket->getValue('key'));
236
                $choice->setCount($bucket['doc_count']);
237
                $choice->setActive($active);
238
239
                $choice->setUrlParameters(
240
                    $this->getOptionUrlParameters($bucket['key'], $name, $data, $active)
241
                );
242
243
                if ($activeName == 'all-selected') {
244
                    $unsortedChoices[$activeName][$name][$bucket['key']] = $choice;
245
                } else {
246
                    $unsortedChoices[$activeName][$bucket['key']] = $choice;
247
                }
248
            }
249
        }
250
251
        if (isset($unsortedChoices['all-selected'])) {
252
            foreach ($unsortedChoices['all-selected'] as $name => $buckets) {
253
                if (in_array($name, $activeNames)) {
254
                    continue;
255
                }
256
257
                $unsortedChoices[$name] = $buckets;
258
            }
259
260
            unset($unsortedChoices['all-selected']);
261
        }
262
263
        ksort($unsortedChoices);
264
265
        /** @var AggregateViewData $data */
266
        foreach ($unsortedChoices as $name => $choices) {
267
            $choiceViewData = new ViewData\ChoicesAwareViewData();
268
            $choiceViewData->setName($name);
269
            $choiceViewData->setChoices($choices);
270
            $choiceViewData->setUrlParameters([]);
271
            $choiceViewData->setResetUrlParameters([]);
272
            $data->addItem($choiceViewData);
273
        }
274
275
        return $data;
276
    }
277
278
    /**
279
     * {@inheritdoc}
280
     */
281
    public function isRelated()
282
    {
283
        return true;
284
    }
285
286
    /**
287
     * Fetches buckets from search results.
288
     *
289
     * @param DocumentIterator $result     Search results.
290
     * @param string           $filterName Filter name.
291
     * @param array            $values     Values from the state object
292
     *
293
     * @return array Buckets.
294
     */
295 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...
296
    {
297
        $data = [];
298
        $aggregation = $result->getAggregation(sprintf('%s-filter', $filterName));
299
300
        if ($aggregation->getAggregation($filterName)) {
301
            $aggregation = $aggregation->find($filterName.'.query');
302
            $data['all-selected'] = $aggregation;
303
304
            return $data;
305
        }
306
307
        if (!empty($values)) {
308
            foreach ($values as $name => $value) {
309
                $data[$name] = $aggregation->find(sprintf('%s.%s.query', $name, $filterName));
310
            }
311
312
            $data['all-selected'] = $aggregation->find(sprintf('all-selected.%s.query', $filterName));
313
314
            return $data;
315
        }
316
317
        return [];
318
    }
319
320
    /**
321
     * A method used to add an additional filter to the aggregations
322
     * in preProcessSearch
323
     *
324
     * @param FilterAggregation $filterAggregation
325
     * @param NestedAggregation $deepLevelAggregation
326
     * @param array             $terms Terms of additional filter
327
     * @param string            $aggName
328
     *
329
     * @return BuilderInterface
330
     */
331 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...
332
        $filterAggregation,
333
        $deepLevelAggregation,
334
        $terms,
335
        $aggName
336
    ) {
337
        list($path, $field) = explode('>', $this->getField());
338
        $boolQuery = new BoolQuery();
339
340
        foreach ($terms as $term) {
341
            $boolQuery->add(
342
                new NestedQuery($path, new TermQuery($field, $term))
343
            );
344
        }
345
346
        if ($boolQuery->getQueries() == []) {
347
            $boolQuery->add(new MatchAllQuery());
348
        }
349
350
        $innerFilterAggregation = new FilterAggregation(
351
            $aggName,
352
            $boolQuery
353
        );
354
        $innerFilterAggregation->addAggregation($deepLevelAggregation);
355
        $filterAggregation->addAggregation($innerFilterAggregation);
356
    }
357
358
    /**
359
     * @param string   $key
360
     * @param string   $name
361
     * @param ViewData $data
362
     * @param bool     $active True when the choice is active
363
     *
364
     * @return array
365
     */
366
    protected function getOptionUrlParameters($key, $name, ViewData $data, $active)
367
    {
368
        $value = $data->getState()->getValue();
369
        $parameters = $data->getResetUrlParameters();
370
371 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...
372
            if ($active) {
373
                unset($value[array_search($key, $value)]);
374
                $parameters[$this->getRequestField()] = $value;
375
376
                return $parameters;
377
            }
378
379
            $parameters[$this->getRequestField()] = $value;
380
        }
381
382
        $parameters[$this->getRequestField()][$name] = $key;
383
384
        return $parameters;
385
    }
386
387
    /**
388
     * Returns whether choice with the specified key is active.
389
     *
390
     * @param string   $key
391
     * @param ViewData $data
392
     * @param string   $activeName
393
     *
394
     * @return bool
395
     */
396
    protected function isChoiceActive($key, ViewData $data, $activeName)
397
    {
398
        return $data->getState()->isActive() && in_array($key, $data->getState()->getValue());
399
    }
400
401
    /**
402
     * Forms $unsortedChoices array with all possible choices.
403
     * 0 is assigned to the document count of the choices.
404
     *
405
     * @param DocumentIterator $result
406
     * @param ViewData         $data
407
     *
408
     * @return array
409
     */
410
    private function formInitialUnsortedChoices($result, $data)
411
    {
412
        $unsortedChoices = [];
413
        $urlParameters = array_merge(
414
            $data->getResetUrlParameters(),
415
            $data->getState()->getUrlParameters()
416
        );
417
418
        foreach ($result->getAggregation($data->getName())->getAggregation('query') as $bucket) {
419
            $groupName = $bucket->getAggregation('name')->getBuckets()[0]['key'];
420
            $choice = new ViewData\Choice();
421
            $choice->setActive(false);
422
            $choice->setUrlParameters($urlParameters);
423
            $choice->setLabel($bucket['key']);
424
            $choice->setCount(0);
425
            $unsortedChoices[$groupName][$bucket['key']] = $choice;
426
            $unsortedChoices['all-selected'][$groupName][$bucket['key']] = $choice;
427
        }
428
429
        return $unsortedChoices;
430
    }
431
}
432