Completed
Push — master ( 5e5f70...48902f )
by Simonas
10s
created

DynamicAggregate::addSubFilterAggregation()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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