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 $groupName => $value) { |
135
|
|
|
$innerBoolQuery = new BoolQuery(); |
136
|
|
|
$nestedQuery = new NestedQuery($path, $innerBoolQuery); |
137
|
|
|
$innerBoolQuery->add( |
138
|
|
|
new TermQuery($field, $value) |
139
|
|
|
); |
140
|
|
|
$innerBoolQuery->add( |
141
|
|
|
new TermQuery($this->getNameField(), $groupName) |
142
|
|
|
); |
143
|
|
|
$boolQuery->add($nestedQuery); |
144
|
|
|
} |
145
|
|
|
$search->addPostFilter($boolQuery); |
146
|
|
|
} |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* {@inheritdoc} |
151
|
|
|
*/ |
152
|
|
|
public function preProcessSearch(Search $search, Search $relatedSearch, FilterState $state = null) |
153
|
|
|
{ |
154
|
|
|
list($path, $field) = explode('>', $this->getField()); |
155
|
|
|
$filter = !empty($filter = $relatedSearch->getPostFilters()) ? $filter : new MatchAllQuery(); |
156
|
|
|
$aggregation = new NestedAggregation($state->getName(), $path); |
|
|
|
|
157
|
|
|
$nameAggregation = new TermsAggregation('name', $this->getNameField()); |
|
|
|
|
158
|
|
|
$valueAggregation = new TermsAggregation('value', $field); |
|
|
|
|
159
|
|
|
$filterAggregation = new FilterAggregation($state->getName() . '-filter'); |
|
|
|
|
160
|
|
|
$nameAggregation->addAggregation($valueAggregation); |
161
|
|
|
$aggregation->addAggregation($nameAggregation); |
162
|
|
|
$filterAggregation->setFilter($filter); |
163
|
|
|
|
164
|
|
|
if ($this->getSortType()) { |
165
|
|
|
$valueAggregation->addParameter('order', [$this->getSortType()['type'] => $this->getSortType()['order']]); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
if ($state->isActive()) { |
169
|
|
|
foreach ($state->getValue() as $key => $term) { |
170
|
|
|
$terms = $state->getValue(); |
171
|
|
|
unset($terms[$key]); |
172
|
|
|
|
173
|
|
|
$this->addSubFilterAggregation( |
174
|
|
|
$filterAggregation, |
175
|
|
|
$aggregation, |
176
|
|
|
$terms, |
177
|
|
|
$key |
178
|
|
|
); |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$this->addSubFilterAggregation( |
183
|
|
|
$filterAggregation, |
184
|
|
|
$aggregation, |
185
|
|
|
$state->getValue() ? $state->getValue() : [], |
186
|
|
|
'all-selected' |
187
|
|
|
); |
188
|
|
|
|
189
|
|
|
$search->addAggregation($filterAggregation); |
190
|
|
|
|
191
|
|
|
if ($this->getShowZeroChoices()) { |
192
|
|
|
$search->addAggregation($aggregation); |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* {@inheritdoc} |
198
|
|
|
*/ |
199
|
|
|
public function createViewData() |
200
|
|
|
{ |
201
|
|
|
return new AggregateViewData(); |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* {@inheritdoc} |
206
|
|
|
*/ |
207
|
|
|
public function getViewData(DocumentIterator $result, ViewData $data) |
208
|
|
|
{ |
209
|
|
|
$unsortedChoices = []; |
210
|
|
|
$activeNames = $data->getState()->isActive() ? array_keys($data->getState()->getValue()) : []; |
211
|
|
|
$filterAggregations = $this->fetchAggregation($result, $data->getName(), $data->getState()->getValue()); |
212
|
|
|
|
213
|
|
|
if ($this->getShowZeroChoices()) { |
214
|
|
|
$unsortedChoices = $this->formInitialUnsortedChoices($result, $data); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** @var AggregationValue $bucket */ |
218
|
|
|
foreach ($filterAggregations as $activeName => $filterAggregation) { |
219
|
|
|
foreach ($filterAggregation as $nameAggregation) { |
220
|
|
|
$name = $nameAggregation['key']; |
221
|
|
|
|
222
|
|
|
if (($name != $activeName && $activeName != 'all-selected') || |
223
|
|
|
($activeName == 'all-selected' && in_array($name, $activeNames))) { |
224
|
|
|
continue; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
foreach ($nameAggregation['value']['buckets'] as $bucket) { |
228
|
|
|
$choice = $this->createChoice($data, $name, $activeName, $bucket); |
|
|
|
|
229
|
|
|
$unsortedChoices[$name][$bucket['key']] = $choice; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
$this->addViewDataItem($data, $name, $unsortedChoices[$name]); |
|
|
|
|
233
|
|
|
unset($unsortedChoices[$name]); |
234
|
|
|
} |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
if ($this->getShowZeroChoices() && !empty($unsortedChoices)) { |
238
|
|
|
foreach ($unsortedChoices as $name => $choices) { |
239
|
|
|
$this->addViewDataItem($data, $name, $unsortedChoices[$name]); |
|
|
|
|
240
|
|
|
} |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** @var AggregateViewData $data */ |
244
|
|
|
$data->sortItems(); |
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
|
|
|
protected function fetchAggregation(DocumentIterator $result, $filterName, $values) |
267
|
|
|
{ |
268
|
|
|
$data = []; |
269
|
|
|
$values = empty($values) ? [] : $values; |
270
|
|
|
$aggregation = $result->getAggregation(sprintf('%s-filter', $filterName)); |
271
|
|
|
|
272
|
|
|
foreach ($values as $name => $value) { |
273
|
|
|
$data[$name] = $aggregation->find(sprintf('%s.%s.name', $name, $filterName)); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
$data['all-selected'] = $aggregation->find(sprintf('all-selected.%s.name', $filterName)); |
277
|
|
|
|
278
|
|
|
return $data; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* A method used to add an additional filter to the aggregations |
283
|
|
|
* in preProcessSearch |
284
|
|
|
* |
285
|
|
|
* @param FilterAggregation $filterAggregation |
286
|
|
|
* @param NestedAggregation $deepLevelAggregation |
287
|
|
|
* @param array $terms Terms of additional filter |
288
|
|
|
* @param string $aggName |
289
|
|
|
* |
290
|
|
|
* @return BuilderInterface |
291
|
|
|
*/ |
292
|
|
|
protected function addSubFilterAggregation( |
293
|
|
|
$filterAggregation, |
294
|
|
|
&$deepLevelAggregation, |
295
|
|
|
$terms, |
296
|
|
|
$aggName |
297
|
|
|
) { |
298
|
|
|
list($path, $field) = explode('>', $this->getField()); |
299
|
|
|
$boolQuery = new BoolQuery(); |
300
|
|
|
|
301
|
|
View Code Duplication |
foreach ($terms as $groupName => $term) { |
|
|
|
|
302
|
|
|
$nestedBoolQuery = new BoolQuery(); |
303
|
|
|
$nestedBoolQuery->add(new TermQuery($field, $term)); |
304
|
|
|
$nestedBoolQuery->add(new TermQuery($this->getNameField(), $groupName)); |
305
|
|
|
$boolQuery->add(new NestedQuery($path, $nestedBoolQuery)); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
$boolQuery = !empty($boolQuery->getQueries()) ? $boolQuery : new MatchAllQuery(); |
309
|
|
|
$innerFilterAggregation = new FilterAggregation($aggName, $boolQuery); |
|
|
|
|
310
|
|
|
$innerFilterAggregation->addAggregation($deepLevelAggregation); |
311
|
|
|
$filterAggregation->addAggregation($innerFilterAggregation); |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* @param string $key |
316
|
|
|
* @param string $name |
317
|
|
|
* @param ViewData $data |
318
|
|
|
* @param bool $active True when the choice is active |
319
|
|
|
* |
320
|
|
|
* @return array |
321
|
|
|
*/ |
322
|
|
|
protected function getOptionUrlParameters($key, $name, ViewData $data, $active) |
323
|
|
|
{ |
324
|
|
|
$value = $data->getState()->getValue(); |
325
|
|
|
$parameters = $data->getResetUrlParameters(); |
326
|
|
|
|
327
|
|
View Code Duplication |
if (!empty($value)) { |
|
|
|
|
328
|
|
|
if ($active) { |
329
|
|
|
unset($value[array_search($key, $value)]); |
330
|
|
|
$parameters[$this->getRequestField()] = $value; |
331
|
|
|
|
332
|
|
|
return $parameters; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
$parameters[$this->getRequestField()] = $value; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
$parameters[$this->getRequestField()][$name] = $key; |
339
|
|
|
|
340
|
|
|
return $parameters; |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* Returns whether choice with the specified key is active. |
345
|
|
|
* |
346
|
|
|
* @param string $key |
347
|
|
|
* @param ViewData $data |
348
|
|
|
* @param string $activeName |
349
|
|
|
* |
350
|
|
|
* @return bool |
351
|
|
|
*/ |
352
|
|
View Code Duplication |
protected function isChoiceActive($key, ViewData $data, $activeName) |
|
|
|
|
353
|
|
|
{ |
354
|
|
|
if ($data->getState()->isActive()) { |
355
|
|
|
$value = $data->getState()->getValue(); |
356
|
|
|
|
357
|
|
|
if (isset($value[$activeName]) && $key == $value[$activeName]) { |
358
|
|
|
return true; |
359
|
|
|
} |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
return false; |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
/** |
366
|
|
|
* Forms $unsortedChoices array with all possible choices. |
367
|
|
|
* 0 is assigned to the document count of the choices. |
368
|
|
|
* |
369
|
|
|
* @param DocumentIterator $result |
370
|
|
|
* @param ViewData $data |
371
|
|
|
* |
372
|
|
|
* @return array |
373
|
|
|
*/ |
374
|
|
|
protected function formInitialUnsortedChoices($result, $data) |
375
|
|
|
{ |
376
|
|
|
$unsortedChoices = []; |
377
|
|
|
$urlParameters = array_merge( |
378
|
|
|
$data->getResetUrlParameters(), |
379
|
|
|
$data->getState()->getUrlParameters() |
380
|
|
|
); |
381
|
|
|
|
382
|
|
|
foreach ($result->getAggregation($data->getName())->getAggregation('name') as $nameBucket) { |
383
|
|
|
$groupName = $nameBucket['key']; |
384
|
|
|
|
385
|
|
|
foreach ($nameBucket->getAggregation('value') as $bucket) { |
386
|
|
|
$bucketArray = ['key' => $bucket['key'], 'doc_count' => 0]; |
387
|
|
|
$choice = $this->createChoice($data, $bucket['key'], '', $bucketArray, $urlParameters); |
|
|
|
|
388
|
|
|
$unsortedChoices[$groupName][$bucket['key']] = $choice; |
389
|
|
|
} |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
return $unsortedChoices; |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
/** |
396
|
|
|
* @param AggregateViewData $data |
397
|
|
|
* @param string $name |
398
|
|
|
* @param string $activeName |
399
|
|
|
* @param AggregationValue $bucket |
400
|
|
|
* @param array $urlParameters |
401
|
|
|
* @return ViewData\Choice |
402
|
|
|
*/ |
403
|
|
|
protected function createChoice($data, $name, $activeName, $bucket, $urlParameters = null) |
404
|
|
|
{ |
405
|
|
|
$active = $this->isChoiceActive($bucket['key'], $data, $activeName); |
406
|
|
|
|
407
|
|
|
if (empty($urlParameters)) { |
408
|
|
|
$urlParameters = $this->getOptionUrlParameters($bucket['key'], $name, $data, $active); |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
$choice = new ViewData\Choice(); |
412
|
|
|
$choice->setLabel($bucket['key']); |
413
|
|
|
$choice->setCount($bucket['doc_count']); |
414
|
|
|
$choice->setActive($active); |
415
|
|
|
$choice->setUrlParameters($urlParameters); |
416
|
|
|
|
417
|
|
|
return $choice; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* @param AggregateViewData $data |
422
|
|
|
* @param string $name |
423
|
|
|
* @param ViewData\Choice[] $choices |
424
|
|
|
*/ |
425
|
|
|
protected function addViewDataItem($data, $name, $choices) |
426
|
|
|
{ |
427
|
|
|
$choiceViewData = new ViewData\ChoicesAwareViewData(); |
428
|
|
|
$choiceViewData->setName($name); |
429
|
|
|
$choiceViewData->setChoices($choices); |
430
|
|
|
$choiceViewData->setUrlParameters([]); |
431
|
|
|
$choiceViewData->setResetUrlParameters($data->getResetUrlParameters()); |
432
|
|
|
$data->addItem($choiceViewData); |
433
|
|
|
} |
434
|
|
|
} |
435
|
|
|
|
This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.