You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

879 lines
29 KiB

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