Make WordPress Core

Changeset 17699


Ignore:
Timestamp:
04/25/2011 05:27:35 PM (13 years ago)
Author:
ryan
Message:

Introduce WP_Meta_Query and relation support. Props scribu, greuben. fixes #17165 #17011

Location:
trunk/wp-includes
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/wp-includes/meta.php

    r17674 r17699  
    356356 * Given a meta query, generates SQL clauses to be appended to a main query
    357357 *
    358  * @since 3.1.0
    359  * @access private
    360  *
    361  * @param array $meta_query List of metadata queries. A single query is an associative array:
    362  * - 'key' string The meta key
    363  * - 'value' string|array The meta value
    364  * - 'compare' (optional) string How to compare the key to the value.
    365  *      Possible values: '=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'.
    366  *      Default: '='
    367  * - 'type' string (optional) The type of the value.
    368  *      Possible values: 'NUMERIC', 'BINARY', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', 'UNSIGNED'.
    369  *      Default: 'CHAR'
    370  *
     358 * @since 3.2.0
     359 *
     360 * @see WP_Meta_Query
     361 *
     362 * @param array (optional) $meta_query A meta query
    371363 * @param string $type Type of meta
    372364 * @param string $primary_table
     
    375367 * @return array( 'join' => $join_sql, 'where' => $where_sql )
    376368 */
    377 function _get_meta_sql( $meta_query, $type, $primary_table, $primary_id_column, $context = null ) {
    378     global $wpdb;
    379 
    380     if ( ! $meta_table = _get_meta_table( $type ) )
    381         return false;
    382 
    383     $meta_id_column = esc_sql( $type . '_id' );
    384 
    385     $join = '';
    386     $where = '';
    387     $i = 0;
    388     foreach ( $meta_query as $q ) {
    389         $meta_key = isset( $q['key'] ) ? trim( $q['key'] ) : '';
    390         $meta_compare = isset( $q['compare'] ) ? strtoupper( $q['compare'] ) : '=';
    391         $meta_type = isset( $q['type'] ) ? strtoupper( $q['type'] ) : 'CHAR';
    392 
    393         if ( ! in_array( $meta_compare, array( '=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) )
    394             $meta_compare = '=';
    395 
    396         if ( 'NUMERIC' == $meta_type )
    397             $meta_type = 'SIGNED';
    398         elseif ( ! in_array( $meta_type, array( 'BINARY', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', 'UNSIGNED' ) ) )
    399             $meta_type = 'CHAR';
    400 
    401         if ( empty( $meta_key ) && empty( $meta_value ) )
    402             continue;
    403 
    404         $alias = $i ? 'mt' . $i : $meta_table;
    405 
    406         $join .= "\nINNER JOIN $meta_table";
    407         $join .= $i ? " AS $alias" : '';
    408         $join .= " ON ($primary_table.$primary_id_column = $alias.$meta_id_column)";
    409 
    410         $i++;
    411 
    412         if ( !empty( $meta_key ) )
    413             $where .= $wpdb->prepare( " AND $alias.meta_key = %s", $meta_key );
    414 
    415         if ( !isset( $q['value'] ) )
    416             continue;
    417         $meta_value = $q['value'];
    418 
    419         if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
    420             if ( ! is_array( $meta_value ) )
    421                 $meta_value = preg_split( '/[,\s]+/', $meta_value );
    422 
    423             if ( empty( $meta_value ) )
     369function get_meta_sql( $meta_query = false, $type, $primary_table, $primary_id_column, $context = null ) {
     370    $meta_query_obj = new WP_Meta_Query( $meta_query );
     371    return $meta_query_obj->get_sql( $type, $primary_table, $primary_id_column, $context );
     372}
     373
     374/**
     375 * Container class for a multiple metadata query
     376 *
     377 * @since 3.2
     378 */
     379class WP_Meta_Query {
     380    /**
     381    * List of metadata queries. A single query is an associative array:
     382    * - 'key' string The meta key
     383    * - 'value' string|array The meta value
     384    * - 'compare' (optional) string How to compare the key to the value.
     385    *              Possible values: '=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'.
     386    *              Default: '='
     387    * - 'type' string (optional) The type of the value.
     388    *              Possible values: 'NUMERIC', 'BINARY', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', 'UNSIGNED'.
     389    *              Default: 'CHAR'
     390    *
     391    * @since 3.2
     392    * @access public
     393    * @var array
     394    */
     395    public $queries = array();
     396
     397    /**
     398     * The relation between the queries. Can be one of 'AND' or 'OR'.
     399     *
     400     * @since 3.2
     401     * @access public
     402     * @var string
     403     */
     404    public $relation;
     405
     406    /**
     407     * Constructor
     408     *
     409     * @param array (optional) $meta_query A meta query
     410     */
     411    function __construct( $meta_query = false ) {
     412        if ( !$meta_query )
     413            return;
     414
     415        if ( isset( $meta_query['relation'] ) && strtoupper( $meta_query['relation'] ) == 'OR' ) {
     416            $this->relation = 'OR';
     417        } else {
     418            $this->relation = 'AND';
     419        }
     420
     421        $this->queries = array();
     422
     423        foreach ( $meta_query as $key => $query ) {
     424            if ( ! is_array( $query ) )
    424425                continue;
    425         } else {
    426             $meta_value = trim( $meta_value );
    427         }
    428 
    429         if ( 'IN' == substr( $meta_compare, -2) ) {
    430             $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
    431         } elseif ( 'BETWEEN' == substr( $meta_compare, -7) ) {
    432             $meta_value = array_slice( $meta_value, 0, 2 );
    433             $meta_compare_string = '%s AND %s';
    434         } elseif ( 'LIKE' == substr( $meta_compare, -4 ) ) {
    435             $meta_value = '%' . like_escape( $meta_value ) . '%';
    436             $meta_compare_string = '%s';
    437         } else {
    438             $meta_compare_string = '%s';
    439         }
    440 
    441         $where .= $wpdb->prepare( " AND CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string}", $meta_value );
    442     }
    443 
    444     return apply_filters_ref_array( 'get_meta_sql', array( compact( 'join', 'where' ), $meta_query, $type, $primary_table, $primary_id_column, &$context ) );
    445 }
    446 
    447 /**
    448  * Populates the $meta_query property
    449  *
    450  * @access private
    451  * @since 3.1.0
    452  *
    453  * @param array $qv The query variables
    454  */
    455 function _parse_meta_query( &$qv ) {
    456     $meta_query = array();
    457 
    458     // Simple query needs to be first for orderby=meta_value to work correctly
    459     foreach ( array( 'key', 'compare', 'type' ) as $key ) {
    460         if ( !empty( $qv[ "meta_$key" ] ) )
    461             $meta_query[0][ $key ] = $qv[ "meta_$key" ];
    462     }
    463 
    464     // WP_Query sets 'meta_value' = '' by default
    465     if ( isset( $qv[ 'meta_value' ] ) && '' !== $qv[ 'meta_value' ] )
    466         $meta_query[0]['value'] = $qv[ 'meta_value' ];
    467 
    468     if ( !empty( $qv['meta_query'] ) && is_array( $qv['meta_query'] ) ) {
    469         $meta_query = array_merge( $meta_query, $qv['meta_query'] );
    470     }
    471 
    472     $qv['meta_query'] = $meta_query;
     426
     427            $this->queries[] = $query;
     428        }
     429    }
     430
     431    /**
     432     * Constructs a meta query based on 'meta_*' query vars
     433     *
     434     * @since 3.2
     435     * @access public
     436     *
     437     * @param array $qv The query variables
     438     */
     439    function parse_query_vars( $qv ) {
     440        $meta_query = array();
     441
     442        // Simple query needs to be first for orderby=meta_value to work correctly
     443        foreach ( array( 'key', 'compare', 'type' ) as $key ) {
     444            if ( !empty( $qv[ "meta_$key" ] ) )
     445                $meta_query[0][ $key ] = $qv[ "meta_$key" ];
     446        }
     447
     448        // WP_Query sets 'meta_value' = '' by default
     449        if ( isset( $qv[ 'meta_value' ] ) && '' !== $qv[ 'meta_value' ] )
     450            $meta_query[0]['value'] = $qv[ 'meta_value' ];
     451
     452        if ( !empty( $qv['meta_query'] ) && is_array( $qv['meta_query'] ) ) {
     453            $meta_query = array_merge( $meta_query, $qv['meta_query'] );
     454        }
     455
     456        $this->__construct( $meta_query );
     457    }
     458
     459    /**
     460     * Generates SQL clauses to be appended to a main query.
     461     *
     462     * @since 3.2
     463     * @access public
     464     *
     465     * @param string $type Type of meta
     466     * @param string $primary_table
     467     * @param string $primary_id_column
     468     * @param object $context (optional) The main query object
     469     * @return array( 'join' => $join_sql, 'where' => $where_sql )
     470     */
     471    function get_sql( $type, $primary_table, $primary_id_column, $context = null ) {
     472        global $wpdb;
     473
     474        if ( ! $meta_table = _get_meta_table( $type ) )
     475            return false;
     476
     477        $meta_id_column = esc_sql( $type . '_id' );
     478
     479        $join = '';
     480        $where = array();
     481        $i = 0;
     482        foreach ( $this->queries as $k => $q ) {
     483            $meta_key = isset( $q['key'] ) ? trim( $q['key'] ) : '';
     484            $meta_compare = isset( $q['compare'] ) ? strtoupper( $q['compare'] ) : '=';
     485            $meta_type = isset( $q['type'] ) ? strtoupper( $q['type'] ) : 'CHAR';
     486
     487            if ( ! in_array( $meta_compare, array( '=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) )
     488                $meta_compare = '=';
     489
     490            if ( 'NUMERIC' == $meta_type )
     491                $meta_type = 'SIGNED';
     492            elseif ( ! in_array( $meta_type, array( 'BINARY', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', 'UNSIGNED' ) ) )
     493                $meta_type = 'CHAR';
     494
     495            if ( empty( $meta_key ) && empty( $meta_value ) )
     496                continue;
     497
     498            $alias = $i ? 'mt' . $i : $meta_table;
     499
     500            $join .= "\nINNER JOIN $meta_table";
     501            $join .= $i ? " AS $alias" : '';
     502            $join .= " ON ($primary_table.$primary_id_column = $alias.$meta_id_column)";
     503
     504            $i++;
     505
     506            if ( !empty( $meta_key ) )
     507                $where[$k] = $wpdb->prepare( "$alias.meta_key = %s", $meta_key );
     508
     509            if ( !isset( $q['value'] ) )
     510                continue;
     511            $meta_value = $q['value'];
     512
     513            if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
     514                if ( ! is_array( $meta_value ) )
     515                    $meta_value = preg_split( '/[,\s]+/', $meta_value );
     516
     517                if ( empty( $meta_value ) )
     518                    continue;
     519            } else {
     520                $meta_value = trim( $meta_value );
     521            }
     522
     523            if ( 'IN' == substr( $meta_compare, -2) ) {
     524                $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
     525            } elseif ( 'BETWEEN' == substr( $meta_compare, -7) ) {
     526                $meta_value = array_slice( $meta_value, 0, 2 );
     527                $meta_compare_string = '%s AND %s';
     528            } elseif ( 'LIKE' == substr( $meta_compare, -4 ) ) {
     529                $meta_value = '%' . like_escape( $meta_value ) . '%';
     530                $meta_compare_string = '%s';
     531            } else {
     532                $meta_compare_string = '%s';
     533            }
     534
     535            $where[$k] = ' (' . $where[$k] . $wpdb->prepare( " AND CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string})", $meta_value );
     536        }
     537        $where = ' AND (' . implode( " {$this->relation} ", $where ) . ' )';
     538
     539        return apply_filters_ref_array( 'get_meta_sql', array( compact( 'join', 'where' ), $this->queries, $type, $primary_table, $primary_id_column, $context ) );
     540    }
     541
    473542}
    474543
  • trunk/wp-includes/query.php

    r17689 r17699  
    848848     */
    849849    var $tax_query;
     850
     851    /**
     852     * Metadata query container
     853     *
     854     * @since 3.2
     855     * @access public
     856     * @var object WP_Meta_Query
     857     */
     858    var $meta_query = false;
    850859
    851860    /**
     
    15261535            unset( $tax_query );
    15271536
    1528             _parse_meta_query( $qv );
    1529 
    15301537            if ( empty($qv['author']) || ($qv['author'] == '0') ) {
    15311538                $this->is_author = false;
     
    19011908        $q = $this->fill_query_vars($q);
    19021909
     1910        // Parse meta query
     1911        $this->meta_query = new WP_Meta_Query();
     1912        $this->meta_query->parse_query_vars( $q );
     1913
    19031914        // Set a flag if a pre_get_posts hook changed the query vars.
    19041915        $hash = md5( serialize( $this->query_vars ) );
     
    22362247        }
    22372248
    2238         if ( !empty( $this->tax_query->queries ) || !empty( $q['meta_key'] ) ) {
     2249        if ( !empty( $this->tax_query->queries ) || !empty( $this->meta_query->queries ) ) {
    22392250            $groupby = "{$wpdb->posts}.ID";
    22402251        }
     
    24692480        }
    24702481
    2471         // Parse the meta query again if query vars have changed.
    2472         if ( $this->query_vars_changed ) {
    2473             $meta_query_hash = md5( serialize( $q['meta_query'] ) );
    2474             $_meta_query = $q['meta_query'];
    2475             unset( $q['meta_query'] );
    2476             _parse_meta_query( $q );
    2477             if ( md5( serialize( $q['meta_query'] ) ) != $meta_query_hash && is_array( $_meta_query ) )
    2478                 $q['meta_query'] = array_merge( $_meta_query, $q['meta_query'] );
    2479         }
    2480 
    2481         if ( !empty( $q['meta_query'] ) ) {
    2482             $clauses = call_user_func_array( '_get_meta_sql', array( $q['meta_query'], 'post', $wpdb->posts, 'ID', &$this) );
     2482        if ( !empty( $this->meta_query->queries ) ) {
     2483            $clauses = $this->meta_query->get_sql( 'post', $wpdb->posts, 'ID', $this );
    24832484            $join .= $clauses['join'];
    24842485            $where .= $clauses['where'];
  • trunk/wp-includes/user.php

    r17674 r17699  
    502502        }
    503503
    504         _parse_meta_query( $qv );
    505 
    506504        $role = trim( $qv['role'] );
    507505
     
    518516        }
    519517
    520         if ( !empty( $qv['meta_query'] ) ) {
    521             $clauses = call_user_func_array( '_get_meta_sql', array( $qv['meta_query'], 'user', $wpdb->users, 'ID', &$this ) );
     518        $meta_query = new WP_Meta_Query();
     519        $meta_query->parse_query_vars( $qv );
     520
     521        if ( !empty( $meta_query->queries ) ) {
     522            $clauses = $meta_query->get_sql( 'user', $wpdb->users, 'ID', $this );
    522523            $this->query_from .= $clauses['join'];
    523524            $this->query_where .= $clauses['where'];
Note: See TracChangeset for help on using the changeset viewer.