PluginProbe ʕ •ᴥ•ʔ
Admin Columns / 7.1.4
Admin Columns v7.1.4
7.1.4 7.0.19 2.3.5 2.4 2.4.1 2.4.10 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.6.1 2.5.6.2 2.5.6.3 2.5.6.4 3.0 3.0.1 3.0.2 3.0.3 3.0.5 3.0.7 3.1 3.1.1 3.1.10 3.1.2 3.1.3 3.1.5 3.2.3 3.2.7 3.3.1 3.4.1 3.4.6 3.4.8 4.0.1 4.0.3 4.1.6 4.2.2 4.2.5 4.3 4.3.2 4.4.1 4.4.4 4.4.5 4.5.5 4.6.1 4.7.18 4.7.19 4.7.20 4.7.7 7.0.13 7.0.14 7.0.16 trunk 1.0 1.1 1.1.3 1.2 1.2.1 1.3 1.3.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.5.1 1.4.6 1.4.6.1 1.4.6.2 1.4.6.3 1.4.6.4 1.4.7 1.4.8 1.4.9 2.0.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.2 2.2.1 2.2.1.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.5.1 2.2.6 2.2.6.1 2.2.6.2 2.2.6.3 2.2.6.4 2.2.7 2.2.8 2.2.8.1 2.2.9 2.3.1 2.3.2 2.3.3
codepress-admin-columns / classes / Meta / Query.php
codepress-admin-columns / classes / Meta Last commit date
Query.php 2 days ago QueryMetaFactory.php 2 days ago
Query.php
470 lines
1 <?php
2
3 declare(strict_types=1);
4
5 namespace AC\Meta;
6
7 use AC\MetaType;
8 use InvalidArgumentException;
9 use WP_Meta_Query;
10
11 class Query
12 {
13 private WP_Meta_Query $query;
14
15 private string $sql = '';
16
17 private array $select = [];
18
19 private string $count = '';
20
21 private bool $distinct = false;
22
23 private string $join = '';
24
25 private array $join_where = [];
26
27 private array $where = [];
28
29 private string $group_by = '';
30
31 private array $order_by = [];
32
33 private int $limit = 0;
34
35 private function __construct(WP_Meta_Query $query)
36 {
37 $this->query = $query;
38 }
39
40 public static function create_post(): self
41 {
42 return self::build(MetaType::POST, 'posts', 'ID');
43 }
44
45 public static function create_user(): self
46 {
47 return self::build(MetaType::USER, 'users', 'ID');
48 }
49
50 public static function create_comment(): self
51 {
52 return self::build(MetaType::COMMENT, 'comments', 'comment_ID');
53 }
54
55 public static function create_term(): self
56 {
57 return self::build(MetaType::TERM, 'terms', 'term_id');
58 }
59
60 public static function from_meta_type(MetaType $meta_type): self
61 {
62 switch ((string)$meta_type) {
63 case MetaType::POST:
64 return self::create_post();
65 case MetaType::USER:
66 return self::create_user();
67 case MetaType::COMMENT:
68 return self::create_comment();
69 case MetaType::TERM:
70 return self::create_term();
71 default:
72 throw new InvalidArgumentException(
73 sprintf('Unsupported meta type "%s".', $meta_type)
74 );
75 }
76 }
77
78 private static function build(string $type, string $table, string $id_column): self
79 {
80 global $wpdb;
81
82 $query = new WP_Meta_Query();
83 $query->get_sql($type, $wpdb->{$table}, $id_column);
84
85 return new self($query);
86 }
87
88 /**
89 * Add a single field or multiple comma separated
90 *
91 * @param string $field e.g. id or id, meta_value
92 *
93 * @return $this
94 */
95 public function select(string $field): self
96 {
97 $fields = explode(',', $field);
98
99 foreach ($fields as $_field) {
100 $this->select[] = trim($_field);
101 }
102
103 return $this;
104 }
105
106 /**
107 * Add a COUNT clause AS count
108 */
109 public function count(string $field): self
110 {
111 $this->count = $field;
112
113 return $this;
114 }
115
116 /**
117 * Group by an aggregated column.
118 * Supports: count
119 *
120 * @param string $field
121 *
122 * @return $this
123 */
124 public function group_by(string $field): self
125 {
126 $this->group_by = $field;
127
128 return $this;
129 }
130
131 public function join(string $type = 'inner'): self
132 {
133 $this->join = strtoupper($type);
134
135 return $this;
136 }
137
138 /**
139 * @see get_where_clause()
140 */
141 public function join_where(string $field, ?string $operator = null, $value = null, string $boolean = 'AND'): self
142 {
143 // set default join
144 if (! $this->join) {
145 $this->join();
146 }
147
148 $this->join_where[] = $this->get_where_clause($field, $operator, $value, $boolean);
149
150 return $this;
151 }
152
153 public function order_by(string $order_by, string $order = 'asc'): self
154 {
155 $parts = explode(',', $order_by);
156
157 foreach ($parts as $_order_by) {
158 $this->order_by[] = [
159 'order_by' => trim($_order_by),
160 'order' => strtoupper($order),
161 ];
162 }
163
164 return $this;
165 }
166
167 public function limit(int $limit): void
168 {
169 $this->limit = absint($limit);
170 }
171
172 public function distinct(): self
173 {
174 $this->distinct = true;
175
176 return $this;
177 }
178
179 /**
180 * Set a where clause
181 *
182 * @param string|array $field
183 * @param string|null $operator
184 * @param string|int|array $value
185 * @param string $boolean
186 *
187 * @return array
188 */
189 private function get_where_clause($field, ?string $operator = null, $value = null, string $boolean = 'AND'): array
190 {
191 // allows to omit operator
192 if (null === $value) {
193 $value = $operator;
194 $operator = '=';
195 }
196
197 $where = [
198 'nested' => false,
199 'boolean' => strtoupper($boolean),
200 'field' => $field,
201 'operator' => strtoupper((string)$operator),
202 'value' => $value,
203 ];
204
205 // set default join
206 if ($field === 'post_type' && ! $this->join) {
207 $this->join();
208 }
209
210 $nested = [];
211
212 if (is_array($field)) {
213 $count = count($field);
214 for ($i = 0; $i < $count; $i++) {
215 $nested[] = array_pop($this->where);
216 }
217 }
218
219 if ($nested) {
220 $where['nested'] = true;
221 $where['field'] = array_reverse($nested);
222 }
223
224 return $where;
225 }
226
227 /**
228 * @see get_where_clause()
229 */
230 public function remove_where($field, ?string $operator = null, $value = null, string $boolean = 'AND'): self
231 {
232 $where = $this->get_where_clause($field, $operator, $value, $boolean);
233
234 foreach ($this->where as $k => $v) {
235 if ($v == $where) {
236 unset($this->where[$k]);
237 }
238 }
239
240 return $this;
241 }
242
243 /**
244 * @see get_where_clause()
245 */
246 public function where($field, ?string $operator = null, $value = null, string $boolean = 'AND'): self
247 {
248 $this->where[] = $this->get_where_clause($field, $operator, $value, $boolean);
249
250 return $this;
251 }
252
253 /**
254 * @see get_where_clause()
255 */
256 public function or_where($field, ?string $operator = null, $value = null): self
257 {
258 return $this->where($field, $operator, $value, 'OR');
259 }
260
261 public function where_in(array $in): self
262 {
263 return $this->where('id', 'in', $in);
264 }
265
266 public function where_is_null($field): self
267 {
268 return $this->where($field, '', 'IS NULL');
269 }
270
271 public function where_post_type(string $post_type): self
272 {
273 return $this->where('post_type', '=', $post_type);
274 }
275
276 public function where_post_types(array $post_types): self
277 {
278 return $this->where('post_type', 'in', $post_types);
279 }
280
281 private function parse_field(string $field): string
282 {
283 switch ($field) {
284 case 'id':
285 $query = $this->get_query();
286
287 $field = $this->join ? 'pt.' . $query->primary_id_column : 'mt.' . $query->meta_id_column;
288
289 break;
290 case 'meta_key':
291 case 'meta_value':
292 $field = 'mt.' . $field;
293
294 break;
295 case 'taxonomy':
296 case 'post_type':
297 $field = 'pt.' . $field;
298
299 break;
300 }
301
302 return $field;
303 }
304
305 private function parse_where(string $where, array $clauses): string
306 {
307 global $wpdb;
308
309 foreach ($clauses as $clause) {
310 if ($clause['nested']) {
311 $clause['field'][0]['boolean'] = null;
312
313 $where .= sprintf(' %s ( %s ) ', $clause['boolean'], $this->parse_where('', $clause['field']));
314 } else {
315 switch ($clause['operator']) {
316 case 'IN':
317 $clause['value'] = sprintf(' ( %s ) ', implode(', ', array_map('intval', $clause['value'])));
318
319 break;
320 default:
321 $valid_raw = ['IS NULL', 'IS NOT NULL'];
322
323 if (! in_array($clause['value'], $valid_raw)) {
324 $clause['value'] = $wpdb->prepare('%s', $clause['value']);
325 }
326 }
327
328 $clause['field'] = $this->parse_field($clause['field']);
329
330 $where .= implode(' ', $clause);
331 }
332 }
333
334 return $where;
335 }
336
337 public function get(): array
338 {
339 global $wpdb;
340
341 // parse SELECT
342 $select = 'SELECT ';
343 $select .= $this->distinct ? 'DISTINCT ' : '';
344
345 if (empty($this->select)) {
346 $this->select('id');
347 }
348
349 $fields = [];
350
351 foreach ($this->select as $field) {
352 $parsed = $this->parse_field($field);
353
354 // output 'id' in the results
355 if ('id' === $field) {
356 $parsed .= ' AS id';
357 }
358
359 $fields[] = $parsed;
360 }
361
362 if ($this->count) {
363 $fields[] = sprintf('COUNT(%s) AS count', $this->parse_field($this->count));
364 }
365
366 $select .= implode(', ', $fields);
367
368 // parse FROM
369 $from_tpl = ' FROM %s AS %s';
370
371 $query = $this->get_query();
372
373 $from = sprintf($from_tpl, $query->meta_table, 'mt');
374 $join = '';
375
376 if ($this->join) {
377 $from = sprintf($from_tpl, $query->primary_table, 'pt');
378 $join = sprintf(
379 ' %s JOIN %s AS mt ON mt.%s = pt.%s %s',
380 $this->join,
381 $query->meta_table,
382 $query->meta_id_column,
383 $query->primary_id_column,
384 $this->parse_where('', $this->join_where)
385 );
386 }
387
388 // parse WHERE
389 $where = $this->parse_where(' WHERE 1=1', $this->where);
390
391 // parse GROUP BY
392 $group_by = '';
393
394 if ($this->group_by) {
395 $group_by = ' GROUP BY ' . $this->parse_field($this->group_by);
396 }
397
398 // parse ORDER BY
399 $order_by = '';
400
401 if (! empty($this->order_by)) {
402 $order_by_clauses = [];
403
404 foreach ($this->order_by as $order_by_clause) {
405 $order_by_clauses[] = $this->parse_field(
406 $order_by_clause['order_by']
407 ) . ' ' . $order_by_clause['order'];
408 }
409
410 $order_by = ' ORDER BY ' . implode(', ', $order_by_clauses);
411 }
412
413 $limit = '';
414
415 if ($this->limit) {
416 $limit = ' LIMIT ' . $this->limit;
417 }
418
419 // build query and store it
420 $sql = $select . $from . $join . $where . $group_by . $order_by . $limit;
421
422 $this->set_sql($sql);
423
424 $results = $wpdb->get_results($sql);
425
426 if (! is_array($results)) {
427 return [];
428 }
429
430 $return = $results;
431
432 if (count($fields) === 1) {
433 $return = [];
434 $field = $this->select[0];
435
436 foreach ($results as $result) {
437 $return[] = $result->$field;
438 }
439 }
440
441 return $return;
442 }
443
444 /**
445 * Return last SQL query that was queried
446 */
447 public function get_sql(): string
448 {
449 $sql = (string) preg_replace('/ +/', ' ', $this->sql);
450 $sql = (string) preg_replace(
451 '/(SELECT|FROM|LEFT|INNER|WHERE|(AND|OR) \(|(AND|OR) (?!\()|ORDER BY|GROUP BY|LIMIT)/',
452 "\n$1",
453 $sql
454 );
455
456 return $sql . "\n";
457 }
458
459 private function set_sql(string $sql): void
460 {
461 $this->sql = $sql;
462 }
463
464 public function get_query(): WP_Meta_Query
465 {
466 return $this->query;
467 }
468
469 }
470