WP_Term_Query::get_terms()   F
last analyzed

Complexity

Conditions 120
Paths 0

Size

Total Lines 513
Code Lines 279

Duplication

Lines 18
Ratio 3.51 %

Importance

Changes 0
Metric Value
cc 120
eloc 279
nc 0
nop 0
dl 18
loc 513
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Taxonomy API: WP_Term_Query class.
5
 *
6
 * @package WordPress
7
 * @subpackage Taxonomy
8
 * @since 4.6.0
9
 */
10
11
/**
12
 * Class used for querying terms.
13
 *
14
 * @since 4.6.0
15
 *
16
 * @see WP_Term_Query::__construct() for accepted arguments.
17
 */
18
class WP_Term_Query {
19
20
	/**
21
	 * SQL string used to perform database query.
22
	 *
23
	 * @since 4.6.0
24
	 * @access public
25
	 * @var string
26
	 */
27
	public $request;
28
29
	/**
30
	 * Metadata query container.
31
	 *
32
	 * @since 4.6.0
33
	 * @access public
34
	 * @var object WP_Meta_Query
35
	 */
36
	public $meta_query = false;
37
38
	/**
39
	 * Metadata query clauses.
40
	 *
41
	 * @since 4.6.0
42
	 * @access protected
43
	 * @var array
44
	 */
45
	protected $meta_query_clauses;
46
47
	/**
48
	 * SQL query clauses.
49
	 *
50
	 * @since 4.6.0
51
	 * @access protected
52
	 * @var array
53
	 */
54
	protected $sql_clauses = array(
55
		'select'  => '',
56
		'from'    => '',
57
		'where'   => array(),
58
		'orderby' => '',
59
		'limits'  => '',
60
	);
61
62
	/**
63
	 * Query vars set by the user.
64
	 *
65
	 * @since 4.6.0
66
	 * @access public
67
	 * @var array
68
	 */
69
	public $query_vars;
70
71
	/**
72
	 * Default values for query vars.
73
	 *
74
	 * @since 4.6.0
75
	 * @access public
76
	 * @var array
77
	 */
78
	public $query_var_defaults;
79
80
	/**
81
	 * List of terms located by the query.
82
	 *
83
	 * @since 4.6.0
84
	 * @access public
85
	 * @var array
86
	 */
87
	public $terms;
88
89
	/**
90
	 * Constructor.
91
	 *
92
	 * Sets up the term query, based on the query vars passed.
93
	 *
94
	 * @since 4.6.0
95
	 * @since 4.6.0 Introduced 'term_taxonomy_id' parameter.
96
	 * @since 4.7.0 Introduced 'object_ids' parameter.
97
	 * @access public
98
	 *
99
	 * @param string|array $query {
100
	 *     Optional. Array or query string of term query parameters. Default empty.
101
	 *
102
	 *     @type string|array $taxonomy               Taxonomy name, or array of taxonomies, to which results should
103
	 *                                                be limited.
104
	 *     @type int|array    $object_ids             Optional. Object ID, or array of object IDs. Results will be
105
	 *                                                limited to terms associated with these objects.
106
	 *     @type string       $orderby                Field(s) to order terms by. Accepts term fields ('name',
107
	 *                                                'slug', 'term_group', 'term_id', 'id', 'description', 'parent'),
108
	 *                                                'count' for term taxonomy count, 'include' to match the
109
	 *                                                'order' of the $include param, 'meta_value', 'meta_value_num',
110
	 *                                                the value of `$meta_key`, the array keys of `$meta_query`, or
111
	 *                                                'none' to omit the ORDER BY clause. Defaults to 'name'.
112
	 *     @type string       $order                  Whether to order terms in ascending or descending order.
113
	 *                                                Accepts 'ASC' (ascending) or 'DESC' (descending).
114
	 *                                                Default 'ASC'.
115
	 *     @type bool|int     $hide_empty             Whether to hide terms not assigned to any posts. Accepts
116
	 *                                                1|true or 0|false. Default 1|true.
117
	 *     @type array|string $include                Array or comma/space-separated string of term ids to include.
118
	 *                                                Default empty array.
119
	 *     @type array|string $exclude                Array or comma/space-separated string of term ids to exclude.
120
	 *                                                If $include is non-empty, $exclude is ignored.
121
	 *                                                Default empty array.
122
	 *     @type array|string $exclude_tree           Array or comma/space-separated string of term ids to exclude
123
	 *                                                along with all of their descendant terms. If $include is
124
	 *                                                non-empty, $exclude_tree is ignored. Default empty array.
125
	 *     @type int|string   $number                 Maximum number of terms to return. Accepts ''|0 (all) or any
126
	 *                                                positive number. Default ''|0 (all).
127
	 *     @type int          $offset                 The number by which to offset the terms query. Default empty.
128
	 *     @type string       $fields                 Term fields to query for. Accepts 'all' (returns an array of
129
	 *                                                complete term objects), 'all_with_object_id' (returns an
130
	 *                                                array of term objects with the 'object_id' param; only works
131
	 *                                                when the `$fields` parameter is 'object_ids' ), 'ids'
132
	 *                                                (returns an array of ids), 'tt_ids' (returns an array of
133
	 *                                                term taxonomy ids), 'id=>parent' (returns an associative
134
	 *                                                array with ids as keys, parent term IDs as values), 'names'
135
	 *                                                (returns an array of term names), 'count' (returns the number
136
	 *                                                of matching terms), 'id=>name' (returns an associative array
137
	 *                                                with ids as keys, term names as values), or 'id=>slug'
138
	 *                                                (returns an associative array with ids as keys, term slugs
139
	 *                                                as values). Default 'all'.
140
	 *     @type bool         $count                  Whether to return a term count (true) or array of term objects
141
	 *                                                (false). Will take precedence over `$fields` if true.
142
	 *                                                Default false.
143
	 *     @type string|array $name                   Optional. Name or array of names to return term(s) for.
144
	 *                                                Default empty.
145
	 *     @type string|array $slug                   Optional. Slug or array of slugs to return term(s) for.
146
	 *                                                Default empty.
147
	 *     @type int|array    $term_taxonomy_id       Optional. Term taxonomy ID, or array of term taxonomy IDs,
148
	 *                                                to match when querying terms.
149
	 *     @type bool         $hierarchical           Whether to include terms that have non-empty descendants (even
150
	 *                                                if $hide_empty is set to true). Default true.
151
	 *     @type string       $search                 Search criteria to match terms. Will be SQL-formatted with
152
	 *                                                wildcards before and after. Default empty.
153
	 *     @type string       $name__like             Retrieve terms with criteria by which a term is LIKE
154
	 *                                                `$name__like`. Default empty.
155
	 *     @type string       $description__like      Retrieve terms where the description is LIKE
156
	 *                                                `$description__like`. Default empty.
157
	 *     @type bool         $pad_counts             Whether to pad the quantity of a term's children in the
158
	 *                                                quantity of each term's "count" object variable.
159
	 *                                                Default false.
160
	 *     @type string       $get                    Whether to return terms regardless of ancestry or whether the
161
	 *                                                terms are empty. Accepts 'all' or empty (disabled).
162
	 *                                                Default empty.
163
	 *     @type int          $child_of               Term ID to retrieve child terms of. If multiple taxonomies
164
	 *                                                are passed, $child_of is ignored. Default 0.
165
	 *     @type int|string   $parent                 Parent term ID to retrieve direct-child terms of.
166
	 *                                                Default empty.
167
	 *     @type bool         $childless              True to limit results to terms that have no children.
168
	 *                                                This parameter has no effect on non-hierarchical taxonomies.
169
	 *                                                Default false.
170
	 *     @type string       $cache_domain           Unique cache key to be produced when this query is stored in
171
	 *                                                an object cache. Default is 'core'.
172
	 *     @type bool         $update_term_meta_cache Whether to prime meta caches for matched terms. Default true.
173
	 *     @type array        $meta_query             Optional. Meta query clauses to limit retrieved terms by.
174
	 *                                                See `WP_Meta_Query`. Default empty.
175
	 *     @type string       $meta_key               Limit terms to those matching a specific metadata key.
176
	 *                                                Can be used in conjunction with `$meta_value`. Default empty.
177
	 *     @type string       $meta_value             Limit terms to those matching a specific metadata value.
178
	 *                                                Usually used in conjunction with `$meta_key`. Default empty.
179
	 *     @type string       $meta_type              Type of object metadata is for (e.g., comment, post, or user).
180
	 *                                                Default empty.
181
	 *     @type string       $meta_compare           Comparison operator to test the 'meta_value'. Default empty.
182
	 * }
183
	 */
184
	public function __construct( $query = '' ) {
185
		$this->query_var_defaults = array(
186
			'taxonomy'               => null,
187
			'object_ids'             => null,
188
			'orderby'                => 'name',
189
			'order'                  => 'ASC',
190
			'hide_empty'             => true,
191
			'include'                => array(),
192
			'exclude'                => array(),
193
			'exclude_tree'           => array(),
194
			'number'                 => '',
195
			'offset'                 => '',
196
			'fields'                 => 'all',
197
			'count'                  => false,
198
			'name'                   => '',
199
			'slug'                   => '',
200
			'term_taxonomy_id'       => '',
201
			'hierarchical'           => true,
202
			'search'                 => '',
203
			'name__like'             => '',
204
			'description__like'      => '',
205
			'pad_counts'             => false,
206
			'get'                    => '',
207
			'child_of'               => 0,
208
			'parent'                 => '',
209
			'childless'              => false,
210
			'cache_domain'           => 'core',
211
			'update_term_meta_cache' => true,
212
			'meta_query'             => '',
213
			'meta_key'               => '',
214
			'meta_value'             => '',
215
			'meta_type'              => '',
216
			'meta_compare'           => '',
217
		);
218
219
		if ( ! empty( $query ) ) {
220
			$this->query( $query );
221
		}
222
	}
223
224
	/**
225
	 * Parse arguments passed to the term query with default query parameters.
226
	 *
227
	 * @since 4.6.0
228
	 * @access public
229
	 *
230
	 * @param string|array $query WP_Term_Query arguments. See WP_Term_Query::__construct()
231
	 */
232
	public function parse_query( $query = '' ) {
233
		if ( empty( $query ) ) {
234
			$query = $this->query_vars;
235
		}
236
237
		$taxonomies = isset( $query['taxonomy'] ) ? (array) $query['taxonomy'] : null;
238
239
		/**
240
		 * Filters the terms query default arguments.
241
		 *
242
		 * Use {@see 'get_terms_args'} to filter the passed arguments.
243
		 *
244
		 * @since 4.4.0
245
		 *
246
		 * @param array $defaults   An array of default get_terms() arguments.
247
		 * @param array $taxonomies An array of taxonomies.
248
		 */
249
		$this->query_var_defaults = apply_filters( 'get_terms_defaults', $this->query_var_defaults, $taxonomies );
0 ignored issues
show
Documentation Bug introduced by
It seems like apply_filters('get_terms..._defaults, $taxonomies) of type * is incompatible with the declared type array of property $query_var_defaults.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
250
251
		$query = wp_parse_args( $query, $this->query_var_defaults );
252
253
		$query['number'] = absint( $query['number'] );
254
		$query['offset'] = absint( $query['offset'] );
255
256
		// 'parent' overrides 'child_of'.
257
		if ( 0 < intval( $query['parent'] ) ) {
258
			$query['child_of'] = false;
259
		}
260
261 View Code Duplication
		if ( 'all' == $query['get'] ) {
262
			$query['childless'] = false;
263
			$query['child_of'] = 0;
264
			$query['hide_empty'] = 0;
265
			$query['hierarchical'] = false;
266
			$query['pad_counts'] = false;
267
		}
268
269
		$query['taxonomy'] = $taxonomies;
270
271
		$this->query_vars = $query;
272
273
		/**
274
		 * Fires after term query vars have been parsed.
275
		 *
276
		 * @since 4.6.0
277
		 *
278
		 * @param WP_Term_Query $this Current instance of WP_Term_Query.
279
		 */
280
		do_action( 'parse_term_query', $this );
281
	}
282
283
	/**
284
	 * Sets up the query for retrieving terms.
285
	 *
286
	 * @since 4.6.0
287
	 * @access public
288
	 *
289
	 * @param string|array $query Array or URL query string of parameters.
290
	 * @return array|int List of terms, or number of terms when 'count' is passed as a query var.
291
	 */
292
	public function query( $query ) {
293
		$this->query_vars = wp_parse_args( $query );
294
		return $this->get_terms();
295
	}
296
297
	/**
298
	 * Get terms, based on query_vars.
299
	 *
300
	 * @since 4.6.0
301
	 * @access public
302
	 *
303
	 * @global wpdb $wpdb WordPress database abstraction object.
304
	 *
305
	 * @return array List of terms.
306
	 */
307
	public function get_terms() {
308
		global $wpdb;
309
310
		$this->parse_query( $this->query_vars );
311
		$args = &$this->query_vars;
312
313
		// Set up meta_query so it's available to 'pre_get_terms'.
314
		$this->meta_query = new WP_Meta_Query();
315
		$this->meta_query->parse_query_vars( $args );
316
317
		/**
318
		 * Fires before terms are retrieved.
319
		 *
320
		 * @since 4.6.0
321
		 *
322
		 * @param WP_Term_Query $this Current instance of WP_Term_Query.
323
		 */
324
		do_action( 'pre_get_terms', $this );
325
326
		$taxonomies = (array) $args['taxonomy'];
327
328
		// Save queries by not crawling the tree in the case of multiple taxes or a flat tax.
329
		$has_hierarchical_tax = false;
330
		if ( $taxonomies ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $taxonomies of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
331
			foreach ( $taxonomies as $_tax ) {
332
				if ( is_taxonomy_hierarchical( $_tax ) ) {
333
					$has_hierarchical_tax = true;
334
				}
335
			}
336
		}
337
338
		if ( ! $has_hierarchical_tax ) {
339
			$args['hierarchical'] = false;
340
			$args['pad_counts'] = false;
341
		}
342
343
		// 'parent' overrides 'child_of'.
344
		if ( 0 < intval( $args['parent'] ) ) {
345
			$args['child_of'] = false;
346
		}
347
348 View Code Duplication
		if ( 'all' == $args['get'] ) {
349
			$args['childless'] = false;
350
			$args['child_of'] = 0;
351
			$args['hide_empty'] = 0;
352
			$args['hierarchical'] = false;
353
			$args['pad_counts'] = false;
354
		}
355
356
		/**
357
		 * Filters the terms query arguments.
358
		 *
359
		 * @since 3.1.0
360
		 *
361
		 * @param array $args       An array of get_terms() arguments.
362
		 * @param array $taxonomies An array of taxonomies.
363
		 */
364
		$args = apply_filters( 'get_terms_args', $args, $taxonomies );
365
366
		// Avoid the query if the queried parent/child_of term has no descendants.
367
		$child_of = $args['child_of'];
368
		$parent   = $args['parent'];
369
370
		if ( $child_of ) {
371
			$_parent = $child_of;
372
		} elseif ( $parent ) {
373
			$_parent = $parent;
374
		} else {
375
			$_parent = false;
376
		}
377
378
		if ( $_parent ) {
379
			$in_hierarchy = false;
380
			foreach ( $taxonomies as $_tax ) {
381
				$hierarchy = _get_term_hierarchy( $_tax );
382
383
				if ( isset( $hierarchy[ $_parent ] ) ) {
384
					$in_hierarchy = true;
385
				}
386
			}
387
388
			if ( ! $in_hierarchy ) {
389
				return array();
390
			}
391
		}
392
393
		// 'term_order' is a legal sort order only when joining the relationship table.
394
		$_orderby = $this->query_vars['orderby'];
395
		if ( 'term_order' === $_orderby && empty( $this->query_vars['object_ids'] ) ) {
396
			$_orderby = 'term_id';
397
		}
398
		$orderby = $this->parse_orderby( $_orderby );
399
400
		if ( $orderby ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $orderby of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
401
			$orderby = "ORDER BY $orderby";
402
		}
403
404
		$order = $this->parse_order( $this->query_vars['order'] );
405
406
		if ( $taxonomies ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $taxonomies of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
407
			$this->sql_clauses['where']['taxonomy'] = "tt.taxonomy IN ('" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "')";
408
		}
409
410
		$exclude      = $args['exclude'];
411
		$exclude_tree = $args['exclude_tree'];
412
		$include      = $args['include'];
413
414
		$inclusions = '';
415
		if ( ! empty( $include ) ) {
416
			$exclude = '';
417
			$exclude_tree = '';
418
			$inclusions = implode( ',', wp_parse_id_list( $include ) );
419
		}
420
421
		if ( ! empty( $inclusions ) ) {
422
			$this->sql_clauses['where']['inclusions'] = 't.term_id IN ( ' . $inclusions . ' )';
423
		}
424
425
		$exclusions = array();
426
		if ( ! empty( $exclude_tree ) ) {
427
			$exclude_tree = wp_parse_id_list( $exclude_tree );
428
			$excluded_children = $exclude_tree;
429
			foreach ( $exclude_tree as $extrunk ) {
430
				$excluded_children = array_merge(
431
					$excluded_children,
432
					(array) get_terms( $taxonomies[0], array(
433
						'child_of' => intval( $extrunk ),
434
						'fields' => 'ids',
435
						'hide_empty' => 0
436
					) )
437
				);
438
			}
439
			$exclusions = array_merge( $excluded_children, $exclusions );
440
		}
441
442
		if ( ! empty( $exclude ) ) {
443
			$exclusions = array_merge( wp_parse_id_list( $exclude ), $exclusions );
444
		}
445
446
		// 'childless' terms are those without an entry in the flattened term hierarchy.
447
		$childless = (bool) $args['childless'];
448
		if ( $childless ) {
449
			foreach ( $taxonomies as $_tax ) {
450
				$term_hierarchy = _get_term_hierarchy( $_tax );
451
				$exclusions = array_merge( array_keys( $term_hierarchy ), $exclusions );
452
			}
453
		}
454
455
		if ( ! empty( $exclusions ) ) {
456
			$exclusions = 't.term_id NOT IN (' . implode( ',', array_map( 'intval', $exclusions ) ) . ')';
457
		} else {
458
			$exclusions = '';
459
		}
460
461
		/**
462
		 * Filters the terms to exclude from the terms query.
463
		 *
464
		 * @since 2.3.0
465
		 *
466
		 * @param string $exclusions `NOT IN` clause of the terms query.
467
		 * @param array  $args       An array of terms query arguments.
468
		 * @param array  $taxonomies An array of taxonomies.
469
		 */
470
		$exclusions = apply_filters( 'list_terms_exclusions', $exclusions, $args, $taxonomies );
471
472
		if ( ! empty( $exclusions ) ) {
473
			// Must do string manipulation here for backward compatibility with filter.
474
			$this->sql_clauses['where']['exclusions'] = preg_replace( '/^\s*AND\s*/', '', $exclusions );
475
		}
476
477
		if (
478
			( ! empty( $args['name'] ) ) ||
479
			( is_string( $args['name'] ) && 0 !== strlen( $args['name'] ) )
480
		) {
481
			$names = (array) $args['name'];
482
			foreach ( $names as &$_name ) {
483
				// `sanitize_term_field()` returns slashed data.
484
				$_name = stripslashes( sanitize_term_field( 'name', $_name, 0, reset( $taxonomies ), 'db' ) );
485
			}
486
487
			$this->sql_clauses['where']['name'] = "t.name IN ('" . implode( "', '", array_map( 'esc_sql', $names ) ) . "')";
488
		}
489
490
		if (
491
			( ! empty( $args['slug'] ) ) ||
492
			( is_string( $args['slug'] ) && 0 !== strlen( $args['slug'] ) )
493
		) {
494
			if ( is_array( $args['slug'] ) ) {
495
				$slug = array_map( 'sanitize_title', $args['slug'] );
496
				$this->sql_clauses['where']['slug'] = "t.slug IN ('" . implode( "', '", $slug ) . "')";
497
			} else {
498
				$slug = sanitize_title( $args['slug'] );
499
				$this->sql_clauses['where']['slug'] = "t.slug = '$slug'";
500
			}
501
		}
502
503
		if ( ! empty( $args['term_taxonomy_id'] ) ) {
504
			if ( is_array( $args['term_taxonomy_id'] ) ) {
505
				$tt_ids = implode( ',', array_map( 'intval', $args['term_taxonomy_id'] ) );
506
				$this->sql_clauses['where']['term_taxonomy_id'] = "tt.term_taxonomy_id IN ({$tt_ids})";
507
			} else {
508
				$this->sql_clauses['where']['term_taxonomy_id'] = $wpdb->prepare( "tt.term_taxonomy_id = %d", $args['term_taxonomy_id'] );
509
			}
510
		}
511
512 View Code Duplication
		if ( ! empty( $args['name__like'] ) ) {
513
			$this->sql_clauses['where']['name__like'] = $wpdb->prepare( "t.name LIKE %s", '%' . $wpdb->esc_like( $args['name__like'] ) . '%' );
514
		}
515
516 View Code Duplication
		if ( ! empty( $args['description__like'] ) ) {
517
			$this->sql_clauses['where']['description__like'] = $wpdb->prepare( "tt.description LIKE %s", '%' . $wpdb->esc_like( $args['description__like'] ) . '%' );
518
		}
519
520
		if ( ! empty( $args['object_ids'] ) ) {
521
			$object_ids = $args['object_ids'];
522
			if ( ! is_array( $object_ids ) ) {
523
				$object_ids = array( $object_ids );
524
			}
525
526
			$object_ids = implode( ', ', array_map( 'intval', $object_ids ) );
527
			$this->sql_clauses['where']['object_ids'] = "tr.object_id IN ($object_ids)";
528
		}
529
530
		/*
531
		 * When querying for object relationships, the 'count > 0' check
532
		 * added by 'hide_empty' is superfluous.
533
		 */
534
		if ( ! empty( $args['object_ids'] ) ) {
535
			$args['hide_empty'] = false;
536
		}
537
538
		if ( '' !== $parent ) {
539
			$parent = (int) $parent;
540
			$this->sql_clauses['where']['parent'] = "tt.parent = '$parent'";
541
		}
542
543
		$hierarchical = $args['hierarchical'];
544
		if ( 'count' == $args['fields'] ) {
545
			$hierarchical = false;
546
		}
547
		if ( $args['hide_empty'] && !$hierarchical ) {
548
			$this->sql_clauses['where']['count'] = 'tt.count > 0';
549
		}
550
551
		$number = $args['number'];
552
		$offset = $args['offset'];
553
554
		// Don't limit the query results when we have to descend the family tree.
555
		if ( $number && ! $hierarchical && ! $child_of && '' === $parent ) {
556 View Code Duplication
			if ( $offset ) {
557
				$limits = 'LIMIT ' . $offset . ',' . $number;
558
			} else {
559
				$limits = 'LIMIT ' . $number;
560
			}
561
		} else {
562
			$limits = '';
563
		}
564
565
566
		if ( ! empty( $args['search'] ) ) {
567
			$this->sql_clauses['where']['search'] = $this->get_search_sql( $args['search'] );
568
		}
569
570
		// Meta query support.
571
		$join = '';
572
		$distinct = '';
573
574
		// Reparse meta_query query_vars, in case they were modified in a 'pre_get_terms' callback.
575
		$this->meta_query->parse_query_vars( $this->query_vars );
576
		$mq_sql = $this->meta_query->get_sql( 'term', 't', 'term_id' );
577
		$meta_clauses = $this->meta_query->get_clauses();
578
579
		if ( ! empty( $meta_clauses ) ) {
580
			$join .= $mq_sql['join'];
581
			$this->sql_clauses['where']['meta_query'] = preg_replace( '/^\s*AND\s*/', '', $mq_sql['where'] );
582
			$distinct .= "DISTINCT";
583
584
		}
585
586
		$selects = array();
587
		switch ( $args['fields'] ) {
588
			case 'all':
589
			case 'all_with_object_id' :
590
			case 'tt_ids' :
591
			case 'slugs' :
592
				$selects = array( 't.*', 'tt.*' );
593
				if ( 'all_with_object_id' === $args['fields'] && ! empty( $args['object_ids'] ) ) {
594
					$selects[] = 'tr.object_id';
595
				}
596
				break;
597
			case 'ids':
598
			case 'id=>parent':
599
				$selects = array( 't.term_id', 'tt.parent', 'tt.count', 'tt.taxonomy' );
600
				break;
601
			case 'names':
602
				$selects = array( 't.term_id', 'tt.parent', 'tt.count', 't.name', 'tt.taxonomy' );
603
				break;
604
			case 'count':
605
				$orderby = '';
606
				$order = '';
607
				$selects = array( 'COUNT(*)' );
608
				break;
609
			case 'id=>name':
610
				$selects = array( 't.term_id', 't.name', 'tt.count', 'tt.taxonomy' );
611
				break;
612
			case 'id=>slug':
613
				$selects = array( 't.term_id', 't.slug', 'tt.count', 'tt.taxonomy' );
614
				break;
615
		}
616
617
		$_fields = $args['fields'];
618
619
		/**
620
		 * Filters the fields to select in the terms query.
621
		 *
622
		 * Field lists modified using this filter will only modify the term fields returned
623
		 * by the function when the `$fields` parameter set to 'count' or 'all'. In all other
624
		 * cases, the term fields in the results array will be determined by the `$fields`
625
		 * parameter alone.
626
		 *
627
		 * Use of this filter can result in unpredictable behavior, and is not recommended.
628
		 *
629
		 * @since 2.8.0
630
		 *
631
		 * @param array $selects    An array of fields to select for the terms query.
632
		 * @param array $args       An array of term query arguments.
633
		 * @param array $taxonomies An array of taxonomies.
634
		 */
635
		$fields = implode( ', ', apply_filters( 'get_terms_fields', $selects, $args, $taxonomies ) );
636
637
		$join .= " INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
638
639
		if ( ! empty( $this->query_vars['object_ids'] ) ) {
640
			$join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id";
641
		}
642
643
		$where = implode( ' AND ', $this->sql_clauses['where'] );
644
645
		/**
646
		 * Filters the terms query SQL clauses.
647
		 *
648
		 * @since 3.1.0
649
		 *
650
		 * @param array $pieces     Terms query SQL clauses.
651
		 * @param array $taxonomies An array of taxonomies.
652
		 * @param array $args       An array of terms query arguments.
653
		 */
654
		$clauses = apply_filters( 'terms_clauses', compact( 'fields', 'join', 'where', 'distinct', 'orderby', 'order', 'limits' ), $taxonomies, $args );
655
656
		$fields = isset( $clauses[ 'fields' ] ) ? $clauses[ 'fields' ] : '';
657
		$join = isset( $clauses[ 'join' ] ) ? $clauses[ 'join' ] : '';
658
		$where = isset( $clauses[ 'where' ] ) ? $clauses[ 'where' ] : '';
659
		$distinct = isset( $clauses[ 'distinct' ] ) ? $clauses[ 'distinct' ] : '';
660
		$orderby = isset( $clauses[ 'orderby' ] ) ? $clauses[ 'orderby' ] : '';
661
		$order = isset( $clauses[ 'order' ] ) ? $clauses[ 'order' ] : '';
662
		$limits = isset( $clauses[ 'limits' ] ) ? $clauses[ 'limits' ] : '';
663
664
		if ( $where ) {
665
			$where = "WHERE $where";
666
		}
667
668
		$this->sql_clauses['select']  = "SELECT $distinct $fields";
669
		$this->sql_clauses['from']    = "FROM $wpdb->terms AS t $join";
670
		$this->sql_clauses['orderby'] = $orderby ? "$orderby $order" : '';
671
		$this->sql_clauses['limits']  = $limits;
672
673
		$this->request = "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['orderby']} {$this->sql_clauses['limits']}";
674
675
		// $args can be anything. Only use the args defined in defaults to compute the key.
676
		$key = md5( serialize( wp_array_slice_assoc( $args, array_keys( $this->query_var_defaults ) ) ) . serialize( $taxonomies ) . $this->request );
677
		$last_changed = wp_cache_get_last_changed( 'terms' );
678
		$cache_key = "get_terms:$key:$last_changed";
679
		$cache = wp_cache_get( $cache_key, 'terms' );
680
		if ( false !== $cache ) {
681
			if ( 'all' === $_fields ) {
682
				$cache = array_map( 'get_term', $cache );
683
			}
684
685
			$this->terms = $cache;
0 ignored issues
show
Documentation Bug introduced by
It seems like $cache of type * is incompatible with the declared type array of property $terms.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
686
			return $this->terms;
687
		}
688
689
		if ( 'count' == $_fields ) {
690
			$count = $wpdb->get_var( $this->request );
691
			wp_cache_set( $cache_key, $count, 'terms' );
692
			return $count;
693
		}
694
695
		$terms = $wpdb->get_results( $this->request );
696
		if ( 'all' == $_fields || 'all_with_object_id' === $_fields ) {
697
			update_term_cache( $terms );
698
		}
699
700
		// Prime termmeta cache.
701
		if ( $args['update_term_meta_cache'] ) {
702
			$term_ids = wp_list_pluck( $terms, 'term_id' );
703
			update_termmeta_cache( $term_ids );
704
		}
705
706
		if ( empty( $terms ) ) {
707
			wp_cache_add( $cache_key, array(), 'terms', DAY_IN_SECONDS );
708
			return array();
709
		}
710
711
		if ( $child_of ) {
712
			foreach ( $taxonomies as $_tax ) {
713
				$children = _get_term_hierarchy( $_tax );
714
				if ( ! empty( $children ) ) {
715
					$terms = _get_term_children( $child_of, $terms, $_tax );
716
				}
717
			}
718
		}
719
720
		// Update term counts to include children.
721
		if ( $args['pad_counts'] && 'all' == $_fields ) {
722
			foreach ( $taxonomies as $_tax ) {
723
				_pad_term_counts( $terms, $_tax );
724
			}
725
		}
726
727
		// Make sure we show empty categories that have children.
728
		if ( $hierarchical && $args['hide_empty'] && is_array( $terms ) ) {
729
			foreach ( $terms as $k => $term ) {
730
				if ( ! $term->count ) {
731
					$children = get_term_children( $term->term_id, $term->taxonomy );
732
					if ( is_array( $children ) ) {
733
						foreach ( $children as $child_id ) {
734
							$child = get_term( $child_id, $term->taxonomy );
735
							if ( $child->count ) {
736
								continue 2;
737
							}
738
						}
739
					}
740
741
					// It really is empty.
742
					unset( $terms[ $k ] );
743
				}
744
			}
745
		}
746
747
		/*
748
		 * When querying for terms connected to objects, we may get
749
		 * duplicate results. The duplicates should be preserved if
750
		 * `$fields` is 'all_with_object_id', but should otherwise be
751
		 * removed.
752
		 */
753
		if ( ! empty( $args['object_ids'] ) && 'all_with_object_id' != $_fields ) {
754
			$_tt_ids = $_terms = array();
755
			foreach ( $terms as $term ) {
756
				if ( isset( $_tt_ids[ $term->term_id ] ) ) {
757
					continue;
758
				}
759
760
				$_tt_ids[ $term->term_id ] = 1;
761
				$_terms[] = $term;
762
			}
763
764
			$terms = $_terms;
765
		}
766
767
		$_terms = array();
768
		if ( 'id=>parent' == $_fields ) {
769
			foreach ( $terms as $term ) {
770
				$_terms[ $term->term_id ] = $term->parent;
771
			}
772
		} elseif ( 'ids' == $_fields ) {
773
			foreach ( $terms as $term ) {
774
				$_terms[] = (int) $term->term_id;
775
			}
776
		} elseif ( 'tt_ids' == $_fields ) {
777
			foreach ( $terms as $term ) {
778
				$_terms[] = (int) $term->term_taxonomy_id;
779
			}
780
		} elseif ( 'names' == $_fields ) {
781
			foreach ( $terms as $term ) {
782
				$_terms[] = $term->name;
783
			}
784
		} elseif ( 'slugs' == $_fields ) {
785
			foreach ( $terms as $term ) {
786
				$_terms[] = $term->slug;
787
			}
788
		} elseif ( 'id=>name' == $_fields ) {
789
			foreach ( $terms as $term ) {
790
				$_terms[ $term->term_id ] = $term->name;
791
			}
792
		} elseif ( 'id=>slug' == $_fields ) {
793
			foreach ( $terms as $term ) {
794
				$_terms[ $term->term_id ] = $term->slug;
795
			}
796
		}
797
798
		if ( ! empty( $_terms ) ) {
799
			$terms = $_terms;
800
		}
801
802
		// Hierarchical queries are not limited, so 'offset' and 'number' must be handled now.
803
		if ( $hierarchical && $number && is_array( $terms ) ) {
804
			if ( $offset >= count( $terms ) ) {
805
				$terms = array();
806
			} else {
807
				$terms = array_slice( $terms, $offset, $number, true );
808
			}
809
		}
810
811
		wp_cache_add( $cache_key, $terms, 'terms', DAY_IN_SECONDS );
812
813
		if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
814
			$terms = array_map( 'get_term', $terms );
815
		}
816
817
		$this->terms = $terms;
818
		return $this->terms;
819
	}
820
821
	/**
822
	 * Parse and sanitize 'orderby' keys passed to the term query.
823
	 *
824
	 * @since 4.6.0
825
	 * @access protected
826
	 *
827
	 * @global wpdb $wpdb WordPress database abstraction object.
828
	 *
829
	 * @param string $orderby_raw Alias for the field to order by.
830
	 * @return string|false Value to used in the ORDER clause. False otherwise.
831
	 */
832
	protected function parse_orderby( $orderby_raw ) {
833
		$_orderby = strtolower( $orderby_raw );
834
		$maybe_orderby_meta = false;
835
836
		if ( in_array( $_orderby, array( 'term_id', 'name', 'slug', 'term_group' ), true ) ) {
837
			$orderby = "t.$_orderby";
838
		} elseif ( in_array( $_orderby, array( 'count', 'parent', 'taxonomy', 'term_taxonomy_id', 'description' ), true ) ) {
839
			$orderby = "tt.$_orderby";
840
		} elseif ( 'term_order' === $_orderby ) {
841
			$orderby = 'tr.term_order';
842 View Code Duplication
		} elseif ( 'include' == $_orderby && ! empty( $this->query_vars['include'] ) ) {
843
			$include = implode( ',', wp_parse_id_list( $this->query_vars['include'] ) );
844
			$orderby = "FIELD( t.term_id, $include )";
845
		} elseif ( 'none' == $_orderby ) {
846
			$orderby = '';
847
		} elseif ( empty( $_orderby ) || 'id' == $_orderby || 'term_id' === $_orderby ) {
848
			$orderby = 't.term_id';
849
		} else {
850
			$orderby = 't.name';
851
852
			// This may be a value of orderby related to meta.
853
			$maybe_orderby_meta = true;
854
		}
855
856
		/**
857
		 * Filters the ORDERBY clause of the terms query.
858
		 *
859
		 * @since 2.8.0
860
		 *
861
		 * @param string $orderby    `ORDERBY` clause of the terms query.
862
		 * @param array  $args       An array of terms query arguments.
863
		 * @param array  $taxonomies An array of taxonomies.
864
		 */
865
		$orderby = apply_filters( 'get_terms_orderby', $orderby, $this->query_vars, $this->query_vars['taxonomy'] );
866
867
		// Run after the 'get_terms_orderby' filter for backward compatibility.
868
		if ( $maybe_orderby_meta ) {
869
			$maybe_orderby_meta = $this->parse_orderby_meta( $_orderby );
870
			if ( $maybe_orderby_meta ) {
871
				$orderby = $maybe_orderby_meta;
872
			}
873
		}
874
875
		return $orderby;
876
	}
877
878
	/**
879
	 * Generate the ORDER BY clause for an 'orderby' param that is potentially related to a meta query.
880
	 *
881
	 * @since 4.6.0
882
	 * @access protected
883
	 *
884
	 * @param string $orderby_raw Raw 'orderby' value passed to WP_Term_Query.
885
	 * @return string ORDER BY clause.
886
	 */
887
	protected function parse_orderby_meta( $orderby_raw ) {
888
		$orderby = '';
889
890
		// Tell the meta query to generate its SQL, so we have access to table aliases.
891
		$this->meta_query->get_sql( 'term', 't', 'term_id' );
892
		$meta_clauses = $this->meta_query->get_clauses();
893
		if ( ! $meta_clauses || ! $orderby_raw ) {
894
			return $orderby;
895
		}
896
897
		$allowed_keys = array();
898
		$primary_meta_key = null;
899
		$primary_meta_query = reset( $meta_clauses );
900
		if ( ! empty( $primary_meta_query['key'] ) ) {
901
			$primary_meta_key = $primary_meta_query['key'];
902
			$allowed_keys[] = $primary_meta_key;
903
		}
904
		$allowed_keys[] = 'meta_value';
905
		$allowed_keys[] = 'meta_value_num';
906
		$allowed_keys   = array_merge( $allowed_keys, array_keys( $meta_clauses ) );
907
908
		if ( ! in_array( $orderby_raw, $allowed_keys, true ) ) {
909
			return $orderby;
910
		}
911
912
		switch( $orderby_raw ) {
913
			case $primary_meta_key:
914 View Code Duplication
			case 'meta_value':
915
				if ( ! empty( $primary_meta_query['type'] ) ) {
916
					$orderby = "CAST({$primary_meta_query['alias']}.meta_value AS {$primary_meta_query['cast']})";
917
				} else {
918
					$orderby = "{$primary_meta_query['alias']}.meta_value";
919
				}
920
				break;
921
922
			case 'meta_value_num':
923
				$orderby = "{$primary_meta_query['alias']}.meta_value+0";
924
				break;
925
926
			default:
927
				if ( array_key_exists( $orderby_raw, $meta_clauses ) ) {
928
					// $orderby corresponds to a meta_query clause.
929
					$meta_clause = $meta_clauses[ $orderby_raw ];
930
					$orderby = "CAST({$meta_clause['alias']}.meta_value AS {$meta_clause['cast']})";
931
				}
932
				break;
933
		}
934
935
		return $orderby;
936
	}
937
938
	/**
939
	 * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
940
	 *
941
	 * @since 4.6.0
942
	 * @access protected
943
	 *
944
	 * @param string $order The 'order' query variable.
945
	 * @return string The sanitized 'order' query variable.
946
	 */
947 View Code Duplication
	protected function parse_order( $order ) {
948
		if ( ! is_string( $order ) || empty( $order ) ) {
949
			return 'DESC';
950
		}
951
952
		if ( 'ASC' === strtoupper( $order ) ) {
953
			return 'ASC';
954
		} else {
955
			return 'DESC';
956
		}
957
	}
958
959
	/**
960
	 * Used internally to generate a SQL string related to the 'search' parameter.
961
	 *
962
	 * @since 4.6.0
963
	 * @access protected
964
	 *
965
	 * @global wpdb $wpdb WordPress database abstraction object.
966
	 *
967
	 * @param string $string
968
	 * @return string
969
	 */
970
	protected function get_search_sql( $string ) {
971
		global $wpdb;
972
973
		$like = '%' . $wpdb->esc_like( $string ) . '%';
974
975
		return $wpdb->prepare( '((t.name LIKE %s) OR (t.slug LIKE %s))', $like, $like );
976
	}
977
}
978