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-tax-query.php (2 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
 * Taxonomy API: WP_Tax_Query class
4
 *
5
 * @package WordPress
6
 * @subpackage Taxonomy
7
 * @since 4.4.0
8
 */
9
10
/**
11
 * Core class used to implement taxonomy queries for the Taxonomy API.
12
 *
13
 * Used for generating SQL clauses that filter a primary query according to object
14
 * taxonomy terms.
15
 *
16
 * WP_Tax_Query is a helper that allows primary query classes, such as WP_Query, to filter
17
 * their results by object metadata, by generating `JOIN` and `WHERE` subclauses to be
18
 * attached to the primary SQL query string.
19
 *
20
 * @since 3.1.0
21
 */
22
class WP_Tax_Query {
23
24
	/**
25
	 * Array of taxonomy queries.
26
	 *
27
	 * See WP_Tax_Query::__construct() for information on tax query arguments.
28
	 *
29
	 * @since 3.1.0
30
	 * @access public
31
	 * @var array
32
	 */
33
	public $queries = array();
34
35
	/**
36
	 * The relation between the queries. Can be one of 'AND' or 'OR'.
37
	 *
38
	 * @since 3.1.0
39
	 * @access public
40
	 * @var string
41
	 */
42
	public $relation;
43
44
	/**
45
	 * Standard response when the query should not return any rows.
46
	 *
47
	 * @since 3.2.0
48
	 *
49
	 * @static
50
	 * @access private
51
	 * @var string
52
	 */
53
	private static $no_results = array( 'join' => array( '' ), 'where' => array( '0 = 1' ) );
54
55
	/**
56
	 * A flat list of table aliases used in the JOIN clauses.
57
	 *
58
	 * @since 4.1.0
59
	 * @access protected
60
	 * @var array
61
	 */
62
	protected $table_aliases = array();
63
64
	/**
65
	 * Terms and taxonomies fetched by this query.
66
	 *
67
	 * We store this data in a flat array because they are referenced in a
68
	 * number of places by WP_Query.
69
	 *
70
	 * @since 4.1.0
71
	 * @access public
72
	 * @var array
73
	 */
74
	public $queried_terms = array();
75
76
	/**
77
	 * Database table that where the metadata's objects are stored (eg $wpdb->users).
78
	 *
79
	 * @since 4.1.0
80
	 * @access public
81
	 * @var string
82
	 */
83
	public $primary_table;
84
85
	/**
86
	 * Column in 'primary_table' that represents the ID of the object.
87
	 *
88
	 * @since 4.1.0
89
	 * @access public
90
	 * @var string
91
	 */
92
	public $primary_id_column;
93
94
	/**
95
	 * @since 4.7.0
96
	 * @access protected
97
	 * @var wpdb
98
	 */
99
	protected $db;
100
101
	/**
102
	 * Constructor.
103
	 *
104
	 * @since 3.1.0
105
	 * @since 4.1.0 Added support for `$operator` 'NOT EXISTS' and 'EXISTS' values.
106
	 * @access public
107
	 *
108
	 * @param array $tax_query {
109
	 *     Array of taxonomy query clauses.
110
	 *
111
	 *     @type string $relation Optional. The MySQL keyword used to join
112
	 *                            the clauses of the query. Accepts 'AND', or 'OR'. Default 'AND'.
113
	 *     @type array {
114
	 *         Optional. An array of first-order clause parameters, or another fully-formed tax query.
115
	 *
116
	 *         @type string           $taxonomy         Taxonomy being queried. Optional when field=term_taxonomy_id.
117
	 *         @type string|int|array $terms            Term or terms to filter by.
118
	 *         @type string           $field            Field to match $terms against. Accepts 'term_id', 'slug',
119
	 *                                                 'name', or 'term_taxonomy_id'. Default: 'term_id'.
120
	 *         @type string           $operator         MySQL operator to be used with $terms in the WHERE clause.
121
	 *                                                  Accepts 'AND', 'IN', 'NOT IN', 'EXISTS', 'NOT EXISTS'.
122
	 *                                                  Default: 'IN'.
123
	 *         @type bool             $include_children Optional. Whether to include child terms.
124
	 *                                                  Requires a $taxonomy. Default: true.
125
	 *     }
126
	 * }
127
	 */
128
	public function __construct( $tax_query ) {
129
		$this->db = $GLOBALS['wpdb'];
130
131
		if ( isset( $tax_query['relation'] ) ) {
132
			$this->relation = $this->sanitize_relation( $tax_query['relation'] );
133
		} else {
134
			$this->relation = 'AND';
135
		}
136
137
		$this->queries = $this->sanitize_query( $tax_query );
138
	}
139
140
	/**
141
	 * Ensure the 'tax_query' argument passed to the class constructor is well-formed.
142
	 *
143
	 * Ensures that each query-level clause has a 'relation' key, and that
144
	 * each first-order clause contains all the necessary keys from `$defaults`.
145
	 *
146
	 * @since 4.1.0
147
	 * @access public
148
	 *
149
	 * @param array $queries Array of queries clauses.
150
	 * @return array Sanitized array of query clauses.
151
	 */
152
	public function sanitize_query( $queries ) {
153
		$cleaned_query = array();
154
155
		$defaults = array(
156
			'taxonomy' => '',
157
			'terms' => array(),
158
			'field' => 'term_id',
159
			'operator' => 'IN',
160
			'include_children' => true,
161
		);
162
163
		foreach ( $queries as $key => $query ) {
164
			if ( 'relation' === $key ) {
165
				$cleaned_query['relation'] = $this->sanitize_relation( $query );
166
167
			// First-order clause.
168
			} elseif ( self::is_first_order_clause( $query ) ) {
169
170
				$cleaned_clause = array_merge( $defaults, $query );
171
				$cleaned_clause['terms'] = (array) $cleaned_clause['terms'];
172
				$cleaned_query[] = $cleaned_clause;
173
174
				/*
175
				 * Keep a copy of the clause in the flate
176
				 * $queried_terms array, for use in WP_Query.
177
				 */
178
				if ( ! empty( $cleaned_clause['taxonomy'] ) && 'NOT IN' !== $cleaned_clause['operator'] ) {
179
					$taxonomy = $cleaned_clause['taxonomy'];
180
					if ( ! isset( $this->queried_terms[ $taxonomy ] ) ) {
181
						$this->queried_terms[ $taxonomy ] = array();
182
					}
183
184
					/*
185
					 * Backward compatibility: Only store the first
186
					 * 'terms' and 'field' found for a given taxonomy.
187
					 */
188 View Code Duplication
					if ( ! empty( $cleaned_clause['terms'] ) && ! isset( $this->queried_terms[ $taxonomy ]['terms'] ) ) {
189
						$this->queried_terms[ $taxonomy ]['terms'] = $cleaned_clause['terms'];
190
					}
191
192 View Code Duplication
					if ( ! empty( $cleaned_clause['field'] ) && ! isset( $this->queried_terms[ $taxonomy ]['field'] ) ) {
193
						$this->queried_terms[ $taxonomy ]['field'] = $cleaned_clause['field'];
194
					}
195
				}
196
197
			// Otherwise, it's a nested query, so we recurse.
198
			} elseif ( is_array( $query ) ) {
199
				$cleaned_subquery = $this->sanitize_query( $query );
200
201
				if ( ! empty( $cleaned_subquery ) ) {
202
					// All queries with children must have a relation.
203
					if ( ! isset( $cleaned_subquery['relation'] ) ) {
204
						$cleaned_subquery['relation'] = 'AND';
205
					}
206
207
					$cleaned_query[] = $cleaned_subquery;
208
				}
209
			}
210
		}
211
212
		return $cleaned_query;
213
	}
214
215
	/**
216
	 * Sanitize a 'relation' operator.
217
	 *
218
	 * @since 4.1.0
219
	 * @access public
220
	 *
221
	 * @param string $relation Raw relation key from the query argument.
222
	 * @return string Sanitized relation ('AND' or 'OR').
223
	 */
224
	public function sanitize_relation( $relation ) {
225
		if ( 'OR' === strtoupper( $relation ) ) {
226
			return 'OR';
227
		} else {
228
			return 'AND';
229
		}
230
	}
231
232
	/**
233
	 * Determine whether a clause is first-order.
234
	 *
235
	 * A "first-order" clause is one that contains any of the first-order
236
	 * clause keys ('terms', 'taxonomy', 'include_children', 'field',
237
	 * 'operator'). An empty clause also counts as a first-order clause,
238
	 * for backward compatibility. Any clause that doesn't meet this is
239
	 * determined, by process of elimination, to be a higher-order query.
240
	 *
241
	 * @since 4.1.0
242
	 *
243
	 * @static
244
	 * @access protected
245
	 *
246
	 * @param array $query Tax query arguments.
247
	 * @return bool Whether the query clause is a first-order clause.
248
	 */
249
	protected static function is_first_order_clause( $query ) {
250
		return is_array( $query ) && ( empty( $query ) || array_key_exists( 'terms', $query ) || array_key_exists( 'taxonomy', $query ) || array_key_exists( 'include_children', $query ) || array_key_exists( 'field', $query ) || array_key_exists( 'operator', $query ) );
251
	}
252
253
	/**
254
	 * Generates SQL clauses to be appended to a main query.
255
	 *
256
	 * @since 3.1.0
257
	 *
258
	 * @static
259
	 * @access public
260
	 *
261
	 * @param string $primary_table     Database table where the object being filtered is stored (eg wp_users).
262
	 * @param string $primary_id_column ID column for the filtered object in $primary_table.
263
	 * @return array {
264
	 *     Array containing JOIN and WHERE SQL clauses to append to the main query.
265
	 *
266
	 *     @type string $join  SQL fragment to append to the main JOIN clause.
267
	 *     @type string $where SQL fragment to append to the main WHERE clause.
268
	 * }
269
	 */
270
	public function get_sql( $primary_table, $primary_id_column ) {
271
		$this->primary_table = $primary_table;
272
		$this->primary_id_column = $primary_id_column;
273
274
		return $this->get_sql_clauses();
275
	}
276
277
	/**
278
	 * Generate SQL clauses to be appended to a main query.
279
	 *
280
	 * Called by the public WP_Tax_Query::get_sql(), this method
281
	 * is abstracted out to maintain parity with the other Query classes.
282
	 *
283
	 * @since 4.1.0
284
	 * @access protected
285
	 *
286
	 * @return array {
287
	 *     Array containing JOIN and WHERE SQL clauses to append to the main query.
288
	 *
289
	 *     @type string $join  SQL fragment to append to the main JOIN clause.
290
	 *     @type string $where SQL fragment to append to the main WHERE clause.
291
	 * }
292
	 */
293 View Code Duplication
	protected function get_sql_clauses() {
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...
294
		/*
295
		 * $queries are passed by reference to get_sql_for_query() for recursion.
296
		 * To keep $this->queries unaltered, pass a copy.
297
		 */
298
		$queries = $this->queries;
299
		$sql = $this->get_sql_for_query( $queries );
300
301
		if ( ! empty( $sql['where'] ) ) {
302
			$sql['where'] = ' AND ' . $sql['where'];
303
		}
304
305
		return $sql;
306
	}
307
308
	/**
309
	 * Generate SQL clauses for a single query array.
310
	 *
311
	 * If nested subqueries are found, this method recurses the tree to
312
	 * produce the properly nested SQL.
313
	 *
314
	 * @since 4.1.0
315
	 * @access protected
316
	 *
317
	 * @param array $query Query to parse, passed by reference.
318
	 * @param int   $depth Optional. Number of tree levels deep we currently are.
319
	 *                     Used to calculate indentation. Default 0.
320
	 * @return array {
321
	 *     Array containing JOIN and WHERE SQL clauses to append to a single query array.
322
	 *
323
	 *     @type string $join  SQL fragment to append to the main JOIN clause.
324
	 *     @type string $where SQL fragment to append to the main WHERE clause.
325
	 * }
326
	 */
327 View Code Duplication
	protected function get_sql_for_query( &$query, $depth = 0 ) {
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...
328
		$sql_chunks = array(
329
			'join'  => array(),
330
			'where' => array(),
331
		);
332
333
		$sql = array(
334
			'join'  => '',
335
			'where' => '',
336
		);
337
338
		$indent = '';
339
		for ( $i = 0; $i < $depth; $i++ ) {
340
			$indent .= "  ";
341
		}
342
343
		foreach ( $query as $key => &$clause ) {
344
			if ( 'relation' === $key ) {
345
				$relation = $query['relation'];
346
			} elseif ( is_array( $clause ) ) {
347
348
				// This is a first-order clause.
349
				if ( $this->is_first_order_clause( $clause ) ) {
350
					$clause_sql = $this->get_sql_for_clause( $clause, $query );
351
352
					$where_count = count( $clause_sql['where'] );
353
					if ( ! $where_count ) {
354
						$sql_chunks['where'][] = '';
355
					} elseif ( 1 === $where_count ) {
356
						$sql_chunks['where'][] = $clause_sql['where'][0];
357
					} else {
358
						$sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
359
					}
360
361
					$sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
362
				// This is a subquery, so we recurse.
363
				} else {
364
					$clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
365
366
					$sql_chunks['where'][] = $clause_sql['where'];
367
					$sql_chunks['join'][]  = $clause_sql['join'];
368
				}
369
			}
370
		}
371
372
		// Filter to remove empties.
373
		$sql_chunks['join']  = array_filter( $sql_chunks['join'] );
374
		$sql_chunks['where'] = array_filter( $sql_chunks['where'] );
375
376
		if ( empty( $relation ) ) {
377
			$relation = 'AND';
378
		}
379
380
		// Filter duplicate JOIN clauses and combine into a single string.
381
		if ( ! empty( $sql_chunks['join'] ) ) {
382
			$sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
383
		}
384
385
		// Generate a single WHERE clause with proper brackets and indentation.
386
		if ( ! empty( $sql_chunks['where'] ) ) {
387
			$sql['where'] = '( ' . "\n  " . $indent . implode( ' ' . "\n  " . $indent . $relation . ' ' . "\n  " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')';
388
		}
389
390
		return $sql;
391
	}
392
393
	/**
394
	 * Generate SQL JOIN and WHERE clauses for a "first-order" query clause.
395
	 *
396
	 * @since 4.1.0
397
	 * @access public
398
	 *
399
	 * @param array $clause       Query clause, passed by reference.
400
	 * @param array $parent_query Parent query array.
401
	 * @return array {
402
	 *     Array containing JOIN and WHERE SQL clauses to append to a first-order query.
403
	 *
404
	 *     @type string $join  SQL fragment to append to the main JOIN clause.
405
	 *     @type string $where SQL fragment to append to the main WHERE clause.
406
	 * }
407
	 */
408
	public function get_sql_for_clause( &$clause, $parent_query ) {
409
		$sql = array(
410
			'where' => array(),
411
			'join'  => array(),
412
		);
413
414
		$join = $where = '';
415
416
		$this->clean_query( $clause );
417
418
		if ( is_wp_error( $clause ) ) {
419
			return self::$no_results;
420
		}
421
422
		$terms = $clause['terms'];
423
		$operator = strtoupper( $clause['operator'] );
424
425
		if ( 'IN' == $operator ) {
426
427
			if ( empty( $terms ) ) {
428
				return self::$no_results;
429
			}
430
431
			$terms = implode( ',', $terms );
432
433
			/*
434
			 * Before creating another table join, see if this clause has a
435
			 * sibling with an existing join that can be shared.
436
			 */
437
			$alias = $this->find_compatible_table_alias( $clause, $parent_query );
438
			if ( false === $alias ) {
439
				$i = count( $this->table_aliases );
440
				$alias = $i ? 'tt' . $i : $this->db->term_relationships;
441
442
				// Store the alias as part of a flat array to build future iterators.
443
				$this->table_aliases[] = $alias;
444
445
				// Store the alias with this clause, so later siblings can use it.
446
				$clause['alias'] = $alias;
447
448
				$join .= " LEFT JOIN {$this->db->term_relationships}";
449
				$join .= $i ? " AS $alias" : '';
450
				$join .= " ON ($this->primary_table.$this->primary_id_column = $alias.object_id)";
451
			}
452
453
454
			$where = "$alias.term_taxonomy_id $operator ($terms)";
455
456
		} elseif ( 'NOT IN' == $operator ) {
457
458
			if ( empty( $terms ) ) {
459
				return $sql;
460
			}
461
462
			$terms = implode( ',', $terms );
463
464
			$where = "$this->primary_table.$this->primary_id_column NOT IN (
465
				SELECT object_id
466
				FROM {$this->db->term_relationships}
467
				WHERE term_taxonomy_id IN ($terms)
468
			)";
469
470
		} elseif ( 'AND' == $operator ) {
471
472
			if ( empty( $terms ) ) {
473
				return $sql;
474
			}
475
476
			$num_terms = count( $terms );
477
478
			$terms = implode( ',', $terms );
479
480
			$where = "(
481
				SELECT COUNT(1)
482
				FROM {$this->db->term_relationships}
483
				WHERE term_taxonomy_id IN ($terms)
484
				AND object_id = $this->primary_table.$this->primary_id_column
485
			) = $num_terms";
486
487
		} elseif ( 'NOT EXISTS' === $operator || 'EXISTS' === $operator ) {
488
489
			$where = $this->db->prepare( "$operator (
490
				SELECT 1
491
				FROM {$this->db->term_relationships}
492
				INNER JOIN {$this->db->term_taxonomy}
493
				ON {$this->db->term_taxonomy}.term_taxonomy_id = {$this->db->term_relationships}.term_taxonomy_id
494
				WHERE {$this->db->term_taxonomy}.taxonomy = %s
495
				AND {$this->db->term_relationships}.object_id = $this->primary_table.$this->primary_id_column
496
			)", $clause['taxonomy'] );
497
498
		}
499
500
		$sql['join'][]  = $join;
501
		$sql['where'][] = $where;
502
		return $sql;
503
	}
504
505
	/**
506
	 * Identify an existing table alias that is compatible with the current query clause.
507
	 *
508
	 * We avoid unnecessary table joins by allowing each clause to look for
509
	 * an existing table alias that is compatible with the query that it
510
	 * needs to perform.
511
	 *
512
	 * An existing alias is compatible if (a) it is a sibling of `$clause`
513
	 * (ie, it's under the scope of the same relation), and (b) the combination
514
	 * of operator and relation between the clauses allows for a shared table
515
	 * join. In the case of WP_Tax_Query, this only applies to 'IN'
516
	 * clauses that are connected by the relation 'OR'.
517
	 *
518
	 * @since 4.1.0
519
	 * @access protected
520
	 *
521
	 * @param array       $clause       Query clause.
522
	 * @param array       $parent_query Parent query of $clause.
523
	 * @return string|false Table alias if found, otherwise false.
524
	 */
525
	protected function find_compatible_table_alias( $clause, $parent_query ) {
526
		$alias = false;
527
528
		// Sanity check. Only IN queries use the JOIN syntax .
529
		if ( ! isset( $clause['operator'] ) || 'IN' !== $clause['operator'] ) {
530
			return $alias;
531
		}
532
533
		// Since we're only checking IN queries, we're only concerned with OR relations.
534
		if ( ! isset( $parent_query['relation'] ) || 'OR' !== $parent_query['relation'] ) {
535
			return $alias;
536
		}
537
538
		$compatible_operators = array( 'IN' );
539
540
		foreach ( $parent_query as $sibling ) {
541
			if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) {
542
				continue;
543
			}
544
545
			if ( empty( $sibling['alias'] ) || empty( $sibling['operator'] ) ) {
546
				continue;
547
			}
548
549
			// The sibling must both have compatible operator to share its alias.
550
			if ( in_array( strtoupper( $sibling['operator'] ), $compatible_operators ) ) {
551
				$alias = $sibling['alias'];
552
				break;
553
			}
554
		}
555
556
		return $alias;
557
	}
