/bp-core/classes/class-bp-user-query.php

  1. <?php 
  2. /** 
  3. * Core component classes. 
  4. * 
  5. * @package BuddyPress 
  6. * @subpackage Core 
  7. * @since 1.7.0 
  8. */ 
  9.  
  10. // Exit if accessed directly. 
  11. defined( 'ABSPATH' ) || exit; 
  12.  
  13. /** 
  14. * BuddyPress User Query class. 
  15. * 
  16. * Used for querying users in a BuddyPress context, in situations where WP_User_Query won't do the trick: 
  17. * Member directories, the Friends component, etc. 
  18. * 
  19. * @since 1.7.0 
  20. * 
  21. * @param array $query { 
  22. * Query arguments. All items are optional. 
  23. * @type string $type Determines sort order. Select from 'newest', 'active', 'online',  
  24. * 'random', 'popular', 'alphabetical'. Default: 'newest'. 
  25. * @type int $per_page Number of results to return. Default: 0 (no limit). 
  26. * @type int $page Page offset (together with $per_page). Default: 1. 
  27. * @type int $user_id ID of a user. If present, and if the friends component is activated,  
  28. * results will be limited to the friends of that user. Default: 0. 
  29. * @type string|bool $search_terms Terms to search by. Search happens across xprofile fields. Requires 
  30. * XProfile component. Default: false. 
  31. * @type string $search_wildcard When searching with $search_terms, set where wildcards around the 
  32. * term should be positioned. Accepts 'both', 'left', 'right'. 
  33. * Default: 'both'. 
  34. * @type array|string|bool $include An array or comma-separated list of user IDs to which query should 
  35. * be limited. Default: false. 
  36. * @type array|string|bool $exclude An array or comma-separated list of user IDs that will be excluded 
  37. * from query results. Default: false. 
  38. * @type array|string|bool $user_ids An array or comma-separated list of IDs corresponding to the users 
  39. * that should be returned. When this parameter is passed, it will 
  40. * override all others; BP User objects will be constructed using these 
  41. * IDs only. Default: false. 
  42. * @type array|string $member_type Array or comma-separated list of member types to limit results to. 
  43. * @type array|string $member_type__in Array or comma-separated list of member types to limit results to. 
  44. * @type array|string $member_type__not_in Array or comma-separated list of member types that will be 
  45. * excluded from results. 
  46. * @type string|bool $meta_key Limit results to users that have usermeta associated with this meta_key. 
  47. * Usually used with $meta_value. Default: false. 
  48. * @type string|bool $meta_value When used with $meta_key, limits results to users whose usermeta value 
  49. * associated with $meta_key matches $meta_value. Default: false. 
  50. * @type array $xprofile_query Filter results by xprofile data. Requires the xprofile component. 
  51. * See {@see BP_XProfile_Query} for details. 
  52. * @type bool $populate_extras True if you want to fetch extra metadata 
  53. * about returned users, such as total group and friend counts. 
  54. * @type string $count_total Determines how BP_User_Query will do a count of total users matching 
  55. * the other filter criteria. Default value is 'count_query', which 
  56. * does a separate SELECT COUNT query to determine the total. 
  57. * 'sql_count_found_rows' uses SQL_COUNT_FOUND_ROWS and 
  58. * SELECT FOUND_ROWS(). Pass an empty string to skip the total user 
  59. * count query. 
  60. * } 
  61. */ 
  62. class BP_User_Query { 
  63.  
  64. /** Variables *************************************************************/ 
  65.  
  66. /** 
  67. * Unaltered params as passed to the constructor. 
  68. * 
  69. * @since 1.8.0 
  70. * @var array 
  71. */ 
  72. public $query_vars_raw = array(); 
  73.  
  74. /** 
  75. * Array of variables to query with. 
  76. * 
  77. * @since 1.7.0 
  78. * @var array 
  79. */ 
  80. public $query_vars = array(); 
  81.  
  82. /** 
  83. * List of found users and their respective data. 
  84. * 
  85. * @since 1.7.0 
  86. * @var array 
  87. */ 
  88. public $results = array(); 
  89.  
  90. /** 
  91. * Total number of found users for the current query. 
  92. * 
  93. * @since 1.7.0 
  94. * @var int 
  95. */ 
  96. public $total_users = 0; 
  97.  
  98. /** 
  99. * List of found user IDs. 
  100. * 
  101. * @since 1.7.0 
  102. * @var array 
  103. */ 
  104. public $user_ids = array(); 
  105.  
  106. /** 
  107. * SQL clauses for the user ID query. 
  108. * 
  109. * @since 1.7.0 
  110. * @var array 
  111. */ 
  112. public $uid_clauses = array(); 
  113.  
  114. /** 
  115. * SQL table where the user ID is being fetched from. 
  116. * 
  117. * @since 2.2.0 
  118. * @var string 
  119. */ 
  120. public $uid_table = ''; 
  121.  
  122. /** 
  123. * SQL database column name to order by. 
  124. * 
  125. * @since 1.7.0 
  126. * @var string 
  127. */ 
  128. public $uid_name = ''; 
  129.  
  130. /** 
  131. * Standard response when the query should not return any rows. 
  132. * 
  133. * @since 1.7.0 
  134. * @var string 
  135. */ 
  136. protected $no_results = array( 'join' => '', 'where' => '0 = 1' ); 
  137.  
  138.  
  139. /** Methods ***************************************************************/ 
  140.  
  141. /** 
  142. * Constructor. 
  143. * 
  144. * @since 1.7.0 
  145. * 
  146. * @param string|array|null $query See {@link BP_User_Query}. 
  147. */ 
  148. public function __construct( $query = null ) { 
  149.  
  150. // Store the raw query vars for later access. 
  151. $this->query_vars_raw = $query; 
  152.  
  153. // Allow extending classes to register action/filter hooks. 
  154. $this->setup_hooks(); 
  155.  
  156. if ( ! empty( $this->query_vars_raw ) ) { 
  157. $this->query_vars = wp_parse_args( $this->query_vars_raw, array( 
  158. 'type' => 'newest',  
  159. 'per_page' => 0,  
  160. 'page' => 1,  
  161. 'user_id' => 0,  
  162. 'search_terms' => false,  
  163. 'search_wildcard' => 'both',  
  164. 'include' => false,  
  165. 'exclude' => false,  
  166. 'user_ids' => false,  
  167. 'member_type' => '',  
  168. 'member_type__in' => '',  
  169. 'member_type__not_in' => '',  
  170. 'meta_key' => false,  
  171. 'meta_value' => false,  
  172. 'xprofile_query' => false,  
  173. 'populate_extras' => true,  
  174. 'count_total' => 'count_query' 
  175. ) ); 
  176.  
  177. /** 
  178. * Fires before the construction of the BP_User_Query query. 
  179. * 
  180. * @since 1.7.0 
  181. * 
  182. * @param BP_User_Query $this Current instance of the BP_User_Query. Passed by reference. 
  183. */ 
  184. do_action_ref_array( 'bp_pre_user_query_construct', array( &$this ) ); 
  185.  
  186. // Get user ids 
  187. // If the user_ids param is present, we skip the query. 
  188. if ( false !== $this->query_vars['user_ids'] ) { 
  189. $this->user_ids = wp_parse_id_list( $this->query_vars['user_ids'] ); 
  190. } else { 
  191. $this->prepare_user_ids_query(); 
  192. $this->do_user_ids_query(); 
  193.  
  194. // Bail if no user IDs were found. 
  195. if ( empty( $this->user_ids ) ) { 
  196. return; 
  197.  
  198. // Fetch additional data. First, using WP_User_Query. 
  199. $this->do_wp_user_query(); 
  200.  
  201. // Get BuddyPress specific user data. 
  202. $this->populate_extras(); 
  203.  
  204. /** 
  205. * Allow extending classes to set up action/filter hooks. 
  206. * 
  207. * When extending BP_User_Query, you may need to use some of its 
  208. * internal hooks to modify the output. It's not convenient to call 
  209. * add_action() or add_filter() in your class constructor, because 
  210. * BP_User_Query::__construct() contains a fair amount of logic that 
  211. * you may not want to override in your class. Define this method in 
  212. * your own class if you need a place where your extending class can 
  213. * add its hooks early in the query-building process. See 
  214. * {@link BP_Group_Member_Query::setup_hooks()} for an example. 
  215. * 
  216. * @since 1.8.0 
  217. */ 
  218. public function setup_hooks() {} 
  219.  
  220. /** 
  221. * Prepare the query for user_ids. 
  222. * 
  223. * @since 1.7.0 
  224. */ 
  225. public function prepare_user_ids_query() { 
  226. global $wpdb; 
  227.  
  228. $bp = buddypress(); 
  229.  
  230. // Default query variables used here. 
  231. $type = ''; 
  232. $per_page = 0; 
  233. $page = 1; 
  234. $user_id = 0; 
  235. $include = false; 
  236. $search_terms = false; 
  237. $exclude = false; 
  238. $meta_key = false; 
  239. $meta_value = false; 
  240.  
  241. extract( $this->query_vars ); 
  242.  
  243. // Setup the main SQL query container. 
  244. $sql = array( 
  245. 'select' => '',  
  246. 'where' => array('1=1'),  
  247. 'orderby' => '',  
  248. 'order' => '',  
  249. 'limit' => '' 
  250. ); 
  251.  
  252. /** TYPE **************************************************************/ 
  253.  
  254. // Determines the sort order, which means it also determines where the 
  255. // user IDs are drawn from (the SELECT and WHERE statements). 
  256. switch ( $type ) { 
  257.  
  258. // 'online' query happens against the last_activity usermeta key 
  259. // Filter 'bp_user_query_online_interval' to modify the 
  260. // number of minutes used as an interval. 
  261. case 'online' : 
  262. $this->uid_name = 'user_id'; 
  263. $this->uid_table = $bp->members->table_name_last_activity; 
  264. $sql['select'] = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u"; 
  265. $sql['where'][] = $wpdb->prepare( "u.component = %s AND u.type = 'last_activity'", buddypress()->members->id ); 
  266.  
  267. /** 
  268. * Filters the threshold for activity timestamp minutes since to indicate online status. 
  269. * 
  270. * @since 1.8.0 
  271. * 
  272. * @param int $value Amount of minutes for threshold. Default 15. 
  273. */ 
  274. $sql['where'][] = $wpdb->prepare( "u.date_recorded >= DATE_SUB( UTC_TIMESTAMP(), INTERVAL %d MINUTE )", apply_filters( 'bp_user_query_online_interval', 15 ) ); 
  275. $sql['orderby'] = "ORDER BY u.date_recorded"; 
  276. $sql['order'] = "DESC"; 
  277.  
  278. break; 
  279.  
  280. // 'active', 'newest', and 'random' queries 
  281. // all happen against the last_activity usermeta key. 
  282. case 'active' : 
  283. case 'newest' : 
  284. case 'random' : 
  285. $this->uid_name = 'user_id'; 
  286. $this->uid_table = $bp->members->table_name_last_activity; 
  287. $sql['select'] = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u"; 
  288. $sql['where'][] = $wpdb->prepare( "u.component = %s AND u.type = 'last_activity'", buddypress()->members->id ); 
  289.  
  290. if ( 'newest' == $type ) { 
  291. $sql['orderby'] = "ORDER BY u.user_id"; 
  292. $sql['order'] = "DESC"; 
  293. } elseif ( 'random' == $type ) { 
  294. $sql['orderby'] = "ORDER BY rand()"; 
  295. } else { 
  296. $sql['orderby'] = "ORDER BY u.date_recorded"; 
  297. $sql['order'] = "DESC"; 
  298.  
  299. break; 
  300.  
  301. // 'popular' sorts by the 'total_friend_count' usermeta. 
  302. case 'popular' : 
  303. $this->uid_name = 'user_id'; 
  304. $this->uid_table = $wpdb->usermeta; 
  305. $sql['select'] = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u"; 
  306. $sql['where'][] = $wpdb->prepare( "u.meta_key = %s", bp_get_user_meta_key( 'total_friend_count' ) ); 
  307. $sql['orderby'] = "ORDER BY CONVERT(u.meta_value, SIGNED)"; 
  308. $sql['order'] = "DESC"; 
  309.  
  310. break; 
  311.  
  312. // 'alphabetical' sorts depend on the xprofile setup. 
  313. case 'alphabetical' : 
  314.  
  315. // We prefer to do alphabetical sorts against the display_name field 
  316. // of wp_users, because the table is smaller and better indexed. We 
  317. // can do so if xprofile sync is enabled, or if xprofile is inactive. 
  318. // 
  319. // @todo remove need for bp_is_active() check. 
  320. if ( ! bp_disable_profile_sync() || ! bp_is_active( 'xprofile' ) ) { 
  321. $this->uid_name = 'ID'; 
  322. $this->uid_table = $wpdb->users; 
  323. $sql['select'] = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u"; 
  324. $sql['orderby'] = "ORDER BY u.display_name"; 
  325. $sql['order'] = "ASC"; 
  326.  
  327. // When profile sync is disabled, alphabetical sorts must happen against 
  328. // the xprofile table. 
  329. } else { 
  330. $this->uid_name = 'user_id'; 
  331. $this->uid_table = $bp->profile->table_name_data; 
  332. $sql['select'] = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u"; 
  333. $sql['where'][] = $wpdb->prepare( "u.field_id = %d", bp_xprofile_fullname_field_id() ); 
  334. $sql['orderby'] = "ORDER BY u.value"; 
  335. $sql['order'] = "ASC"; 
  336.  
  337. // Alphabetical queries ignore last_activity, while BP uses last_activity 
  338. // to infer spam/deleted/non-activated users. To ensure that these users 
  339. // are filtered out, we add an appropriate sub-query. 
  340. $sql['where'][] = "u.{$this->uid_name} IN ( SELECT ID FROM {$wpdb->users} WHERE " . bp_core_get_status_sql( '' ) . " )"; 
  341.  
  342. break; 
  343.  
  344. // Any other 'type' falls through. 
  345. default : 
  346. $this->uid_name = 'ID'; 
  347. $this->uid_table = $wpdb->users; 
  348. $sql['select'] = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u"; 
  349.  
  350. // In this case, we assume that a plugin is 
  351. // handling order, so we leave those clauses 
  352. // blank. 
  353. break; 
  354.  
  355. /** WHERE *************************************************************/ 
  356.  
  357. // 'include' - User ids to include in the results. 
  358. $include = false !== $include ? wp_parse_id_list( $include ) : array(); 
  359. $include_ids = $this->get_include_ids( $include ); 
  360.  
  361. // An array containing nothing but 0 should always fail. 
  362. if ( 1 === count( $include_ids ) && 0 == reset( $include_ids ) ) { 
  363. $sql['where'][] = $this->no_results['where']; 
  364. } elseif ( ! empty( $include_ids ) ) { 
  365. $include_ids = implode( ', ', wp_parse_id_list( $include_ids ) ); 
  366. $sql['where'][] = "u.{$this->uid_name} IN ({$include_ids})"; 
  367.  
  368. // 'exclude' - User ids to exclude from the results. 
  369. if ( false !== $exclude ) { 
  370. $exclude_ids = implode( ', ', wp_parse_id_list( $exclude ) ); 
  371. $sql['where'][] = "u.{$this->uid_name} NOT IN ({$exclude_ids})"; 
  372.  
  373. // 'user_id' - When a user id is passed, limit to the friends of the user 
  374. // @todo remove need for bp_is_active() check. 
  375. if ( ! empty( $user_id ) && bp_is_active( 'friends' ) ) { 
  376. $friend_ids = friends_get_friend_user_ids( $user_id ); 
  377. $friend_ids = implode( ', ', wp_parse_id_list( $friend_ids ) ); 
  378.  
  379. if ( ! empty( $friend_ids ) ) { 
  380. $sql['where'][] = "u.{$this->uid_name} IN ({$friend_ids})"; 
  381.  
  382. // If the user has no friends, the query should always 
  383. // return no users. 
  384. } else { 
  385. $sql['where'][] = $this->no_results['where']; 
  386.  
  387. /** Search Terms ******************************************************/ 
  388.  
  389. // 'search_terms' searches user_login and user_nicename 
  390. // xprofile field matches happen in bp_xprofile_bp_user_query_search(). 
  391. if ( false !== $search_terms ) { 
  392. $search_terms = bp_esc_like( wp_kses_normalize_entities( $search_terms ) ); 
  393.  
  394. if ( $search_wildcard === 'left' ) { 
  395. $search_terms_nospace = '%' . $search_terms; 
  396. $search_terms_space = '%' . $search_terms . ' %'; 
  397. } elseif ( $search_wildcard === 'right' ) { 
  398. $search_terms_nospace = $search_terms . '%'; 
  399. $search_terms_space = '% ' . $search_terms . '%'; 
  400. } else { 
  401. $search_terms_nospace = '%' . $search_terms . '%'; 
  402. $search_terms_space = '%' . $search_terms . '%'; 
  403.  
  404. $sql['where']['search'] = $wpdb->prepare( 
  405. "u.{$this->uid_name} IN ( SELECT ID FROM {$wpdb->users} WHERE ( user_login LIKE %s OR user_login LIKE %s OR user_nicename LIKE %s OR user_nicename LIKE %s ) )",  
  406. $search_terms_nospace,  
  407. $search_terms_space,  
  408. $search_terms_nospace,  
  409. $search_terms_space 
  410. ); 
  411.  
  412. // Only use $member_type__in if $member_type is not set. 
  413. if ( empty( $member_type ) && ! empty( $member_type__in ) ) { 
  414. $member_type = $member_type__in; 
  415.  
  416. // Member types to exclude. Note that this takes precedence over inclusions. 
  417. if ( ! empty( $member_type__not_in ) ) { 
  418. $member_type_clause = $this->get_sql_clause_for_member_types( $member_type__not_in, 'NOT IN' ); 
  419.  
  420. // Member types to include. 
  421. } elseif ( ! empty( $member_type ) ) { 
  422. $member_type_clause = $this->get_sql_clause_for_member_types( $member_type, 'IN' ); 
  423.  
  424. if ( ! empty( $member_type_clause ) ) { 
  425. $sql['where']['member_type'] = $member_type_clause; 
  426.  
  427. // 'meta_key', 'meta_value' allow usermeta search 
  428. // To avoid global joins, do a separate query. 
  429. if ( false !== $meta_key ) { 
  430. $meta_sql = $wpdb->prepare( "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = %s", $meta_key ); 
  431.  
  432. if ( false !== $meta_value ) { 
  433. $meta_sql .= $wpdb->prepare( " AND meta_value = %s", $meta_value ); 
  434.  
  435. $found_user_ids = $wpdb->get_col( $meta_sql ); 
  436.  
  437. if ( ! empty( $found_user_ids ) ) { 
  438. $sql['where'][] = "u.{$this->uid_name} IN (" . implode( ', ', wp_parse_id_list( $found_user_ids ) ) . ")"; 
  439. } else { 
  440. $sql['where'][] = '1 = 0'; 
  441.  
  442. // 'per_page', 'page' - handles LIMIT. 
  443. if ( !empty( $per_page ) && !empty( $page ) ) { 
  444. $sql['limit'] = $wpdb->prepare( "LIMIT %d, %d", intval( ( $page - 1 ) * $per_page ), intval( $per_page ) ); 
  445. } else { 
  446. $sql['limit'] = ''; 
  447.  
  448. /** 
  449. * Filters the clauses for the user query. 
  450. * 
  451. * @since 2.0.0 
  452. * 
  453. * @param array $sql Array of SQL clauses to be used in the query. 
  454. * @param BP_User_Query $this Current BP_User_Query instance. 
  455. */ 
  456. $sql = apply_filters_ref_array( 'bp_user_query_uid_clauses', array( $sql, &$this ) ); 
  457.  
  458. // Assemble the query chunks. 
  459. $this->uid_clauses['select'] = $sql['select']; 
  460. $this->uid_clauses['where'] = ! empty( $sql['where'] ) ? 'WHERE ' . implode( ' AND ', $sql['where'] ) : ''; 
  461. $this->uid_clauses['orderby'] = $sql['orderby']; 
  462. $this->uid_clauses['order'] = $sql['order']; 
  463. $this->uid_clauses['limit'] = $sql['limit']; 
  464.  
  465. /** 
  466. * Fires before the BP_User_Query query is made. 
  467. * 
  468. * @since 1.7.0 
  469. * 
  470. * @param BP_User_Query $this Current BP_User_Query instance. Passed by reference. 
  471. */ 
  472. do_action_ref_array( 'bp_pre_user_query', array( &$this ) ); 
  473.  
  474. /** 
  475. * Query for IDs of users that match the query parameters. 
  476. * 
  477. * Perform a database query to specifically get only user IDs, using 
  478. * existing query variables set previously in the constructor. 
  479. * 
  480. * Also used to quickly perform user total counts. 
  481. * 
  482. * @since 1.7.0 
  483. */ 
  484. public function do_user_ids_query() { 
  485. global $wpdb; 
  486.  
  487. // If counting using SQL_CALC_FOUND_ROWS, set it up here. 
  488. if ( 'sql_calc_found_rows' == $this->query_vars['count_total'] ) { 
  489. $this->uid_clauses['select'] = str_replace( 'SELECT', 'SELECT SQL_CALC_FOUND_ROWS', $this->uid_clauses['select'] ); 
  490.  
  491. // Get the specific user ids. 
  492. $this->user_ids = $wpdb->get_col( "{$this->uid_clauses['select']} {$this->uid_clauses['where']} {$this->uid_clauses['orderby']} {$this->uid_clauses['order']} {$this->uid_clauses['limit']}" ); 
  493.  
  494. // Get the total user count. 
  495. if ( 'sql_calc_found_rows' == $this->query_vars['count_total'] ) { 
  496.  
  497. /** 
  498. * Filters the found user SQL statements before query. 
  499. * 
  500. * If "sql_calc_found_rows" is the provided count_total query var 
  501. * then the value will be "SELECT FOUND_ROWS()". Otherwise it will 
  502. * use a "SELECT COUNT()" query statement. 
  503. * 
  504. * @since 1.7.0 
  505. * 
  506. * @param string $value SQL statement to select FOUND_ROWS(). 
  507. * @param BP_User_Query $this Current BP_User_Query instance. 
  508. */ 
  509. $this->total_users = $wpdb->get_var( apply_filters( 'bp_found_user_query', "SELECT FOUND_ROWS()", $this ) ); 
  510. } elseif ( 'count_query' == $this->query_vars['count_total'] ) { 
  511. $count_select = preg_replace( '/^SELECT.*?FROM (\S+) u/', "SELECT COUNT(u.{$this->uid_name}) FROM $1 u", $this->uid_clauses['select'] ); 
  512.  
  513. /** This filter is documented in bp-core/classes/class-bp-user-query.php */ 
  514. $this->total_users = $wpdb->get_var( apply_filters( 'bp_found_user_query', "{$count_select} {$this->uid_clauses['where']}", $this ) ); 
  515.  
  516. /** 
  517. * Use WP_User_Query() to pull data for the user IDs retrieved in the main query. 
  518. * 
  519. * @since 1.7.0 
  520. */ 
  521. public function do_wp_user_query() { 
  522. $fields = array( 'ID', 'user_login', 'user_pass', 'user_nicename', 'user_email', 'user_url', 'user_registered', 'user_activation_key', 'user_status', 'display_name' ); 
  523.  
  524. if ( is_multisite() ) { 
  525. $fields[] = 'spam'; 
  526. $fields[] = 'deleted'; 
  527.  
  528. /** 
  529. * Filters the WP User Query arguments before passing into the class. 
  530. * 
  531. * @since 1.7.0 
  532. * 
  533. * @param array $value Array of arguments for the user query. 
  534. * @param BP_User_Query $this Current BP_User_Query instance. 
  535. */ 
  536. $wp_user_query = new WP_User_Query( apply_filters( 'bp_wp_user_query_args', array( 
  537.  
  538. // Relevant. 
  539. 'fields' => $fields,  
  540. 'include' => $this->user_ids,  
  541.  
  542. // Overrides 
  543. 'blog_id' => 0, // BP does not require blog roles. 
  544. 'count_total' => false // We already have a count. 
  545.  
  546. ), $this ) ); 
  547.  
  548. // WP_User_Query doesn't cache the data it pulls from wp_users,  
  549. // and it does not give us a way to save queries by fetching 
  550. // only uncached users. However, BP does cache this data, so 
  551. // we set it here. 
  552. foreach ( $wp_user_query->results as $u ) { 
  553. wp_cache_set( 'bp_core_userdata_' . $u->ID, $u, 'bp' ); 
  554.  
  555. // We calculate total_users using a standalone query, except 
  556. // when a whitelist of user_ids is passed to the constructor. 
  557. // This clause covers the latter situation, and ensures that 
  558. // pagination works when querying by $user_ids. 
  559. if ( empty( $this->total_users ) ) { 
  560. $this->total_users = count( $wp_user_query->results ); 
  561.  
  562. // Reindex for easier matching. 
  563. $r = array(); 
  564. foreach ( $wp_user_query->results as $u ) { 
  565. $r[ $u->ID ] = $u; 
  566.  
  567. // Match up to the user ids from the main query. 
  568. foreach ( $this->user_ids as $key => $uid ) { 
  569. if ( isset( $r[ $uid ] ) ) { 
  570. $r[ $uid ]->ID = (int) $uid; 
  571. $r[ $uid ]->user_status = (int) $r[ $uid ]->user_status; 
  572.  
  573. $this->results[ $uid ] = $r[ $uid ]; 
  574.  
  575. // The BP template functions expect an 'id' 
  576. // (as opposed to 'ID') property. 
  577. $this->results[ $uid ]->id = (int) $uid; 
  578.  
  579. // Remove user ID from original user_ids property. 
  580. } else { 
  581. unset( $this->user_ids[ $key ] ); 
  582.  
  583. /** 
  584. * Fetch the IDs of users to put in the IN clause of the main query. 
  585. * 
  586. * By default, returns the value passed to it 
  587. * ($this->query_vars['include']). Having this abstracted into a 
  588. * standalone method means that extending classes can override the 
  589. * logic, parsing together their own user_id limits with the 'include' 
  590. * ids passed to the class constructor. See {@link BP_Group_Member_Query} 
  591. * for an example. 
  592. * 
  593. * @since 1.8.0 
  594. * 
  595. * @param array $include Sanitized array of user IDs, as passed to the 'include' 
  596. * parameter of the class constructor. 
  597. * @return array The list of users to which the main query should be 
  598. * limited. 
  599. */ 
  600. public function get_include_ids( $include = array() ) { 
  601. return $include; 
  602.  
  603. /** 
  604. * Perform a database query to populate any extra metadata we might need. 
  605. * 
  606. * Different components will hook into the 'bp_user_query_populate_extras' 
  607. * action to loop in the things they want. 
  608. * 
  609. * @since 1.7.0 
  610. * 
  611. * @global WPDB $wpdb Global WordPress database access object. 
  612. */ 
  613. public function populate_extras() { 
  614. global $wpdb; 
  615.  
  616. // Bail if no users. 
  617. if ( empty( $this->user_ids ) || empty( $this->results ) ) { 
  618. return; 
  619.  
  620. // Bail if the populate_extras flag is set to false 
  621. // In the case of the 'popular' sort type, we force 
  622. // populate_extras to true, because we need the friend counts. 
  623. if ( 'popular' == $this->query_vars['type'] ) { 
  624. $this->query_vars['populate_extras'] = 1; 
  625.  
  626. if ( ! (bool) $this->query_vars['populate_extras'] ) { 
  627. return; 
  628.  
  629. // Turn user ID's into a query-usable, comma separated value. 
  630. $user_ids_sql = implode( ', ', wp_parse_id_list( $this->user_ids ) ); 
  631.  
  632. /** 
  633. * Allows users to independently populate custom extras. 
  634. * 
  635. * Note that anything you add here should query using $user_ids_sql, to 
  636. * avoid running multiple queries per user in the loop. 
  637. * 
  638. * Two BuddyPress components currently do this: 
  639. * - XProfile: To override display names. 
  640. * - Friends: To set whether or not a user is the current users friend. 
  641. * 
  642. * @see bp_xprofile_filter_user_query_populate_extras() 
  643. * @see bp_friends_filter_user_query_populate_extras() 
  644. * 
  645. * @since 1.7.0 
  646. * 
  647. * @param BP_User_Query $this Current BP_User_Query instance. 
  648. * @param string $user_ids_sql Comma-separated string of user IDs. 
  649. */ 
  650. do_action_ref_array( 'bp_user_query_populate_extras', array( $this, $user_ids_sql ) ); 
  651.  
  652. // Fetch last_active data from the activity table. 
  653. $last_activities = BP_Core_User::get_last_activity( $this->user_ids ); 
  654.  
  655. // Set a last_activity value for each user, even if it's empty. 
  656. foreach ( $this->results as $user_id => $user ) { 
  657. $user_last_activity = isset( $last_activities[ $user_id ] ) ? $last_activities[ $user_id ]['date_recorded'] : ''; 
  658. $this->results[ $user_id ]->last_activity = $user_last_activity; 
  659.  
  660. // Fetch usermeta data 
  661. // We want the three following pieces of info from usermeta: 
  662. // - friend count 
  663. // - latest update. 
  664. $total_friend_count_key = bp_get_user_meta_key( 'total_friend_count' ); 
  665. $bp_latest_update_key = bp_get_user_meta_key( 'bp_latest_update' ); 
  666.  
  667. // Total_friend_count must be set for each user, even if its 
  668. // value is 0. 
  669. foreach ( $this->results as $uindex => $user ) { 
  670. $this->results[$uindex]->total_friend_count = 0; 
  671.  
  672. // Create, prepare, and run the separate usermeta query. 
  673. $user_metas = $wpdb->get_results( $wpdb->prepare( "SELECT user_id, meta_key, meta_value FROM {$wpdb->usermeta} WHERE meta_key IN (%s, %s) AND user_id IN ({$user_ids_sql})", $total_friend_count_key, $bp_latest_update_key ) ); 
  674.  
  675. // The $members_template global expects the index key to be different 
  676. // from the meta_key in some cases, so we rejig things here. 
  677. foreach ( $user_metas as $user_meta ) { 
  678. switch ( $user_meta->meta_key ) { 
  679. case $total_friend_count_key : 
  680. $key = 'total_friend_count'; 
  681. break; 
  682.  
  683. case $bp_latest_update_key : 
  684. $key = 'latest_update'; 
  685. break; 
  686.  
  687. if ( isset( $this->results[ $user_meta->user_id ] ) ) { 
  688. $this->results[ $user_meta->user_id ]->{$key} = $user_meta->meta_value; 
  689.  
  690. // When meta_key or meta_value have been passed to the query,  
  691. // fetch the resulting values for use in the template functions. 
  692. if ( ! empty( $this->query_vars['meta_key'] ) ) { 
  693. $meta_sql = array( 
  694. 'select' => "SELECT user_id, meta_key, meta_value",  
  695. 'from' => "FROM $wpdb->usermeta",  
  696. 'where' => $wpdb->prepare( "WHERE meta_key = %s", $this->query_vars['meta_key'] ) 
  697. ); 
  698.  
  699. if ( false !== $this->query_vars['meta_value'] ) { 
  700. $meta_sql['where'] .= $wpdb->prepare( " AND meta_value = %s", $this->query_vars['meta_value'] ); 
  701.  
  702. $metas = $wpdb->get_results( "{$meta_sql['select']} {$meta_sql['from']} {$meta_sql['where']}" ); 
  703.  
  704. if ( ! empty( $metas ) ) { 
  705. foreach ( $metas as $meta ) { 
  706. if ( isset( $this->results[ $meta->user_id ] ) ) { 
  707. $this->results[ $meta->user_id ]->meta_key = $meta->meta_key; 
  708.  
  709. if ( ! empty( $meta->meta_value ) ) { 
  710. $this->results[ $meta->user_id ]->meta_value = $meta->meta_value; 
  711.  
  712. /** 
  713. * Get a SQL clause representing member_type include/exclusion. 
  714. * 
  715. * @since 2.4.0 
  716. * 
  717. * @param string|array $member_types Array or comma-separated list of member types. 
  718. * @param string $operator 'IN' or 'NOT IN'. 
  719. * @return string 
  720. */ 
  721. protected function get_sql_clause_for_member_types( $member_types, $operator ) { 
  722. global $wpdb; 
  723.  
  724. // Sanitize. 
  725. if ( 'NOT IN' !== $operator ) { 
  726. $operator = 'IN'; 
  727.  
  728. // Parse and sanitize types. 
  729. if ( ! is_array( $member_types ) ) { 
  730. $member_types = preg_split( '/[, \s+]/', $member_types ); 
  731.  
  732. $types = array(); 
  733. foreach ( $member_types as $mt ) { 
  734. if ( bp_get_member_type_object( $mt ) ) { 
  735. $types[] = $mt; 
  736.  
  737. $tax_query = new WP_Tax_Query( array( 
  738. array( 
  739. 'taxonomy' => bp_get_member_type_tax_name(),  
  740. 'field' => 'name',  
  741. 'operator' => $operator,  
  742. 'terms' => $types,  
  743. ),  
  744. ) ); 
  745.  
  746. // Switch to the root blog, where member type taxonomies live. 
  747. $site_id = bp_get_taxonomy_term_site_id( bp_get_member_type_tax_name() ); 
  748. $switched = false; 
  749. if ( $site_id !== get_current_blog_id() ) { 
  750. switch_to_blog( $site_id ); 
  751. $switched = true; 
  752.  
  753. $sql_clauses = $tax_query->get_sql( 'u', $this->uid_name ); 
  754.  
  755. $clause = ''; 
  756.  
  757. // The no_results clauses are the same between IN and NOT IN. 
  758. if ( false !== strpos( $sql_clauses['where'], '0 = 1' ) ) { 
  759. $clause = $this->no_results['where']; 
  760.  
  761. // The tax_query clause generated for NOT IN can be used almost as-is. We just trim the leading 'AND'. 
  762. } elseif ( 'NOT IN' === $operator ) { 
  763. $clause = preg_replace( '/^\s*AND\s*/', '', $sql_clauses['where'] ); 
  764.  
  765. // IN clauses must be converted to a subquery. 
  766. } elseif ( preg_match( '/' . $wpdb->term_relationships . '\.term_taxonomy_id IN \([0-9, ]+\)/', $sql_clauses['where'], $matches ) ) { 
  767. $clause = "u.{$this->uid_name} IN ( SELECT object_id FROM $wpdb->term_relationships WHERE {$matches[0]} )"; 
  768.  
  769. if ( $switched ) { 
  770. restore_current_blog(); 
  771.  
  772. return $clause; 
.