BP_Activity_Activity
Database interaction class for the BuddyBoss activity component.
Description
Instance methods are available for creating/editing an activity, static methods for querying activities.
Source
File: bp-activity/classes/class-bp-activity-activity.php
class BP_Activity_Activity {
/** Properties ************************************************************/
/**
* ID of the activity item.
*
* @since BuddyPress 1.0.0
* @var int
*/
var $id;
/**
* ID of the associated item.
*
* @since BuddyPress 1.0.0
* @var int
*/
var $item_id;
/**
* ID of the associated secondary item.
*
* @since BuddyPress 1.0.0
* @var int
*/
var $secondary_item_id;
/**
* ID of user associated with the activity item.
*
* @since BuddyPress 1.0.0
* @var int
*/
var $user_id;
/**
* The primary URL for the activity in RSS feeds.
*
* @since BuddyPress 1.0.0
* @var string
*/
var $primary_link;
/**
* BuddyPress component the activity item relates to.
*
* @since BuddyPress 1.2.0
* @var string
*/
var $component;
/**
* Activity type, eg 'new_blog_post'.
*
* @since BuddyPress 1.2.0
* @var string
*/
var $type;
/**
* Description of the activity, eg 'Alex updated his profile.'.
*
* @since BuddyPress 1.2.0
* @var string
*/
var $action;
/**
* The content of the activity item.
*
* @since BuddyPress 1.2.0
* @var string
*/
var $content;
/**
* The date the activity item was recorded, in 'Y-m-d h:i:s' format.
*
* @since BuddyPress 1.0.0
* @var string
*/
var $date_recorded;
/**
* Whether the item should be hidden in sitewide streams.
*
* @since BuddyPress 1.1.0
* @var int
*/
var $hide_sitewide = 0;
/**
* Node boundary start for activity or activity comment.
*
* @since BuddyPress 1.5.0
* @var int
*/
var $mptt_left;
/**
* Node boundary end for activity or activity comment.
*
* @since BuddyPress 1.5.0
* @var int
*/
var $mptt_right;
/**
* Whether this item is marked as spam.
*
* @since BuddyPress 1.6.0
* @var int
*/
var $is_spam;
/**
* Privacy of the activity, eg 'public'.
*
* @since BuddyBoss 1.0.0
* @var string
*/
var $privacy;
/**
* Error holder.
*
* @since BuddyPress 2.6.0
*
* @var WP_Error
*/
public $errors;
/**
* Error type to return. Either 'bool' or 'wp_error'.
*
* @since BuddyPress 2.6.0
*
* @var string
*/
public $error_type = 'bool';
/**
* Constructor method.
*
* @since BuddyPress 1.5.0
*
* @param int|bool $id Optional. The ID of a specific activity item.
*/
public function __construct( $id = false ) {
// Instantiate errors object.
$this->errors = new WP_Error;
if ( !empty( $id ) ) {
$this->id = (int) $id;
$this->populate();
}
}
/**
* Populate the object with data about the specific activity item.
*
* @since BuddyPress 1.0.0
*/
public function populate() {
global $wpdb;
$row = wp_cache_get( $this->id, 'bp_activity' );
if ( false === $row ) {
$bp = buddypress();
$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->activity->table_name} WHERE id = %d", $this->id ) );
wp_cache_set( $this->id, $row, 'bp_activity' );
}
if ( empty( $row ) ) {
$this->id = 0;
return;
}
$this->id = (int) $row->id;
$this->item_id = (int) $row->item_id;
$this->secondary_item_id = (int) $row->secondary_item_id;
$this->user_id = (int) $row->user_id;
$this->primary_link = $row->primary_link;
$this->component = $row->component;
$this->type = $row->type;
$this->action = $row->action;
$this->content = $row->content;
$this->date_recorded = $row->date_recorded;
$this->hide_sitewide = (int) $row->hide_sitewide;
$this->mptt_left = (int) $row->mptt_left;
$this->mptt_right = (int) $row->mptt_right;
$this->is_spam = (int) $row->is_spam;
$this->privacy = $row->privacy;
// Generate dynamic 'action' when possible.
$action = bp_activity_generate_action_string( $this );
if ( false !== $action ) {
$this->action = $action;
// If no callback is available, use the literal string from
// the database row.
} elseif ( ! empty( $row->action ) ) {
$this->action = $row->action;
// Provide a fallback to avoid PHP notices.
} else {
$this->action = '';
}
}
/**
* Save the activity item to the database.
*
* @since BuddyPress 1.0.0
*
* @return WP_Error|bool True on success.
*/
public function save() {
global $wpdb;
$bp = buddypress();
$this->id = apply_filters_ref_array( 'bp_activity_id_before_save', array( $this->id, &$this ) );
$this->item_id = apply_filters_ref_array( 'bp_activity_item_id_before_save', array( $this->item_id, &$this ) );
$this->secondary_item_id = apply_filters_ref_array( 'bp_activity_secondary_item_id_before_save', array( $this->secondary_item_id, &$this ) );
$this->user_id = apply_filters_ref_array( 'bp_activity_user_id_before_save', array( $this->user_id, &$this ) );
$this->primary_link = apply_filters_ref_array( 'bp_activity_primary_link_before_save', array( $this->primary_link, &$this ) );
$this->component = apply_filters_ref_array( 'bp_activity_component_before_save', array( $this->component, &$this ) );
$this->type = apply_filters_ref_array( 'bp_activity_type_before_save', array( $this->type, &$this ) );
$this->action = apply_filters_ref_array( 'bp_activity_action_before_save', array( $this->action, &$this ) );
$this->content = apply_filters_ref_array( 'bp_activity_content_before_save', array( $this->content, &$this ) );
$this->date_recorded = apply_filters_ref_array( 'bp_activity_date_recorded_before_save', array( $this->date_recorded, &$this ) );
$this->hide_sitewide = apply_filters_ref_array( 'bp_activity_hide_sitewide_before_save', array( $this->hide_sitewide, &$this ) );
$this->mptt_left = apply_filters_ref_array( 'bp_activity_mptt_left_before_save', array( $this->mptt_left, &$this ) );
$this->mptt_right = apply_filters_ref_array( 'bp_activity_mptt_right_before_save', array( $this->mptt_right, &$this ) );
$this->is_spam = apply_filters_ref_array( 'bp_activity_is_spam_before_save', array( $this->is_spam, &$this ) );
$this->privacy = apply_filters_ref_array( 'bp_activity_privacy_before_save', array( $this->privacy, &$this ) );
/**
* Fires before the current activity item gets saved.
*
* Please use this hook to filter the properties above. Each part will be passed in.
*
* @since BuddyPress 1.0.0
*
* @param BP_Activity_Activity $this Current instance of the activity item being saved. Passed by reference.
*/
do_action_ref_array( 'bp_activity_before_save', array( &$this ) );
if ( 'wp_error' === $this->error_type && $this->errors->get_error_code() ) {
return $this->errors;
}
if ( empty( $this->component ) || empty( $this->type ) ) {
if ( 'bool' === $this->error_type ) {
return false;
} else {
if ( empty( $this->component ) ) {
$this->errors->add( 'bp_activity_missing_component' );
} else {
$this->errors->add( 'bp_activity_missing_type' );
}
return $this->errors;
}
}
if ( empty( $this->primary_link ) ) {
$this->primary_link = bp_loggedin_user_domain();
}
// If we have an existing ID, update the activity item, otherwise insert it.
if ( ! empty( $this->id ) ) {
$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, privacy = %s 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->privacy, $this->id );
} else {
$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, privacy ) VALUES ( %d, %s, %s, %s, %s, %s, %s, %d, %d, %d, %d, %s )", $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->privacy );
}
if ( false === $wpdb->query( $q ) ) {
return false;
}
// If this is a new activity item, set the $id property.
if ( empty( $this->id ) ) {
$this->id = $wpdb->insert_id;
// If an existing activity item, prevent any changes to the content generating new @mention notifications.
} else {
add_filter( 'bp_activity_at_name_do_notifications', '__return_false' );
}
/**
* Fires after an activity item has been saved to the database.
*
* @since BuddyPress 1.0.0
*
* @param BP_Activity_Activity $this Current instance of activity item being saved. Passed by reference.
*/
do_action_ref_array( 'bp_activity_after_save', array( &$this ) );
return true;
}
/** Static Methods ***************************************************/
/**
* Get activity items, as specified by parameters.
*
* @since BuddyPress 1.2.0
* @since BuddyPress 2.4.0 Introduced the `$fields` parameter.
* @since BuddyPress 2.9.0 Introduced the `$order_by` parameter.
*
* @see BP_Activity_Activity::get_filter_sql() for a description of the
* 'filter' parameter.
* @see WP_Meta_Query::queries for a description of the 'meta_query'
* parameter format.
*
* @param array $args {
* An array of arguments. All items are optional.
* @type int $page Which page of results to fetch. Using page=1 without per_page will result
* in no pagination. Default: 1.
* @type int|bool $per_page Number of results per page. Default: 25.
* @type int|bool $max Maximum number of results to return. Default: false (unlimited).
* @type string $fields Activity fields to return. Pass 'ids' to get only the activity IDs.
* 'all' returns full activity objects.
* @type string $sort ASC or DESC. Default: 'DESC'.
* @type string $order_by Column to order results by.
* @type array $exclude Array of activity IDs to exclude. Default: false.
* @type array $in Array of ids to limit query by (IN). Default: false.
* @type array $meta_query Array of meta_query conditions. See WP_Meta_Query::queries.
* @type array $date_query Array of date_query conditions. See first parameter of
* WP_Date_Query::__construct().
* @type array $filter_query Array of advanced query conditions. See BP_Activity_Query::__construct().
* @type string|array $scope Pre-determined set of activity arguments.
* @type array $filter See BP_Activity_Activity::get_filter_sql().
* @type string $search_terms Limit results by a search term. Default: false.
* @type string $privacy Limit results by a privacy. Default: public.
* @type bool $display_comments Whether to include activity comments. Default: false.
* @type bool $show_hidden Whether to show items marked hide_sitewide. Default: false.
* @type string $spam Spam status. Default: 'ham_only'.
* @type bool $update_meta_cache Whether to pre-fetch metadata for queried activity items. Default: true.
* @type string|bool $count_total If true, an additional DB query is run to count the total activity items
* for the query. Default: false.
* }
* @return array The array returned has two keys:
* - 'total' is the count of located activities
* - 'activities' is an array of the located activities
*/
public static function get( $args = array() ) {
global $wpdb;
// Backward compatibility with old method of passing arguments.
if ( !is_array( $args ) || func_num_args() > 1 ) {
_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.', 'buddyboss' ), __METHOD__, __FILE__ ) );
$old_args_keys = array(
0 => 'max',
1 => 'page',
2 => 'per_page',
3 => 'sort',
4 => 'search_terms',
5 => 'filter',
6 => 'display_comments',
7 => 'show_hidden',
8 => 'exclude',
9 => 'in',
10 => 'spam'
);
$args = bp_core_parse_args_array( $old_args_keys, func_get_args() );
}
$bp = buddypress();
$r = wp_parse_args( $args, array(
'page' => 1, // The current page.
'per_page' => 25, // Activity items per page.
'max' => false, // Max number of items to return.
'fields' => 'all', // Fields to include.
'sort' => 'DESC', // ASC or DESC.
'order_by' => 'date_recorded', // Column to order by.
'exclude' => false, // Array of ids to exclude.
'in' => false, // Array of ids to limit query by (IN).
'meta_query' => false, // Filter by activitymeta.
'date_query' => false, // Filter by date.
'filter_query' => false, // Advanced filtering - see BP_Activity_Query.
'filter' => false, // See self::get_filter_sql().
'scope' => false, // Preset activity arguments.
'search_terms' => false, // Terms to search by.
'privacy' => false, // public, loggedin, onlyme, friends, media.
'display_comments' => false, // Whether to include activity comments.
'show_hidden' => false, // Show items marked hide_sitewide.
'spam' => 'ham_only', // Spam status.
'update_meta_cache' => true, // Whether or not to update meta cache.
'count_total' => false, // Whether or not to use count_total.
) );
// Select conditions.
$select_sql = "SELECT DISTINCT a.id";
$from_sql = " FROM {$bp->activity->table_name} a";
$join_sql = '';
// Where conditions.
$where_conditions = array();
// Excluded types.
$excluded_types = array();
// Scope takes precedence.
if ( ! empty( $r['scope'] ) ) {
$scope_query = self::get_scope_query_sql( $r['scope'], $r );
// Add our SQL conditions if matches were found.
if ( ! empty( $scope_query['sql'] ) ) {
$where_conditions['scope_query_sql'] = $scope_query['sql'];
}
// Override some arguments if needed.
if ( ! empty( $scope_query['override'] ) ) {
$r = array_replace_recursive( $r, $scope_query['override'] );
}
// Advanced filtering.
} elseif ( ! empty( $r['filter_query'] ) ) {
$filter_query = new BP_Activity_Query( $r['filter_query'] );
$sql = $filter_query->get_sql();
if ( ! empty( $sql ) ) {
$where_conditions['filter_query_sql'] = $sql;
}
}
// Regular filtering.
if ( $r['filter'] && $filter_sql = BP_Activity_Activity::get_filter_sql( $r['filter'] ) ) {
$where_conditions['filter_sql'] = $filter_sql;
}
// Spam.
if ( 'ham_only' == $r['spam'] ) {
$where_conditions['spam_sql'] = 'a.is_spam = 0';
} elseif ( 'spam_only' == $r['spam'] ) {
$where_conditions['spam_sql'] = 'a.is_spam = 1';
}
// Searching.
if ( $r['search_terms'] ) {
$search_terms_like = '%' . bp_esc_like( $r['search_terms'] ) . '%';
$where_conditions['search_sql'] = $wpdb->prepare( 'a.content LIKE %s', $search_terms_like );
/**
* Filters whether or not to include users for search parameters.
*
* @since BuddyPress 3.0.0
*
* @param bool $value Whether or not to include user search. Default false.
*/
if ( apply_filters( 'bp_activity_get_include_user_search', false ) ) {
$user_search = get_user_by( 'slug', $r['search_terms'] );
if ( false !== $user_search ) {
$user_id = $user_search->ID;
$where_conditions['search_sql'] .= $wpdb->prepare( ' OR a.user_id = %d', $user_id );
}
}
}
// Sorting.
$sort = $r['sort'];
if ( $sort != 'ASC' && $sort != 'DESC' ) {
$sort = 'DESC';
}
switch( $r['order_by'] ) {
case 'id' :
case 'user_id' :
case 'component' :
case 'type' :
case 'action' :
case 'content' :
case 'primary_link' :
case 'item_id' :
case 'secondary_item_id' :
case 'date_recorded' :
case 'hide_sitewide' :
case 'mptt_left' :
case 'mptt_right' :
case 'is_spam' :
break;
default :
$r['order_by'] = 'date_recorded';
break;
}
$order_by = 'a.' . $r['order_by'];
// Hide Hidden Items?
if ( ! $r['show_hidden'] ) {
$where_conditions['hidden_sql'] = "a.hide_sitewide = 0";
}
// Exclude specified items.
if ( ! empty( $r['exclude'] ) ) {
$exclude = implode( ',', wp_parse_id_list( $r['exclude'] ) );
$where_conditions['exclude'] = "a.id NOT IN ({$exclude})";
}
// The specific ids to which you want to limit the query.
if ( ! empty( $r['in'] ) ) {
$in = implode( ',', wp_parse_id_list( $r['in'] ) );
$where_conditions['in'] = "a.id IN ({$in})";
}
// The filter activities by their privacy
if ( ! empty( $r['privacy'] ) ) {
$privacy = "'" . implode ( "', '", $r['privacy'] ) . "'";
$where_conditions['privacy'] = "a.privacy IN ({$privacy})";
}
// Process meta_query into SQL.
$meta_query_sql = self::get_meta_query_sql( $r['meta_query'] );
if ( ! empty( $meta_query_sql['join'] ) ) {
$join_sql .= $meta_query_sql['join'];
}
if ( ! empty( $meta_query_sql['where'] ) ) {
$where_conditions[] = $meta_query_sql['where'];
}
// Process date_query into SQL.
$date_query_sql = self::get_date_query_sql( $r['date_query'] );
if ( ! empty( $date_query_sql ) ) {
$where_conditions['date'] = $date_query_sql;
}
// Alter the query based on whether we want to show activity item
// comments in the stream like normal comments or threaded below
// the activity.
if ( false === $r['display_comments'] || 'threaded' === $r['display_comments'] ) {
$excluded_types[] = 'activity_comment';
}
// Exclude 'last_activity' items unless the 'action' filter has
// been explicitly set.
if ( empty( $r['filter']['object'] ) ) {
$excluded_types[] = 'last_activity';
}
// Build the excluded type sql part.
if ( ! empty( $excluded_types ) ) {
$not_in = "'" . implode( "', '", esc_sql( $excluded_types ) ) . "'";
$where_conditions['excluded_types'] = "a.type NOT IN ({$not_in})";
}
/**
* Filters the MySQL WHERE conditions for the Activity items get method.
*
* @since BuddyPress 1.9.0
*
* @param array $where_conditions Current conditions for MySQL WHERE statement.
* @param array $r Parsed arguments passed into method.
* @param string $select_sql Current SELECT MySQL statement at point of execution.
* @param string $from_sql Current FROM MySQL statement at point of execution.
* @param string $join_sql Current INNER JOIN MySQL statement at point of execution.
*/
$where_conditions = apply_filters( 'bp_activity_get_where_conditions', $where_conditions, $r, $select_sql, $from_sql, $join_sql );
// Join the where conditions together.
$where_sql = 'WHERE ' . join( ' AND ', $where_conditions );
/**
* Filter the MySQL JOIN clause for the main activity query.
*
* @since BuddyPress 2.5.0
*
* @param string $join_sql JOIN clause.
* @param array $r Method parameters.
* @param string $select_sql Current SELECT MySQL statement.
* @param string $from_sql Current FROM MySQL statement.
* @param string $where_sql Current WHERE MySQL statement.
*/
$join_sql = apply_filters( 'bp_activity_get_join_sql', $join_sql, $r, $select_sql, $from_sql, $where_sql );
// Sanitize page and per_page parameters.
$page = absint( $r['page'] );
$per_page = absint( $r['per_page'] );
$retval = array(
'activities' => null,
'total' => null,
'has_more_items' => null,
);
/**
* Filters if BuddyPress should use legacy query structure over current structure for version 2.0+.
*
* It is not recommended to use the legacy structure, but allowed to if needed.
*
* @since BuddyPress 2.0.0
*
* @param bool $value Whether to use legacy structure or not.
* @param BP_Activity_Activity $value Current method being called.
* @param array $r Parsed arguments passed into method.
*/
if ( apply_filters( 'bp_use_legacy_activity_query', false, __METHOD__, $r ) ) {
// Legacy queries joined against the user table.
$select_sql = "SELECT DISTINCT a.*, u.user_email, u.user_nicename, u.user_login, u.display_name";
$from_sql = " FROM {$bp->activity->table_name} a LEFT JOIN {$wpdb->users} u ON a.user_id = u.ID";
if ( ! empty( $page ) && ! empty( $per_page ) ) {
$pag_sql = $wpdb->prepare( "LIMIT %d, %d", absint( ( $page - 1 ) * $per_page ), $per_page );
/** This filter is documented in bp-activity/bp-activity-classes.php */
$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 );
} else {
$pag_sql = '';
/**
* Filters the legacy MySQL query statement so plugins can alter before results are fetched.
*
* @since BuddyPress 1.5.0
*
* @param string $value Concatenated MySQL statement pieces to be query results with for legacy query.
* @param string $select_sql Final SELECT MySQL statement portion for legacy query.
* @param string $from_sql Final FROM MySQL statement portion for legacy query.
* @param string $where_sql Final WHERE MySQL statement portion for legacy query.
* @param string $sort Final sort direction for legacy query.
*/
$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 );
}
/*
* Queries that include 'last_activity' are cached separately,
* since they are generally much less long-lived.
*/
if ( preg_match( '/a\.type NOT IN \([^\)]*\'last_activity\'[^\)]*\)/', $activity_sql ) ) {
$cache_group = 'bp_activity';
} else {
$cache_group = 'bp_activity_with_last_activity';
}
$activities = $wpdb->get_results( $activity_sql );
// Integer casting for legacy activity query.
foreach ( (array) $activities as $i => $ac ) {
$activities[ $i ]->id = (int) $ac->id;
$activities[ $i ]->item_id = (int) $ac->item_id;
$activities[ $i ]->secondary_item_id = (int) $ac->secondary_item_id;
$activities[ $i ]->user_id = (int) $ac->user_id;
$activities[ $i ]->hide_sitewide = (int) $ac->hide_sitewide;
$activities[ $i ]->mptt_left = (int) $ac->mptt_left;
$activities[ $i ]->mptt_right = (int) $ac->mptt_right;
$activities[ $i ]->is_spam = (int) $ac->is_spam;
}
} else {
// Query first for activity IDs.
$activity_ids_sql = "{$select_sql} {$from_sql} {$join_sql} {$where_sql} ORDER BY {$order_by} {$sort}, a.id {$sort}";
if ( ! empty( $per_page ) && ! empty( $page ) ) {
// We query for $per_page + 1 items in order to
// populate the has_more_items flag.
$activity_ids_sql .= $wpdb->prepare( " LIMIT %d, %d", absint( ( $page - 1 ) * $per_page ), $per_page + 1 );
}
/**
* Filters the paged activities MySQL statement.
*
* @since BuddyPress 2.0.0
*
* @param string $activity_ids_sql MySQL statement used to query for Activity IDs.
* @param array $r Array of arguments passed into method.
*/
$activity_ids_sql = apply_filters( 'bp_activity_paged_activities_sql', $activity_ids_sql, $r );
/*
* Queries that include 'last_activity' are cached separately,
* since they are generally much less long-lived.
*/
if ( preg_match( '/a\.type NOT IN \([^\)]*\'last_activity\'[^\)]*\)/', $activity_ids_sql ) ) {
$cache_group = 'bp_activity';
} else {
$cache_group = 'bp_activity_with_last_activity';
}
$cached = bp_core_get_incremented_cache( $activity_ids_sql, $cache_group );
if ( false === $cached ) {
$activity_ids = $wpdb->get_col( $activity_ids_sql );
bp_core_set_incremented_cache( $activity_ids_sql, $cache_group, $activity_ids );
} else {
$activity_ids = $cached;
}
$retval['has_more_items'] = ! empty( $per_page ) && count( $activity_ids ) > $per_page;
// If we've fetched more than the $per_page value, we
// can discard the extra now.
if ( ! empty( $per_page ) && count( $activity_ids ) === $per_page + 1 ) {
array_pop( $activity_ids );
}
if ( 'ids' === $r['fields'] ) {
$activities = array_map( 'intval', $activity_ids );
} else {
$activities = self::get_activity_data( $activity_ids );
}
}
if ( 'ids' !== $r['fields'] ) {
// Get the fullnames of users so we don't have to query in the loop.
$activities = self::append_user_fullnames( $activities );
// Get activity meta.
$activity_ids = array();
foreach ( (array) $activities as $activity ) {
$activity_ids[] = $activity->id;
}
if ( ! empty( $activity_ids ) && $r['update_meta_cache'] ) {
bp_activity_update_meta_cache( $activity_ids );
}
if ( $activities && $r['display_comments'] ) {
$activities = BP_Activity_Activity::append_comments( $activities, $r['spam'] );
}
// Pre-fetch data associated with activity users and other objects.
BP_Activity_Activity::prefetch_object_data( $activities );
// Generate action strings.
$activities = BP_Activity_Activity::generate_action_strings( $activities );
}
$retval['activities'] = $activities;
// If $max is set, only return up to the max results.
if ( ! empty( $r['count_total'] ) ) {
/**
* Filters the total activities MySQL statement.
*
* @since BuddyPress 1.5.0
*
* @param string $value MySQL statement used to query for total activities.
* @param string $where_sql MySQL WHERE statement portion.
* @param string $sort Sort direction for query.
*/
$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 );
$cached = bp_core_get_incremented_cache( $total_activities_sql, $cache_group );
if ( false === $cached ) {
$total_activities = $wpdb->get_var( $total_activities_sql );
bp_core_set_incremented_cache( $total_activities_sql, $cache_group, $total_activities );
} else {
$total_activities = $cached;
}
if ( !empty( $r['max'] ) ) {
if ( (int) $total_activities > (int) $r['max'] ) {
$total_activities = $r['max'];
}
}
$retval['total'] = $total_activities;
}
return $retval;
}
/**
* Convert activity IDs to activity objects, as expected in template loop.
*
* @since BuddyPress 2.0.0
*
* @param array $activity_ids Array of activity IDs.
* @return array
*/
protected static function get_activity_data( $activity_ids = array() ) {
global $wpdb;
// Bail if no activity ID's passed.
if ( empty( $activity_ids ) ) {
return array();
}
// Get BuddyPress.
$bp = buddypress();
$activities = array();
$uncached_ids = bp_get_non_cached_ids( $activity_ids, 'bp_activity' );
// Prime caches as necessary.
if ( ! empty( $uncached_ids ) ) {
// Format the activity ID's for use in the query below.
$uncached_ids_sql = implode( ',', wp_parse_id_list( $uncached_ids ) );
// Fetch data from activity table, preserving order.
$queried_adata = $wpdb->get_results( "SELECT * FROM {$bp->activity->table_name} WHERE id IN ({$uncached_ids_sql})");
// Put that data into the placeholders created earlier,
// and add it to the cache.
foreach ( (array) $queried_adata as $adata ) {
wp_cache_set( $adata->id, $adata, 'bp_activity' );
}
}
// Now fetch data from the cache.
foreach ( $activity_ids as $activity_id ) {
// Integer casting.
$activity = wp_cache_get( $activity_id, 'bp_activity' );
if ( ! empty( $activity ) ) {
$activity->id = (int) $activity->id;
$activity->user_id = (int) $activity->user_id;
$activity->item_id = (int) $activity->item_id;
$activity->secondary_item_id = (int) $activity->secondary_item_id;
$activity->hide_sitewide = (int) $activity->hide_sitewide;
$activity->mptt_left = (int) $activity->mptt_left;
$activity->mptt_right = (int) $activity->mptt_right;
$activity->is_spam = (int) $activity->is_spam;
}
$activities[] = $activity;
}
// Then fetch user data.
$user_query = new BP_User_Query( array(
'user_ids' => wp_list_pluck( $activities, 'user_id' ),
'populate_extras' => false,
) );
// Associated located user data with activity items.
foreach ( $activities as $a_index => $a_item ) {
$a_user_id = intval( $a_item->user_id );
$a_user = isset( $user_query->results[ $a_user_id ] ) ? $user_query->results[ $a_user_id ] : '';
if ( !empty( $a_user ) ) {
$activities[ $a_index ]->user_email = $a_user->user_email;
$activities[ $a_index ]->user_nicename = $a_user->user_nicename;
$activities[ $a_index ]->user_login = $a_user->user_login;
$activities[ $a_index ]->display_name = $a_user->display_name;
}
}
return $activities;
}
/**
* Append xProfile fullnames to an activity array.
*
* @since BuddyPress 2.0.0
*
* @param array $activities Activities array.
* @return array
*/
protected static function append_user_fullnames( $activities ) {
if ( bp_is_active( 'xprofile' ) && ! empty( $activities ) ) {
$activity_user_ids = wp_list_pluck( $activities, 'user_id' );
if ( ! empty( $activity_user_ids ) ) {
$fullnames = bp_core_get_user_displaynames( $activity_user_ids );
if ( ! empty( $fullnames ) ) {
foreach ( (array) $activities as $i => $activity ) {
if ( ! empty( $fullnames[ $activity->user_id ] ) ) {
$activities[ $i ]->user_fullname = $fullnames[ $activity->user_id ];
}
}
}
}
}
return $activities;
}
/**
* Pre-fetch data for objects associated with activity items.
*
* Activity items are associated with users, and often with other
* BuddyPress data objects. Here, we pre-fetch data about these
* associated objects, so that inline lookups - done primarily when
* building action strings - do not result in excess database queries.
*
* The only object data required for activity component activity types
* (activity_update and activity_comment) is related to users, and that
* info is fetched separately in BP_Activity_Activity::get_activity_data().
* So this method contains nothing but a filter that allows other
* components, such as bp-friends and bp-groups, to hook in and prime
* their own caches at the beginning of an activity loop.
*
* @since BuddyPress 2.0.0
*
* @param array $activities Array of activities.
* @return array $activities Array of activities.
*/
protected static function prefetch_object_data( $activities ) {
/**
* Filters inside prefetch_object_data method to aid in pre-fetching object data associated with activity item.
*
* @since BuddyPress 2.0.0
*
* @param array $activities Array of activities.
*/
return apply_filters( 'bp_activity_prefetch_object_data', $activities );
}
/**
* Generate action strings for the activities located in BP_Activity_Activity::get().
*
* If no string can be dynamically generated for a given item
* (typically because the activity type has not been properly
* registered), the static 'action' value pulled from the database will
* be left in place.
*
* @since BuddyPress 2.0.0
*
* @param array $activities Array of activities.
* @return array
*/
protected static function generate_action_strings( $activities ) {
foreach ( $activities as $key => $activity ) {
$generated_action = bp_activity_generate_action_string( $activity );
if ( false !== $generated_action ) {
$activity->action = $generated_action;
}
$activities[ $key ] = $activity;
}
return $activities;
}
/**
* Get the SQL for the 'meta_query' param in BP_Activity_Activity::get().
*
* We use WP_Meta_Query to do the heavy lifting of parsing the
* meta_query array and creating the necessary SQL clauses. However,
* since BP_Activity_Activity::get() builds its SQL differently than
* WP_Query, we have to alter the return value (stripping the leading
* AND keyword from the 'where' clause).
*
* @since BuddyPress 1.8.0
*
* @param array $meta_query An array of meta_query filters. See the
* documentation for WP_Meta_Query for details.
* @return array $sql_array 'join' and 'where' clauses.
*/
public static function get_meta_query_sql( $meta_query = array() ) {
global $wpdb;
$sql_array = array(
'join' => '',
'where' => '',
);
if ( ! empty( $meta_query ) ) {
$activity_meta_query = new WP_Meta_Query( $meta_query );
// WP_Meta_Query expects the table name at
// $wpdb->activitymeta.
$wpdb->activitymeta = buddypress()->activity->table_name_meta;
$meta_sql = $activity_meta_query->get_sql( 'activity', 'a', 'id' );
// Strip the leading AND - BP handles it in get().
$sql_array['where'] = preg_replace( '/^\sAND/', '', $meta_sql['where'] );
$sql_array['join'] = $meta_sql['join'];
}
return $sql_array;
}
/**
* Get the SQL for the 'date_query' param in BP_Activity_Activity::get().
*
* We use BP_Date_Query, which extends WP_Date_Query, to do the heavy lifting
* of parsing the date_query array and creating the necessary SQL clauses.
* However, since BP_Activity_Activity::get() builds its SQL differently than
* WP_Query, we have to alter the return value (stripping the leading AND
* keyword from the query).
*
* @since BuddyPress 2.1.0
*
* @param array $date_query An array of date_query parameters. See the
* documentation for the first parameter of WP_Date_Query.
* @return string
*/
public static function get_date_query_sql( $date_query = array() ) {
$sql = '';
// Date query.
if ( ! empty( $date_query ) && is_array( $date_query ) ) {
$date_query = new BP_Date_Query( $date_query, 'date_recorded' );
$sql = preg_replace( '/^\sAND/', '', $date_query->get_sql() );
}
return $sql;
}
/**
* Get the SQL for the 'scope' param in BP_Activity_Activity::get().
*
* A scope is a predetermined set of activity arguments. This method is used
* to grab these activity arguments and override any existing args if needed.
*
* Can handle multiple scopes.
*
* @since BuddyPress 2.2.0
*
* @param mixed $scope The activity scope. Accepts string or array of scopes.
* @param array $r Current activity arguments. Same as those of BP_Activity_Activity::get(),
* but merged with defaults.
* @return false|array 'sql' WHERE SQL string and 'override' activity args.
*/
public static function get_scope_query_sql( $scope = false, $r = array() ) {
// Define arrays for future use.
$query_args = array();
$override = array();
$retval = array();
// Check for array of scopes.
if ( is_array( $scope ) ) {
$scopes = $scope;
// Explode a comma separated string of scopes.
} elseif ( is_string( $scope ) ) {
$scopes = explode( ',', $scope );
}
// Bail if no scope passed.
if ( empty( $scopes ) ) {
return false;
}
// Helper to easily grab the 'user_id'.
if ( ! empty( $r['filter']['user_id'] ) ) {
$r['user_id'] = $r['filter']['user_id'];
}
// Parse each scope; yes! we handle multiples!
foreach ( $scopes as $scope ) {
$scope_args = array();
/**
* Plugins can hook here to set their activity arguments for custom scopes.
*
* This is a dynamic filter based on the activity scope. eg:
* - 'bp_activity_set_groups_scope_args'
* - 'bp_activity_set_friends_scope_args'
*
* To see how this filter is used, plugin devs should check out:
* - bp_groups_filter_activity_scope() - used for 'groups' scope
* - bp_friends_filter_activity_scope() - used for 'friends' scope
*
* @since BuddyPress 2.2.0
*
* @param array {
* Activity query clauses.
* @type array {
* Activity arguments for your custom scope.
* See {@link BP_Activity_Query::_construct()} for more details.
* }
* @type array $override Optional. Override existing activity arguments passed by $r.
* }
* }
* @param array $r Current activity arguments passed in BP_Activity_Activity::get().
*/
$scope_args = apply_filters( "bp_activity_set_{$scope}_scope_args", array(), $r );
if ( ! empty( $scope_args ) ) {
// Merge override properties from other scopes
// this might be a problem...
if ( ! empty( $scope_args['override'] ) ) {
$override = array_merge( $override, $scope_args['override'] );
unset( $scope_args['override'] );
}
// Save scope args.
if ( ! empty( $scope_args ) ) {
$query_args[] = $scope_args;
}
}
}
if ( ! empty( $query_args ) ) {
// Set relation to OR.
$query_args['relation'] = 'OR';
$query = new BP_Activity_Query( $query_args );
$sql = $query->get_sql();
if ( ! empty( $sql ) ) {
$retval['sql'] = $sql;
}
}
if ( ! empty( $override ) ) {
$retval['override'] = $override;
}
return $retval;
}
/**
* In BuddyPress 1.2.x, this was used to retrieve specific activity feed items (for example, on an activity's permalink page).
*
* As of 1.5.x, use BP_Activity_Activity::get() with an 'in' parameter instead.
*
* @since BuddyPress 1.2.0
*
* @deprecated 1.5
* @deprecated Use BP_Activity_Activity::get() with an 'in' parameter instead.
*
* @param mixed $activity_ids Array or comma-separated string of activity IDs to retrieve.
* @param int|bool $max Maximum number of results to return. (Optional; default is no maximum).
* @param int $page The set of results that the user is viewing. Used in pagination. (Optional; default is 1).
* @param int $per_page Specifies how many results per page. Used in pagination. (Optional; default is 25).
* @param string $sort MySQL column sort; ASC or DESC. (Optional; default is DESC).
* @param bool $display_comments Retrieve an activity item's associated comments or not. (Optional; default is false).
* @return array
*/
public static function get_specific( $activity_ids, $max = false, $page = 1, $per_page = 25, $sort = 'DESC', $display_comments = false ) {
_deprecated_function( __FUNCTION__, '1.5', 'Use BP_Activity_Activity::get() with the "in" parameter instead.' );
return BP_Activity_Activity::get( $max, $page, $per_page, $sort, false, false, $display_comments, false, false, $activity_ids );
}
/**
* Get the first activity ID that matches a set of criteria.
*
* @since BuddyPress 1.2.0
*
* @todo Should parameters be optional?
*
* @param int $user_id User ID to filter by.
* @param string $component Component to filter by.
* @param string $type Activity type to filter by.
* @param int $item_id Associated item to filter by.
* @param int $secondary_item_id Secondary associated item to filter by.
* @param string $action Action to filter by.
* @param string $content Content to filter by.
* @param string $date_recorded Date to filter by.
* @return int|false Activity ID on success, false if none is found.
*/
public static function get_id( $user_id, $component, $type, $item_id, $secondary_item_id, $action, $content, $date_recorded ) {
global $wpdb;
$bp = buddypress();
$where_args = false;
if ( ! empty( $user_id ) ) {
$where_args[] = $wpdb->prepare( "user_id = %d", $user_id );
}
if ( ! empty( $component ) ) {
$where_args[] = $wpdb->prepare( "component = %s", $component );
}
if ( ! empty( $type ) ) {
$where_args[] = $wpdb->prepare( "type = %s", $type );
}
if ( ! empty( $item_id ) ) {
$where_args[] = $wpdb->prepare( "item_id = %d", $item_id );
}
if ( ! empty( $secondary_item_id ) ) {
$where_args[] = $wpdb->prepare( "secondary_item_id = %d", $secondary_item_id );
}
if ( ! empty( $action ) ) {
$where_args[] = $wpdb->prepare( "action = %s", $action );
}
if ( ! empty( $content ) ) {
$where_args[] = $wpdb->prepare( "content = %s", $content );
}
if ( ! empty( $date_recorded ) ) {
$where_args[] = $wpdb->prepare( "date_recorded = %s", $date_recorded );
}
if ( ! empty( $where_args ) ) {
$where_sql = 'WHERE ' . join( ' AND ', $where_args );
$result = $wpdb->get_var( "SELECT id FROM {$bp->activity->table_name} {$where_sql}" );
return is_numeric( $result ) ? (int) $result : false;
}
return false;
}
/**
* Delete activity items from the database.
*
* To delete a specific activity item, pass an 'id' parameter.
* Otherwise use the filters.
*
* @since BuddyPress 1.2.0
*
* @param array $args {
* @int $id Optional. The ID of a specific item to delete.
* @string $action Optional. The action to filter by.
* @string $content Optional. The content to filter by.
* @string $component Optional. The component name to filter by.
* @string $type Optional. The activity type to filter by.
* @string $primary_link Optional. The primary URL to filter by.
* @int $user_id Optional. The user ID to filter by.
* @int $item_id Optional. The associated item ID to filter by.
* @int $secondary_item_id Optional. The secondary associated item ID to filter by.
* @string $date_recorded Optional. The date to filter by.
* @int $hide_sitewide Optional. Default: false.
* }
* @return array|bool An array of deleted activity IDs on success, false on failure.
*/
public static function delete( $args = array() ) {
global $wpdb;
$bp = buddypress();
$r = wp_parse_args( $args, array(
'id' => false,
'action' => false,
'content' => false,
'component' => false,
'type' => false,
'primary_link' => false,
'user_id' => false,
'item_id' => false,
'secondary_item_id' => false,
'date_recorded' => false,
'hide_sitewide' => false
) );
// Setup empty array from where query arguments.
$where_args = array();
// ID.
if ( ! empty( $r['id'] ) ) {
$where_args[] = $wpdb->prepare( "id = %d", $r['id'] );
}
// User ID.
if ( ! empty( $r['user_id'] ) ) {
$where_args[] = $wpdb->prepare( "user_id = %d", $r['user_id'] );
}
// Action.
if ( ! empty( $r['action'] ) ) {
$where_args[] = $wpdb->prepare( "action = %s", $r['action'] );
}
// Content.
if ( ! empty( $r['content'] ) ) {
$where_args[] = $wpdb->prepare( "content = %s", $r['content'] );
}
// Component.
if ( ! empty( $r['component'] ) ) {
$where_args[] = $wpdb->prepare( "component = %s", $r['component'] );
}
// Type.
if ( ! empty( $r['type'] ) ) {
$where_args[] = $wpdb->prepare( "type = %s", $r['type'] );
}
// Primary Link.
if ( ! empty( $r['primary_link'] ) ) {
$where_args[] = $wpdb->prepare( "primary_link = %s", $r['primary_link'] );
}
// Item ID.
if ( ! empty( $r['item_id'] ) ) {
$where_args[] = $wpdb->prepare( "item_id = %d", $r['item_id'] );
}
// Secondary item ID.
if ( ! empty( $r['secondary_item_id'] ) ) {
$where_args[] = $wpdb->prepare( "secondary_item_id = %d", $r['secondary_item_id'] );
}
// Date Recorded.
if ( ! empty( $r['date_recorded'] ) ) {
$where_args[] = $wpdb->prepare( "date_recorded = %s", $r['date_recorded'] );
}
// Hidden sitewide.
if ( ! empty( $r['hide_sitewide'] ) ) {
$where_args[] = $wpdb->prepare( "hide_sitewide = %d", $r['hide_sitewide'] );
}
// Bail if no where arguments.
if ( empty( $where_args ) ) {
return false;
}
// Join the where arguments for querying.
$where_sql = 'WHERE ' . join( ' AND ', $where_args );
// Fetch all activities being deleted so we can perform more actions.
$activities = $wpdb->get_results( "SELECT * FROM {$bp->activity->table_name} {$where_sql}" );
/**
* Action to allow intercepting activity items to be deleted.
*
* @since BuddyPress 2.3.0
*
* @param array $activities Array of activities.
* @param array $r Array of parsed arguments.
*/
do_action_ref_array( 'bp_activity_before_delete', array( $activities, $r ) );
// Attempt to delete activities from the database.
$deleted = $wpdb->query( "DELETE FROM {$bp->activity->table_name} {$where_sql}" );
// Bail if nothing was deleted.
if ( empty( $deleted ) ) {
return false;
}
/**
* Action to allow intercepting activity items just deleted.
*
* @since BuddyPress 2.3.0
*
* @param array $activities Array of activities.
* @param array $r Array of parsed arguments.
*/
do_action_ref_array( 'bp_activity_after_delete', array( $activities, $r ) );
// Pluck the activity IDs out of the $activities array.
$activity_ids = wp_parse_id_list( wp_list_pluck( $activities, 'id' ) );
// Handle accompanying activity comments and meta deletion.
if ( ! empty( $activity_ids ) ) {
// Delete all activity meta entries for activity items.
BP_Activity_Activity::delete_activity_meta_entries( $activity_ids );
// Setup empty array for comments.
$comment_ids = array();
// Loop through activity ids and attempt to delete comments.
foreach ( $activity_ids as $activity_id ) {
// Attempt to delete comments.
$comments = BP_Activity_Activity::delete( array(
'type' => 'activity_comment',
'item_id' => $activity_id
) );
// Merge IDs together.
if ( ! empty( $comments ) ) {
$comment_ids = array_merge( $comment_ids, $comments );
}
}
// Merge activity IDs with any deleted comment IDs.
if ( ! empty( $comment_ids ) ) {
$activity_ids = array_unique( array_merge( $activity_ids, $comment_ids ) );
}
}
return $activity_ids;
}
/**
* Delete the comments associated with a set of activity items.
*
* This method is no longer used by BuddyPress, and it is recommended not to
* use it going forward, and use BP_Activity_Activity::delete() instead.
*
* @since BuddyPress 1.2.0
*
* @deprecated 2.3.0
*
* @param array $activity_ids Activity IDs whose comments should be deleted.
* @param bool $delete_meta Should we delete the activity meta items for these comments.
* @return bool True on success.
*/
public static function delete_activity_item_comments( $activity_ids = array(), $delete_meta = true ) {
global $wpdb;
$bp = buddypress();
$delete_meta = (bool) $delete_meta;
$activity_ids = implode( ',', wp_parse_id_list( $activity_ids ) );
if ( $delete_meta ) {
// Fetch the activity comment IDs for our deleted activity items.
$activity_comment_ids = $wpdb->get_col( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" );
if ( ! empty( $activity_comment_ids ) ) {
self::delete_activity_meta_entries( $activity_comment_ids );
}
}
return $wpdb->query( "DELETE FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" );
}
/**
* Delete the meta entries associated with a set of activity items.
*
* @since BuddyPress 1.2.0
*
* @param array $activity_ids Activity IDs whose meta should be deleted.
* @return bool True on success.
*/
public static function delete_activity_meta_entries( $activity_ids = array() ) {
$activity_ids = wp_parse_id_list( $activity_ids );
foreach ( $activity_ids as $activity_id ) {
bp_activity_delete_meta( $activity_id );
}
return true;
}
/**
* Append activity comments to their associated activity items.
*
* @since BuddyPress 1.2.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param array $activities Activities to fetch comments for.
* @param string $spam Optional. 'ham_only' (default), 'spam_only' or 'all'.
* @return array The updated activities with nested comments.
*/
public static function append_comments( $activities, $spam = 'ham_only' ) {
$activity_comments = array();
// Now fetch the activity comments and parse them into the correct position in the activities array.
foreach ( (array) $activities as $activity ) {
$top_level_parent_id = 'activity_comment' == $activity->type ? $activity->item_id : 0;
$activity_comments[$activity->id] = BP_Activity_Activity::get_activity_comments( $activity->id, $activity->mptt_left, $activity->mptt_right, $spam, $top_level_parent_id );
}
// Merge the comments with the activity items.
foreach ( (array) $activities as $key => $activity ) {
if ( isset( $activity_comments[$activity->id] ) ) {
$activities[$key]->children = $activity_comments[$activity->id];
}
}
return $activities;
}
/**
* Get activity comments that are associated with a specific activity ID.
*
* @since BuddyPress 1.2.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param int $activity_id Activity ID to fetch comments for.
* @param int $left Left-most node boundary.
* @param int $right Right-most node boundary.
* @param string $spam Optional. 'ham_only' (default), 'spam_only' or 'all'.
* @param int $top_level_parent_id Optional. The id of the root-level parent activity item.
* @return array The updated activities with nested comments.
*/
public static function get_activity_comments( $activity_id, $left, $right, $spam = 'ham_only', $top_level_parent_id = 0 ) {
global $wpdb;
if ( empty( $top_level_parent_id ) ) {
$top_level_parent_id = $activity_id;
}
$comments = wp_cache_get( $activity_id, 'bp_activity_comments' );
// We store the string 'none' to cache the fact that the
// activity item has no comments.
if ( 'none' === $comments ) {
$comments = false;
// A true cache miss.
} elseif ( empty( $comments ) ) {
$bp = buddypress();
// Select the user's fullname with the query.
if ( bp_is_active( 'xprofile' ) ) {
$fullname_select = ", pd.value as user_fullname";
$fullname_from = ", {$bp->profile->table_name_data} pd ";
$fullname_where = "AND pd.user_id = a.user_id AND pd.field_id = 1";
// Prevent debug errors.
} else {
$fullname_select = $fullname_from = $fullname_where = '';
}
// Don't retrieve activity comments marked as spam.
if ( 'ham_only' == $spam ) {
$spam_sql = 'AND a.is_spam = 0';
} elseif ( 'spam_only' == $spam ) {
$spam_sql = 'AND a.is_spam = 1';
} else {
$spam_sql = '';
}
// Legacy query - not recommended.
/**
* Filters if BuddyPress should use the legacy activity query.
*
* @since BuddyPress 2.0.0
*
* @param bool $value Whether or not to use the legacy query.
* @param BP_Activity_Activity $value Magic method referring to currently called method.
* @param array $func_args Array of the method's argument list.
*/
if ( apply_filters( 'bp_use_legacy_activity_query', false, __METHOD__, func_get_args() ) ) {
/**
* Filters the MySQL prepared statement for the legacy activity query.
*
* @since BuddyPress 1.5.0
*
* @param string $value Prepared statement for the activity query.
* @param int $activity_id Activity ID to fetch comments for.
* @param int $left Left-most node boundary.
* @param int $right Right-most node boundary.
* @param string $spam_sql SQL Statement portion to differentiate between ham or spam.
*/
$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 );
$descendants = $wpdb->get_results( $sql );
// We use the mptt BETWEEN clause to limit returned
// descendants to the correct part of the tree.
} else {
$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 );
$descendant_ids = $wpdb->get_col( $sql );
$descendants = self::get_activity_data( $descendant_ids );
$descendants = self::append_user_fullnames( $descendants );
}
$ref = array();
// Loop descendants and build an assoc array.
foreach ( (array) $descendants as $d ) {
$d->children = array();
// If we have a reference on the parent.
if ( isset( $ref[ $d->secondary_item_id ] ) ) {
$ref[ $d->secondary_item_id ]->children[ $d->id ] = $d;
$ref[ $d->id ] =& $ref[ $d->secondary_item_id ]->children[ $d->id ];
// If we don't have a reference on the parent, put in the root level.
} else {
$comments[ $d->id ] = $d;
$ref[ $d->id ] =& $comments[ $d->id ];
}
}
// Calculate depth for each item.
foreach ( $ref as &$r ) {
$depth = 1;
$parent_id = $r->secondary_item_id;
while ( $parent_id !== $r->item_id ) {
$depth++;
// When display_comments=stream, the parent comment may not be part of the
// returned results, so we manually fetch it.
if ( empty( $ref[ $parent_id ] ) ) {
$direct_parent = new BP_Activity_Activity( $parent_id );
if ( isset( $direct_parent->secondary_item_id ) ) {
// If the direct parent is not an activity update, that means we've reached
// the parent activity item (eg. new_blog_post).
if ( 'activity_update' !== $direct_parent->type ) {
$parent_id = $r->item_id;
} else {
$parent_id = $direct_parent->secondary_item_id;
}
} else {
// Something went wrong. Short-circuit the depth calculation.
$parent_id = $r->item_id;
}
} else {
$parent_id = $ref[ $parent_id ]->secondary_item_id;
}
}
$r->depth = $depth;
}
// If we cache a value of false, it'll count as a cache
// miss the next time the activity comments are fetched.
// Storing the string 'none' is a hack workaround to
// avoid unnecessary queries.
if ( false === $comments ) {
$cache_value = 'none';
} else {
$cache_value = $comments;
}
wp_cache_set( $activity_id, $cache_value, 'bp_activity_comments' );
}
return $comments;
}
/**
* Rebuild nested comment tree under an activity or activity comment.
*
* @since BuddyPress 1.2.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param int $parent_id ID of an activity or activity comment.
* @param int $left Node boundary start for activity or activity comment.
* @return int Right Node boundary of activity or activity comment.
*/
public static function rebuild_activity_comment_tree( $parent_id, $left = 1 ) {
global $wpdb;
$bp = buddypress();
// The right value of this node is the left value + 1.
$right = intval( $left + 1 );
// Get all descendants of this node.
$comments = BP_Activity_Activity::get_child_comments( $parent_id );
$descendants = wp_list_pluck( $comments, 'id' );
// Loop the descendants and recalculate the left and right values.
foreach ( (array) $descendants as $descendant_id ) {
$right = BP_Activity_Activity::rebuild_activity_comment_tree( $descendant_id, $right );
}
// We've got the left value, and now that we've processed the children
// of this node we also know the right value.
if ( 1 === $left ) {
$wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET mptt_left = %d, mptt_right = %d WHERE id = %d", $left, $right, $parent_id ) );
} else {
$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 ) );
}
// Return the right value of this node + 1.
return intval( $right + 1 );
}
/**
* Get child comments of an activity or activity comment.
*
* @since BuddyPress 1.2.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param int $parent_id ID of an activity or activity comment.
* @return object Numerically indexed array of child comments.
*/
public static function get_child_comments( $parent_id ) {
global $wpdb;
$bp = buddypress();
return $wpdb->get_results( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND secondary_item_id = %d", $parent_id ) );
}
/**
* Get a list of components that have recorded activity associated with them.
*
* @since BuddyPress 1.2.0
*
* @param bool $skip_last_activity If true, components will not be
* included if the only activity type associated with them is
* 'last_activity'. (Since 2.0.0, 'last_activity' is stored in
* the activity table, but these items are not full-fledged
* activity items.) Default: true.
* @return array List of component names.
*/
public static function get_recorded_components( $skip_last_activity = true ) {
global $wpdb;
$bp = buddypress();
if ( true === $skip_last_activity ) {
$components = $wpdb->get_col( "SELECT DISTINCT component FROM {$bp->activity->table_name} WHERE action != '' AND action != 'last_activity' ORDER BY component ASC" );
} else {
$components = $wpdb->get_col( "SELECT DISTINCT component FROM {$bp->activity->table_name} ORDER BY component ASC" );
}
return $components;
}
/**
* Get sitewide activity items for use in an RSS feed.
*
* @since BuddyPress 1.0.0
*
* @param int $limit Optional. Number of items to fetch. Default: 35.
* @return array $activity_feed List of activity items, with RSS data added.
*/
public static function get_sitewide_items_for_feed( $limit = 35 ) {
$activities = bp_activity_get_sitewide( array( 'max' => $limit ) );
$activity_feed = array();
for ( $i = 0, $count = count( $activities ); $i < $count; ++$i ) {
$title = explode( '<span', $activities[$i]['content'] );
$activity_feed[$i]['title'] = trim( strip_tags( $title[0] ) );
$activity_feed[$i]['link'] = $activities[$i]['primary_link'];
$activity_feed[$i]['description'] = @sprintf( $activities[$i]['content'], '' );
$activity_feed[$i]['pubdate'] = $activities[$i]['date_recorded'];
}
return $activity_feed;
}
/**
* Create SQL IN clause for filter queries.
*
* @since BuddyPress 1.5.0
*
* @see BP_Activity_Activity::get_filter_sql()
*
* @param string $field The database field.
* @param array|bool $items The values for the IN clause, or false when none are found.
* @return string|false
*/
public static function get_in_operator_sql( $field, $items ) {
global $wpdb;
// Split items at the comma.
if ( ! is_array( $items ) ) {
$items = explode( ',', $items );
}
// Array of prepared integers or quoted strings.
$items_prepared = array();
// Clean up and format each item.
foreach ( $items as $item ) {
// Clean up the string.
$item = trim( $item );
// Pass everything through prepare for security and to safely quote strings.
$items_prepared[] = ( is_numeric( $item ) ) ? $wpdb->prepare( '%d', $item ) : $wpdb->prepare( '%s', $item );
}
// Build IN operator sql syntax.
if ( count( $items_prepared ) )
return sprintf( '%s IN ( %s )', trim( $field ), implode( ',', $items_prepared ) );
else
return false;
}
/**
* Create filter SQL clauses.
*
* @since BuddyPress 1.5.0
*
* @param array $filter_array {
* Fields and values to filter by.
*
* @type array|string|int $user_id User ID(s).
* @type array|string $object Corresponds to the 'component'
* column in the database.
* @type array|string $action Corresponds to the 'type' column
* in the database.
* @type array|string|int $primary_id Corresponds to the 'item_id'
* column in the database.
* @type array|string|int $secondary_id Corresponds to the
* 'secondary_item_id' column in the database.
* @type int $offset Return only those items with an ID greater
* than the offset value.
* @type string $since Return only those items that have a
* date_recorded value greater than a
* given MySQL-formatted date.
* }
* @return string The filter clause, for use in a SQL query.
*/
public static function get_filter_sql( $filter_array ) {
$filter_sql = array();
if ( !empty( $filter_array['user_id'] ) ) {
$user_sql = BP_Activity_Activity::get_in_operator_sql( 'a.user_id', $filter_array['user_id'] );
if ( !empty( $user_sql ) )
$filter_sql[] = $user_sql;
}
if ( !empty( $filter_array['object'] ) ) {
$object_sql = BP_Activity_Activity::get_in_operator_sql( 'a.component', $filter_array['object'] );
if ( !empty( $object_sql ) )
$filter_sql[] = $object_sql;
}
if ( !empty( $filter_array['action'] ) ) {
$action_sql = BP_Activity_Activity::get_in_operator_sql( 'a.type', $filter_array['action'] );
if ( ! empty( $action_sql ) )
$filter_sql[] = $action_sql;
}
if ( !empty( $filter_array['primary_id'] ) ) {
$pid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.item_id', $filter_array['primary_id'] );
if ( !empty( $pid_sql ) )
$filter_sql[] = $pid_sql;
}
if ( !empty( $filter_array['secondary_id'] ) ) {
$sid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.secondary_item_id', $filter_array['secondary_id'] );
if ( !empty( $sid_sql ) )
$filter_sql[] = $sid_sql;
}
if ( ! empty( $filter_array['offset'] ) ) {
$sid_sql = absint( $filter_array['offset'] );
$filter_sql[] = "a.id >= {$sid_sql}";
}
if ( ! empty( $filter_array['since'] ) ) {
// Validate that this is a proper Y-m-d H:i:s date.
// Trick: parse to UNIX date then translate back.
$translated_date = date( 'Y-m-d H:i:s', strtotime( $filter_array['since'] ) );
if ( $translated_date === $filter_array['since'] ) {
$filter_sql[] = "a.date_recorded > '{$translated_date}'";
}
}
if ( empty( $filter_sql ) )
return false;
return join( ' AND ', $filter_sql );
}
/**
* Get the date/time of last recorded activity.
*
* @since BuddyPress 1.2.0
*
* @return string ISO timestamp.
*/
public static function get_last_updated() {
global $wpdb;
$bp = buddypress();
return $wpdb->get_var( "SELECT date_recorded FROM {$bp->activity->table_name} ORDER BY date_recorded DESC LIMIT 1" );
}
/**
* Get favorite count for a given user.
*
* @since BuddyPress 1.2.0
*
* @param int $user_id The ID of the user whose favorites you're counting.
* @return int $value A count of the user's favorites.
*/
public static function total_favorite_count( $user_id ) {
// Get activities from user meta.
$favorite_activity_entries = bp_get_user_meta( $user_id, 'bp_favorite_activities', true );
if ( ! empty( $favorite_activity_entries ) ) {
return count( $favorite_activity_entries );
}
// No favorites.
return 0;
}
/**
* Check whether an activity item exists with a given string content.
*
* @since BuddyPress 1.1.0
*
* @param string $content The content to filter by.
* @return int|false The ID of the first matching item if found, otherwise false.
*/
public static function check_exists_by_content( $content ) {
global $wpdb;
$bp = buddypress();
$result = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE content = %s", $content ) );
return is_numeric( $result ) ? (int) $result : false;
}
/**
* Hide all activity for a given user.
*
* @since BuddyPress 1.2.0
*
* @param int $user_id The ID of the user whose activity you want to mark hidden.
* @return mixed
*/
public static function hide_all_for_user( $user_id ) {
global $wpdb;
$bp = buddypress();
return $wpdb->get_var( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET hide_sitewide = 1 WHERE user_id = %d", $user_id ) );
}
}
Changelog
| Version | Description |
|---|---|
| BuddyPress 1.0.0 | Introduced. |
Methods
- __construct — Constructor method.
- append_comments — Append activity comments to their associated activity items.
- append_user_fullnames — Append xProfile fullnames to an activity array.
- check_exists_by_content — Check whether an activity item exists with a given string content.
- delete — Delete activity items from the database.
- delete_activity_item_comments — Delete the comments associated with a set of activity items. — deprecated
- delete_activity_meta_entries — Delete the meta entries associated with a set of activity items.
- generate_action_strings — Generate action strings for the activities located in BP_Activity_Activity::get().
- get — Get activity items, as specified by parameters.
- get_activity_comments — Get activity comments that are associated with a specific activity ID.
- get_activity_data — Convert activity IDs to activity objects, as expected in template loop.
- get_child_comments — Get child comments of an activity or activity comment.
- get_date_query_sql — Get the SQL for the 'date_query' param in BP_Activity_Activity::get().
- get_filter_sql — Create filter SQL clauses.
- get_id — Get the first activity ID that matches a set of criteria.
- get_in_operator_sql — Create SQL IN clause for filter queries.
- get_last_updated — Get the date/time of last recorded activity.
- get_meta_query_sql — Get the SQL for the 'meta_query' param in BP_Activity_Activity::get().
- get_recorded_components — Get a list of components that have recorded activity associated with them.
- get_scope_query_sql — Get the SQL for the 'scope' param in BP_Activity_Activity::get().
- get_sitewide_items_for_feed — Get sitewide activity items for use in an RSS feed.
- get_specific — In BuddyPress 1.2.x, this was used to retrieve specific activity feed items (for example, on an activity's permalink page). — deprecated
- hide_all_for_user — Hide all activity for a given user.
- populate — Populate the object with data about the specific activity item.
- prefetch_object_data — Pre-fetch data for objects associated with activity items.
- rebuild_activity_comment_tree — Rebuild nested comment tree under an activity or activity comment.
- save — Save the activity item to the database.
- total_favorite_count — Get favorite count for a given user.
Questions?
We're always happy to help with code or other questions you might have! Search our developer docs, contact support, or connect with our sales team.