wp/wp-includes/class-wp-user-query.php
changeset 7 cf61fcea0001
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
       
     1 <?php
       
     2 /**
       
     3  * User API: WP_User_Query class
       
     4  *
       
     5  * @package WordPress
       
     6  * @subpackage Users
       
     7  * @since 4.4.0
       
     8  */
       
     9 
       
    10 /**
       
    11  * Core class used for querying users.
       
    12  *
       
    13  * @since 3.1.0
       
    14  *
       
    15  * @see WP_User_Query::prepare_query() for information on accepted arguments.
       
    16  */
       
    17 class WP_User_Query {
       
    18 
       
    19 	/**
       
    20 	 * Query vars, after parsing
       
    21 	 *
       
    22 	 * @since 3.5.0
       
    23 	 * @var array
       
    24 	 */
       
    25 	public $query_vars = array();
       
    26 
       
    27 	/**
       
    28 	 * List of found user ids
       
    29 	 *
       
    30 	 * @since 3.1.0
       
    31 	 * @var array
       
    32 	 */
       
    33 	private $results;
       
    34 
       
    35 	/**
       
    36 	 * Total number of found users for the current query
       
    37 	 *
       
    38 	 * @since 3.1.0
       
    39 	 * @var int
       
    40 	 */
       
    41 	private $total_users = 0;
       
    42 
       
    43 	/**
       
    44 	 * Metadata query container.
       
    45 	 *
       
    46 	 * @since 4.2.0
       
    47 	 * @var WP_Meta_Query
       
    48 	 */
       
    49 	public $meta_query = false;
       
    50 
       
    51 	/**
       
    52 	 * The SQL query used to fetch matching users.
       
    53 	 *
       
    54 	 * @since 4.4.0
       
    55 	 * @var string
       
    56 	 */
       
    57 	public $request;
       
    58 
       
    59 	private $compat_fields = array( 'results', 'total_users' );
       
    60 
       
    61 	// SQL clauses
       
    62 	public $query_fields;
       
    63 	public $query_from;
       
    64 	public $query_where;
       
    65 	public $query_orderby;
       
    66 	public $query_limit;
       
    67 
       
    68 	/**
       
    69 	 * PHP5 constructor.
       
    70 	 *
       
    71 	 * @since 3.1.0
       
    72 	 *
       
    73 	 * @param null|string|array $query Optional. The query variables.
       
    74 	 */
       
    75 	public function __construct( $query = null ) {
       
    76 		if ( ! empty( $query ) ) {
       
    77 			$this->prepare_query( $query );
       
    78 			$this->query();
       
    79 		}
       
    80 	}
       
    81 
       
    82 	/**
       
    83 	 * Fills in missing query variables with default values.
       
    84 	 *
       
    85 	 * @since 4.4.0
       
    86 	 *
       
    87 	 * @param array $args Query vars, as passed to `WP_User_Query`.
       
    88 	 * @return array Complete query variables with undefined ones filled in with defaults.
       
    89 	 */
       
    90 	public static function fill_query_vars( $args ) {
       
    91 		$defaults = array(
       
    92 			'blog_id' => get_current_blog_id(),
       
    93 			'role' => '',
       
    94 			'role__in' => array(),
       
    95 			'role__not_in' => array(),
       
    96 			'meta_key' => '',
       
    97 			'meta_value' => '',
       
    98 			'meta_compare' => '',
       
    99 			'include' => array(),
       
   100 			'exclude' => array(),
       
   101 			'search' => '',
       
   102 			'search_columns' => array(),
       
   103 			'orderby' => 'login',
       
   104 			'order' => 'ASC',
       
   105 			'offset' => '',
       
   106 			'number' => '',
       
   107 			'paged' => 1,
       
   108 			'count_total' => true,
       
   109 			'fields' => 'all',
       
   110 			'who' => '',
       
   111 			'has_published_posts' => null,
       
   112 			'nicename' => '',
       
   113 			'nicename__in' => array(),
       
   114 			'nicename__not_in' => array(),
       
   115 			'login' => '',
       
   116 			'login__in' => array(),
       
   117 			'login__not_in' => array()
       
   118 		);
       
   119 
       
   120 		return wp_parse_args( $args, $defaults );
       
   121 	}
       
   122 
       
   123 	/**
       
   124 	 * Prepare the query variables.
       
   125 	 *
       
   126 	 * @since 3.1.0
       
   127 	 * @since 4.1.0 Added the ability to order by the `include` value.
       
   128 	 * @since 4.2.0 Added 'meta_value_num' support for `$orderby` parameter. Added multi-dimensional array syntax
       
   129 	 *              for `$orderby` parameter.
       
   130 	 * @since 4.3.0 Added 'has_published_posts' parameter.
       
   131 	 * @since 4.4.0 Added 'paged', 'role__in', and 'role__not_in' parameters. The 'role' parameter was updated to
       
   132 	 *              permit an array or comma-separated list of values. The 'number' parameter was updated to support
       
   133 	 *              querying for all users with using -1.
       
   134 	 * @since 4.7.0 Added 'nicename', 'nicename__in', 'nicename__not_in', 'login', 'login__in',
       
   135 	 *              and 'login__not_in' parameters.
       
   136 	 *
       
   137 	 *
       
   138 	 * @global wpdb $wpdb WordPress database abstraction object.
       
   139 	 * @global int  $blog_id
       
   140 	 *
       
   141 	 * @param string|array $query {
       
   142 	 *     Optional. Array or string of Query parameters.
       
   143 	 *
       
   144 	 *     @type int          $blog_id             The site ID. Default is the current site.
       
   145 	 *     @type string|array $role                An array or a comma-separated list of role names that users must match
       
   146 	 *                                             to be included in results. Note that this is an inclusive list: users
       
   147 	 *                                             must match *each* role. Default empty.
       
   148 	 *     @type array        $role__in            An array of role names. Matched users must have at least one of these
       
   149 	 *                                             roles. Default empty array.
       
   150 	 *     @type array        $role__not_in        An array of role names to exclude. Users matching one or more of these
       
   151 	 *                                             roles will not be included in results. Default empty array.
       
   152 	 *     @type string       $meta_key            User meta key. Default empty.
       
   153 	 *     @type string       $meta_value          User meta value. Default empty.
       
   154 	 *     @type string       $meta_compare        Comparison operator to test the `$meta_value`. Accepts '=', '!=',
       
   155 	 *                                             '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN',
       
   156 	 *                                             'BETWEEN', 'NOT BETWEEN', 'EXISTS', 'NOT EXISTS', 'REGEXP',
       
   157 	 *                                             'NOT REGEXP', or 'RLIKE'. Default '='.
       
   158 	 *     @type array        $include             An array of user IDs to include. Default empty array.
       
   159 	 *     @type array        $exclude             An array of user IDs to exclude. Default empty array.
       
   160 	 *     @type string       $search              Search keyword. Searches for possible string matches on columns.
       
   161 	 *                                             When `$search_columns` is left empty, it tries to determine which
       
   162 	 *                                             column to search in based on search string. Default empty.
       
   163 	 *     @type array        $search_columns      Array of column names to be searched. Accepts 'ID', 'login',
       
   164 	 *                                             'nicename', 'email', 'url'. Default empty array.
       
   165 	 *     @type string|array $orderby             Field(s) to sort the retrieved users by. May be a single value,
       
   166 	 *                                             an array of values, or a multi-dimensional array with fields as
       
   167 	 *                                             keys and orders ('ASC' or 'DESC') as values. Accepted values are
       
   168 	 *                                             'ID', 'display_name' (or 'name'), 'include', 'user_login'
       
   169 	 *                                             (or 'login'), 'login__in', 'user_nicename' (or 'nicename'),
       
   170 	 *                                             'nicename__in', 'user_email (or 'email'), 'user_url' (or 'url'),
       
   171 	 *                                             'user_registered' (or 'registered'), 'post_count', 'meta_value',
       
   172 	 *                                             'meta_value_num', the value of `$meta_key`, or an array key of
       
   173 	 *                                             `$meta_query`. To use 'meta_value' or 'meta_value_num', `$meta_key`
       
   174 	 *                                             must be also be defined. Default 'user_login'.
       
   175 	 *     @type string       $order               Designates ascending or descending order of users. Order values
       
   176 	 *                                             passed as part of an `$orderby` array take precedence over this
       
   177 	 *                                             parameter. Accepts 'ASC', 'DESC'. Default 'ASC'.
       
   178 	 *     @type int          $offset              Number of users to offset in retrieved results. Can be used in
       
   179 	 *                                             conjunction with pagination. Default 0.
       
   180 	 *     @type int          $number              Number of users to limit the query for. Can be used in
       
   181 	 *                                             conjunction with pagination. Value -1 (all) is supported, but
       
   182 	 *                                             should be used with caution on larger sites.
       
   183 	 *                                             Default empty (all users).
       
   184 	 *     @type int          $paged               When used with number, defines the page of results to return.
       
   185 	 *                                             Default 1.
       
   186 	 *     @type bool         $count_total         Whether to count the total number of users found. If pagination
       
   187 	 *                                             is not needed, setting this to false can improve performance.
       
   188 	 *                                             Default true.
       
   189 	 *     @type string|array $fields              Which fields to return. Single or all fields (string), or array
       
   190 	 *                                             of fields. Accepts 'ID', 'display_name', 'user_login',
       
   191 	 *                                             'user_nicename', 'user_email', 'user_url', 'user_registered'.
       
   192 	 *                                             Use 'all' for all fields and 'all_with_meta' to include
       
   193 	 *                                             meta fields. Default 'all'.
       
   194 	 *     @type string       $who                 Type of users to query. Accepts 'authors'.
       
   195 	 *                                             Default empty (all users).
       
   196 	 *     @type bool|array   $has_published_posts Pass an array of post types to filter results to users who have
       
   197 	 *                                             published posts in those post types. `true` is an alias for all
       
   198 	 *                                             public post types.
       
   199 	 *     @type string       $nicename            The user nicename. Default empty.
       
   200 	 *     @type array        $nicename__in        An array of nicenames to include. Users matching one of these
       
   201 	 *                                             nicenames will be included in results. Default empty array.
       
   202 	 *     @type array        $nicename__not_in    An array of nicenames to exclude. Users matching one of these
       
   203 	 *                                             nicenames will not be included in results. Default empty array.
       
   204 	 *     @type string       $login               The user login. Default empty.
       
   205 	 *     @type array        $login__in           An array of logins to include. Users matching one of these
       
   206 	 *                                             logins will be included in results. Default empty array.
       
   207 	 *     @type array        $login__not_in       An array of logins to exclude. Users matching one of these
       
   208 	 *                                             logins will not be included in results. Default empty array.
       
   209 	 * }
       
   210 	 */
       
   211 	public function prepare_query( $query = array() ) {
       
   212 		global $wpdb;
       
   213 
       
   214 		if ( empty( $this->query_vars ) || ! empty( $query ) ) {
       
   215 			$this->query_limit = null;
       
   216 			$this->query_vars = $this->fill_query_vars( $query );
       
   217 		}
       
   218 
       
   219 		/**
       
   220 		 * Fires before the WP_User_Query has been parsed.
       
   221 		 *
       
   222 		 * The passed WP_User_Query object contains the query variables, not
       
   223 		 * yet passed into SQL.
       
   224 		 *
       
   225 		 * @since 4.0.0
       
   226 		 *
       
   227 		 * @param WP_User_Query $this The current WP_User_Query instance,
       
   228 		 *                            passed by reference.
       
   229 		 */
       
   230 		do_action( 'pre_get_users', $this );
       
   231 
       
   232 		// Ensure that query vars are filled after 'pre_get_users'.
       
   233 		$qv =& $this->query_vars;
       
   234 		$qv =  $this->fill_query_vars( $qv );
       
   235 
       
   236 		if ( is_array( $qv['fields'] ) ) {
       
   237 			$qv['fields'] = array_unique( $qv['fields'] );
       
   238 
       
   239 			$this->query_fields = array();
       
   240 			foreach ( $qv['fields'] as $field ) {
       
   241 				$field = 'ID' === $field ? 'ID' : sanitize_key( $field );
       
   242 				$this->query_fields[] = "$wpdb->users.$field";
       
   243 			}
       
   244 			$this->query_fields = implode( ',', $this->query_fields );
       
   245 		} elseif ( 'all' == $qv['fields'] ) {
       
   246 			$this->query_fields = "$wpdb->users.*";
       
   247 		} else {
       
   248 			$this->query_fields = "$wpdb->users.ID";
       
   249 		}
       
   250 
       
   251 		if ( isset( $qv['count_total'] ) && $qv['count_total'] )
       
   252 			$this->query_fields = 'SQL_CALC_FOUND_ROWS ' . $this->query_fields;
       
   253 
       
   254 		$this->query_from = "FROM $wpdb->users";
       
   255 		$this->query_where = "WHERE 1=1";
       
   256 
       
   257 		// Parse and sanitize 'include', for use by 'orderby' as well as 'include' below.
       
   258 		if ( ! empty( $qv['include'] ) ) {
       
   259 			$include = wp_parse_id_list( $qv['include'] );
       
   260 		} else {
       
   261 			$include = false;
       
   262 		}
       
   263 
       
   264 		$blog_id = 0;
       
   265 		if ( isset( $qv['blog_id'] ) ) {
       
   266 			$blog_id = absint( $qv['blog_id'] );
       
   267 		}
       
   268 
       
   269 		if ( $qv['has_published_posts'] && $blog_id ) {
       
   270 			if ( true === $qv['has_published_posts'] ) {
       
   271 				$post_types = get_post_types( array( 'public' => true ) );
       
   272 			} else {
       
   273 				$post_types = (array) $qv['has_published_posts'];
       
   274 			}
       
   275 
       
   276 			foreach ( $post_types as &$post_type ) {
       
   277 				$post_type = $wpdb->prepare( '%s', $post_type );
       
   278 			}
       
   279 
       
   280 			$posts_table = $wpdb->get_blog_prefix( $blog_id ) . 'posts';
       
   281 			$this->query_where .= " AND $wpdb->users.ID IN ( SELECT DISTINCT $posts_table.post_author FROM $posts_table WHERE $posts_table.post_status = 'publish' AND $posts_table.post_type IN ( " . join( ", ", $post_types ) . " ) )";
       
   282 		}
       
   283 
       
   284 		// nicename
       
   285 		if ( '' !== $qv['nicename']) {
       
   286 			$this->query_where .= $wpdb->prepare( ' AND user_nicename = %s', $qv['nicename'] );
       
   287 		}
       
   288 
       
   289 		if ( ! empty( $qv['nicename__in'] ) ) {
       
   290 			$sanitized_nicename__in = array_map( 'esc_sql', $qv['nicename__in'] );
       
   291 			$nicename__in = implode( "','", $sanitized_nicename__in );
       
   292 			$this->query_where .= " AND user_nicename IN ( '$nicename__in' )";
       
   293 		}
       
   294 
       
   295 		if ( ! empty( $qv['nicename__not_in'] ) ) {
       
   296 			$sanitized_nicename__not_in = array_map( 'esc_sql', $qv['nicename__not_in'] );
       
   297 			$nicename__not_in = implode( "','", $sanitized_nicename__not_in );
       
   298 			$this->query_where .= " AND user_nicename NOT IN ( '$nicename__not_in' )";
       
   299 		}
       
   300 
       
   301 		// login
       
   302 		if ( '' !== $qv['login']) {
       
   303 			$this->query_where .= $wpdb->prepare( ' AND user_login = %s', $qv['login'] );
       
   304 		}
       
   305 
       
   306 		if ( ! empty( $qv['login__in'] ) ) {
       
   307 			$sanitized_login__in = array_map( 'esc_sql', $qv['login__in'] );
       
   308 			$login__in = implode( "','", $sanitized_login__in );
       
   309 			$this->query_where .= " AND user_login IN ( '$login__in' )";
       
   310 		}
       
   311 
       
   312 		if ( ! empty( $qv['login__not_in'] ) ) {
       
   313 			$sanitized_login__not_in = array_map( 'esc_sql', $qv['login__not_in'] );
       
   314 			$login__not_in = implode( "','", $sanitized_login__not_in );
       
   315 			$this->query_where .= " AND user_login NOT IN ( '$login__not_in' )";
       
   316 		}
       
   317 
       
   318 		// Meta query.
       
   319 		$this->meta_query = new WP_Meta_Query();
       
   320 		$this->meta_query->parse_query_vars( $qv );
       
   321 
       
   322 		if ( isset( $qv['who'] ) && 'authors' == $qv['who'] && $blog_id ) {
       
   323 			$who_query = array(
       
   324 				'key' => $wpdb->get_blog_prefix( $blog_id ) . 'user_level',
       
   325 				'value' => 0,
       
   326 				'compare' => '!=',
       
   327 			);
       
   328 
       
   329 			// Prevent extra meta query.
       
   330 			$qv['blog_id'] = $blog_id = 0;
       
   331 
       
   332 			if ( empty( $this->meta_query->queries ) ) {
       
   333 				$this->meta_query->queries = array( $who_query );
       
   334 			} else {
       
   335 				// Append the cap query to the original queries and reparse the query.
       
   336 				$this->meta_query->queries = array(
       
   337 					'relation' => 'AND',
       
   338 					array( $this->meta_query->queries, $who_query ),
       
   339 				);
       
   340 			}
       
   341 
       
   342 			$this->meta_query->parse_query_vars( $this->meta_query->queries );
       
   343 		}
       
   344 
       
   345 		$roles = array();
       
   346 		if ( isset( $qv['role'] ) ) {
       
   347 			if ( is_array( $qv['role'] ) ) {
       
   348 				$roles = $qv['role'];
       
   349 			} elseif ( is_string( $qv['role'] ) && ! empty( $qv['role'] ) ) {
       
   350 				$roles = array_map( 'trim', explode( ',', $qv['role'] ) );
       
   351 			}
       
   352 		}
       
   353 
       
   354 		$role__in = array();
       
   355 		if ( isset( $qv['role__in'] ) ) {
       
   356 			$role__in = (array) $qv['role__in'];
       
   357 		}
       
   358 
       
   359 		$role__not_in = array();
       
   360 		if ( isset( $qv['role__not_in'] ) ) {
       
   361 			$role__not_in = (array) $qv['role__not_in'];
       
   362 		}
       
   363 
       
   364 		if ( $blog_id && ( ! empty( $roles ) || ! empty( $role__in ) || ! empty( $role__not_in ) || is_multisite() ) ) {
       
   365 			$role_queries  = array();
       
   366 
       
   367 			$roles_clauses = array( 'relation' => 'AND' );
       
   368 			if ( ! empty( $roles ) ) {
       
   369 				foreach ( $roles as $role ) {
       
   370 					$roles_clauses[] = array(
       
   371 						'key'     => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities',
       
   372 						'value'   => '"' . $role . '"',
       
   373 						'compare' => 'LIKE',
       
   374 					);
       
   375 				}
       
   376 
       
   377 				$role_queries[] = $roles_clauses;
       
   378 			}
       
   379 
       
   380 			$role__in_clauses = array( 'relation' => 'OR' );
       
   381 			if ( ! empty( $role__in ) ) {
       
   382 				foreach ( $role__in as $role ) {
       
   383 					$role__in_clauses[] = array(
       
   384 						'key'     => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities',
       
   385 						'value'   => '"' . $role . '"',
       
   386 						'compare' => 'LIKE',
       
   387 					);
       
   388 				}
       
   389 
       
   390 				$role_queries[] = $role__in_clauses;
       
   391 			}
       
   392 
       
   393 			$role__not_in_clauses = array( 'relation' => 'AND' );
       
   394 			if ( ! empty( $role__not_in ) ) {
       
   395 				foreach ( $role__not_in as $role ) {
       
   396 					$role__not_in_clauses[] = array(
       
   397 						'key'     => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities',
       
   398 						'value'   => '"' . $role . '"',
       
   399 						'compare' => 'NOT LIKE',
       
   400 					);
       
   401 				}
       
   402 
       
   403 				$role_queries[] = $role__not_in_clauses;
       
   404 			}
       
   405 
       
   406 			// If there are no specific roles named, make sure the user is a member of the site.
       
   407 			if ( empty( $role_queries ) ) {
       
   408 				$role_queries[] = array(
       
   409 					'key' => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities',
       
   410 					'compare' => 'EXISTS',
       
   411 				);
       
   412 			}
       
   413 
       
   414 			// Specify that role queries should be joined with AND.
       
   415 			$role_queries['relation'] = 'AND';
       
   416 
       
   417 			if ( empty( $this->meta_query->queries ) ) {
       
   418 				$this->meta_query->queries = $role_queries;
       
   419 			} else {
       
   420 				// Append the cap query to the original queries and reparse the query.
       
   421 				$this->meta_query->queries = array(
       
   422 					'relation' => 'AND',
       
   423 					array( $this->meta_query->queries, $role_queries ),
       
   424 				);
       
   425 			}
       
   426 
       
   427 			$this->meta_query->parse_query_vars( $this->meta_query->queries );
       
   428 		}
       
   429 
       
   430 		if ( ! empty( $this->meta_query->queries ) ) {
       
   431 			$clauses = $this->meta_query->get_sql( 'user', $wpdb->users, 'ID', $this );
       
   432 			$this->query_from .= $clauses['join'];
       
   433 			$this->query_where .= $clauses['where'];
       
   434 
       
   435 			if ( $this->meta_query->has_or_relation() ) {
       
   436 				$this->query_fields = 'DISTINCT ' . $this->query_fields;
       
   437 			}
       
   438 		}
       
   439 
       
   440 		// sorting
       
   441 		$qv['order'] = isset( $qv['order'] ) ? strtoupper( $qv['order'] ) : '';
       
   442 		$order = $this->parse_order( $qv['order'] );
       
   443 
       
   444 		if ( empty( $qv['orderby'] ) ) {
       
   445 			// Default order is by 'user_login'.
       
   446 			$ordersby = array( 'user_login' => $order );
       
   447 		} elseif ( is_array( $qv['orderby'] ) ) {
       
   448 			$ordersby = $qv['orderby'];
       
   449 		} else {
       
   450 			// 'orderby' values may be a comma- or space-separated list.
       
   451 			$ordersby = preg_split( '/[,\s]+/', $qv['orderby'] );
       
   452 		}
       
   453 
       
   454 		$orderby_array = array();
       
   455 		foreach ( $ordersby as $_key => $_value ) {
       
   456 			if ( ! $_value ) {
       
   457 				continue;
       
   458 			}
       
   459 
       
   460 			if ( is_int( $_key ) ) {
       
   461 				// Integer key means this is a flat array of 'orderby' fields.
       
   462 				$_orderby = $_value;
       
   463 				$_order = $order;
       
   464 			} else {
       
   465 				// Non-integer key means this the key is the field and the value is ASC/DESC.
       
   466 				$_orderby = $_key;
       
   467 				$_order = $_value;
       
   468 			}
       
   469 
       
   470 			$parsed = $this->parse_orderby( $_orderby );
       
   471 
       
   472 			if ( ! $parsed ) {
       
   473 				continue;
       
   474 			}
       
   475 
       
   476 			if ( 'nicename__in' === $_orderby || 'login__in' === $_orderby ) {
       
   477 				$orderby_array[] = $parsed;
       
   478 			} else {
       
   479 				$orderby_array[] = $parsed . ' ' . $this->parse_order( $_order );
       
   480 			}
       
   481 		}
       
   482 
       
   483 		// If no valid clauses were found, order by user_login.
       
   484 		if ( empty( $orderby_array ) ) {
       
   485 			$orderby_array[] = "user_login $order";
       
   486 		}
       
   487 
       
   488 		$this->query_orderby = 'ORDER BY ' . implode( ', ', $orderby_array );
       
   489 
       
   490 		// limit
       
   491 		if ( isset( $qv['number'] ) && $qv['number'] > 0 ) {
       
   492 			if ( $qv['offset'] ) {
       
   493 				$this->query_limit = $wpdb->prepare("LIMIT %d, %d", $qv['offset'], $qv['number']);
       
   494 			} else {
       
   495 				$this->query_limit = $wpdb->prepare( "LIMIT %d, %d", $qv['number'] * ( $qv['paged'] - 1 ), $qv['number'] );
       
   496 			}
       
   497 		}
       
   498 
       
   499 		$search = '';
       
   500 		if ( isset( $qv['search'] ) )
       
   501 			$search = trim( $qv['search'] );
       
   502 
       
   503 		if ( $search ) {
       
   504 			$leading_wild = ( ltrim($search, '*') != $search );
       
   505 			$trailing_wild = ( rtrim($search, '*') != $search );
       
   506 			if ( $leading_wild && $trailing_wild )
       
   507 				$wild = 'both';
       
   508 			elseif ( $leading_wild )
       
   509 				$wild = 'leading';
       
   510 			elseif ( $trailing_wild )
       
   511 				$wild = 'trailing';
       
   512 			else
       
   513 				$wild = false;
       
   514 			if ( $wild )
       
   515 				$search = trim($search, '*');
       
   516 
       
   517 			$search_columns = array();
       
   518 			if ( $qv['search_columns'] ) {
       
   519 				$search_columns = array_intersect( $qv['search_columns'], array( 'ID', 'user_login', 'user_email', 'user_url', 'user_nicename', 'display_name' ) );
       
   520 			}
       
   521 			if ( ! $search_columns ) {
       
   522 				if ( false !== strpos( $search, '@') )
       
   523 					$search_columns = array('user_email');
       
   524 				elseif ( is_numeric($search) )
       
   525 					$search_columns = array('user_login', 'ID');
       
   526 				elseif ( preg_match('|^https?://|', $search) && ! ( is_multisite() && wp_is_large_network( 'users' ) ) )
       
   527 					$search_columns = array('user_url');
       
   528 				else
       
   529 					$search_columns = array('user_login', 'user_url', 'user_email', 'user_nicename', 'display_name');
       
   530 			}
       
   531 
       
   532 			/**
       
   533 			 * Filters the columns to search in a WP_User_Query search.
       
   534 			 *
       
   535 			 * The default columns depend on the search term, and include 'user_email',
       
   536 			 * 'user_login', 'ID', 'user_url', 'display_name', and 'user_nicename'.
       
   537 			 *
       
   538 			 * @since 3.6.0
       
   539 			 *
       
   540 			 * @param array         $search_columns Array of column names to be searched.
       
   541 			 * @param string        $search         Text being searched.
       
   542 			 * @param WP_User_Query $this           The current WP_User_Query instance.
       
   543 			 */
       
   544 			$search_columns = apply_filters( 'user_search_columns', $search_columns, $search, $this );
       
   545 
       
   546 			$this->query_where .= $this->get_search_sql( $search, $search_columns, $wild );
       
   547 		}
       
   548 
       
   549 		if ( ! empty( $include ) ) {
       
   550 			// Sanitized earlier.
       
   551 			$ids = implode( ',', $include );
       
   552 			$this->query_where .= " AND $wpdb->users.ID IN ($ids)";
       
   553 		} elseif ( ! empty( $qv['exclude'] ) ) {
       
   554 			$ids = implode( ',', wp_parse_id_list( $qv['exclude'] ) );
       
   555 			$this->query_where .= " AND $wpdb->users.ID NOT IN ($ids)";
       
   556 		}
       
   557 
       
   558 		// Date queries are allowed for the user_registered field.
       
   559 		if ( ! empty( $qv['date_query'] ) && is_array( $qv['date_query'] ) ) {
       
   560 			$date_query = new WP_Date_Query( $qv['date_query'], 'user_registered' );
       
   561 			$this->query_where .= $date_query->get_sql();
       
   562 		}
       
   563 
       
   564 		/**
       
   565 		 * Fires after the WP_User_Query has been parsed, and before
       
   566 		 * the query is executed.
       
   567 		 *
       
   568 		 * The passed WP_User_Query object contains SQL parts formed
       
   569 		 * from parsing the given query.
       
   570 		 *
       
   571 		 * @since 3.1.0
       
   572 		 *
       
   573 		 * @param WP_User_Query $this The current WP_User_Query instance,
       
   574 		 *                            passed by reference.
       
   575 		 */
       
   576 		do_action_ref_array( 'pre_user_query', array( &$this ) );
       
   577 	}
       
   578 
       
   579 	/**
       
   580 	 * Execute the query, with the current variables.
       
   581 	 *
       
   582 	 * @since 3.1.0
       
   583 	 *
       
   584 	 * @global wpdb $wpdb WordPress database abstraction object.
       
   585 	 */
       
   586 	public function query() {
       
   587 		global $wpdb;
       
   588 
       
   589 		$qv =& $this->query_vars;
       
   590 
       
   591 		$this->request = "SELECT $this->query_fields $this->query_from $this->query_where $this->query_orderby $this->query_limit";
       
   592 
       
   593 		if ( is_array( $qv['fields'] ) || 'all' == $qv['fields'] ) {
       
   594 			$this->results = $wpdb->get_results( $this->request );
       
   595 		} else {
       
   596 			$this->results = $wpdb->get_col( $this->request );
       
   597 		}
       
   598 
       
   599 		/**
       
   600 		 * Filters SELECT FOUND_ROWS() query for the current WP_User_Query instance.
       
   601 		 *
       
   602 		 * @since 3.2.0
       
   603 		 *
       
   604 		 * @global wpdb $wpdb WordPress database abstraction object.
       
   605 		 *
       
   606 		 * @param string $sql The SELECT FOUND_ROWS() query for the current WP_User_Query.
       
   607 		 */
       
   608 		if ( isset( $qv['count_total'] ) && $qv['count_total'] )
       
   609 			$this->total_users = (int) $wpdb->get_var( apply_filters( 'found_users_query', 'SELECT FOUND_ROWS()' ) );
       
   610 
       
   611 		if ( !$this->results )
       
   612 			return;
       
   613 
       
   614 		if ( 'all_with_meta' == $qv['fields'] ) {
       
   615 			cache_users( $this->results );
       
   616 
       
   617 			$r = array();
       
   618 			foreach ( $this->results as $userid )
       
   619 				$r[ $userid ] = new WP_User( $userid, '', $qv['blog_id'] );
       
   620 
       
   621 			$this->results = $r;
       
   622 		} elseif ( 'all' == $qv['fields'] ) {
       
   623 			foreach ( $this->results as $key => $user ) {
       
   624 				$this->results[ $key ] = new WP_User( $user, '', $qv['blog_id'] );
       
   625 			}
       
   626 		}
       
   627 	}
       
   628 
       
   629 	/**
       
   630 	 * Retrieve query variable.
       
   631 	 *
       
   632 	 * @since 3.5.0
       
   633 	 *
       
   634 	 * @param string $query_var Query variable key.
       
   635 	 * @return mixed
       
   636 	 */
       
   637 	public function get( $query_var ) {
       
   638 		if ( isset( $this->query_vars[$query_var] ) )
       
   639 			return $this->query_vars[$query_var];
       
   640 
       
   641 		return null;
       
   642 	}
       
   643 
       
   644 	/**
       
   645 	 * Set query variable.
       
   646 	 *
       
   647 	 * @since 3.5.0
       
   648 	 *
       
   649 	 * @param string $query_var Query variable key.
       
   650 	 * @param mixed $value Query variable value.
       
   651 	 */
       
   652 	public function set( $query_var, $value ) {
       
   653 		$this->query_vars[$query_var] = $value;
       
   654 	}
       
   655 
       
   656 	/**
       
   657 	 * Used internally to generate an SQL string for searching across multiple columns
       
   658 	 *
       
   659 	 * @since 3.1.0
       
   660 	 *
       
   661 	 * @global wpdb $wpdb WordPress database abstraction object.
       
   662 	 *
       
   663 	 * @param string $string
       
   664 	 * @param array  $cols
       
   665 	 * @param bool   $wild   Whether to allow wildcard searches. Default is false for Network Admin, true for single site.
       
   666 	 *                       Single site allows leading and trailing wildcards, Network Admin only trailing.
       
   667 	 * @return string
       
   668 	 */
       
   669 	protected function get_search_sql( $string, $cols, $wild = false ) {
       
   670 		global $wpdb;
       
   671 
       
   672 		$searches = array();
       
   673 		$leading_wild = ( 'leading' == $wild || 'both' == $wild ) ? '%' : '';
       
   674 		$trailing_wild = ( 'trailing' == $wild || 'both' == $wild ) ? '%' : '';
       
   675 		$like = $leading_wild . $wpdb->esc_like( $string ) . $trailing_wild;
       
   676 
       
   677 		foreach ( $cols as $col ) {
       
   678 			if ( 'ID' == $col ) {
       
   679 				$searches[] = $wpdb->prepare( "$col = %s", $string );
       
   680 			} else {
       
   681 				$searches[] = $wpdb->prepare( "$col LIKE %s", $like );
       
   682 			}
       
   683 		}
       
   684 
       
   685 		return ' AND (' . implode(' OR ', $searches) . ')';
       
   686 	}
       
   687 
       
   688 	/**
       
   689 	 * Return the list of users.
       
   690 	 *
       
   691 	 * @since 3.1.0
       
   692 	 *
       
   693 	 * @return array Array of results.
       
   694 	 */
       
   695 	public function get_results() {
       
   696 		return $this->results;
       
   697 	}
       
   698 
       
   699 	/**
       
   700 	 * Return the total number of users for the current query.
       
   701 	 *
       
   702 	 * @since 3.1.0
       
   703 	 *
       
   704 	 * @return int Number of total users.
       
   705 	 */
       
   706 	public function get_total() {
       
   707 		return $this->total_users;
       
   708 	}
       
   709 
       
   710 	/**
       
   711 	 * Parse and sanitize 'orderby' keys passed to the user query.
       
   712 	 *
       
   713 	 * @since 4.2.0
       
   714 	 *
       
   715 	 * @global wpdb $wpdb WordPress database abstraction object.
       
   716 	 *
       
   717 	 * @param string $orderby Alias for the field to order by.
       
   718 	 * @return string Value to used in the ORDER clause, if `$orderby` is valid.
       
   719 	 */
       
   720 	protected function parse_orderby( $orderby ) {
       
   721 		global $wpdb;
       
   722 
       
   723 		$meta_query_clauses = $this->meta_query->get_clauses();
       
   724 
       
   725 		$_orderby = '';
       
   726 		if ( in_array( $orderby, array( 'login', 'nicename', 'email', 'url', 'registered' ) ) ) {
       
   727 			$_orderby = 'user_' . $orderby;
       
   728 		} elseif ( in_array( $orderby, array( 'user_login', 'user_nicename', 'user_email', 'user_url', 'user_registered' ) ) ) {
       
   729 			$_orderby = $orderby;
       
   730 		} elseif ( 'name' == $orderby || 'display_name' == $orderby ) {
       
   731 			$_orderby = 'display_name';
       
   732 		} elseif ( 'post_count' == $orderby ) {
       
   733 			// todo: avoid the JOIN
       
   734 			$where = get_posts_by_author_sql( 'post' );
       
   735 			$this->query_from .= " LEFT OUTER JOIN (
       
   736 				SELECT post_author, COUNT(*) as post_count
       
   737 				FROM $wpdb->posts
       
   738 				$where
       
   739 				GROUP BY post_author
       
   740 			) p ON ({$wpdb->users}.ID = p.post_author)
       
   741 			";
       
   742 			$_orderby = 'post_count';
       
   743 		} elseif ( 'ID' == $orderby || 'id' == $orderby ) {
       
   744 			$_orderby = 'ID';
       
   745 		} elseif ( 'meta_value' == $orderby || $this->get( 'meta_key' ) == $orderby ) {
       
   746 			$_orderby = "$wpdb->usermeta.meta_value";
       
   747 		} elseif ( 'meta_value_num' == $orderby ) {
       
   748 			$_orderby = "$wpdb->usermeta.meta_value+0";
       
   749 		} elseif ( 'include' === $orderby && ! empty( $this->query_vars['include'] ) ) {
       
   750 			$include = wp_parse_id_list( $this->query_vars['include'] );
       
   751 			$include_sql = implode( ',', $include );
       
   752 			$_orderby = "FIELD( $wpdb->users.ID, $include_sql )";
       
   753 		} elseif ( 'nicename__in' === $orderby ) {
       
   754 			$sanitized_nicename__in = array_map( 'esc_sql', $this->query_vars['nicename__in'] );
       
   755 			$nicename__in = implode( "','", $sanitized_nicename__in );
       
   756 			$_orderby = "FIELD( user_nicename, '$nicename__in' )";
       
   757 		} elseif ( 'login__in' === $orderby ) {
       
   758 			$sanitized_login__in = array_map( 'esc_sql', $this->query_vars['login__in'] );
       
   759 			$login__in = implode( "','", $sanitized_login__in );
       
   760 			$_orderby = "FIELD( user_login, '$login__in' )";
       
   761 		} elseif ( isset( $meta_query_clauses[ $orderby ] ) ) {
       
   762 			$meta_clause = $meta_query_clauses[ $orderby ];
       
   763 			$_orderby = sprintf( "CAST(%s.meta_value AS %s)", esc_sql( $meta_clause['alias'] ), esc_sql( $meta_clause['cast'] ) );
       
   764 		}
       
   765 
       
   766 		return $_orderby;
       
   767 	}
       
   768 
       
   769 	/**
       
   770 	 * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
       
   771 	 *
       
   772 	 * @since 4.2.0
       
   773 	 *
       
   774 	 * @param string $order The 'order' query variable.
       
   775 	 * @return string The sanitized 'order' query variable.
       
   776 	 */
       
   777 	protected function parse_order( $order ) {
       
   778 		if ( ! is_string( $order ) || empty( $order ) ) {
       
   779 			return 'DESC';
       
   780 		}
       
   781 
       
   782 		if ( 'ASC' === strtoupper( $order ) ) {
       
   783 			return 'ASC';
       
   784 		} else {
       
   785 			return 'DESC';
       
   786 		}
       
   787 	}
       
   788 
       
   789 	/**
       
   790 	 * Make private properties readable for backward compatibility.
       
   791 	 *
       
   792 	 * @since 4.0.0
       
   793 	 *
       
   794 	 * @param string $name Property to get.
       
   795 	 * @return mixed Property.
       
   796 	 */
       
   797 	public function __get( $name ) {
       
   798 		if ( in_array( $name, $this->compat_fields ) ) {
       
   799 			return $this->$name;
       
   800 		}
       
   801 	}
       
   802 
       
   803 	/**
       
   804 	 * Make private properties settable for backward compatibility.
       
   805 	 *
       
   806 	 * @since 4.0.0
       
   807 	 *
       
   808 	 * @param string $name  Property to check if set.
       
   809 	 * @param mixed  $value Property value.
       
   810 	 * @return mixed Newly-set property.
       
   811 	 */
       
   812 	public function __set( $name, $value ) {
       
   813 		if ( in_array( $name, $this->compat_fields ) ) {
       
   814 			return $this->$name = $value;
       
   815 		}
       
   816 	}
       
   817 
       
   818 	/**
       
   819 	 * Make private properties checkable for backward compatibility.
       
   820 	 *
       
   821 	 * @since 4.0.0
       
   822 	 *
       
   823 	 * @param string $name Property to check if set.
       
   824 	 * @return bool Whether the property is set.
       
   825 	 */
       
   826 	public function __isset( $name ) {
       
   827 		if ( in_array( $name, $this->compat_fields ) ) {
       
   828 			return isset( $this->$name );
       
   829 		}
       
   830 	}
       
   831 
       
   832 	/**
       
   833 	 * Make private properties un-settable for backward compatibility.
       
   834 	 *
       
   835 	 * @since 4.0.0
       
   836 	 *
       
   837 	 * @param string $name Property to unset.
       
   838 	 */
       
   839 	public function __unset( $name ) {
       
   840 		if ( in_array( $name, $this->compat_fields ) ) {
       
   841 			unset( $this->$name );
       
   842 		}
       
   843 	}
       
   844 
       
   845 	/**
       
   846 	 * Make private/protected methods readable for backward compatibility.
       
   847 	 *
       
   848 	 * @since 4.0.0
       
   849 	 *
       
   850 	 * @param callable $name      Method to call.
       
   851 	 * @param array    $arguments Arguments to pass when calling.
       
   852 	 * @return mixed Return value of the callback, false otherwise.
       
   853 	 */
       
   854 	public function __call( $name, $arguments ) {
       
   855 		if ( 'get_search_sql' === $name ) {
       
   856 			return call_user_func_array( array( $this, $name ), $arguments );
       
   857 		}
       
   858 		return false;
       
   859 	}
       
   860 }