WP_Meta_Query

Core class used to implement meta queries for the Meta API.

Defined (1)

The class is defined in the following location(s).

/wp-includes/class-wp-meta-query.php  
  1. class WP_Meta_Query { 
  2. /** 
  3. * Array of metadata queries. 
  4. * See WP_Meta_Query::__construct() for information on meta query arguments. 
  5. * @since 3.2.0 
  6. * @access public 
  7. * @var array 
  8. */ 
  9. public $queries = array(); 
  10.  
  11. /** 
  12. * The relation between the queries. Can be one of 'AND' or 'OR'. 
  13. * @since 3.2.0 
  14. * @access public 
  15. * @var string 
  16. */ 
  17. public $relation; 
  18.  
  19. /** 
  20. * Database table to query for the metadata. 
  21. * @since 4.1.0 
  22. * @access public 
  23. * @var string 
  24. */ 
  25. public $meta_table; 
  26.  
  27. /** 
  28. * Column in meta_table that represents the ID of the object the metadata belongs to. 
  29. * @since 4.1.0 
  30. * @access public 
  31. * @var string 
  32. */ 
  33. public $meta_id_column; 
  34.  
  35. /** 
  36. * Database table that where the metadata's objects are stored (eg $wpdb->users). 
  37. * @since 4.1.0 
  38. * @access public 
  39. * @var string 
  40. */ 
  41. public $primary_table; 
  42.  
  43. /** 
  44. * Column in primary_table that represents the ID of the object. 
  45. * @since 4.1.0 
  46. * @access public 
  47. * @var string 
  48. */ 
  49. public $primary_id_column; 
  50.  
  51. /** 
  52. * A flat list of table aliases used in JOIN clauses. 
  53. * @since 4.1.0 
  54. * @access protected 
  55. * @var array 
  56. */ 
  57. protected $table_aliases = array(); 
  58.  
  59. /** 
  60. * A flat list of clauses, keyed by clause 'name'. 
  61. * @since 4.2.0 
  62. * @access protected 
  63. * @var array 
  64. */ 
  65. protected $clauses = array(); 
  66.  
  67. /** 
  68. * Whether the query contains any OR relations. 
  69. * @since 4.3.0 
  70. * @access protected 
  71. * @var bool 
  72. */ 
  73. protected $has_or_relation = false; 
  74.  
  75. /** 
  76. * Constructor. 
  77. * @since 3.2.0 
  78. * @since 4.2.0 Introduced support for naming query clauses by associative array keys. 
  79. * @access public 
  80. * @param array $meta_query { 
  81. * Array of meta query clauses. When first-order clauses or sub-clauses use strings as 
  82. * their array keys, they may be referenced in the 'orderby' parameter of the parent query. 
  83. * @type string $relation Optional. The MySQL keyword used to join 
  84. * the clauses of the query. Accepts 'AND', or 'OR'. Default 'AND'. 
  85. * @type array { 
  86. * Optional. An array of first-order clause parameters, or another fully-formed meta query. 
  87. * @type string $key Meta key to filter by. 
  88. * @type string $value Meta value to filter by. 
  89. * @type string $compare MySQL operator used for comparing the $value. Accepts '=',  
  90. * '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE',  
  91. * 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'REGEXP',  
  92. * 'NOT REGEXP', 'RLIKE', 'EXISTS' or 'NOT EXISTS'. 
  93. * Default is 'IN' when `$value` is an array, '=' otherwise. 
  94. * @type string $type MySQL data type that the meta_value column will be CAST to for 
  95. * comparisons. Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE',  
  96. * 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', or 'UNSIGNED'. 
  97. * Default is 'CHAR'. 
  98. * } 
  99. * } 
  100. */ 
  101. public function __construct( $meta_query = false ) { 
  102. if ( !$meta_query ) 
  103. return; 
  104.  
  105. if ( isset( $meta_query['relation'] ) && strtoupper( $meta_query['relation'] ) == 'OR' ) { 
  106. $this->relation = 'OR'; 
  107. } else { 
  108. $this->relation = 'AND'; 
  109.  
  110. $this->queries = $this->sanitize_query( $meta_query ); 
  111.  
  112. /** 
  113. * Ensure the 'meta_query' argument passed to the class constructor is well-formed. 
  114. * Eliminates empty items and ensures that a 'relation' is set. 
  115. * @since 4.1.0 
  116. * @access public 
  117. * @param array $queries Array of query clauses. 
  118. * @return array Sanitized array of query clauses. 
  119. */ 
  120. public function sanitize_query( $queries ) { 
  121. $clean_queries = array(); 
  122.  
  123. if ( ! is_array( $queries ) ) { 
  124. return $clean_queries; 
  125.  
  126. foreach ( $queries as $key => $query ) { 
  127. if ( 'relation' === $key ) { 
  128. $relation = $query; 
  129.  
  130. } elseif ( ! is_array( $query ) ) { 
  131. continue; 
  132.  
  133. // First-order clause. 
  134. } elseif ( $this->is_first_order_clause( $query ) ) { 
  135. if ( isset( $query['value'] ) && array() === $query['value'] ) { 
  136. unset( $query['value'] ); 
  137.  
  138. $clean_queries[ $key ] = $query; 
  139.  
  140. // Otherwise, it's a nested query, so we recurse. 
  141. } else { 
  142. $cleaned_query = $this->sanitize_query( $query ); 
  143.  
  144. if ( ! empty( $cleaned_query ) ) { 
  145. $clean_queries[ $key ] = $cleaned_query; 
  146.  
  147. if ( empty( $clean_queries ) ) { 
  148. return $clean_queries; 
  149.  
  150. // Sanitize the 'relation' key provided in the query. 
  151. if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) { 
  152. $clean_queries['relation'] = 'OR'; 
  153. $this->has_or_relation = true; 
  154.  
  155. /** 
  156. * If there is only a single clause, call the relation 'OR'. 
  157. * This value will not actually be used to join clauses, but it 
  158. * simplifies the logic around combining key-only queries. 
  159. */ 
  160. } elseif ( 1 === count( $clean_queries ) ) { 
  161. $clean_queries['relation'] = 'OR'; 
  162.  
  163. // Default to AND. 
  164. } else { 
  165. $clean_queries['relation'] = 'AND'; 
  166.  
  167. return $clean_queries; 
  168.  
  169. /** 
  170. * Determine whether a query clause is first-order. 
  171. * A first-order meta query clause is one that has either a 'key' or 
  172. * a 'value' array key. 
  173. * @since 4.1.0 
  174. * @access protected 
  175. * @param array $query Meta query arguments. 
  176. * @return bool Whether the query clause is a first-order clause. 
  177. */ 
  178. protected function is_first_order_clause( $query ) { 
  179. return isset( $query['key'] ) || isset( $query['value'] ); 
  180.  
  181. /** 
  182. * Constructs a meta query based on 'meta_*' query vars 
  183. * @since 3.2.0 
  184. * @access public 
  185. * @param array $qv The query variables 
  186. */ 
  187. public function parse_query_vars( $qv ) { 
  188. $meta_query = array(); 
  189.  
  190. /** 
  191. * For orderby=meta_value to work correctly, simple query needs to be 
  192. * first (so that its table join is against an unaliased meta table) and 
  193. * needs to be its own clause (so it doesn't interfere with the logic of 
  194. * the rest of the meta_query). 
  195. */ 
  196. $primary_meta_query = array(); 
  197. foreach ( array( 'key', 'compare', 'type' ) as $key ) { 
  198. if ( ! empty( $qv[ "meta_$key" ] ) ) { 
  199. $primary_meta_query[ $key ] = $qv[ "meta_$key" ]; 
  200.  
  201. // WP_Query sets 'meta_value' = '' by default. 
  202. if ( isset( $qv['meta_value'] ) && '' !== $qv['meta_value'] && ( ! is_array( $qv['meta_value'] ) || $qv['meta_value'] ) ) { 
  203. $primary_meta_query['value'] = $qv['meta_value']; 
  204.  
  205. $existing_meta_query = isset( $qv['meta_query'] ) && is_array( $qv['meta_query'] ) ? $qv['meta_query'] : array(); 
  206.  
  207. if ( ! empty( $primary_meta_query ) && ! empty( $existing_meta_query ) ) { 
  208. $meta_query = array( 
  209. 'relation' => 'AND',  
  210. $primary_meta_query,  
  211. $existing_meta_query,  
  212. ); 
  213. } elseif ( ! empty( $primary_meta_query ) ) { 
  214. $meta_query = array( 
  215. $primary_meta_query,  
  216. ); 
  217. } elseif ( ! empty( $existing_meta_query ) ) { 
  218. $meta_query = $existing_meta_query; 
  219.  
  220. $this->__construct( $meta_query ); 
  221.  
  222. /** 
  223. * Return the appropriate alias for the given meta type if applicable. 
  224. * @since 3.7.0 
  225. * @access public 
  226. * @param string $type MySQL type to cast meta_value. 
  227. * @return string MySQL type. 
  228. */ 
  229. public function get_cast_for_type( $type = '' ) { 
  230. if ( empty( $type ) ) 
  231. return 'CHAR'; 
  232.  
  233. $meta_type = strtoupper( $type ); 
  234.  
  235. if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:, \s?\d+)?\))?|DECIMAL(?:\(\d+(?:, \s?\d+)?\))?)$/', $meta_type ) ) 
  236. return 'CHAR'; 
  237.  
  238. if ( 'NUMERIC' == $meta_type ) 
  239. $meta_type = 'SIGNED'; 
  240.  
  241. return $meta_type; 
  242.  
  243. /** 
  244. * Generates SQL clauses to be appended to a main query. 
  245. * @since 3.2.0 
  246. * @access public 
  247. * @param string $type Type of meta, eg 'user', 'post'. 
  248. * @param string $primary_table Database table where the object being filtered is stored (eg wp_users). 
  249. * @param string $primary_id_column ID column for the filtered object in $primary_table. 
  250. * @param object $context Optional. The main query object. 
  251. * @return false|array { 
  252. * Array containing JOIN and WHERE SQL clauses to append to the main query. 
  253. * @type string $join SQL fragment to append to the main JOIN clause. 
  254. * @type string $where SQL fragment to append to the main WHERE clause. 
  255. * } 
  256. */ 
  257. public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) { 
  258. if ( ! $meta_table = _get_meta_table( $type ) ) { 
  259. return false; 
  260.  
  261. $this->table_aliases = array(); 
  262.  
  263. $this->meta_table = $meta_table; 
  264. $this->meta_id_column = sanitize_key( $type . '_id' ); 
  265.  
  266. $this->primary_table = $primary_table; 
  267. $this->primary_id_column = $primary_id_column; 
  268.  
  269. $sql = $this->get_sql_clauses(); 
  270.  
  271. /** 
  272. * If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should 
  273. * be LEFT. Otherwise posts with no metadata will be excluded from results. 
  274. */ 
  275. if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) { 
  276. $sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] ); 
  277.  
  278. /** 
  279. * Filters the meta query's generated SQL. 
  280. * @since 3.1.0 
  281. * @param array $clauses Array containing the query's JOIN and WHERE clauses. 
  282. * @param array $queries Array of meta queries. 
  283. * @param string $type Type of meta. 
  284. * @param string $primary_table Primary table. 
  285. * @param string $primary_id_column Primary column ID. 
  286. * @param object $context The main query object. 
  287. */ 
  288. return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) ); 
  289.  
  290. /** 
  291. * Generate SQL clauses to be appended to a main query. 
  292. * Called by the public WP_Meta_Query::get_sql(), this method is abstracted 
  293. * out to maintain parity with the other Query classes. 
  294. * @since 4.1.0 
  295. * @access protected 
  296. * @return array { 
  297. * Array containing JOIN and WHERE SQL clauses to append to the main query. 
  298. * @type string $join SQL fragment to append to the main JOIN clause. 
  299. * @type string $where SQL fragment to append to the main WHERE clause. 
  300. * } 
  301. */ 
  302. protected function get_sql_clauses() { 
  303. /** 
  304. * $queries are passed by reference to get_sql_for_query() for recursion. 
  305. * To keep $this->queries unaltered, pass a copy. 
  306. */ 
  307. $queries = $this->queries; 
  308. $sql = $this->get_sql_for_query( $queries ); 
  309.  
  310. if ( ! empty( $sql['where'] ) ) { 
  311. $sql['where'] = ' AND ' . $sql['where']; 
  312.  
  313. return $sql; 
  314.  
  315. /** 
  316. * Generate SQL clauses for a single query array. 
  317. * If nested subqueries are found, this method recurses the tree to 
  318. * produce the properly nested SQL. 
  319. * @since 4.1.0 
  320. * @access protected 
  321. * @param array $query Query to parse, passed by reference. 
  322. * @param int $depth Optional. Number of tree levels deep we currently are. 
  323. * Used to calculate indentation. Default 0. 
  324. * @return array { 
  325. * Array containing JOIN and WHERE SQL clauses to append to a single query array. 
  326. * @type string $join SQL fragment to append to the main JOIN clause. 
  327. * @type string $where SQL fragment to append to the main WHERE clause. 
  328. * } 
  329. */ 
  330. protected function get_sql_for_query( &$query, $depth = 0 ) { 
  331. $sql_chunks = array( 
  332. 'join' => array(),  
  333. 'where' => array(),  
  334. ); 
  335.  
  336. $sql = array( 
  337. 'join' => '',  
  338. 'where' => '',  
  339. ); 
  340.  
  341. $indent = ''; 
  342. for ( $i = 0; $i < $depth; $i++ ) { 
  343. $indent .= " "; 
  344.  
  345. foreach ( $query as $key => &$clause ) { 
  346. if ( 'relation' === $key ) { 
  347. $relation = $query['relation']; 
  348. } elseif ( is_array( $clause ) ) { 
  349.  
  350. // This is a first-order clause. 
  351. if ( $this->is_first_order_clause( $clause ) ) { 
  352. $clause_sql = $this->get_sql_for_clause( $clause, $query, $key ); 
  353.  
  354. $where_count = count( $clause_sql['where'] ); 
  355. if ( ! $where_count ) { 
  356. $sql_chunks['where'][] = ''; 
  357. } elseif ( 1 === $where_count ) { 
  358. $sql_chunks['where'][] = $clause_sql['where'][0]; 
  359. } else { 
  360. $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; 
  361.  
  362. $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); 
  363. // This is a subquery, so we recurse. 
  364. } else { 
  365. $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); 
  366.  
  367. $sql_chunks['where'][] = $clause_sql['where']; 
  368. $sql_chunks['join'][] = $clause_sql['join']; 
  369.  
  370. // Filter to remove empties. 
  371. $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); 
  372. $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); 
  373.  
  374. if ( empty( $relation ) ) { 
  375. $relation = 'AND'; 
  376.  
  377. // Filter duplicate JOIN clauses and combine into a single string. 
  378. if ( ! empty( $sql_chunks['join'] ) ) { 
  379. $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); 
  380.  
  381. // Generate a single WHERE clause with proper brackets and indentation. 
  382. if ( ! empty( $sql_chunks['where'] ) ) { 
  383. $sql['where'] = '( ' . "\n " . $indent . implode( ' ' . "\n " . $indent . $relation . ' ' . "\n " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')'; 
  384.  
  385. return $sql; 
  386.  
  387. /** 
  388. * Generate SQL JOIN and WHERE clauses for a first-order query clause. 
  389. * "First-order" means that it's an array with a 'key' or 'value'. 
  390. * @since 4.1.0 
  391. * @access public 
  392. * @global wpdb $wpdb WordPress database abstraction object. 
  393. * @param array $clause Query clause, passed by reference. 
  394. * @param array $parent_query Parent query array. 
  395. * @param string $clause_key Optional. The array key used to name the clause in the original `$meta_query` 
  396. * parameters. If not provided, a key will be generated automatically. 
  397. * @return array { 
  398. * Array containing JOIN and WHERE SQL clauses to append to a first-order query. 
  399. * @type string $join SQL fragment to append to the main JOIN clause. 
  400. * @type string $where SQL fragment to append to the main WHERE clause. 
  401. * } 
  402. */ 
  403. public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) { 
  404. global $wpdb; 
  405.  
  406. $sql_chunks = array( 
  407. 'where' => array(),  
  408. 'join' => array(),  
  409. ); 
  410.  
  411. if ( isset( $clause['compare'] ) ) { 
  412. $clause['compare'] = strtoupper( $clause['compare'] ); 
  413. } else { 
  414. $clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '='; 
  415.  
  416. if ( ! in_array( $clause['compare'], array( 
  417. '=', '!=', '>', '>=', '<', '<=',  
  418. 'LIKE', 'NOT LIKE',  
  419. 'IN', 'NOT IN',  
  420. 'BETWEEN', 'NOT BETWEEN',  
  421. 'EXISTS', 'NOT EXISTS',  
  422. 'REGEXP', 'NOT REGEXP', 'RLIKE' 
  423. ) ) ) { 
  424. $clause['compare'] = '='; 
  425.  
  426. $meta_compare = $clause['compare']; 
  427.  
  428. // First build the JOIN clause, if one is required. 
  429. $join = ''; 
  430.  
  431. // We prefer to avoid joins if possible. Look for an existing join compatible with this clause. 
  432. $alias = $this->find_compatible_table_alias( $clause, $parent_query ); 
  433. if ( false === $alias ) { 
  434. $i = count( $this->table_aliases ); 
  435. $alias = $i ? 'mt' . $i : $this->meta_table; 
  436.  
  437. // JOIN clauses for NOT EXISTS have their own syntax. 
  438. if ( 'NOT EXISTS' === $meta_compare ) { 
  439. $join .= " LEFT JOIN $this->meta_table"; 
  440. $join .= $i ? " AS $alias" : ''; 
  441. $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] ); 
  442.  
  443. // All other JOIN clauses. 
  444. } else { 
  445. $join .= " INNER JOIN $this->meta_table"; 
  446. $join .= $i ? " AS $alias" : ''; 
  447. $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )"; 
  448.  
  449. $this->table_aliases[] = $alias; 
  450. $sql_chunks['join'][] = $join; 
  451.  
  452. // Save the alias to this clause, for future siblings to find. 
  453. $clause['alias'] = $alias; 
  454.  
  455. // Determine the data type. 
  456. $_meta_type = isset( $clause['type'] ) ? $clause['type'] : ''; 
  457. $meta_type = $this->get_cast_for_type( $_meta_type ); 
  458. $clause['cast'] = $meta_type; 
  459.  
  460. // Fallback for clause keys is the table alias. Key must be a string. 
  461. if ( is_int( $clause_key ) || ! $clause_key ) { 
  462. $clause_key = $clause['alias']; 
  463.  
  464. // Ensure unique clause keys, so none are overwritten. 
  465. $iterator = 1; 
  466. $clause_key_base = $clause_key; 
  467. while ( isset( $this->clauses[ $clause_key ] ) ) { 
  468. $clause_key = $clause_key_base . '-' . $iterator; 
  469. $iterator++; 
  470.  
  471. // Store the clause in our flat array. 
  472. $this->clauses[ $clause_key ] =& $clause; 
  473.  
  474. // Next, build the WHERE clause. 
  475.  
  476. // meta_key. 
  477. if ( array_key_exists( 'key', $clause ) ) { 
  478. if ( 'NOT EXISTS' === $meta_compare ) { 
  479. $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL'; 
  480. } else { 
  481. $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) ); 
  482.  
  483. // meta_value. 
  484. if ( array_key_exists( 'value', $clause ) ) { 
  485. $meta_value = $clause['value']; 
  486.  
  487. if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) { 
  488. if ( ! is_array( $meta_value ) ) { 
  489. $meta_value = preg_split( '/[, \s]+/', $meta_value ); 
  490. } else { 
  491. $meta_value = trim( $meta_value ); 
  492.  
  493. switch ( $meta_compare ) { 
  494. case 'IN' : 
  495. case 'NOT IN' : 
  496. $meta_compare_string = '(' . substr( str_repeat( ', %s', count( $meta_value ) ), 1 ) . ')'; 
  497. $where = $wpdb->prepare( $meta_compare_string, $meta_value ); 
  498. break; 
  499.  
  500. case 'BETWEEN' : 
  501. case 'NOT BETWEEN' : 
  502. $meta_value = array_slice( $meta_value, 0, 2 ); 
  503. $where = $wpdb->prepare( '%s AND %s', $meta_value ); 
  504. break; 
  505.  
  506. case 'LIKE' : 
  507. case 'NOT LIKE' : 
  508. $meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%'; 
  509. $where = $wpdb->prepare( '%s', $meta_value ); 
  510. break; 
  511.  
  512. // EXISTS with a value is interpreted as '='. 
  513. case 'EXISTS' : 
  514. $meta_compare = '='; 
  515. $where = $wpdb->prepare( '%s', $meta_value ); 
  516. break; 
  517.  
  518. // 'value' is ignored for NOT EXISTS. 
  519. case 'NOT EXISTS' : 
  520. $where = ''; 
  521. break; 
  522.  
  523. default : 
  524. $where = $wpdb->prepare( '%s', $meta_value ); 
  525. break; 
  526.  
  527.  
  528. if ( $where ) { 
  529. if ( 'CHAR' === $meta_type ) { 
  530. $sql_chunks['where'][] = "$alias.meta_value {$meta_compare} {$where}"; 
  531. } else { 
  532. $sql_chunks['where'][] = "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$where}"; 
  533.  
  534. /** 
  535. * Multiple WHERE clauses (for meta_key and meta_value) should 
  536. * be joined in parentheses. 
  537. */ 
  538. if ( 1 < count( $sql_chunks['where'] ) ) { 
  539. $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' ); 
  540.  
  541. return $sql_chunks; 
  542.  
  543. /** 
  544. * Get a flattened list of sanitized meta clauses. 
  545. * This array should be used for clause lookup, as when the table alias and CAST type must be determined for 
  546. * a value of 'orderby' corresponding to a meta clause. 
  547. * @since 4.2.0 
  548. * @access public 
  549. * @return array Meta clauses. 
  550. */ 
  551. public function get_clauses() { 
  552. return $this->clauses; 
  553.  
  554. /** 
  555. * Identify an existing table alias that is compatible with the current 
  556. * query clause. 
  557. * We avoid unnecessary table joins by allowing each clause to look for 
  558. * an existing table alias that is compatible with the query that it 
  559. * needs to perform. 
  560. * An existing alias is compatible if (a) it is a sibling of `$clause` 
  561. * (ie, it's under the scope of the same relation), and (b) the combination 
  562. * of operator and relation between the clauses allows for a shared table join. 
  563. * In the case of WP_Meta_Query, this only applies to 'IN' clauses that are 
  564. * connected by the relation 'OR'. 
  565. * @since 4.1.0 
  566. * @access protected 
  567. * @param array $clause Query clause. 
  568. * @param array $parent_query Parent query of $clause. 
  569. * @return string|bool Table alias if found, otherwise false. 
  570. */ 
  571. protected function find_compatible_table_alias( $clause, $parent_query ) { 
  572. $alias = false; 
  573.  
  574. foreach ( $parent_query as $sibling ) { 
  575. // If the sibling has no alias yet, there's nothing to check. 
  576. if ( empty( $sibling['alias'] ) ) { 
  577. continue; 
  578.  
  579. // We're only interested in siblings that are first-order clauses. 
  580. if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) { 
  581. continue; 
  582.  
  583. $compatible_compares = array(); 
  584.  
  585. // Clauses connected by OR can share joins as long as they have "positive" operators. 
  586. if ( 'OR' === $parent_query['relation'] ) { 
  587. $compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' ); 
  588.  
  589. // Clauses joined by AND with "negative" operators share a join only if they also share a key. 
  590. } elseif ( isset( $sibling['key'] ) && isset( $clause['key'] ) && $sibling['key'] === $clause['key'] ) { 
  591. $compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' ); 
  592.  
  593. $clause_compare = strtoupper( $clause['compare'] ); 
  594. $sibling_compare = strtoupper( $sibling['compare'] ); 
  595. if ( in_array( $clause_compare, $compatible_compares ) && in_array( $sibling_compare, $compatible_compares ) ) { 
  596. $alias = $sibling['alias']; 
  597. break; 
  598.  
  599. /** 
  600. * Filters the table alias identified as compatible with the current clause. 
  601. * @since 4.1.0 
  602. * @param string|bool $alias Table alias, or false if none was found. 
  603. * @param array $clause First-order query clause. 
  604. * @param array $parent_query Parent of $clause. 
  605. * @param object $this WP_Meta_Query object. 
  606. */ 
  607. return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this ) ; 
  608.  
  609. /** 
  610. * Checks whether the current query has any OR relations. 
  611. * In some cases, the presence of an OR relation somewhere in the query will require 
  612. * the use of a `DISTINCT` or `GROUP BY` keyword in the `SELECT` clause. The current 
  613. * method can be used in these cases to determine whether such a clause is necessary. 
  614. * @since 4.3.0 
  615. * @return bool True if the query contains any `OR` relations, otherwise false. 
  616. */ 
  617. public function has_or_relation() { 
  618. return $this->has_or_relation;