[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/ -> class-wp-meta-query.php (source)

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


Generated: Sun Sep 15 01:00:03 2019 Cross-referenced by PHPXref 0.7.1