Completed
Pull Request — master (#189)
by
unknown
62:23
created

DynamicAggregateFilter::isRelated()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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