-
Notifications
You must be signed in to change notification settings - Fork 1
/
class-aggregation.php
317 lines (285 loc) · 8.59 KB
/
class-aggregation.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
<?php
/**
* Elasticsearch Extensions: Aggregation Abstract Class
*
* @package Elasticsearch_Extensions
*/
namespace Elasticsearch_Extensions\Aggregations;
use Elasticsearch_Extensions\DSL;
/**
* Aggregation abstract class. Responsible for building the DSL and requests
* for aggregations as well as holding the result of the aggregation after a
* response was received.
*/
abstract class Aggregation {
/**
* Results for this aggregation from Elasticsearch. An array of Bucket objects.
*
* @var Bucket[]
*/
protected array $buckets = [];
/**
* A reference to the DSL class, initialized with the map from the adapter.
*
* @var DSL
*/
protected DSL $dsl;
/**
* The human-readable label for this aggregation.
*
* @var string
*/
protected string $label = '';
/**
* The order to apply to the results. One of 'ASC', 'DESC'.
* Defaults to 'DESC'.
*
* @var string
*/
protected $order = 'DESC';
/**
* The field to sort results by. Defaults to 'count'. Can also be 'key' or
* 'label' or a field specific to an implementing class.
*
* @var string
*/
protected $orderby = 'count';
/**
* The query var this aggregation should use.
*
* @var string
*/
protected string $query_var = '';
/**
* The values for the query var for this aggregation.
*
* @var string[]
*/
protected array $query_values = [];
/**
* Build the aggregation type object.
*
* @param DSL $dsl The DSL object, initialized with the map from the adapter.
* @param array $args Optional. Additional arguments to pass to the aggregation.
*/
public function __construct( DSL $dsl, array $args = [] ) {
$this->dsl = $dsl;
foreach ( $args as $key => $value ) {
if ( property_exists( $this, $key ) ) {
$this->$key = $value;
}
}
// Extract selected values from the query var.
$this->query_values = $this->extract_query_values();
}
/**
* Outputs checkboxes for all buckets in the aggregation.
*/
public function checkboxes() {
// Bail if we have no buckets.
if ( empty( $this->buckets ) ) {
return;
}
?>
<fieldset class="elasticsearch-extensions__checkbox-group">
<legend><?php echo esc_html( $this->get_label() ); ?></legend>
<?php foreach ( $this->buckets as $bucket ) : ?>
<label>
<input
<?php checked( $bucket->selected ); ?>
name="fs[<?php echo esc_attr( $this->query_var ); ?>][]"
type="checkbox"
value="<?php echo esc_attr( $bucket->key ); ?>"
/>
<?php echo esc_html( $bucket->label ); ?> (<?php echo esc_html( (string) $bucket->count ); ?>)
</label>
<?php endforeach; ?>
</fieldset>
<?php
}
/**
* A helper function for getting query values for the current query var or
* for an arbitrary query var. We can't use get_query_var() here because
* custom query var registration happens too late for our purposes, so we
* need to do it manually.
*
* @param string $key Optional. The key to look up. Defaults to the current query var.
*
* @return string[] The values for the given key.
*/
protected function extract_query_values( string $key = '' ): array {
$query_var = $key ?: $this->get_query_var();
/**
* Filters extracted query values for a given aggregation.
*
* @param array $query_values The array of extracted query values.
* @param Aggregation $aggregation The aggregation being processed.
*/
return apply_filters(
'elasticsearch_extensions_aggregation_query_values',
array_values( array_filter( (array) ( $_GET['fs'][ $query_var ] ?? [] ) ) ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
$this
);
}
/**
* Gets an array of DSL representing each filter for this aggregation that
* should be applied in the query in order to match the requested values.
*
* @return array Array of DSL fragments to apply.
*/
abstract public function filter(): array;
/**
* Gets a list of results for this aggregation.
*
* @return Bucket[] An array of Bucket objects.
*/
public function get_buckets(): array {
return $this->buckets;
}
/**
* Gets the human-readable label for this aggregation.
*
* @return string The human-readable label for this aggregation.
*/
public function get_label(): string {
return $this->label;
}
/**
* Get the query var for this aggregation.
*
* @return string The query var for this aggregation.
*/
public function get_query_var(): string {
return $this->query_var;
}
/**
* Get the values for the query var for this aggregation.
*
* @return array The values for the query var.
*/
public function get_query_values(): array {
return $this->query_values;
}
/**
* Outputs the default form inputs for this aggregation. Defaults to
* checkboxes, but can be overridden in individual aggregation classes if a
* different input format makes more logical sense, or to create a totally
* custom input fieldset.
*/
public function input(): void {
$this->checkboxes();
}
/**
* Determines whether the specified key is selected in the query for this
* aggregation.
*
* @param string $key The key to check.
*
* @return bool True if selected, false if not.
*/
protected function is_selected( string $key ): bool {
return in_array( $key, $this->query_values, true );
}
/**
* Given a raw array of Elasticsearch aggregation buckets, parses it into
* Bucket objects and passes them to save_buckets for finalization.
*
* @param array $buckets The raw aggregation buckets from Elasticsearch.
*/
abstract public function parse_buckets( array $buckets ): void;
/**
* Get DSL for the aggregation to add to the Elasticsearch request object.
* Instructs Elasticsearch to return buckets for this aggregation in the
* response.
*
* @return array DSL fragment.
*/
abstract public function request(): array;
/**
* Outputs a select control for all buckets in the aggregation.
*/
public function select() {
// Bail if we have no buckets.
if ( empty( $this->buckets ) ) {
return;
}
?>
<div class="elasticsearch-extensions__select-control">
<label for="<?php echo esc_attr( $this->get_query_var() ); ?>">
<?php echo esc_html( $this->get_label() ); ?>
</label>
<select
id="<?php echo esc_attr( $this->get_query_var() ); ?>"
name="fs[<?php echo esc_attr( $this->query_var ); ?>][]"
>
<option value="">
<?php esc_html_e( 'All', 'elasticsearch-extensions' ); ?>
</option>
<?php foreach ( $this->buckets as $bucket ) : ?>
<option
<?php selected( $bucket->selected ); ?>
value="<?php echo esc_attr( $bucket->key ); ?>"
>
<?php echo esc_html( $bucket->label ); ?> (<?php echo esc_html( (string) $bucket->count ); ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<?php
}
/**
* Performs post-processing on buckets before saving them to the object.
*
* @param Bucket[] $buckets The buckets to save.
*/
protected function set_buckets( array $buckets ): void {
/**
* Allows the buckets to be filtered before they are displayed, which
* can allow for removing certain items, or changing labels, or changing
* the sort order of buckets.
*
* @param Bucket[] $buckets The array of buckets to filter.
* @param Aggregation $aggregation The aggregation that the buckets are associated with.
*/
$this->buckets = apply_filters( 'elasticsearch_extensions_aggregation_buckets', $this->sort_buckets( $buckets ), $this );
}
/**
* Apply default sorting rules based on count, key, and label. Can be
* overridden by implementing classes to allow for custom sort logic.
*
* @param Bucket[] $buckets Buckets to be sorted.
*
* @return Bucket[] The sorted bucket array.
*/
protected function sort_buckets( array $buckets ): array {
// If the sort is one of the standard keys, apply it.
if ( in_array( $this->orderby, [ 'count', 'key', 'label' ], true ) ) {
usort(
$buckets,
/**
* Compares two buckets to determine which should come first.
*
* @param Bucket $a The first bucket to compare.
* @param Bucket $b The second bucket to compare.
*
* @return int Less than one if a is before b, more than one if b is before a, or zero if they are equal.
*/
function ( Bucket $a, Bucket $b ): int {
switch ( $this->orderby ) {
case 'key':
return strcasecmp( $a->key, $b->key );
case 'label':
return strcasecmp( $a->label, $b->label );
default:
return $a->count - $b->count;
}
}
);
}
// If the sort order is descending, flip the order.
if ( 'DESC' === $this->order ) {
$buckets = array_reverse( $buckets );
}
return $buckets;
}
}