[ Index ] |
PHP Cross Reference of BuddyPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * BuddyPress Activity Classes 4 * 5 * @package BuddyPress 6 * @subpackage Activity 7 * @since 1.0.0 8 */ 9 10 // Exit if accessed directly. 11 defined( 'ABSPATH' ) || exit; 12 13 /** 14 * Database interaction class for the BuddyPress activity component. 15 * Instance methods are available for creating/editing an activity, 16 * static methods for querying activities. 17 * 18 * @since 1.0.0 19 */ 20 class BP_Activity_Activity { 21 22 /** Properties ************************************************************/ 23 24 /** 25 * ID of the activity item. 26 * 27 * @since 1.0.0 28 * @var int 29 */ 30 var $id; 31 32 /** 33 * ID of the associated item. 34 * 35 * @since 1.0.0 36 * @var int 37 */ 38 var $item_id; 39 40 /** 41 * ID of the associated secondary item. 42 * 43 * @since 1.0.0 44 * @var int 45 */ 46 var $secondary_item_id; 47 48 /** 49 * ID of user associated with the activity item. 50 * 51 * @since 1.0.0 52 * @var int 53 */ 54 var $user_id; 55 56 /** 57 * The primary URL for the activity in RSS feeds. 58 * 59 * @since 1.0.0 60 * @var string 61 */ 62 var $primary_link; 63 64 /** 65 * BuddyPress component the activity item relates to. 66 * 67 * @since 1.2.0 68 * @var string 69 */ 70 var $component; 71 72 /** 73 * Activity type, eg 'new_blog_post'. 74 * 75 * @since 1.2.0 76 * @var string 77 */ 78 var $type; 79 80 /** 81 * Description of the activity, eg 'Alex updated his profile.'. 82 * 83 * @since 1.2.0 84 * @var string 85 */ 86 var $action; 87 88 /** 89 * The content of the activity item. 90 * 91 * @since 1.2.0 92 * @var string 93 */ 94 var $content; 95 96 /** 97 * The date the activity item was recorded, in 'Y-m-d h:i:s' format. 98 * 99 * @since 1.0.0 100 * @var string 101 */ 102 var $date_recorded; 103 104 /** 105 * Whether the item should be hidden in sitewide streams. 106 * 107 * @since 1.1.0 108 * @var int 109 */ 110 var $hide_sitewide = 0; 111 112 /** 113 * Node boundary start for activity or activity comment. 114 * 115 * @since 1.5.0 116 * @var int 117 */ 118 var $mptt_left; 119 120 /** 121 * Node boundary end for activity or activity comment. 122 * 123 * @since 1.5.0 124 * @var int 125 */ 126 var $mptt_right; 127 128 /** 129 * Whether this item is marked as spam. 130 * 131 * @since 1.6.0 132 * @var int 133 */ 134 var $is_spam; 135 136 /** 137 * Error holder. 138 * 139 * @since 2.6.0 140 * 141 * @var WP_Error 142 */ 143 public $errors; 144 145 /** 146 * Error type to return. Either 'bool' or 'wp_error'. 147 * 148 * @since 2.6.0 149 * 150 * @var string 151 */ 152 public $error_type = 'bool'; 153 154 /** 155 * Constructor method. 156 * 157 * @since 1.5.0 158 * 159 * @param int|bool $id Optional. The ID of a specific activity item. 160 */ 161 public function __construct( $id = false ) { 162 // Instantiate errors object. 163 $this->errors = new WP_Error; 164 165 if ( !empty( $id ) ) { 166 $this->id = (int) $id; 167 $this->populate(); 168 } 169 } 170 171 /** 172 * Populate the object with data about the specific activity item. 173 * 174 * @since 1.0.0 175 */ 176 public function populate() { 177 global $wpdb; 178 179 $row = wp_cache_get( $this->id, 'bp_activity' ); 180 181 if ( false === $row ) { 182 $bp = buddypress(); 183 $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->activity->table_name} WHERE id = %d", $this->id ) ); 184 185 wp_cache_set( $this->id, $row, 'bp_activity' ); 186 } 187 188 if ( empty( $row ) ) { 189 $this->id = 0; 190 return; 191 } 192 193 $this->id = (int) $row->id; 194 $this->item_id = (int) $row->item_id; 195 $this->secondary_item_id = (int) $row->secondary_item_id; 196 $this->user_id = (int) $row->user_id; 197 $this->primary_link = $row->primary_link; 198 $this->component = $row->component; 199 $this->type = $row->type; 200 $this->action = $row->action; 201 $this->content = $row->content; 202 $this->date_recorded = $row->date_recorded; 203 $this->hide_sitewide = (int) $row->hide_sitewide; 204 $this->mptt_left = (int) $row->mptt_left; 205 $this->mptt_right = (int) $row->mptt_right; 206 $this->is_spam = (int) $row->is_spam; 207 208 // Generate dynamic 'action' when possible. 209 $action = bp_activity_generate_action_string( $this ); 210 if ( false !== $action ) { 211 $this->action = $action; 212 213 // If no callback is available, use the literal string from 214 // the database row. 215 } elseif ( ! empty( $row->action ) ) { 216 $this->action = $row->action; 217 218 // Provide a fallback to avoid PHP notices. 219 } else { 220 $this->action = ''; 221 } 222 } 223 224 /** 225 * Save the activity item to the database. 226 * 227 * @since 1.0.0 228 * 229 * @return WP_Error|bool True on success. 230 */ 231 public function save() { 232 global $wpdb; 233 234 $bp = buddypress(); 235 236 $this->id = apply_filters_ref_array( 'bp_activity_id_before_save', array( $this->id, &$this ) ); 237 $this->item_id = apply_filters_ref_array( 'bp_activity_item_id_before_save', array( $this->item_id, &$this ) ); 238 $this->secondary_item_id = apply_filters_ref_array( 'bp_activity_secondary_item_id_before_save', array( $this->secondary_item_id, &$this ) ); 239 $this->user_id = apply_filters_ref_array( 'bp_activity_user_id_before_save', array( $this->user_id, &$this ) ); 240 $this->primary_link = apply_filters_ref_array( 'bp_activity_primary_link_before_save', array( $this->primary_link, &$this ) ); 241 $this->component = apply_filters_ref_array( 'bp_activity_component_before_save', array( $this->component, &$this ) ); 242 $this->type = apply_filters_ref_array( 'bp_activity_type_before_save', array( $this->type, &$this ) ); 243 $this->action = apply_filters_ref_array( 'bp_activity_action_before_save', array( $this->action, &$this ) ); 244 $this->content = apply_filters_ref_array( 'bp_activity_content_before_save', array( $this->content, &$this ) ); 245 $this->date_recorded = apply_filters_ref_array( 'bp_activity_date_recorded_before_save', array( $this->date_recorded, &$this ) ); 246 $this->hide_sitewide = apply_filters_ref_array( 'bp_activity_hide_sitewide_before_save', array( $this->hide_sitewide, &$this ) ); 247 $this->mptt_left = apply_filters_ref_array( 'bp_activity_mptt_left_before_save', array( $this->mptt_left, &$this ) ); 248 $this->mptt_right = apply_filters_ref_array( 'bp_activity_mptt_right_before_save', array( $this->mptt_right, &$this ) ); 249 $this->is_spam = apply_filters_ref_array( 'bp_activity_is_spam_before_save', array( $this->is_spam, &$this ) ); 250 251 /** 252 * Fires before the current activity item gets saved. 253 * 254 * Please use this hook to filter the properties above. Each part will be passed in. 255 * 256 * @since 1.0.0 257 * 258 * @param BP_Activity_Activity $this Current instance of the activity item being saved. Passed by reference. 259 */ 260 do_action_ref_array( 'bp_activity_before_save', array( &$this ) ); 261 262 if ( 'wp_error' === $this->error_type && $this->errors->get_error_code() ) { 263 return $this->errors; 264 } 265 266 if ( empty( $this->component ) || empty( $this->type ) ) { 267 if ( 'bool' === $this->error_type ) { 268 return false; 269 } else { 270 if ( empty( $this->component ) ) { 271 $this->errors->add( 'bp_activity_missing_component', __( 'You need to define a component parameter to insert activity.', 'buddypress' ) ); 272 } else { 273 $this->errors->add( 'bp_activity_missing_type', __( 'You need to define a type parameter to insert activity.', 'buddypress' ) ); 274 } 275 276 return $this->errors; 277 } 278 } 279 280 /** 281 * Use this filter to make the content of your activity required. 282 * 283 * @since 6.0.0 284 * 285 * @param bool $value True if the content of the activity type is required. 286 * False otherwise. 287 * @param string $type The type of the activity we are about to insert. 288 */ 289 $type_requires_content = (bool) apply_filters( 'bp_activity_type_requires_content', $this->type === 'activity_update', $this->type ); 290 if ( $type_requires_content && ! $this->content ) { 291 if ( 'bool' === $this->error_type ) { 292 return false; 293 } else { 294 $this->errors->add( 'bp_activity_missing_content', __( 'Please enter some content to post.', 'buddypress' ) ); 295 296 return $this->errors; 297 } 298 } 299 300 if ( empty( $this->primary_link ) ) { 301 $this->primary_link = bp_loggedin_user_domain(); 302 } 303 304 // If we have an existing ID, update the activity item, otherwise insert it. 305 if ( ! empty( $this->id ) ) { 306 $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 ); 307 } else { 308 $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 ); 309 } 310 311 if ( false === $wpdb->query( $q ) ) { 312 return false; 313 } 314 315 // If this is a new activity item, set the $id property. 316 if ( empty( $this->id ) ) { 317 $this->id = $wpdb->insert_id; 318 319 // If an existing activity item, prevent any changes to the content generating new @mention notifications. 320 } else { 321 add_filter( 'bp_activity_at_name_do_notifications', '__return_false' ); 322 } 323 324 /** 325 * Fires after an activity item has been saved to the database. 326 * 327 * @since 1.0.0 328 * 329 * @param BP_Activity_Activity $this Current instance of activity item being saved. Passed by reference. 330 */ 331 do_action_ref_array( 'bp_activity_after_save', array( &$this ) ); 332 333 return true; 334 } 335 336 /** Static Methods ***************************************************/ 337 338 /** 339 * Get activity items, as specified by parameters. 340 * 341 * @since 1.2.0 342 * @since 2.4.0 Introduced the `$fields` parameter. 343 * @since 2.9.0 Introduced the `$order_by` parameter. 344 * 345 * @see BP_Activity_Activity::get_filter_sql() for a description of the 346 * 'filter' parameter. 347 * @see WP_Meta_Query::queries for a description of the 'meta_query' 348 * parameter format. 349 * 350 * @param array $args { 351 * An array of arguments. All items are optional. 352 * @type int $page Which page of results to fetch. Using page=1 without per_page will result 353 * in no pagination. Default: 1. 354 * @type int|bool $per_page Number of results per page. Default: 25. 355 * @type int|bool $max Maximum number of results to return. Default: false (unlimited). 356 * @type string $fields Activity fields to return. Pass 'ids' to get only the activity IDs. 357 * 'all' returns full activity objects. 358 * @type string $sort ASC or DESC. Default: 'DESC'. 359 * @type string $order_by Column to order results by. 360 * @type array $exclude Array of activity IDs to exclude. Default: false. 361 * @type array $in Array of ids to limit query by (IN). Default: false. 362 * @type array $meta_query Array of meta_query conditions. See WP_Meta_Query::queries. 363 * @type array $date_query Array of date_query conditions. See first parameter of 364 * WP_Date_Query::__construct(). 365 * @type array $filter_query Array of advanced query conditions. See BP_Activity_Query::__construct(). 366 * @type string|array $scope Pre-determined set of activity arguments. 367 * @type array $filter See BP_Activity_Activity::get_filter_sql(). 368 * @type string $search_terms Limit results by a search term. Default: false. 369 * @type bool $display_comments Whether to include activity comments. Default: false. 370 * @type bool $show_hidden Whether to show items marked hide_sitewide. Default: false. 371 * @type string $spam Spam status. Default: 'ham_only'. 372 * @type bool $update_meta_cache Whether to pre-fetch metadata for queried activity items. Default: true. 373 * @type string|bool $count_total If true, an additional DB query is run to count the total activity items 374 * for the query. Default: false. 375 * } 376 * @return array The array returned has two keys: 377 * - 'total' is the count of located activities 378 * - 'activities' is an array of the located activities 379 */ 380 public static function get( $args = array() ) { 381 global $wpdb; 382 383 $function_args = func_get_args(); 384 385 // Backward compatibility with old method of passing arguments. 386 if ( !is_array( $args ) || count( $function_args ) > 1 ) { 387 _deprecated_argument( 388 __METHOD__, 389 '1.6', 390 sprintf( 391 /* translators: 1: the name of the method. 2: the name of the file. */ 392 __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), 393 __METHOD__, 394 __FILE__ 395 ) 396 ); 397 398 $old_args_keys = array( 399 0 => 'max', 400 1 => 'page', 401 2 => 'per_page', 402 3 => 'sort', 403 4 => 'search_terms', 404 5 => 'filter', 405 6 => 'display_comments', 406 7 => 'show_hidden', 407 8 => 'exclude', 408 9 => 'in', 409 10 => 'spam' 410 ); 411 412 $args = bp_core_parse_args_array( $old_args_keys, $function_args ); 413 } 414 415 $bp = buddypress(); 416 $r = wp_parse_args( $args, array( 417 'page' => 1, // The current page. 418 'per_page' => 25, // Activity items per page. 419 'max' => false, // Max number of items to return. 420 'fields' => 'all', // Fields to include. 421 'sort' => 'DESC', // ASC or DESC. 422 'order_by' => 'date_recorded', // Column to order by. 423 'exclude' => false, // Array of ids to exclude. 424 'in' => false, // Array of ids to limit query by (IN). 425 'meta_query' => false, // Filter by activitymeta. 426 'date_query' => false, // Filter by date. 427 'filter_query' => false, // Advanced filtering - see BP_Activity_Query. 428 'filter' => false, // See self::get_filter_sql(). 429 'scope' => false, // Preset activity arguments. 430 'search_terms' => false, // Terms to search by. 431 'display_comments' => false, // Whether to include activity comments. 432 'show_hidden' => false, // Show items marked hide_sitewide. 433 'spam' => 'ham_only', // Spam status. 434 'update_meta_cache' => true, // Whether or not to update meta cache. 435 'count_total' => false, // Whether or not to use count_total. 436 ) ); 437 438 // Select conditions. 439 $select_sql = "SELECT DISTINCT a.id"; 440 441 $from_sql = " FROM {$bp->activity->table_name} a"; 442 443 $join_sql = ''; 444 445 // Where conditions. 446 $where_conditions = array(); 447 448 // Excluded types. 449 $excluded_types = array(); 450 451 // Scope takes precedence. 452 if ( ! empty( $r['scope'] ) ) { 453 $scope_query = self::get_scope_query_sql( $r['scope'], $r ); 454 455 // Add our SQL conditions if matches were found. 456 if ( ! empty( $scope_query['sql'] ) ) { 457 $where_conditions['scope_query_sql'] = $scope_query['sql']; 458 } 459 460 // Override some arguments if needed. 461 if ( ! empty( $scope_query['override'] ) ) { 462 $r = array_replace_recursive( $r, $scope_query['override'] ); 463 } 464 465 // Advanced filtering. 466 } elseif ( ! empty( $r['filter_query'] ) ) { 467 $filter_query = new BP_Activity_Query( $r['filter_query'] ); 468 $sql = $filter_query->get_sql(); 469 if ( ! empty( $sql ) ) { 470 $where_conditions['filter_query_sql'] = $sql; 471 } 472 } 473 474 // Regular filtering. 475 if ( $r['filter'] && $filter_sql = BP_Activity_Activity::get_filter_sql( $r['filter'] ) ) { 476 $where_conditions['filter_sql'] = $filter_sql; 477 } 478 479 // Spam. 480 if ( 'ham_only' == $r['spam'] ) { 481 $where_conditions['spam_sql'] = 'a.is_spam = 0'; 482 } elseif ( 'spam_only' == $r['spam'] ) { 483 $where_conditions['spam_sql'] = 'a.is_spam = 1'; 484 } 485 486 // Searching. 487 if ( $r['search_terms'] ) { 488 $search_terms_like = '%' . bp_esc_like( $r['search_terms'] ) . '%'; 489 $where_conditions['search_sql'] = $wpdb->prepare( 'a.content LIKE %s', $search_terms_like ); 490 491 /** 492 * Filters whether or not to include users for search parameters. 493 * 494 * @since 3.0.0 495 * 496 * @param bool $value Whether or not to include user search. Default false. 497 */ 498 if ( apply_filters( 'bp_activity_get_include_user_search', false ) ) { 499 $user_search = get_user_by( 'slug', $r['search_terms'] ); 500 if ( false !== $user_search ) { 501 $user_id = $user_search->ID; 502 $where_conditions['search_sql'] .= $wpdb->prepare( ' OR a.user_id = %d', $user_id ); 503 } 504 } 505 } 506 507 // Sorting. 508 $sort = $r['sort']; 509 if ( $sort != 'ASC' && $sort != 'DESC' ) { 510 $sort = 'DESC'; 511 } 512 513 switch( $r['order_by'] ) { 514 case 'id' : 515 case 'user_id' : 516 case 'component' : 517 case 'type' : 518 case 'action' : 519 case 'content' : 520 case 'primary_link' : 521 case 'item_id' : 522 case 'secondary_item_id' : 523 case 'date_recorded' : 524 case 'hide_sitewide' : 525 case 'mptt_left' : 526 case 'mptt_right' : 527 case 'is_spam' : 528 break; 529 530 default : 531 $r['order_by'] = 'date_recorded'; 532 break; 533 } 534 $order_by = 'a.' . $r['order_by']; 535 536 // Hide Hidden Items? 537 if ( ! $r['show_hidden'] ) { 538 $where_conditions['hidden_sql'] = "a.hide_sitewide = 0"; 539 } 540 541 // Exclude specified items. 542 if ( ! empty( $r['exclude'] ) ) { 543 $exclude = implode( ',', wp_parse_id_list( $r['exclude'] ) ); 544 $where_conditions['exclude'] = "a.id NOT IN ({$exclude})"; 545 } 546 547 // The specific ids to which you want to limit the query. 548 if ( ! empty( $r['in'] ) ) { 549 $in = implode( ',', wp_parse_id_list( $r['in'] ) ); 550 $where_conditions['in'] = "a.id IN ({$in})"; 551 } 552 553 // Process meta_query into SQL. 554 $meta_query_sql = self::get_meta_query_sql( $r['meta_query'] ); 555 556 if ( ! empty( $meta_query_sql['join'] ) ) { 557 $join_sql .= $meta_query_sql['join']; 558 } 559 560 if ( ! empty( $meta_query_sql['where'] ) ) { 561 $where_conditions[] = $meta_query_sql['where']; 562 } 563 564 // Process date_query into SQL. 565 $date_query_sql = self::get_date_query_sql( $r['date_query'] ); 566 567 if ( ! empty( $date_query_sql ) ) { 568 $where_conditions['date'] = $date_query_sql; 569 } 570 571 // Alter the query based on whether we want to show activity item 572 // comments in the stream like normal comments or threaded below 573 // the activity. 574 if ( false === $r['display_comments'] || 'threaded' === $r['display_comments'] ) { 575 $excluded_types[] = 'activity_comment'; 576 } 577 578 // Exclude 'last_activity' items unless the 'action' filter has 579 // been explicitly set. 580 if ( empty( $r['filter']['object'] ) ) { 581 $excluded_types[] = 'last_activity'; 582 } 583 584 // Build the excluded type sql part. 585 if ( ! empty( $excluded_types ) ) { 586 $not_in = "'" . implode( "', '", esc_sql( $excluded_types ) ) . "'"; 587 $where_conditions['excluded_types'] = "a.type NOT IN ({$not_in})"; 588 } 589 590 /** 591 * Filters the MySQL WHERE conditions for the Activity items get method. 592 * 593 * @since 1.9.0 594 * 595 * @param array $where_conditions Current conditions for MySQL WHERE statement. 596 * @param array $r Parsed arguments passed into method. 597 * @param string $select_sql Current SELECT MySQL statement at point of execution. 598 * @param string $from_sql Current FROM MySQL statement at point of execution. 599 * @param string $join_sql Current INNER JOIN MySQL statement at point of execution. 600 */ 601 $where_conditions = apply_filters( 'bp_activity_get_where_conditions', $where_conditions, $r, $select_sql, $from_sql, $join_sql ); 602 603 // Join the where conditions together. 604 $where_sql = 'WHERE ' . join( ' AND ', $where_conditions ); 605 606 /** 607 * Filter the MySQL JOIN clause for the main activity query. 608 * 609 * @since 2.5.0 610 * 611 * @param string $join_sql JOIN clause. 612 * @param array $r Method parameters. 613 * @param string $select_sql Current SELECT MySQL statement. 614 * @param string $from_sql Current FROM MySQL statement. 615 * @param string $where_sql Current WHERE MySQL statement. 616 */ 617 $join_sql = apply_filters( 'bp_activity_get_join_sql', $join_sql, $r, $select_sql, $from_sql, $where_sql ); 618 619 // Sanitize page and per_page parameters. 620 $page = absint( $r['page'] ); 621 $per_page = absint( $r['per_page'] ); 622 623 $retval = array( 624 'activities' => null, 625 'total' => null, 626 'has_more_items' => null, 627 ); 628 629 /** 630 * Filters if BuddyPress should use legacy query structure over current structure for version 2.0+. 631 * 632 * It is not recommended to use the legacy structure, but allowed to if needed. 633 * 634 * @since 2.0.0 635 * 636 * @param bool $value Whether to use legacy structure or not. 637 * @param BP_Activity_Activity $value Current method being called. 638 * @param array $r Parsed arguments passed into method. 639 */ 640 if ( apply_filters( 'bp_use_legacy_activity_query', false, __METHOD__, $r ) ) { 641 642 // Legacy queries joined against the user table. 643 $select_sql = "SELECT DISTINCT a.*, u.user_email, u.user_nicename, u.user_login, u.display_name"; 644 $from_sql = " FROM {$bp->activity->table_name} a LEFT JOIN {$wpdb->users} u ON a.user_id = u.ID"; 645 646 if ( ! empty( $page ) && ! empty( $per_page ) ) { 647 $pag_sql = $wpdb->prepare( "LIMIT %d, %d", absint( ( $page - 1 ) * $per_page ), $per_page ); 648 649 /** This filter is documented in bp-activity/bp-activity-classes.php */ 650 $activity_sql = apply_filters( 'bp_activity_get_user_join_filter', "{$select_sql} {$from_sql} {$join_sql} {$where_sql} ORDER BY a.date_recorded {$sort}, a.id {$sort} {$pag_sql}", $select_sql, $from_sql, $where_sql, $sort, $pag_sql ); 651 } else { 652 $pag_sql = ''; 653 654 /** 655 * Filters the legacy MySQL query statement so plugins can alter before results are fetched. 656 * 657 * @since 1.5.0 658 * 659 * @param string $value Concatenated MySQL statement pieces to be query results with for legacy query. 660 * @param string $select_sql Final SELECT MySQL statement portion for legacy query. 661 * @param string $from_sql Final FROM MySQL statement portion for legacy query. 662 * @param string $where_sql Final WHERE MySQL statement portion for legacy query. 663 * @param string $sort Final sort direction for legacy query. 664 */ 665 $activity_sql = apply_filters( 'bp_activity_get_user_join_filter', "{$select_sql} {$from_sql} {$join_sql} {$where_sql} ORDER BY a.date_recorded {$sort}, a.id {$sort}", $select_sql, $from_sql, $where_sql, $sort, $pag_sql ); 666 } 667 668 /* 669 * Queries that include 'last_activity' are cached separately, 670 * since they are generally much less long-lived. 671 */ 672 if ( preg_match( '/a\.type NOT IN \([^\)]*\'last_activity\'[^\)]*\)/', $activity_sql ) ) { 673 $cache_group = 'bp_activity'; 674 } else { 675 $cache_group = 'bp_activity_with_last_activity'; 676 } 677 678 $activities = $wpdb->get_results( $activity_sql ); 679 680 // Integer casting for legacy activity query. 681 foreach ( (array) $activities as $i => $ac ) { 682 $activities[ $i ]->id = (int) $ac->id; 683 $activities[ $i ]->item_id = (int) $ac->item_id; 684 $activities[ $i ]->secondary_item_id = (int) $ac->secondary_item_id; 685 $activities[ $i ]->user_id = (int) $ac->user_id; 686 $activities[ $i ]->hide_sitewide = (int) $ac->hide_sitewide; 687 $activities[ $i ]->mptt_left = (int) $ac->mptt_left; 688 $activities[ $i ]->mptt_right = (int) $ac->mptt_right; 689 $activities[ $i ]->is_spam = (int) $ac->is_spam; 690 } 691 692 } else { 693 // Query first for activity IDs. 694 $activity_ids_sql = "{$select_sql} {$from_sql} {$join_sql} {$where_sql} ORDER BY {$order_by} {$sort}, a.id {$sort}"; 695 696 if ( ! empty( $per_page ) && ! empty( $page ) ) { 697 // We query for $per_page + 1 items in order to 698 // populate the has_more_items flag. 699 $activity_ids_sql .= $wpdb->prepare( " LIMIT %d, %d", absint( ( $page - 1 ) * $per_page ), $per_page + 1 ); 700 } 701 702 /** 703 * Filters the paged activities MySQL statement. 704 * 705 * @since 2.0.0 706 * 707 * @param string $activity_ids_sql MySQL statement used to query for Activity IDs. 708 * @param array $r Array of arguments passed into method. 709 */ 710 $activity_ids_sql = apply_filters( 'bp_activity_paged_activities_sql', $activity_ids_sql, $r ); 711 712 /* 713 * Queries that include 'last_activity' are cached separately, 714 * since they are generally much less long-lived. 715 */ 716 if ( preg_match( '/a\.type NOT IN \([^\)]*\'last_activity\'[^\)]*\)/', $activity_ids_sql ) ) { 717 $cache_group = 'bp_activity'; 718 } else { 719 $cache_group = 'bp_activity_with_last_activity'; 720 } 721 722 $cached = bp_core_get_incremented_cache( $activity_ids_sql, $cache_group ); 723 if ( false === $cached ) { 724 $activity_ids = $wpdb->get_col( $activity_ids_sql ); 725 bp_core_set_incremented_cache( $activity_ids_sql, $cache_group, $activity_ids ); 726 } else { 727 $activity_ids = $cached; 728 } 729 730 $retval['has_more_items'] = ! empty( $per_page ) && count( $activity_ids ) > $per_page; 731 732 // If we've fetched more than the $per_page value, we 733 // can discard the extra now. 734 if ( ! empty( $per_page ) && count( $activity_ids ) === $per_page + 1 ) { 735 array_pop( $activity_ids ); 736 } 737 738 if ( 'ids' === $r['fields'] ) { 739 $activities = array_map( 'intval', $activity_ids ); 740 } else { 741 $activities = self::get_activity_data( $activity_ids ); 742 } 743 } 744 745 if ( 'ids' !== $r['fields'] ) { 746 // Get the fullnames of users so we don't have to query in the loop. 747 $activities = self::append_user_fullnames( $activities ); 748 749 // Get activity meta. 750 $activity_ids = array(); 751 foreach ( (array) $activities as $activity ) { 752 $activity_ids[] = $activity->id; 753 } 754 755 if ( ! empty( $activity_ids ) && $r['update_meta_cache'] ) { 756 bp_activity_update_meta_cache( $activity_ids ); 757 } 758 759 if ( $activities && $r['display_comments'] ) { 760 $activities = BP_Activity_Activity::append_comments( $activities, $r['spam'] ); 761 } 762 763 // Pre-fetch data associated with activity users and other objects. 764 BP_Activity_Activity::prefetch_object_data( $activities ); 765 766 // Generate action strings. 767 $activities = BP_Activity_Activity::generate_action_strings( $activities ); 768 } 769 770 $retval['activities'] = $activities; 771 772 // If $max is set, only return up to the max results. 773 if ( ! empty( $r['count_total'] ) ) { 774 775 /** 776 * Filters the total activities MySQL statement. 777 * 778 * @since 1.5.0 779 * 780 * @param string $value MySQL statement used to query for total activities. 781 * @param string $where_sql MySQL WHERE statement portion. 782 * @param string $sort Sort direction for query. 783 */ 784 $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 ); 785 $cached = bp_core_get_incremented_cache( $total_activities_sql, $cache_group ); 786 if ( false === $cached ) { 787 $total_activities = $wpdb->get_var( $total_activities_sql ); 788 bp_core_set_incremented_cache( $total_activities_sql, $cache_group, $total_activities ); 789 } else { 790 $total_activities = $cached; 791 } 792 793 if ( !empty( $r['max'] ) ) { 794 if ( (int) $total_activities > (int) $r['max'] ) { 795 $total_activities = $r['max']; 796 } 797 } 798 799 $retval['total'] = $total_activities; 800 } 801 802 return $retval; 803 } 804 805 /** 806 * Convert activity IDs to activity objects, as expected in template loop. 807 * 808 * @since 2.0.0 809 * 810 * @param array $activity_ids Array of activity IDs. 811 * @return array 812 */ 813 protected static function get_activity_data( $activity_ids = array() ) { 814 global $wpdb; 815 816 // Bail if no activity ID's passed. 817 if ( empty( $activity_ids ) ) { 818 return array(); 819 } 820 821 // Get BuddyPress. 822 $bp = buddypress(); 823 824 $activities = array(); 825 $uncached_ids = bp_get_non_cached_ids( $activity_ids, 'bp_activity' ); 826 827 // Prime caches as necessary. 828 if ( ! empty( $uncached_ids ) ) { 829 // Format the activity ID's for use in the query below. 830 $uncached_ids_sql = implode( ',', wp_parse_id_list( $uncached_ids ) ); 831 832 // Fetch data from activity table, preserving order. 833 $queried_adata = $wpdb->get_results( "SELECT * FROM {$bp->activity->table_name} WHERE id IN ({$uncached_ids_sql})"); 834 835 // Put that data into the placeholders created earlier, 836 // and add it to the cache. 837 foreach ( (array) $queried_adata as $adata ) { 838 wp_cache_set( $adata->id, $adata, 'bp_activity' ); 839 } 840 } 841 842 // Now fetch data from the cache. 843 foreach ( $activity_ids as $activity_id ) { 844 // Integer casting. 845 $activity = wp_cache_get( $activity_id, 'bp_activity' ); 846 if ( ! empty( $activity ) ) { 847 $activity->id = (int) $activity->id; 848 $activity->user_id = (int) $activity->user_id; 849 $activity->item_id = (int) $activity->item_id; 850 $activity->secondary_item_id = (int) $activity->secondary_item_id; 851 $activity->hide_sitewide = (int) $activity->hide_sitewide; 852 $activity->mptt_left = (int) $activity->mptt_left; 853 $activity->mptt_right = (int) $activity->mptt_right; 854 $activity->is_spam = (int) $activity->is_spam; 855 } 856 857 $activities[] = $activity; 858 } 859 860 // Then fetch user data. 861 $user_query = new BP_User_Query( array( 862 'user_ids' => wp_list_pluck( $activities, 'user_id' ), 863 'populate_extras' => false, 864 ) ); 865 866 // Associated located user data with activity items. 867 foreach ( $activities as $a_index => $a_item ) { 868 $a_user_id = intval( $a_item->user_id ); 869 $a_user = isset( $user_query->results[ $a_user_id ] ) ? $user_query->results[ $a_user_id ] : ''; 870 871 if ( !empty( $a_user ) ) { 872 $activities[ $a_index ]->user_email = $a_user->user_email; 873 $activities[ $a_index ]->user_nicename = $a_user->user_nicename; 874 $activities[ $a_index ]->user_login = $a_user->user_login; 875 $activities[ $a_index ]->display_name = $a_user->display_name; 876 } 877 } 878 879 return $activities; 880 } 881 882 /** 883 * Append xProfile fullnames to an activity array. 884 * 885 * @since 2.0.0 886 * 887 * @param array $activities Activities array. 888 * @return array 889 */ 890 protected static function append_user_fullnames( $activities ) { 891 892 if ( bp_is_active( 'xprofile' ) && ! empty( $activities ) ) { 893 $activity_user_ids = wp_list_pluck( $activities, 'user_id' ); 894 895 if ( ! empty( $activity_user_ids ) ) { 896 $fullnames = bp_core_get_user_displaynames( $activity_user_ids ); 897 if ( ! empty( $fullnames ) ) { 898 foreach ( (array) $activities as $i => $activity ) { 899 if ( ! empty( $fullnames[ $activity->user_id ] ) ) { 900 $activities[ $i ]->user_fullname = $fullnames[ $activity->user_id ]; 901 } 902 } 903 } 904 } 905 } 906 907 return $activities; 908 } 909 910 /** 911 * Pre-fetch data for objects associated with activity items. 912 * 913 * Activity items are associated with users, and often with other 914 * BuddyPress data objects. Here, we pre-fetch data about these 915 * associated objects, so that inline lookups - done primarily when 916 * building action strings - do not result in excess database queries. 917 * 918 * The only object data required for activity component activity types 919 * (activity_update and activity_comment) is related to users, and that 920 * info is fetched separately in BP_Activity_Activity::get_activity_data(). 921 * So this method contains nothing but a filter that allows other 922 * components, such as bp-friends and bp-groups, to hook in and prime 923 * their own caches at the beginning of an activity loop. 924 * 925 * @since 2.0.0 926 * 927 * @param array $activities Array of activities. 928 * @return array $activities Array of activities. 929 */ 930 protected static function prefetch_object_data( $activities ) { 931 932 /** 933 * Filters inside prefetch_object_data method to aid in pre-fetching object data associated with activity item. 934 * 935 * @since 2.0.0 936 * 937 * @param array $activities Array of activities. 938 */ 939 return apply_filters( 'bp_activity_prefetch_object_data', $activities ); 940 } 941 942 /** 943 * Generate action strings for the activities located in BP_Activity_Activity::get(). 944 * 945 * If no string can be dynamically generated for a given item 946 * (typically because the activity type has not been properly 947 * registered), the static 'action' value pulled from the database will 948 * be left in place. 949 * 950 * @since 2.0.0 951 * 952 * @param array $activities Array of activities. 953 * @return array 954 */ 955 protected static function generate_action_strings( $activities ) { 956 foreach ( $activities as $key => $activity ) { 957 $generated_action = bp_activity_generate_action_string( $activity ); 958 if ( false !== $generated_action ) { 959 $activity->action = $generated_action; 960 } 961 962 $activities[ $key ] = $activity; 963 } 964 965 return $activities; 966 } 967 968 /** 969 * Get the SQL for the 'meta_query' param in BP_Activity_Activity::get(). 970 * 971 * We use WP_Meta_Query to do the heavy lifting of parsing the 972 * meta_query array and creating the necessary SQL clauses. However, 973 * since BP_Activity_Activity::get() builds its SQL differently than 974 * WP_Query, we have to alter the return value (stripping the leading 975 * AND keyword from the 'where' clause). 976 * 977 * @since 1.8.0 978 * 979 * @param array $meta_query An array of meta_query filters. See the 980 * documentation for WP_Meta_Query for details. 981 * @return array $sql_array 'join' and 'where' clauses. 982 */ 983 public static function get_meta_query_sql( $meta_query = array() ) { 984 global $wpdb; 985 986 $sql_array = array( 987 'join' => '', 988 'where' => '', 989 ); 990 991 if ( ! empty( $meta_query ) ) { 992 $activity_meta_query = new WP_Meta_Query( $meta_query ); 993 994 // WP_Meta_Query expects the table name at 995 // $wpdb->activitymeta. 996 $wpdb->activitymeta = buddypress()->activity->table_name_meta; 997 998 $meta_sql = $activity_meta_query->get_sql( 'activity', 'a', 'id' ); 999 1000 // Strip the leading AND - BP handles it in get(). 1001 $sql_array['where'] = preg_replace( '/^\sAND/', '', $meta_sql['where'] ); 1002 $sql_array['join'] = $meta_sql['join']; 1003 } 1004 1005 return $sql_array; 1006 } 1007 1008 /** 1009 * Get the SQL for the 'date_query' param in BP_Activity_Activity::get(). 1010 * 1011 * We use BP_Date_Query, which extends WP_Date_Query, to do the heavy lifting 1012 * of parsing the date_query array and creating the necessary SQL clauses. 1013 * However, since BP_Activity_Activity::get() builds its SQL differently than 1014 * WP_Query, we have to alter the return value (stripping the leading AND 1015 * keyword from the query). 1016 * 1017 * @since 2.1.0 1018 * 1019 * @param array $date_query An array of date_query parameters. See the 1020 * documentation for the first parameter of WP_Date_Query. 1021 * @return string 1022 */ 1023 public static function get_date_query_sql( $date_query = array() ) { 1024 $sql = ''; 1025 1026 // Date query. 1027 if ( ! empty( $date_query ) && is_array( $date_query ) ) { 1028 $date_query = new BP_Date_Query( $date_query, 'date_recorded' ); 1029 $sql = preg_replace( '/^\sAND/', '', $date_query->get_sql() ); 1030 } 1031 1032 return $sql; 1033 } 1034 1035 /** 1036 * Get the SQL for the 'scope' param in BP_Activity_Activity::get(). 1037 * 1038 * A scope is a predetermined set of activity arguments. This method is used 1039 * to grab these activity arguments and override any existing args if needed. 1040 * 1041 * Can handle multiple scopes. 1042 * 1043 * @since 2.2.0 1044 * 1045 * @param mixed $scope The activity scope. Accepts string or array of scopes. 1046 * @param array $r Current activity arguments. Same as those of BP_Activity_Activity::get(), 1047 * but merged with defaults. 1048 * @return false|array 'sql' WHERE SQL string and 'override' activity args. 1049 */ 1050 public static function get_scope_query_sql( $scope = false, $r = array() ) { 1051 1052 // Define arrays for future use. 1053 $query_args = array(); 1054 $override = array(); 1055 $retval = array(); 1056 1057 // Check for array of scopes. 1058 if ( is_array( $scope ) ) { 1059 $scopes = $scope; 1060 1061 // Explode a comma separated string of scopes. 1062 } elseif ( is_string( $scope ) ) { 1063 $scopes = explode( ',', $scope ); 1064 } 1065 1066 // Bail if no scope passed. 1067 if ( empty( $scopes ) ) { 1068 return false; 1069 } 1070 1071 // Helper to easily grab the 'user_id'. 1072 if ( ! empty( $r['filter']['user_id'] ) ) { 1073 $r['user_id'] = $r['filter']['user_id']; 1074 } 1075 1076 // Parse each scope; yes! we handle multiples! 1077 foreach ( $scopes as $scope ) { 1078 $scope_args = array(); 1079 1080 /** 1081 * Plugins can hook here to set their activity arguments for custom scopes. 1082 * 1083 * This is a dynamic filter based on the activity scope. eg: 1084 * - 'bp_activity_set_groups_scope_args' 1085 * - 'bp_activity_set_friends_scope_args' 1086 * 1087 * To see how this filter is used, plugin devs should check out: 1088 * - bp_groups_filter_activity_scope() - used for 'groups' scope 1089 * - bp_friends_filter_activity_scope() - used for 'friends' scope 1090 * 1091 * @since 2.2.0 1092 * 1093 * @param array { 1094 * Activity query clauses. 1095 * @type array { 1096 * Activity arguments for your custom scope. 1097 * See {@link BP_Activity_Query::_construct()} for more details. 1098 * } 1099 * @type array $override Optional. Override existing activity arguments passed by $r. 1100 * } 1101 * } 1102 * @param array $r Current activity arguments passed in BP_Activity_Activity::get(). 1103 */ 1104 $scope_args = apply_filters( "bp_activity_set_{$scope}_scope_args", array(), $r ); 1105 1106 if ( ! empty( $scope_args ) ) { 1107 // Merge override properties from other scopes 1108 // this might be a problem... 1109 if ( ! empty( $scope_args['override'] ) ) { 1110 $override = array_merge( $override, $scope_args['override'] ); 1111 unset( $scope_args['override'] ); 1112 } 1113 1114 // Save scope args. 1115 if ( ! empty( $scope_args ) ) { 1116 $query_args[] = $scope_args; 1117 } 1118 } 1119 } 1120 1121 if ( ! empty( $query_args ) ) { 1122 // Set relation to OR. 1123 $query_args['relation'] = 'OR'; 1124 1125 $query = new BP_Activity_Query( $query_args ); 1126 $sql = $query->get_sql(); 1127 if ( ! empty( $sql ) ) { 1128 $retval['sql'] = $sql; 1129 } 1130 } 1131 1132 if ( ! empty( $override ) ) { 1133 $retval['override'] = $override; 1134 } 1135 1136 return $retval; 1137 } 1138 1139 /** 1140 * In BuddyPress 1.2.x, this was used to retrieve specific activity stream items (for example, on an activity's permalink page). 1141 * 1142 * As of 1.5.x, use BP_Activity_Activity::get() with an 'in' parameter instead. 1143 * 1144 * @since 1.2.0 1145 * 1146 * @deprecated 1.5 1147 * @deprecated Use BP_Activity_Activity::get() with an 'in' parameter instead. 1148 * 1149 * @param mixed $activity_ids Array or comma-separated string of activity IDs to retrieve. 1150 * @param int|bool $max Maximum number of results to return. (Optional; default is no maximum). 1151 * @param int $page The set of results that the user is viewing. Used in pagination. (Optional; default is 1). 1152 * @param int $per_page Specifies how many results per page. Used in pagination. (Optional; default is 25). 1153 * @param string $sort MySQL column sort; ASC or DESC. (Optional; default is DESC). 1154 * @param bool $display_comments Retrieve an activity item's associated comments or not. (Optional; default is false). 1155 * @return array 1156 */ 1157 public static function get_specific( $activity_ids, $max = false, $page = 1, $per_page = 25, $sort = 'DESC', $display_comments = false ) { 1158 _deprecated_function( __FUNCTION__, '1.5', 'Use BP_Activity_Activity::get() with the "in" parameter instead.' ); 1159 return BP_Activity_Activity::get( $max, $page, $per_page, $sort, false, false, $display_comments, false, false, $activity_ids ); 1160 } 1161 1162 /** 1163 * Get the first activity ID that matches a set of criteria. 1164 * 1165 * @since 1.2.0 1166 * 1167 * @todo Should parameters be optional? 1168 * 1169 * @param int $user_id User ID to filter by. 1170 * @param string $component Component to filter by. 1171 * @param string $type Activity type to filter by. 1172 * @param int $item_id Associated item to filter by. 1173 * @param int $secondary_item_id Secondary associated item to filter by. 1174 * @param string $action Action to filter by. 1175 * @param string $content Content to filter by. 1176 * @param string $date_recorded Date to filter by. 1177 * @return int|false Activity ID on success, false if none is found. 1178 */ 1179 public static function get_id( $user_id, $component, $type, $item_id, $secondary_item_id, $action, $content, $date_recorded ) { 1180 global $wpdb; 1181 1182 $bp = buddypress(); 1183 1184 $where_args = false; 1185 1186 if ( ! empty( $user_id ) ) { 1187 $where_args[] = $wpdb->prepare( "user_id = %d", $user_id ); 1188 } 1189 1190 if ( ! empty( $component ) ) { 1191 $where_args[] = $wpdb->prepare( "component = %s", $component ); 1192 } 1193 1194 if ( ! empty( $type ) ) { 1195 $where_args[] = $wpdb->prepare( "type = %s", $type ); 1196 } 1197 1198 if ( ! empty( $item_id ) ) { 1199 $where_args[] = $wpdb->prepare( "item_id = %d", $item_id ); 1200 } 1201 1202 if ( ! empty( $secondary_item_id ) ) { 1203 $where_args[] = $wpdb->prepare( "secondary_item_id = %d", $secondary_item_id ); 1204 } 1205 1206 if ( ! empty( $action ) ) { 1207 $where_args[] = $wpdb->prepare( "action = %s", $action ); 1208 } 1209 1210 if ( ! empty( $content ) ) { 1211 $where_args[] = $wpdb->prepare( "content = %s", $content ); 1212 } 1213 1214 if ( ! empty( $date_recorded ) ) { 1215 $where_args[] = $wpdb->prepare( "date_recorded = %s", $date_recorded ); 1216 } 1217 1218 if ( ! empty( $where_args ) ) { 1219 $where_sql = 'WHERE ' . join( ' AND ', $where_args ); 1220 $result = $wpdb->get_var( "SELECT id FROM {$bp->activity->table_name} {$where_sql}" ); 1221 1222 return is_numeric( $result ) ? (int) $result : false; 1223 } 1224 1225 return false; 1226 } 1227 1228 /** 1229 * Delete activity items from the database. 1230 * 1231 * To delete a specific activity item, pass an 'id' parameter. 1232 * Otherwise use the filters. 1233 * 1234 * @since 1.2.0 1235 * 1236 * @param array $args { 1237 * @int $id Optional. The ID of a specific item to delete. 1238 * @string $action Optional. The action to filter by. 1239 * @string $content Optional. The content to filter by. 1240 * @string $component Optional. The component name to filter by. 1241 * @string $type Optional. The activity type to filter by. 1242 * @string $primary_link Optional. The primary URL to filter by. 1243 * @int $user_id Optional. The user ID to filter by. 1244 * @int $item_id Optional. The associated item ID to filter by. 1245 * @int $secondary_item_id Optional. The secondary associated item ID to filter by. 1246 * @string $date_recorded Optional. The date to filter by. 1247 * @int $hide_sitewide Optional. Default: false. 1248 * } 1249 * @return array|bool An array of deleted activity IDs on success, false on failure. 1250 */ 1251 public static function delete( $args = array() ) { 1252 global $wpdb; 1253 1254 $bp = buddypress(); 1255 $r = wp_parse_args( $args, array( 1256 'id' => false, 1257 'action' => false, 1258 'content' => false, 1259 'component' => false, 1260 'type' => false, 1261 'primary_link' => false, 1262 'user_id' => false, 1263 'item_id' => false, 1264 'secondary_item_id' => false, 1265 'date_recorded' => false, 1266 'hide_sitewide' => false 1267 ) ); 1268 1269 // Setup empty array from where query arguments. 1270 $where_args = array(); 1271 1272 // ID. 1273 if ( ! empty( $r['id'] ) ) { 1274 $where_args[] = $wpdb->prepare( "id = %d", $r['id'] ); 1275 } 1276 1277 // User ID. 1278 if ( ! empty( $r['user_id'] ) ) { 1279 $where_args[] = $wpdb->prepare( "user_id = %d", $r['user_id'] ); 1280 } 1281 1282 // Action. 1283 if ( ! empty( $r['action'] ) ) { 1284 $where_args[] = $wpdb->prepare( "action = %s", $r['action'] ); 1285 } 1286 1287 // Content. 1288 if ( ! empty( $r['content'] ) ) { 1289 $where_args[] = $wpdb->prepare( "content = %s", $r['content'] ); 1290 } 1291 1292 // Component. 1293 if ( ! empty( $r['component'] ) ) { 1294 $where_args[] = $wpdb->prepare( "component = %s", $r['component'] ); 1295 } 1296 1297 // Type. 1298 if ( ! empty( $r['type'] ) ) { 1299 $where_args[] = $wpdb->prepare( "type = %s", $r['type'] ); 1300 } 1301 1302 // Primary Link. 1303 if ( ! empty( $r['primary_link'] ) ) { 1304 $where_args[] = $wpdb->prepare( "primary_link = %s", $r['primary_link'] ); 1305 } 1306 1307 // Item ID. 1308 if ( ! empty( $r['item_id'] ) ) { 1309 $where_args[] = $wpdb->prepare( "item_id = %d", $r['item_id'] ); 1310 } 1311 1312 // Secondary item ID. 1313 if ( ! empty( $r['secondary_item_id'] ) ) { 1314 $where_args[] = $wpdb->prepare( "secondary_item_id = %d", $r['secondary_item_id'] ); 1315 } 1316 1317 // Date Recorded. 1318 if ( ! empty( $r['date_recorded'] ) ) { 1319 $where_args[] = $wpdb->prepare( "date_recorded = %s", $r['date_recorded'] ); 1320 } 1321 1322 // Hidden sitewide. 1323 if ( ! empty( $r['hide_sitewide'] ) ) { 1324 $where_args[] = $wpdb->prepare( "hide_sitewide = %d", $r['hide_sitewide'] ); 1325 } 1326 1327 // Bail if no where arguments. 1328 if ( empty( $where_args ) ) { 1329 return false; 1330 } 1331 1332 // Join the where arguments for querying. 1333 $where_sql = 'WHERE ' . join( ' AND ', $where_args ); 1334 1335 // Fetch all activities being deleted so we can perform more actions. 1336 $activities = $wpdb->get_results( "SELECT * FROM {$bp->activity->table_name} {$where_sql}" ); 1337 1338 /** 1339 * Action to allow intercepting activity items to be deleted. 1340 * 1341 * @since 2.3.0 1342 * 1343 * @param array $activities Array of activities. 1344 * @param array $r Array of parsed arguments. 1345 */ 1346 do_action_ref_array( 'bp_activity_before_delete', array( $activities, $r ) ); 1347 1348 // Attempt to delete activities from the database. 1349 $deleted = $wpdb->query( "DELETE FROM {$bp->activity->table_name} {$where_sql}" ); 1350 1351 // Bail if nothing was deleted. 1352 if ( empty( $deleted ) ) { 1353 return false; 1354 } 1355 1356 /** 1357 * Action to allow intercepting activity items just deleted. 1358 * 1359 * @since 2.3.0 1360 * 1361 * @param array $activities Array of activities. 1362 * @param array $r Array of parsed arguments. 1363 */ 1364 do_action_ref_array( 'bp_activity_after_delete', array( $activities, $r ) ); 1365 1366 // Pluck the activity IDs out of the $activities array. 1367 $activity_ids = wp_parse_id_list( wp_list_pluck( $activities, 'id' ) ); 1368 1369 // Handle accompanying activity comments and meta deletion. 1370 if ( ! empty( $activity_ids ) ) { 1371 1372 // Delete all activity meta entries for activity items. 1373 BP_Activity_Activity::delete_activity_meta_entries( $activity_ids ); 1374 1375 // Setup empty array for comments. 1376 $comment_ids = array(); 1377 1378 // Loop through activity ids and attempt to delete comments. 1379 foreach ( $activity_ids as $activity_id ) { 1380 1381 // Attempt to delete comments. 1382 $comments = BP_Activity_Activity::delete( array( 1383 'type' => 'activity_comment', 1384 'item_id' => $activity_id 1385 ) ); 1386 1387 // Merge IDs together. 1388 if ( ! empty( $comments ) ) { 1389 $comment_ids = array_merge( $comment_ids, $comments ); 1390 } 1391 } 1392 1393 // Merge activity IDs with any deleted comment IDs. 1394 if ( ! empty( $comment_ids ) ) { 1395 $activity_ids = array_unique( array_merge( $activity_ids, $comment_ids ) ); 1396 } 1397 } 1398 1399 return $activity_ids; 1400 } 1401 1402 /** 1403 * Delete the comments associated with a set of activity items. 1404 * 1405 * This method is no longer used by BuddyPress, and it is recommended not to 1406 * use it going forward, and use BP_Activity_Activity::delete() instead. 1407 * 1408 * @since 1.2.0 1409 * 1410 * @deprecated 2.3.0 1411 * 1412 * @param array $activity_ids Activity IDs whose comments should be deleted. 1413 * @param bool $delete_meta Should we delete the activity meta items for these comments. 1414 * @return bool True on success. 1415 */ 1416 public static function delete_activity_item_comments( $activity_ids = array(), $delete_meta = true ) { 1417 global $wpdb; 1418 1419 $bp = buddypress(); 1420 1421 $delete_meta = (bool) $delete_meta; 1422 $activity_ids = implode( ',', wp_parse_id_list( $activity_ids ) ); 1423 1424 if ( $delete_meta ) { 1425 // Fetch the activity comment IDs for our deleted activity items. 1426 $activity_comment_ids = $wpdb->get_col( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" ); 1427 1428 if ( ! empty( $activity_comment_ids ) ) { 1429 self::delete_activity_meta_entries( $activity_comment_ids ); 1430 } 1431 } 1432 1433 return $wpdb->query( "DELETE FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" ); 1434 } 1435 1436 /** 1437 * Delete the meta entries associated with a set of activity items. 1438 * 1439 * @since 1.2.0 1440 * 1441 * @param array $activity_ids Activity IDs whose meta should be deleted. 1442 * @return bool True on success. 1443 */ 1444 public static function delete_activity_meta_entries( $activity_ids = array() ) { 1445 $activity_ids = wp_parse_id_list( $activity_ids ); 1446 1447 foreach ( $activity_ids as $activity_id ) { 1448 bp_activity_delete_meta( $activity_id ); 1449 } 1450 1451 return true; 1452 } 1453 1454 /** 1455 * Append activity comments to their associated activity items. 1456 * 1457 * @since 1.2.0 1458 * 1459 * @global wpdb $wpdb WordPress database object. 1460 * 1461 * @param array $activities Activities to fetch comments for. 1462 * @param string $spam Optional. 'ham_only' (default), 'spam_only' or 'all'. 1463 * @return array The updated activities with nested comments. 1464 */ 1465 public static function append_comments( $activities, $spam = 'ham_only' ) { 1466 $activity_comments = array(); 1467 1468 // Now fetch the activity comments and parse them into the correct position in the activities array. 1469 foreach ( (array) $activities as $activity ) { 1470 $top_level_parent_id = 'activity_comment' == $activity->type ? $activity->item_id : 0; 1471 $activity_comments[$activity->id] = BP_Activity_Activity::get_activity_comments( $activity->id, $activity->mptt_left, $activity->mptt_right, $spam, $top_level_parent_id ); 1472 } 1473 1474 // Merge the comments with the activity items. 1475 foreach ( (array) $activities as $key => $activity ) { 1476 if ( isset( $activity_comments[$activity->id] ) ) { 1477 $activities[$key]->children = $activity_comments[$activity->id]; 1478 } 1479 } 1480 1481 return $activities; 1482 } 1483 1484 /** 1485 * Get activity comments that are associated with a specific activity ID. 1486 * 1487 * @since 1.2.0 1488 * 1489 * @global wpdb $wpdb WordPress database object. 1490 * 1491 * @param int $activity_id Activity ID to fetch comments for. 1492 * @param int $left Left-most node boundary. 1493 * @param int $right Right-most node boundary. 1494 * @param string $spam Optional. 'ham_only' (default), 'spam_only' or 'all'. 1495 * @param int $top_level_parent_id Optional. The id of the root-level parent activity item. 1496 * @return array The updated activities with nested comments. 1497 */ 1498 public static function get_activity_comments( $activity_id, $left, $right, $spam = 'ham_only', $top_level_parent_id = 0 ) { 1499 global $wpdb; 1500 1501 $function_args = func_get_args(); 1502 1503 if ( empty( $top_level_parent_id ) ) { 1504 $top_level_parent_id = $activity_id; 1505 } 1506 1507 $comments = wp_cache_get( $activity_id, 'bp_activity_comments' ); 1508 1509 // We store the string 'none' to cache the fact that the 1510 // activity item has no comments. 1511 if ( 'none' === $comments ) { 1512 $comments = false; 1513 1514 // A true cache miss. 1515 } elseif ( empty( $comments ) ) { 1516 1517 $bp = buddypress(); 1518 1519 // Select the user's fullname with the query. 1520 if ( bp_is_active( 'xprofile' ) ) { 1521 $fullname_select = ", pd.value as user_fullname"; 1522 $fullname_from = ", {$bp->profile->table_name_data} pd "; 1523 $fullname_where = "AND pd.user_id = a.user_id AND pd.field_id = 1"; 1524 1525 // Prevent debug errors. 1526 } else { 1527 $fullname_select = $fullname_from = $fullname_where = ''; 1528 } 1529 1530 // Don't retrieve activity comments marked as spam. 1531 if ( 'ham_only' == $spam ) { 1532 $spam_sql = 'AND a.is_spam = 0'; 1533 } elseif ( 'spam_only' == $spam ) { 1534 $spam_sql = 'AND a.is_spam = 1'; 1535 } else { 1536 $spam_sql = ''; 1537 } 1538 1539 // Legacy query - not recommended. 1540 1541 /** 1542 * Filters if BuddyPress should use the legacy activity query. 1543 * 1544 * @since 2.0.0 1545 * 1546 * @param bool $value Whether or not to use the legacy query. 1547 * @param BP_Activity_Activity $value Magic method referring to currently called method. 1548 * @param array $func_args Array of the method's argument list. 1549 */ 1550 if ( apply_filters( 'bp_use_legacy_activity_query', false, __METHOD__, $function_args ) ) { 1551 1552 /** 1553 * Filters the MySQL prepared statement for the legacy activity query. 1554 * 1555 * @since 1.5.0 1556 * 1557 * @param string $value Prepared statement for the activity query. 1558 * @param int $activity_id Activity ID to fetch comments for. 1559 * @param int $left Left-most node boundary. 1560 * @param int $right Right-most node boundary. 1561 * @param string $spam_sql SQL Statement portion to differentiate between ham or spam. 1562 */ 1563 $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 ); 1564 1565 $descendants = $wpdb->get_results( $sql ); 1566 1567 // We use the mptt BETWEEN clause to limit returned 1568 // descendants to the correct part of the tree. 1569 } else { 1570 $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 ); 1571 1572 $descendant_ids = $wpdb->get_col( $sql ); 1573 $descendants = self::get_activity_data( $descendant_ids ); 1574 $descendants = self::append_user_fullnames( $descendants ); 1575 $descendants = self::generate_action_strings( $descendants ); 1576 } 1577 1578 $ref = array(); 1579 1580 // Loop descendants and build an assoc array. 1581 foreach ( (array) $descendants as $d ) { 1582 $d->children = array(); 1583 1584 // If we have a reference on the parent. 1585 if ( isset( $ref[ $d->secondary_item_id ] ) ) { 1586 $ref[ $d->secondary_item_id ]->children[ $d->id ] = $d; 1587 $ref[ $d->id ] =& $ref[ $d->secondary_item_id ]->children[ $d->id ]; 1588 1589 // If we don't have a reference on the parent, put in the root level. 1590 } else { 1591 $comments[ $d->id ] = $d; 1592 $ref[ $d->id ] =& $comments[ $d->id ]; 1593 } 1594 } 1595 1596 // Calculate depth for each item. 1597 foreach ( $ref as &$r ) { 1598 $depth = 1; 1599 $parent_id = $r->secondary_item_id; 1600 1601 while ( $parent_id !== $r->item_id ) { 1602 $depth++; 1603 1604 // When display_comments=stream, the parent comment may not be part of the 1605 // returned results, so we manually fetch it. 1606 if ( empty( $ref[ $parent_id ] ) ) { 1607 $direct_parent = new BP_Activity_Activity( $parent_id ); 1608 if ( isset( $direct_parent->secondary_item_id ) ) { 1609 // If the direct parent is not an activity update, that means we've reached 1610 // the parent activity item (eg. new_blog_post). 1611 if ( 'activity_update' !== $direct_parent->type ) { 1612 $parent_id = $r->item_id; 1613 1614 } else { 1615 $parent_id = $direct_parent->secondary_item_id; 1616 } 1617 1618 } else { 1619 // Something went wrong. Short-circuit the depth calculation. 1620 $parent_id = $r->item_id; 1621 } 1622 } else { 1623 $parent_id = $ref[ $parent_id ]->secondary_item_id; 1624 } 1625 } 1626 $r->depth = $depth; 1627 } 1628 1629 // If we cache a value of false, it'll count as a cache 1630 // miss the next time the activity comments are fetched. 1631 // Storing the string 'none' is a hack workaround to 1632 // avoid unnecessary queries. 1633 if ( false === $comments ) { 1634 $cache_value = 'none'; 1635 } else { 1636 $cache_value = $comments; 1637 } 1638 1639 wp_cache_set( $activity_id, $cache_value, 'bp_activity_comments' ); 1640 } 1641 1642 return $comments; 1643 } 1644 1645 /** 1646 * Rebuild nested comment tree under an activity or activity comment. 1647 * 1648 * @since 1.2.0 1649 * 1650 * @global wpdb $wpdb WordPress database object. 1651 * 1652 * @param int $parent_id ID of an activity or activity comment. 1653 * @param int $left Node boundary start for activity or activity comment. 1654 * @return int Right Node boundary of activity or activity comment. 1655 */ 1656 public static function rebuild_activity_comment_tree( $parent_id, $left = 1 ) { 1657 global $wpdb; 1658 1659 $bp = buddypress(); 1660 1661 // The right value of this node is the left value + 1. 1662 $right = intval( $left + 1 ); 1663 1664 // Get all descendants of this node. 1665 $comments = BP_Activity_Activity::get_child_comments( $parent_id ); 1666 $descendants = wp_list_pluck( $comments, 'id' ); 1667 1668 // Loop the descendants and recalculate the left and right values. 1669 foreach ( (array) $descendants as $descendant_id ) { 1670 $right = BP_Activity_Activity::rebuild_activity_comment_tree( $descendant_id, $right ); 1671 } 1672 1673 // We've got the left value, and now that we've processed the children 1674 // of this node we also know the right value. 1675 if ( 1 === $left ) { 1676 $wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET mptt_left = %d, mptt_right = %d WHERE id = %d", $left, $right, $parent_id ) ); 1677 } else { 1678 $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 ) ); 1679 } 1680 1681 // Return the right value of this node + 1. 1682 return intval( $right + 1 ); 1683 } 1684 1685 /** 1686 * Get child comments of an activity or activity comment. 1687 * 1688 * @since 1.2.0 1689 * 1690 * @global wpdb $wpdb WordPress database object. 1691 * 1692 * @param int $parent_id ID of an activity or activity comment. 1693 * @return object Numerically indexed array of child comments. 1694 */ 1695 public static function get_child_comments( $parent_id ) { 1696 global $wpdb; 1697 1698 $bp = buddypress(); 1699 1700 return $wpdb->get_results( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND secondary_item_id = %d", $parent_id ) ); 1701 } 1702 1703 /** 1704 * Get a list of components that have recorded activity associated with them. 1705 * 1706 * @since 1.2.0 1707 * 1708 * @param bool $skip_last_activity If true, components will not be 1709 * included if the only activity type associated with them is 1710 * 'last_activity'. (Since 2.0.0, 'last_activity' is stored in 1711 * the activity table, but these items are not full-fledged 1712 * activity items.) Default: true. 1713 * @return array List of component names. 1714 */ 1715 public static function get_recorded_components( $skip_last_activity = true ) { 1716 global $wpdb; 1717 1718 $bp = buddypress(); 1719 1720 if ( true === $skip_last_activity ) { 1721 $components = $wpdb->get_col( "SELECT DISTINCT component FROM {$bp->activity->table_name} WHERE action != '' AND action != 'last_activity' ORDER BY component ASC" ); 1722 } else { 1723 $components = $wpdb->get_col( "SELECT DISTINCT component FROM {$bp->activity->table_name} ORDER BY component ASC" ); 1724 } 1725 1726 return $components; 1727 } 1728 1729 /** 1730 * Get sitewide activity items for use in an RSS feed. 1731 * 1732 * @since 1.0.0 1733 * 1734 * @param int $limit Optional. Number of items to fetch. Default: 35. 1735 * @return array $activity_feed List of activity items, with RSS data added. 1736 */ 1737 public static function get_sitewide_items_for_feed( $limit = 35 ) { 1738 $activities = bp_activity_get_sitewide( array( 'max' => $limit ) ); 1739 $activity_feed = array(); 1740 1741 for ( $i = 0, $count = count( $activities ); $i < $count; ++$i ) { 1742 $title = explode( '<span', $activities[$i]['content'] ); 1743 $activity_feed[$i]['title'] = trim( strip_tags( $title[0] ) ); 1744 $activity_feed[$i]['link'] = $activities[$i]['primary_link']; 1745 $activity_feed[$i]['description'] = @sprintf( $activities[$i]['content'], '' ); 1746 $activity_feed[$i]['pubdate'] = $activities[$i]['date_recorded']; 1747 } 1748 1749 return $activity_feed; 1750 } 1751 1752 /** 1753 * Create SQL IN clause for filter queries. 1754 * 1755 * @since 1.5.0 1756 * 1757 * @see BP_Activity_Activity::get_filter_sql() 1758 * 1759 * @param string $field The database field. 1760 * @param array|bool $items The values for the IN clause, or false when none are found. 1761 * @return string|false 1762 */ 1763 public static function get_in_operator_sql( $field, $items ) { 1764 global $wpdb; 1765 1766 // Split items at the comma. 1767 if ( ! is_array( $items ) ) { 1768 $items = explode( ',', $items ); 1769 } 1770 1771 // Array of prepared integers or quoted strings. 1772 $items_prepared = array(); 1773 1774 // Clean up and format each item. 1775 foreach ( $items as $item ) { 1776 // Clean up the string. 1777 $item = trim( $item ); 1778 // Pass everything through prepare for security and to safely quote strings. 1779 $items_prepared[] = ( is_numeric( $item ) ) ? $wpdb->prepare( '%d', $item ) : $wpdb->prepare( '%s', $item ); 1780 } 1781 1782 // Build IN operator sql syntax. 1783 if ( count( $items_prepared ) ) 1784 return sprintf( '%s IN ( %s )', trim( $field ), implode( ',', $items_prepared ) ); 1785 else 1786 return false; 1787 } 1788 1789 /** 1790 * Create filter SQL clauses. 1791 * 1792 * @since 1.5.0 1793 * 1794 * @param array $filter_array { 1795 * Fields and values to filter by. 1796 * 1797 * @type array|string|int $user_id User ID(s). 1798 * @type array|string $object Corresponds to the 'component' 1799 * column in the database. 1800 * @type array|string $action Corresponds to the 'type' column 1801 * in the database. 1802 * @type array|string|int $primary_id Corresponds to the 'item_id' 1803 * column in the database. 1804 * @type array|string|int $secondary_id Corresponds to the 1805 * 'secondary_item_id' column in the database. 1806 * @type int $offset Return only those items with an ID greater 1807 * than the offset value. 1808 * @type string $since Return only those items that have a 1809 * date_recorded value greater than a 1810 * given MySQL-formatted date. 1811 * } 1812 * @return string The filter clause, for use in a SQL query. 1813 */ 1814 public static function get_filter_sql( $filter_array ) { 1815 1816 $filter_sql = array(); 1817 1818 if ( !empty( $filter_array['user_id'] ) ) { 1819 $user_sql = BP_Activity_Activity::get_in_operator_sql( 'a.user_id', $filter_array['user_id'] ); 1820 if ( !empty( $user_sql ) ) 1821 $filter_sql[] = $user_sql; 1822 } 1823 1824 if ( !empty( $filter_array['object'] ) ) { 1825 $object_sql = BP_Activity_Activity::get_in_operator_sql( 'a.component', $filter_array['object'] ); 1826 if ( !empty( $object_sql ) ) 1827 $filter_sql[] = $object_sql; 1828 } 1829 1830 if ( !empty( $filter_array['action'] ) ) { 1831 $action_sql = BP_Activity_Activity::get_in_operator_sql( 'a.type', $filter_array['action'] ); 1832 if ( ! empty( $action_sql ) ) 1833 $filter_sql[] = $action_sql; 1834 } 1835 1836 if ( !empty( $filter_array['primary_id'] ) ) { 1837 $pid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.item_id', $filter_array['primary_id'] ); 1838 if ( !empty( $pid_sql ) ) 1839 $filter_sql[] = $pid_sql; 1840 } 1841 1842 if ( !empty( $filter_array['secondary_id'] ) ) { 1843 $sid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.secondary_item_id', $filter_array['secondary_id'] ); 1844 if ( !empty( $sid_sql ) ) 1845 $filter_sql[] = $sid_sql; 1846 } 1847 1848 if ( ! empty( $filter_array['offset'] ) ) { 1849 $sid_sql = absint( $filter_array['offset'] ); 1850 $filter_sql[] = "a.id >= {$sid_sql}"; 1851 } 1852 1853 if ( ! empty( $filter_array['since'] ) ) { 1854 // Validate that this is a proper Y-m-d H:i:s date. 1855 // Trick: parse to UNIX date then translate back. 1856 $translated_date = date( 'Y-m-d H:i:s', strtotime( $filter_array['since'] ) ); 1857 if ( $translated_date === $filter_array['since'] ) { 1858 $filter_sql[] = "a.date_recorded > '{$translated_date}'"; 1859 } 1860 } 1861 1862 if ( empty( $filter_sql ) ) 1863 return false; 1864 1865 return join( ' AND ', $filter_sql ); 1866 } 1867 1868 /** 1869 * Get the date/time of last recorded activity. 1870 * 1871 * @since 1.2.0 1872 * 1873 * @return string ISO timestamp. 1874 */ 1875 public static function get_last_updated() { 1876 global $wpdb; 1877 1878 $bp = buddypress(); 1879 1880 return $wpdb->get_var( "SELECT date_recorded FROM {$bp->activity->table_name} ORDER BY date_recorded DESC LIMIT 1" ); 1881 } 1882 1883 /** 1884 * Get favorite count for a given user. 1885 * 1886 * @since 1.2.0 1887 * 1888 * @param int $user_id The ID of the user whose favorites you're counting. 1889 * @return int $value A count of the user's favorites. 1890 */ 1891 public static function total_favorite_count( $user_id ) { 1892 1893 // Get activities from user meta. 1894 $favorite_activity_entries = bp_get_user_meta( $user_id, 'bp_favorite_activities', true ); 1895 if ( ! empty( $favorite_activity_entries ) ) { 1896 return count( $favorite_activity_entries ); 1897 } 1898 1899 // No favorites. 1900 return 0; 1901 } 1902 1903 /** 1904 * Check whether an activity item exists with a given string content. 1905 * 1906 * @since 1.1.0 1907 * 1908 * @param string $content The content to filter by. 1909 * @return int|false The ID of the first matching item if found, otherwise false. 1910 */ 1911 public static function check_exists_by_content( $content ) { 1912 global $wpdb; 1913 1914 $bp = buddypress(); 1915 1916 $result = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE content = %s", $content ) ); 1917 1918 return is_numeric( $result ) ? (int) $result : false; 1919 } 1920 1921 /** 1922 * Hide all activity for a given user. 1923 * 1924 * @since 1.2.0 1925 * 1926 * @param int $user_id The ID of the user whose activity you want to mark hidden. 1927 * @return mixed 1928 */ 1929 public static function hide_all_for_user( $user_id ) { 1930 global $wpdb; 1931 1932 $bp = buddypress(); 1933 1934 return $wpdb->get_var( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET hide_sitewide = 1 WHERE user_id = %d", $user_id ) ); 1935 } 1936 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Apr 14 01:01:40 2021 | Cross-referenced by PHPXref 0.7.1 |