558
559
	/**
560
	 * Validates a single query.
561
	 *
562
	 * @since 3.2.0
563
	 * @access private
564
	 *
565
	 * @param array $query The single query. Passed by reference.
566
	 */
567
	private function clean_query( &$query ) {
568
		if ( empty( $query['taxonomy'] ) ) {
569
			if ( 'term_taxonomy_id' !== $query['field'] ) {
570
				$query = new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
571
				return;
572
			}
573
574
			// so long as there are shared terms, include_children requires that a taxonomy is set
575
			$query['include_children'] = false;
576
		} elseif ( ! taxonomy_exists( $query['taxonomy'] ) ) {
577
			$query = new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
578
			return;
579
		}
580
581
		$query['terms'] = array_unique( (array) $query['terms'] );
582
583
		if ( is_taxonomy_hierarchical( $query['taxonomy'] ) && $query['include_children'] ) {
584
			$this->transform_query( $query, 'term_id' );
585
586
			if ( is_wp_error( $query ) )
587
				return;
588
589
			$children = array();
590
			foreach ( $query['terms'] as $term ) {
591
				$children = array_merge( $children, get_term_children( $term, $query['taxonomy'] ) );
592
				$children[] = $term;
593
			}
594
			$query['terms'] = $children;
595
		}
596
597
		$this->transform_query( $query, 'term_taxonomy_id' );
598
	}
