/bp-groups/classes/class-bp-group-member-query.php

  1. <?php 
  2. /** 
  3. * BuddyPress Groups Classes. 
  4. * 
  5. * @package BuddyPress 
  6. * @subpackage GroupsClasses 
  7. * @since 1.8.0 
  8. */ 
  9.  
  10. // Exit if accessed directly. 
  11. defined( 'ABSPATH' ) || exit; 
  12.  
  13. /** 
  14. * Query for the members of a group. 
  15. * 
  16. * Special notes about the group members data schema: 
  17. * - *Members* are entries with is_confirmed = 1. 
  18. * - *Pending requests* are entries with is_confirmed = 0 and inviter_id = 0. 
  19. * - *Pending and sent invitations* are entries with is_confirmed = 0 and 
  20. * inviter_id != 0 and invite_sent = 1. 
  21. * - *Pending and unsent invitations* are entries with is_confirmed = 0 and 
  22. * inviter_id != 0 and invite_sent = 0. 
  23. * - *Membership requests* are entries with is_confirmed = 0 and 
  24. * inviter_id = 0 (and invite_sent = 0). 
  25. * 
  26. * @since 1.8.0 
  27. * 
  28. * @param array $args { 
  29. * Array of arguments. Accepts all arguments from 
  30. * {@link BP_User_Query}, with the following additions: 
  31. * 
  32. * @type int $group_id ID of the group to limit results to. 
  33. * @type array $group_role Array of group roles to match ('member',  
  34. * 'mod', 'admin', 'banned'). 
  35. * Default: array( 'member' ). 
  36. * @type bool $is_confirmed Whether to limit to confirmed members. 
  37. * Default: true. 
  38. * @type string $type Sort order. Accepts any value supported by 
  39. * {@link BP_User_Query}, in addition to 'last_joined' 
  40. * and 'first_joined'. Default: 'last_joined'. 
  41. * } 
  42. */ 
  43. class BP_Group_Member_Query extends BP_User_Query { 
  44.  
  45. /** 
  46. * Array of group member ids, cached to prevent redundant lookups. 
  47. * 
  48. * @since 1.8.1 
  49. * @var null|array Null if not yet defined, otherwise an array of ints. 
  50. */ 
  51. protected $group_member_ids; 
  52.  
  53. /** 
  54. * Set up action hooks. 
  55. * 
  56. * @since 1.8.0 
  57. */ 
  58. public function setup_hooks() { 
  59. // Take this early opportunity to set the default 'type' param 
  60. // to 'last_joined', which will ensure that BP_User_Query 
  61. // trusts our order and does not try to apply its own. 
  62. if ( empty( $this->query_vars_raw['type'] ) ) { 
  63. $this->query_vars_raw['type'] = 'last_joined'; 
  64.  
  65. // Set the sort order. 
  66. add_action( 'bp_pre_user_query', array( $this, 'set_orderby' ) ); 
  67.  
  68. // Set up our populate_extras method. 
  69. add_action( 'bp_user_query_populate_extras', array( $this, 'populate_group_member_extras' ), 10, 2 ); 
  70.  
  71. /** 
  72. * Get a list of user_ids to include in the IN clause of the main query. 
  73. * 
  74. * Overrides BP_User_Query::get_include_ids(), adding our additional 
  75. * group-member logic. 
  76. * 
  77. * @since 1.8.0 
  78. * 
  79. * @param array $include Existing group IDs in the $include parameter,  
  80. * as calculated in BP_User_Query. 
  81. * @return array 
  82. */ 
  83. public function get_include_ids( $include = array() ) { 
  84. // The following args are specific to group member queries, and 
  85. // are not present in the query_vars of a normal BP_User_Query. 
  86. // We loop through to make sure that defaults are set (though 
  87. // values passed to the constructor will, as usual, override 
  88. // these defaults). 
  89. $this->query_vars = wp_parse_args( $this->query_vars, array( 
  90. 'group_id' => 0,  
  91. 'group_role' => array( 'member' ),  
  92. 'is_confirmed' => true,  
  93. 'invite_sent' => null,  
  94. 'inviter_id' => null,  
  95. 'type' => 'last_joined',  
  96. ) ); 
  97.  
  98. $group_member_ids = $this->get_group_member_ids(); 
  99.  
  100. // If the group member query returned no users, bail with an 
  101. // array that will guarantee no matches for BP_User_Query. 
  102. if ( empty( $group_member_ids ) ) { 
  103. return array( 0 ); 
  104.  
  105. if ( ! empty( $include ) ) { 
  106. $group_member_ids = array_intersect( $include, $group_member_ids ); 
  107.  
  108. return $group_member_ids; 
  109.  
  110. /** 
  111. * Get the members of the queried group. 
  112. * 
  113. * @since 1.8.0 
  114. * 
  115. * @return array $ids User IDs of relevant group member ids. 
  116. */ 
  117. protected function get_group_member_ids() { 
  118. global $wpdb; 
  119.  
  120. if ( is_array( $this->group_member_ids ) ) { 
  121. return $this->group_member_ids; 
  122.  
  123. $bp = buddypress(); 
  124. $sql = array( 
  125. 'select' => "SELECT user_id FROM {$bp->groups->table_name_members}",  
  126. 'where' => array(),  
  127. 'orderby' => '',  
  128. 'order' => '',  
  129. ); 
  130.  
  131. /** WHERE clauses *****************************************************/ 
  132.  
  133. // Group id. 
  134. $sql['where'][] = $wpdb->prepare( "group_id = %d", $this->query_vars['group_id'] ); 
  135.  
  136. // If is_confirmed. 
  137. $is_confirmed = ! empty( $this->query_vars['is_confirmed'] ) ? 1 : 0; 
  138. $sql['where'][] = $wpdb->prepare( "is_confirmed = %d", $is_confirmed ); 
  139.  
  140. // If invite_sent. 
  141. if ( ! is_null( $this->query_vars['invite_sent'] ) ) { 
  142. $invite_sent = ! empty( $this->query_vars['invite_sent'] ) ? 1 : 0; 
  143. $sql['where'][] = $wpdb->prepare( "invite_sent = %d", $invite_sent ); 
  144.  
  145. // If inviter_id. 
  146. if ( ! is_null( $this->query_vars['inviter_id'] ) ) { 
  147. $inviter_id = $this->query_vars['inviter_id']; 
  148.  
  149. // Empty: inviter_id = 0. (pass false, 0, or empty array). 
  150. if ( empty( $inviter_id ) ) { 
  151. $sql['where'][] = "inviter_id = 0"; 
  152.  
  153. // The string 'any' matches any non-zero value (inviter_id != 0). 
  154. } elseif ( 'any' === $inviter_id ) { 
  155. $sql['where'][] = "inviter_id != 0"; 
  156.  
  157. // Assume that a list of inviter IDs has been passed. 
  158. } else { 
  159. // Parse and sanitize. 
  160. $inviter_ids = wp_parse_id_list( $inviter_id ); 
  161. if ( ! empty( $inviter_ids ) ) { 
  162. $inviter_ids_sql = implode( ', ', $inviter_ids ); 
  163. $sql['where'][] = "inviter_id IN ({$inviter_ids_sql})"; 
  164.  
  165. // Role information is stored as follows: admins have 
  166. // is_admin = 1, mods have is_mod = 1, banned have is_banned = 
  167. // 1, and members have all three set to 0. 
  168. $roles = !empty( $this->query_vars['group_role'] ) ? $this->query_vars['group_role'] : array(); 
  169. if ( is_string( $roles ) ) { 
  170. $roles = explode( ', ', $roles ); 
  171.  
  172. // Sanitize: Only 'admin', 'mod', 'member', and 'banned' are valid. 
  173. $allowed_roles = array( 'admin', 'mod', 'member', 'banned' ); 
  174. foreach ( $roles as $role_key => $role_value ) { 
  175. if ( ! in_array( $role_value, $allowed_roles ) ) { 
  176. unset( $roles[ $role_key ] ); 
  177.  
  178. $roles = array_unique( $roles ); 
  179.  
  180. // When querying for a set of roles containing 'member' (for 
  181. // which there is no dedicated is_ column), figure out a list 
  182. // of columns *not* to match. 
  183. $roles_sql = ''; 
  184. if ( in_array( 'member', $roles ) ) { 
  185. $role_columns = array(); 
  186. foreach ( array_diff( $allowed_roles, $roles ) as $excluded_role ) { 
  187. $role_columns[] = 'is_' . $excluded_role . ' = 0'; 
  188.  
  189. if ( ! empty( $role_columns ) ) { 
  190. $roles_sql = '(' . implode( ' AND ', $role_columns ) . ')'; 
  191.  
  192. // When querying for a set of roles *not* containing 'member',  
  193. // simply construct a list of is_* = 1 clauses. 
  194. } else { 
  195. $role_columns = array(); 
  196. foreach ( $roles as $role ) { 
  197. $role_columns[] = 'is_' . $role . ' = 1'; 
  198.  
  199. if ( ! empty( $role_columns ) ) { 
  200. $roles_sql = '(' . implode( ' OR ', $role_columns ) . ')'; 
  201.  
  202. if ( ! empty( $roles_sql ) ) { 
  203. $sql['where'][] = $roles_sql; 
  204.  
  205. $sql['where'] = ! empty( $sql['where'] ) ? 'WHERE ' . implode( ' AND ', $sql['where'] ) : ''; 
  206.  
  207. // We fetch group members in order of last_joined, regardless 
  208. // of 'type'. If the 'type' value is not 'last_joined' or 
  209. // 'first_joined', the order will be overridden in 
  210. // BP_Group_Member_Query::set_orderby(). 
  211. $sql['orderby'] = "ORDER BY date_modified"; 
  212. $sql['order'] = 'first_joined' === $this->query_vars['type'] ? 'ASC' : 'DESC'; 
  213.  
  214. $this->group_member_ids = $wpdb->get_col( "{$sql['select']} {$sql['where']} {$sql['orderby']} {$sql['order']}" ); 
  215.  
  216. /** 
  217. * Filters the member IDs for the current group member query. 
  218. * 
  219. * Use this filter to build a custom query (such as when you've 
  220. * defined a custom 'type'). 
  221. * 
  222. * @since 2.0.0 
  223. * 
  224. * @param array $group_member_ids Array of associated member IDs. 
  225. * @param BP_Group_Member_Query $this Current BP_Group_Member_Query instance. 
  226. */ 
  227. $this->group_member_ids = apply_filters( 'bp_group_member_query_group_member_ids', $this->group_member_ids, $this ); 
  228.  
  229. return $this->group_member_ids; 
  230.  
  231. /** 
  232. * Tell BP_User_Query to order by the order of our query results. 
  233. * 
  234. * We only override BP_User_Query's native ordering in case of the 
  235. * 'last_joined' and 'first_joined' $type parameters. 
  236. * 
  237. * @since 1.8.1 
  238. * 
  239. * @param BP_User_Query $query BP_User_Query object. 
  240. */ 
  241. public function set_orderby( $query ) { 
  242. $gm_ids = $this->get_group_member_ids(); 
  243. if ( empty( $gm_ids ) ) { 
  244. $gm_ids = array( 0 ); 
  245.  
  246. // For 'last_joined', 'first_joined', and 'group_activity' 
  247. // types, we override the default orderby clause of 
  248. // BP_User_Query. In the case of 'group_activity', we perform 
  249. // a separate query to get the necessary order. In the case of 
  250. // 'last_joined' and 'first_joined', we can trust the order of 
  251. // results from BP_Group_Member_Query::get_group_members(). 
  252. // In all other cases, we fall through and let BP_User_Query 
  253. // do its own (non-group-specific) ordering. 
  254. if ( in_array( $query->query_vars['type'], array( 'last_joined', 'first_joined', 'group_activity' ) ) ) { 
  255.  
  256. // Group Activity DESC. 
  257. if ( 'group_activity' == $query->query_vars['type'] ) { 
  258. $gm_ids = $this->get_gm_ids_ordered_by_activity( $query, $gm_ids ); 
  259.  
  260. // The first param in the FIELD() clause is the sort column id. 
  261. $gm_ids = array_merge( array( 'u.id' ), wp_parse_id_list( $gm_ids ) ); 
  262. $gm_ids_sql = implode( ', ', $gm_ids ); 
  263.  
  264. $query->uid_clauses['orderby'] = "ORDER BY FIELD(" . $gm_ids_sql . ")"; 
  265.  
  266. // Prevent this filter from running on future BP_User_Query 
  267. // instances on the same page. 
  268. remove_action( 'bp_pre_user_query', array( $this, 'set_orderby' ) ); 
  269.  
  270. /** 
  271. * Fetch additional data required in bp_group_has_members() loops. 
  272. * 
  273. * Additional data fetched: 
  274. * - is_banned 
  275. * - date_modified 
  276. * 
  277. * @since 1.8.0 
  278. * 
  279. * @param BP_User_Query $query BP_User_Query object. Because we're 
  280. * filtering the current object, we use 
  281. * $this inside of the method instead. 
  282. * @param string $user_ids_sql Sanitized, comma-separated string of 
  283. * the user ids returned by the main query. 
  284. */ 
  285. public function populate_group_member_extras( $query, $user_ids_sql ) { 
  286. global $wpdb; 
  287.  
  288. $bp = buddypress(); 
  289. $extras = $wpdb->get_results( $wpdb->prepare( "SELECT id, user_id, date_modified, is_admin, is_mod, comments, user_title, invite_sent, is_confirmed, inviter_id, is_banned FROM {$bp->groups->table_name_members} WHERE user_id IN ({$user_ids_sql}) AND group_id = %d", $this->query_vars['group_id'] ) ); 
  290.  
  291. foreach ( (array) $extras as $extra ) { 
  292. if ( isset( $this->results[ $extra->user_id ] ) ) { 
  293. // The user_id is provided for backward compatibility. 
  294. $this->results[ $extra->user_id ]->user_id = (int) $extra->user_id; 
  295. $this->results[ $extra->user_id ]->is_admin = (int) $extra->is_admin; 
  296. $this->results[ $extra->user_id ]->is_mod = (int) $extra->is_mod; 
  297. $this->results[ $extra->user_id ]->is_banned = (int) $extra->is_banned; 
  298. $this->results[ $extra->user_id ]->date_modified = $extra->date_modified; 
  299. $this->results[ $extra->user_id ]->user_title = $extra->user_title; 
  300. $this->results[ $extra->user_id ]->comments = $extra->comments; 
  301. $this->results[ $extra->user_id ]->invite_sent = (int) $extra->invite_sent; 
  302. $this->results[ $extra->user_id ]->inviter_id = (int) $extra->inviter_id; 
  303. $this->results[ $extra->user_id ]->is_confirmed = (int) $extra->is_confirmed; 
  304. $this->results[ $extra->user_id ]->membership_id = (int) $extra->id; 
  305.  
  306. // Don't filter other BP_User_Query objects on the same page. 
  307. remove_action( 'bp_user_query_populate_extras', array( $this, 'populate_group_member_extras' ), 10 ); 
  308.  
  309. /** 
  310. * Sort user IDs by how recently they have generated activity within a given group. 
  311. * 
  312. * @since 2.1.0 
  313. * 
  314. * @param BP_User_Query $query BP_User_Query object. 
  315. * @param array $gm_ids array of group member ids. 
  316. * @return array 
  317. */ 
  318. public function get_gm_ids_ordered_by_activity( $query, $gm_ids = array() ) { 
  319. global $wpdb; 
  320.  
  321. if ( empty( $gm_ids ) ) { 
  322. return $gm_ids; 
  323.  
  324. if ( ! bp_is_active( 'activity' ) ) { 
  325. return $gm_ids; 
  326.  
  327. $activity_table = buddypress()->activity->table_name; 
  328.  
  329. $sql = array( 
  330. 'select' => "SELECT user_id, max( date_recorded ) as date_recorded FROM {$activity_table}",  
  331. 'where' => array(),  
  332. 'groupby' => 'GROUP BY user_id',  
  333. 'orderby' => 'ORDER BY date_recorded',  
  334. 'order' => 'DESC',  
  335. ); 
  336.  
  337. $sql['where'] = array( 
  338. 'user_id IN (' . implode( ', ', wp_parse_id_list( $gm_ids ) ) . ')',  
  339. 'item_id = ' . absint( $query->query_vars['group_id'] ),  
  340. $wpdb->prepare( "component = %s", buddypress()->groups->id ),  
  341. ); 
  342.  
  343. $sql['where'] = 'WHERE ' . implode( ' AND ', $sql['where'] ); 
  344.  
  345. $group_user_ids = $wpdb->get_results( "{$sql['select']} {$sql['where']} {$sql['groupby']} {$sql['orderby']} {$sql['order']}" ); 
  346.  
  347. return wp_list_pluck( $group_user_ids, 'user_id' ); 
.