Changeset 9485 for trunk/src/bp-core/bp-core-classes.php
- Timestamp:
- 02/15/2015 12:48:56 AM (10 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/bp-core/bp-core-classes.php
r9471 r9485 10 10 defined( 'ABSPATH' ) || exit; 11 11 12 /** 13 * BuddyPress User Query class. 14 * 15 * Used for querying users in a BuddyPress context, in situations where WP_User_Query won't do the trick: 16 * Member directories, the Friends component, etc. 17 * 18 * @since BuddyPress (1.7.0) 19 * 20 * @param array $query { 21 * Query arguments. All items are optional. 22 * @type string $type Determines sort order. Select from 'newest', 'active', 'online', 23 * 'random', 'popular', 'alphabetical'. Default: 'newest'. 24 * @type int $per_page Number of results to return. Default: 0 (no limit). 25 * @type int $page Page offset (together with $per_page). Default: 1. 26 * @type int $user_id ID of a user. If present, and if the friends component is activated, 27 * results will be limited to the friends of that user. Default: 0. 28 * @type string|bool $search_terms Terms to search by. Search happens across xprofile fields. Requires 29 * XProfile component. Default: false. 30 * @type string $search_wildcard When searching with $search_terms, set where wildcards around the term 31 * should be positioned. Accepts 'both', 'left', 'right'. Default: 'both'. 32 * @type array|string|bool $include An array or comma-separated list of user IDs to which query should 33 * be limited. Default: false. 34 * @type array|string|bool $exclude An array or comma-separated list of user IDs that will be excluded from 35 * query results. Default: false. 36 * @type array|string|bool $user_ids An array or comma-separated list of IDs corresponding to the users 37 * that should be returned. When this parameter is passed, it will 38 * override all others; BP User objects will be constructed using these 39 * IDs only. Default: false. 40 * @type array|string $member_type Array or comma-separated list of member types to limit results to. 41 * @type string|bool $meta_key Limit results to users that have usermeta associated with this meta_key. 42 * Usually used with $meta_value. Default: false. 43 * @type string|bool $meta_value When used with $meta_key, limits results to users whose usermeta value 44 * associated with $meta_key matches $meta_value. Default: false. 45 * @type array $xprofile_query Filter results by xprofile data. Requires the xprofile component. See 46 * {@see BP_XProfile_Query} for details. 47 * @type bool $populate_extras True if you want to fetch extra metadata 48 * about returned users, such as total group and friend counts. 49 * @type string $count_total Determines how BP_User_Query will do a count of total users matching 50 * the other filter criteria. Default value is 'count_query', which does 51 * a separate SELECT COUNT query to determine the total. 52 * 'sql_count_found_rows' uses SQL_COUNT_FOUND_ROWS and 53 * SELECT FOUND_ROWS(). Pass an empty string to skip the total user 54 * count query. 55 * } 56 */ 57 class BP_User_Query { 58 59 /** Variables *************************************************************/ 60 61 /** 62 * Unaltered params as passed to the constructor. 63 * 64 * @since BuddyPress (1.8.0) 65 * @var array 66 */ 67 public $query_vars_raw = array(); 68 69 /** 70 * Array of variables to query with. 71 * 72 * @since BuddyPress (1.7.0) 73 * @var array 74 */ 75 public $query_vars = array(); 76 77 /** 78 * List of found users and their respective data. 79 * 80 * @access public To allow components to manipulate them. 81 * @since BuddyPress (1.7.0) 82 * @var array 83 */ 84 public $results = array(); 85 86 /** 87 * Total number of found users for the current query. 88 * 89 * @access public To allow components to manipulate it. 90 * @since BuddyPress (1.7.0) 91 * @var int 92 */ 93 public $total_users = 0; 94 95 /** 96 * List of found user IDs. 97 * 98 * @access public To allow components to manipulate it. 99 * @since BuddyPress (1.7.0) 100 * @var array 101 */ 102 public $user_ids = array(); 103 104 /** 105 * SQL clauses for the user ID query. 106 * 107 * @access public To allow components to manipulate it. 108 * @since BuddyPress (1.7.0) 109 * @var array 110 */ 111 public $uid_clauses = array(); 112 113 /** 114 * SQL table where the user ID is being fetched from. 115 * 116 * @since BuddyPress (2.2.0) 117 * @access public 118 * @var string 119 */ 120 public $uid_table = ''; 121 122 /** 123 * SQL database column name to order by. 124 * 125 * @since BuddyPress (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 * @access protected 134 * @since BuddyPress (1.7.0) 135 * @var string 136 */ 137 protected $no_results = array( 'join' => '', 'where' => '0 = 1' ); 138 139 140 /** Methods ***************************************************************/ 141 142 /** 143 * Constructor. 144 * 145 * @since BuddyPress (1.7.0) 146 * 147 * @param string|array $query See {@link BP_User_Query}. 148 */ 149 public function __construct( $query = null ) { 150 151 // Store the raw query vars for later access 152 $this->query_vars_raw = $query; 153 154 // Allow extending classes to register action/filter hooks 155 $this->setup_hooks(); 156 157 if ( ! empty( $this->query_vars_raw ) ) { 158 $this->query_vars = wp_parse_args( $this->query_vars_raw, array( 159 'type' => 'newest', 160 'per_page' => 0, 161 'page' => 1, 162 'user_id' => 0, 163 'search_terms' => false, 164 'search_wildcard' => 'both', 165 'include' => false, 166 'exclude' => false, 167 'user_ids' => false, 168 'member_type' => '', 169 'meta_key' => false, 170 'meta_value' => false, 171 'xprofile_query' => false, 172 'populate_extras' => true, 173 'count_total' => 'count_query' 174 ) ); 175 176 // Plugins can use this filter to modify query args 177 // before the query is constructed 178 do_action_ref_array( 'bp_pre_user_query_construct', array( &$this ) ); 179 180 // Get user ids 181 // If the user_ids param is present, we skip the query 182 if ( false !== $this->query_vars['user_ids'] ) { 183 $this->user_ids = wp_parse_id_list( $this->query_vars['user_ids'] ); 184 } else { 185 $this->prepare_user_ids_query(); 186 $this->do_user_ids_query(); 187 } 188 } 189 190 // Bail if no user IDs were found 191 if ( empty( $this->user_ids ) ) { 192 return; 193 } 194 195 // Fetch additional data. First, using WP_User_Query 196 $this->do_wp_user_query(); 197 198 // Get BuddyPress specific user data 199 $this->populate_extras(); 200 } 201 202 /** 203 * Allow extending classes to set up action/filter hooks. 204 * 205 * When extending BP_User_Query, you may need to use some of its 206 * internal hooks to modify the output. It's not convenient to call 207 * add_action() or add_filter() in your class constructor, because 208 * BP_User_Query::__construct() contains a fair amount of logic that 209 * you may not want to override in your class. Define this method in 210 * your own class if you need a place where your extending class can 211 * add its hooks early in the query-building process. See 212 * {@link BP_Group_Member_Query::setup_hooks()} for an example. 213 * 214 * @since BuddyPress (1.8.0) 215 */ 216 public function setup_hooks() {} 217 218 /** 219 * Prepare the query for user_ids. 220 * 221 * @since BuddyPress (1.7.0) 222 */ 223 public function prepare_user_ids_query() { 224 global $wpdb; 225 226 $bp = buddypress(); 227 228 // Default query variables used here 229 $type = ''; 230 $per_page = 0; 231 $page = 1; 232 $user_id = 0; 233 $include = false; 234 $search_terms = false; 235 $exclude = false; 236 $meta_key = false; 237 $meta_value = false; 238 239 extract( $this->query_vars ); 240 241 // Setup the main SQL query container 242 $sql = array( 243 'select' => '', 244 'where' => array(), 245 'orderby' => '', 246 'order' => '', 247 'limit' => '' 248 ); 249 250 /** TYPE **************************************************************/ 251 252 // Determines the sort order, which means it also determines where the 253 // user IDs are drawn from (the SELECT and WHERE statements) 254 switch ( $type ) { 255 256 // 'online' query happens against the last_activity usermeta key 257 // Filter 'bp_user_query_online_interval' to modify the 258 // number of minutes used as an interval 259 case 'online' : 260 $this->uid_name = 'user_id'; 261 $this->uid_table = $bp->members->table_name_last_activity; 262 $sql['select'] = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u"; 263 $sql['where'][] = $wpdb->prepare( "u.component = %s AND u.type = 'last_activity'", buddypress()->members->id ); 264 $sql['where'][] = $wpdb->prepare( "u.date_recorded >= DATE_SUB( UTC_TIMESTAMP(), INTERVAL %d MINUTE )", apply_filters( 'bp_user_query_online_interval', 15 ) ); 265 $sql['orderby'] = "ORDER BY u.date_recorded"; 266 $sql['order'] = "DESC"; 267 268 break; 269 270 // 'active', 'newest', and 'random' queries 271 // all happen against the last_activity usermeta key 272 case 'active' : 273 case 'newest' : 274 case 'random' : 275 $this->uid_name = 'user_id'; 276 $this->uid_table = $bp->members->table_name_last_activity; 277 $sql['select'] = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u"; 278 $sql['where'][] = $wpdb->prepare( "u.component = %s AND u.type = 'last_activity'", buddypress()->members->id ); 279 280 if ( 'newest' == $type ) { 281 $sql['orderby'] = "ORDER BY u.user_id"; 282 $sql['order'] = "DESC"; 283 } elseif ( 'random' == $type ) { 284 $sql['orderby'] = "ORDER BY rand()"; 285 } else { 286 $sql['orderby'] = "ORDER BY u.date_recorded"; 287 $sql['order'] = "DESC"; 288 } 289 290 break; 291 292 // 'popular' sorts by the 'total_friend_count' usermeta 293 case 'popular' : 294 $this->uid_name = 'user_id'; 295 $this->uid_table = $wpdb->usermeta; 296 $sql['select'] = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u"; 297 $sql['where'][] = $wpdb->prepare( "u.meta_key = %s", bp_get_user_meta_key( 'total_friend_count' ) ); 298 $sql['orderby'] = "ORDER BY CONVERT(u.meta_value, SIGNED)"; 299 $sql['order'] = "DESC"; 300 301 break; 302 303 // 'alphabetical' sorts depend on the xprofile setup 304 case 'alphabetical' : 305 306 // We prefer to do alphabetical sorts against the display_name field 307 // of wp_users, because the table is smaller and better indexed. We 308 // can do so if xprofile sync is enabled, or if xprofile is inactive. 309 // 310 // @todo remove need for bp_is_active() check 311 if ( ! bp_disable_profile_sync() || ! bp_is_active( 'xprofile' ) ) { 312 $this->uid_name = 'ID'; 313 $this->uid_table = $wpdb->users; 314 $sql['select'] = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u"; 315 $sql['orderby'] = "ORDER BY u.display_name"; 316 $sql['order'] = "ASC"; 317 318 // When profile sync is disabled, alphabetical sorts must happen against 319 // the xprofile table 320 } else { 321 $this->uid_name = 'user_id'; 322 $this->uid_table = $bp->profile->table_name_data; 323 $sql['select'] = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u"; 324 $sql['where'][] = $wpdb->prepare( "u.field_id = %d", bp_xprofile_fullname_field_id() ); 325 $sql['orderby'] = "ORDER BY u.value"; 326 $sql['order'] = "ASC"; 327 } 328 329 // Alphabetical queries ignore last_activity, while BP uses last_activity 330 // to infer spam/deleted/non-activated users. To ensure that these users 331 // are filtered out, we add an appropriate sub-query. 332 $sql['where'][] = "u.{$this->uid_name} IN ( SELECT ID FROM {$wpdb->users} WHERE " . bp_core_get_status_sql( '' ) . " )"; 333 334 break; 335 336 // Any other 'type' falls through 337 default : 338 $this->uid_name = 'ID'; 339 $this->uid_table = $wpdb->users; 340 $sql['select'] = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u"; 341 342 // In this case, we assume that a plugin is 343 // handling order, so we leave those clauses 344 // blank 345 346 break; 347 } 348 349 /** WHERE *************************************************************/ 350 351 // 'include' - User ids to include in the results 352 $include = false !== $include ? wp_parse_id_list( $include ) : array(); 353 $include_ids = $this->get_include_ids( $include ); 354 if ( ! empty( $include_ids ) ) { 355 $include_ids = implode( ',', wp_parse_id_list( $include_ids ) ); 356 $sql['where'][] = "u.{$this->uid_name} IN ({$include_ids})"; 357 } 358 359 // 'exclude' - User ids to exclude from the results 360 if ( false !== $exclude ) { 361 $exclude_ids = implode( ',', wp_parse_id_list( $exclude ) ); 362 $sql['where'][] = "u.{$this->uid_name} NOT IN ({$exclude_ids})"; 363 } 364 365 // 'user_id' - When a user id is passed, limit to the friends of the user 366 // @todo remove need for bp_is_active() check 367 if ( ! empty( $user_id ) && bp_is_active( 'friends' ) ) { 368 $friend_ids = friends_get_friend_user_ids( $user_id ); 369 $friend_ids = implode( ',', wp_parse_id_list( $friend_ids ) ); 370 371 if ( ! empty( $friend_ids ) ) { 372 $sql['where'][] = "u.{$this->uid_name} IN ({$friend_ids})"; 373 374 // If the user has no friends, the query should always 375 // return no users 376 } else { 377 $sql['where'][] = $this->no_results['where']; 378 } 379 } 380 381 /** Search Terms ******************************************************/ 382 383 // 'search_terms' searches user_login and user_nicename 384 // xprofile field matches happen in bp_xprofile_bp_user_query_search() 385 if ( false !== $search_terms ) { 386 $search_terms = bp_esc_like( wp_kses_normalize_entities( $search_terms ) ); 387 388 if ( $search_wildcard === 'left' ) { 389 $search_terms_nospace = '%' . $search_terms; 390 $search_terms_space = '%' . $search_terms . ' %'; 391 } elseif ( $search_wildcard === 'right' ) { 392 $search_terms_nospace = $search_terms . '%'; 393 $search_terms_space = '% ' . $search_terms . '%'; 394 } else { 395 $search_terms_nospace = '%' . $search_terms . '%'; 396 $search_terms_space = '%' . $search_terms . '%'; 397 } 398 399 $sql['where']['search'] = $wpdb->prepare( 400 "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 ) )", 401 $search_terms_nospace, 402 $search_terms_space, 403 $search_terms_nospace, 404 $search_terms_space 405 ); 406 } 407 408 // Member type. 409 if ( ! empty( $member_type ) ) { 410 $member_types = array(); 411 412 if ( ! is_array( $member_type ) ) { 413 $member_type = preg_split( '/[,\s+]/', $member_type ); 414 } 415 416 foreach ( $member_type as $mt ) { 417 if ( ! bp_get_member_type_object( $mt ) ) { 418 continue; 419 } 420 421 $member_types[] = $mt; 422 } 423 424 if ( ! empty( $member_types ) ) { 425 $member_type_tq = new WP_Tax_Query( array( 426 array( 427 'taxonomy' => 'bp_member_type', 428 'field' => 'name', 429 'operator' => 'IN', 430 'terms' => $member_types, 431 ), 432 ) ); 433 434 // Switch to the root blog, where member type taxonomies live. 435 switch_to_blog( bp_get_root_blog_id() ); 436 437 $member_type_sql_clauses = $member_type_tq->get_sql( 'u', $this->uid_name ); 438 restore_current_blog(); 439 440 441 442 // Grab the first term_relationships clause and convert to a subquery. 443 if ( preg_match( '/' . $wpdb->term_relationships . '\.term_taxonomy_id IN \([0-9, ]+\)/', $member_type_sql_clauses['where'], $matches ) ) { 444 $sql['where']['member_type'] = "u.{$this->uid_name} IN ( SELECT object_id FROM $wpdb->term_relationships WHERE {$matches[0]} )"; 445 } 446 } 447 } 448 449 // 'meta_key', 'meta_value' allow usermeta search 450 // To avoid global joins, do a separate query 451 if ( false !== $meta_key ) { 452 $meta_sql = $wpdb->prepare( "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = %s", $meta_key ); 453 454 if ( false !== $meta_value ) { 455 $meta_sql .= $wpdb->prepare( " AND meta_value = %s", $meta_value ); 456 } 457 458 $found_user_ids = $wpdb->get_col( $meta_sql ); 459 460 if ( ! empty( $found_user_ids ) ) { 461 $sql['where'][] = "u.{$this->uid_name} IN (" . implode( ',', wp_parse_id_list( $found_user_ids ) ) . ")"; 462 } else { 463 $sql['where'][] = '1 = 0'; 464 } 465 } 466 467 // 'per_page', 'page' - handles LIMIT 468 if ( !empty( $per_page ) && !empty( $page ) ) { 469 $sql['limit'] = $wpdb->prepare( "LIMIT %d, %d", intval( ( $page - 1 ) * $per_page ), intval( $per_page ) ); 470 } else { 471 $sql['limit'] = ''; 472 } 473 474 // Allow custom filters 475 $sql = apply_filters_ref_array( 'bp_user_query_uid_clauses', array( $sql, &$this ) ); 476 477 // Assemble the query chunks 478 $this->uid_clauses['select'] = $sql['select']; 479 $this->uid_clauses['where'] = ! empty( $sql['where'] ) ? 'WHERE ' . implode( ' AND ', $sql['where'] ) : ''; 480 $this->uid_clauses['orderby'] = $sql['orderby']; 481 $this->uid_clauses['order'] = $sql['order']; 482 $this->uid_clauses['limit'] = $sql['limit']; 483 484 do_action_ref_array( 'bp_pre_user_query', array( &$this ) ); 485 } 486 487 /** 488 * Query for IDs of users that match the query parameters. 489 * 490 * Perform a database query to specifically get only user IDs, using 491 * existing query variables set previously in the constructor. 492 * 493 * Also used to quickly perform user total counts. 494 * 495 * @since BuddyPress (1.7.0) 496 */ 497 public function do_user_ids_query() { 498 global $wpdb; 499 500 // If counting using SQL_CALC_FOUND_ROWS, set it up here 501 if ( 'sql_calc_found_rows' == $this->query_vars['count_total'] ) { 502 $this->uid_clauses['select'] = str_replace( 'SELECT', 'SELECT SQL_CALC_FOUND_ROWS', $this->uid_clauses['select'] ); 503 } 504 505 // Get the specific user ids 506 $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']}" ); 507 508 // Get the total user count 509 if ( 'sql_calc_found_rows' == $this->query_vars['count_total'] ) { 510 $this->total_users = $wpdb->get_var( apply_filters( 'bp_found_user_query', "SELECT FOUND_ROWS()", $this ) ); 511 } elseif ( 'count_query' == $this->query_vars['count_total'] ) { 512 $count_select = preg_replace( '/^SELECT.*?FROM (\S+) u/', "SELECT COUNT(u.{$this->uid_name}) FROM $1 u", $this->uid_clauses['select'] ); 513 $this->total_users = $wpdb->get_var( apply_filters( 'bp_found_user_query', "{$count_select} {$this->uid_clauses['where']}", $this ) ); 514 } 515 } 516 517 /** 518 * Use WP_User_Query() to pull data for the user IDs retrieved in the main query. 519 * 520 * @since BuddyPress (1.7.0) 521 */ 522 public function do_wp_user_query() { 523 $fields = array( 'ID', 'user_login', 'user_pass', 'user_nicename', 'user_email', 'user_url', 'user_registered', 'user_activation_key', 'user_status', 'display_name' ); 524 525 if ( is_multisite() ) { 526 $fields[] = 'spam'; 527 $fields[] = 'deleted'; 528 } 529 530 $wp_user_query = new WP_User_Query( apply_filters( 'bp_wp_user_query_args', array( 531 532 // Relevant 533 'fields' => $fields, 534 'include' => $this->user_ids, 535 536 // Overrides 537 'blog_id' => 0, // BP does not require blog roles 538 'count_total' => false // We already have a count 539 540 ), $this ) ); 541 542 // WP_User_Query doesn't cache the data it pulls from wp_users, 543 // and it does not give us a way to save queries by fetching 544 // only uncached users. However, BP does cache this data, so 545 // we set it here. 546 foreach ( $wp_user_query->results as $u ) { 547 wp_cache_set( 'bp_core_userdata_' . $u->ID, $u, 'bp' ); 548 } 549 550 // We calculate total_users using a standalone query, except 551 // when a whitelist of user_ids is passed to the constructor. 552 // This clause covers the latter situation, and ensures that 553 // pagination works when querying by $user_ids. 554 if ( empty( $this->total_users ) ) { 555 $this->total_users = count( $wp_user_query->results ); 556 } 557 558 // Reindex for easier matching 559 $r = array(); 560 foreach ( $wp_user_query->results as $u ) { 561 $r[ $u->ID ] = $u; 562 } 563 564 // Match up to the user ids from the main query 565 foreach ( $this->user_ids as $uid ) { 566 if ( isset( $r[ $uid ] ) ) { 567 $this->results[ $uid ] = $r[ $uid ]; 568 569 // The BP template functions expect an 'id' 570 // (as opposed to 'ID') property 571 $this->results[ $uid ]->id = $uid; 572 } 573 } 574 } 575 576 /** 577 * Fetch the IDs of users to put in the IN clause of the main query. 578 * 579 * By default, returns the value passed to it 580 * ($this->query_vars['include']). Having this abstracted into a 581 * standalone method means that extending classes can override the 582 * logic, parsing together their own user_id limits with the 'include' 583 * ids passed to the class constructor. See {@link BP_Group_Member_Query} 584 * for an example. 585 * 586 * @since BuddyPress (1.8.0) 587 * 588 * @param array Sanitized array of user IDs, as passed to the 'include' 589 * parameter of the class constructor. 590 * @return array The list of users to which the main query should be 591 * limited. 592 */ 593 public function get_include_ids( $include = array() ) { 594 return $include; 595 } 596 597 /** 598 * Perform a database query to populate any extra metadata we might need. 599 * 600 * Different components will hook into the 'bp_user_query_populate_extras' 601 * action to loop in the things they want. 602 * 603 * @since BuddyPress (1.7.0) 604 * 605 * @global WPDB $wpdb Global WordPress database access object. 606 */ 607 public function populate_extras() { 608 global $wpdb; 609 610 // Bail if no users 611 if ( empty( $this->user_ids ) || empty( $this->results ) ) { 612 return; 613 } 614 615 // Bail if the populate_extras flag is set to false 616 // In the case of the 'popular' sort type, we force 617 // populate_extras to true, because we need the friend counts 618 if ( 'popular' == $this->query_vars['type'] ) { 619 $this->query_vars['populate_extras'] = 1; 620 } 621 622 if ( ! (bool) $this->query_vars['populate_extras'] ) { 623 return; 624 } 625 626 // Turn user ID's into a query-usable, comma separated value 627 $user_ids_sql = implode( ',', wp_parse_id_list( $this->user_ids ) ); 628 629 /** 630 * Use this action to independently populate your own custom extras. 631 * 632 * Note that anything you add here should query using $user_ids_sql, to 633 * avoid running multiple queries per user in the loop. 634 * 635 * Two BuddyPress components currently do this: 636 * - XProfile: To override display names 637 * - Friends: To set whether or not a user is the current users friend 638 * 639 * @see bp_xprofile_filter_user_query_populate_extras() 640 * @see bp_friends_filter_user_query_populate_extras() 641 */ 642 do_action_ref_array( 'bp_user_query_populate_extras', array( $this, $user_ids_sql ) ); 643 644 // Fetch last_active data from the activity table 645 $last_activities = BP_Core_User::get_last_activity( $this->user_ids ); 646 647 // Set a last_activity value for each user, even if it's empty 648 foreach ( $this->results as $user_id => $user ) { 649 $user_last_activity = isset( $last_activities[ $user_id ] ) ? $last_activities[ $user_id ]['date_recorded'] : ''; 650 $this->results[ $user_id ]->last_activity = $user_last_activity; 651 } 652 653 // Fetch usermeta data 654 // We want the three following pieces of info from usermeta: 655 // - friend count 656 // - latest update 657 $total_friend_count_key = bp_get_user_meta_key( 'total_friend_count' ); 658 $bp_latest_update_key = bp_get_user_meta_key( 'bp_latest_update' ); 659 660 // total_friend_count must be set for each user, even if its 661 // value is 0 662 foreach ( $this->results as $uindex => $user ) { 663 $this->results[$uindex]->total_friend_count = 0; 664 } 665 666 // Create, prepare, and run the separate usermeta query 667 $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 ) ); 668 669 // The $members_template global expects the index key to be different 670 // from the meta_key in some cases, so we rejig things here. 671 foreach ( $user_metas as $user_meta ) { 672 switch ( $user_meta->meta_key ) { 673 case $total_friend_count_key : 674 $key = 'total_friend_count'; 675 break; 676 677 case $bp_latest_update_key : 678 $key = 'latest_update'; 679 break; 680 } 681 682 if ( isset( $this->results[ $user_meta->user_id ] ) ) { 683 $this->results[ $user_meta->user_id ]->{$key} = $user_meta->meta_value; 684 } 685 } 686 687 // When meta_key or meta_value have been passed to the query, 688 // fetch the resulting values for use in the template functions 689 if ( ! empty( $this->query_vars['meta_key'] ) ) { 690 $meta_sql = array( 691 'select' => "SELECT user_id, meta_key, meta_value", 692 'from' => "FROM $wpdb->usermeta", 693 'where' => $wpdb->prepare( "WHERE meta_key = %s", $this->query_vars['meta_key'] ) 694 ); 695 696 if ( false !== $this->query_vars['meta_value'] ) { 697 $meta_sql['where'] .= $wpdb->prepare( " AND meta_value = %s", $this->query_vars['meta_value'] ); 698 } 699 700 $metas = $wpdb->get_results( "{$meta_sql['select']} {$meta_sql['from']} {$meta_sql['where']}" ); 701 702 if ( ! empty( $metas ) ) { 703 foreach ( $metas as $meta ) { 704 if ( isset( $this->results[ $meta->user_id ] ) ) { 705 $this->results[ $meta->user_id ]->meta_key = $meta->meta_key; 706 707 if ( ! empty( $meta->meta_value ) ) { 708 $this->results[ $meta->user_id ]->meta_value = $meta->meta_value; 709 } 710 } 711 } 712 } 713 } 714 } 715 } 716 717 /** 718 * Fetch data about a BuddyPress user. 719 * 720 * BP_Core_User class can be used by any component. It will fetch useful 721 * details for any user when provided with a user_id. 722 * 723 * Example: 724 * $user = new BP_Core_User( $user_id ); 725 * $user_avatar = $user->avatar; 726 * $user_email = $user->email; 727 * $user_status = $user->status; 728 * etc. 729 */ 730 class BP_Core_User { 731 732 /** 733 * ID of the user which the object relates to. 734 * 735 * @var integer 736 */ 737 public $id; 738 739 /** 740 * The URL to the full size of the avatar for the user. 741 * 742 * @var string 743 */ 744 public $avatar; 745 746 /** 747 * The URL to the thumb size of the avatar for the user. 748 * 749 * @var string 750 */ 751 public $avatar_thumb; 752 753 /** 754 * The URL to the mini size of the avatar for the user. 755 * 756 * @var string 757 */ 758 public $avatar_mini; 759 760 /** 761 * The full name of the user 762 * 763 * @var string 764 */ 765 public $fullname; 766 767 /** 768 * The email for the user. 769 * 770 * @var string 771 */ 772 public $email; 773 774 /** 775 * The absolute url for the user's profile. 776 * 777 * @var string 778 */ 779 public $user_url; 780 781 /** 782 * The HTML for the user link, with the link text being the user's full name. 783 * 784 * @var string 785 */ 786 public $user_link; 787 788 /** 789 * Contains a formatted string when the last time the user was active. 790 * 791 * Example: "active 2 hours and 50 minutes ago" 792 * 793 * @var string 794 */ 795 public $last_active; 796 797 /* Extras */ 798 799 /** 800 * The total number of "Friends" the user has on site. 801 * 802 * @var integer 803 */ 804 public $total_friends; 805 806 /** 807 * The total number of blog posts posted by the user 808 * 809 * @var integer 810 * @deprecated No longer used 811 */ 812 public $total_blogs; 813 814 /** 815 * The total number of groups the user is a part of. 816 * 817 * Example: "1 group", "2 groups" 818 * 819 * @var string 820 */ 821 public $total_groups; 822 823 /** 824 * Profile information for the specific user. 825 * 826 * @since BuddyPress (1.2.0) 827 * @var array 828 */ 829 public $profile_data; 830 831 /** Public Methods *******************************************************/ 832 833 /** 834 * Class constructor. 835 * 836 * @param integer $user_id The ID for the user being queried. 837 * @param bool $populate_extras Whether to fetch extra information 838 * such as group/friendship counts or not. Default: false. 839 */ 840 public function __construct( $user_id, $populate_extras = false ) { 841 if ( !empty( $user_id ) ) { 842 $this->id = $user_id; 843 $this->populate(); 844 845 if ( !empty( $populate_extras ) ) { 846 $this->populate_extras(); 847 } 848 } 849 } 850 851 /** 852 * Populate the instantiated class with data based on the User ID provided. 853 * 854 * @uses bp_core_get_userurl() Returns the URL with no HTML markup for 855 * a user based on their user id. 856 * @uses bp_core_get_userlink() Returns a HTML formatted link for a 857 * user with the user's full name as the link text. 858 * @uses bp_core_get_user_email() Returns the email address for the 859 * user based on user ID. 860 * @uses bp_get_user_meta() BP function returns the value of passed 861 * usermeta name from usermeta table. 862 * @uses bp_core_fetch_avatar() Returns HTML formatted avatar for a user 863 * @uses bp_profile_last_updated_date() Returns the last updated date 864 * for a user. 865 */ 866 public function populate() { 867 868 if ( bp_is_active( 'xprofile' ) ) 869 $this->profile_data = $this->get_profile_data(); 870 871 if ( !empty( $this->profile_data ) ) { 872 $full_name_field_name = bp_xprofile_fullname_field_name(); 873 874 $this->user_url = bp_core_get_user_domain( $this->id, $this->profile_data['user_nicename'], $this->profile_data['user_login'] ); 875 $this->fullname = esc_attr( $this->profile_data[$full_name_field_name]['field_data'] ); 876 $this->user_link = "<a href='{$this->user_url}' title='{$this->fullname}'>{$this->fullname}</a>"; 877 $this->email = esc_attr( $this->profile_data['user_email'] ); 878 } else { 879 $this->user_url = bp_core_get_user_domain( $this->id ); 880 $this->user_link = bp_core_get_userlink( $this->id ); 881 $this->fullname = esc_attr( bp_core_get_user_displayname( $this->id ) ); 882 $this->email = esc_attr( bp_core_get_user_email( $this->id ) ); 883 } 884 885 // Cache a few things that are fetched often 886 wp_cache_set( 'bp_user_fullname_' . $this->id, $this->fullname, 'bp' ); 887 wp_cache_set( 'bp_user_email_' . $this->id, $this->email, 'bp' ); 888 wp_cache_set( 'bp_user_url_' . $this->id, $this->user_url, 'bp' ); 889 890 $this->avatar = bp_core_fetch_avatar( array( 'item_id' => $this->id, 'type' => 'full', 'alt' => sprintf( __( 'Profile photo of %s', 'buddypress' ), $this->fullname ) ) ); 891 $this->avatar_thumb = bp_core_fetch_avatar( array( 'item_id' => $this->id, 'type' => 'thumb', 'alt' => sprintf( __( 'Profile photo of %s', 'buddypress' ), $this->fullname ) ) ); 892 $this->avatar_mini = bp_core_fetch_avatar( array( 'item_id' => $this->id, 'type' => 'thumb', 'alt' => sprintf( __( 'Profile photo of %s', 'buddypress' ), $this->fullname ), 'width' => 30, 'height' => 30 ) ); 893 $this->last_active = bp_core_get_last_activity( bp_get_user_last_activity( $this->id ), __( 'active %s', 'buddypress' ) ); 894 } 895 896 /** 897 * Populates extra fields such as group and friendship counts. 898 */ 899 public function populate_extras() { 900 901 if ( bp_is_active( 'friends' ) ) { 902 $this->total_friends = BP_Friends_Friendship::total_friend_count( $this->id ); 903 } 904 905 if ( bp_is_active( 'groups' ) ) { 906 $this->total_groups = BP_Groups_Member::total_group_count( $this->id ); 907 $this->total_groups = sprintf( _n( '%d group', '%d groups', $this->total_groups, 'buddypress' ), $this->total_groups ); 908 } 909 } 910 911 /** 912 * Fetch xprofile data for the current user. 913 * 914 * @see BP_XProfile_ProfileData::get_all_for_user() for description of 915 * return value. 916 * 917 * @return array See {@link BP_XProfile_Profile_Data::get_all_for_user()}. 918 */ 919 public function get_profile_data() { 920 return BP_XProfile_ProfileData::get_all_for_user( $this->id ); 921 } 922 923 /** Static Methods ********************************************************/ 924 925 /** 926 * Get a list of users that match the query parameters. 927 * 928 * Since BuddyPress 1.7, use {@link BP_User_Query} instead. 929 * 930 * @deprecated 1.7.0 Use {@link BP_User_Query}. 931 * 932 * @see BP_User_Query for a description of parameters, most of which 933 * are used there in the same way. 934 * 935 * @param string $type See {@link BP_User_Query}. 936 * @param int $limit See {@link BP_User_Query}. Default: 0. 937 * @param int $page See {@link BP_User_Query}. Default: 1. 938 * @param int $user_id See {@link BP_User_Query}. Default: 0. 939 * @param mixed $include See {@link BP_User_Query}. Default: false. 940 * @param string|bool $search_terms See {@link BP_User_Query}. 941 * Default: false. 942 * @param bool $populate_extras See {@link BP_User_Query}. 943 * Default: true. 944 * @param mixed $exclude See {@link BP_User_Query}. Default: false. 945 * @param string|bool $meta_key See {@link BP_User_Query}. 946 * Default: false. 947 * @param string|bool $meta_value See {@link BP_User_Query}. 948 * Default: false. 949 * @return array { 950 * @type int $total_users Total number of users matched by query 951 * params. 952 * @type array $paged_users The current page of users matched by 953 * query params. 954 * } 955 */ 956 public static function get_users( $type, $limit = 0, $page = 1, $user_id = 0, $include = false, $search_terms = false, $populate_extras = true, $exclude = false, $meta_key = false, $meta_value = false ) { 957 global $wpdb; 958 959 _deprecated_function( __METHOD__, '1.7', 'BP_User_Query' ); 960 961 $bp = buddypress(); 962 963 $sql = array(); 964 965 $sql['select_main'] = "SELECT DISTINCT u.ID as id, u.user_registered, u.user_nicename, u.user_login, u.display_name, u.user_email"; 966 967 if ( 'active' == $type || 'online' == $type || 'newest' == $type ) { 968 $sql['select_active'] = ", um.meta_value as last_activity"; 969 } 970 971 if ( 'popular' == $type ) { 972 $sql['select_popular'] = ", um.meta_value as total_friend_count"; 973 } 974 975 if ( 'alphabetical' == $type ) { 976 $sql['select_alpha'] = ", pd.value as fullname"; 977 } 978 979 if ( $meta_key ) { 980 $sql['select_meta'] = ", umm.meta_key"; 981 982 if ( $meta_value ) { 983 $sql['select_meta'] .= ", umm.meta_value"; 984 } 985 } 986 987 $sql['from'] = "FROM {$wpdb->users} u LEFT JOIN {$wpdb->usermeta} um ON um.user_id = u.ID"; 988 989 // We search against xprofile fields, so we must join the table 990 if ( $search_terms && bp_is_active( 'xprofile' ) ) { 991 $sql['join_profiledata_search'] = "LEFT JOIN {$bp->profile->table_name_data} spd ON u.ID = spd.user_id"; 992 } 993 994 // Alphabetical sorting is done by the xprofile Full Name field 995 if ( 'alphabetical' == $type ) { 996 $sql['join_profiledata_alpha'] = "LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id"; 997 } 998 999 if ( $meta_key ) { 1000 $sql['join_meta'] = "LEFT JOIN {$wpdb->usermeta} umm ON umm.user_id = u.ID"; 1001 } 1002 1003 $sql['where'] = 'WHERE ' . bp_core_get_status_sql( 'u.' ); 1004 1005 if ( 'active' == $type || 'online' == $type || 'newest' == $type ) { 1006 $sql['where_active'] = $wpdb->prepare( "AND um.meta_key = %s", bp_get_user_meta_key( 'last_activity' ) ); 1007 } 1008 1009 if ( 'popular' == $type ) { 1010 $sql['where_popular'] = $wpdb->prepare( "AND um.meta_key = %s", bp_get_user_meta_key( 'total_friend_count' ) ); 1011 } 1012 1013 if ( 'online' == $type ) { 1014 $sql['where_online'] = "AND DATE_ADD( um.meta_value, INTERVAL 5 MINUTE ) >= UTC_TIMESTAMP()"; 1015 } 1016 1017 if ( 'alphabetical' == $type ) { 1018 $sql['where_alpha'] = "AND pd.field_id = 1"; 1019 } 1020 1021 if ( !empty( $exclude ) ) { 1022 $exclude = implode( ',', wp_parse_id_list( $exclude ) ); 1023 $sql['where_exclude'] = "AND u.ID NOT IN ({$exclude})"; 1024 } 1025 1026 // Passing an $include value of 0 or '0' will necessarily result in an empty set 1027 // returned. The default value of false will hit the 'else' clause. 1028 if ( 0 === $include || '0' === $include ) { 1029 $sql['where_users'] = "AND 0 = 1"; 1030 } else { 1031 if ( !empty( $include ) ) { 1032 $include = implode( ',', wp_parse_id_list( $include ) ); 1033 $sql['where_users'] = "AND u.ID IN ({$include})"; 1034 } elseif ( !empty( $user_id ) && bp_is_active( 'friends' ) ) { 1035 $friend_ids = friends_get_friend_user_ids( $user_id ); 1036 1037 if ( !empty( $friend_ids ) ) { 1038 $friend_ids = implode( ',', wp_parse_id_list( $friend_ids ) ); 1039 $sql['where_friends'] = "AND u.ID IN ({$friend_ids})"; 1040 1041 // User has no friends, return false since there will be no users to fetch. 1042 } else { 1043 return false; 1044 } 1045 } 1046 } 1047 1048 if ( !empty( $search_terms ) && bp_is_active( 'xprofile' ) ) { 1049 $search_terms_like = '%' . bp_esc_like( $search_terms ) . '%'; 1050 $sql['where_searchterms'] = $wpdb->prepare( "AND spd.value LIKE %s", $search_terms_like ); 1051 } 1052 1053 if ( !empty( $meta_key ) ) { 1054 $sql['where_meta'] = $wpdb->prepare( " AND umm.meta_key = %s", $meta_key ); 1055 1056 // If a meta value is provided, match it 1057 if ( $meta_value ) { 1058 $sql['where_meta'] .= $wpdb->prepare( " AND umm.meta_value = %s", $meta_value ); 1059 } 1060 } 1061 1062 switch ( $type ) { 1063 case 'active': case 'online': default: 1064 $sql[] = "ORDER BY um.meta_value DESC"; 1065 break; 1066 case 'newest': 1067 $sql[] = "ORDER BY u.ID DESC"; 1068 break; 1069 case 'alphabetical': 1070 $sql[] = "ORDER BY pd.value ASC"; 1071 break; 1072 case 'random': 1073 $sql[] = "ORDER BY rand()"; 1074 break; 1075 case 'popular': 1076 $sql[] = "ORDER BY CONVERT(um.meta_value, SIGNED) DESC"; 1077 break; 1078 } 1079 1080 if ( !empty( $limit ) && !empty( $page ) ) { 1081 $sql['pagination'] = $wpdb->prepare( "LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) ); 1082 } 1083 1084 // Get paginated results 1085 $paged_users_sql = apply_filters( 'bp_core_get_paged_users_sql', join( ' ', (array) $sql ), $sql ); 1086 $paged_users = $wpdb->get_results( $paged_users_sql ); 1087 1088 // Re-jig the SQL so we can get the total user count 1089 unset( $sql['select_main'] ); 1090 1091 if ( !empty( $sql['select_active'] ) ) { 1092 unset( $sql['select_active'] ); 1093 } 1094 1095 if ( !empty( $sql['select_popular'] ) ) { 1096 unset( $sql['select_popular'] ); 1097 } 1098 1099 if ( !empty( $sql['select_alpha'] ) ) { 1100 unset( $sql['select_alpha'] ); 1101 } 1102 1103 if ( !empty( $sql['pagination'] ) ) { 1104 unset( $sql['pagination'] ); 1105 } 1106 1107 array_unshift( $sql, "SELECT COUNT(u.ID)" ); 1108 1109 // Get total user results 1110 $total_users_sql = apply_filters( 'bp_core_get_total_users_sql', join( ' ', (array) $sql ), $sql ); 1111 $total_users = $wpdb->get_var( $total_users_sql ); 1112 1113 /*** 1114 * Lets fetch some other useful data in a separate queries, this will be faster than querying the data for every user in a list. 1115 * We can't add these to the main query above since only users who have this information will be returned (since the much of the data is in usermeta and won't support any type of directional join) 1116 */ 1117 if ( !empty( $populate_extras ) ) { 1118 $user_ids = array(); 1119 1120 foreach ( (array) $paged_users as $user ) { 1121 $user_ids[] = $user->id; 1122 } 1123 1124 // Add additional data to the returned results 1125 $paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids, $type ); 1126 } 1127 1128 return array( 'users' => $paged_users, 'total' => $total_users ); 1129 } 1130 1131 1132 /** 1133 * Fetch the details for all users whose usernames start with the given letter. 1134 * 1135 * @global wpdb $wpdb WordPress database object. 1136 * 1137 * @param string $letter The letter the users names are to start with. 1138 * @param int $limit The number of users we wish to retrive. 1139 * @param int $page The page number we are currently on, used in 1140 * conjunction with $limit to get the start position for the 1141 * limit. 1142 * @param bool $populate_extras Populate extra user fields? 1143 * @param string $exclude Comma-separated IDs of users whose results 1144 * aren't to be fetched. 1145 * @return mixed False on error, otherwise associative array of results. 1146 */ 1147 public static function get_users_by_letter( $letter, $limit = null, $page = 1, $populate_extras = true, $exclude = '' ) { 1148 global $wpdb; 1149 1150 $pag_sql = ''; 1151 if ( $limit && $page ) { 1152 $pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) ); 1153 } 1154 1155 // Multibyte compliance 1156 if ( function_exists( 'mb_strlen' ) ) { 1157 if ( mb_strlen( $letter, 'UTF-8' ) > 1 || is_numeric( $letter ) || !$letter ) { 1158 return false; 1159 } 1160 } else { 1161 if ( strlen( $letter ) > 1 || is_numeric( $letter ) || !$letter ) { 1162 return false; 1163 } 1164 } 1165 1166 $bp = buddypress(); 1167 1168 $letter_like = bp_esc_like( $letter ) . '%'; 1169 $status_sql = bp_core_get_status_sql( 'u.' ); 1170 1171 if ( !empty( $exclude ) ) { 1172 $exclude = implode( ',', wp_parse_id_list( $exclude ) ); 1173 $exclude_sql = " AND u.id NOT IN ({$exclude})"; 1174 } else { 1175 $exclude_sql = ''; 1176 } 1177 1178 $total_users_sql = apply_filters( 'bp_core_users_by_letter_count_sql', $wpdb->prepare( "SELECT COUNT(DISTINCT u.ID) FROM {$wpdb->users} u LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id LEFT JOIN {$bp->profile->table_name_fields} pf ON pd.field_id = pf.id WHERE {$status_sql} AND pf.name = %s {$exclude_sql} AND pd.value LIKE %s ORDER BY pd.value ASC", bp_xprofile_fullname_field_name(), $letter_like ) ); 1179 $paged_users_sql = apply_filters( 'bp_core_users_by_letter_sql', $wpdb->prepare( "SELECT DISTINCT u.ID as id, u.user_registered, u.user_nicename, u.user_login, u.user_email FROM {$wpdb->users} u LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id LEFT JOIN {$bp->profile->table_name_fields} pf ON pd.field_id = pf.id WHERE {$status_sql} AND pf.name = %s {$exclude_sql} AND pd.value LIKE %s ORDER BY pd.value ASC{$pag_sql}", bp_xprofile_fullname_field_name(), $letter_like ) ); 1180 1181 $total_users = $wpdb->get_var( $total_users_sql ); 1182 $paged_users = $wpdb->get_results( $paged_users_sql ); 1183 1184 /*** 1185 * Lets fetch some other useful data in a separate queries, this will be 1186 * faster than querying the data for every user in a list. We can't add 1187 * these to the main query above since only users who have this 1188 * information will be returned (since the much of the data is in 1189 * usermeta and won't support any type of directional join) 1190 */ 1191 $user_ids = array(); 1192 foreach ( (array) $paged_users as $user ) 1193 $user_ids[] = (int) $user->id; 1194 1195 // Add additional data to the returned results 1196 if ( $populate_extras ) { 1197 $paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids ); 1198 } 1199 1200 return array( 'users' => $paged_users, 'total' => $total_users ); 1201 } 1202 1203 /** 1204 * Get details of specific users from the database. 1205 * 1206 * Use {@link BP_User_Query} with the 'user_ids' param instead. 1207 * 1208 * @global wpdb $wpdb WordPress database object. 1209 * @param array $user_ids The user IDs of the users who we wish to 1210 * fetch information on. 1211 * @param int $limit The limit of results we want. 1212 * @param int $page The page we are on for pagination. 1213 * @param bool $populate_extras Populate extra user fields? 1214 * @return array Associative array. 1215 */ 1216 public static function get_specific_users( $user_ids, $limit = null, $page = 1, $populate_extras = true ) { 1217 global $wpdb; 1218 1219 $pag_sql = ''; 1220 if ( $limit && $page ) 1221 $pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) ); 1222 1223 $user_ids = implode( ',', wp_parse_id_list( $user_ids ) ); 1224 $status_sql = bp_core_get_status_sql(); 1225 1226 $total_users_sql = apply_filters( 'bp_core_get_specific_users_count_sql', "SELECT COUNT(ID) FROM {$wpdb->users} WHERE {$status_sql} AND ID IN ({$user_ids})" ); 1227 $paged_users_sql = apply_filters( 'bp_core_get_specific_users_count_sql', "SELECT ID as id, user_registered, user_nicename, user_login, user_email FROM {$wpdb->users} WHERE {$status_sql} AND ID IN ({$user_ids}) {$pag_sql}" ); 1228 1229 $total_users = $wpdb->get_var( $total_users_sql ); 1230 $paged_users = $wpdb->get_results( $paged_users_sql ); 1231 1232 /*** 1233 * Lets fetch some other useful data in a separate queries, this will be 1234 * faster than querying the data for every user in a list. We can't add 1235 * these to the main query above since only users who have this 1236 * information will be returned (since the much of the data is in 1237 * usermeta and won't support any type of directional join) 1238 */ 1239 1240 // Add additional data to the returned results 1241 if ( !empty( $populate_extras ) ) { 1242 $paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids ); 1243 } 1244 1245 return array( 'users' => $paged_users, 'total' => $total_users ); 1246 } 1247 1248 /** 1249 * Find users who match on the value of an xprofile data. 1250 * 1251 * @global wpdb $wpdb WordPress database object. 1252 * 1253 * @param string $search_terms The terms to search the profile table 1254 * value column for. 1255 * @param integer $limit The limit of results we want. 1256 * @param integer $page The page we are on for pagination. 1257 * @param boolean $populate_extras Populate extra user fields? 1258 * @return array Associative array. 1259 */ 1260 public static function search_users( $search_terms, $limit = null, $page = 1, $populate_extras = true ) { 1261 global $wpdb; 1262 1263 $bp = buddypress(); 1264 1265 $user_ids = array(); 1266 $pag_sql = $limit && $page ? $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * intval( $limit ) ), intval( $limit ) ) : ''; 1267 1268 $search_terms_like = '%' . bp_esc_like( $search_terms ) . '%'; 1269 $status_sql = bp_core_get_status_sql( 'u.' ); 1270 1271 $total_users_sql = apply_filters( 'bp_core_search_users_count_sql', $wpdb->prepare( "SELECT COUNT(DISTINCT u.ID) as id FROM {$wpdb->users} u LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id WHERE {$status_sql} AND pd.value LIKE %s ORDER BY pd.value ASC", $search_terms_like ), $search_terms ); 1272 $paged_users_sql = apply_filters( 'bp_core_search_users_sql', $wpdb->prepare( "SELECT DISTINCT u.ID as id, u.user_registered, u.user_nicename, u.user_login, u.user_email FROM {$wpdb->users} u LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id WHERE {$status_sql} AND pd.value LIKE %s ORDER BY pd.value ASC{$pag_sql}", $search_terms_like ), $search_terms, $pag_sql ); 1273 1274 $total_users = $wpdb->get_var( $total_users_sql ); 1275 $paged_users = $wpdb->get_results( $paged_users_sql ); 1276 1277 /*** 1278 * Lets fetch some other useful data in a separate queries, this will be faster than querying the data for every user in a list. 1279 * We can't add these to the main query above since only users who have this information will be returned (since the much of the data is in usermeta and won't support any type of directional join) 1280 */ 1281 foreach ( (array) $paged_users as $user ) 1282 $user_ids[] = $user->id; 1283 1284 // Add additional data to the returned results 1285 if ( $populate_extras ) 1286 $paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids ); 1287 1288 return array( 'users' => $paged_users, 'total' => $total_users ); 1289 } 1290 1291 /** 1292 * Fetch extra user information, such as friend count and last profile update message. 1293 * 1294 * Accepts multiple user IDs to fetch data for. 1295 * 1296 * @global wpdb $wpdb WordPress database object. 1297 * 1298 * @param array $paged_users An array of stdClass containing the users. 1299 * @param string $user_ids The user ids to select information about. 1300 * @param string $type The type of fields we wish to get. 1301 * @return mixed False on error, otherwise associative array of results. 1302 */ 1303 public static function get_user_extras( &$paged_users, &$user_ids, $type = false ) { 1304 global $wpdb; 1305 1306 $bp = buddypress(); 1307 1308 if ( empty( $user_ids ) ) 1309 return $paged_users; 1310 1311 // Sanitize user IDs 1312 $user_ids = implode( ',', wp_parse_id_list( $user_ids ) ); 1313 1314 // Fetch the user's full name 1315 if ( bp_is_active( 'xprofile' ) && 'alphabetical' != $type ) { 1316 $names = $wpdb->get_results( $wpdb->prepare( "SELECT pd.user_id as id, pd.value as fullname FROM {$bp->profile->table_name_fields} pf, {$bp->profile->table_name_data} pd WHERE pf.id = pd.field_id AND pf.name = %s AND pd.user_id IN ( {$user_ids} )", bp_xprofile_fullname_field_name() ) ); 1317 for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) { 1318 foreach ( (array) $names as $name ) { 1319 if ( $name->id == $paged_users[$i]->id ) 1320 $paged_users[$i]->fullname = $name->fullname; 1321 } 1322 } 1323 } 1324 1325 // Fetch the user's total friend count 1326 if ( 'popular' != $type ) { 1327 $friend_count = $wpdb->get_results( $wpdb->prepare( "SELECT user_id as id, meta_value as total_friend_count FROM {$wpdb->usermeta} WHERE meta_key = %s AND user_id IN ( {$user_ids} )", bp_get_user_meta_key( 'total_friend_count' ) ) ); 1328 for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) { 1329 foreach ( (array) $friend_count as $fcount ) { 1330 if ( $fcount->id == $paged_users[$i]->id ) 1331 $paged_users[$i]->total_friend_count = (int) $fcount->total_friend_count; 1332 } 1333 } 1334 } 1335 1336 // Fetch whether or not the user is a friend 1337 if ( bp_is_active( 'friends' ) ) { 1338 $friend_status = $wpdb->get_results( $wpdb->prepare( "SELECT initiator_user_id, friend_user_id, is_confirmed FROM {$bp->friends->table_name} WHERE (initiator_user_id = %d AND friend_user_id IN ( {$user_ids} ) ) OR (initiator_user_id IN ( {$user_ids} ) AND friend_user_id = %d )", bp_loggedin_user_id(), bp_loggedin_user_id() ) ); 1339 for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) { 1340 foreach ( (array) $friend_status as $status ) { 1341 if ( $status->initiator_user_id == $paged_users[$i]->id || $status->friend_user_id == $paged_users[$i]->id ) 1342 $paged_users[$i]->is_friend = $status->is_confirmed; 1343 } 1344 } 1345 } 1346 1347 if ( 'active' != $type ) { 1348 $user_activity = $wpdb->get_results( $wpdb->prepare( "SELECT user_id as id, meta_value as last_activity FROM {$wpdb->usermeta} WHERE meta_key = %s AND user_id IN ( {$user_ids} )", bp_get_user_meta_key( 'last_activity' ) ) ); 1349 for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) { 1350 foreach ( (array) $user_activity as $activity ) { 1351 if ( $activity->id == $paged_users[$i]->id ) 1352 $paged_users[$i]->last_activity = $activity->last_activity; 1353 } 1354 } 1355 } 1356 1357 // Fetch the user's last_activity 1358 if ( 'active' != $type ) { 1359 $user_activity = $wpdb->get_results( $wpdb->prepare( "SELECT user_id as id, meta_value as last_activity FROM {$wpdb->usermeta} WHERE meta_key = %s AND user_id IN ( {$user_ids} )", bp_get_user_meta_key( 'last_activity' ) ) ); 1360 for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) { 1361 foreach ( (array) $user_activity as $activity ) { 1362 if ( $activity->id == $paged_users[$i]->id ) 1363 $paged_users[$i]->last_activity = $activity->last_activity; 1364 } 1365 } 1366 } 1367 1368 // Fetch the user's latest update 1369 $user_update = $wpdb->get_results( $wpdb->prepare( "SELECT user_id as id, meta_value as latest_update FROM {$wpdb->usermeta} WHERE meta_key = %s AND user_id IN ( {$user_ids} )", bp_get_user_meta_key( 'bp_latest_update' ) ) ); 1370 for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) { 1371 foreach ( (array) $user_update as $update ) { 1372 if ( $update->id == $paged_users[$i]->id ) 1373 $paged_users[$i]->latest_update = $update->latest_update; 1374 } 1375 } 1376 1377 return $paged_users; 1378 } 1379 1380 /** 1381 * Get WordPress user details for a specified user. 1382 * 1383 * @global wpdb $wpdb WordPress database object. 1384 * 1385 * @param integer $user_id User ID. 1386 * @return array Associative array. 1387 */ 1388 public static function get_core_userdata( $user_id ) { 1389 global $wpdb; 1390 1391 if ( !$user = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->users} WHERE ID = %d LIMIT 1", $user_id ) ) ) 1392 return false; 1393 1394 return $user; 1395 } 1396 1397 /** 1398 * Get last activity data for a user or set of users. 1399 * 1400 * @param int|array User IDs or multiple user IDs. 1401 * @return array 1402 */ 1403 public static function get_last_activity( $user_id ) { 1404 global $wpdb; 1405 1406 // Sanitize and remove empty values 1407 $user_ids = array_filter( wp_parse_id_list( $user_id ) ); 1408 1409 if ( empty( $user_ids ) ) { 1410 return false; 1411 } 1412 1413 $uncached_user_ids = bp_get_non_cached_ids( $user_ids, 'bp_last_activity' ); 1414 if ( ! empty( $uncached_user_ids ) ) { 1415 $bp = buddypress(); 1416 1417 $user_ids_sql = implode( ',', $uncached_user_ids ); 1418 $user_count = count( $uncached_user_ids ); 1419 1420 $last_activities = $wpdb->get_results( $wpdb->prepare( "SELECT id, user_id, date_recorded FROM {$bp->members->table_name_last_activity} WHERE component = %s AND type = 'last_activity' AND user_id IN ({$user_ids_sql}) LIMIT {$user_count}", $bp->members->id ) ); 1421 1422 foreach ( $last_activities as $last_activity ) { 1423 wp_cache_set( $last_activity->user_id, array( 1424 'user_id' => $last_activity->user_id, 1425 'date_recorded' => $last_activity->date_recorded, 1426 'activity_id' => $last_activity->id, 1427 ), 'bp_last_activity' ); 1428 } 1429 } 1430 1431 // Fetch all user data from the cache 1432 $retval = array(); 1433 foreach ( $user_ids as $user_id ) { 1434 $retval[ $user_id ] = wp_cache_get( $user_id, 'bp_last_activity' ); 1435 } 1436 1437 return $retval; 1438 } 1439 1440 /** 1441 * Set a user's last_activity value. 1442 * 1443 * Will create a new entry if it does not exist. Otherwise updates the 1444 * existing entry. 1445 * 1446 * @since 2.0 1447 * 1448 * @param int $user_id ID of the user whose last_activity you are updating. 1449 * @param string $time MySQL-formatted time string. 1450 * @return bool True on success, false on failure. 1451 */ 1452 public static function update_last_activity( $user_id, $time ) { 1453 global $wpdb; 1454 1455 $table_name = buddypress()->members->table_name_last_activity; 1456 1457 $activity = self::get_last_activity( $user_id ); 1458 1459 if ( ! empty( $activity[ $user_id ] ) ) { 1460 $updated = $wpdb->update( 1461 $table_name, 1462 1463 // Data to update 1464 array( 1465 'date_recorded' => $time, 1466 ), 1467 1468 // WHERE 1469 array( 1470 'id' => $activity[ $user_id ]['activity_id'], 1471 ), 1472 1473 // Data sanitization format 1474 array( 1475 '%s', 1476 ), 1477 1478 // WHERE sanitization format 1479 array( 1480 '%d', 1481 ) 1482 ); 1483 1484 // add new date to existing activity entry for caching 1485 $activity[ $user_id ]['date_recorded'] = $time; 1486 1487 } else { 1488 $updated = $wpdb->insert( 1489 $table_name, 1490 1491 // Data 1492 array( 1493 'user_id' => $user_id, 1494 'component' => buddypress()->members->id, 1495 'type' => 'last_activity', 1496 'action' => '', 1497 'content' => '', 1498 'primary_link' => '', 1499 'item_id' => 0, 1500 'date_recorded' => $time, 1501 ), 1502 1503 // Data sanitization format 1504 array( 1505 '%d', 1506 '%s', 1507 '%s', 1508 '%s', 1509 '%s', 1510 '%s', 1511 '%d', 1512 '%s', 1513 ) 1514 ); 1515 1516 // set up activity array for caching 1517 // view the foreach loop in the get_last_activity() method for format 1518 $activity = array(); 1519 $activity[ $user_id ] = array( 1520 'user_id' => $user_id, 1521 'date_recorded' => $time, 1522 'activity_id' => $wpdb->insert_id, 1523 ); 1524 } 1525 1526 // set cache 1527 wp_cache_set( $user_id, $activity[ $user_id ], 'bp_last_activity' ); 1528 1529 return $updated; 1530 } 1531 1532 /** 1533 * Delete a user's last_activity value. 1534 * 1535 * @since 2.0 1536 * 1537 * @param int $user_id 1538 * @return bool True on success, false on failure or if no last_activity 1539 * is found for the user. 1540 */ 1541 public static function delete_last_activity( $user_id ) { 1542 global $wpdb; 1543 1544 $existing = self::get_last_activity( $user_id ); 1545 1546 if ( empty( $existing ) ) { 1547 return false; 1548 } 1549 1550 $deleted = $wpdb->delete( 1551 buddypress()->members->table_name_last_activity, 1552 1553 // WHERE 1554 array( 1555 'id' => $existing[ $user_id ]['activity_id'], 1556 ), 1557 1558 // WHERE sanitization format 1559 array( 1560 '%s', 1561 ) 1562 ); 1563 1564 wp_cache_delete( $user_id, 'bp_last_activity' ); 1565 1566 return $deleted; 1567 } 1568 } 1569 1570 if ( class_exists( 'WP_Date_Query' ) ) : 1571 /** 1572 * BuddyPress date query class. 1573 * 1574 * Extends the {@link WP_Date_Query} class for use with BuddyPress. 1575 * 1576 * @since BuddyPress (2.1.0) 1577 * 1578 * @param array $date_query { 1579 * Date query arguments. See first parameter of {@link WP_Date_Query::__construct()}. 1580 * } 1581 * @param string $column The DB column to query against. 1582 */ 1583 class BP_Date_Query extends WP_Date_Query { 1584 /** 1585 * The column to query against. Can be changed via the query arguments. 1586 * 1587 * @var string 1588 */ 1589 public $column; 1590 1591 /** 1592 * Constructor. 1593 * 1594 * @see WP_Date_Query::__construct() 1595 */ 1596 public function __construct( $date_query, $column = '' ) { 1597 if ( ! empty( $column ) ) { 1598 $this->column = $column; 1599 add_filter( 'date_query_valid_columns', array( $this, 'register_date_column' ) ); 1600 } 1601 1602 parent::__construct( $date_query, $column ); 1603 } 1604 1605 /** 1606 * Destructor. 1607 */ 1608 public function __destruct() { 1609 remove_filter( 'date_query_valid_columns', array( $this, 'register_date_column' ) ); 1610 } 1611 1612 /** 1613 * Registers our date column with WP Date Query to pass validation. 1614 * 1615 * @param array $retval Current DB columns 1616 * @return array 1617 */ 1618 public function register_date_column( $retval = array() ) { 1619 $retval[] = $this->column; 1620 return $retval; 1621 } 1622 } 1623 endif; 1624 1625 /** 1626 * BP_Core_Notification is deprecated. 1627 * 1628 * Use BP_Notifications_Notification instead. 1629 * 1630 * @package BuddyPress Core 1631 * @deprecated since BuddyPress (1.9) 1632 */ 1633 class BP_Core_Notification { 1634 1635 /** 1636 * The notification id 1637 * 1638 * @var integer 1639 */ 1640 public $id; 1641 1642 /** 1643 * The ID to which the notification relates to within the component. 1644 * 1645 * @var integer 1646 */ 1647 public $item_id; 1648 1649 /** 1650 * The secondary ID to which the notification relates to within the component. 1651 * 1652 * @var integer 1653 */ 1654 public $secondary_item_id = null; 1655 1656 /** 1657 * The user ID for who the notification is for. 1658 * 1659 * @var integer 1660 */ 1661 public $user_id; 1662 1663 /** 1664 * The name of the component that the notification is for. 1665 * 1666 * @var string 1667 */ 1668 public $component_name; 1669 1670 /** 1671 * The action within the component which the notification is related to. 1672 * 1673 * @var string 1674 */ 1675 public $component_action; 1676 1677 /** 1678 * The date the notification was created. 1679 * 1680 * @var string 1681 */ 1682 public $date_notified; 1683 1684 /** 1685 * Is the notification new or has it already been read. 1686 * 1687 * @var boolean 1688 */ 1689 public $is_new; 1690 1691 /** Public Methods ********************************************************/ 1692 1693 /** 1694 * Constructor 1695 * 1696 * @param integer $id 1697 */ 1698 public function __construct( $id = 0 ) { 1699 if ( !empty( $id ) ) { 1700 $this->id = $id; 1701 $this->populate(); 1702 } 1703 } 1704 1705 /** 1706 * Update or insert notification details into the database. 1707 * 1708 * @global wpdb $wpdb WordPress database object 1709 * @return bool Success or failure 1710 */ 1711 public function save() { 1712 global $wpdb; 1713 1714 $bp = buddypress(); 1715 1716 // Update 1717 if ( !empty( $this->id ) ) { 1718 $sql = $wpdb->prepare( "UPDATE {$bp->core->table_name_notifications} SET item_id = %d, secondary_item_id = %d, user_id = %d, component_name = %s, component_action = %d, date_notified = %s, is_new = %d ) WHERE id = %d", $this->item_id, $this->secondary_item_id, $this->user_id, $this->component_name, $this->component_action, $this->date_notified, $this->is_new, $this->id ); 1719 1720 // Save 1721 } else { 1722 $sql = $wpdb->prepare( "INSERT INTO {$bp->core->table_name_notifications} ( item_id, secondary_item_id, user_id, component_name, component_action, date_notified, is_new ) VALUES ( %d, %d, %d, %s, %s, %s, %d )", $this->item_id, $this->secondary_item_id, $this->user_id, $this->component_name, $this->component_action, $this->date_notified, $this->is_new ); 1723 } 1724 1725 if ( !$result = $wpdb->query( $sql ) ) 1726 return false; 1727 1728 $this->id = $wpdb->insert_id; 1729 1730 return true; 1731 } 1732 1733 /** Private Methods *******************************************************/ 1734 1735 /** 1736 * Fetches the notification data from the database. 1737 * 1738 * @global wpdb $wpdb WordPress database object 1739 */ 1740 public function populate() { 1741 global $wpdb; 1742 1743 $bp = buddypress(); 1744 1745 if ( $notification = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->core->table_name_notifications} WHERE id = %d", $this->id ) ) ) { 1746 $this->item_id = $notification->item_id; 1747 $this->secondary_item_id = $notification->secondary_item_id; 1748 $this->user_id = $notification->user_id; 1749 $this->component_name = $notification->component_name; 1750 $this->component_action = $notification->component_action; 1751 $this->date_notified = $notification->date_notified; 1752 $this->is_new = $notification->is_new; 1753 } 1754 } 1755 1756 /** Static Methods ********************************************************/ 1757 1758 public static function check_access( $user_id, $notification_id ) { 1759 global $wpdb; 1760 1761 $bp = buddypress(); 1762 1763 return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$bp->core->table_name_notifications} WHERE id = %d AND user_id = %d", $notification_id, $user_id ) ); 1764 } 1765 1766 /** 1767 * Fetches all the notifications in the database for a specific user. 1768 * 1769 * @global wpdb $wpdb WordPress database object 1770 * @param integer $user_id User ID 1771 * @param string $status 'is_new' or 'all' 1772 * @return array Associative array 1773 * @static 1774 */ 1775 public static function get_all_for_user( $user_id, $status = 'is_new' ) { 1776 global $wpdb; 1777 1778 $bp = buddypress(); 1779 1780 $is_new = ( 'is_new' === $status ) 1781 ? ' AND is_new = 1 ' 1782 : ''; 1783 1784 return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$bp->core->table_name_notifications} WHERE user_id = %d {$is_new}", $user_id ) ); 1785 } 1786 1787 /** 1788 * Delete all the notifications for a user based on the component name and action. 1789 * 1790 * @global wpdb $wpdb WordPress database object 1791 * @param integer $user_id 1792 * @param string $component_name 1793 * @param string $component_action 1794 * @static 1795 */ 1796 public static function delete_for_user_by_type( $user_id, $component_name, $component_action ) { 1797 global $wpdb; 1798 1799 $bp = buddypress(); 1800 1801 return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->core->table_name_notifications} WHERE user_id = %d AND component_name = %s AND component_action = %s", $user_id, $component_name, $component_action ) ); 1802 } 1803 1804 /** 1805 * Delete all the notifications that have a specific item id, component name and action. 1806 * 1807 * @global wpdb $wpdb WordPress database object 1808 * @param integer $user_id The ID of the user who the notifications are for. 1809 * @param integer $item_id The item ID of the notifications we wish to delete. 1810 * @param string $component_name The name of the component that the notifications we wish to delete. 1811 * @param string $component_action The action of the component that the notifications we wish to delete. 1812 * @param integer $secondary_item_id (optional) The secondary item id of the notifications that we wish to use to delete. 1813 * @static 1814 */ 1815 public static function delete_for_user_by_item_id( $user_id, $item_id, $component_name, $component_action, $secondary_item_id = false ) { 1816 global $wpdb; 1817 1818 $bp = buddypress(); 1819 1820 $secondary_item_sql = !empty( $secondary_item_id ) 1821 ? $wpdb->prepare( " AND secondary_item_id = %d", $secondary_item_id ) 1822 : ''; 1823 1824 return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->core->table_name_notifications} WHERE user_id = %d AND item_id = %d AND component_name = %s AND component_action = %s{$secondary_item_sql}", $user_id, $item_id, $component_name, $component_action ) ); 1825 } 1826 1827 /** 1828 * Deletes all the notifications sent by a specific user, by component and action. 1829 * 1830 * @global wpdb $wpdb WordPress database object 1831 * @param integer $user_id The ID of the user whose sent notifications we wish to delete. 1832 * @param string $component_name The name of the component the notification was sent from. 1833 * @param string $component_action The action of the component the notification was sent from. 1834 * @static 1835 */ 1836 public static function delete_from_user_by_type( $user_id, $component_name, $component_action ) { 1837 global $wpdb; 1838 1839 $bp = buddypress(); 1840 1841 return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->core->table_name_notifications} WHERE item_id = %d AND component_name = %s AND component_action = %s", $user_id, $component_name, $component_action ) ); 1842 } 1843 1844 /** 1845 * Deletes all the notifications for all users by item id, and optional secondary item id, and component name and action. 1846 * 1847 * @global wpdb $wpdb WordPress database object 1848 * @param string $item_id The item id that they notifications are to be for. 1849 * @param string $component_name The component that the notifications are to be from. 1850 * @param string $component_action The action that the notifications are to be from. 1851 * @param string $secondary_item_id Optional secondary item id that the notifications are to have. 1852 * @static 1853 */ 1854 public static function delete_all_by_type( $item_id, $component_name, $component_action, $secondary_item_id ) { 1855 global $wpdb; 1856 1857 if ( $component_action ) 1858 $component_action_sql = $wpdb->prepare( "AND component_action = %s", $component_action ); 1859 else 1860 $component_action_sql = ''; 1861 1862 if ( $secondary_item_id ) 1863 $secondary_item_sql = $wpdb->prepare( "AND secondary_item_id = %d", $secondary_item_id ); 1864 else 1865 $secondary_item_sql = ''; 1866 1867 $bp = buddypress(); 1868 1869 return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->core->table_name_notifications} WHERE item_id = %d AND component_name = %s {$component_action_sql} {$secondary_item_sql}", $item_id, $component_name ) ); 1870 } 1871 } 1872 1873 /** 1874 * API to create BuddyPress buttons. 1875 * 1876 * @since BuddyPress (1.2.6) 1877 * 1878 * @param array $args { 1879 * Array of arguments. 1880 * @type string $id String describing the button type. 1881 * @type string $component The name of the component the button belongs to. 1882 * Default: 'core'. 1883 * @type bool $must_be_logged_in Optional. Does the user need to be logged 1884 * in to see this button? Default: true. 1885 * @type bool $block_self Optional. True if the button should be hidden 1886 * when a user is viewing his own profile. Default: true. 1887 * @type string|bool $wrapper Optional. HTML element type that should wrap 1888 * the button: 'div', 'span', 'p', or 'li'. False for no wrapper at 1889 * all. Default: 'div'. 1890 * @type string $wrapper_id Optional. DOM ID of the button wrapper element. 1891 * Default: ''. 1892 * @type string $wrapper_class Optional. DOM class of the button wrapper 1893 * element. Default: ''. 1894 * @type string $link_href Optional. Destination link of the button. 1895 * Default: ''. 1896 * @type string $link_class Optional. DOM class of the button. Default: ''. 1897 * @type string $link_id Optional. DOM ID of the button. Default: ''. 1898 * @type string $link_rel Optional. DOM 'rel' attribute of the button. 1899 * Default: ''. 1900 * @type string $link_title Optional. Title attribute of the button. 1901 * Default: ''. 1902 * @type string $link_text Optional. Text to appear on the button. 1903 * Default: ''. 1904 * } 1905 */ 1906 class BP_Button { 1907 1908 /** Button properties *****************************************************/ 1909 1910 /** 1911 * The button ID. 1912 * 1913 * @var string 1914 */ 1915 public $id = ''; 1916 1917 /** 1918 * The name of the component that the button belongs to. 1919 * 1920 * @var string 1921 */ 1922 public $component = 'core'; 1923 1924 /** 1925 * Does the user need to be logged in to see this button? 1926 * 1927 * @var bool 1928 */ 1929 public $must_be_logged_in = true; 1930 1931 /** 1932 * Whether the button should be hidden when viewing your own profile. 1933 * 1934 * @var bool 1935 */ 1936 public $block_self = true; 1937 1938 /** Wrapper ***************************************************************/ 1939 1940 /** 1941 * The type of DOM element to use for a wrapper. 1942 * 1943 * @var string|bool 'div', 'span', 'p', 'li', or false for no wrapper. 1944 */ 1945 public $wrapper = 'div'; 1946 1947 /** 1948 * The DOM class of the button wrapper. 1949 * 1950 * @var string 1951 */ 1952 public $wrapper_class = ''; 1953 1954 /** 1955 * The DOM ID of the button wrapper. 1956 * 1957 * @var string 1958 */ 1959 public $wrapper_id = ''; 1960 1961 /** Button ****************************************************************/ 1962 1963 /** 1964 * The destination link of the button. 1965 * 1966 * @var string 1967 */ 1968 public $link_href = ''; 1969 1970 /** 1971 * The DOM class of the button link. 1972 * 1973 * @var string 1974 */ 1975 public $link_class = ''; 1976 1977 /** 1978 * The DOM ID of the button link. 1979 * 1980 * @var string 1981 */ 1982 public $link_id = ''; 1983 1984 /** 1985 * The DOM rel value of the button link. 1986 * 1987 * @var string 1988 */ 1989 public $link_rel = ''; 1990 1991 /** 1992 * Title of the button link. 1993 * 1994 * @var string 1995 */ 1996 public $link_title = ''; 1997 1998 /** 1999 * The contents of the button link. 2000 * 2001 * @var string 2002 */ 2003 public $link_text = ''; 2004 2005 /** HTML result ***********************************************************/ 2006 2007 public $contents = ''; 2008 2009 /** Methods ***************************************************************/ 2010 2011 /** 2012 * Builds the button based on class parameters. 2013 * 2014 * @since BuddyPress (1.2.6) 2015 * 2016 * @param array $args See {@BP_Button}. 2017 * @return bool|null Returns false when the button is not allowed for 2018 * the current context. 2019 */ 2020 public function __construct( $args = '' ) { 2021 2022 $r = wp_parse_args( $args, get_class_vars( __CLASS__ ) ); 2023 2024 // Required button properties 2025 $this->id = $r['id']; 2026 $this->component = $r['component']; 2027 $this->must_be_logged_in = (bool) $r['must_be_logged_in']; 2028 $this->block_self = (bool) $r['block_self']; 2029 $this->wrapper = $r['wrapper']; 2030 2031 // $id and $component are required 2032 if ( empty( $r['id'] ) || empty( $r['component'] ) ) 2033 return false; 2034 2035 // No button if component is not active 2036 if ( ! bp_is_active( $this->component ) ) 2037 return false; 2038 2039 // No button for guests if must be logged in 2040 if ( true == $this->must_be_logged_in && ! is_user_logged_in() ) 2041 return false; 2042 2043 // block_self 2044 if ( true == $this->block_self ) { 2045 // No button if you are the current user in a members loop 2046 // This condition takes precedence, because members loops 2047 // can be found on user profiles 2048 if ( bp_get_member_user_id() ) { 2049 if ( is_user_logged_in() && bp_loggedin_user_id() == bp_get_member_user_id() ) { 2050 return false; 2051 } 2052 2053 // No button if viewing your own profile (and not in 2054 // a members loop) 2055 } elseif ( bp_is_my_profile() ) { 2056 return false; 2057 } 2058 } 2059 2060 // Wrapper properties 2061 if ( false !== $this->wrapper ) { 2062 2063 // Wrapper ID 2064 if ( !empty( $r['wrapper_id'] ) ) { 2065 $this->wrapper_id = ' id="' . $r['wrapper_id'] . '"'; 2066 } 2067 2068 // Wrapper class 2069 if ( !empty( $r['wrapper_class'] ) ) { 2070 $this->wrapper_class = ' class="generic-button ' . $r['wrapper_class'] . '"'; 2071 } else { 2072 $this->wrapper_class = ' class="generic-button"'; 2073 } 2074 2075 // Set before and after 2076 $before = '<' . $r['wrapper'] . $this->wrapper_class . $this->wrapper_id . '>'; 2077 $after = '</' . $r['wrapper'] . '>'; 2078 2079 // No wrapper 2080 } else { 2081 $before = $after = ''; 2082 } 2083 2084 // Link properties 2085 if ( !empty( $r['link_id'] ) ) $this->link_id = ' id="' . $r['link_id'] . '"'; 2086 if ( !empty( $r['link_href'] ) ) $this->link_href = ' href="' . $r['link_href'] . '"'; 2087 if ( !empty( $r['link_title'] ) ) $this->link_title = ' title="' . $r['link_title'] . '"'; 2088 if ( !empty( $r['link_rel'] ) ) $this->link_rel = ' rel="' . $r['link_rel'] . '"'; 2089 if ( !empty( $r['link_class'] ) ) $this->link_class = ' class="' . $r['link_class'] . '"'; 2090 if ( !empty( $r['link_text'] ) ) $this->link_text = $r['link_text']; 2091 2092 // Build the button 2093 $this->contents = $before . '<a'. $this->link_href . $this->link_title . $this->link_id . $this->link_rel . $this->link_class . '>' . $this->link_text . '</a>' . $after; 2094 2095 // Allow button to be manipulated externally 2096 $this->contents = apply_filters( 'bp_button_' . $this->component . '_' . $this->id, $this->contents, $this, $before, $after ); 2097 } 2098 2099 /** 2100 * Return the markup for the generated button. 2101 * 2102 * @since BuddyPress (1.2.6) 2103 * 2104 * @return string Button markup. 2105 */ 2106 public function contents() { 2107 return $this->contents; 2108 } 2109 2110 /** 2111 * Output the markup of button. 2112 * 2113 * @since BuddyPress (1.2.6) 2114 */ 2115 public function display() { 2116 if ( !empty( $this->contents ) ) 2117 echo $this->contents; 2118 } 2119 } 2120 2121 /** 2122 * Enable oEmbeds in BuddyPress contexts. 2123 * 2124 * Extends WP_Embed class for use with BuddyPress. 2125 * 2126 * @since BuddyPress (1.5.0) 2127 * 2128 * @see WP_Embed 2129 */ 2130 class BP_Embed extends WP_Embed { 2131 2132 /** 2133 * Constructor 2134 * 2135 * @global WP_Embed $wp_embed 2136 */ 2137 public function __construct() { 2138 global $wp_embed; 2139 2140 // Make sure we populate the WP_Embed handlers array. 2141 // These are providers that use a regex callback on the URL in question. 2142 // Do not confuse with oEmbed providers, which require an external ping. 2143 // Used in WP_Embed::shortcode() 2144 $this->handlers = $wp_embed->handlers; 2145 2146 if ( bp_use_embed_in_activity() ) { 2147 add_filter( 'bp_get_activity_content_body', array( &$this, 'autoembed' ), 8 ); 2148 add_filter( 'bp_get_activity_content_body', array( &$this, 'run_shortcode' ), 7 ); 2149 } 2150 2151 if ( bp_use_embed_in_activity_replies() ) { 2152 add_filter( 'bp_get_activity_content', array( &$this, 'autoembed' ), 8 ); 2153 add_filter( 'bp_get_activity_content', array( &$this, 'run_shortcode' ), 7 ); 2154 } 2155 2156 if ( bp_use_embed_in_forum_posts() ) { 2157 add_filter( 'bp_get_the_topic_post_content', array( &$this, 'autoembed' ), 8 ); 2158 add_filter( 'bp_get_the_topic_post_content', array( &$this, 'run_shortcode' ), 7 ); 2159 } 2160 2161 if ( bp_use_embed_in_private_messages() ) { 2162 add_filter( 'bp_get_the_thread_message_content', array( &$this, 'autoembed' ), 8 ); 2163 add_filter( 'bp_get_the_thread_message_content', array( &$this, 'run_shortcode' ), 7 ); 2164 } 2165 2166 do_action_ref_array( 'bp_core_setup_oembed', array( &$this ) ); 2167 } 2168 2169 /** 2170 * The {@link do_shortcode()} callback function. 2171 * 2172 * Attempts to convert a URL into embed HTML. Starts by checking the 2173 * URL against the regex of the registered embed handlers. Next, checks 2174 * the URL against the regex of registered {@link WP_oEmbed} providers 2175 * if oEmbed discovery is false. If none of the regex matches and it's 2176 * enabled, then the URL will be passed to {@link BP_Embed::parse_oembed()} 2177 * for oEmbed parsing. 2178 * 2179 * @uses wp_parse_args() 2180 * @uses wp_embed_defaults() 2181 * @uses current_user_can() 2182 * @uses _wp_oembed_get_object() 2183 * @uses WP_Embed::maybe_make_link() 2184 * 2185 * @param array $attr Shortcode attributes. 2186 * @param string $url The URL attempting to be embeded. 2187 * @return string The embed HTML on success, otherwise the original URL. 2188 */ 2189 public function shortcode( $attr, $url = '' ) { 2190 if ( empty( $url ) ) 2191 return ''; 2192 2193 $rawattr = $attr; 2194 $attr = wp_parse_args( $attr, wp_embed_defaults() ); 2195 2196 // kses converts & into & and we need to undo this 2197 // See http://core.trac.wordpress.org/ticket/11311 2198 $url = str_replace( '&', '&', $url ); 2199 2200 // Look for known internal handlers 2201 ksort( $this->handlers ); 2202 foreach ( $this->handlers as $priority => $handlers ) { 2203 foreach ( $handlers as $hid => $handler ) { 2204 if ( preg_match( $handler['regex'], $url, $matches ) && is_callable( $handler['callback'] ) ) { 2205 if ( false !== $return = call_user_func( $handler['callback'], $matches, $attr, $url, $rawattr ) ) { 2206 return apply_filters( 'embed_handler_html', $return, $url, $attr ); 2207 } 2208 } 2209 } 2210 } 2211 2212 // Get object ID 2213 $id = apply_filters( 'embed_post_id', 0 ); 2214 2215 // Is oEmbed discovery on? 2216 $attr['discover'] = ( apply_filters( 'bp_embed_oembed_discover', false ) && current_user_can( 'unfiltered_html' ) ); 2217 2218 // Set up a new WP oEmbed object to check URL with registered oEmbed providers 2219 require_once( ABSPATH . WPINC . '/class-oembed.php' ); 2220 $oembed_obj = _wp_oembed_get_object(); 2221 2222 // If oEmbed discovery is true, skip oEmbed provider check 2223 $is_oembed_link = false; 2224 if ( !$attr['discover'] ) { 2225 foreach ( (array) $oembed_obj->providers as $provider_matchmask => $provider ) { 2226 $regex = ( $is_regex = $provider[1] ) ? $provider_matchmask : '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $provider_matchmask ), '#' ) ) . '#i'; 2227 2228 if ( preg_match( $regex, $url ) ) 2229 $is_oembed_link = true; 2230 } 2231 2232 // If url doesn't match a WP oEmbed provider, stop parsing 2233 if ( !$is_oembed_link ) 2234 return $this->maybe_make_link( $url ); 2235 } 2236 2237 return $this->parse_oembed( $id, $url, $attr, $rawattr ); 2238 } 2239 2240 /** 2241 * Base function so BP components/plugins can parse links to be embedded. 2242 * 2243 * View an example to add support in {@link bp_activity_embed()}. 2244 * 2245 * @uses apply_filters() Filters cache. 2246 * @uses do_action() To save cache. 2247 * @uses wp_oembed_get() Connects to oEmbed provider and returns HTML 2248 * on success. 2249 * @uses WP_Embed::maybe_make_link() Process URL for hyperlinking on 2250 * oEmbed failure. 2251 * 2252 * @param int $id ID to do the caching for. 2253 * @param string $url The URL attempting to be embedded. 2254 * @param array $attr Shortcode attributes from {@link WP_Embed::shortcode()}. 2255 * @param array $rawattr Untouched shortcode attributes from 2256 * {@link WP_Embed::shortcode()}. 2257 * @return string The embed HTML on success, otherwise the original URL. 2258 */ 2259 public function parse_oembed( $id, $url, $attr, $rawattr ) { 2260 $id = intval( $id ); 2261 2262 if ( $id ) { 2263 // Setup the cachekey 2264 $cachekey = '_oembed_' . md5( $url . serialize( $attr ) ); 2265 2266 // Let components / plugins grab their cache 2267 $cache = ''; 2268 $cache = apply_filters( 'bp_embed_get_cache', $cache, $id, $cachekey, $url, $attr, $rawattr ); 2269 2270 // Grab cache and return it if available 2271 if ( !empty( $cache ) ) { 2272 return apply_filters( 'bp_embed_oembed_html', $cache, $url, $attr, $rawattr ); 2273 2274 // If no cache, ping the oEmbed provider and cache the result 2275 } else { 2276 $html = wp_oembed_get( $url, $attr ); 2277 $cache = ( $html ) ? $html : $url; 2278 2279 // Let components / plugins save their cache 2280 do_action( 'bp_embed_update_cache', $cache, $cachekey, $id ); 2281 2282 // If there was a result, return it 2283 if ( $html ) 2284 return apply_filters( 'bp_embed_oembed_html', $html, $url, $attr, $rawattr ); 2285 } 2286 } 2287 2288 // Still unknown 2289 return $this->maybe_make_link( $url ); 2290 } 2291 } 2292 2293 /** 2294 * Create HTML list of BP nav items. 2295 * 2296 * @since BuddyPress (1.7.0) 2297 */ 2298 class BP_Walker_Nav_Menu extends Walker_Nav_Menu { 2299 2300 /** 2301 * Description of fields indexes for building markup. 2302 * 2303 * @since BuddyPress (1.7.0) 2304 * @var array 2305 */ 2306 var $db_fields = array( 'id' => 'css_id', 'parent' => 'parent' ); 2307 2308 /** 2309 * Tree type. 2310 * 2311 * @since BuddyPress (1.7.0) 2312 * @var string 2313 */ 2314 var $tree_type = array(); 2315 2316 /** 2317 * Display array of elements hierarchically. 2318 * 2319 * This method is almost identical to the version in {@link Walker::walk()}. 2320 * The only change is on one line which has been commented. An IF was 2321 * comparing 0 to a non-empty string which was preventing child elements 2322 * being grouped under their parent menu element. 2323 * 2324 * This caused a problem for BuddyPress because our primary/secondary 2325 * navigations don't have a unique numerical ID that describes a 2326 * hierarchy (we use a slug). Obviously, WordPress Menus use Posts, and 2327 * those have ID/post_parent. 2328 * 2329 * @since BuddyPress (1.7.0) 2330 * 2331 * @see Walker::walk() 2332 * 2333 * @param array $elements See {@link Walker::walk()}. 2334 * @param int $max_depth See {@link Walker::walk()}. 2335 * @return string See {@link Walker::walk()}. 2336 */ 2337 public function walk( $elements, $max_depth ) { 2338 $func_args = func_get_args(); 2339 2340 $args = array_slice( $func_args, 2 ); 2341 $output = ''; 2342 2343 if ( $max_depth < -1 ) // invalid parameter 2344 return $output; 2345 2346 if ( empty( $elements ) ) // nothing to walk 2347 return $output; 2348 2349 $parent_field = $this->db_fields['parent']; 2350 2351 // flat display 2352 if ( -1 == $max_depth ) { 2353 2354 $empty_array = array(); 2355 foreach ( $elements as $e ) 2356 $this->display_element( $e, $empty_array, 1, 0, $args, $output ); 2357 2358 return $output; 2359 } 2360 2361 /* 2362 * need to display in hierarchical order 2363 * separate elements into two buckets: top level and children elements 2364 * children_elements is two dimensional array, eg. 2365 * children_elements[10][] contains all sub-elements whose parent is 10. 2366 */ 2367 $top_level_elements = array(); 2368 $children_elements = array(); 2369 2370 foreach ( $elements as $e ) { 2371 // BuddyPress: changed '==' to '==='. This is the only change from version in Walker::walk(). 2372 if ( 0 === $e->$parent_field ) 2373 $top_level_elements[] = $e; 2374 else 2375 $children_elements[$e->$parent_field][] = $e; 2376 } 2377 2378 /* 2379 * when none of the elements is top level 2380 * assume the first one must be root of the sub elements 2381 */ 2382 if ( empty( $top_level_elements ) ) { 2383 2384 $first = array_slice( $elements, 0, 1 ); 2385 $root = $first[0]; 2386 $top_level_elements = array(); 2387 $children_elements = array(); 2388 2389 foreach ( $elements as $e ) { 2390 if ( $root->$parent_field == $e->$parent_field ) 2391 $top_level_elements[] = $e; 2392 else 2393 $children_elements[$e->$parent_field][] = $e; 2394 } 2395 } 2396 2397 foreach ( $top_level_elements as $e ) 2398 $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output ); 2399 2400 /* 2401 * if we are displaying all levels, and remaining children_elements is not empty, 2402 * then we got orphans, which should be displayed regardless 2403 */ 2404 if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) { 2405 $empty_array = array(); 2406 2407 foreach ( $children_elements as $orphans ) 2408 foreach ( $orphans as $op ) 2409 $this->display_element( $op, $empty_array, 1, 0, $args, $output ); 2410 } 2411 2412 return $output; 2413 } 2414 2415 /** 2416 * Display the current <li> that we are on. 2417 * 2418 * @see Walker::start_el() for complete description of parameters . 2419 * 2420 * @since BuddyPress (1.7.0) 2421 * 2422 * @param string $output Passed by reference. Used to append 2423 * additional content. 2424 * @param object $item Menu item data object. 2425 * @param int $depth Depth of menu item. Used for padding. Optional, 2426 * defaults to 0. 2427 * @param array $args Optional. See {@link Walker::start_el()}. 2428 * @param int $current_page Menu item ID. Optional. 2429 */ 2430 public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) { 2431 // If we're someway down the tree, indent the HTML with the appropriate number of tabs 2432 $indent = $depth ? str_repeat( "\t", $depth ) : ''; 2433 2434 // Add HTML classes 2435 $class_names = join( ' ', apply_filters( 'bp_nav_menu_css_class', array_filter( $item->class ), $item, $args ) ); 2436 $class_names = ! empty( $class_names ) ? ' class="' . esc_attr( $class_names ) . '"' : ''; 2437 2438 // Add HTML ID 2439 $id = sanitize_html_class( $item->css_id . '-personal-li' ); // Backpat with BP pre-1.7 2440 $id = apply_filters( 'bp_nav_menu_item_id', $id, $item, $args ); 2441 $id = ! empty( $id ) ? ' id="' . esc_attr( $id ) . '"' : ''; 2442 2443 // Opening tag; closing tag is handled in Walker_Nav_Menu::end_el(). 2444 $output .= $indent . '<li' . $id . $class_names . '>'; 2445 2446 // Add href attribute 2447 $attributes = ! empty( $item->link ) ? ' href="' . esc_attr( esc_url( $item->link ) ) . '"' : ''; 2448 2449 // Construct the link 2450 $item_output = $args->before; 2451 $item_output .= '<a' . $attributes . '>'; 2452 $item_output .= $args->link_before . apply_filters( 'the_title', $item->name, 0 ) . $args->link_after; 2453 $item_output .= '</a>'; 2454 $item_output .= $args->after; 2455 2456 // $output is byref 2457 $output .= apply_filters( 'bp_walker_nav_menu_start_el', $item_output, $item, $depth, $args ); 2458 } 2459 } 2460 2461 /** 2462 * Create a set of BuddyPress-specific links for use in the Menus admin UI. 2463 * 2464 * Borrowed heavily from {@link Walker_Nav_Menu_Checklist}, but modified so as not 2465 * to require an actual post type or taxonomy, and to force certain CSS classes 2466 * 2467 * @since BuddyPress (1.9.0) 2468 */ 2469 class BP_Walker_Nav_Menu_Checklist extends Walker_Nav_Menu { 2470 2471 /** 2472 * Constructor. 2473 * 2474 * @see Walker_Nav_Menu::__construct() for a description of parameters. 2475 * 2476 * @param array $fields See {@link Walker_Nav_Menu::__construct()}. 2477 */ 2478 public function __construct( $fields = false ) { 2479 if ( $fields ) { 2480 $this->db_fields = $fields; 2481 } 2482 } 2483 2484 /** 2485 * Create the markup to start a tree level. 2486 * 2487 * @see Walker_Nav_Menu::start_lvl() for description of parameters. 2488 * 2489 * @param string $output See {@Walker_Nav_Menu::start_lvl()}. 2490 * @param int $depth See {@Walker_Nav_Menu::start_lvl()}. 2491 * @param array $args See {@Walker_Nav_Menu::start_lvl()}. 2492 */ 2493 public function start_lvl( &$output, $depth = 0, $args = array() ) { 2494 $indent = str_repeat( "\t", $depth ); 2495 $output .= "\n$indent<ul class='children'>\n"; 2496 } 2497 2498 /** 2499 * Create the markup to end a tree level. 2500 * 2501 * @see Walker_Nav_Menu::end_lvl() for description of parameters. 2502 * 2503 * @param string $output See {@Walker_Nav_Menu::end_lvl()}. 2504 * @param int $depth See {@Walker_Nav_Menu::end_lvl()}. 2505 * @param array $args See {@Walker_Nav_Menu::end_lvl()}. 2506 */ 2507 public function end_lvl( &$output, $depth = 0, $args = array() ) { 2508 $indent = str_repeat( "\t", $depth ); 2509 $output .= "\n$indent</ul>"; 2510 } 2511 2512 /** 2513 * Create the markup to start an element. 2514 * 2515 * @see Walker::start_el() for description of parameters. 2516 * 2517 * @param string $output Passed by reference. Used to append additional 2518 * content. 2519 * @param object $item Menu item data object. 2520 * @param int $depth Depth of menu item. Used for padding. 2521 * @param object $args See {@Walker::start_el()}. 2522 * @param int $id See {@Walker::start_el()}. 2523 */ 2524 function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) { 2525 global $_nav_menu_placeholder; 2526 2527 $_nav_menu_placeholder = ( 0 > $_nav_menu_placeholder ) ? intval($_nav_menu_placeholder) - 1 : -1; 2528 $possible_object_id = isset( $item->post_type ) && 'nav_menu_item' == $item->post_type ? $item->object_id : $_nav_menu_placeholder; 2529 $possible_db_id = ( ! empty( $item->ID ) ) && ( 0 < $possible_object_id ) ? (int) $item->ID : 0; 2530 2531 $indent = ( $depth ) ? str_repeat( "\t", $depth ) : ''; 2532 2533 $output .= $indent . '<li>'; 2534 $output .= '<label class="menu-item-title">'; 2535 $output .= '<input type="checkbox" class="menu-item-checkbox'; 2536 2537 if ( property_exists( $item, 'label' ) ) { 2538 $title = $item->label; 2539 } 2540 2541 $output .= '" name="menu-item[' . $possible_object_id . '][menu-item-object-id]" value="'. esc_attr( $item->object_id ) .'" /> '; 2542 $output .= isset( $title ) ? esc_html( $title ) : esc_html( $item->title ); 2543 $output .= '</label>'; 2544 2545 if ( empty( $item->url ) ) { 2546 $item->url = $item->guid; 2547 } 2548 2549 if ( ! in_array( array( 'bp-menu', 'bp-'. $item->post_excerpt .'-nav' ), $item->classes ) ) { 2550 $item->classes[] = 'bp-menu'; 2551 $item->classes[] = 'bp-'. $item->post_excerpt .'-nav'; 2552 } 2553 2554 // Menu item hidden fields 2555 $output .= '<input type="hidden" class="menu-item-db-id" name="menu-item[' . $possible_object_id . '][menu-item-db-id]" value="' . $possible_db_id . '" />'; 2556 $output .= '<input type="hidden" class="menu-item-object" name="menu-item[' . $possible_object_id . '][menu-item-object]" value="'. esc_attr( $item->object ) .'" />'; 2557 $output .= '<input type="hidden" class="menu-item-parent-id" name="menu-item[' . $possible_object_id . '][menu-item-parent-id]" value="'. esc_attr( $item->menu_item_parent ) .'" />'; 2558 $output .= '<input type="hidden" class="menu-item-type" name="menu-item[' . $possible_object_id . '][menu-item-type]" value="custom" />'; 2559 $output .= '<input type="hidden" class="menu-item-title" name="menu-item[' . $possible_object_id . '][menu-item-title]" value="'. esc_attr( $item->title ) .'" />'; 2560 $output .= '<input type="hidden" class="menu-item-url" name="menu-item[' . $possible_object_id . '][menu-item-url]" value="'. esc_attr( $item->url ) .'" />'; 2561 $output .= '<input type="hidden" class="menu-item-target" name="menu-item[' . $possible_object_id . '][menu-item-target]" value="'. esc_attr( $item->target ) .'" />'; 2562 $output .= '<input type="hidden" class="menu-item-attr_title" name="menu-item[' . $possible_object_id . '][menu-item-attr_title]" value="'. esc_attr( $item->attr_title ) .'" />'; 2563 $output .= '<input type="hidden" class="menu-item-classes" name="menu-item[' . $possible_object_id . '][menu-item-classes]" value="'. esc_attr( implode( ' ', $item->classes ) ) .'" />'; 2564 $output .= '<input type="hidden" class="menu-item-xfn" name="menu-item[' . $possible_object_id . '][menu-item-xfn]" value="'. esc_attr( $item->xfn ) .'" />'; 2565 } 2566 } 2567 2568 /** 2569 * Base class for the BuddyPress Suggestions API. 2570 * 2571 * Originally built to power BuddyPress' at-mentions suggestions, it's flexible enough to be used 2572 * for similar kinds of future core requirements, or those desired by third-party developers. 2573 * 2574 * To implement a new suggestions service, create a new class that extends this one, and update 2575 * the list of default services in {@link bp_core_get_suggestions()}. If you're building a plugin, 2576 * it's recommend that you use the `bp_suggestions_services` filter to do this. :) 2577 * 2578 * While the implementation of the query logic is left to you, it should be as quick and efficient 2579 * as possible. When implementing the abstract methods in this class, pay close attention to the 2580 * recommendations provided in the phpDoc blocks, particularly the expected return types. 2581 * 2582 * @since BuddyPress (2.1.0) 2583 */ 2584 abstract class BP_Suggestions { 2585 2586 /** 2587 * Default arguments common to all suggestions services. 2588 * 2589 * If your custom service requires further defaults, add them here. 2590 * 2591 * @since BuddyPress (2.1.0) 2592 * @var array 2593 */ 2594 protected $default_args = array( 2595 'limit' => 16, 2596 'term' => '', 2597 'type' => '', 2598 ); 2599 2600 /** 2601 * Holds the arguments for the query (about to made to the suggestions service). 2602 * 2603 * This includes `$default_args`, as well as the user-supplied values. 2604 * 2605 * @since BuddyPress (2.1.0) 2606 * @var array 2607 */ 2608 protected $args = array( 2609 ); 2610 2611 2612 /** 2613 * Constructor. 2614 * 2615 * @param array $args Optional. If set, used as the parameters for the suggestions service query. 2616 * @since BuddyPress (2.1.0) 2617 */ 2618 public function __construct( array $args = array() ) { 2619 if ( ! empty( $args ) ) { 2620 $this->set_query( $args ); 2621 } 2622 } 2623 2624 /** 2625 * Set the parameters for the suggestions service query. 2626 * 2627 * @param array $args { 2628 * @type int $limit Maximum number of results to display. Optional, default: 16. 2629 * @type string $type The name of the suggestion service to use for the request. Mandatory. 2630 * @type string $term The suggestion service will try to find results that contain this string. 2631 * Mandatory. 2632 * } 2633 * @since BuddyPress (2.1.0) 2634 */ 2635 public function set_query( array $args = array() ) { 2636 $this->args = wp_parse_args( $args, $this->default_args ); 2637 } 2638 2639 /** 2640 * Validate and sanitise the parameters for the suggestion service query. 2641 * 2642 * Be sure to call this class' version of this method when implementing it in your own service. 2643 * If validation fails, you must return a WP_Error object. 2644 * 2645 * @return true|WP_Error If validation fails, return a WP_Error object. On success, return true (bool). 2646 * @since BuddyPress (2.1.0) 2647 */ 2648 public function validate() { 2649 $this->args['limit'] = absint( $this->args['limit'] ); 2650 $this->args['term'] = trim( sanitize_text_field( $this->args['term'] ) ); 2651 $this->args = apply_filters( 'bp_suggestions_args', $this->args, $this ); 2652 2653 2654 // Check for invalid or missing mandatory parameters. 2655 if ( ! $this->args['limit'] || ! $this->args['term'] ) { 2656 return new WP_Error( 'missing_parameter' ); 2657 } 2658 2659 // Check for blocked users (e.g. deleted accounts, or spammers). 2660 if ( is_user_logged_in() && ! bp_is_user_active( get_current_user_id() ) ) { 2661 return new WP_Error( 'invalid_user' ); 2662 } 2663 2664 return apply_filters( 'bp_suggestions_validate_args', true, $this ); 2665 } 2666 2667 /** 2668 * Find and return a list of suggestions that match the query. 2669 * 2670 * The return type is important. If no matches are found, an empty array must be returned. 2671 * Matches must be returned as objects in an array. 2672 * 2673 * The object format for each match must be: { 'ID': string, 'image': string, 'name': string } 2674 * For example: { 'ID': 'admin', 'image': 'http://example.com/logo.png', 'name': 'Name Surname' } 2675 * 2676 * @return array|WP_Error Array of results. If there were problems, returns a WP_Error object. 2677 * @since BuddyPress (2.1.0) 2678 */ 2679 abstract public function get_suggestions(); 2680 } 2681 2682 /** 2683 * Adds support for user at-mentions to the Suggestions API. 2684 * 2685 * This class is in the Core component because it's required by a class in the Groups component, 2686 * and Groups is loaded before Members (alphabetical order). 2687 * 2688 * @since BuddyPress (2.1.0) 2689 */ 2690 class BP_Members_Suggestions extends BP_Suggestions { 2691 2692 /** 2693 * Default arguments for this suggestions service. 2694 * 2695 * @since BuddyPress (2.1.0) 2696 * @var array $args { 2697 * @type int $limit Maximum number of results to display. Default: 16. 2698 * @type bool $only_friends If true, only match the current user's friends. Default: false. 2699 * @type string $term The suggestion service will try to find results that contain this string. 2700 * Mandatory. 2701 * } 2702 */ 2703 protected $default_args = array( 2704 'limit' => 10, 2705 'only_friends' => false, 2706 'term' => '', 2707 'type' => '', 2708 ); 2709 2710 2711 /** 2712 * Validate and sanitise the parameters for the suggestion service query. 2713 * 2714 * @return true|WP_Error If validation fails, return a WP_Error object. On success, return true (bool). 2715 * @since BuddyPress (2.1.0) 2716 */ 2717 public function validate() { 2718 $this->args['only_friends'] = (bool) $this->args['only_friends']; 2719 $this->args = apply_filters( 'bp_members_suggestions_args', $this->args, $this ); 2720 2721 // Check for invalid or missing mandatory parameters. 2722 if ( $this->args['only_friends'] && ( ! bp_is_active( 'friends' ) || ! is_user_logged_in() ) ) { 2723 return new WP_Error( 'missing_requirement' ); 2724 } 2725 2726 return apply_filters( 'bp_members_suggestions_validate_args', parent::validate(), $this ); 2727 } 2728 2729 /** 2730 * Find and return a list of username suggestions that match the query. 2731 * 2732 * @return array|WP_Error Array of results. If there were problems, returns a WP_Error object. 2733 * @since BuddyPress (2.1.0) 2734 */ 2735 public function get_suggestions() { 2736 $user_query = array( 2737 'count_total' => '', // Prevents total count 2738 'populate_extras' => false, 2739 'type' => 'alphabetical', 2740 2741 'page' => 1, 2742 'per_page' => $this->args['limit'], 2743 'search_terms' => $this->args['term'], 2744 'search_wildcard' => 'right', 2745 ); 2746 2747 // Only return matches of friends of this user. 2748 if ( $this->args['only_friends'] && is_user_logged_in() ) { 2749 $user_query['user_id'] = get_current_user_id(); 2750 } 2751 2752 $user_query = apply_filters( 'bp_members_suggestions_query_args', $user_query, $this ); 2753 if ( is_wp_error( $user_query ) ) { 2754 return $user_query; 2755 } 2756 2757 2758 $user_query = new BP_User_Query( $user_query ); 2759 $results = array(); 2760 2761 foreach ( $user_query->results as $user ) { 2762 $result = new stdClass(); 2763 $result->ID = $user->user_nicename; 2764 $result->image = bp_core_fetch_avatar( array( 'html' => false, 'item_id' => $user->ID ) ); 2765 $result->name = bp_core_get_user_displayname( $user->ID ); 2766 2767 $results[] = $result; 2768 } 2769 2770 return apply_filters( 'bp_members_suggestions_get_suggestions', $results, $this ); 2771 } 2772 } 2773 2774 /** 2775 * Base class for creating query classes that generate SQL fragments for filtering results based on recursive query params. 2776 * 2777 * @since BuddyPress (2.2.0) 2778 */ 2779 abstract class BP_Recursive_Query { 2780 2781 /** 2782 * Query arguments passed to the constructor. 2783 * 2784 * @since BuddyPress (2.2.0) 2785 * @access public 2786 * @var array 2787 */ 2788 public $queries = array(); 2789 2790 /** 2791 * Generate SQL clauses to be appended to a main query. 2792 * 2793 * Extending classes should call this method from within a publicly 2794 * accessible get_sql() method, and manipulate the SQL as necessary. 2795 * For example, {@link BP_XProfile_Query::get_sql()} is merely a wrapper for 2796 * get_sql_clauses(), while {@link BP_Activity_Query::get_sql()} discards 2797 * the empty 'join' clause, and only passes the 'where' clause. 2798 * 2799 * @since BuddyPress (2.2.0) 2800 * @access protected 2801 * 2802 * @param string $primary_table 2803 * @param string $primary_id_column 2804 * @return array 2805 */ 2806 protected function get_sql_clauses() { 2807 $sql = $this->get_sql_for_query( $this->queries ); 2808 2809 if ( ! empty( $sql['where'] ) ) { 2810 $sql['where'] = ' AND ' . "\n" . $sql['where'] . "\n"; 2811 } 2812 2813 return $sql; 2814 } 2815 2816 /** 2817 * Generate SQL clauses for a single query array. 2818 * 2819 * If nested subqueries are found, this method recurses the tree to 2820 * produce the properly nested SQL. 2821 * 2822 * Subclasses generally do not need to call this method. It is invoked 2823 * automatically from get_sql_clauses(). 2824 * 2825 * @since BuddyPress (2.2.0) 2826 * @access protected 2827 * 2828 * @param array $query Query to parse. 2829 * @param int $depth Optional. Number of tree levels deep we 2830 * currently are. Used to calculate indentation. 2831 * @return array 2832 */ 2833 protected function get_sql_for_query( $query, $depth = 0 ) { 2834 $sql_chunks = array( 2835 'join' => array(), 2836 'where' => array(), 2837 ); 2838 2839 $sql = array( 2840 'join' => '', 2841 'where' => '', 2842 ); 2843 2844 $indent = ''; 2845 for ( $i = 0; $i < $depth; $i++ ) { 2846 $indent .= "\t"; 2847 } 2848 2849 foreach ( $query as $key => $clause ) { 2850 if ( 'relation' === $key ) { 2851 $relation = $query['relation']; 2852 } elseif ( is_array( $clause ) ) { 2853 // This is a first-order clause 2854 if ( $this->is_first_order_clause( $clause ) ) { 2855 $clause_sql = $this->get_sql_for_clause( $clause, $query ); 2856 2857 $where_count = count( $clause_sql['where'] ); 2858 if ( ! $where_count ) { 2859 $sql_chunks['where'][] = ''; 2860 } elseif ( 1 === $where_count ) { 2861 $sql_chunks['where'][] = $clause_sql['where'][0]; 2862 } else { 2863 $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; 2864 } 2865 2866 $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); 2867 // This is a subquery 2868 } else { 2869 $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); 2870 2871 $sql_chunks['where'][] = $clause_sql['where']; 2872 $sql_chunks['join'][] = $clause_sql['join']; 2873 } 2874 } 2875 } 2876 2877 // Filter empties 2878 $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); 2879 $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); 2880 2881 if ( empty( $relation ) ) { 2882 $relation = 'AND'; 2883 } 2884 2885 if ( ! empty( $sql_chunks['join'] ) ) { 2886 $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); 2887 } 2888 2889 if ( ! empty( $sql_chunks['where'] ) ) { 2890 $sql['where'] = '( ' . "\n\t" . $indent . implode( ' ' . "\n\t" . $indent . $relation . ' ' . "\n\t" . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')' . "\n"; 2891 } 2892 2893 return $sql; 2894 } 2895 2896 /** 2897 * Recursive-friendly query sanitizer. 2898 * 2899 * Ensures that each query-level clause has a 'relation' key, and that 2900 * each first-order clause contains all the necessary keys from 2901 * $defaults. 2902 * 2903 * Extend this method if your class uses different sanitizing logic. 2904 * 2905 * @since BuddyPress (2.2.0) 2906 * @access public 2907 * 2908 * @param array $queries Array of query clauses. 2909 * @return array Sanitized array of query clauses. 2910 */ 2911 protected function sanitize_query( $queries ) { 2912 $clean_queries = array(); 2913 2914 if ( ! is_array( $queries ) ) { 2915 return $clean_queries; 2916 } 2917 2918 foreach ( $queries as $key => $query ) { 2919 if ( 'relation' === $key ) { 2920 $relation = $query; 2921 2922 } elseif ( ! is_array( $query ) ) { 2923 continue; 2924 2925 // First-order clause. 2926 } elseif ( $this->is_first_order_clause( $query ) ) { 2927 if ( isset( $query['value'] ) && array() === $query['value'] ) { 2928 unset( $query['value'] ); 2929 } 2930 2931 $clean_queries[] = $query; 2932 2933 // Otherwise, it's a nested query, so we recurse. 2934 } else { 2935 $cleaned_query = $this->sanitize_query( $query ); 2936 2937 if ( ! empty( $cleaned_query ) ) { 2938 $clean_queries[] = $cleaned_query; 2939 } 2940 } 2941 } 2942 2943 if ( empty( $clean_queries ) ) { 2944 return $clean_queries; 2945 } 2946 2947 // Sanitize the 'relation' key provided in the query. 2948 if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) { 2949 $clean_queries['relation'] = 'OR'; 2950 2951 /* 2952 * If there is only a single clause, call the relation 'OR'. 2953 * This value will not actually be used to join clauses, but it 2954 * simplifies the logic around combining key-only queries. 2955 */ 2956 } elseif ( 1 === count( $clean_queries ) ) { 2957 $clean_queries['relation'] = 'OR'; 2958 2959 // Default to AND. 2960 } else { 2961 $clean_queries['relation'] = 'AND'; 2962 } 2963 2964 return $clean_queries; 2965 } 2966 2967 /** 2968 * Generate JOIN and WHERE clauses for a first-order clause. 2969 * 2970 * Must be overridden in a subclass. 2971 * 2972 * @since BuddyPress (2.2.0) 2973 * @access protected 2974 * 2975 * @param array $clause Array of arguments belonging to the clause. 2976 * @param array $parent_query Parent query to which the clause belongs. 2977 * @return array { 2978 * @type array $join Array of subclauses for the JOIN statement. 2979 * @type array $where Array of subclauses for the WHERE statement. 2980 * } 2981 */ 2982 abstract protected function get_sql_for_clause( $clause, $parent_query ); 2983 2984 /** 2985 * Determine whether a clause is first-order. 2986 * 2987 * Must be overridden in a subclass. 2988 * 2989 * @since BuddyPress (2.2.0) 2990 * @access protected 2991 * 2992 * @param array $q Clause to check. 2993 * @return bool 2994 */ 2995 abstract protected function is_first_order_clause( $query ); 2996 } 12 require __DIR__ . '/classes/class-bp-user-query.php'; 13 require __DIR__ . '/classes/class-bp-core-user.php'; 14 require __DIR__ . '/classes/class-bp-date-query.php'; 15 require __DIR__ . '/classes/class-bp-core-notification.php'; 16 require __DIR__ . '/classes/class-bp-button.php'; 17 require __DIR__ . '/classes/class-bp-embed.php'; 18 require __DIR__ . '/classes/class-bp-walker-nav-menu.php'; 19 require __DIR__ . '/classes/class-bp-walker-nav-menu-checklist.php'; 20 require __DIR__ . '/classes/class-bp-suggestions.php'; 21 require __DIR__ . '/classes/class-bp-members-suggestions.php'; 22 require __DIR__ . '/classes/class-bp-recursive-query.php';
Note: See TracChangeset
for help on using the changeset viewer.