Skip to:
Content

BuddyPress.org

Ticket #5839: 5839.4.patch

File 5839.4.patch, 38.5 KB (added by boonebgorges, 7 years ago)
  • src/bp-core/bp-core-adminbar.php

    diff --git src/bp-core/bp-core-adminbar.php src/bp-core/bp-core-adminbar.php
    index a875c49..eade1b6 100644
    function bp_core_enqueue_admin_bar_css() { 
    127127
    128128        // Enqueue the additional adminbar css
    129129        wp_enqueue_style( 'bp-admin-bar' );
    130 }
    131  No newline at end of file
     130}
  • src/bp-core/bp-core-classes.php

    diff --git src/bp-core/bp-core-classes.php src/bp-core/bp-core-classes.php
    index 1244cdf..c99c031 100644
    if ( !defined( 'ABSPATH' ) ) exit; 
    4848 *     @type string|bool $meta_value When used with $meta_key, limits results
    4949 *           to users whose usermeta value associated with $meta_key matches
    5050 *           $meta_value. Default: false.
     51 *     @type array $xprofile_query Filter results by xprofile data. Requires
     52 *           the xprofile component. See {@link BP_XProfile_Query} for details.
    5153 *     @type bool $populate_extras True if you want to fetch extra metadata
    5254 *           about returned users, such as total group and friend counts.
    5355 *     @type string $count_total Determines how BP_User_Query will do a count
    class BP_User_Query { 
    115117        public $uid_clauses = array();
    116118
    117119        /**
     120         * SQL table where the user ID is being fetched from.
     121         *
     122         * @since BuddyPress (2.1.0)
     123         * @access public
     124         * @var string
     125         */
     126        public $uid_table = '';
     127
     128        /**
    118129         * SQL database column name to order by.
    119130         *
    120131         * @since BuddyPress (1.7.0)
    class BP_User_Query { 
    162173                                'user_ids'        => false,
    163174                                'meta_key'        => false,
    164175                                'meta_value'      => false,
     176                                'xprofile_query'  => false,
    165177                                'populate_extras' => true,
    166178                                'count_total'     => 'count_query'
    167179                        ) );
    class BP_User_Query { 
    249261                        // number of minutes used as an interval
    250262                        case 'online' :
    251263                                $this->uid_name = 'user_id';
    252                                 $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$bp->members->table_name_last_activity} u";
     264                                $this->uid_table = $bp->members->table_name_last_activity;
     265                                $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
    253266                                $sql['where'][] = $wpdb->prepare( "u.component = %s AND u.type = 'last_activity'", buddypress()->members->id );
    254267                                $sql['where'][] = $wpdb->prepare( "u.date_recorded >= DATE_SUB( UTC_TIMESTAMP(), INTERVAL %d MINUTE )", apply_filters( 'bp_user_query_online_interval', 15 ) );
    255268                                $sql['orderby'] = "ORDER BY u.date_recorded";
    class BP_User_Query { 
    263276                        case 'newest' :
    264277                        case 'random' :
    265278                                $this->uid_name = 'user_id';
    266                                 $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$bp->members->table_name_last_activity} u";
     279                                $this->uid_table = $bp->members->table_name_last_activity;
     280                                $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
    267281                                $sql['where'][] = $wpdb->prepare( "u.component = %s AND u.type = 'last_activity'", buddypress()->members->id );
    268282
    269283                                if ( 'newest' == $type ) {
    class BP_User_Query { 
    281295                        // 'popular' sorts by the 'total_friend_count' usermeta
    282296                        case 'popular' :
    283297                                $this->uid_name = 'user_id';
    284                                 $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$wpdb->usermeta} u";
     298                                $this->uid_table = $wpdb->usermeta;
     299                                $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
    285300                                $sql['where'][] = $wpdb->prepare( "u.meta_key = %s", bp_get_user_meta_key( 'total_friend_count' ) );
    286301                                $sql['orderby'] = "ORDER BY CONVERT(u.meta_value, SIGNED)";
    287302                                $sql['order']   = "DESC";
    class BP_User_Query { 
    298313                                // @todo remove need for bp_is_active() check
    299314                                if ( ! bp_disable_profile_sync() || ! bp_is_active( 'xprofile' ) ) {
    300315                                        $this->uid_name = 'ID';
    301                                         $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$wpdb->users} u";
     316                                        $this->uid_table = $wpdb->users;
     317                                        $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
    302318                                        $sql['orderby'] = "ORDER BY u.display_name";
    303319                                        $sql['order']   = "ASC";
    304320
    class BP_User_Query { 
    306322                                // the xprofile table
    307323                                } else {
    308324                                        $this->uid_name = 'user_id';
    309                                         $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$bp->profile->table_name_data} u";
     325                                        $this->uid_table = $bp->profile->table_name_data;
     326                                        $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
    310327                                        $sql['where'][] = $wpdb->prepare( "u.field_id = %d", bp_xprofile_fullname_field_id() );
    311328                                        $sql['orderby'] = "ORDER BY u.value";
    312329                                        $sql['order']   = "ASC";
    class BP_User_Query { 
    322339                        // Any other 'type' falls through
    323340                        default :
    324341                                $this->uid_name = 'ID';
    325                                 $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$wpdb->users} u";
     342                                $this->uid_table = $wpdb->users;
     343                                $sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
    326344
    327345                                // In this case, we assume that a plugin is
    328346                                // handling order, so we leave those clauses
  • src/bp-xprofile/bp-xprofile-classes.php

    diff --git src/bp-xprofile/bp-xprofile-classes.php src/bp-xprofile/bp-xprofile-classes.php
    index cbb1b8b4..ed08b9b 100644
    abstract class BP_XProfile_Field_Type { 
    32623262                return $html;
    32633263        }
    32643264}
     3265
     3266/**
     3267 * Class for generating SQL clauses to filter a user query by xprofile data.
     3268 *
     3269 * @since BuddyPress (2.2.0)
     3270 */
     3271class BP_XProfile_Query {
     3272        /**
     3273         * Array of xprofile queries.
     3274         *
     3275         * See {@see WP_XProfile_Query::__construct()} for information on parameters.
     3276         *
     3277         * @since BuddyPress (2.2.0)
     3278         * @access public
     3279         * @var array
     3280         */
     3281        public $queries = array();
     3282
     3283        /**
     3284         * Database table that where the metadata's objects are stored (eg $wpdb->users).
     3285         *
     3286         * @since BuddyPress (2.2.0)
     3287         * @access public
     3288         * @var string
     3289         */
     3290        public $primary_table;
     3291
     3292        /**
     3293         * Column in primary_table that represents the ID of the object.
     3294         *
     3295         * @since BuddyPress (2.2.0)
     3296         * @access public
     3297         * @var string
     3298         */
     3299        public $primary_id_column;
     3300
     3301        /**
     3302         * A flat list of table aliases used in JOIN clauses.
     3303         *
     3304         * @since BuddyPress (2.2.0)
     3305         * @access protected
     3306         * @var array
     3307         */
     3308        protected $table_aliases = array();
     3309        public function __construct( $xprofile_query ) {
     3310                if ( empty( $xprofile_query ) ) {
     3311                        return;
     3312                }
     3313
     3314                $this->queries = $this->sanitize_query( $xprofile_query );
     3315        }
     3316
     3317        /**
     3318         * Ensure the `xprofile_query` argument passed to the class constructor is well-formed.
     3319         *
     3320         * Eliminates empty items and ensures that a 'relation' is set.
     3321         *
     3322         * @since BuddyPress (2.2.0)
     3323         * @access public
     3324         *
     3325         * @param array $queries Array of query clauses.
     3326         * @return array Sanitized array of query clauses.
     3327         */
     3328        public function sanitize_query( $queries ) {
     3329                $clean_queries = array();
     3330
     3331                if ( ! is_array( $queries ) ) {
     3332                        return $clean_queries;
     3333                }
     3334
     3335                foreach ( $queries as $key => $query ) {
     3336                        if ( 'relation' === $key ) {
     3337                                $relation = $query;
     3338
     3339                        } else if ( ! is_array( $query ) ) {
     3340                                continue;
     3341
     3342                        // First-order clause.
     3343                        } else if ( $this->is_first_order_clause( $query ) ) {
     3344                                if ( isset( $query['value'] ) && array() === $query['value'] ) {
     3345                                        unset( $query['value'] );
     3346                                }
     3347
     3348                                $clean_queries[] = $query;
     3349
     3350                        // Otherwise, it's a nested query, so we recurse.
     3351                        } else {
     3352                                $cleaned_query = $this->sanitize_query( $query );
     3353
     3354                                if ( ! empty( $cleaned_query ) ) {
     3355                                        $clean_queries[] = $cleaned_query;
     3356                                }
     3357                        }
     3358                }
     3359
     3360                if ( empty( $clean_queries ) ) {
     3361                        return $clean_queries;
     3362                }
     3363
     3364                // Sanitize the 'relation' key provided in the query.
     3365                if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
     3366                        $clean_queries['relation'] = 'OR';
     3367
     3368                /*
     3369                 * If there is only a single clause, call the relation 'OR'.
     3370                 * This value will not actually be used to join clauses, but it
     3371                 * simplifies the logic around combining key-only queries.
     3372                 */
     3373                } else if ( 1 === count( $clean_queries ) ) {
     3374                        $clean_queries['relation'] = 'OR';
     3375
     3376                // Default to AND.
     3377                } else {
     3378                        $clean_queries['relation'] = 'AND';
     3379                }
     3380
     3381                return $clean_queries;
     3382        }
     3383
     3384        /**
     3385         * Determine whether a query clause is first-order.
     3386         *
     3387         * A first-order meta query clause is one that has either a 'key' or
     3388         * a 'value' array key.
     3389         *
     3390         * @since BuddyPress (2.2.0)
     3391         * @access protected
     3392         *
     3393         * @param array $query Meta query arguments.
     3394         * @return bool Whether the query clause is a first-order clause.
     3395         */
     3396        protected function is_first_order_clause( $query ) {
     3397                return isset( $query['field'] ) || isset( $query['value'] );
     3398        }
     3399
     3400        /**
     3401         * Return the appropriate alias for the given meta type if applicable.
     3402         *
     3403         * @since BuddyPress (2.2.0)
     3404         * @access public
     3405         *
     3406         * @param string $type MySQL type to cast meta_value.
     3407         * @return string MySQL type.
     3408         */
     3409        public function get_cast_for_type( $type = '' ) {
     3410                if ( empty( $type ) )
     3411                        return 'CHAR';
     3412
     3413                $meta_type = strtoupper( $type );
     3414
     3415                if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) )
     3416                        return 'CHAR';
     3417
     3418                if ( 'NUMERIC' == $meta_type )
     3419                        $meta_type = 'SIGNED';
     3420
     3421                return $meta_type;
     3422        }
     3423
     3424        /**
     3425         * Generate SQL clauses to be appended to a main query.
     3426         *
     3427         * Called by the public {@see WP_Meta_Query::get_sql()}, this method
     3428         * is abstracted out to maintain parity with the other Query classes.
     3429         *
     3430         * @since BuddyPress (2.2.0)
     3431         * @access protected
     3432         *
     3433         * @return array {
     3434         *     Array containing JOIN and WHERE SQL clauses to append to the main query.
     3435         *
     3436         *     @type string $join  SQL fragment to append to the main JOIN clause.
     3437         *     @type string $where SQL fragment to append to the main WHERE clause.
     3438         * }
     3439         */
     3440        protected function get_sql_clauses() {
     3441                /*
     3442                 * $queries are passed by reference to get_sql_for_query() for recursion.
     3443                 * To keep $this->queries unaltered, pass a copy.
     3444                 */
     3445                $queries = $this->queries;
     3446                $sql = $this->get_sql_for_query( $queries );
     3447
     3448                if ( ! empty( $sql['where'] ) ) {
     3449                        $sql['where'] = ' AND ' . $sql['where'];
     3450                }
     3451
     3452                return $sql;
     3453        }
     3454
     3455        /**
     3456         * Generate SQL clauses for a single query array.
     3457         *
     3458         * If nested subqueries are found, this method recurses the tree to
     3459         * produce the properly nested SQL.
     3460         *
     3461         * @since BuddyPress (2.2.0)
     3462         * @access protected
     3463         *
     3464         * @param array $query Query to parse.
     3465         * @param int   $depth Optional. Number of tree levels deep we currently are.
     3466         *               Used to calculate indentation.
     3467         * @return array {
     3468         *     Array containing JOIN and WHERE SQL clauses to append to a single query array.
     3469         *
     3470         *     @type string $join  SQL fragment to append to the main JOIN clause.
     3471         *     @type string $where SQL fragment to append to the main WHERE clause.
     3472         * }
     3473         */
     3474        protected function get_sql_for_query( &$query, $depth = 0 ) {
     3475                $sql_chunks = array(
     3476                        'join'  => array(),
     3477                        'where' => array(),
     3478                );
     3479
     3480                $sql = array(
     3481                        'join'  => '',
     3482                        'where' => '',
     3483                );
     3484
     3485                $indent = '';
     3486                for ( $i = 0; $i < $depth; $i++ ) {
     3487                        $indent .= "  ";
     3488                }
     3489
     3490                foreach ( $query as $key => &$clause ) {
     3491                        if ( 'relation' === $key ) {
     3492                                $relation = $query['relation'];
     3493                        } else if ( is_array( $clause ) ) {
     3494
     3495                                // This is a first-order clause.
     3496                                if ( $this->is_first_order_clause( $clause ) ) {
     3497                                        $clause_sql = $this->get_sql_for_clause( $clause, $query );
     3498
     3499                                        $where_count = count( $clause_sql['where'] );
     3500                                        if ( ! $where_count ) {
     3501                                                $sql_chunks['where'][] = '';
     3502                                        } else if ( 1 === $where_count ) {
     3503                                                $sql_chunks['where'][] = $clause_sql['where'][0];
     3504                                        } else {
     3505                                                $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
     3506                                        }
     3507
     3508                                        $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
     3509                                // This is a subquery, so we recurse.
     3510                                } else {
     3511                                        $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
     3512
     3513                                        $sql_chunks['where'][] = $clause_sql['where'];
     3514                                        $sql_chunks['join'][]  = $clause_sql['join'];
     3515                                }
     3516                        }
     3517                }
     3518
     3519                // Filter to remove empties.
     3520                $sql_chunks['join']  = array_filter( $sql_chunks['join'] );
     3521                $sql_chunks['where'] = array_filter( $sql_chunks['where'] );
     3522
     3523                if ( empty( $relation ) ) {
     3524                        $relation = 'AND';
     3525                }
     3526
     3527                // Filter duplicate JOIN clauses and combine into a single string.
     3528                if ( ! empty( $sql_chunks['join'] ) ) {
     3529                        $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
     3530                }
     3531
     3532                // Generate a single WHERE clause with proper brackets and indentation.
     3533                if ( ! empty( $sql_chunks['where'] ) ) {
     3534                        $sql['where'] = '( ' . "\n  " . $indent . implode( ' ' . "\n  " . $indent . $relation . ' ' . "\n  " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')';
     3535                }
     3536
     3537                return $sql;
     3538        }
     3539
     3540        /**
     3541         * Generates SQL clauses to be appended to a main query.
     3542         *
     3543         * @since BuddyPress (2.2.0)
     3544         * @access public
     3545         *
     3546         * @param string $primary_table     Database table where the object being filtered is stored (eg wp_users).
     3547         * @param string $primary_id_column ID column for the filtered object in $primary_table.
     3548         * @return array {
     3549         *     Array containing JOIN and WHERE SQL clauses to append to the main query.
     3550         *
     3551         *     @type string $join  SQL fragment to append to the main JOIN clause.
     3552         *     @type string $where SQL fragment to append to the main WHERE clause.
     3553         * }
     3554         */
     3555        public function get_sql( $primary_table, $primary_id_column, $context = null ) {
     3556                global $wpdb;
     3557
     3558                $this->primary_table     = $primary_table;
     3559                $this->primary_id_column = $primary_id_column;
     3560
     3561                $sql = $this->get_sql_clauses();
     3562
     3563                /*
     3564                 * If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should
     3565                 * be LEFT. Otherwise posts with no metadata will be excluded from results.
     3566                 */
     3567                if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) {
     3568                        $sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] );
     3569                }
     3570
     3571                /**
     3572                 * Filter the meta query's generated SQL.
     3573                 *
     3574                 * @since 2.1.0
     3575                 *
     3576                 * @param array $args {
     3577                 *     An array of meta query SQL arguments.
     3578                 *
     3579                 *     @type array  $clauses           Array containing the query's JOIN and WHERE clauses.
     3580                 *     @type array  $queries           Array of meta queries.
     3581                 *     @type string $primary_table     Primary table.
     3582                 *     @type string $primary_id_column Primary column ID.
     3583                 * }
     3584                 */
     3585                return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $primary_table, $primary_id_column ) );
     3586        }
     3587
     3588        /**
     3589         * Generate SQL JOIN and WHERE clauses for a first-order query clause.
     3590         *
     3591         * "First-order" means that it's an array with a 'key' or 'value'.
     3592         *
     3593         * @since BuddyPress (2.2.0)
     3594         * @access public
     3595         *
     3596         * @param array $clause       Query clause.
     3597         * @param array $parent_query Parent query array.
     3598         * @return array {
     3599         *     Array containing JOIN and WHERE SQL clauses to append to a first-order query.
     3600         *
     3601         *     @type string $join  SQL fragment to append to the main JOIN clause.
     3602         *     @type string $where SQL fragment to append to the main WHERE clause.
     3603         * }
     3604         */
     3605        public function get_sql_for_clause( &$clause, $parent_query ) {
     3606                global $wpdb;
     3607
     3608                $sql_chunks = array(
     3609                        'where' => array(),
     3610                        'join' => array(),
     3611                );
     3612
     3613                if ( isset( $clause['compare'] ) ) {
     3614                        $clause['compare'] = strtoupper( $clause['compare'] );
     3615                } else {
     3616                        $clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
     3617                }
     3618
     3619                if ( ! in_array( $clause['compare'], array(
     3620                        '=', '!=', '>', '>=', '<', '<=',
     3621                        'LIKE', 'NOT LIKE',
     3622                        'IN', 'NOT IN',
     3623                        'BETWEEN', 'NOT BETWEEN',
     3624                        'EXISTS', 'NOT EXISTS',
     3625                        'REGEXP', 'NOT REGEXP', 'RLIKE'
     3626                ) ) ) {
     3627                        $clause['compare'] = '=';
     3628                }
     3629
     3630                $field_compare = $clause['compare'];
     3631
     3632                // First build the JOIN clause, if one is required.
     3633                $join = '';
     3634
     3635                $data_table = buddypress()->profile->table_name_data;
     3636
     3637                // We prefer to avoid joins if possible. Look for an existing join compatible with this clause.
     3638                $alias = $this->find_compatible_table_alias( $clause, $parent_query );
     3639                if ( false === $alias ) {
     3640                        $i = count( $this->table_aliases );
     3641                        $alias = $i ? 'xpq' . $i : $data_table;
     3642
     3643                        // JOIN clauses for NOT EXISTS have their own syntax.
     3644                        if ( 'NOT EXISTS' === $field_compare ) {
     3645                                $join .= " LEFT JOIN $data_table";
     3646                                $join .= $i ? " AS $alias" : '';
     3647                                $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.user_id AND $alias.field_id = %d )", $clause['field'] );
     3648
     3649                        // All other JOIN clauses.
     3650                        } else {
     3651                                $join .= " INNER JOIN $data_table";
     3652                                $join .= $i ? " AS $alias" : '';
     3653                                $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.user_id )";
     3654                        }
     3655
     3656                        $this->table_aliases[] = $alias;
     3657                        $sql_chunks['join'][] = $join;
     3658                }
     3659
     3660                // Save the alias to this clause, for future siblings to find.
     3661                $clause['alias'] = $alias;
     3662
     3663                // Next, build the WHERE clause.
     3664                $where = '';
     3665
     3666                // field_id.
     3667                if ( array_key_exists( 'field', $clause ) ) {
     3668                        // Convert field name to ID if necessary.
     3669                        if ( ! is_numeric( $clause['field'] ) ) {
     3670                                $clause['field'] = xprofile_get_field_id_from_name( $clause['field'] );
     3671                        }
     3672
     3673                        // NOT EXISTS has its own syntax.
     3674                        if ( 'NOT EXISTS' === $field_compare ) {
     3675                                $sql_chunks['where'][] = $alias . '.user_id IS NULL';
     3676                        } else {
     3677                                $sql_chunks['where'][] = $wpdb->prepare( "$alias.field_id = %d", $clause['field'] );
     3678                        }
     3679                }
     3680
     3681                // value.
     3682                if ( array_key_exists( 'value', $clause ) ) {
     3683                        $field_value = $clause['value'];
     3684                        $field_type = $this->get_cast_for_type( isset( $clause['type'] ) ? $clause['type'] : '' );
     3685
     3686                        if ( in_array( $field_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
     3687                                if ( ! is_array( $field_value ) ) {
     3688                                        $field_value = preg_split( '/[,\s]+/', $field_value );
     3689                                }
     3690                        } else {
     3691                                $field_value = trim( $field_value );
     3692                        }
     3693
     3694                        switch ( $field_compare ) {
     3695                                case 'IN' :
     3696                                case 'NOT IN' :
     3697                                        $field_compare_string = '(' . substr( str_repeat( ',%s', count( $field_value ) ), 1 ) . ')';
     3698                                        $where = $wpdb->prepare( $field_compare_string, $field_value );
     3699                                        break;
     3700
     3701                                case 'BETWEEN' :
     3702                                case 'NOT BETWEEN' :
     3703                                        $field_value = array_slice( $field_value, 0, 2 );
     3704                                        $where = $wpdb->prepare( '%s AND %s', $field_value );
     3705                                        break;
     3706
     3707                                case 'LIKE' :
     3708                                case 'NOT LIKE' :
     3709                                        $field_value = '%' . $wpdb->esc_like( $field_value ) . '%';
     3710                                        $where = $wpdb->prepare( '%s', $field_value );
     3711                                        break;
     3712
     3713                                default :
     3714                                        $where = $wpdb->prepare( '%s', $field_value );
     3715                                        break;
     3716
     3717                        }
     3718
     3719                        if ( $where ) {
     3720                                $sql_chunks['where'][] = "CAST($alias.value AS {$field_type}) {$field_compare} {$where}";
     3721                        }
     3722                }
     3723
     3724                /*
     3725                 * Multiple WHERE clauses (for meta_key and meta_value) should
     3726                 * be joined in parentheses.
     3727                 */
     3728                if ( 1 < count( $sql_chunks['where'] ) ) {
     3729                        $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' );
     3730                }
     3731
     3732                return $sql_chunks;
     3733        }
     3734
     3735        /**
     3736         * Identify an existing table alias that is compatible with the current query clause.
     3737         *
     3738         * We avoid unnecessary table joins by allowing each clause to look for
     3739         * an existing table alias that is compatible with the query that it
     3740         * needs to perform. An existing alias is compatible if (a) it is a
     3741         * sibling of $clause (ie, it's under the scope of the same relation),
     3742         * and (b) the combination of operator and relation between the clauses
     3743         * allows for a shared table join. In the case of WP_Meta_Query, this
     3744         * only applies to IN clauses that are connected by the relation OR.
     3745         *
     3746         * @since BuddyPress (2.2.0)
     3747         * @access protected
     3748         *
     3749         * @param  array       $clause       Query clause.
     3750         * @param  array       $parent_query Parent query of $clause.
     3751         * @return string|bool Table alias if found, otherwise false.
     3752         */
     3753        protected function find_compatible_table_alias( $clause, $parent_query ) {
     3754                $alias = false;
     3755
     3756                foreach ( $parent_query as $sibling ) {
     3757                        // If the sibling has no alias yet, there's nothing to check.
     3758                        if ( empty( $sibling['alias'] ) ) {
     3759                                continue;
     3760                        }
     3761
     3762                        // We're only interested in siblings that are first-order clauses.
     3763                        if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) {
     3764                                continue;
     3765                        }
     3766
     3767                        $compatible_compares = array();
     3768
     3769                        // Clauses connected by OR can share joins as long as they have "positive" operators.
     3770                        if ( 'OR' === $parent_query['relation'] ) {
     3771                                $compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' );
     3772
     3773                        // Clauses joined by AND with "negative" operators share a join only if they also share a key.
     3774                        } else if ( isset( $sibling['field_id'] ) && isset( $clause['field_id'] ) && $sibling['field_id'] === $clause['field_id'] ) {
     3775                                $compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' );
     3776                        }
     3777
     3778                        $clause_compare  = strtoupper( $clause['compare'] );
     3779                        $sibling_compare = strtoupper( $sibling['compare'] );
     3780                        if ( in_array( $clause_compare, $compatible_compares ) && in_array( $sibling_compare, $compatible_compares ) ) {
     3781                                $alias = $sibling['alias'];
     3782                                break;
     3783                        }
     3784                }
     3785
     3786                return $alias;
     3787        }
     3788}
  • src/bp-xprofile/bp-xprofile-filters.php

    diff --git src/bp-xprofile/bp-xprofile-filters.php src/bp-xprofile/bp-xprofile-filters.php
    index 21eaa3d..411a8d4 100644
    function xprofile_filter_pre_validate_value_by_field_type( $value, $field, $fiel 
    200200/**
    201201 * Filter an Extended Profile field value, and attempt to make clickable links
    202202 * to members search results out of them.
    203  * 
     203 *
    204204 * - Not run on datebox field types
    205205 * - Not run on values without commas with less than 5 words
    206206 * - URL's are made clickable
    function bp_xprofile_filter_user_query_populate_extras( BP_User_Query $user_quer 
    323323add_filter( 'bp_user_query_populate_extras', 'bp_xprofile_filter_user_query_populate_extras', 2, 2 );
    324324
    325325/**
     326 * Parse 'xprofile_query' argument passed to BP_User_Query.
     327 *
     328 * @since BuddyPress (2.2.0)
     329 *
     330 * @param BP_User_Query User query object.
     331 */
     332function bp_xprofile_add_xprofile_query_to_user_query( BP_User_Query $q ) {
     333        global $wpdb;
     334
     335        if ( empty( $q->query_vars['xprofile_query'] ) ) {
     336                return;
     337        }
     338
     339        $xprofile_query = new BP_XProfile_Query( $q->query_vars['xprofile_query'] );
     340        $sql = $xprofile_query->get_sql( 'u', $q->uid_name );
     341
     342        if ( ! empty( $sql['join'] ) ) {
     343                $q->uid_clauses['select'] .= $sql['join'];
     344                $q->uid_clauses['where'] .= $sql['where'];
     345        }
     346}
     347add_action( 'bp_pre_user_query', 'bp_xprofile_add_xprofile_query_to_user_query' );
     348
     349/**
    326350 * Filter meta queries to modify for the xprofile data schema.
    327351 *
    328352 * @since BuddyPress (2.0.0)
  • new file tests/phpunit/testcases/xprofile/class-bp-xprofile-query.php

    diff --git tests/phpunit/testcases/xprofile/class-bp-xprofile-query.php tests/phpunit/testcases/xprofile/class-bp-xprofile-query.php
    new file mode 100644
    index 0000000..2f3f751
    - +  
     1<?php
     2
     3/**
     4 * @group xprofile
     5 * @group BP_XProfile_Query
     6 */
     7class BP_Tests_BP_XProfile_Query extends BP_UnitTestCase {
     8        protected $group;
     9        protected $fields = array();
     10        protected $users = array();
     11
     12        public function tearDown() {
     13                parent::tearDown();
     14                $this->group = '';
     15                $this->fields = array();
     16        }
     17
     18        public function test_no_field() {
     19                $this->create_fields( 2 );
     20                $this->create_users( 3 );
     21
     22                xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
     23                xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
     24                xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo');
     25
     26                $q = new BP_User_Query( array(
     27                        'xprofile_query' => array(
     28                                array(
     29                                        'value' => 'foo',
     30                                ),
     31                        ),
     32                ) );
     33
     34                $expected = array( $this->users[0], $this->users[2] );
     35                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     36        }
     37
     38        public function test_no_value() {
     39                $this->create_fields( 2 );
     40                $this->create_users( 3 );
     41
     42                xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
     43                xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
     44                xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo');
     45
     46                $q = new BP_User_Query( array(
     47                        'xprofile_query' => array(
     48                                array(
     49                                        'field' => $this->fields[1],
     50                                ),
     51                        ),
     52                ) );
     53
     54                $expected = array( $this->users[2] );
     55                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     56        }
     57
     58        public function test_translate_field_name_to_field_id() {
     59                $this->create_fields( 0 );
     60                $f = $this->factory->xprofile_field->create( array(
     61                        'field_group_id' => $this->group,
     62                        'type' => 'textbox',
     63                        'name' => 'Foo Field',
     64                ) );
     65                $this->create_users( 2 );
     66
     67                xprofile_set_field_data( $f, $this->users[0], 'foo' );
     68
     69                $q = new BP_User_Query( array(
     70                        'xprofile_query' => array(
     71                                array(
     72                                        'field' => 'Foo Field',
     73                                ),
     74                        ),
     75                ) );
     76
     77                $expected = array( $this->users[0] );
     78                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     79        }
     80
     81        public function test_single_clause_compare_default() {
     82                $this->create_fields( 2 );
     83                $this->create_users( 3 );
     84
     85                xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
     86                xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
     87                xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo');
     88
     89                $q = new BP_User_Query( array(
     90                        'xprofile_query' => array(
     91                                array(
     92                                        'field' => $this->fields[0],
     93                                        'value' => 'foo',
     94                                ),
     95                        ),
     96                ) );
     97
     98                $expected = array( $this->users[0] );
     99                $this->assertEquals( $expected, array_keys( $q->results ) );
     100        }
     101
     102        public function test_single_clause_compare_equals() {
     103                $this->create_fields( 2 );
     104                $this->create_users( 3 );
     105
     106                xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
     107                xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
     108                xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo');
     109
     110                $q = new BP_User_Query( array(
     111                        'xprofile_query' => array(
     112                                array(
     113                                        'field' => $this->fields[0],
     114                                        'value' => 'foo',
     115                                        'compare' => '=',
     116                                ),
     117                        ),
     118                ) );
     119
     120                $expected = array( $this->users[0] );
     121                $this->assertEquals( $expected, array_keys( $q->results ) );
     122        }
     123
     124        public function test_single_clause_compare_not_equals() {
     125                $this->create_fields( 1 );
     126                $this->create_users( 2 );
     127
     128                xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
     129                xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
     130
     131                $q = new BP_User_Query( array(
     132                        'xprofile_query' => array(
     133                                array(
     134                                        'field' => $this->fields[0],
     135                                        'value' => 'foo',
     136                                        'compare' => '!=',
     137                                ),
     138                        ),
     139                ) );
     140
     141                $expected = array( $this->users[1] );
     142                $this->assertEquals( $expected, array_keys( $q->results ) );
     143        }
     144
     145        public function test_single_clause_compare_arithmetic_comparisons() {
     146                $this->create_fields( 1 );
     147                $this->create_users( 3 );
     148
     149                xprofile_set_field_data( $this->fields[0], $this->users[0], '1' );
     150                xprofile_set_field_data( $this->fields[0], $this->users[1], '2' );
     151                xprofile_set_field_data( $this->fields[0], $this->users[2], '3' );
     152
     153                // <
     154                $q = new BP_User_Query( array(
     155                        'xprofile_query' => array(
     156                                array(
     157                                        'field' => $this->fields[0],
     158                                        'value' => 2,
     159                                        'compare' => '<',
     160                                ),
     161                        ),
     162                ) );
     163
     164                $expected = array( $this->users[0] );
     165                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     166
     167                // <=
     168                $q = new BP_User_Query( array(
     169                        'xprofile_query' => array(
     170                                array(
     171                                        'field' => $this->fields[0],
     172                                        'value' => 2,
     173                                        'compare' => '<=',
     174                                ),
     175                        ),
     176                ) );
     177
     178                $expected = array( $this->users[0], $this->users[1] );
     179                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     180
     181                // >=
     182                $q = new BP_User_Query( array(
     183                        'xprofile_query' => array(
     184                                array(
     185                                        'field' => $this->fields[0],
     186                                        'value' => 2,
     187                                        'compare' => '>=',
     188                                ),
     189                        ),
     190                ) );
     191
     192                $expected = array( $this->users[1], $this->users[2] );
     193                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     194
     195                // >
     196                $q = new BP_User_Query( array(
     197                        'xprofile_query' => array(
     198                                array(
     199                                        'field' => $this->fields[0],
     200                                        'value' => 2,
     201                                        'compare' => '>',
     202                                ),
     203                        ),
     204                ) );
     205
     206                $expected = array( $this->users[2] );
     207                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     208        }
     209
     210        public function test_single_clause_compare_like() {
     211                $this->create_fields( 1 );
     212                $this->create_users( 2 );
     213
     214                xprofile_set_field_data( $this->fields[0], $this->users[0], 'bar' );
     215
     216                $q = new BP_User_Query( array(
     217                        'xprofile_query' => array(
     218                                array(
     219                                        'field' => $this->fields[0],
     220                                        'value' => 'ba',
     221                                        'compare' => 'LIKE',
     222                                ),
     223                        ),
     224                ) );
     225
     226                $expected = array( $this->users[0] );
     227                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     228        }
     229
     230        public function test_single_clause_compare_not_like() {
     231                $this->create_fields( 1 );
     232                $this->create_users( 3 );
     233
     234                xprofile_set_field_data( $this->fields[0], $this->users[0], 'bar' );
     235                xprofile_set_field_data( $this->fields[0], $this->users[1], 'rab' );
     236
     237                $q = new BP_User_Query( array(
     238                        'xprofile_query' => array(
     239                                array(
     240                                        'field' => $this->fields[0],
     241                                        'value' => 'ba',
     242                                        'compare' => 'NOT LIKE',
     243                                ),
     244                        ),
     245                ) );
     246
     247                $expected = array( $this->users[1] );
     248                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     249        }
     250
     251        public function test_single_clause_compare_between_not_between() {
     252                $this->create_fields( 1 );
     253                $this->create_users( 3 );
     254
     255                xprofile_set_field_data( $this->fields[0], $this->users[0], '1' );
     256                xprofile_set_field_data( $this->fields[0], $this->users[1], '10' );
     257                xprofile_set_field_data( $this->fields[0], $this->users[2], '100' );
     258
     259                $q = new BP_User_Query( array(
     260                        'xprofile_query' => array(
     261                                array(
     262                                        'field' => $this->fields[0],
     263                                        'value' => array( 9, 12 ),
     264                                        'compare' => 'BETWEEN',
     265                                        'type' => 'NUMERIC',
     266                                ),
     267                        ),
     268                ) );
     269
     270                $expected = array( $this->users[1] );
     271                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     272
     273                $q = new BP_User_Query( array(
     274                        'xprofile_query' => array(
     275                                array(
     276                                        'field' => $this->fields[0],
     277                                        'value' => array( 9, 12 ),
     278                                        'compare' => 'NOT BETWEEN',
     279                                        'type' => 'NUMERIC',
     280                                ),
     281                        ),
     282                ) );
     283
     284                $expected = array( $this->users[0], $this->users[2] );
     285                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     286        }
     287
     288        public function test_single_clause_compare_regexp_rlike() {
     289                $this->create_fields( 1 );
     290                $this->create_users( 3 );
     291
     292                xprofile_set_field_data( $this->fields[0], $this->users[0], 'bar' );
     293                xprofile_set_field_data( $this->fields[0], $this->users[1], 'baz' );
     294
     295                $q = new BP_User_Query( array(
     296                        'xprofile_query' => array(
     297                                array(
     298                                        'field' => $this->fields[0],
     299                                        'value' => 'z$',
     300                                        'compare' => 'REGEXP',
     301                                ),
     302                        ),
     303                ) );
     304
     305                $expected = array( $this->users[1] );
     306                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     307
     308                // RLIKE is a synonym for REGEXP.
     309                $q = new BP_User_Query( array(
     310                        'xprofile_query' => array(
     311                                array(
     312                                        'field' => $this->fields[0],
     313                                        'value' => 'z$',
     314                                        'compare' => 'RLIKE',
     315                                ),
     316                        ),
     317                ) );
     318
     319                $expected = array( $this->users[1] );
     320                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     321        }
     322
     323        public function test_single_clause_compare_not_regexp() {
     324                $this->create_fields( 1 );
     325                $this->create_users( 3 );
     326
     327                xprofile_set_field_data( $this->fields[0], $this->users[0], 'bar' );
     328                xprofile_set_field_data( $this->fields[0], $this->users[1], 'baz' );
     329
     330                $q = new BP_User_Query( array(
     331                        'xprofile_query' => array(
     332                                array(
     333                                        'field' => $this->fields[0],
     334                                        'value' => 'z$',
     335                                        'compare' => 'NOT REGEXP',
     336                                ),
     337                        ),
     338                ) );
     339
     340                $expected = array( $this->users[0] );
     341                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     342        }
     343
     344        public function test_single_clause_compare_not_exists() {
     345                $this->create_fields( 2 );
     346                $this->create_users( 2 );
     347
     348                xprofile_set_field_data( $this->fields[0], $this->users[0], 'bar' );
     349                xprofile_set_field_data( $this->fields[1], $this->users[1], 'bar' );
     350
     351                $q = new BP_User_Query( array(
     352                        'xprofile_query' => array(
     353                                array(
     354                                        'field' => $this->fields[0],
     355                                        'compare' => 'NOT EXISTS',
     356                                ),
     357                        ),
     358                ) );
     359
     360                $expected = array( $this->users[1] );
     361                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     362        }
     363
     364        public function test_relation_default_to_and() {
     365                $this->create_fields( 2 );
     366                $this->create_users( 4 );
     367
     368                xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
     369                xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
     370                xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo' );
     371                xprofile_set_field_data( $this->fields[1], $this->users[3], 'bar' );
     372                xprofile_set_field_data( $this->fields[0], $this->users[3], 'foo' );
     373
     374                $q = new BP_User_Query( array(
     375                        'xprofile_query' => array(
     376                                array(
     377                                        'field' => $this->fields[0],
     378                                        'value' => 'foo',
     379                                ),
     380                                array(
     381                                        'field' => $this->fields[1],
     382                                        'value' => 'bar',
     383                                ),
     384                        ),
     385                ) );
     386
     387                $expected = array( $this->users[3] );
     388                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     389        }
     390
     391        public function test_relation_and() {
     392                $this->create_fields( 2 );
     393                $this->create_users( 4 );
     394
     395                xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
     396                xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
     397                xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo' );
     398                xprofile_set_field_data( $this->fields[1], $this->users[3], 'bar' );
     399                xprofile_set_field_data( $this->fields[0], $this->users[3], 'foo' );
     400
     401                $q = new BP_User_Query( array(
     402                        'xprofile_query' => array(
     403                                'relation' => 'AND',
     404                                array(
     405                                        'field' => $this->fields[0],
     406                                        'value' => 'foo',
     407                                ),
     408                                array(
     409                                        'field' => $this->fields[1],
     410                                        'value' => 'bar',
     411                                ),
     412                        ),
     413                ) );
     414
     415                $expected = array( $this->users[3] );
     416                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     417        }
     418
     419        public function test_relation_or() {
     420                $this->create_fields( 2 );
     421                $this->create_users( 4 );
     422
     423                xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
     424                xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
     425                xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo' );
     426                xprofile_set_field_data( $this->fields[1], $this->users[3], 'bar' );
     427                xprofile_set_field_data( $this->fields[0], $this->users[3], 'foo' );
     428
     429                $q = new BP_User_Query( array(
     430                        'xprofile_query' => array(
     431                                'relation' => 'OR',
     432                                array(
     433                                        'field' => $this->fields[0],
     434                                        'value' => 'foo',
     435                                ),
     436                                array(
     437                                        'field' => $this->fields[1],
     438                                        'value' => 'bar',
     439                                ),
     440                        ),
     441                ) );
     442
     443                $expected = array( $this->users[0], $this->users[3] );
     444                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     445        }
     446
     447        public function test_relation_and_with_compare_not_exists() {
     448                $this->create_fields( 2 );
     449                $this->create_users( 4 );
     450
     451                xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
     452                xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
     453                xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo' );
     454                xprofile_set_field_data( $this->fields[1], $this->users[3], 'bar' );
     455
     456                $q = new BP_User_Query( array(
     457                        'xprofile_query' => array(
     458                                'relation' => 'AND',
     459                                array(
     460                                        'field' => $this->fields[0],
     461                                        'compare' => 'NOT EXISTS',
     462                                ),
     463                                array(
     464                                        'field' => $this->fields[1],
     465                                        'value' => 'bar',
     466                                ),
     467                        ),
     468                ) );
     469
     470                $expected = array( $this->users[3] );
     471                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     472        }
     473
     474        public function test_relation_or_with_compare_not_exists() {
     475                $this->create_fields( 2 );
     476                $this->create_users( 4 );
     477
     478                xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
     479                xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
     480                xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo' );
     481                xprofile_set_field_data( $this->fields[1], $this->users[3], 'bar' );
     482                xprofile_set_field_data( $this->fields[0], $this->users[3], 'bar' );
     483
     484                $q = new BP_User_Query( array(
     485                        'xprofile_query' => array(
     486                                'relation' => 'OR',
     487                                array(
     488                                        'field' => $this->fields[0],
     489                                        'compare' => 'NOT EXISTS',
     490                                ),
     491                                array(
     492                                        'field' => $this->fields[1],
     493                                        'value' => 'bar',
     494                                ),
     495                        ),
     496                ) );
     497
     498                $expected = array( $this->users[2], $this->users[3] );
     499                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     500        }
     501
     502        /**
     503         * Tests for table join logic.
     504         */
     505        public function test_relation_or_compare_equals_and_like() {
     506                $this->create_fields( 2 );
     507                $this->create_users( 4 );
     508
     509                xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
     510                xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
     511                xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo' );
     512                xprofile_set_field_data( $this->fields[1], $this->users[3], 'barry' );
     513
     514                $q = new BP_User_Query( array(
     515                        'xprofile_query' => array(
     516                                'relation' => 'OR',
     517                                array(
     518                                        'field' => $this->fields[0],
     519                                        'compare' => '=',
     520                                        'value' => 'foo',
     521                                ),
     522                                array(
     523                                        'field' => $this->fields[1],
     524                                        'value' => 'bar',
     525                                        'compare' => 'LIKE',
     526                                ),
     527                        ),
     528                ) );
     529
     530                $expected = array( $this->users[0], $this->users[3] );
     531                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     532        }
     533
     534        public function test_nested() {
     535                $this->create_fields( 2 );
     536                $this->create_users( 3 );
     537
     538                xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
     539                xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
     540                xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo' );
     541                xprofile_set_field_data( $this->fields[1], $this->users[1], 'foo' );
     542
     543                $q = new BP_User_Query( array(
     544                        'xprofile_query' => array(
     545                                'relation' => 'OR',
     546                                array(
     547                                        'field' => $this->fields[0],
     548                                        'compare' => '=',
     549                                        'value' => 'foo',
     550                                ),
     551                                array(
     552                                        'relation' => 'AND',
     553                                        array(
     554                                                'field' => $this->fields[0],
     555                                                'value' => 'bar',
     556                                        ),
     557                                        array(
     558                                                'field' => $this->fields[1],
     559                                                'value' => 'foo',
     560                                        ),
     561                                ),
     562                        ),
     563                ) );
     564
     565                $expected = array( $this->users[0], $this->users[1] );
     566                $this->assertEqualSets( $expected, array_keys( $q->results ) );
     567        }
     568
     569        /** Helpers **********************************************************/
     570
     571        protected function create_fields( $count ) {
     572                $this->group = $this->factory->xprofile_group->create();
     573                for ( $i = 0; $i < $count; $i++ ) {
     574                        $this->fields[] = $this->factory->xprofile_field->create( array(
     575                                'field_group_id' => $this->group,
     576                                'type' => 'textbox',
     577                        ) );
     578                }
     579        }
     580
     581        protected function create_users( $count ) {
     582                for ( $i = 0; $i < $count; $i++ ) {
     583                        $this->users[] = $this->create_user();
     584                }
     585        }
     586}