Changeset 9485
- Timestamp:
- 02/15/2015 12:48:56 AM (10 years ago)
- Location:
- trunk/src
- Files:
-
- 50 added
- 9 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/bp-activity/bp-activity-classes.php
r9385 r9485 10 10 defined( 'ABSPATH' ) || exit; 11 11 12 /** 13 * Database interaction class for the BuddyPress activity component. 14 * 15 * Instance methods are available for creating/editing an activity, 16 * static methods for querying activities. 17 * 18 * @since BuddyPress (1.0) 19 */ 20 class BP_Activity_Activity { 21 22 /** Properties ************************************************************/ 23 24 /** 25 * ID of the activity item. 26 * 27 * @var int 28 */ 29 var $id; 30 31 /** 32 * ID of the associated item. 33 * 34 * @var int 35 */ 36 var $item_id; 37 38 /** 39 * ID of the associated secondary item. 40 * 41 * @var int 42 */ 43 var $secondary_item_id; 44 45 /** 46 * ID of user associated with the activity item. 47 * 48 * @var int 49 */ 50 var $user_id; 51 52 /** 53 * The primary URL for the activity in RSS feeds. 54 * 55 * @var string 56 */ 57 var $primary_link; 58 59 /** 60 * BuddyPress component the activity item relates to. 61 * 62 * @var string 63 */ 64 var $component; 65 66 /** 67 * Activity type, eg 'new_blog_post'. 68 * 69 * @var string 70 */ 71 var $type; 72 73 /** 74 * Description of the activity, eg 'Alex updated his profile.' 75 * 76 * @var string 77 */ 78 var $action; 79 80 /** 81 * The content of the activity item. 82 * 83 * @var string 84 */ 85 var $content; 86 87 /** 88 * The date the activity item was recorded, in 'Y-m-d h:i:s' format. 89 * 90 * @var string 91 */ 92 var $date_recorded; 93 94 /** 95 * Whether the item should be hidden in sitewide streams. 96 * 97 * @var int 98 */ 99 var $hide_sitewide = false; 100 101 /** 102 * Node boundary start for activity or activity comment. 103 * 104 * @var int 105 */ 106 var $mptt_left; 107 108 /** 109 * Node boundary end for activity or activity comment. 110 * 111 * @var int 112 */ 113 var $mptt_right; 114 115 /** 116 * Whether this item is marked as spam. 117 * 118 * @var int 119 */ 120 var $is_spam; 121 122 /** 123 * Constructor method. 124 * 125 * @param int $id Optional. The ID of a specific activity item. 126 */ 127 public function __construct( $id = false ) { 128 if ( !empty( $id ) ) { 129 $this->id = $id; 130 $this->populate(); 131 } 132 } 133 134 /** 135 * Populate the object with data about the specific activity item. 136 */ 137 public function populate() { 138 global $wpdb; 139 140 $row = wp_cache_get( $this->id, 'bp_activity' ); 141 142 if ( false === $row ) { 143 $bp = buddypress(); 144 $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->activity->table_name} WHERE id = %d", $this->id ) ); 145 146 wp_cache_set( $this->id, $row, 'bp_activity' ); 147 } 148 149 if ( ! empty( $row ) ) { 150 $this->id = (int) $row->id; 151 $this->item_id = (int) $row->item_id; 152 $this->secondary_item_id = (int) $row->secondary_item_id; 153 $this->user_id = (int) $row->user_id; 154 $this->primary_link = $row->primary_link; 155 $this->component = $row->component; 156 $this->type = $row->type; 157 $this->action = $row->action; 158 $this->content = $row->content; 159 $this->date_recorded = $row->date_recorded; 160 $this->hide_sitewide = $row->hide_sitewide; 161 $this->mptt_left = (int) $row->mptt_left; 162 $this->mptt_right = (int) $row->mptt_right; 163 $this->is_spam = $row->is_spam; 164 } 165 166 // Generate dynamic 'action' when possible 167 $action = bp_activity_generate_action_string( $this ); 168 if ( false !== $action ) { 169 $this->action = $action; 170 171 // If no callback is available, use the literal string from 172 // the database row 173 } elseif ( ! empty( $row->action ) ) { 174 $this->action = $row->action; 175 176 // Provide a fallback to avoid PHP notices 177 } else { 178 $this->action = ''; 179 } 180 } 181 182 /** 183 * Save the activity item to the database. 184 * 185 * @return bool True on success. 186 */ 187 public function save() { 188 global $wpdb; 189 190 $bp = buddypress(); 191 192 $this->id = apply_filters_ref_array( 'bp_activity_id_before_save', array( $this->id, &$this ) ); 193 $this->item_id = apply_filters_ref_array( 'bp_activity_item_id_before_save', array( $this->item_id, &$this ) ); 194 $this->secondary_item_id = apply_filters_ref_array( 'bp_activity_secondary_item_id_before_save', array( $this->secondary_item_id, &$this ) ); 195 $this->user_id = apply_filters_ref_array( 'bp_activity_user_id_before_save', array( $this->user_id, &$this ) ); 196 $this->primary_link = apply_filters_ref_array( 'bp_activity_primary_link_before_save', array( $this->primary_link, &$this ) ); 197 $this->component = apply_filters_ref_array( 'bp_activity_component_before_save', array( $this->component, &$this ) ); 198 $this->type = apply_filters_ref_array( 'bp_activity_type_before_save', array( $this->type, &$this ) ); 199 $this->action = apply_filters_ref_array( 'bp_activity_action_before_save', array( $this->action, &$this ) ); 200 $this->content = apply_filters_ref_array( 'bp_activity_content_before_save', array( $this->content, &$this ) ); 201 $this->date_recorded = apply_filters_ref_array( 'bp_activity_date_recorded_before_save', array( $this->date_recorded, &$this ) ); 202 $this->hide_sitewide = apply_filters_ref_array( 'bp_activity_hide_sitewide_before_save', array( $this->hide_sitewide, &$this ) ); 203 $this->mptt_left = apply_filters_ref_array( 'bp_activity_mptt_left_before_save', array( $this->mptt_left, &$this ) ); 204 $this->mptt_right = apply_filters_ref_array( 'bp_activity_mptt_right_before_save', array( $this->mptt_right, &$this ) ); 205 $this->is_spam = apply_filters_ref_array( 'bp_activity_is_spam_before_save', array( $this->is_spam, &$this ) ); 206 207 /** 208 * Fires before the current activity item gets saved. 209 * 210 * Please use this hook to filter the properties above. Each part will be passed in. 211 * 212 * @since BuddyPress (1.0.0) 213 * 214 * @param BP_Activity_Activity Current instance of the activity item being saved. 215 */ 216 do_action_ref_array( 'bp_activity_before_save', array( &$this ) ); 217 218 if ( empty( $this->component ) || empty( $this->type ) ) { 219 return false; 220 } 221 222 if ( empty( $this->primary_link ) ) { 223 $this->primary_link = bp_loggedin_user_domain(); 224 } 225 226 // If we have an existing ID, update the activity item, otherwise insert it. 227 if ( ! empty( $this->id ) ) { 228 $q = $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET user_id = %d, component = %s, type = %s, action = %s, content = %s, primary_link = %s, date_recorded = %s, item_id = %d, secondary_item_id = %d, hide_sitewide = %d, is_spam = %d WHERE id = %d", $this->user_id, $this->component, $this->type, $this->action, $this->content, $this->primary_link, $this->date_recorded, $this->item_id, $this->secondary_item_id, $this->hide_sitewide, $this->is_spam, $this->id ); 229 } else { 230 $q = $wpdb->prepare( "INSERT INTO {$bp->activity->table_name} ( user_id, component, type, action, content, primary_link, date_recorded, item_id, secondary_item_id, hide_sitewide, is_spam ) VALUES ( %d, %s, %s, %s, %s, %s, %s, %d, %d, %d, %d )", $this->user_id, $this->component, $this->type, $this->action, $this->content, $this->primary_link, $this->date_recorded, $this->item_id, $this->secondary_item_id, $this->hide_sitewide, $this->is_spam ); 231 } 232 233 if ( false === $wpdb->query( $q ) ) { 234 return false; 235 } 236 237 // If this is a new activity item, set the $id property 238 if ( empty( $this->id ) ) { 239 $this->id = $wpdb->insert_id; 240 241 // If an existing activity item, prevent any changes to the content generating new @mention notifications. 242 } else { 243 add_filter( 'bp_activity_at_name_do_notifications', '__return_false' ); 244 } 245 246 /** 247 * Fires after an activity item has been saved to the database. 248 * 249 * @since BuddyPress (1.0.0) 250 * 251 * @param BP_Activity_Activity Reference to current instance of activity being saved. 252 */ 253 do_action_ref_array( 'bp_activity_after_save', array( &$this ) ); 254 255 return true; 256 } 257 258 /** Static Methods ***************************************************/ 259 260 /** 261 * Get activity items, as specified by parameters 262 * 263 * @see BP_Activity_Activity::get_filter_sql() for a description of the 264 * 'filter' parameter. 265 * @see WP_Meta_Query::queries for a description of the 'meta_query' 266 * parameter format. 267 * 268 * @param array $args { 269 * An array of arguments. All items are optional. 270 * 271 * @type int $page Which page of results to fetch. Using page=1 without per_page will result 272 * in no pagination. Default: 1. 273 * @type int|bool $per_page Number of results per page. Default: 25. 274 * @type int|bool $max Maximum number of results to return. Default: false (unlimited). 275 * @type string $sort ASC or DESC. Default: 'DESC'. 276 * @type array $exclude Array of activity IDs to exclude. Default: false. 277 * @type array $in Array of ids to limit query by (IN). Default: false. 278 * @type array $meta_query Array of meta_query conditions. See WP_Meta_Query::queries. 279 * @type array $date_query Array of date_query conditions. See first parameter of 280 * WP_Date_Query::__construct(). 281 * @type array $filter_query Array of advanced query conditions. See BP_Activity_Query::__construct(). 282 * @type string|array $scope Pre-determined set of activity arguments. 283 * @type array $filter See BP_Activity_Activity::get_filter_sql(). 284 * @type string $search_terms Limit results by a search term. Default: false. 285 * @type bool $display_comments Whether to include activity comments. Default: false. 286 * @type bool $show_hidden Whether to show items marked hide_sitewide. Default: false. 287 * @type string $spam Spam status. Default: 'ham_only'. 288 * @type bool $update_meta_cache Whether to pre-fetch metadata for queried activity items. Default: true. 289 * @type string|bool $count_total If true, an additional DB query is run to count the total activity items 290 * for the query. Default: false. 291 * } 292 * @return array The array returned has two keys: 293 * - 'total' is the count of located activities 294 * - 'activities' is an array of the located activities 295 */ 296 public static function get( $args = array() ) { 297 global $wpdb; 298 299 // Backward compatibility with old method of passing arguments 300 if ( !is_array( $args ) || func_num_args() > 1 ) { 301 _deprecated_argument( __METHOD__, '1.6', sprintf( __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), __METHOD__, __FILE__ ) ); 302 303 $old_args_keys = array( 304 0 => 'max', 305 1 => 'page', 306 2 => 'per_page', 307 3 => 'sort', 308 4 => 'search_terms', 309 5 => 'filter', 310 6 => 'display_comments', 311 7 => 'show_hidden', 312 8 => 'exclude', 313 9 => 'in', 314 10 => 'spam' 315 ); 316 317 $func_args = func_get_args(); 318 $args = bp_core_parse_args_array( $old_args_keys, $func_args ); 319 } 320 321 $bp = buddypress(); 322 $r = wp_parse_args( $args, array( 323 'page' => 1, // The current page 324 'per_page' => 25, // Activity items per page 325 'max' => false, // Max number of items to return 326 'sort' => 'DESC', // ASC or DESC 327 'exclude' => false, // Array of ids to exclude 328 'in' => false, // Array of ids to limit query by (IN) 329 'meta_query' => false, // Filter by activitymeta 330 'date_query' => false, // Filter by date 331 'filter_query' => false, // Advanced filtering - see BP_Activity_Query 332 'filter' => false, // See self::get_filter_sql() 333 'scope' => false, // Preset activity arguments 334 'search_terms' => false, // Terms to search by 335 'display_comments' => false, // Whether to include activity comments 336 'show_hidden' => false, // Show items marked hide_sitewide 337 'spam' => 'ham_only', // Spam status 338 'update_meta_cache' => true, 339 'count_total' => false, 340 ) ); 341 342 // Select conditions 343 $select_sql = "SELECT DISTINCT a.id"; 344 345 $from_sql = " FROM {$bp->activity->table_name} a"; 346 347 $join_sql = ''; 348 349 // Where conditions 350 $where_conditions = array(); 351 352 // Excluded types 353 $excluded_types = array(); 354 355 // Scope takes precedence 356 if ( ! empty( $r['scope'] ) ) { 357 $scope_query = self::get_scope_query_sql( $r['scope'], $r ); 358 359 // Add our SQL conditions if matches were found 360 if ( ! empty( $scope_query['sql'] ) ) { 361 $where_conditions['scope_query_sql'] = $scope_query['sql']; 362 } 363 364 // override some arguments if needed 365 if ( ! empty( $scope_query['override'] ) ) { 366 $r = self::array_replace_recursive( $r, $scope_query['override'] ); 367 } 368 369 // Advanced filtering 370 } elseif ( ! empty( $r['filter_query'] ) ) { 371 $filter_query = new BP_Activity_Query( $r['filter_query'] ); 372 $sql = $filter_query->get_sql(); 373 if ( ! empty( $sql ) ) { 374 $where_conditions['filter_query_sql'] = $sql; 375 } 376 } 377 378 // Regular filtering 379 if ( $r['filter'] && $filter_sql = BP_Activity_Activity::get_filter_sql( $r['filter'] ) ) { 380 $where_conditions['filter_sql'] = $filter_sql; 381 } 382 383 // Spam 384 if ( 'ham_only' == $r['spam'] ) { 385 $where_conditions['spam_sql'] = 'a.is_spam = 0'; 386 } elseif ( 'spam_only' == $r['spam'] ) { 387 $where_conditions['spam_sql'] = 'a.is_spam = 1'; 388 } 389 390 // Searching 391 if ( $r['search_terms'] ) { 392 $search_terms_like = '%' . bp_esc_like( $r['search_terms'] ) . '%'; 393 $where_conditions['search_sql'] = $wpdb->prepare( 'a.content LIKE %s', $search_terms_like ); 394 } 395 396 // Sorting 397 $sort = $r['sort']; 398 if ( $sort != 'ASC' && $sort != 'DESC' ) { 399 $sort = 'DESC'; 400 } 401 402 // Hide Hidden Items? 403 if ( ! $r['show_hidden'] ) { 404 $where_conditions['hidden_sql'] = "a.hide_sitewide = 0"; 405 } 406 407 // Exclude specified items 408 if ( ! empty( $r['exclude'] ) ) { 409 $exclude = implode( ',', wp_parse_id_list( $r['exclude'] ) ); 410 $where_conditions['exclude'] = "a.id NOT IN ({$exclude})"; 411 } 412 413 // The specific ids to which you want to limit the query 414 if ( ! empty( $r['in'] ) ) { 415 $in = implode( ',', wp_parse_id_list( $r['in'] ) ); 416 $where_conditions['in'] = "a.id IN ({$in})"; 417 } 418 419 // Process meta_query into SQL 420 $meta_query_sql = self::get_meta_query_sql( $r['meta_query'] ); 421 422 if ( ! empty( $meta_query_sql['join'] ) ) { 423 $join_sql .= $meta_query_sql['join']; 424 } 425 426 if ( ! empty( $meta_query_sql['where'] ) ) { 427 $where_conditions[] = $meta_query_sql['where']; 428 } 429 430 // Process date_query into SQL 431 $date_query_sql = self::get_date_query_sql( $r['date_query'] ); 432 433 if ( ! empty( $date_query_sql ) ) { 434 $where_conditions['date'] = $date_query_sql; 435 } 436 437 // Alter the query based on whether we want to show activity item 438 // comments in the stream like normal comments or threaded below 439 // the activity. 440 if ( false === $r['display_comments'] || 'threaded' === $r['display_comments'] ) { 441 $excluded_types[] = 'activity_comment'; 442 } 443 444 // Exclude 'last_activity' items unless the 'action' filter has 445 // been explicitly set 446 if ( empty( $r['filter']['object'] ) ) { 447 $excluded_types[] = 'last_activity'; 448 } 449 450 // Build the excluded type sql part 451 if ( ! empty( $excluded_types ) ) { 452 $not_in = "'" . implode( "', '", esc_sql( $excluded_types ) ) . "'"; 453 $where_conditions['excluded_types'] = "a.type NOT IN ({$not_in})"; 454 } 455 456 /** 457 * Filters the MySQL WHERE conditions for the Activity items get method. 458 * 459 * @since BuddyPress (1.9.0) 460 * 461 * @param array $where_conditions Current conditions for MySQL WHERE statement. 462 * @param array $r Parsed arguments passed into method. 463 * @param string $select_sql Current SELECT MySQL statement at point of execution. 464 * @param string $from_sql Current FROM MySQL statement at point of execution. 465 * @param string $join_sql Current INNER JOIN MySQL statement at point of execution. 466 */ 467 $where_conditions = apply_filters( 'bp_activity_get_where_conditions', $where_conditions, $r, $select_sql, $from_sql, $join_sql ); 468 469 // Join the where conditions together 470 $where_sql = 'WHERE ' . join( ' AND ', $where_conditions ); 471 472 /** 473 * Filters the preferred order of indexes for activity item. 474 * 475 * @since BuddyPress (1.6.0) 476 * 477 * @param array Array of indexes in preferred order. 478 */ 479 $indexes = apply_filters( 'bp_activity_preferred_index_order', array( 'user_id', 'item_id', 'secondary_item_id', 'date_recorded', 'component', 'type', 'hide_sitewide', 'is_spam' ) ); 480 481 foreach( $indexes as $key => $index ) { 482 if ( false !== strpos( $where_sql, $index ) ) { 483 $the_index = $index; 484 break; // Take the first one we find 485 } 486 } 487 488 if ( !empty( $the_index ) ) { 489 $index_hint_sql = "USE INDEX ({$the_index})"; 490 } else { 491 $index_hint_sql = ''; 492 } 493 494 // Sanitize page and per_page parameters 495 $page = absint( $r['page'] ); 496 $per_page = absint( $r['per_page'] ); 497 498 $retval = array( 499 'activities' => null, 500 'total' => null, 501 'has_more_items' => null, 502 ); 503 504 /** 505 * Filters if BuddyPress should use legacy query structure over current structure for version 2.0+. 506 * 507 * It is not recommended to use the legacy structure, but allowed to if needed. 508 * 509 * @since BuddyPress (2.0.0) 510 * 511 * @param bool Whether to use legacy structure or not. 512 * @param BP_Activity_Activity Current method being called. 513 * @param array $r Parsed arguments passed into method. 514 */ 515 if ( apply_filters( 'bp_use_legacy_activity_query', false, __METHOD__, $r ) ) { 516 517 // Legacy queries joined against the user table 518 $select_sql = "SELECT DISTINCT a.*, u.user_email, u.user_nicename, u.user_login, u.display_name"; 519 $from_sql = " FROM {$bp->activity->table_name} a LEFT JOIN {$wpdb->users} u ON a.user_id = u.ID"; 520 521 if ( ! empty( $page ) && ! empty( $per_page ) ) { 522 $pag_sql = $wpdb->prepare( "LIMIT %d, %d", absint( ( $page - 1 ) * $per_page ), $per_page ); 523 524 /** this filter is documented in bp-activity/bp-activity-classes.php */ 525 $activities = $wpdb->get_results( apply_filters( 'bp_activity_get_user_join_filter', "{$select_sql} {$from_sql} {$join_sql} {$where_sql} ORDER BY a.date_recorded {$sort} {$pag_sql}", $select_sql, $from_sql, $where_sql, $sort, $pag_sql ) ); 526 } else { 527 $pag_sql = ''; 528 529 /** 530 * Filters the legacy MySQL query statement so plugins can alter before results are fetched. 531 * 532 * @since BuddyPress (1.5.0) 533 * 534 * @param string Concatenated MySQL statement pieces to be query results with for legacy query. 535 * @param string $select_sql Final SELECT MySQL statement portion for legacy query. 536 * @param string $from_sql Final FROM MySQL statement portion for legacy query. 537 * @param string $where_sql Final WHERE MySQL statement portion for legacy query. 538 * @param string $sort Final sort direction for legacy query. 539 */ 540 $activities = $wpdb->get_results( apply_filters( 'bp_activity_get_user_join_filter', "{$select_sql} {$from_sql} {$join_sql} {$where_sql} ORDER BY a.date_recorded {$sort}", $select_sql, $from_sql, $where_sql, $sort, $pag_sql ) ); 541 } 542 543 } else { 544 // Query first for activity IDs 545 $activity_ids_sql = "{$select_sql} {$from_sql} {$join_sql} {$where_sql} ORDER BY a.date_recorded {$sort}"; 546 547 if ( ! empty( $per_page ) && ! empty( $page ) ) { 548 // We query for $per_page + 1 items in order to 549 // populate the has_more_items flag 550 $activity_ids_sql .= $wpdb->prepare( " LIMIT %d, %d", absint( ( $page - 1 ) * $per_page ), $per_page + 1 ); 551 } 552 553 /** 554 * Filters the paged activities MySQL statement. 555 * 556 * @since BuddyPress (2.0.0) 557 * 558 * @param string $activity_ids_sql MySQL statement used to query for Activity IDs. 559 * @param array $r Array of arguments passed into method. 560 */ 561 $activity_ids_sql = apply_filters( 'bp_activity_paged_activities_sql', $activity_ids_sql, $r ); 562 563 $activity_ids = $wpdb->get_col( $activity_ids_sql ); 564 565 $retval['has_more_items'] = ! empty( $per_page ) && count( $activity_ids ) > $per_page; 566 567 // If we've fetched more than the $per_page value, we 568 // can discard the extra now 569 if ( ! empty( $per_page ) && count( $activity_ids ) === $per_page + 1 ) { 570 array_pop( $activity_ids ); 571 } 572 573 $activities = self::get_activity_data( $activity_ids ); 574 } 575 576 // Get the fullnames of users so we don't have to query in the loop 577 $activities = self::append_user_fullnames( $activities ); 578 579 // Get activity meta 580 $activity_ids = array(); 581 foreach ( (array) $activities as $activity ) { 582 $activity_ids[] = $activity->id; 583 } 584 585 if ( ! empty( $activity_ids ) && $r['update_meta_cache'] ) { 586 bp_activity_update_meta_cache( $activity_ids ); 587 } 588 589 if ( $activities && $r['display_comments'] ) { 590 $activities = BP_Activity_Activity::append_comments( $activities, $r['spam'] ); 591 } 592 593 // Pre-fetch data associated with activity users and other objects 594 BP_Activity_Activity::prefetch_object_data( $activities ); 595 596 // Generate action strings 597 $activities = BP_Activity_Activity::generate_action_strings( $activities ); 598 599 $retval['activities'] = $activities; 600 601 // If $max is set, only return up to the max results 602 if ( ! empty( $r['count_total'] ) ) { 603 604 /** 605 * Filters the total activities MySQL statement. 606 * 607 * @since BuddyPress (1.5.0) 608 * 609 * @param string MySQL statement used to query for total activities. 610 * @param string $where_sql MySQL WHERE statement portion. 611 * @param string $sort sort direction for query. 612 */ 613 $total_activities_sql = apply_filters( 'bp_activity_total_activities_sql', "SELECT count(DISTINCT a.id) FROM {$bp->activity->table_name} a {$join_sql} {$where_sql}", $where_sql, $sort ); 614 $total_activities = $wpdb->get_var( $total_activities_sql ); 615 616 if ( !empty( $r['max'] ) ) { 617 if ( (int) $total_activities > (int) $r['max'] ) { 618 $total_activities = $r['max']; 619 } 620 } 621 622 $retval['total'] = $total_activities; 623 } 624 625 return $retval; 626 } 627 628 /** 629 * Convert activity IDs to activity objects, as expected in template loop. 630 * 631 * @since 2.0 632 * 633 * @param array $activity_ids Array of activity IDs. 634 * @return array 635 */ 636 protected static function get_activity_data( $activity_ids = array() ) { 637 global $wpdb; 638 639 // Bail if no activity ID's passed 640 if ( empty( $activity_ids ) ) { 641 return array(); 642 } 643 644 // Get BuddyPress 645 $bp = buddypress(); 646 647 $activities = array(); 648 $uncached_ids = bp_get_non_cached_ids( $activity_ids, 'bp_activity' ); 649 650 // Prime caches as necessary 651 if ( ! empty( $uncached_ids ) ) { 652 // Format the activity ID's for use in the query below 653 $uncached_ids_sql = implode( ',', wp_parse_id_list( $uncached_ids ) ); 654 655 // Fetch data from activity table, preserving order 656 $queried_adata = $wpdb->get_results( "SELECT * FROM {$bp->activity->table_name} WHERE id IN ({$uncached_ids_sql})"); 657 658 // Put that data into the placeholders created earlier, 659 // and add it to the cache 660 foreach ( (array) $queried_adata as $adata ) { 661 wp_cache_set( $adata->id, $adata, 'bp_activity' ); 662 } 663 } 664 665 // Now fetch data from the cache 666 foreach ( $activity_ids as $activity_id ) { 667 $activities[] = wp_cache_get( $activity_id, 'bp_activity' ); 668 } 669 670 // Then fetch user data 671 $user_query = new BP_User_Query( array( 672 'user_ids' => wp_list_pluck( $activities, 'user_id' ), 673 'populate_extras' => false, 674 ) ); 675 676 // Associated located user data with activity items 677 foreach ( $activities as $a_index => $a_item ) { 678 $a_user_id = intval( $a_item->user_id ); 679 $a_user = isset( $user_query->results[ $a_user_id ] ) ? $user_query->results[ $a_user_id ] : ''; 680 681 if ( !empty( $a_user ) ) { 682 $activities[ $a_index ]->user_email = $a_user->user_email; 683 $activities[ $a_index ]->user_nicename = $a_user->user_nicename; 684 $activities[ $a_index ]->user_login = $a_user->user_login; 685 $activities[ $a_index ]->display_name = $a_user->display_name; 686 } 687 } 688 689 return $activities; 690 } 691 692 /** 693 * Append xProfile fullnames to an activity array. 694 * 695 * @since BuddyPress (2.0.0) 696 * 697 * @param array $activities Activities array. 698 * @return array 699 */ 700 protected static function append_user_fullnames( $activities ) { 701 702 if ( bp_is_active( 'xprofile' ) && ! empty( $activities ) ) { 703 $activity_user_ids = wp_list_pluck( $activities, 'user_id' ); 704 705 if ( ! empty( $activity_user_ids ) ) { 706 $fullnames = bp_core_get_user_displaynames( $activity_user_ids ); 707 if ( ! empty( $fullnames ) ) { 708 foreach ( (array) $activities as $i => $activity ) { 709 if ( ! empty( $fullnames[ $activity->user_id ] ) ) { 710 $activities[ $i ]->user_fullname = $fullnames[ $activity->user_id ]; 711 } 712 } 713 } 714 } 715 } 716 717 return $activities; 718 } 719 720 /** 721 * Pre-fetch data for objects associated with activity items. 722 * 723 * Activity items are associated with users, and often with other 724 * BuddyPress data objects. Here, we pre-fetch data about these 725 * associated objects, so that inline lookups - done primarily when 726 * building action strings - do not result in excess database queries. 727 * 728 * The only object data required for activity component activity types 729 * (activity_update and activity_comment) is related to users, and that 730 * info is fetched separately in BP_Activity_Activity::get_activity_data(). 731 * So this method contains nothing but a filter that allows other 732 * components, such as bp-friends and bp-groups, to hook in and prime 733 * their own caches at the beginning of an activity loop. 734 * 735 * @since BuddyPress (2.0.0) 736 * 737 * @param array $activities Array of activities. 738 */ 739 protected static function prefetch_object_data( $activities ) { 740 741 /** 742 * Filters inside prefetch_object_data method to aid in pre-fetching object data associated with activity item. 743 * 744 * @since BuddyPress (2.0.0) 745 * 746 * @param array $activities Array of activities. 747 */ 748 return apply_filters( 'bp_activity_prefetch_object_data', $activities ); 749 } 750 751 /** 752 * Generate action strings for the activities located in BP_Activity_Activity::get(). 753 * 754 * If no string can be dynamically generated for a given item 755 * (typically because the activity type has not been properly 756 * registered), the static 'action' value pulled from the database will 757 * be left in place. 758 * 759 * @since BuddyPress (2.0.0) 760 * 761 * @param array $activities Array of activities. 762 * @return array 763 */ 764 protected static function generate_action_strings( $activities ) { 765 foreach ( $activities as $key => $activity ) { 766 $generated_action = bp_activity_generate_action_string( $activity ); 767 if ( false !== $generated_action ) { 768 $activity->action = $generated_action; 769 } 770 771 $activities[ $key ] = $activity; 772 } 773 774 return $activities; 775 } 776 777 /** 778 * Get the SQL for the 'meta_query' param in BP_Activity_Activity::get(). 779 * 780 * We use WP_Meta_Query to do the heavy lifting of parsing the 781 * meta_query array and creating the necessary SQL clauses. However, 782 * since BP_Activity_Activity::get() builds its SQL differently than 783 * WP_Query, we have to alter the return value (stripping the leading 784 * AND keyword from the 'where' clause). 785 * 786 * @since BuddyPress (1.8) 787 * 788 * @param array $meta_query An array of meta_query filters. See the 789 * documentation for WP_Meta_Query for details. 790 * @return array $sql_array 'join' and 'where' clauses. 791 */ 792 public static function get_meta_query_sql( $meta_query = array() ) { 793 global $wpdb; 794 795 $sql_array = array( 796 'join' => '', 797 'where' => '', 798 ); 799 800 if ( ! empty( $meta_query ) ) { 801 $activity_meta_query = new WP_Meta_Query( $meta_query ); 802 803 // WP_Meta_Query expects the table name at 804 // $wpdb->activitymeta 805 $wpdb->activitymeta = buddypress()->activity->table_name_meta; 806 807 $meta_sql = $activity_meta_query->get_sql( 'activity', 'a', 'id' ); 808 809 // Strip the leading AND - BP handles it in get() 810 $sql_array['where'] = preg_replace( '/^\sAND/', '', $meta_sql['where'] ); 811 $sql_array['join'] = $meta_sql['join']; 812 } 813 814 return $sql_array; 815 } 816 817 /** 818 * Get the SQL for the 'date_query' param in BP_Activity_Activity::get(). 819 * 820 * We use BP_Date_Query, which extends WP_Date_Query, to do the heavy lifting 821 * of parsing the date_query array and creating the necessary SQL clauses. 822 * However, since BP_Activity_Activity::get() builds its SQL differently than 823 * WP_Query, we have to alter the return value (stripping the leading AND 824 * keyword from the query). 825 * 826 * @since BuddyPress (2.1.0) 827 * 828 * @param array $date_query An array of date_query parameters. See the 829 * documentation for the first parameter of WP_Date_Query. 830 * @return string 831 */ 832 public static function get_date_query_sql( $date_query = array() ) { 833 $sql = ''; 834 835 // Date query 836 if ( ! empty( $date_query ) && is_array( $date_query ) && class_exists( 'BP_Date_Query' ) ) { 837 $date_query = new BP_Date_Query( $date_query, 'date_recorded' ); 838 $sql = preg_replace( '/^\sAND/', '', $date_query->get_sql() ); 839 } 840 841 return $sql; 842 } 843 844 /** 845 * Get the SQL for the 'scope' param in BP_Activity_Activity::get(). 846 * 847 * A scope is a predetermined set of activity arguments. This method is used 848 * to grab these activity arguments and override any existing args if needed. 849 * 850 * Can handle multiple scopes. 851 * 852 * @since BuddyPress (2.2.0) 853 * 854 * @param mixed $scope The activity scope. Accepts string or array of scopes 855 * @param array $r Current activity arguments. Same as those of BP_Activity_Activity::get(), 856 * but merged with defaults. 857 * @return array 'sql' WHERE SQL string and 'override' activity args 858 */ 859 public static function get_scope_query_sql( $scope = false, $r = array() ) { 860 861 // Define arrays for future use 862 $query_args = array(); 863 $override = array(); 864 $retval = array(); 865 866 // Check for array of scopes 867 if ( is_array( $scope ) ) { 868 $scopes = $scope; 869 870 // Explode a comma separated string of scopes 871 } elseif ( is_string( $scope ) ) { 872 $scopes = explode( ',', $scope ); 873 } 874 875 // Bail if no scope passed 876 if ( empty( $scopes ) ) { 877 return false; 878 } 879 880 // Helper to easily grab the 'user_id' 881 if ( ! empty( $r['filter']['user_id'] ) ) { 882 $r['user_id'] = $r['filter']['user_id']; 883 } 884 885 // parse each scope; yes! we handle multiples! 886 foreach ( $scopes as $scope ) { 887 $scope_args = array(); 888 889 /** 890 * Plugins can hook here to set their activity arguments for custom scopes. 891 * 892 * This is a dynamic filter based on the activity scope. eg: 893 * - 'bp_activity_set_groups_scope_args' 894 * - 'bp_activity_set_friends_scope_args' 895 * 896 * To see how this filter is used, plugin devs should check out: 897 * - bp_groups_filter_activity_scope() - used for 'groups' scope 898 * - bp_friends_filter_activity_scope() - used for 'friends' scope 899 * 900 * @since BuddyPress (2.2.0) 901 * 902 * @param array { 903 * Activity query clauses. 904 * 905 * @type array { 906 * Activity arguments for your custom scope. 907 * See {@link BP_Activity_Query::_construct()} for more details. 908 * } 909 * @type array $override Optional. Override existing activity arguments passed by $r. 910 * } 911 * @param array $r Current activity arguments passed in BP_Activity_Activity::get() 912 */ 913 $scope_args = apply_filters( "bp_activity_set_{$scope}_scope_args", array(), $r ); 914 915 if ( ! empty( $scope_args ) ) { 916 // merge override properties from other scopes 917 // this might be a problem... 918 if ( ! empty( $scope_args['override'] ) ) { 919 $override = array_merge( $override, $scope_args['override'] ); 920 unset( $scope_args['override'] ); 921 } 922 923 // save scope args 924 if ( ! empty( $scope_args ) ) { 925 $query_args[] = $scope_args; 926 } 927 } 928 } 929 930 if ( ! empty( $query_args ) ) { 931 // set relation to OR 932 $query_args['relation'] = 'OR'; 933 934 $query = new BP_Activity_Query( $query_args ); 935 $sql = $query->get_sql(); 936 if ( ! empty( $sql ) ) { 937 $retval['sql'] = $sql; 938 } 939 } 940 941 if ( ! empty( $override ) ) { 942 $retval['override'] = $override; 943 } 944 945 return $retval; 946 } 947 948 /** 949 * In BuddyPress 1.2.x, this was used to retrieve specific activity stream items (for example, on an activity's permalink page). 950 * 951 * As of 1.5.x, use BP_Activity_Activity::get() with an 'in' parameter instead. 952 * 953 * @since BuddyPress (1.2) 954 * 955 * @deprecated 1.5 956 * @deprecated Use BP_Activity_Activity::get() with an 'in' parameter instead. 957 * 958 * @param mixed $activity_ids Array or comma-separated string of activity IDs to retrieve 959 * @param int $max Maximum number of results to return. (Optional; default is no maximum) 960 * @param int $page The set of results that the user is viewing. Used in pagination. (Optional; default is 1) 961 * @param int $per_page Specifies how many results per page. Used in pagination. (Optional; default is 25) 962 * @param string MySQL column sort; ASC or DESC. (Optional; default is DESC) 963 * @param bool $display_comments Retrieve an activity item's associated comments or not. (Optional; default is false) 964 * @return array 965 */ 966 public static function get_specific( $activity_ids, $max = false, $page = 1, $per_page = 25, $sort = 'DESC', $display_comments = false ) { 967 _deprecated_function( __FUNCTION__, '1.5', 'Use BP_Activity_Activity::get() with the "in" parameter instead.' ); 968 return BP_Activity_Activity::get( $max, $page, $per_page, $sort, false, false, $display_comments, false, false, $activity_ids ); 969 } 970 971 /** 972 * Get the first activity ID that matches a set of criteria. 973 * 974 * @param int $user_id User ID to filter by 975 * @param string $component Component to filter by 976 * @param string $type Activity type to filter by 977 * @param int $item_id Associated item to filter by 978 * @param int $secondary_item_id Secondary associated item to filter by 979 * @param string $action Action to filter by 980 * @param string $content Content to filter by 981 * @param string $date_recorded Date to filter by 982 * 983 * @todo Should parameters be optional? 984 * 985 * @return int|bool Activity ID on success, false if none is found. 986 */ 987 public static function get_id( $user_id, $component, $type, $item_id, $secondary_item_id, $action, $content, $date_recorded ) { 988 global $wpdb; 989 990 $bp = buddypress(); 991 992 $where_args = false; 993 994 if ( ! empty( $user_id ) ) { 995 $where_args[] = $wpdb->prepare( "user_id = %d", $user_id ); 996 } 997 998 if ( ! empty( $component ) ) { 999 $where_args[] = $wpdb->prepare( "component = %s", $component ); 1000 } 1001 1002 if ( ! empty( $type ) ) { 1003 $where_args[] = $wpdb->prepare( "type = %s", $type ); 1004 } 1005 1006 if ( ! empty( $item_id ) ) { 1007 $where_args[] = $wpdb->prepare( "item_id = %d", $item_id ); 1008 } 1009 1010 if ( ! empty( $secondary_item_id ) ) { 1011 $where_args[] = $wpdb->prepare( "secondary_item_id = %d", $secondary_item_id ); 1012 } 1013 1014 if ( ! empty( $action ) ) { 1015 $where_args[] = $wpdb->prepare( "action = %s", $action ); 1016 } 1017 1018 if ( ! empty( $content ) ) { 1019 $where_args[] = $wpdb->prepare( "content = %s", $content ); 1020 } 1021 1022 if ( ! empty( $date_recorded ) ) { 1023 $where_args[] = $wpdb->prepare( "date_recorded = %s", $date_recorded ); 1024 } 1025 1026 if ( ! empty( $where_args ) ) { 1027 $where_sql = 'WHERE ' . join( ' AND ', $where_args ); 1028 return $wpdb->get_var( "SELECT id FROM {$bp->activity->table_name} {$where_sql}" ); 1029 } 1030 1031 return false; 1032 } 1033 1034 /** 1035 * Delete activity items from the database. 1036 * 1037 * To delete a specific activity item, pass an 'id' parameter. 1038 * Otherwise use the filters. 1039 * 1040 * @since BuddyPress (1.2) 1041 * 1042 * @param array $args { 1043 * @int $id Optional. The ID of a specific item to delete. 1044 * @string $action Optional. The action to filter by. 1045 * @string $content Optional. The content to filter by. 1046 * @string $component Optional. The component name to filter by. 1047 * @string $type Optional. The activity type to filter by. 1048 * @string $primary_link Optional. The primary URL to filter by. 1049 * @int $user_id Optional. The user ID to filter by. 1050 * @int $item_id Optional. The associated item ID to filter by. 1051 * @int $secondary_item_id Optional. The secondary associated item ID to filter by. 1052 * @string $date_recorded Optional. The date to filter by. 1053 * @int $hide_sitewide Optional. Default: false. 1054 * } 1055 * @return array|bool An array of deleted activity IDs on success, false on failure. 1056 */ 1057 public static function delete( $args = array() ) { 1058 global $wpdb; 1059 1060 $bp = buddypress(); 1061 1062 $defaults = array( 1063 'id' => false, 1064 'action' => false, 1065 'content' => false, 1066 'component' => false, 1067 'type' => false, 1068 'primary_link' => false, 1069 'user_id' => false, 1070 'item_id' => false, 1071 'secondary_item_id' => false, 1072 'date_recorded' => false, 1073 'hide_sitewide' => false 1074 ); 1075 $params = wp_parse_args( $args, $defaults ); 1076 extract( $params ); 1077 1078 $where_args = false; 1079 1080 if ( !empty( $id ) ) 1081 $where_args[] = $wpdb->prepare( "id = %d", $id ); 1082 1083 if ( !empty( $user_id ) ) 1084 $where_args[] = $wpdb->prepare( "user_id = %d", $user_id ); 1085 1086 if ( !empty( $action ) ) 1087 $where_args[] = $wpdb->prepare( "action = %s", $action ); 1088 1089 if ( !empty( $content ) ) 1090 $where_args[] = $wpdb->prepare( "content = %s", $content ); 1091 1092 if ( !empty( $component ) ) 1093 $where_args[] = $wpdb->prepare( "component = %s", $component ); 1094 1095 if ( !empty( $type ) ) 1096 $where_args[] = $wpdb->prepare( "type = %s", $type ); 1097 1098 if ( !empty( $primary_link ) ) 1099 $where_args[] = $wpdb->prepare( "primary_link = %s", $primary_link ); 1100 1101 if ( !empty( $item_id ) ) 1102 $where_args[] = $wpdb->prepare( "item_id = %d", $item_id ); 1103 1104 if ( !empty( $secondary_item_id ) ) 1105 $where_args[] = $wpdb->prepare( "secondary_item_id = %d", $secondary_item_id ); 1106 1107 if ( !empty( $date_recorded ) ) 1108 $where_args[] = $wpdb->prepare( "date_recorded = %s", $date_recorded ); 1109 1110 if ( !empty( $hide_sitewide ) ) 1111 $where_args[] = $wpdb->prepare( "hide_sitewide = %d", $hide_sitewide ); 1112 1113 if ( !empty( $where_args ) ) 1114 $where_sql = 'WHERE ' . join( ' AND ', $where_args ); 1115 else 1116 return false; 1117 1118 // Fetch the activity IDs so we can delete any comments for this activity item 1119 $activity_ids = $wpdb->get_col( "SELECT id FROM {$bp->activity->table_name} {$where_sql}" ); 1120 1121 if ( ! $wpdb->query( "DELETE FROM {$bp->activity->table_name} {$where_sql}" ) ) { 1122 return false; 1123 } 1124 1125 // Handle accompanying activity comments and meta deletion 1126 if ( $activity_ids ) { 1127 $activity_ids_comma = implode( ',', wp_parse_id_list( $activity_ids ) ); 1128 $activity_comments_where_sql = "WHERE type = 'activity_comment' AND item_id IN ({$activity_ids_comma})"; 1129 1130 // Fetch the activity comment IDs for our deleted activity items 1131 $activity_comment_ids = $wpdb->get_col( "SELECT id FROM {$bp->activity->table_name} {$activity_comments_where_sql}" ); 1132 1133 // We have activity comments! 1134 if ( ! empty( $activity_comment_ids ) ) { 1135 // Delete activity comments 1136 $wpdb->query( "DELETE FROM {$bp->activity->table_name} {$activity_comments_where_sql}" ); 1137 1138 // Merge activity IDs with activity comment IDs 1139 $activity_ids = array_merge( $activity_ids, $activity_comment_ids ); 1140 } 1141 1142 // Delete all activity meta entries for activity items and activity comments 1143 BP_Activity_Activity::delete_activity_meta_entries( $activity_ids ); 1144 } 1145 1146 return $activity_ids; 1147 } 1148 1149 /** 1150 * Delete the comments associated with a set of activity items. 1151 * 1152 * @since BuddyPress (1.2) 1153 * 1154 * @todo Mark as deprecated? Method is no longer used internally. 1155 * 1156 * @param array $activity_ids Activity IDs whose comments should be deleted. 1157 * @param bool $delete_meta Should we delete the activity meta items for these comments? 1158 * @return bool True on success. 1159 */ 1160 public static function delete_activity_item_comments( $activity_ids = array(), $delete_meta = true ) { 1161 global $wpdb; 1162 1163 $bp = buddypress(); 1164 1165 $delete_meta = (bool) $delete_meta; 1166 $activity_ids = implode( ',', wp_parse_id_list( $activity_ids ) ); 1167 1168 if ( $delete_meta ) { 1169 // Fetch the activity comment IDs for our deleted activity items 1170 $activity_comment_ids = $wpdb->get_col( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" ); 1171 1172 if ( ! empty( $activity_comment_ids ) ) { 1173 self::delete_activity_meta_entries( $activity_comment_ids ); 1174 } 1175 } 1176 1177 return $wpdb->query( "DELETE FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" ); 1178 } 1179 1180 /** 1181 * Delete the meta entries associated with a set of activity items. 1182 * 1183 * @since BuddyPress (1.2) 1184 * 1185 * @param array $activity_ids Activity IDs whose meta should be deleted. 1186 * @return bool True on success. 1187 */ 1188 public static function delete_activity_meta_entries( $activity_ids = array() ) { 1189 $activity_ids = wp_parse_id_list( $activity_ids ); 1190 1191 foreach ( $activity_ids as $activity_id ) { 1192 bp_activity_delete_meta( $activity_id ); 1193 } 1194 1195 return true; 1196 } 1197 1198 /** 1199 * Append activity comments to their associated activity items. 1200 * 1201 * @since BuddyPress (1.2) 1202 * 1203 * @global wpdb $wpdb WordPress database object 1204 * 1205 * @param array $activities Activities to fetch comments for. 1206 * @param bool $spam Optional. 'ham_only' (default), 'spam_only' or 'all'. 1207 * @return array The updated activities with nested comments. 1208 */ 1209 public static function append_comments( $activities, $spam = 'ham_only' ) { 1210 $activity_comments = array(); 1211 1212 // Now fetch the activity comments and parse them into the correct position in the activities array. 1213 foreach ( (array) $activities as $activity ) { 1214 $top_level_parent_id = 'activity_comment' == $activity->type ? $activity->item_id : 0; 1215 $activity_comments[$activity->id] = BP_Activity_Activity::get_activity_comments( $activity->id, $activity->mptt_left, $activity->mptt_right, $spam, $top_level_parent_id ); 1216 } 1217 1218 // Merge the comments with the activity items 1219 foreach ( (array) $activities as $key => $activity ) { 1220 if ( isset( $activity_comments[$activity->id] ) ) { 1221 $activities[$key]->children = $activity_comments[$activity->id]; 1222 } 1223 } 1224 1225 return $activities; 1226 } 1227 1228 /** 1229 * Get activity comments that are associated with a specific activity ID. 1230 * 1231 * @since BuddyPress (1.2) 1232 * 1233 * @global wpdb $wpdb WordPress database object. 1234 * 1235 * @param int $activity_id Activity ID to fetch comments for. 1236 * @param int $left Left-most node boundary. 1237 * @param into $right Right-most node boundary. 1238 * @param bool $spam Optional. 'ham_only' (default), 'spam_only' or 'all'. 1239 * @param int $top_level_parent_id Optional. The id of the root-level parent activity item. 1240 * @return array The updated activities with nested comments. 1241 */ 1242 public static function get_activity_comments( $activity_id, $left, $right, $spam = 'ham_only', $top_level_parent_id = 0 ) { 1243 global $wpdb; 1244 1245 if ( empty( $top_level_parent_id ) ) { 1246 $top_level_parent_id = $activity_id; 1247 } 1248 1249 $comments = wp_cache_get( $activity_id, 'bp_activity_comments' ); 1250 1251 // We store the string 'none' to cache the fact that the 1252 // activity item has no comments 1253 if ( 'none' === $comments ) { 1254 $comments = false; 1255 1256 // A true cache miss 1257 } elseif ( empty( $comments ) ) { 1258 1259 $bp = buddypress(); 1260 1261 // Select the user's fullname with the query 1262 if ( bp_is_active( 'xprofile' ) ) { 1263 $fullname_select = ", pd.value as user_fullname"; 1264 $fullname_from = ", {$bp->profile->table_name_data} pd "; 1265 $fullname_where = "AND pd.user_id = a.user_id AND pd.field_id = 1"; 1266 1267 // Prevent debug errors 1268 } else { 1269 $fullname_select = $fullname_from = $fullname_where = ''; 1270 } 1271 1272 // Don't retrieve activity comments marked as spam 1273 if ( 'ham_only' == $spam ) { 1274 $spam_sql = 'AND a.is_spam = 0'; 1275 } elseif ( 'spam_only' == $spam ) { 1276 $spam_sql = 'AND a.is_spam = 1'; 1277 } else { 1278 $spam_sql = ''; 1279 } 1280 1281 // Legacy query - not recommended 1282 $func_args = func_get_args(); 1283 1284 /** 1285 * Filters if BuddyPress should use the legacy activity query. 1286 * 1287 * @since BuddyPress (2.0.0) 1288 * 1289 * @param bool Whether or not to use the legacy query. 1290 * @param BP_Activity_Activity Magic method referring to currently called method. 1291 * @param array $func_args Array of the method's argument list. 1292 */ 1293 if ( apply_filters( 'bp_use_legacy_activity_query', false, __METHOD__, $func_args ) ) { 1294 1295 /** 1296 * Filters the MySQL prepared statement for the legacy activity query. 1297 * 1298 * @since BuddyPress (1.5.0) 1299 * 1300 * @param string Prepared statement for the activity query. 1301 * @param int $activity_id Activity ID to fetch comments for. 1302 * @param int $left Left-most node boundary. 1303 * @param int $right Right-most node boundary. 1304 * @param string $spam_sql SQL Statement portion to differentiate between ham or spam. 1305 */ 1306 $sql = apply_filters( 'bp_activity_comments_user_join_filter', $wpdb->prepare( "SELECT a.*, u.user_email, u.user_nicename, u.user_login, u.display_name{$fullname_select} FROM {$bp->activity->table_name} a, {$wpdb->users} u{$fullname_from} WHERE u.ID = a.user_id {$fullname_where} AND a.type = 'activity_comment' {$spam_sql} AND a.item_id = %d AND a.mptt_left > %d AND a.mptt_left < %d ORDER BY a.date_recorded ASC", $top_level_parent_id, $left, $right ), $activity_id, $left, $right, $spam_sql ); 1307 1308 $descendants = $wpdb->get_results( $sql ); 1309 1310 // We use the mptt BETWEEN clause to limit returned 1311 // descendants to the correct part of the tree. 1312 } else { 1313 $sql = $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} a WHERE a.type = 'activity_comment' {$spam_sql} AND a.item_id = %d and a.mptt_left > %d AND a.mptt_left < %d ORDER BY a.date_recorded ASC", $top_level_parent_id, $left, $right ); 1314 1315 $descendant_ids = $wpdb->get_col( $sql ); 1316 $descendants = self::get_activity_data( $descendant_ids ); 1317 $descendants = self::append_user_fullnames( $descendants ); 1318 } 1319 1320 $ref = array(); 1321 1322 // Loop descendants and build an assoc array 1323 foreach ( (array) $descendants as $d ) { 1324 $d->children = array(); 1325 1326 // If we have a reference on the parent 1327 if ( isset( $ref[ $d->secondary_item_id ] ) ) { 1328 $ref[ $d->secondary_item_id ]->children[ $d->id ] = $d; 1329 $ref[ $d->id ] =& $ref[ $d->secondary_item_id ]->children[ $d->id ]; 1330 1331 // If we don't have a reference on the parent, put in the root level 1332 } else { 1333 $comments[ $d->id ] = $d; 1334 $ref[ $d->id ] =& $comments[ $d->id ]; 1335 } 1336 } 1337 1338 // Calculate depth for each item 1339 foreach ( $ref as &$r ) { 1340 $depth = 1; 1341 $parent_id = $r->secondary_item_id; 1342 while ( $parent_id !== $r->item_id ) { 1343 $depth++; 1344 1345 // When display_comments=stream, the 1346 // parent comment may not be part of 1347 // the returned results, so we manually 1348 // fetch it 1349 if ( empty( $ref[ $parent_id ] ) ) { 1350 $direct_parent = new BP_Activity_Activity( $parent_id ); 1351 if ( isset( $direct_parent->secondary_item_id ) ) { 1352 $parent_id = $direct_parent->secondary_item_id; 1353 } else { 1354 // Something went wrong 1355 // Short-circuit the 1356 // depth calculation 1357 $parent_id = $r->item_id; 1358 } 1359 } else { 1360 $parent_id = $ref[ $parent_id ]->secondary_item_id; 1361 } 1362 } 1363 $r->depth = $depth; 1364 } 1365 1366 // If we cache a value of false, it'll count as a cache 1367 // miss the next time the activity comments are fetched. 1368 // Storing the string 'none' is a hack workaround to 1369 // avoid unnecessary queries. 1370 if ( false === $comments ) { 1371 $cache_value = 'none'; 1372 } else { 1373 $cache_value = $comments; 1374 } 1375 1376 wp_cache_set( $activity_id, $cache_value, 'bp_activity_comments' ); 1377 } 1378 1379 return $comments; 1380 } 1381 1382 /** 1383 * Rebuild nested comment tree under an activity or activity comment. 1384 * 1385 * @since BuddyPress (1.2) 1386 * 1387 * @global wpdb $wpdb WordPress database object 1388 * 1389 * @param int $parent_id ID of an activity or activity comment 1390 * @param int $left Node boundary start for activity or activity comment 1391 * @return int Right Node boundary of activity or activity comment 1392 */ 1393 public static function rebuild_activity_comment_tree( $parent_id, $left = 1 ) { 1394 global $wpdb; 1395 1396 $bp = buddypress(); 1397 1398 // The right value of this node is the left value + 1 1399 $right = intval( $left + 1 ); 1400 1401 // Get all descendants of this node 1402 $comments = BP_Activity_Activity::get_child_comments( $parent_id ); 1403 $descendants = wp_list_pluck( $comments, 'id' ); 1404 1405 // Loop the descendants and recalculate the left and right values 1406 foreach ( (array) $descendants as $descendant_id ) { 1407 $right = BP_Activity_Activity::rebuild_activity_comment_tree( $descendant_id, $right ); 1408 } 1409 1410 // We've got the left value, and now that we've processed the children 1411 // of this node we also know the right value 1412 if ( 1 === $left ) { 1413 $wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET mptt_left = %d, mptt_right = %d WHERE id = %d", $left, $right, $parent_id ) ); 1414 } else { 1415 $wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET mptt_left = %d, mptt_right = %d WHERE type = 'activity_comment' AND id = %d", $left, $right, $parent_id ) ); 1416 } 1417 1418 // Return the right value of this node + 1 1419 return intval( $right + 1 ); 1420 } 1421 1422 /** 1423 * Get child comments of an activity or activity comment. 1424 * 1425 * @since BuddyPress (1.2) 1426 * 1427 * @global wpdb $wpdb WordPress database object. 1428 * 1429 * @param int $parent_id ID of an activity or activity comment. 1430 * @return object Numerically indexed array of child comments. 1431 */ 1432 public static function get_child_comments( $parent_id ) { 1433 global $wpdb; 1434 1435 $bp = buddypress(); 1436 1437 return $wpdb->get_results( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND secondary_item_id = %d", $parent_id ) ); 1438 } 1439 1440 /** 1441 * Get a list of components that have recorded activity associated with them. 1442 * 1443 * @param bool $skip_last_activity If true, components will not be 1444 * included if the only activity type associated with them is 1445 * 'last_activity'. (Since 2.0.0, 'last_activity' is stored in 1446 * the activity table, but these items are not full-fledged 1447 * activity items.) Default: true. 1448 * @return array List of component names. 1449 */ 1450 public static function get_recorded_components( $skip_last_activity = true ) { 1451 global $wpdb; 1452 1453 $bp = buddypress(); 1454 1455 if ( true === $skip_last_activity ) { 1456 $components = $wpdb->get_col( "SELECT DISTINCT component FROM {$bp->activity->table_name} WHERE action != '' AND action != 'last_activity' ORDER BY component ASC" ); 1457 } else { 1458 $components = $wpdb->get_col( "SELECT DISTINCT component FROM {$bp->activity->table_name} ORDER BY component ASC" ); 1459 } 1460 1461 return $components; 1462 } 1463 1464 /** 1465 * Get sitewide activity items for use in an RSS feed. 1466 * 1467 * @param int $limit Optional. Number of items to fetch. Default: 35. 1468 * @return array $activity_feed List of activity items, with RSS data added. 1469 */ 1470 public static function get_sitewide_items_for_feed( $limit = 35 ) { 1471 $activities = bp_activity_get_sitewide( array( 'max' => $limit ) ); 1472 $activity_feed = array(); 1473 1474 for ( $i = 0, $count = count( $activities ); $i < $count; ++$i ) { 1475 $title = explode( '<span', $activities[$i]['content'] ); 1476 $activity_feed[$i]['title'] = trim( strip_tags( $title[0] ) ); 1477 $activity_feed[$i]['link'] = $activities[$i]['primary_link']; 1478 $activity_feed[$i]['description'] = @sprintf( $activities[$i]['content'], '' ); 1479 $activity_feed[$i]['pubdate'] = $activities[$i]['date_recorded']; 1480 } 1481 1482 return $activity_feed; 1483 } 1484 1485 /** 1486 * Create SQL IN clause for filter queries. 1487 * 1488 * @since BuddyPress (1.5) 1489 * 1490 * @see BP_Activity_Activity::get_filter_sql() 1491 * 1492 * @param string $field The database field. 1493 * @param array|bool $items The values for the IN clause, or false when none are found. 1494 */ 1495 public static function get_in_operator_sql( $field, $items ) { 1496 global $wpdb; 1497 1498 // split items at the comma 1499 if ( ! is_array( $items ) ) { 1500 $items = explode( ',', $items ); 1501 } 1502 1503 // array of prepared integers or quoted strings 1504 $items_prepared = array(); 1505 1506 // clean up and format each item 1507 foreach ( $items as $item ) { 1508 // clean up the string 1509 $item = trim( $item ); 1510 // pass everything through prepare for security and to safely quote strings 1511 $items_prepared[] = ( is_numeric( $item ) ) ? $wpdb->prepare( '%d', $item ) : $wpdb->prepare( '%s', $item ); 1512 } 1513 1514 // build IN operator sql syntax 1515 if ( count( $items_prepared ) ) 1516 return sprintf( '%s IN ( %s )', trim( $field ), implode( ',', $items_prepared ) ); 1517 else 1518 return false; 1519 } 1520 1521 /** 1522 * Create filter SQL clauses. 1523 * 1524 * @since BuddyPress (1.5.0) 1525 * 1526 * @param array $filter_array { 1527 * Fields and values to filter by. 1528 * @type array|string|id $user_id User ID(s). 1529 * @type array|string $object Corresponds to the 'component' 1530 * column in the database. 1531 * @type array|string $action Corresponds to the 'type' column 1532 * in the database. 1533 * @type array|string|int $primary_id Corresponds to the 'item_id' 1534 * column in the database. 1535 * @type array|string|int $secondary_id Corresponds to the 1536 * 'secondary_item_id' column in the database. 1537 * @type int $offset Return only those items with an ID greater 1538 * than the offset value. 1539 * @type string $since Return only those items that have a 1540 * date_recorded value greater than a given MySQL-formatted 1541 * date. 1542 * } 1543 * @return string The filter clause, for use in a SQL query. 1544 */ 1545 public static function get_filter_sql( $filter_array ) { 1546 1547 $filter_sql = array(); 1548 1549 if ( !empty( $filter_array['user_id'] ) ) { 1550 $user_sql = BP_Activity_Activity::get_in_operator_sql( 'a.user_id', $filter_array['user_id'] ); 1551 if ( !empty( $user_sql ) ) 1552 $filter_sql[] = $user_sql; 1553 } 1554 1555 if ( !empty( $filter_array['object'] ) ) { 1556 $object_sql = BP_Activity_Activity::get_in_operator_sql( 'a.component', $filter_array['object'] ); 1557 if ( !empty( $object_sql ) ) 1558 $filter_sql[] = $object_sql; 1559 } 1560 1561 if ( !empty( $filter_array['action'] ) ) { 1562 $action_sql = BP_Activity_Activity::get_in_operator_sql( 'a.type', $filter_array['action'] ); 1563 if ( ! empty( $action_sql ) ) 1564 $filter_sql[] = $action_sql; 1565 } 1566 1567 if ( !empty( $filter_array['primary_id'] ) ) { 1568 $pid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.item_id', $filter_array['primary_id'] ); 1569 if ( !empty( $pid_sql ) ) 1570 $filter_sql[] = $pid_sql; 1571 } 1572 1573 if ( !empty( $filter_array['secondary_id'] ) ) { 1574 $sid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.secondary_item_id', $filter_array['secondary_id'] ); 1575 if ( !empty( $sid_sql ) ) 1576 $filter_sql[] = $sid_sql; 1577 } 1578 1579 if ( ! empty( $filter_array['offset'] ) ) { 1580 $sid_sql = absint( $filter_array['offset'] ); 1581 $filter_sql[] = "a.id >= {$sid_sql}"; 1582 } 1583 1584 if ( ! empty( $filter_array['since'] ) ) { 1585 // Validate that this is a proper Y-m-d H:i:s date 1586 // Trick: parse to UNIX date then translate back 1587 $translated_date = date( 'Y-m-d H:i:s', strtotime( $filter_array['since'] ) ); 1588 if ( $translated_date === $filter_array['since'] ) { 1589 $filter_sql[] = "a.date_recorded > '{$translated_date}'"; 1590 } 1591 } 1592 1593 if ( empty( $filter_sql ) ) 1594 return false; 1595 1596 return join( ' AND ', $filter_sql ); 1597 } 1598 1599 /** 1600 * Get the date/time of last recorded activity. 1601 * 1602 * @since BuddyPress (1.2) 1603 * 1604 * @return string ISO timestamp. 1605 */ 1606 public static function get_last_updated() { 1607 global $wpdb; 1608 1609 $bp = buddypress(); 1610 1611 return $wpdb->get_var( "SELECT date_recorded FROM {$bp->activity->table_name} ORDER BY date_recorded DESC LIMIT 1" ); 1612 } 1613 1614 /** 1615 * Get favorite count for a given user. 1616 * 1617 * @since BuddyPress (1.2) 1618 * 1619 * @param int The ID of the user whose favorites you're counting. 1620 * @return int A count of the user's favorites. 1621 */ 1622 public static function total_favorite_count( $user_id ) { 1623 1624 // Get activities from user meta 1625 $favorite_activity_entries = bp_get_user_meta( $user_id, 'bp_favorite_activities', true ); 1626 if ( ! empty( $favorite_activity_entries ) ) { 1627 return count( maybe_unserialize( $favorite_activity_entries ) ); 1628 } 1629 1630 // No favorites 1631 return 0; 1632 } 1633 1634 /** 1635 * Check whether an activity item exists with a given string content. 1636 * 1637 * @param string $content The content to filter by. 1638 * @return int|bool The ID of the first matching item if found, otherwise false. 1639 */ 1640 public static function check_exists_by_content( $content ) { 1641 global $wpdb; 1642 1643 $bp = buddypress(); 1644 1645 return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE content = %s", $content ) ); 1646 } 1647 1648 /** 1649 * Hide all activity for a given user. 1650 * 1651 * @param int $user_id The ID of the user whose activity you want to mark hidden. 1652 * @param int 1653 */ 1654 public static function hide_all_for_user( $user_id ) { 1655 global $wpdb; 1656 1657 $bp = buddypress(); 1658 1659 return $wpdb->get_var( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET hide_sitewide = 1 WHERE user_id = %d", $user_id ) ); 1660 } 1661 1662 /** 1663 * PHP-agnostic version of {@link array_replace_recursive()}. 1664 * 1665 * array_replace_recursive() is a PHP 5.3 function. BuddyPress (and 1666 * WordPress) currently supports down to PHP 5.2, so this method is a workaround 1667 * for PHP 5.2. 1668 * 1669 * Note: array_replace_recursive() supports infinite arguments, but for our use- 1670 * case, we only need to support two arguments. 1671 * 1672 * Subject to removal once WordPress makes PHP 5.3.0 the minimum requirement. 1673 * 1674 * @since BuddyPress (2.2.0) 1675 * 1676 * @see http://php.net/manual/en/function.array-replace-recursive.php#109390 1677 * 1678 * @param array $base Array with keys needing to be replaced 1679 * @param array $replacements Array with the replaced keys 1680 * @return array 1681 */ 1682 protected static function array_replace_recursive( $base = array(), $replacements = array() ) { 1683 if ( function_exists( 'array_replace_recursive' ) ) { 1684 return array_replace_recursive( $base, $replacements ); 1685 } 1686 1687 // PHP 5.2-compatible version 1688 // http://php.net/manual/en/function.array-replace-recursive.php#109390 1689 foreach ( array_slice( func_get_args(), 1 ) as $replacements ) { 1690 $bref_stack = array( &$base ); 1691 $head_stack = array( $replacements ); 1692 1693 do { 1694 end( $bref_stack ); 1695 1696 $bref = &$bref_stack[ key( $bref_stack ) ]; 1697 $head = array_pop( $head_stack ); 1698 1699 unset( $bref_stack[ key($bref_stack) ] ); 1700 1701 foreach ( array_keys( $head ) as $key ) { 1702 if ( isset( $key, $bref ) && is_array( $bref[$key] ) && is_array( $head[$key] ) ) { 1703 $bref_stack[] = &$bref[ $key ]; 1704 $head_stack[] = $head[ $key ]; 1705 } else { 1706 $bref[ $key ] = $head[ $key ]; 1707 } 1708 } 1709 } while( count( $head_stack ) ); 1710 } 1711 1712 return $base; 1713 } 1714 } 1715 1716 /** 1717 * Class for generating the WHERE SQL clause for advanced activity fetching. 1718 * 1719 * This is notably used in {@link BP_Activity_Activity::get()} with the 1720 * 'filter_query' parameter. 1721 * 1722 * @since BuddyPress (2.2.0) 1723 */ 1724 class BP_Activity_Query extends BP_Recursive_Query { 1725 /** 1726 * Array of activity queries. 1727 * 1728 * See {@see BP_Activity_Query::__construct()} for information on query arguments. 1729 * 1730 * @since BuddyPress (2.2.0) 1731 * @access public 1732 * @var array 1733 */ 1734 public $queries = array(); 1735 1736 /** 1737 * Table alias. 1738 * 1739 * @since BuddyPress (2.2.0) 1740 * @access public 1741 * @var string 1742 */ 1743 public $table_alias = ''; 1744 1745 /** 1746 * Supported DB columns. 1747 * 1748 * See the 'wp_bp_activity' DB table schema. 1749 * 1750 * @since BuddyPress (2.2.0) 1751 * @access public 1752 * @var array 1753 */ 1754 public $db_columns = array( 1755 'id', 'user_id', 'component', 'type', 'action', 'content', 1756 'item_id', 'secondary_item_id', 'hide_sitewide', 'is_spam', 1757 ); 1758 1759 /** 1760 * Constructor. 1761 * 1762 * @since BuddyPress (2.2.0) 1763 * 1764 * @param array $query { 1765 * Array of query clauses. 1766 * 1767 * @type array { 1768 * @type string $column Required. The column to query against. Basically, any DB column in the main 1769 * 'wp_bp_activity' table. 1770 * @type string $value Required. Value to filter by. 1771 * @type string $compare Optional. The comparison operator. Default '='. 1772 * Accepts '=', '!=', '>', '>=', '<', '<=', 'IN', 'NOT IN', 'LIKE', 1773 * 'NOT LIKE', BETWEEN', 'NOT BETWEEN', 'REGEXP', 'NOT REGEXP', 'RLIKE' 1774 * @type string $relation Optional. The boolean relationship between the activity queries. 1775 * Accepts 'OR', 'AND'. Default 'AND'. 1776 * @type array { 1777 * Optional. Another fully-formed activity query. See parameters above. 1778 * } 1779 * } 1780 * } 1781 */ 1782 public function __construct( $query = array() ) { 1783 if ( ! is_array( $query ) ) { 1784 return; 1785 } 1786 1787 $this->queries = $this->sanitize_query( $query ); 1788 } 1789 1790 /** 1791 * Generates WHERE SQL clause to be appended to a main query. 1792 * 1793 * @since BuddyPress (2.2.0) 1794 * @access public 1795 * 1796 * @param string $alias An existing table alias that is compatible with the current query clause. 1797 * Default: 'a'. BP_Activity_Activity::get() uses 'a', so we default to that. 1798 * @return string SQL fragment to append to the main WHERE clause. 1799 */ 1800 public function get_sql( $alias = 'a' ) { 1801 if ( ! empty( $alias ) ) { 1802 $this->table_alias = sanitize_title( $alias ); 1803 } 1804 1805 $sql = $this->get_sql_clauses(); 1806 1807 // we only need the 'where' clause 1808 // 1809 // also trim trailing "AND" clause from parent BP_Recursive_Query class 1810 // since it's not necessary for our needs 1811 return preg_replace( '/^\sAND/', '', $sql['where'] ); 1812 } 1813 1814 /** 1815 * Generate WHERE clauses for a first-order clause. 1816 * 1817 * @since BuddyPress (2.2.0) 1818 * @access protected 1819 * 1820 * @param array $clause Array of arguments belonging to the clause. 1821 * @param array $parent_query Parent query to which the clause belongs. 1822 * @return array { 1823 * @type array $where Array of subclauses for the WHERE statement. 1824 * @type array $join Empty array. Not used. 1825 * } 1826 */ 1827 protected function get_sql_for_clause( $clause, $parent_query ) { 1828 global $wpdb; 1829 1830 $sql_chunks = array( 1831 'where' => array(), 1832 'join' => array(), 1833 ); 1834 1835 $column = isset( $clause['column'] ) ? $this->validate_column( $clause['column'] ) : ''; 1836 $value = isset( $clause['value'] ) ? $clause['value'] : ''; 1837 if ( empty( $column ) || ! isset( $clause['value'] ) ) { 1838 return $sql_chunks; 1839 } 1840 1841 if ( isset( $clause['compare'] ) ) { 1842 $clause['compare'] = strtoupper( $clause['compare'] ); 1843 } else { 1844 $clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '='; 1845 } 1846 1847 // default 'compare' to '=' if no valid operator is found 1848 if ( ! in_array( $clause['compare'], array( 1849 '=', '!=', '>', '>=', '<', '<=', 1850 'LIKE', 'NOT LIKE', 1851 'IN', 'NOT IN', 1852 'BETWEEN', 'NOT BETWEEN', 1853 'REGEXP', 'NOT REGEXP', 'RLIKE' 1854 ) ) ) { 1855 $clause['compare'] = '='; 1856 } 1857 1858 $compare = $clause['compare']; 1859 1860 $alias = ! empty( $this->table_alias ) ? "{$this->table_alias}." : ''; 1861 1862 // Next, Build the WHERE clause. 1863 $where = ''; 1864 1865 // value. 1866 if ( isset( $clause['value'] ) ) { 1867 if ( in_array( $compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) { 1868 if ( ! is_array( $value ) ) { 1869 $value = preg_split( '/[,\s]+/', $value ); 1870 } 1871 } 1872 1873 // tinyint 1874 if ( ! empty( $column ) && true === in_array( $column, array( 'hide_sitewide', 'is_spam' ) ) ) { 1875 $sql_chunks['where'][] = $wpdb->prepare( "{$alias}{$column} = %d", $value ); 1876 1877 } else { 1878 switch ( $compare ) { 1879 // IN uses different syntax 1880 case 'IN' : 1881 case 'NOT IN' : 1882 $in_sql = BP_Activity_Activity::get_in_operator_sql( "{$alias}{$column}", $value ); 1883 1884 // 'NOT IN' operator is as easy as a string replace! 1885 if ( 'NOT IN' === $compare ) { 1886 $in_sql = str_replace( 'IN', 'NOT IN', $in_sql ); 1887 } 1888 1889 $sql_chunks['where'][] = $in_sql; 1890 break; 1891 1892 case 'BETWEEN' : 1893 case 'NOT BETWEEN' : 1894 $value = array_slice( $value, 0, 2 ); 1895 $where = $wpdb->prepare( '%s AND %s', $value ); 1896 break; 1897 1898 case 'LIKE' : 1899 case 'NOT LIKE' : 1900 $value = '%' . bp_esc_like( $value ) . '%'; 1901 $where = $wpdb->prepare( '%s', $value ); 1902 break; 1903 1904 default : 1905 $where = $wpdb->prepare( '%s', $value ); 1906 break; 1907 1908 } 1909 } 1910 1911 if ( $where ) { 1912 $sql_chunks['where'][] = "{$alias}{$column} {$compare} {$where}"; 1913 } 1914 } 1915 1916 /* 1917 * Multiple WHERE clauses should be joined in parentheses. 1918 */ 1919 if ( 1 < count( $sql_chunks['where'] ) ) { 1920 $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' ); 1921 } 1922 1923 return $sql_chunks; 1924 } 1925 1926 /** 1927 * Determine whether a clause is first-order. 1928 * 1929 * @since BuddyPress (2.2.0) 1930 * @access protected 1931 * 1932 * @param array $query Clause to check. 1933 * @return bool 1934 */ 1935 protected function is_first_order_clause( $query ) { 1936 return isset( $query['column'] ) || isset( $query['value'] ); 1937 } 1938 1939 /** 1940 * Validates a column name parameter. 1941 * 1942 * Column names are checked against a whitelist of known tables. 1943 * See {@link BP_Activity_Query::db_tables}. 1944 * 1945 * @since BuddyPress (2.2.0) 1946 * @access public 1947 * 1948 * @param string $column The user-supplied column name. 1949 * @return string A validated column name value. 1950 */ 1951 public function validate_column( $column = '' ) { 1952 if ( in_array( $column, $this->db_columns ) ) { 1953 return $column; 1954 } else { 1955 return ''; 1956 } 1957 } 1958 } 1959 1960 /** 1961 * Create a RSS feed using the activity component. 1962 * 1963 * You should only construct a new feed when you've validated that you're on 1964 * the appropriate screen. 1965 * 1966 * See {@link bp_activity_action_sitewide_feed()} as an example. 1967 * 1968 * Accepted parameters: 1969 * id - internal id for the feed; should be alphanumeric only 1970 * (required) 1971 * title - RSS feed title 1972 * link - Relevant link for the RSS feed 1973 * description - RSS feed description 1974 * ttl - Time-to-live (see inline doc in constructor) 1975 * update_period - Part of the syndication module (see inline doc in 1976 * constructor for more info) 1977 * update_frequency - Part of the syndication module (see inline doc in 1978 * constructor for more info) 1979 * max - Number of feed items to display 1980 * activity_args - Arguments passed to {@link bp_has_activities()} 1981 * 1982 * @since BuddyPress (1.8) 1983 */ 1984 class BP_Activity_Feed { 1985 /** 1986 * Holds our custom class properties. 1987 * 1988 * These variables are stored in a protected array that is magically 1989 * updated using PHP 5.2+ methods. 1990 * 1991 * @see BP_Feed::__construct() This is where $data is added 1992 * @var array 1993 */ 1994 protected $data; 1995 1996 /** 1997 * Magic method for checking the existence of a certain data variable. 1998 * 1999 * @param string $key 2000 */ 2001 public function __isset( $key ) { return isset( $this->data[$key] ); } 2002 2003 /** 2004 * Magic method for getting a certain data variable. 2005 * 2006 * @param string $key 2007 */ 2008 public function __get( $key ) { return isset( $this->data[$key] ) ? $this->data[$key] : null; } 2009 2010 /** 2011 * Constructor. 2012 * 2013 * @param array $args Optional 2014 */ 2015 public function __construct( $args = array() ) { 2016 2017 /** 2018 * Filters if BuddyPress should consider feeds enabled. If disabled, it will return early. 2019 * 2020 * @since BuddyPress (1.8.0) 2021 * 2022 * @param bool true Default true aka feeds are enabled. 2023 */ 2024 if ( false === (bool) apply_filters( 'bp_activity_enable_feeds', true ) ) { 2025 global $wp_query; 2026 2027 // set feed flag to false 2028 $wp_query->is_feed = false; 2029 2030 return false; 2031 } 2032 2033 // Setup data 2034 $this->data = wp_parse_args( $args, array( 2035 // Internal identifier for the RSS feed - should be alphanumeric only 2036 'id' => '', 2037 2038 // RSS title - should be plain-text 2039 'title' => '', 2040 2041 // relevant link for the RSS feed 2042 'link' => '', 2043 2044 // RSS description - should be plain-text 2045 'description' => '', 2046 2047 // Time-to-live - number of minutes to cache the data before an aggregator 2048 // requests it again. This is only acknowledged if the RSS client supports it 2049 // 2050 // See: http://www.rssboard.org/rss-profile#element-channel-ttl 2051 // http://www.kbcafe.com/rss/rssfeedstate.html#ttl 2052 'ttl' => '30', 2053 2054 // Syndication module - similar to ttl, but not really supported by RSS 2055 // clients 2056 // 2057 // See: http://web.resource.org/rss/1.0/modules/syndication/#description 2058 // http://www.kbcafe.com/rss/rssfeedstate.html#syndicationmodule 2059 'update_period' => 'hourly', 2060 'update_frequency' => 2, 2061 2062 // Number of items to display 2063 'max' => 50, 2064 2065 // Activity arguments passed to bp_has_activities() 2066 'activity_args' => array() 2067 ) ); 2068 2069 /** 2070 * Fires before the feed is setup so plugins can modify. 2071 * 2072 * @since BuddyPress (1.8.0) 2073 * 2074 * @param BP_Activity_Feed Reference to current instance of activity feed. 2075 */ 2076 do_action_ref_array( 'bp_activity_feed_prefetch', array( &$this ) ); 2077 2078 // Setup class properties 2079 $this->setup_properties(); 2080 2081 // Check if id is valid 2082 if ( empty( $this->id ) ) { 2083 _doing_it_wrong( 'BP_Activity_Feed', __( "RSS feed 'id' must be defined", 'buddypress' ), 'BP 1.8' ); 2084 return false; 2085 } 2086 2087 /** 2088 * Fires after the feed is setup so plugins can modify. 2089 * 2090 * @since BuddyPress (1.8.0) 2091 * 2092 * @param BP_Activity_Feed Reference to current instance of activity feed. 2093 */ 2094 do_action_ref_array( 'bp_activity_feed_postfetch', array( &$this ) ); 2095 2096 // Setup feed hooks 2097 $this->setup_hooks(); 2098 2099 // Output the feed 2100 $this->output(); 2101 2102 // Kill the rest of the output 2103 die(); 2104 } 2105 2106 /** SETUP ****************************************************************/ 2107 2108 /** 2109 * Setup and validate the class properties. 2110 * 2111 * @access protected 2112 */ 2113 protected function setup_properties() { 2114 $this->id = sanitize_title( $this->id ); 2115 $this->title = strip_tags( $this->title ); 2116 $this->link = esc_url_raw( $this->link ); 2117 $this->description = strip_tags( $this->description ); 2118 $this->ttl = (int) $this->ttl; 2119 $this->update_period = strip_tags( $this->update_period ); 2120 $this->update_frequency = (int) $this->update_frequency; 2121 2122 $this->activity_args = wp_parse_args( $this->activity_args, array( 2123 'max' => $this->max, 2124 'per_page' => $this->max, 2125 'display_comments' => 'stream' 2126 ) ); 2127 2128 } 2129 2130 /** 2131 * Setup some hooks that are used in the feed. 2132 * 2133 * Currently, these hooks are used to maintain backwards compatibility with 2134 * the RSS feeds previous to BP 1.8. 2135 * 2136 * @access protected 2137 */ 2138 protected function setup_hooks() { 2139 add_action( 'bp_activity_feed_rss_attributes', array( $this, 'backpat_rss_attributes' ) ); 2140 add_action( 'bp_activity_feed_channel_elements', array( $this, 'backpat_channel_elements' ) ); 2141 add_action( 'bp_activity_feed_item_elements', array( $this, 'backpat_item_elements' ) ); 2142 } 2143 2144 /** BACKPAT HOOKS ********************************************************/ 2145 2146 /** 2147 * Fire a hook to ensure backward compatibility for RSS attributes. 2148 */ 2149 public function backpat_rss_attributes() { 2150 2151 /** 2152 * Fires inside backpat_rss_attributes method for backwards compatibility related to RSS attributes. 2153 * 2154 * This hook was originally separated out for individual components but has since been abstracted into the BP_Activity_Feed class. 2155 * 2156 * @since BuddyPress (1.0.0) 2157 */ 2158 do_action( 'bp_activity_' . $this->id . '_feed' ); 2159 } 2160 2161 /** 2162 * Fire a hook to ensure backward compatibility for channel elements. 2163 */ 2164 public function backpat_channel_elements() { 2165 2166 /** 2167 * Fires inside backpat_channel_elements method for backwards compatibility related to RSS channel elements. 2168 * 2169 * This hook was originally separated out for individual components but has since been abstracted into the BP_Activity_Feed class. 2170 * 2171 * @since BuddyPress (1.0.0) 2172 */ 2173 do_action( 'bp_activity_' . $this->id . '_feed_head' ); 2174 } 2175 2176 /** 2177 * Fire a hook to ensure backward compatibility for item elements. 2178 */ 2179 public function backpat_item_elements() { 2180 switch ( $this->id ) { 2181 2182 // sitewide and friends feeds use the 'personal' hook 2183 case 'sitewide' : 2184 case 'friends' : 2185 $id = 'personal'; 2186 2187 break; 2188 2189 default : 2190 $id = $this->id; 2191 2192 break; 2193 } 2194 2195 /** 2196 * Fires inside backpat_item_elements method for backwards compatibility related to RSS item elements. 2197 * 2198 * This hook was originally separated out for individual components but has since been abstracted into the BP_Activity_Feed class. 2199 * 2200 * @since BuddyPress (1.0.0) 2201 */ 2202 do_action( 'bp_activity_' . $id . '_feed_item' ); 2203 } 2204 2205 /** HELPERS **************************************************************/ 2206 2207 /** 2208 * Output the feed's item content. 2209 * 2210 * @access protected 2211 */ 2212 protected function feed_content() { 2213 bp_activity_content_body(); 2214 2215 switch ( $this->id ) { 2216 2217 // also output parent activity item if we're on a specific feed 2218 case 'favorites' : 2219 case 'friends' : 2220 case 'mentions' : 2221 case 'personal' : 2222 2223 if ( 'activity_comment' == bp_get_activity_action_name() ) : 2224 ?> 2225 <strong><?php _e( 'In reply to', 'buddypress' ) ?></strong> - 2226 <?php bp_activity_parent_content() ?> 2227 <?php 2228 endif; 2229 2230 break; 2231 } 2232 } 2233 2234 /** 2235 * Sets various HTTP headers related to Content-Type and browser caching. 2236 * 2237 * Most of this class method is derived from {@link WP::send_headers()}. 2238 * 2239 * @since BuddyPress (1.9.0) 2240 * 2241 * @access protected 2242 */ 2243 protected function http_headers() { 2244 // set up some additional headers if not on a directory page 2245 // this is done b/c BP uses pseudo-pages 2246 if ( ! bp_is_directory() ) { 2247 global $wp_query; 2248 2249 $wp_query->is_404 = false; 2250 status_header( 200 ); 2251 } 2252 2253 // Set content-type 2254 @header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true ); 2255 send_nosniff_header(); 2256 2257 // Cache-related variables 2258 $last_modified = mysql2date( 'D, d M Y H:i:s O', bp_activity_get_last_updated(), false ); 2259 $modified_timestamp = strtotime( $last_modified ); 2260 $etag = md5( $last_modified ); 2261 2262 // Set cache-related headers 2263 @header( 'Last-Modified: ' . $last_modified ); 2264 @header( 'Pragma: no-cache' ); 2265 @header( 'ETag: ' . '"' . $etag . '"' ); 2266 2267 // First commit of BuddyPress! (Easter egg) 2268 @header( 'Expires: Tue, 25 Mar 2008 17:13:55 GMT'); 2269 2270 // Get ETag from supported user agents 2271 if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ) { 2272 $client_etag = wp_unslash( $_SERVER['HTTP_IF_NONE_MATCH'] ); 2273 2274 // Remove quotes from ETag 2275 $client_etag = trim( $client_etag, '"' ); 2276 2277 // Strip suffixes from ETag if they exist (eg. "-gzip") 2278 $etag_suffix_pos = strpos( $client_etag, '-' ); 2279 if ( ! empty( $etag_suffix_pos ) ) { 2280 $client_etag = substr( $client_etag, 0, $etag_suffix_pos ); 2281 } 2282 2283 // No ETag found 2284 } else { 2285 $client_etag = false; 2286 } 2287 2288 // Get client last modified timestamp from supported user agents 2289 $client_last_modified = empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ? '' : trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ); 2290 $client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0; 2291 2292 // Set 304 status if feed hasn't been updated since last fetch 2293 if ( ( $client_last_modified && $client_etag ) ? 2294 ( ( $client_modified_timestamp >= $modified_timestamp ) && ( $client_etag == $etag ) ) : 2295 ( ( $client_modified_timestamp >= $modified_timestamp ) || ( $client_etag == $etag ) ) ) { 2296 $status = 304; 2297 } else { 2298 $status = false; 2299 } 2300 2301 // If feed hasn't changed as reported by the user agent, set 304 status header 2302 if ( ! empty( $status ) ) { 2303 status_header( $status ); 2304 2305 // cached response, so stop now! 2306 if ( $status == 304 ) { 2307 exit(); 2308 } 2309 } 2310 } 2311 2312 /** OUTPUT ***************************************************************/ 2313 2314 /** 2315 * Output the RSS feed. 2316 * 2317 * @access protected 2318 */ 2319 protected function output() { 2320 $this->http_headers(); 2321 echo '<?xml version="1.0" encoding="' . get_option( 'blog_charset' ) . '"?'.'>'; 2322 ?> 2323 2324 <rss version="2.0" 2325 xmlns:content="http://purl.org/rss/1.0/modules/content/" 2326 xmlns:atom="http://www.w3.org/2005/Atom" 2327 xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" 2328 xmlns:slash="http://purl.org/rss/1.0/modules/slash/" 2329 <?php 2330 2331 /** 2332 * Fires at the end of the opening RSS tag for feed output so plugins can add extra attributes. 2333 * 2334 * @since BuddyPress (1.8.0) 2335 */ 2336 do_action( 'bp_activity_feed_rss_attributes' ); ?> 2337 > 2338 2339 <channel> 2340 <title><?php echo $this->title; ?></title> 2341 <link><?php echo $this->link; ?></link> 2342 <atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" /> 2343 <description><?php echo $this->description ?></description> 2344 <lastBuildDate><?php echo mysql2date( 'D, d M Y H:i:s O', bp_activity_get_last_updated(), false ); ?></lastBuildDate> 2345 <generator>http://buddypress.org/?v=<?php bp_version(); ?></generator> 2346 <language><?php bloginfo_rss( 'language' ); ?></language> 2347 <ttl><?php echo $this->ttl; ?></ttl> 2348 <sy:updatePeriod><?php echo $this->update_period; ?></sy:updatePeriod> 2349 <sy:updateFrequency><?php echo $this->update_frequency; ?></sy:updateFrequency> 2350 <?php 2351 2352 /** 2353 * Fires at the end of channel elements list in RSS feed so plugins can add extra channel elements. 2354 * 2355 * @since BuddyPress (1.8.0) 2356 */ 2357 do_action( 'bp_activity_feed_channel_elements' ); ?> 2358 2359 <?php if ( bp_has_activities( $this->activity_args ) ) : ?> 2360 <?php while ( bp_activities() ) : bp_the_activity(); ?> 2361 <item> 2362 <guid isPermaLink="false"><?php bp_activity_feed_item_guid(); ?></guid> 2363 <title><?php echo stripslashes( bp_get_activity_feed_item_title() ); ?></title> 2364 <link><?php bp_activity_thread_permalink() ?></link> 2365 <pubDate><?php echo mysql2date( 'D, d M Y H:i:s O', bp_get_activity_feed_item_date(), false ); ?></pubDate> 2366 2367 <?php if ( bp_get_activity_feed_item_description() ) : ?> 2368 <content:encoded><![CDATA[<?php $this->feed_content(); ?>]]></content:encoded> 2369 <?php endif; ?> 2370 2371 <?php if ( bp_activity_can_comment() ) : ?> 2372 <slash:comments><?php bp_activity_comment_count(); ?></slash:comments> 2373 <?php endif; ?> 2374 2375 <?php 2376 2377 /** 2378 * Fires at the end of the individual RSS Item list in RSS feed so plugins can add extra item elements. 2379 * 2380 * @since BuddyPress (1.8.0) 2381 */ 2382 do_action( 'bp_activity_feed_item_elements' ); ?> 2383 </item> 2384 <?php endwhile; ?> 2385 2386 <?php endif; ?> 2387 </channel> 2388 </rss><?php 2389 } 2390 } 12 require __DIR__ . '/classes/class-bp-activity-activity.php'; 13 require __DIR__ . '/classes/class-bp-activity-feed.php'; 14 require __DIR__ . '/classes/class-bp-activity-query.php'; -
trunk/src/bp-blogs/bp-blogs-classes.php
r9471 r9485 11 11 defined( 'ABSPATH' ) || exit; 12 12 13 /** 14 * The main BuddyPress blog class. 15 * 16 * A BP_Blogs_Object represents a link between a specific WordPress blog on a 17 * network and a specific user on that blog. 18 * 19 * @since BuddyPress (1.0.0) 20 */ 21 class BP_Blogs_Blog { 22 public $id; 23 public $user_id; 24 public $blog_id; 25 26 /** 27 * Constructor method. 28 * 29 * @param int $id Optional. The ID of the blog. 30 */ 31 public function __construct( $id = null ) { 32 if ( !empty( $id ) ) { 33 $this->id = $id; 34 $this->populate(); 35 } 36 } 37 38 /** 39 * Populate the object with data about the specific activity item. 40 */ 41 public function populate() { 42 global $wpdb; 43 44 $bp = buddypress(); 45 46 $blog = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->blogs->table_name} WHERE id = %d", $this->id ) ); 47 48 $this->user_id = $blog->user_id; 49 $this->blog_id = $blog->blog_id; 50 } 51 52 /** 53 * Save the BP blog data to the database. 54 * 55 * @return bool True on success, false on failure. 56 */ 57 public function save() { 58 global $wpdb; 59 60 $this->user_id = apply_filters( 'bp_blogs_blog_user_id_before_save', $this->user_id, $this->id ); 61 $this->blog_id = apply_filters( 'bp_blogs_blog_id_before_save', $this->blog_id, $this->id ); 62 63 /** 64 * Fires before the current blog item gets saved. 65 * 66 * Please use this hook to filter the properties above. Each part will be passed in. 67 * 68 * @since BuddyPress (1.0.0) 69 * 70 * @param BP_Blogs_Blog Current instance of the blog item being saved. Passed by reference. 71 */ 72 do_action_ref_array( 'bp_blogs_blog_before_save', array( &$this ) ); 73 74 // Don't try and save if there is no user ID or blog ID set. 75 if ( !$this->user_id || !$this->blog_id ) 76 return false; 77 78 // Don't save if this blog has already been recorded for the user. 79 if ( !$this->id && $this->exists() ) 80 return false; 81 82 $bp = buddypress(); 83 84 if ( $this->id ) { 85 // Update 86 $sql = $wpdb->prepare( "UPDATE {$bp->blogs->table_name} SET user_id = %d, blog_id = %d WHERE id = %d", $this->user_id, $this->blog_id, $this->id ); 87 } else { 88 // Save 89 $sql = $wpdb->prepare( "INSERT INTO {$bp->blogs->table_name} ( user_id, blog_id ) VALUES ( %d, %d )", $this->user_id, $this->blog_id ); 90 } 91 92 if ( !$wpdb->query($sql) ) 93 return false; 94 95 /** 96 * Fires after the current blog item gets saved. 97 * 98 * Please use this hook to filter the properties above. Each part will be passed in. 99 * 100 * @since BuddyPress (1.0.0) 101 * 102 * @param BP_Blogs_Blog Current instance of the blog item being saved. Passed by reference. 103 */ 104 do_action_ref_array( 'bp_blogs_blog_after_save', array( &$this ) ); 105 106 if ( $this->id ) 107 return $this->id; 108 else 109 return $wpdb->insert_id; 110 } 111 112 /** 113 * Check whether an association between this user and this blog exists. 114 * 115 * @return int The number of associations between the user and blog 116 * saved in the blog component tables. 117 */ 118 public function exists() { 119 global $wpdb; 120 121 $bp = buddypress(); 122 123 return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$bp->blogs->table_name} WHERE user_id = %d AND blog_id = %d", $this->user_id, $this->blog_id ) ); 124 } 125 126 /** Static Methods ***************************************************/ 127 128 /** 129 * Retrieve a set of blog-user associations. 130 * 131 * @param string $type The order in which results should be returned. 132 * 'active', 'alphabetical', 'newest', or 'random'. 133 * @param int|bool $limit Optional. The maximum records to return. 134 * Default: false. 135 * @param int|bool $page Optional. The page of records to return. 136 * Default: false (unlimited results). 137 * @param int $user_id Optional. ID of the user whose blogs are being 138 * retrieved. Default: 0. 139 * @param string|bool $search_terms Optional. Search by text stored in 140 * blogmeta (such as the blog name). Default: false. 141 * @param bool $update_meta_cache Whether to pre-fetch metadata for 142 * blogs. Default: true. 143 * @param array $include_blog_ids Array of blog IDs to include. 144 * @return array Multidimensional results array, structured as follows: 145 * 'blogs' - Array of located blog objects 146 * 'total' - A count of the total blogs matching the filter params 147 */ 148 public static function get( $type, $limit = false, $page = false, $user_id = 0, $search_terms = false, $update_meta_cache = true, $include_blog_ids = false ) { 149 global $wpdb; 150 151 $bp = buddypress(); 152 153 if ( !is_user_logged_in() || ( !bp_current_user_can( 'bp_moderate' ) && ( $user_id != bp_loggedin_user_id() ) ) ) 154 $hidden_sql = "AND wb.public = 1"; 155 else 156 $hidden_sql = ''; 157 158 $pag_sql = ( $limit && $page ) ? $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) ) : ''; 159 160 $user_sql = !empty( $user_id ) ? $wpdb->prepare( " AND b.user_id = %d", $user_id ) : ''; 161 162 switch ( $type ) { 163 case 'active': default: 164 $order_sql = "ORDER BY bm.meta_value DESC"; 165 break; 166 case 'alphabetical': 167 $order_sql = "ORDER BY bm_name.meta_value ASC"; 168 break; 169 case 'newest': 170 $order_sql = "ORDER BY wb.registered DESC"; 171 break; 172 case 'random': 173 $order_sql = "ORDER BY RAND()"; 174 break; 175 } 176 177 $include_sql = ''; 178 $include_blog_ids = array_filter( wp_parse_id_list( $include_blog_ids ) ); 179 if ( ! empty( $include_blog_ids ) ) { 180 $blog_ids_sql = implode( ',', $include_blog_ids ); 181 $include_sql = " AND b.blog_id IN ({$blog_ids_sql})"; 182 } 183 184 if ( ! empty( $search_terms ) ) { 185 $search_terms_like = '%' . bp_esc_like( $search_terms ) . '%'; 186 $search_terms_sql = $wpdb->prepare( 'AND (bm_name.meta_value LIKE %s OR bm_description.meta_value LIKE %s)', $search_terms_like, $search_terms_like ); 187 } else { 188 $search_terms_sql = ''; 189 } 190 191 $paged_blogs = $wpdb->get_results( " 192 SELECT b.blog_id, b.user_id as admin_user_id, u.user_email as admin_user_email, wb.domain, wb.path, bm.meta_value as last_activity, bm_name.meta_value as name 193 FROM 194 {$bp->blogs->table_name} b 195 LEFT JOIN {$bp->blogs->table_name_blogmeta} bm ON (b.blog_id = bm.blog_id) 196 LEFT JOIN {$bp->blogs->table_name_blogmeta} bm_name ON (b.blog_id = bm_name.blog_id) 197 LEFT JOIN {$bp->blogs->table_name_blogmeta} bm_description ON (b.blog_id = bm_description.blog_id) 198 LEFT JOIN {$wpdb->base_prefix}blogs wb ON (b.blog_id = wb.blog_id) 199 LEFT JOIN {$wpdb->users} u ON (b.user_id = u.ID) 200 WHERE 201 wb.archived = '0' AND wb.spam = 0 AND wb.mature = 0 AND wb.deleted = 0 {$hidden_sql} 202 AND bm.meta_key = 'last_activity' AND bm_name.meta_key = 'name' AND bm_description.meta_key = 'description' 203 {$search_terms_sql} {$user_sql} {$include_sql} 204 GROUP BY b.blog_id {$order_sql} {$pag_sql} 205 " ); 206 207 $total_blogs = $wpdb->get_var( " 208 SELECT COUNT(DISTINCT b.blog_id) 209 FROM 210 {$bp->blogs->table_name} b 211 LEFT JOIN {$wpdb->base_prefix}blogs wb ON (b.blog_id = wb.blog_id) 212 LEFT JOIN {$bp->blogs->table_name_blogmeta} bm_name ON (b.blog_id = bm_name.blog_id) 213 LEFT JOIN {$bp->blogs->table_name_blogmeta} bm_description ON (b.blog_id = bm_description.blog_id) 214 WHERE 215 wb.archived = '0' AND wb.spam = 0 AND wb.mature = 0 AND wb.deleted = 0 {$hidden_sql} 216 AND 217 bm_name.meta_key = 'name' AND bm_description.meta_key = 'description' 218 {$search_terms_sql} {$user_sql} {$include_sql} 219 " ); 220 221 $blog_ids = array(); 222 foreach ( (array) $paged_blogs as $blog ) { 223 $blog_ids[] = (int) $blog->blog_id; 224 } 225 226 $paged_blogs = BP_Blogs_Blog::get_blog_extras( $paged_blogs, $blog_ids, $type ); 227 228 if ( $update_meta_cache ) { 229 bp_blogs_update_meta_cache( $blog_ids ); 230 } 231 232 return array( 'blogs' => $paged_blogs, 'total' => $total_blogs ); 233 } 234 235 /** 236 * Delete the record of a given blog for all users. 237 * 238 * @param int $blog_id The blog being removed from all users. 239 * @return int|bool Number of rows deleted on success, false on failure. 240 */ 241 public static function delete_blog_for_all( $blog_id ) { 242 global $wpdb; 243 244 bp_blogs_delete_blogmeta( $blog_id ); 245 246 $bp = buddypress(); 247 248 return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->blogs->table_name} WHERE blog_id = %d", $blog_id ) ); 249 } 250 251 /** 252 * Delete the record of a given blog for a specific user. 253 * 254 * @param int $blog_id The blog being removed. 255 * @param int $user_id Optional. The ID of the user from whom the blog 256 * is being removed. If absent, defaults to the logged-in user ID. 257 * @return int|bool Number of rows deleted on success, false on failure. 258 */ 259 public static function delete_blog_for_user( $blog_id, $user_id = null ) { 260 global $wpdb; 261 262 if ( !$user_id ) 263 $user_id = bp_loggedin_user_id(); 264 265 $bp = buddypress(); 266 267 return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->blogs->table_name} WHERE user_id = %d AND blog_id = %d", $user_id, $blog_id ) ); 268 } 269 270 /** 271 * Delete all of a user's blog associations in the BP tables. 272 * 273 * @param int $user_id Optional. The ID of the user whose blog 274 * associations are being deleted. If absent, defaults to 275 * logged-in user ID. 276 * @return int|bool Number of rows deleted on success, false on failure. 277 */ 278 public static function delete_blogs_for_user( $user_id = null ) { 279 global $wpdb; 280 281 if ( !$user_id ) 282 $user_id = bp_loggedin_user_id(); 283 284 $bp = buddypress(); 285 286 return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->blogs->table_name} WHERE user_id = %d", $user_id ) ); 287 } 288 289 /** 290 * Get all of a user's blogs, as tracked by BuddyPress. 291 * 292 * Note that this is different from the WordPress function 293 * {@link get_blogs_of_user()}; the current method returns only those 294 * blogs that have been recorded by BuddyPress, while the WP function 295 * does a true query of a user's blog capabilities. 296 * 297 * @param int $user_id Optional. ID of the user whose blogs are being 298 * queried. Defaults to logged-in user. 299 * @param bool $show_hidden Optional. Whether to include blogs that are 300 * not marked public. Defaults to true when viewing one's own 301 * profile. 302 * @return array Multidimensional results array, structured as follows: 303 * 'blogs' - Array of located blog objects 304 * 'total' - A count of the total blogs for the user. 305 */ 306 public static function get_blogs_for_user( $user_id = 0, $show_hidden = false ) { 307 global $wpdb; 308 309 $bp = buddypress(); 310 311 if ( !$user_id ) 312 $user_id = bp_displayed_user_id(); 313 314 // Show logged in users their hidden blogs. 315 if ( !bp_is_my_profile() && !$show_hidden ) 316 $blogs = $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT b.blog_id, b.id, bm1.meta_value as name, wb.domain, wb.path FROM {$bp->blogs->table_name} b, {$wpdb->base_prefix}blogs wb, {$bp->blogs->table_name_blogmeta} bm1 WHERE b.blog_id = wb.blog_id AND b.blog_id = bm1.blog_id AND bm1.meta_key = 'name' AND wb.public = 1 AND wb.deleted = 0 AND wb.spam = 0 AND wb.mature = 0 AND wb.archived = '0' AND b.user_id = %d ORDER BY b.blog_id", $user_id ) ); 317 else 318 $blogs = $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT b.blog_id, b.id, bm1.meta_value as name, wb.domain, wb.path FROM {$bp->blogs->table_name} b, {$wpdb->base_prefix}blogs wb, {$bp->blogs->table_name_blogmeta} bm1 WHERE b.blog_id = wb.blog_id AND b.blog_id = bm1.blog_id AND bm1.meta_key = 'name' AND wb.deleted = 0 AND wb.spam = 0 AND wb.mature = 0 AND wb.archived = '0' AND b.user_id = %d ORDER BY b.blog_id", $user_id ) ); 319 320 $total_blog_count = BP_Blogs_Blog::total_blog_count_for_user( $user_id ); 321 322 $user_blogs = array(); 323 foreach ( (array) $blogs as $blog ) { 324 $user_blogs[$blog->blog_id] = new stdClass; 325 $user_blogs[$blog->blog_id]->id = $blog->id; 326 $user_blogs[$blog->blog_id]->blog_id = $blog->blog_id; 327 $user_blogs[$blog->blog_id]->siteurl = ( is_ssl() ) ? 'https://' . $blog->domain . $blog->path : 'http://' . $blog->domain . $blog->path; 328 $user_blogs[$blog->blog_id]->name = $blog->name; 329 } 330 331 return array( 'blogs' => $user_blogs, 'count' => $total_blog_count ); 332 } 333 334 /** 335 * Get IDs of all of a user's blogs, as tracked by BuddyPress. 336 * 337 * This method always includes hidden blogs. 338 * 339 * @param int $user_id Optional. ID of the user whose blogs are being 340 * queried. Defaults to logged-in user. 341 * @return int The number of blogs associated with the user. 342 */ 343 public static function get_blog_ids_for_user( $user_id = 0 ) { 344 global $wpdb; 345 346 $bp = buddypress(); 347 348 if ( !$user_id ) 349 $user_id = bp_displayed_user_id(); 350 351 return $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM {$bp->blogs->table_name} WHERE user_id = %d", $user_id ) ); 352 } 353 354 /** 355 * Check whether a blog has been recorded by BuddyPress. 356 * 357 * @param int $blog_id ID of the blog being queried. 358 * @return int|null The ID of the first located entry in the BP table 359 * on success, otherwise null. 360 */ 361 public static function is_recorded( $blog_id ) { 362 global $wpdb; 363 364 $bp = buddypress(); 365 366 return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->blogs->table_name} WHERE blog_id = %d", $blog_id ) ); 367 } 368 369 /** 370 * Return a count of associated blogs for a given user. 371 * 372 * Includes hidden blogs when the logged-in user is the same as the 373 * $user_id parameter, or when the logged-in user has the bp_moderate 374 * cap. 375 * 376 * @param int $user_id Optional. ID of the user whose blogs are being 377 * queried. Defaults to logged-in user. 378 * @return int Blog count for the user. 379 */ 380 public static function total_blog_count_for_user( $user_id = null ) { 381 global $wpdb; 382 383 $bp = buddypress(); 384 385 if ( !$user_id ) 386 $user_id = bp_displayed_user_id(); 387 388 // If the user is logged in return the blog count including their hidden blogs. 389 if ( ( is_user_logged_in() && $user_id == bp_loggedin_user_id() ) || bp_current_user_can( 'bp_moderate' ) ) { 390 return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT b.blog_id) FROM {$bp->blogs->table_name} b LEFT JOIN {$wpdb->base_prefix}blogs wb ON b.blog_id = wb.blog_id WHERE wb.deleted = 0 AND wb.spam = 0 AND wb.mature = 0 AND wb.archived = '0' AND user_id = %d", $user_id ) ); 391 } else { 392 return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT b.blog_id) FROM {$bp->blogs->table_name} b LEFT JOIN {$wpdb->base_prefix}blogs wb ON b.blog_id = wb.blog_id WHERE wb.public = 1 AND wb.deleted = 0 AND wb.spam = 0 AND wb.mature = 0 AND wb.archived = '0' AND user_id = %d", $user_id ) ); 393 } 394 } 395 396 /** 397 * Return a list of blogs matching a search term. 398 * 399 * Matches against blog names and descriptions, as stored in the BP 400 * blogmeta table. 401 * 402 * @param string $filter The search term. 403 * @param int $limit Optional. The maximum number of items to return. 404 * Default: null (no limit). 405 * @param int $page Optional. The page of results to return. Default: 406 * null (no limit). 407 * @return array Multidimensional results array, structured as follows: 408 * 'blogs' - Array of located blog objects 409 * 'total' - A count of the total blogs matching the query. 410 */ 411 public static function search_blogs( $filter, $limit = null, $page = null ) { 412 global $wpdb; 413 414 $search_terms_like = '%' . bp_esc_like( $filter ) . '%'; 415 $search_terms_sql = $wpdb->prepare( 'bm.meta_value LIKE %s', $search_terms_like ); 416 417 $hidden_sql = ''; 418 if ( !bp_current_user_can( 'bp_moderate' ) ) 419 $hidden_sql = "AND wb.public = 1"; 420 421 $pag_sql = ''; 422 if ( $limit && $page ) { 423 $pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) ); 424 } 425 426 $bp = buddypress(); 427 428 $paged_blogs = $wpdb->get_results( "SELECT DISTINCT bm.blog_id FROM {$bp->blogs->table_name_blogmeta} bm LEFT JOIN {$wpdb->base_prefix}blogs wb ON bm.blog_id = wb.blog_id WHERE ( ( bm.meta_key = 'name' OR bm.meta_key = 'description' ) AND {$search_terms_sql} ) {$hidden_sql} AND wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 ORDER BY meta_value ASC{$pag_sql}" ); 429 $total_blogs = $wpdb->get_var( "SELECT COUNT(DISTINCT bm.blog_id) FROM {$bp->blogs->table_name_blogmeta} bm LEFT JOIN {$wpdb->base_prefix}blogs wb ON bm.blog_id = wb.blog_id WHERE ( ( bm.meta_key = 'name' OR bm.meta_key = 'description' ) AND {$search_terms_sql} ) {$hidden_sql} AND wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 ORDER BY meta_value ASC" ); 430 431 return array( 'blogs' => $paged_blogs, 'total' => $total_blogs ); 432 } 433 434 /** 435 * Retrieve a list of all blogs. 436 * 437 * Query will include hidden blogs if the logged-in user has the 438 * 'bp_moderate' cap. 439 * 440 * @param int $limit Optional. The maximum number of items to return. 441 * Default: null (no limit). 442 * @param int $page Optional. The page of results to return. Default: 443 * null (no limit). 444 * @return array Multidimensional results array, structured as follows: 445 * 'blogs' - Array of located blog objects 446 * 'total' - A count of the total blogs. 447 */ 448 public static function get_all( $limit = null, $page = null ) { 449 global $wpdb; 450 451 $bp = buddypress(); 452 453 $hidden_sql = !bp_current_user_can( 'bp_moderate' ) ? "AND wb.public = 1" : ''; 454 $pag_sql = ( $limit && $page ) ? $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) ) : ''; 455 456 $paged_blogs = $wpdb->get_results( "SELECT DISTINCT b.blog_id FROM {$bp->blogs->table_name} b LEFT JOIN {$wpdb->base_prefix}blogs wb ON b.blog_id = wb.blog_id WHERE wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 {$hidden_sql} {$pag_sql}" ); 457 $total_blogs = $wpdb->get_var( "SELECT COUNT(DISTINCT b.blog_id) FROM {$bp->blogs->table_name} b LEFT JOIN {$wpdb->base_prefix}blogs wb ON b.blog_id = wb.blog_id WHERE wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 {$hidden_sql}" ); 458 459 return array( 'blogs' => $paged_blogs, 'total' => $total_blogs ); 460 } 461 462 /** 463 * Retrieve a list of blogs whose names start with a given letter. 464 * 465 * Query will include hidden blogs if the logged-in user has the 466 * 'bp_moderate' cap. 467 * 468 * @param string $letter. The letter you're looking for. 469 * @param int $limit Optional. The maximum number of items to return. 470 * Default: null (no limit). 471 * @param int $page Optional. The page of results to return. Default: 472 * null (no limit). 473 * @return array Multidimensional results array, structured as follows: 474 * 'blogs' - Array of located blog objects. 475 * 'total' - A count of the total blogs matching the query. 476 */ 477 public static function get_by_letter( $letter, $limit = null, $page = null ) { 478 global $wpdb; 479 480 $bp = buddypress(); 481 482 $letter_like = '%' . bp_esc_like( $letter ) . '%'; 483 $letter_sql = $wpdb->prepare( 'bm.meta_value LIKE %s', $letter_like ); 484 485 $hidden_sql = ''; 486 if ( !bp_current_user_can( 'bp_moderate' ) ) 487 $hidden_sql = "AND wb.public = 1"; 488 489 $pag_sql = ''; 490 if ( $limit && $page ) 491 $pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) ); 492 493 $paged_blogs = $wpdb->get_results( "SELECT DISTINCT bm.blog_id FROM {$bp->blogs->table_name_blogmeta} bm LEFT JOIN {$wpdb->base_prefix}blogs wb ON bm.blog_id = wb.blog_id WHERE bm.meta_key = 'name' AND {$letter_sql} {$hidden_sql} AND wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 ORDER BY bm.meta_value ASC{$pag_sql}" ); 494 $total_blogs = $wpdb->get_var( "SELECT COUNT(DISTINCT bm.blog_id) FROM {$bp->blogs->table_name_blogmeta} bm LEFT JOIN {$wpdb->base_prefix}blogs wb ON bm.blog_id = wb.blog_id WHERE bm.meta_key = 'name' AND {$letter_sql} {$hidden_sql} AND wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 ORDER BY bm.meta_value ASC" ); 495 496 return array( 'blogs' => $paged_blogs, 'total' => $total_blogs ); 497 } 498 499 /** 500 * Fetch blog data not caught in the main query and append it to results array. 501 * 502 * Gets the following information, which is either unavailable at the 503 * time of the original query, or is more efficient to look up in one 504 * fell swoop: 505 * - The latest post for each blog, include Featured Image data 506 * - The blog description 507 * 508 * @param array $paged_blogs Array of results from the original query. 509 * @param array $blog_ids Array of IDs returned from the original query. 510 * @param string|bool $type Not currently used. Default: false. 511 * @return array $paged_blogs The located blogs array, with the extras added. 512 */ 513 public static function get_blog_extras( &$paged_blogs, &$blog_ids, $type = false ) { 514 global $wpdb; 515 516 $bp = buddypress(); 517 518 if ( empty( $blog_ids ) ) 519 return $paged_blogs; 520 521 $blog_ids = implode( ',', wp_parse_id_list( $blog_ids ) ); 522 523 for ( $i = 0, $count = count( $paged_blogs ); $i < $count; ++$i ) { 524 $blog_prefix = $wpdb->get_blog_prefix( $paged_blogs[$i]->blog_id ); 525 $paged_blogs[$i]->latest_post = $wpdb->get_row( "SELECT ID, post_content, post_title, post_excerpt, guid FROM {$blog_prefix}posts WHERE post_status = 'publish' AND post_type = 'post' AND id != 1 ORDER BY id DESC LIMIT 1" ); 526 $images = array(); 527 528 // Add URLs to any Featured Image this post might have 529 if ( ! empty( $paged_blogs[$i]->latest_post ) && has_post_thumbnail( $paged_blogs[$i]->latest_post->ID ) ) { 530 531 // Grab 4 sizes of the image. Thumbnail. 532 $image = wp_get_attachment_image_src( get_post_thumbnail_id( $paged_blogs[$i]->latest_post->ID ), 'thumbnail', false ); 533 if ( ! empty( $image ) ) 534 $images['thumbnail'] = $image[0]; 535 536 // Medium 537 $image = wp_get_attachment_image_src( get_post_thumbnail_id( $paged_blogs[$i]->latest_post->ID ), 'medium', false ); 538 if ( ! empty( $image ) ) 539 $images['medium'] = $image[0]; 540 541 // Large 542 $image = wp_get_attachment_image_src( get_post_thumbnail_id( $paged_blogs[$i]->latest_post->ID ), 'large', false ); 543 if ( ! empty( $image ) ) 544 $images['large'] = $image[0]; 545 546 // Post thumbnail 547 $image = wp_get_attachment_image_src( get_post_thumbnail_id( $paged_blogs[$i]->latest_post->ID ), 'post-thumbnail', false ); 548 if ( ! empty( $image ) ) 549 $images['post-thumbnail'] = $image[0]; 550 551 // Add the images to the latest_post object 552 $paged_blogs[$i]->latest_post->images = $images; 553 } 554 } 555 556 /* Fetch the blog description for each blog (as it may be empty we can't fetch it in the main query). */ 557 $blog_descs = $wpdb->get_results( "SELECT blog_id, meta_value as description FROM {$bp->blogs->table_name_blogmeta} WHERE meta_key = 'description' AND blog_id IN ( {$blog_ids} )" ); 558 559 for ( $i = 0, $count = count( $paged_blogs ); $i < $count; ++$i ) { 560 foreach ( (array) $blog_descs as $desc ) { 561 if ( $desc->blog_id == $paged_blogs[$i]->blog_id ) 562 $paged_blogs[$i]->description = $desc->description; 563 } 564 } 565 566 return $paged_blogs; 567 } 568 569 /** 570 * Check whether a given blog is hidden. 571 * 572 * Checks the 'public' column in the wp_blogs table. 573 * 574 * @param int $blog_id The ID of the blog being checked. 575 * @return bool True if hidden (public = 0), false otherwise. 576 */ 577 public static function is_hidden( $blog_id ) { 578 global $wpdb; 579 580 if ( !(int) $wpdb->get_var( $wpdb->prepare( "SELECT public FROM {$wpdb->base_prefix}blogs WHERE blog_id = %d", $blog_id ) ) ) { 581 return true; 582 } 583 584 return false; 585 } 586 587 /** 588 * Get ID of user-blog link. 589 * 590 * @param int $user_id ID of user. 591 * @param int $blog_id ID of blog. 592 * @return int|bool ID of user-blog link, or false if not found. 593 */ 594 public static function get_user_blog( $user_id, $blog_id ) { 595 global $wpdb; 596 597 $bp = buddypress(); 598 599 $user_blog = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->blogs->table_name} WHERE user_id = %d AND blog_id = %d", $user_id, $blog_id ) ); 600 601 if ( empty( $user_blog ) ) { 602 $user_blog = false; 603 } else { 604 $user_blog = intval( $user_blog ); 605 } 606 607 return $user_blog; 608 } 609 } 13 require __DIR__ . '/classes/class-bp-blogs-blog.php'; -
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