Issues (2010)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

wp-includes/class-wp-term-query.php (15 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
	 * @since 4.7.0
91
	 * @access protected
92
	 * @var wpdb
93
	 */
94
	protected $db;
95
96
	/**
97
	 * Constructor.
98
	 *
99
	 * Sets up the term query, based on the query vars passed.
100
	 *
101
	 * @since 4.6.0
102
	 * @since 4.6.0 Introduced 'term_taxonomy_id' parameter.
103
	 * @access public
104
	 *
105
	 * @param string|array $query {
106
	 *     Optional. Array or query string of term query parameters. Default empty.
107
	 *
108
	 *     @type string|array $taxonomy               Taxonomy name, or array of taxonomies, to which results should
109
	 *                                                be limited.
110
	 *     @type string       $orderby                Field(s) to order terms by. Accepts term fields ('name',
111
	 *                                                'slug', 'term_group', 'term_id', 'id', 'description'),
112
	 *                                                'count' for term taxonomy count, 'include' to match the
113
	 *                                                'order' of the $include param, 'meta_value', 'meta_value_num',
114
	 *                                                the value of `$meta_key`, the array keys of `$meta_query`, or
115
	 *                                                'none' to omit the ORDER BY clause. Defaults to 'name'.
116
	 *     @type string       $order                  Whether to order terms in ascending or descending order.
117
	 *                                                Accepts 'ASC' (ascending) or 'DESC' (descending).
118
	 *                                                Default 'ASC'.
119
	 *     @type bool|int     $hide_empty             Whether to hide terms not assigned to any posts. Accepts
120
	 *                                                1|true or 0|false. Default 1|true.
121
	 *     @type array|string $include                Array or comma/space-separated string of term ids to include.
122
	 *                                                Default empty array.
123
	 *     @type array|string $exclude                Array or comma/space-separated string of term ids to exclude.
124
	 *                                                If $include is non-empty, $exclude is ignored.
125
	 *                                                Default empty array.
126
	 *     @type array|string $exclude_tree           Array or comma/space-separated string of term ids to exclude
127
	 *                                                along with all of their descendant terms. If $include is
128
	 *                                                non-empty, $exclude_tree is ignored. Default empty array.
129
	 *     @type int|string   $number                 Maximum number of terms to return. Accepts ''|0 (all) or any
130
	 *                                                positive number. Default ''|0 (all).
131
	 *     @type int          $offset                 The number by which to offset the terms query. Default empty.
132
	 *     @type string       $fields                 Term fields to query for. Accepts 'all' (returns an array of
133
	 *                                                complete term objects), 'ids' (returns an array of ids),
134
	 *                                                'id=>parent' (returns an associative array with ids as keys,
135
	 *                                                parent term IDs as values), 'names' (returns an array of term
136
	 *                                                names), 'count' (returns the number of matching terms),
137
	 *                                                'id=>name' (returns an associative array with ids as keys,
138
	 *                                                term names as values), or 'id=>slug' (returns an associative
139
	 *                                                array with ids as keys, term slugs 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`.
177
	 *     @type string       $meta_value             Limit terms to those matching a specific metadata value.
178
	 *                                                Usually used in conjunction with `$meta_key`.
179
	 * }
180
	 */
181
	public function __construct( $query = '' ) {
182
		$this->db = $GLOBALS['wpdb'];
183
184
		$this->query_var_defaults = array(
185
			'taxonomy'               => null,
186
			'orderby'                => 'name',
187
			'order'                  => 'ASC',
188
			'hide_empty'             => true,
189
			'include'                => array(),
190
			'exclude'                => array(),
191
			'exclude_tree'           => array(),
192
			'number'                 => '',
193
			'offset'                 => '',
194
			'fields'                 => 'all',
195
			'count'                  => false,
196
			'name'                   => '',
197
			'slug'                   => '',
198
			'term_taxonomy_id'       => '',
199
			'hierarchical'           => true,
200
			'search'                 => '',
201
			'name__like'             => '',
202
			'description__like'      => '',
203
			'pad_counts'             => false,
204
			'get'                    => '',
205
			'child_of'               => 0,
206
			'parent'                 => '',
207
			'childless'              => false,
208
			'cache_domain'           => 'core',
209
			'update_term_meta_cache' => true,
210
			'meta_query'             => '',
211
		);
212
213
		if ( ! empty( $query ) ) {
214
			$this->query( $query );
215
		}
216
	}
217
218
	/**
219
	 * Parse arguments passed to the term query with default query parameters.
220
	 *
221
	 * @since 4.6.0
222
	 * @access public
223
	 *
224
	 * @param string|array $query WP_Term_Query arguments. See WP_Term_Query::__construct()
225
	 */
226
	public function parse_query( $query = '' ) {
227
		if ( empty( $query ) ) {
228
			$query = $this->query_vars;
229
		}
230
231
		$taxonomies = isset( $query['taxonomy'] ) ? (array) $query['taxonomy'] : null;
232
233
		/**
234
		 * Filters the terms query default arguments.
235
		 *
236
		 * Use {@see 'get_terms_args'} to filter the passed arguments.
237
		 *
238
		 * @since 4.4.0
239
		 *
240
		 * @param array $defaults   An array of default get_terms() arguments.
241
		 * @param array $taxonomies An array of taxonomies.
242
		 */
243
		$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...
244
245
		$query = wp_parse_args( $query, $this->query_var_defaults );
246
247
		$query['number'] = absint( $query['number'] );
248
		$query['offset'] = absint( $query['offset'] );
249
250
		// 'parent' overrides 'child_of'.
251
		if ( 0 < intval( $query['parent'] ) ) {
252
			$query['child_of'] = false;
253
		}
254
255 View Code Duplication
		if ( 'all' == $query['get'] ) {
256
			$query['childless'] = false;
257
			$query['child_of'] = 0;
258
			$query['hide_empty'] = 0;
259
			$query['hierarchical'] = false;
260
			$query['pad_counts'] = false;
261
		}
262
263
		$query['taxonomy'] = $taxonomies;
264
265
		/**
266
		 * Filters the terms query arguments.
267
		 *
268
		 * @since 3.1.0
269
		 *
270
		 * @param array $args       An array of get_terms() arguments.
271
		 * @param array $taxonomies An array of taxonomies.
272
		 */
273
		$this->query_vars = apply_filters( 'get_terms_args', $query, $taxonomies );
0 ignored issues
show
Documentation Bug introduced by
It seems like apply_filters('get_terms...', $query, $taxonomies) of type * is incompatible with the declared type array of property $query_vars.

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...
274
275
		/**
276
		 * Fires after term query vars have been parsed.
277
		 *
278
		 * @since 4.6.0
279
		 *
280
		 * @param WP_Term_Query $this Current instance of WP_Term_Query.
281
		 */
282
		do_action( 'parse_term_query', $this );
283
	}
284
285
	/**
286
	 * Sets up the query for retrieving terms.
287
	 *
288
	 * @since 4.6.0
289
	 * @access public
290
	 *
291
	 * @param string|array $query Array or URL query string of parameters.
292
	 * @return array|int List of terms, or number of terms when 'count' is passed as a query var.
293
	 */
294
	public function query( $query ) {
295
		$this->query_vars = wp_parse_args( $query );
296
		return $this->get_terms();
297
	}
298
299
	/**
300
	 * Get terms, based on query_vars.
301
	 *
302
	 * @param 4.6.0
303
	 * @access public
304
	 *
305
	 * @return array
306
	 */
307
	public function get_terms() {
308
		$this->parse_query( $this->query_vars );
309
		$args = $this->query_vars;
310
311
		// Set up meta_query so it's available to 'pre_get_terms'.
312
		$this->meta_query = new WP_Meta_Query();
313
		$this->meta_query->parse_query_vars( $args );
314
315
		/**
316
		 * Fires before terms are retrieved.
317
		 *
318
		 * @since 4.6.0
319
		 *
320
		 * @param WP_Term_Query $this Current instance of WP_Term_Query.
321
		 */
322
		do_action( 'pre_get_terms', $this );
323
324
		$taxonomies = $args['taxonomy'];
325
326
		// Save queries by not crawling the tree in the case of multiple taxes or a flat tax.
327
		$has_hierarchical_tax = false;
328
		if ( $taxonomies ) {
329
			foreach ( $taxonomies as $_tax ) {
330
				if ( is_taxonomy_hierarchical( $_tax ) ) {
331
					$has_hierarchical_tax = true;
332
				}
333
			}
334
		}
335
336
		if ( ! $has_hierarchical_tax ) {
337
			$args['hierarchical'] = false;
338
			$args['pad_counts'] = false;
339
		}
340
341
		// 'parent' overrides 'child_of'.
342
		if ( 0 < intval( $args['parent'] ) ) {
343
			$args['child_of'] = false;
344
		}
345
346 View Code Duplication
		if ( 'all' == $args['get'] ) {
347
			$args['childless'] = false;
348
			$args['child_of'] = 0;
349
			$args['hide_empty'] = 0;
350
			$args['hierarchical'] = false;
351
			$args['pad_counts'] = false;
352
		}
353
354
		/**
355
		 * Filters the terms query arguments.
356
		 *
357
		 * @since 3.1.0
358
		 *
359
		 * @param array $args       An array of get_terms() arguments.
360
		 * @param array $taxonomies An array of taxonomies.
361
		 */
362
		$args = apply_filters( 'get_terms_args', $args, $taxonomies );
363
364
		// Avoid the query if the queried parent/child_of term has no descendants.
365
		$child_of = $args['child_of'];
366
		$parent   = $args['parent'];
367
368
		if ( $child_of ) {
369
			$_parent = $child_of;
370
		} elseif ( $parent ) {
371
			$_parent = $parent;
372
		} else {
373
			$_parent = false;
374
		}
375
376
		if ( $_parent ) {
377
			$in_hierarchy = false;
378
			foreach ( $taxonomies as $_tax ) {
379
				$hierarchy = _get_term_hierarchy( $_tax );
380
381
				if ( isset( $hierarchy[ $_parent ] ) ) {
382
					$in_hierarchy = true;
383
				}
384
			}
385
386
			if ( ! $in_hierarchy ) {
387
				return array();
388
			}
389
		}
390
391
		$orderby = $this->parse_orderby( $this->query_vars['orderby'] );
392
		if ( $orderby ) {
393
			$orderby = "ORDER BY $orderby";
394
		}
395
396
		$order = $this->parse_order( $this->query_vars['order'] );
397
398
		if ( $taxonomies ) {
399
			$this->sql_clauses['where']['taxonomy'] = "tt.taxonomy IN ('" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "')";
400
		}
401
402
		$exclude      = $args['exclude'];
403
		$exclude_tree = $args['exclude_tree'];
404
		$include      = $args['include'];
405
406
		$inclusions = '';
407
		if ( ! empty( $include ) ) {
408
			$exclude = '';
409
			$exclude_tree = '';
410
			$inclusions = implode( ',', wp_parse_id_list( $include ) );
411
		}
412
413
		if ( ! empty( $inclusions ) ) {
414
			$this->sql_clauses['where']['inclusions'] = 't.term_id IN ( ' . $inclusions . ' )';
415
		}
416
417
		$exclusions = array();
418
		if ( ! empty( $exclude_tree ) ) {
419
			$exclude_tree = wp_parse_id_list( $exclude_tree );
420
			$excluded_children = $exclude_tree;
421
			foreach ( $exclude_tree as $extrunk ) {
422
				$excluded_children = array_merge(
423
					$excluded_children,
424
					(array) get_terms( $taxonomies[0], array(
425
						'child_of' => intval( $extrunk ),
426
						'fields' => 'ids',
427
						'hide_empty' => 0
428
					) )
429
				);
430
			}
431
			$exclusions = array_merge( $excluded_children, $exclusions );
432
		}
433
434
		if ( ! empty( $exclude ) ) {
435
			$exclusions = array_merge( wp_parse_id_list( $exclude ), $exclusions );
436
		}
437
438
		// 'childless' terms are those without an entry in the flattened term hierarchy.
439
		$childless = (bool) $args['childless'];
440
		if ( $childless ) {
441
			foreach ( $taxonomies as $_tax ) {
442
				$term_hierarchy = _get_term_hierarchy( $_tax );
443
				$exclusions = array_merge( array_keys( $term_hierarchy ), $exclusions );
444
			}
445
		}
446
447
		if ( ! empty( $exclusions ) ) {
448
			$exclusions = 't.term_id NOT IN (' . implode( ',', array_map( 'intval', $exclusions ) ) . ')';
449
		} else {
450
			$exclusions = '';
451
		}
452
453
		/**
454
		 * Filters the terms to exclude from the terms query.
455
		 *
456
		 * @since 2.3.0
457
		 *
458
		 * @param string $exclusions `NOT IN` clause of the terms query.
459
		 * @param array  $args       An array of terms query arguments.
460
		 * @param array  $taxonomies An array of taxonomies.
461
		 */
462
		$exclusions = apply_filters( 'list_terms_exclusions', $exclusions, $args, $taxonomies );
463
464
		if ( ! empty( $exclusions ) ) {
465
			// Must do string manipulation here for backward compatibility with filter.
466
			$this->sql_clauses['where']['exclusions'] = preg_replace( '/^\s*AND\s*/', '', $exclusions );
467
		}
468
469
		if ( ! empty( $args['name'] ) ) {
470
			$names = (array) $args['name'];
471
			foreach ( $names as &$_name ) {
472
				// `sanitize_term_field()` returns slashed data.
473
				$_name = stripslashes( sanitize_term_field( 'name', $_name, 0, reset( $taxonomies ), 'db' ) );
474
			}
475
476
			$this->sql_clauses['where']['name'] = "t.name IN ('" . implode( "', '", array_map( 'esc_sql', $names ) ) . "')";
477
		}
478
479
		if ( ! empty( $args['slug'] ) ) {
480
			if ( is_array( $args['slug'] ) ) {
481
				$slug = array_map( 'sanitize_title', $args['slug'] );
482
				$this->sql_clauses['where']['slug'] = "t.slug IN ('" . implode( "', '", $slug ) . "')";
483
			} else {
484
				$slug = sanitize_title( $args['slug'] );
485
				$this->sql_clauses['where']['slug'] = "t.slug = '$slug'";
486
			}
487
		}
488
489
		if ( ! empty( $args['term_taxonomy_id'] ) ) {
490
			if ( is_array( $args['term_taxonomy_id'] ) ) {
491
				$tt_ids = implode( ',', array_map( 'intval', $args['term_taxonomy_id'] ) );
492
				$this->sql_clauses['where']['term_taxonomy_id'] = "tt.term_taxonomy_id IN ({$tt_ids})";
493
			} else {
494
				$this->sql_clauses['where']['term_taxonomy_id'] = $this->db->prepare( "tt.term_taxonomy_id = %d", $args['term_taxonomy_id'] );
495
			}
496
		}
497
498 View Code Duplication
		if ( ! empty( $args['name__like'] ) ) {
499
			$this->sql_clauses['where']['name__like'] = $this->db->prepare( "t.name LIKE %s", '%' . $this->db->esc_like( $args['name__like'] ) . '%' );
500
		}
501
502 View Code Duplication
		if ( ! empty( $args['description__like'] ) ) {
503
			$this->sql_clauses['where']['description__like'] = $this->db->prepare( "tt.description LIKE %s", '%' . $this->db->esc_like( $args['description__like'] ) . '%' );
504
		}
505
506
		if ( '' !== $parent ) {
507
			$parent = (int) $parent;
508
			$this->sql_clauses['where']['parent'] = "tt.parent = '$parent'";
509
		}
510
511
		$hierarchical = $args['hierarchical'];
512
		if ( 'count' == $args['fields'] ) {
513
			$hierarchical = false;
514
		}
515
		if ( $args['hide_empty'] && !$hierarchical ) {
516
			$this->sql_clauses['where']['count'] = 'tt.count > 0';
517
		}
518
519
		$number = $args['number'];
520
		$offset = $args['offset'];
521
522
		// Don't limit the query results when we have to descend the family tree.
523
		if ( $number && ! $hierarchical && ! $child_of && '' === $parent ) {
524 View Code Duplication
			if ( $offset ) {
525
				$limits = 'LIMIT ' . $offset . ',' . $number;
526
			} else {
527
				$limits = 'LIMIT ' . $number;
528
			}
529
		} else {
530
			$limits = '';
531
		}
532
533
534
		if ( ! empty( $args['search'] ) ) {
535
			$this->sql_clauses['where']['search'] = $this->get_search_sql( $args['search'] );
536
		}
537
538
		// Meta query support.
539
		$join = '';
540
		$distinct = '';
541
542
		// Reparse meta_query query_vars, in case they were modified in a 'pre_get_terms' callback.
543
		$this->meta_query->parse_query_vars( $this->query_vars );
544
		$mq_sql = $this->meta_query->get_sql( 'term', 't', 'term_id' );
545
		$meta_clauses = $this->meta_query->get_clauses();
546
547
		if ( ! empty( $meta_clauses ) ) {
548
			$join .= $mq_sql['join'];
549
			$this->sql_clauses['where']['meta_query'] = preg_replace( '/^\s*AND\s*/', '', $mq_sql['where'] );
550
			$distinct .= "DISTINCT";
551
552
		}
553
554
		$selects = array();
555
		switch ( $args['fields'] ) {
556
			case 'all':
557
				$selects = array( 't.*', 'tt.*' );
558
				break;
559
			case 'ids':
560
			case 'id=>parent':
561
				$selects = array( 't.term_id', 'tt.parent', 'tt.count', 'tt.taxonomy' );
562
				break;
563
			case 'names':
564
				$selects = array( 't.term_id', 'tt.parent', 'tt.count', 't.name', 'tt.taxonomy' );
565
				break;
566
			case 'count':
567
				$orderby = '';
568
				$order = '';
569
				$selects = array( 'COUNT(*)' );
570
				break;
571
			case 'id=>name':
572
				$selects = array( 't.term_id', 't.name', 'tt.count', 'tt.taxonomy' );
573
				break;
574
			case 'id=>slug':
575
				$selects = array( 't.term_id', 't.slug', 'tt.count', 'tt.taxonomy' );
576
				break;
577
		}
578
579
		$_fields = $args['fields'];
580
581
		/**
582
		 * Filters the fields to select in the terms query.
583
		 *
584
		 * Field lists modified using this filter will only modify the term fields returned
585
		 * by the function when the `$fields` parameter set to 'count' or 'all'. In all other
586
		 * cases, the term fields in the results array will be determined by the `$fields`
587
		 * parameter alone.
588
		 *
589
		 * Use of this filter can result in unpredictable behavior, and is not recommended.
590
		 *
591
		 * @since 2.8.0
592
		 *
593
		 * @param array $selects    An array of fields to select for the terms query.
594
		 * @param array $args       An array of term query arguments.
595
		 * @param array $taxonomies An array of taxonomies.
596
		 */
597
		$fields = implode( ', ', apply_filters( 'get_terms_fields', $selects, $args, $taxonomies ) );
598
599
		$join .= " INNER JOIN {$this->db->term_taxonomy} AS tt ON t.term_id = tt.term_id";
600
601
		$where = implode( ' AND ', $this->sql_clauses['where'] );
602
603
		$pieces = array( 'fields', 'join', 'where', 'distinct', 'orderby', 'order', 'limits' );
604
605
		/**
606
		 * Filters the terms query SQL clauses.
607
		 *
608
		 * @since 3.1.0
609
		 *
610
		 * @param array $pieces     Terms query SQL clauses.
611
		 * @param array $taxonomies An array of taxonomies.
612
		 * @param array $args       An array of terms query arguments.
613
		 */
614
		$clauses = apply_filters( 'terms_clauses', compact( $pieces ), $taxonomies, $args );
615
616
		$fields = isset( $clauses[ 'fields' ] ) ? $clauses[ 'fields' ] : '';
617
		$join = isset( $clauses[ 'join' ] ) ? $clauses[ 'join' ] : '';
618
		$where = isset( $clauses[ 'where' ] ) ? $clauses[ 'where' ] : '';
619
		$distinct = isset( $clauses[ 'distinct' ] ) ? $clauses[ 'distinct' ] : '';
620
		$orderby = isset( $clauses[ 'orderby' ] ) ? $clauses[ 'orderby' ] : '';
621
		$order = isset( $clauses[ 'order' ] ) ? $clauses[ 'order' ] : '';
622
		$limits = isset( $clauses[ 'limits' ] ) ? $clauses[ 'limits' ] : '';
623
624
		if ( $where ) {
625
			$where = "WHERE $where";
626
		}
627
628
		$this->sql_clauses['select']  = "SELECT $distinct $fields";
629
		$this->sql_clauses['from']    = "FROM {$this->db->terms} AS t $join";
630
		$this->sql_clauses['orderby'] = $orderby ? "$orderby $order" : '';
631
		$this->sql_clauses['limits']  = $limits;
632
633
		$this->request = "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['orderby']} {$this->sql_clauses['limits']}";
634
635
		// $args can be anything. Only use the args defined in defaults to compute the key.
636
		$key = md5( serialize( wp_array_slice_assoc( $args, array_keys( $this->query_var_defaults ) ) ) . serialize( $taxonomies ) . $this->request );
637
		$last_changed = wp_cache_get( 'last_changed', 'terms' );
638
		if ( ! $last_changed ) {
639
			$last_changed = microtime();
640
			wp_cache_set( 'last_changed', $last_changed, 'terms' );
641
		}
642
		$cache_key = "get_terms:$key:$last_changed";
643
		$cache = wp_cache_get( $cache_key, 'terms' );
644
		if ( false !== $cache ) {
645
			if ( 'all' === $_fields ) {
646
				$cache = array_map( 'get_term', $cache );
647
			}
648
649
			$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...
650
			return $this->terms;
651
		}
652
653
		if ( 'count' == $_fields ) {
654
			return $this->db->get_var( $this->request );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->db->get_var($this->request); of type string|null adds the type string to the return on line 654 which is incompatible with the return type documented by WP_Term_Query::get_terms of type array.
Loading history...
655
		}
656
657
		$terms = $this->db->get_results( $this->request );
658
		if ( 'all' == $_fields ) {
659
			update_term_cache( $terms );
0 ignored issues
show
It seems like $terms defined by $this->db->get_results($this->request) on line 657 can also be of type null; however, update_term_cache() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
660
		}
661
662
		// Prime termmeta cache.
663
		if ( $args['update_term_meta_cache'] ) {
664
			$term_ids = wp_list_pluck( $terms, 'term_id' );
0 ignored issues
show
It seems like $terms defined by $this->db->get_results($this->request) on line 657 can also be of type null; however, wp_list_pluck() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
665
			update_termmeta_cache( $term_ids );
666
		}
667
668
		if ( empty( $terms ) ) {
669
			wp_cache_add( $cache_key, array(), 'terms', DAY_IN_SECONDS );
670
			return array();
671
		}
672
673
		if ( $child_of ) {
674
			foreach ( $taxonomies as $_tax ) {
675
				$children = _get_term_hierarchy( $_tax );
676
				if ( ! empty( $children ) ) {
677
					$terms = _get_term_children( $child_of, $terms, $_tax );
0 ignored issues
show
It seems like $terms defined by _get_term_children($child_of, $terms, $_tax) on line 677 can also be of type null or object<WP_Error> or object<WP_Term>; however, _get_term_children() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
678
				}
679
			}
680
		}
681
682
		// Update term counts to include children.
683
		if ( $args['pad_counts'] && 'all' == $_fields ) {
684
			foreach ( $taxonomies as $_tax ) {
685
				_pad_term_counts( $terms, $_tax );
0 ignored issues
show
It seems like $terms can also be of type null or object<WP_Error> or object<WP_Term>; however, _pad_term_counts() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
686
			}
687
		}
688
689
		// Make sure we show empty categories that have children.
690
		if ( $hierarchical && $args['hide_empty'] && is_array( $terms ) ) {
691
			foreach ( $terms as $k => $term ) {
692
				if ( ! $term->count ) {
693
					$children = get_term_children( $term->term_id, $term->taxonomy );
694
					if ( is_array( $children ) ) {
695
						foreach ( $children as $child_id ) {
696
							$child = get_term( $child_id, $term->taxonomy );
697
							if ( $child->count ) {
698
								continue 2;
699
							}
700
						}
701
					}
702
703
					// It really is empty.
704
					unset( $terms[ $k ] );
705
				}
706
			}
707
		}
708
709
		$_terms = array();
710
		if ( 'id=>parent' == $_fields ) {
711
			foreach ( $terms as $term ) {
0 ignored issues
show
The expression $terms of type array|object<WP_Term>|object<WP_Error>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
712
				$_terms[ $term->term_id ] = $term->parent;
713
			}
714
		} elseif ( 'ids' == $_fields ) {
715
			foreach ( $terms as $term ) {
0 ignored issues
show
The expression $terms of type array|object<WP_Term>|object<WP_Error>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
716
				$_terms[] = $term->term_id;
717
			}
718
		} elseif ( 'names' == $_fields ) {
719
			foreach ( $terms as $term ) {
0 ignored issues
show
The expression $terms of type array|object<WP_Term>|object<WP_Error>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
720
				$_terms[] = $term->name;
721
			}
722
		} elseif ( 'id=>name' == $_fields ) {
723
			foreach ( $terms as $term ) {
0 ignored issues
show
The expression $terms of type array|object<WP_Term>|object<WP_Error>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
724
				$_terms[ $term->term_id ] = $term->name;
725
			}
726
		} elseif ( 'id=>slug' == $_fields ) {
727
			foreach ( $terms as $term ) {
0 ignored issues
show
The expression $terms of type array|object<WP_Term>|object<WP_Error>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
728
				$_terms[ $term->term_id ] = $term->slug;
729
			}
730
		}
731
732
		if ( ! empty( $_terms ) ) {
733
			$terms = $_terms;
734
		}
735
736
		// Hierarchical queries are not limited, so 'offset' and 'number' must be handled now.
737
		if ( $hierarchical && $number && is_array( $terms ) ) {
738
			if ( $offset >= count( $terms ) ) {
739
				$terms = array();
740
			} else {
741
				$terms = array_slice( $terms, $offset, $number, true );
742
			}
743
		}
744
745
		wp_cache_add( $cache_key, $terms, 'terms', DAY_IN_SECONDS );
746
747
		if ( 'all' === $_fields ) {
748
			$terms = array_map( 'get_term', $terms );
749
		}
750
751
		$this->terms = $terms;
752
		return $this->terms;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->terms; (array|WP_Term|WP_Error|null) is incompatible with the return type documented by WP_Term_Query::get_terms of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
753
	}
754
755
	/**
756
	 * Parse and sanitize 'orderby' keys passed to the term query.
757
	 *
758
	 * @since 4.6.0
759
	 * @access protected
760
	 *
761
	 * @param string $orderby_raw Alias for the field to order by.
762
	 * @return string|false Value to used in the ORDER clause. False otherwise.
763
	 */
764
	protected function parse_orderby( $orderby_raw ) {
765
		$_orderby = strtolower( $orderby_raw );
766
		$maybe_orderby_meta = false;
767
		if ( 'count' == $_orderby ) {
768
			$orderby = 'tt.count';
769
		} elseif ( 'name' == $_orderby ) {
770
			$orderby = 't.name';
771
		} elseif ( 'slug' == $_orderby ) {
772
			$orderby = 't.slug';
773
		} elseif ( 'include' == $_orderby && ! empty( $this->query_vars['include'] ) ) {
774
			$include = implode( ',', array_map( 'absint', $this->query_vars['include'] ) );
775
			$orderby = "FIELD( t.term_id, $include )";
776
		} elseif ( 'term_group' == $_orderby ) {
777
			$orderby = 't.term_group';
778
		} elseif ( 'description' == $_orderby ) {
779
			$orderby = 'tt.description';
780
		} elseif ( 'none' == $_orderby ) {
781
			$orderby = '';
782
		} elseif ( empty( $_orderby ) || 'id' == $_orderby || 'term_id' === $_orderby ) {
783
			$orderby = 't.term_id';
784
		} else {
785
			$orderby = 't.name';
786
787
			// This may be a value of orderby related to meta.
788
			$maybe_orderby_meta = true;
789
		}
790
791
		/**
792
		 * Filters the ORDERBY clause of the terms query.
793
		 *
794
		 * @since 2.8.0
795
		 *
796
		 * @param string $orderby    `ORDERBY` clause of the terms query.
797
		 * @param array  $args       An array of terms query arguments.
798
		 * @param array  $taxonomies An array of taxonomies.
799
		 */
800
		$orderby = apply_filters( 'get_terms_orderby', $orderby, $this->query_vars, $this->query_vars['taxonomy'] );
801
802
		// Run after the 'get_terms_orderby' filter for backward compatibility.
803
		if ( $maybe_orderby_meta ) {
804
			$maybe_orderby_meta = $this->parse_orderby_meta( $_orderby );
805
			if ( $maybe_orderby_meta ) {
806
				$orderby = $maybe_orderby_meta;
807
			}
808
		}
809
810
		return $orderby;
811
	}
812
813
	/**
814
	 * Generate the ORDER BY clause for an 'orderby' param that is potentially related to a meta query.
815
	 *
816
	 * @since 4.6.0
817
	 * @access public
818
	 *
819
	 * @param string $orderby_raw Raw 'orderby' value passed to WP_Term_Query.
820
	 * @return string
821
	 */
822
	protected function parse_orderby_meta( $orderby_raw ) {
823
		$orderby = '';
824
825
		// Tell the meta query to generate its SQL, so we have access to table aliases.
826
		$this->meta_query->get_sql( 'term', 't', 'term_id' );
827
		$meta_clauses = $this->meta_query->get_clauses();
828
		if ( ! $meta_clauses || ! $orderby_raw ) {
829
			return $orderby;
830
		}
831
832
		$allowed_keys = array();
833
		$primary_meta_key = null;
834
		$primary_meta_query = reset( $meta_clauses );
835
		if ( ! empty( $primary_meta_query['key'] ) ) {
836
			$primary_meta_key = $primary_meta_query['key'];
837
			$allowed_keys[] = $primary_meta_key;
838
		}
839
		$allowed_keys[] = 'meta_value';
840
		$allowed_keys[] = 'meta_value_num';
841
		$allowed_keys   = array_merge( $allowed_keys, array_keys( $meta_clauses ) );
842
843
		if ( ! in_array( $orderby_raw, $allowed_keys, true ) ) {
844
			return $orderby;
845
		}
846
847
		switch( $orderby_raw ) {
848
			case $primary_meta_key:
849 View Code Duplication
			case 'meta_value':
850
				if ( ! empty( $primary_meta_query['type'] ) ) {
851
					$orderby = "CAST({$primary_meta_query['alias']}.meta_value AS {$primary_meta_query['cast']})";
852
				} else {
853
					$orderby = "{$primary_meta_query['alias']}.meta_value";
854
				}
855
				break;
856
857
			case 'meta_value_num':
858
				$orderby = "{$primary_meta_query['alias']}.meta_value+0";
859
				break;
860
861
			default:
862
				if ( array_key_exists( $orderby_raw, $meta_clauses ) ) {
863
					// $orderby corresponds to a meta_query clause.
864
					$meta_clause = $meta_clauses[ $orderby_raw ];
865
					$orderby = "CAST({$meta_clause['alias']}.meta_value AS {$meta_clause['cast']})";
866
				}
867
				break;
868
		}
869
870
		return $orderby;
871
	}
872
873
	/**
874
	 * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
875
	 *
876
	 * @since 4.6.0
877
	 * @access protected
878
	 *
879
	 * @param string $order The 'order' query variable.
880
	 * @return string The sanitized 'order' query variable.
881
	 */
882 View Code Duplication
	protected function parse_order( $order ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
883
		if ( ! is_string( $order ) || empty( $order ) ) {
884
			return 'DESC';
885
		}
886
887
		if ( 'ASC' === strtoupper( $order ) ) {
888
			return 'ASC';
889
		} else {
890
			return 'DESC';
891
		}
892
	}
893
894
	/**
895
	 * Used internally to generate a SQL string related to the 'search' parameter.
896
	 *
897
	 * @since 4.6.0
898
	 * @access protected
899
	 *
900
	 * @param string $string
901
	 * @return string
902
	 */
903
	protected function get_search_sql( $string ) {
904
		$like = '%' . $this->db->esc_like( $string ) . '%';
905
906
		return $this->db->prepare( '((t.name LIKE %s) OR (t.slug LIKE %s))', $like, $like );
907
	}
908
}
909