type, bp_activity_get_moderated_activity_types() ) ) {
return;
}
// Send back the error so activity update fails.
// @todo This is temporary until some kind of moderation is built.
$moderate = bp_core_check_for_moderation( $activity->user_id, '', $activity->content, 'wp_error' );
if ( is_wp_error( $moderate ) ) {
$activity->errors = $moderate;
// Backpat.
$activity->component = false;
}
}
/**
* Mark the posted activity as spam, if it contains disallowed keywords.
*
* @since 7.0.0
*
* @param BP_Activity_Activity $activity The activity object to check.
*/
function bp_activity_check_disallowed_keys( $activity ) {
// Only check specific types of activity updates.
if ( ! in_array( $activity->type, bp_activity_get_moderated_activity_types() ) ) {
return;
}
// Send back the error so activity update fails.
// @todo This is temporary until some kind of trash status is built.
$disallowed = bp_core_check_for_disallowed_keys( $activity->user_id, '', $activity->content, 'wp_error' );
if ( is_wp_error( $disallowed ) ) {
$activity->errors = $disallowed;
// Backpat.
$activity->component = false;
}
}
/**
* Custom kses filtering for activity content.
*
* @since 1.1.0
*
* @param string $content The activity content.
* @return string $content Filtered activity content.
*/
function bp_activity_filter_kses( $content ) {
$activity_allowedtags = bp_get_allowedtags();
// Don't allow 'class' or 'id'.
foreach ( $activity_allowedtags as $el => &$atts ) {
unset( $atts['class'] );
unset( $atts['id'] );
}
/**
* Filters the allowed HTML tags for BuddyPress Activity content.
*
* @since 1.2.0
*
* @param array $value Array of allowed HTML tags and attributes.
*/
$activity_allowedtags = apply_filters( 'bp_activity_allowed_tags', $activity_allowedtags );
return wp_kses( $content, $activity_allowedtags );
}
/**
* Find and link @-mentioned users in the contents of a given item.
*
* @since 1.2.0
*
* @param string $content The contents of a given item.
* @param int $activity_id The activity id. Deprecated.
* @return string $content Content filtered for mentions.
*/
function bp_activity_at_name_filter( $content, $activity_id = 0 ) {
// Are mentions disabled?
if ( ! bp_activity_do_mentions() ) {
return $content;
}
// Try to find mentions.
$usernames = bp_activity_find_mentions( $content );
// No mentions? Stop now!
if ( empty( $usernames ) )
return $content;
// We don't want to link @mentions that are inside of links, so we
// temporarily remove them.
$replace_count = 0;
$replacements = array();
foreach ( $usernames as $username ) {
// Prevent @ name linking inside tags.
preg_match_all( '/()@' . $username . '.*?<\/a>)/', $content, $content_matches );
if ( ! empty( $content_matches[1] ) ) {
foreach ( $content_matches[1] as $replacement ) {
$replacements[ '#BPAN' . $replace_count ] = $replacement;
$content = str_replace( $replacement, '#BPAN' . $replace_count, $content );
$replace_count++;
}
}
}
// Linkify the mentions with the username.
foreach ( (array) $usernames as $user_id => $username ) {
$content = preg_replace( '/(@' . $username . '\b)/', "@$username", $content );
}
// Put everything back.
if ( ! empty( $replacements ) ) {
foreach ( $replacements as $placeholder => $original ) {
$content = str_replace( $placeholder, $original, $content );
}
}
// Return the content.
return $content;
}
/**
* Catch mentions in an activity item before it is saved into the database.
*
* If mentions are found, replace @mention text with user links and add our
* hook to send mention notifications after the activity item is saved.
*
* @since 1.5.0
*
* @param BP_Activity_Activity $activity Activity Object.
*/
function bp_activity_at_name_filter_updates( $activity ) {
// Are mentions disabled?
if ( ! bp_activity_do_mentions() ) {
return;
}
// If activity was marked as spam, stop the rest of this function.
if ( ! empty( $activity->is_spam ) )
return;
// Try to find mentions.
$usernames = bp_activity_find_mentions( $activity->content );
// We have mentions!
if ( ! empty( $usernames ) ) {
// Replace @mention text with userlinks.
foreach( (array) $usernames as $user_id => $username ) {
$activity->content = preg_replace( '/(@' . $username . '\b)/', "@$username", $activity->content );
}
// Add our hook to send @mention emails after the activity item is saved.
add_action( 'bp_activity_after_save', 'bp_activity_at_name_send_emails' );
// Temporary variable to avoid having to run bp_activity_find_mentions() again.
buddypress()->activity->mentioned_users = $usernames;
}
}
/**
* Sends emails and BP notifications for users @-mentioned in an activity item.
*
* @since 1.7.0
*
* @param BP_Activity_Activity $activity The BP_Activity_Activity object.
*/
function bp_activity_at_name_send_emails( $activity ) {
// Are mentions disabled?
if ( ! bp_activity_do_mentions() ) {
return;
}
$bp = buddypress();
// If our temporary variable doesn't exist, stop now.
if ( empty( $bp->activity->mentioned_users ) )
return;
// Grab our temporary variable from bp_activity_at_name_filter_updates().
$usernames = $bp->activity->mentioned_users;
// Get rid of temporary variable.
unset( $bp->activity->mentioned_users );
// Send @mentions and setup BP notifications.
foreach( (array) $usernames as $user_id => $username ) {
/**
* Filters BuddyPress' ability to send email notifications for @mentions.
*
* @since 1.6.0
* @since 2.5.0 Introduced `$user_id` and `$activity` parameters.
*
* @param bool $value Whether or not BuddyPress should send a notification to the mentioned users.
* @param array $usernames Array of users potentially notified.
* @param int $user_id ID of the current user being notified.
* @param BP_Activity_Activity $activity Activity object.
*/
if ( apply_filters( 'bp_activity_at_name_do_notifications', true, $usernames, $user_id, $activity ) ) {
bp_activity_at_message_notification( $activity->id, $user_id );
}
// Updates mention count for the user.
bp_activity_update_mention_count_for_user( $user_id, $activity->id );
}
}
/**
* Catch links in activity text so rel=nofollow can be added.
*
* @since 1.2.0
*
* @param string $text Activity text.
* @return string $text Text with rel=nofollow added to any links.
*/
function bp_activity_make_nofollow_filter( $text ) {
return preg_replace_callback( '||i', 'bp_activity_make_nofollow_filter_callback', $text );
}
/**
* Adds `rel="nofollow ugc"` to a link.
*
* @since 1.2.0 Adds the nofollow rel attribute.
* @since 7.0.0 Adds the ugc rel attribute.
*
* @param array $matches Items matched by preg_replace_callback() in bp_activity_make_nofollow_filter().
* @return string $text Link with rel=nofollow added.
*/
function bp_activity_make_nofollow_filter_callback( $matches ) {
$text = $matches[1];
// The WP `make_clickable()` formatting function is adding the rel="nofollow" attribute.
$text = str_replace( array( ' rel="nofollow"', " rel='nofollow'" ), '', $text );
return "";
}
/**
* Truncate long activity entries when viewed in activity streams.
*
* This method can only be used inside the Activity loop.
*
* @since 1.5.0
* @since 2.6.0 Added $args parameter.
*
* @param string $text The original activity entry text.
* @param array $args {
* Optional parameters. See $options argument of {@link bp_create_excerpt()}
* for all available parameters.
* }
* @return string $excerpt The truncated text.
*/
function bp_activity_truncate_entry( $text, $args = array() ) {
global $activities_template;
/**
* Provides a filter that lets you choose whether to skip this filter on a per-activity basis.
*
* @since 2.3.0
*
* @param bool $value If true, text should be checked to see if it needs truncating.
*/
$maybe_truncate_text = apply_filters(
'bp_activity_maybe_truncate_entry',
isset( $activities_template->activity->type ) && ! in_array( $activities_template->activity->type, array( 'new_blog_post', ), true )
);
// The full text of the activity update should always show on the single activity screen.
if ( empty( $args['force_truncate'] ) && ( ! $maybe_truncate_text || bp_is_single_activity() ) ) {
return $text;
}
/**
* Filters the appended text for the activity excerpt.
*
* @since 1.5.0
*
* @param string $value Internationalized "Read more" text.
*/
$append_text = apply_filters( 'bp_activity_excerpt_append_text', __( '[Read more]', 'buddypress' ) );
$excerpt_length = bp_activity_get_excerpt_length();
$args = bp_parse_args(
$args,
array(
'ending' => __( '…', 'buddypress' ),
)
);
// Run the text through the excerpt function. If it's too short, the original text will be returned.
$excerpt = bp_create_excerpt( $text, $excerpt_length, $args );
/*
* If the text returned by bp_create_excerpt() is different from the original text (ie it's
* been truncated), add the "Read More" link. Note that bp_create_excerpt() is stripping
* shortcodes, so we have strip them from the $text before the comparison.
*/
if ( strlen( $excerpt ) < strlen( strip_shortcodes( $text ) ) ) {
$id = !empty( $activities_template->activity->current_comment->id ) ? 'acomment-read-more-' . $activities_template->activity->current_comment->id : 'activity-read-more-' . bp_get_activity_id();
$excerpt = sprintf( '%1$s%4$s', $excerpt, $id, bp_get_activity_thread_permalink(), $append_text );
}
/**
* Filters the composite activity excerpt entry.
*
* @since 1.5.0
*
* @param string $excerpt Excerpt text and markup to be displayed.
* @param string $text The original activity entry text.
* @param string $append_text The final append text applied.
*/
return apply_filters( 'bp_activity_truncate_entry', $excerpt, $text, $append_text );
}
/**
* Include extra JavaScript dependencies for activity component.
*
* @since 2.0.0
*
* @param array $js_handles The original dependencies.
* @return array $js_handles The new dependencies.
*/
function bp_activity_get_js_dependencies( $js_handles = array() ) {
if ( bp_activity_do_heartbeat() ) {
$js_handles[] = 'heartbeat';
}
return $js_handles;
}
add_filter( 'bp_core_get_js_dependencies', 'bp_activity_get_js_dependencies', 10, 1 );
/**
* Add a just-posted classes to the most recent activity item.
*
* We use these classes to avoid pagination issues when items are loaded
* dynamically into the activity stream.
*
* @since 2.0.0
*
* @param string $classes Array of classes for most recent activity item.
* @return string $classes
*/
function bp_activity_newest_class( $classes = '' ) {
$bp = buddypress();
if ( ! empty( $bp->activity->last_recorded ) && $bp->activity->last_recorded == bp_get_activity_date_recorded() ) {
$classes .= ' new-update';
}
$classes .= ' just-posted';
return $classes;
}
/**
* Check if Activity Heartbeat feature i on to add a timestamp class.
*
* @since 2.0.0
*
* @param string $classes Array of classes for timestamp.
* @return string $classes
*/
function bp_activity_timestamp_class( $classes = '' ) {
if ( ! bp_activity_do_heartbeat() ) {
return $classes;
}
$activity_date = bp_get_activity_date_recorded();
if ( empty( $activity_date ) ) {
return $classes;
}
$classes .= ' date-recorded-' . strtotime( $activity_date );
return $classes;
}
add_filter( 'bp_get_activity_css_class', 'bp_activity_timestamp_class', 9, 1 );
/**
* Use WordPress Heartbeat API to check for latest activity update.
*
* @since 2.0.0
*
* @param array $response Array containing Heartbeat API response.
* @param array $data Array containing data for Heartbeat API response.
* @return array $response
*/
function bp_activity_heartbeat_last_recorded( $response = array(), $data = array() ) {
if ( empty( $data['bp_activity_last_recorded'] ) ) {
return $response;
}
// Use the querystring argument stored in the cookie (to preserve
// filters), but force the offset to get only new items.
$activity_latest_args = bp_parse_args(
bp_ajax_querystring( 'activity' ),
array( 'since' => date( 'Y-m-d H:i:s', $data['bp_activity_last_recorded'] ) ),
'activity_latest_args'
);
if ( ! empty( $data['bp_activity_last_recorded_search_terms'] ) && empty( $activity_latest_args['search_terms'] ) ) {
$activity_latest_args['search_terms'] = addslashes( $data['bp_activity_last_recorded_search_terms'] );
}
$newest_activities = array();
$last_activity_recorded = 0;
// Temporarily add a just-posted class for new activity items.
add_filter( 'bp_get_activity_css_class', 'bp_activity_newest_class', 10, 1 );
ob_start();
if ( bp_has_activities( $activity_latest_args ) ) {
while ( bp_activities() ) {
bp_the_activity();
$atime = strtotime( bp_get_activity_date_recorded() );
if ( $last_activity_recorded < $atime ) {
$last_activity_recorded = $atime;
}
bp_get_template_part( 'activity/entry' );
}
}
$newest_activities['activities'] = ob_get_contents();
$newest_activities['last_recorded'] = $last_activity_recorded;
ob_end_clean();
// Remove the temporary filter.
remove_filter( 'bp_get_activity_css_class', 'bp_activity_newest_class', 10 );
if ( ! empty( $newest_activities['last_recorded'] ) ) {
$response['bp_activity_newest_activities'] = $newest_activities;
}
return $response;
}
add_filter( 'heartbeat_received', 'bp_activity_heartbeat_last_recorded', 10, 2 );
add_filter( 'heartbeat_nopriv_received', 'bp_activity_heartbeat_last_recorded', 10, 2 );
/**
* Set the strings for WP HeartBeat API where needed.
*
* @since 2.0.0
*
* @param array $strings Localized strings.
* @return array $strings
*/
function bp_activity_heartbeat_strings( $strings = array() ) {
if ( ! bp_activity_do_heartbeat() ) {
return $strings;
}
$global_pulse = 0;
/**
* Filter that checks whether the global heartbeat settings already exist.
*
* @since 2.0.0
*
* @param array $value Heartbeat settings array.
*/
$heartbeat_settings = apply_filters( 'heartbeat_settings', array() );
if ( ! empty( $heartbeat_settings['interval'] ) ) {
// 'Fast' is 5.
$global_pulse = is_numeric( $heartbeat_settings['interval'] ) ? absint( $heartbeat_settings['interval'] ) : 5;
}
/**
* Filters the pulse frequency to be used for the BuddyPress Activity heartbeat.
*
* @since 2.0.0
*
* @param int $value The frequency in seconds between pulses.
*/
$bp_activity_pulse = apply_filters( 'bp_activity_heartbeat_pulse', 15 );
/**
* Use the global pulse value unless:
* a. the BP-specific value has been specifically filtered, or
* b. it doesn't exist (ie, BP will be the only one using the heartbeat,
* so we're responsible for enabling it)
*/
if ( has_filter( 'bp_activity_heartbeat_pulse' ) || empty( $global_pulse ) ) {
$pulse = $bp_activity_pulse;
} else {
$pulse = $global_pulse;
}
$strings = array_merge( $strings, array(
'newest' => __( 'Load Newest', 'buddypress' ),
'pulse' => absint( $pulse ),
) );
return $strings;
}
add_filter( 'bp_core_get_js_strings', 'bp_activity_heartbeat_strings', 10, 1 );
/** Scopes ********************************************************************/
/**
* Set up activity arguments for use with the 'just-me' scope.
*
* @since 2.2.0
*
* @param array $retval Empty array by default.
* @param array $filter Current activity arguments.
* @return array $retval
*/
function bp_activity_filter_just_me_scope( $retval = array(), $filter = array() ) {
// Determine the user_id.
if ( ! empty( $filter['user_id'] ) ) {
$user_id = $filter['user_id'];
} else {
$user_id = bp_displayed_user_id()
? bp_displayed_user_id()
: bp_loggedin_user_id();
}
// Should we show all items regardless of sitewide visibility?
$show_hidden = array();
if ( ! empty( $user_id ) && $user_id !== bp_loggedin_user_id() ) {
$show_hidden = array(
'column' => 'hide_sitewide',
'value' => 0
);
}
$retval = array(
'relation' => 'AND',
array(
'column' => 'user_id',
'value' => $user_id
),
$show_hidden,
// Overrides.
'override' => array(
'display_comments' => 'stream',
'filter' => array( 'user_id' => 0 ),
'show_hidden' => true
),
);
return $retval;
}
add_filter( 'bp_activity_set_just-me_scope_args', 'bp_activity_filter_just_me_scope', 10, 2 );
/**
* Set up activity arguments for use with the 'favorites' scope.
*
* @since 2.2.0
*
* @param array $retval Empty array by default.
* @param array $filter Current activity arguments.
* @return array $retval
*/
function bp_activity_filter_favorites_scope( $retval = array(), $filter = array() ) {
// Determine the user_id.
if ( ! empty( $filter['user_id'] ) ) {
$user_id = $filter['user_id'];
} else {
$user_id = bp_displayed_user_id()
? bp_displayed_user_id()
: bp_loggedin_user_id();
}
// Determine the favorites.
$favs = bp_activity_get_user_favorites( $user_id );
if ( empty( $favs ) ) {
$favs = array( 0 );
}
// Should we show all items regardless of sitewide visibility?
$show_hidden = array();
if ( ! empty( $user_id ) && ( $user_id !== bp_loggedin_user_id() ) ) {
$show_hidden = array(
'column' => 'hide_sitewide',
'value' => 0
);
}
$retval = array(
'relation' => 'AND',
array(
'column' => 'id',
'compare' => 'IN',
'value' => (array) $favs
),
$show_hidden,
// Overrides.
'override' => array(
'display_comments' => true,
'filter' => array( 'user_id' => 0 ),
'show_hidden' => true
),
);
return $retval;
}
add_filter( 'bp_activity_set_favorites_scope_args', 'bp_activity_filter_favorites_scope', 10, 2 );
/**
* Set up activity arguments for use with the 'favorites' scope.
*
* @since 2.2.0
*
* @param array $retval Empty array by default.
* @param array $filter Current activity arguments.
* @return array $retval
*/
function bp_activity_filter_mentions_scope( $retval = array(), $filter = array() ) {
// Are mentions disabled?
if ( ! bp_activity_do_mentions() ) {
return $retval;
}
// Determine the user_id.
if ( ! empty( $filter['user_id'] ) ) {
$user_id = $filter['user_id'];
} else {
$user_id = bp_displayed_user_id()
? bp_displayed_user_id()
: bp_loggedin_user_id();
}
// Should we show all items regardless of sitewide visibility?
$show_hidden = array();
if ( ! empty( $user_id ) && $user_id !== bp_loggedin_user_id() ) {
$show_hidden = array(
'column' => 'hide_sitewide',
'value' => 0
);
}
$retval = array(
'relation' => 'AND',
array(
'column' => 'content',
'compare' => 'LIKE',
// Start search at @ symbol and stop search at closing tag delimiter.
'value' => '@' . bp_activity_get_user_mentionname( $user_id ) . '<'
),
$show_hidden,
// Overrides.
'override' => array(
'display_comments' => 'stream',
'filter' => array( 'user_id' => 0 ),
'show_hidden' => true
),
);
return $retval;
}
add_filter( 'bp_activity_set_mentions_scope_args', 'bp_activity_filter_mentions_scope', 10, 2 );
/**
* Registers Activity personal data exporter.
*
* @since 4.0.0
* @since 5.0.0 adds an `exporter_bp_friendly_name` param to exporters.
*
* @param array $exporters An array of personal data exporters.
* @return array An array of personal data exporters.
*/
function bp_activity_register_personal_data_exporter( $exporters ) {
$exporters['buddypress-activity'] = array(
'exporter_friendly_name' => __( 'BuddyPress Activity Data', 'buddypress' ),
'callback' => 'bp_activity_personal_data_exporter',
'exporter_bp_friendly_name' => _x( 'Activity Data', 'BuddyPress Activity data exporter friendly name', 'buddypress' ),
);
return $exporters;
}