599
600
	/**
601
	 * Transforms a single query, from one field to another.
602
	 *
603
	 * @since 3.2.0
604
	 *
605
	 * @param array  $query           The single query. Passed by reference.
606
	 * @param string $resulting_field The resulting field. Accepts 'slug', 'name', 'term_taxonomy_id',
607
	 *                                or 'term_id'. Default 'term_id'.
608
	 */
609
	public function transform_query( &$query, $resulting_field ) {
610
		if ( empty( $query['terms'] ) )
611
			return;
612
613
		if ( $query['field'] == $resulting_field )
614
			return;
615
616
		$resulting_field = sanitize_key( $resulting_field );
617
618
		switch ( $query['field'] ) {
619
			case 'slug':
620
			case 'name':
621
				foreach ( $query['terms'] as &$term ) {
622
					/*
623
					 * 0 is the $term_id parameter. We don't have a term ID yet, but it doesn't
624
					 * matter because `sanitize_term_field()` ignores the $term_id param when the
625
					 * context is 'db'.
626
					 */
627
					$term = "'" . esc_sql( sanitize_term_field( $query['field'], $term, 0, $query['taxonomy'], 'db' ) ) . "'";
628
				}
629
630
				$terms = implode( ",", $query['terms'] );
631
632
				$terms = $this->db->get_col( "
633
					SELECT {$this->db->term_taxonomy}.$resulting_field
634
					FROM {$this->db->term_taxonomy}
635
					INNER JOIN {$this->db->terms} USING (term_id)
636
					WHERE taxonomy = '{$query['taxonomy']}'
637
					AND {$this->db->terms}.{$query['field']} IN ($terms)
638
				" );
639
				break;
640 View Code Duplication
			case 'term_taxonomy_id':
641
				$terms = implode( ',', array_map( 'intval', $query['terms'] ) );
642
				$terms = $this->db->get_col( "
643
					SELECT $resulting_field
644
					FROM {$this->db->term_taxonomy}
645
					WHERE term_taxonomy_id IN ($terms)
646
				" );
647
				break;
648 View Code Duplication
			default:
649
				$terms = implode( ',', array_map( 'intval', $query['terms'] ) );
650
				$terms = $this->db->get_col( "
651
					SELECT $resulting_field
652
					FROM {$this->db->term_taxonomy}
653
					WHERE taxonomy = '{$query['taxonomy']}'
654
					AND term_id IN ($terms)
655
				" );
656
		}
657
658
		if ( 'AND' == $query['operator'] && count( $terms ) < count( $query['terms'] ) ) {
659
			$query = new WP_Error( 'inexistent_terms', __( 'Inexistent terms.' ) );
660
			return;
661
		}
662
663
		$query['terms'] = $terms;
664
		$query['field'] = $resulting_field;
665
	}
666
}
667