Changeset 9485 for trunk/src/bp-activity/bp-activity-classes.php
- Timestamp:
- 02/15/2015 12:48:56 AM (10 years ago)
- File:
-
- 1 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';
Note: See TracChangeset
for help on using the changeset viewer.