Index: src/bp-activity/bp-activity-actions.php
===================================================================
--- src/bp-activity/bp-activity-actions.php
+++ src/bp-activity/bp-activity-actions.php
@@ -12,23 +12,6 @@
 // Exit if accessed directly.
 defined( 'ABSPATH' ) || exit;
 
-/**
- * Allow core components and dependent plugins to register activity actions.
- *
- * @since 1.2.0
- *
- */
-function bp_register_activity_actions() {
-
-	/**
-	 * Fires on bp_init to allow core components and dependent plugins to register activity actions.
-	 *
-	 * @since 1.2.0
-	 */
-	do_action( 'bp_register_activity_actions' );
-}
-add_action( 'bp_init', 'bp_register_activity_actions', 8 );
-
 /**
  * Catch and route requests for single activity item permalinks.
  *
@@ -428,427 +411,3 @@
 	bp_core_redirect( wp_get_referer() . '#activity-' . bp_action_variable( 0 ) );
 }
 add_action( 'bp_actions', 'bp_activity_action_remove_favorite' );
-
-/**
- * Load the sitewide activity feed.
- *
- * @since 1.0.0
- *
- * @return bool False on failure.
- */
-function bp_activity_action_sitewide_feed() {
-	$bp = buddypress();
-
-	if ( ! bp_is_activity_component() || ! bp_is_current_action( 'feed' ) || bp_is_user() || ! empty( $bp->groups->current_group ) )
-		return false;
-
-	// Setup the feed.
-	buddypress()->activity->feed = new BP_Activity_Feed( array(
-		'id'            => 'sitewide',
-
-		/* translators: Sitewide activity RSS title - "[Site Name] | Site Wide Activity" */
-		'title'         => sprintf( __( '%s | Site-Wide Activity', 'buddypress' ), bp_get_site_name() ),
-
-		'link'          => bp_get_activity_directory_permalink(),
-		'description'   => __( 'Activity feed for the entire site.', 'buddypress' ),
-		'activity_args' => 'display_comments=threaded'
-	) );
-}
-add_action( 'bp_actions', 'bp_activity_action_sitewide_feed' );
-
-/**
- * Load a user's personal activity feed.
- *
- * @since 1.0.0
- *
- * @return bool False on failure.
- */
-function bp_activity_action_personal_feed() {
-	if ( ! bp_is_user_activity() || ! bp_is_current_action( 'feed' ) ) {
-		return false;
-	}
-
-	// Setup the feed.
-	buddypress()->activity->feed = new BP_Activity_Feed( array(
-		'id'            => 'personal',
-
-		/* translators: Personal activity RSS title - "[Site Name] | [User Display Name] | Activity" */
-		'title'         => sprintf( __( '%1$s | %2$s | Activity', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
-
-		'link'          => trailingslashit( bp_displayed_user_domain() . bp_get_activity_slug() ),
-		'description'   => sprintf( __( 'Activity feed for %s.', 'buddypress' ), bp_get_displayed_user_fullname() ),
-		'activity_args' => 'user_id=' . bp_displayed_user_id()
-	) );
-}
-add_action( 'bp_actions', 'bp_activity_action_personal_feed' );
-
-/**
- * Load a user's friends' activity feed.
- *
- * @since 1.0.0
- *
- * @return bool False on failure.
- */
-function bp_activity_action_friends_feed() {
-	if ( ! bp_is_active( 'friends' ) || ! bp_is_user_activity() || ! bp_is_current_action( bp_get_friends_slug() ) || ! bp_is_action_variable( 'feed', 0 ) ) {
-		return false;
-	}
-
-	// Setup the feed.
-	buddypress()->activity->feed = new BP_Activity_Feed( array(
-		'id'            => 'friends',
-
-		/* translators: Friends activity RSS title - "[Site Name] | [User Display Name] | Friends Activity" */
-		'title'         => sprintf( __( '%1$s | %2$s | Friends Activity', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
-
-		'link'          => trailingslashit( bp_displayed_user_domain() . bp_get_activity_slug() . '/' . bp_get_friends_slug() ),
-		'description'   => sprintf( __( "Activity feed for %s's friends.", 'buddypress' ), bp_get_displayed_user_fullname() ),
-		'activity_args' => 'scope=friends'
-	) );
-}
-add_action( 'bp_actions', 'bp_activity_action_friends_feed' );
-
-/**
- * Load the activity feed for a user's groups.
- *
- * @since 1.2.0
- *
- * @return bool False on failure.
- */
-function bp_activity_action_my_groups_feed() {
-	if ( ! bp_is_active( 'groups' ) || ! bp_is_user_activity() || ! bp_is_current_action( bp_get_groups_slug() ) || ! bp_is_action_variable( 'feed', 0 ) ) {
-		return false;
-	}
-
-	// Get displayed user's group IDs.
-	$groups    = groups_get_user_groups();
-	$group_ids = implode( ',', $groups['groups'] );
-
-	// Setup the feed.
-	buddypress()->activity->feed = new BP_Activity_Feed( array(
-		'id'            => 'mygroups',
-
-		/* translators: Member groups activity RSS title - "[Site Name] | [User Display Name] | Groups Activity" */
-		'title'         => sprintf( __( '%1$s | %2$s | Group Activity', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
-
-		'link'          => trailingslashit( bp_displayed_user_domain() . bp_get_activity_slug() . '/' . bp_get_groups_slug() ),
-		'description'   => sprintf( __( "Public group activity feed of which %s is a member.", 'buddypress' ), bp_get_displayed_user_fullname() ),
-		'activity_args' => array(
-			'object'           => buddypress()->groups->id,
-			'primary_id'       => $group_ids,
-			'display_comments' => 'threaded'
-		)
-	) );
-}
-add_action( 'bp_actions', 'bp_activity_action_my_groups_feed' );
-
-/**
- * Load a user's @mentions feed.
- *
- * @since 1.2.0
- *
- * @return bool False on failure.
- */
-function bp_activity_action_mentions_feed() {
-	if ( ! bp_activity_do_mentions() ) {
-		return false;
-	}
-
-	if ( !bp_is_user_activity() || ! bp_is_current_action( 'mentions' ) || ! bp_is_action_variable( 'feed', 0 ) ) {
-		return false;
-	}
-
-	// Setup the feed.
-	buddypress()->activity->feed = new BP_Activity_Feed( array(
-		'id'            => 'mentions',
-
-		/* translators: User mentions activity RSS title - "[Site Name] | [User Display Name] | Mentions" */
-		'title'         => sprintf( __( '%1$s | %2$s | Mentions', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
-
-		'link'          => bp_displayed_user_domain() . bp_get_activity_slug() . '/mentions/',
-		'description'   => sprintf( __( "Activity feed mentioning %s.", 'buddypress' ), bp_get_displayed_user_fullname() ),
-		'activity_args' => array(
-			'search_terms' => '@' . bp_core_get_username( bp_displayed_user_id() )
-		)
-	) );
-}
-add_action( 'bp_actions', 'bp_activity_action_mentions_feed' );
-
-/**
- * Load a user's favorites feed.
- *
- * @since 1.2.0
- *
- * @return bool False on failure.
- */
-function bp_activity_action_favorites_feed() {
-	if ( ! bp_is_user_activity() || ! bp_is_current_action( 'favorites' ) || ! bp_is_action_variable( 'feed', 0 ) ) {
-		return false;
-	}
-
-	// Get displayed user's favorite activity IDs.
-	$favs = bp_activity_get_user_favorites( bp_displayed_user_id() );
-	$fav_ids = implode( ',', (array) $favs );
-
-	// Setup the feed.
-	buddypress()->activity->feed = new BP_Activity_Feed( array(
-		'id'            => 'favorites',
-
-		/* translators: User activity favorites RSS title - "[Site Name] | [User Display Name] | Favorites" */
-		'title'         => sprintf( __( '%1$s | %2$s | Favorites', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
-
-		'link'          => bp_displayed_user_domain() . bp_get_activity_slug() . '/favorites/',
-		'description'   => sprintf( __( "Activity feed of %s's favorites.", 'buddypress' ), bp_get_displayed_user_fullname() ),
-		'activity_args' => 'include=' . $fav_ids
-	) );
-}
-add_action( 'bp_actions', 'bp_activity_action_favorites_feed' );
-
-/**
- * AJAX endpoint for Suggestions API lookups.
- *
- * @since 2.1.0
- */
-function bp_ajax_get_suggestions() {
-	if ( ! bp_is_user_active() || empty( $_GET['term'] ) || empty( $_GET['type'] ) ) {
-		wp_send_json_error( 'missing_parameter' );
-		exit;
-	}
-
-	$args = array(
-		'term' => sanitize_text_field( $_GET['term'] ),
-		'type' => sanitize_text_field( $_GET['type'] ),
-	);
-
-	// Support per-Group suggestions.
-	if ( ! empty( $_GET['group-id'] ) ) {
-		$args['group_id'] = absint( $_GET['group-id'] );
-	}
-
-	$results = bp_core_get_suggestions( $args );
-
-	if ( is_wp_error( $results ) ) {
-		wp_send_json_error( $results->get_error_message() );
-		exit;
-	}
-
-	wp_send_json_success( $results );
-}
-add_action( 'wp_ajax_bp_get_suggestions', 'bp_ajax_get_suggestions' );
-
-/**
- * Detect a change in post type status, and initiate an activity update if necessary.
- *
- * @since 2.2.0
- *
- * @todo Support untrashing better.
- *
- * @param string $new_status New status for the post.
- * @param string $old_status Old status for the post.
- * @param object $post       Post data.
- */
-function bp_activity_catch_transition_post_type_status( $new_status, $old_status, $post ) {
-	if ( ! post_type_supports( $post->post_type, 'buddypress-activity' ) ) {
-		return;
-	}
-
-	// This is an edit.
-	if ( $new_status === $old_status ) {
-		// An edit of an existing post should update the existing activity item.
-		if ( $new_status == 'publish' ) {
-			$edit = bp_activity_post_type_update( $post );
-
-			// Post was never recorded into activity stream, so record it now!
-			if ( null === $edit ) {
-				bp_activity_post_type_publish( $post->ID, $post );
-			}
-
-		// Allow plugins to eventually deal with other post statuses.
-		} else {
-			/**
-			 * Fires when editing the post and the new status is not 'publish'.
-			 *
-			 * This is a variable filter that is dependent on the post type
-			 * being untrashed.
-			 *
-			 * @since 2.5.0
-			 *
-			 * @param WP_Post $post Post data.
-			 * @param string $new_status New status for the post.
-			 * @param string $old_status Old status for the post.
-			 */
-			do_action( 'bp_activity_post_type_edit_' . $post->post_type, $post, $new_status, $old_status );
-		}
-
-		return;
-	}
-
-	// Publishing a previously unpublished post.
-	if ( 'publish' === $new_status ) {
-		// Untrashing the post type - nothing here yet.
-		if ( 'trash' == $old_status ) {
-
-			/**
-			 * Fires if untrashing post in a post type.
-			 *
-			 * This is a variable filter that is dependent on the post type
-			 * being untrashed.
-			 *
-			 * @since 2.2.0
-			 *
-			 * @param WP_Post $post Post data.
-			 */
-			do_action( 'bp_activity_post_type_untrash_' . $post->post_type, $post );
-		} else {
-			// Record the post.
-			bp_activity_post_type_publish( $post->ID, $post );
-		}
-
-	// Unpublishing a previously published post.
-	} elseif ( 'publish' === $old_status ) {
-		// Some form of pending status - only remove the activity entry.
-		bp_activity_post_type_unpublish( $post->ID, $post );
-
-	// For any other cases, allow plugins to eventually deal with it.
-	} else {
-		/**
-		 * Fires when the old and the new post status are not 'publish'.
-		 *
-		 * This is a variable filter that is dependent on the post type
-		 * being untrashed.
-		 *
-		 * @since 2.5.0
-		 *
-		 * @param WP_Post $post Post data.
-		 * @param string $new_status New status for the post.
-		 * @param string $old_status Old status for the post.
-		 */
-		do_action( 'bp_activity_post_type_transition_status_' . $post->post_type, $post, $new_status, $old_status );
-	}
-}
-add_action( 'transition_post_status', 'bp_activity_catch_transition_post_type_status', 10, 3 );
-
-/**
- * When a post type comment status transition occurs, update the relevant activity's status.
- *
- * @since 2.5.0
- *
- * @param string     $new_status New comment status.
- * @param string     $old_status Previous comment status.
- * @param WP_Comment $comment Comment data.
- */
-function bp_activity_transition_post_type_comment_status( $new_status, $old_status, $comment ) {
-	$post_type = get_post_type( $comment->comment_post_ID );
-	if ( ! $post_type ) {
-		return;
-	}
-
-	// Get the post type tracking args.
-	$activity_post_object = bp_activity_get_post_type_tracking_args( $post_type );
-
-	// Bail if the activity type does not exist
-	if ( empty( $activity_post_object->comments_tracking->action_id ) ) {
-		return false;
-
-	// Set the $activity_comment_object
-	} else {
-		$activity_comment_object = $activity_post_object->comments_tracking;
-	}
-
-	// Init an empty activity ID
-	$activity_id = 0;
-
-	/**
-	 * Activity currently doesn't have any concept of a trash, or an unapproved/approved state.
-	 *
-	 * If a blog comment transitions to a "delete" or "hold" status, delete the activity item.
-	 * If a blog comment transitions to trashed, or spammed, mark the activity as spam.
-	 * If a blog comment transitions to approved (and the activity exists), mark the activity as ham.
-	 * If a blog comment transitions to unapproved (and the activity exists), mark the activity as spam.
-	 * Otherwise, record the comment into the activity stream.
-	 */
-
-	// This clause handles delete/hold.
-	if ( in_array( $new_status, array( 'delete', 'hold' ) ) ) {
-		return bp_activity_post_type_remove_comment( $comment->comment_ID, $activity_post_object );
-
-	// These clauses handle trash, spam, and un-spams.
-	} elseif ( in_array( $new_status, array( 'trash', 'spam', 'unapproved' ) ) ) {
-		$action = 'spam_activity';
-	} elseif ( 'approved' == $new_status ) {
-		$action = 'ham_activity';
-	}
-
-	// Get the activity
-	if ( bp_disable_blogforum_comments() ) {
-		$activity_id = bp_activity_get_activity_id( array(
-			'component'         => $activity_comment_object->component_id,
-			'item_id'           => get_current_blog_id(),
-			'secondary_item_id' => $comment->comment_ID,
-			'type'              => $activity_comment_object->action_id,
-		) );
-	} else {
-		$activity_id = get_comment_meta( $comment->comment_ID, 'bp_activity_comment_id', true );
-	}
-
-	/**
-	 * Leave a chance to plugins to manage activity comments differently.
-	 *
-	 * @since  2.5.0
-	 *
-	 * @param bool        $value       True to override BuddyPress management.
-	 * @param string      $post_type   The post type name.
-	 * @param int         $activity_id The post type activity (0 if not found).
-	 * @param string      $new_status  The new status of the post type comment.
-	 * @param string      $old_status  The old status of the post type comment.
-	 * @param WP_Comment  $comment Comment data.
-	 */
-	if ( true === apply_filters( 'bp_activity_pre_transition_post_type_comment_status', false, $post_type, $activity_id, $new_status, $old_status, $comment ) ) {
-		return false;
-	}
-
-	// Check activity item exists
-	if ( empty( $activity_id ) ) {
-		// If no activity exists, but the comment has been approved, record it into the activity table.
-		if ( 'approved' == $new_status ) {
-			return bp_activity_post_type_comment( $comment->comment_ID, true, $activity_post_object );
-		}
-
-		return;
-	}
-
-	// Create an activity object
-	$activity = new BP_Activity_Activity( $activity_id );
-	if ( empty( $activity->component ) ) {
-		return;
-	}
-
-	// Spam/ham the activity if it's not already in that state
-	if ( 'spam_activity' === $action && ! $activity->is_spam ) {
-		bp_activity_mark_as_spam( $activity );
-	} elseif ( 'ham_activity' == $action) {
-		bp_activity_mark_as_ham( $activity );
-	}
-
-	// Add "new_post_type_comment" to the whitelisted activity types, so that the activity's Akismet history is generated
-	$post_type_comment_action = $activity_comment_object->action_id;
-	$comment_akismet_history = function ( $activity_types ) use ( $post_type_comment_action ) {
-		$activity_types[] = $post_type_comment_action;
-
-		return $activity_types;
-	};
-	add_filter( 'bp_akismet_get_activity_types', $comment_akismet_history );
-
-	// Make sure the activity change won't edit the comment if sync is on
-	remove_action( 'bp_activity_before_save', 'bp_blogs_sync_activity_edit_to_post_comment', 20 );
-
-	// Save the updated activity
-	$activity->save();
-
-	// Restore the action
-	add_action( 'bp_activity_before_save', 'bp_blogs_sync_activity_edit_to_post_comment', 20 );
-
-	// Remove the "new_blog_comment" activity type whitelist so we don't break anything
-	remove_filter( 'bp_akismet_get_activity_types', $comment_akismet_history );
-}
-add_action( 'transition_comment_status', 'bp_activity_transition_post_type_comment_status', 10, 3 );
Index: src/bp-activity/bp-activity-feeds.php
new file mode 100644
===================================================================
--- /dev/null
+++ src/bp-activity/bp-activity-feeds.php
@@ -0,0 +1,183 @@
+<?php
+/**
+ * BuddyPress Activity Feeds.
+ *
+ * @package BuddyPress
+ * @subpackage ActivityFeeds
+ * @since 2.9.0
+ */
+
+/**
+ * Load the sitewide activity feed.
+ *
+ * @since 1.0.0
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_sitewide_feed() {
+	$bp = buddypress();
+
+	if ( ! bp_is_activity_component() || ! bp_is_current_action( 'feed' ) || bp_is_user() || ! empty( $bp->groups->current_group ) )
+		return false;
+
+	// Setup the feed.
+	buddypress()->activity->feed = new BP_Activity_Feed( array(
+		'id'            => 'sitewide',
+
+		/* translators: Sitewide activity RSS title - "[Site Name] | Site Wide Activity" */
+		'title'         => sprintf( __( '%s | Site-Wide Activity', 'buddypress' ), bp_get_site_name() ),
+
+		'link'          => bp_get_activity_directory_permalink(),
+		'description'   => __( 'Activity feed for the entire site.', 'buddypress' ),
+		'activity_args' => 'display_comments=threaded'
+	) );
+}
+add_action( 'bp_actions', 'bp_activity_action_sitewide_feed' );
+
+/**
+ * Load a user's personal activity feed.
+ *
+ * @since 1.0.0
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_personal_feed() {
+	if ( ! bp_is_user_activity() || ! bp_is_current_action( 'feed' ) ) {
+		return false;
+	}
+
+	// Setup the feed.
+	buddypress()->activity->feed = new BP_Activity_Feed( array(
+		'id'            => 'personal',
+
+		/* translators: Personal activity RSS title - "[Site Name] | [User Display Name] | Activity" */
+		'title'         => sprintf( __( '%1$s | %2$s | Activity', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
+
+		'link'          => trailingslashit( bp_displayed_user_domain() . bp_get_activity_slug() ),
+		'description'   => sprintf( __( 'Activity feed for %s.', 'buddypress' ), bp_get_displayed_user_fullname() ),
+		'activity_args' => 'user_id=' . bp_displayed_user_id()
+	) );
+}
+add_action( 'bp_actions', 'bp_activity_action_personal_feed' );
+
+/**
+ * Load a user's friends' activity feed.
+ *
+ * @since 1.0.0
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_friends_feed() {
+	if ( ! bp_is_active( 'friends' ) || ! bp_is_user_activity() || ! bp_is_current_action( bp_get_friends_slug() ) || ! bp_is_action_variable( 'feed', 0 ) ) {
+		return false;
+	}
+
+	// Setup the feed.
+	buddypress()->activity->feed = new BP_Activity_Feed( array(
+		'id'            => 'friends',
+
+		/* translators: Friends activity RSS title - "[Site Name] | [User Display Name] | Friends Activity" */
+		'title'         => sprintf( __( '%1$s | %2$s | Friends Activity', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
+
+		'link'          => trailingslashit( bp_displayed_user_domain() . bp_get_activity_slug() . '/' . bp_get_friends_slug() ),
+		'description'   => sprintf( __( "Activity feed for %s's friends.", 'buddypress' ), bp_get_displayed_user_fullname() ),
+		'activity_args' => 'scope=friends'
+	) );
+}
+add_action( 'bp_actions', 'bp_activity_action_friends_feed' );
+
+/**
+ * Load the activity feed for a user's groups.
+ *
+ * @since 1.2.0
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_my_groups_feed() {
+	if ( ! bp_is_active( 'groups' ) || ! bp_is_user_activity() || ! bp_is_current_action( bp_get_groups_slug() ) || ! bp_is_action_variable( 'feed', 0 ) ) {
+		return false;
+	}
+
+	// Get displayed user's group IDs.
+	$groups    = groups_get_user_groups();
+	$group_ids = implode( ',', $groups['groups'] );
+
+	// Setup the feed.
+	buddypress()->activity->feed = new BP_Activity_Feed( array(
+		'id'            => 'mygroups',
+
+		/* translators: Member groups activity RSS title - "[Site Name] | [User Display Name] | Groups Activity" */
+		'title'         => sprintf( __( '%1$s | %2$s | Group Activity', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
+
+		'link'          => trailingslashit( bp_displayed_user_domain() . bp_get_activity_slug() . '/' . bp_get_groups_slug() ),
+		'description'   => sprintf( __( "Public group activity feed of which %s is a member.", 'buddypress' ), bp_get_displayed_user_fullname() ),
+		'activity_args' => array(
+			'object'           => buddypress()->groups->id,
+			'primary_id'       => $group_ids,
+			'display_comments' => 'threaded'
+		)
+	) );
+}
+add_action( 'bp_actions', 'bp_activity_action_my_groups_feed' );
+
+/**
+ * Load a user's @mentions feed.
+ *
+ * @since 1.2.0
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_mentions_feed() {
+	if ( ! bp_activity_do_mentions() ) {
+		return false;
+	}
+
+	if ( !bp_is_user_activity() || ! bp_is_current_action( 'mentions' ) || ! bp_is_action_variable( 'feed', 0 ) ) {
+		return false;
+	}
+
+	// Setup the feed.
+	buddypress()->activity->feed = new BP_Activity_Feed( array(
+		'id'            => 'mentions',
+
+		/* translators: User mentions activity RSS title - "[Site Name] | [User Display Name] | Mentions" */
+		'title'         => sprintf( __( '%1$s | %2$s | Mentions', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
+
+		'link'          => bp_displayed_user_domain() . bp_get_activity_slug() . '/mentions/',
+		'description'   => sprintf( __( "Activity feed mentioning %s.", 'buddypress' ), bp_get_displayed_user_fullname() ),
+		'activity_args' => array(
+			'search_terms' => '@' . bp_core_get_username( bp_displayed_user_id() )
+		)
+	) );
+}
+add_action( 'bp_actions', 'bp_activity_action_mentions_feed' );
+
+/**
+ * Load a user's favorites feed.
+ *
+ * @since 1.2.0
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_favorites_feed() {
+	if ( ! bp_is_user_activity() || ! bp_is_current_action( 'favorites' ) || ! bp_is_action_variable( 'feed', 0 ) ) {
+		return false;
+	}
+
+	// Get displayed user's favorite activity IDs.
+	$favs = bp_activity_get_user_favorites( bp_displayed_user_id() );
+	$fav_ids = implode( ',', (array) $favs );
+
+	// Setup the feed.
+	buddypress()->activity->feed = new BP_Activity_Feed( array(
+		'id'            => 'favorites',
+
+		/* translators: User activity favorites RSS title - "[Site Name] | [User Display Name] | Favorites" */
+		'title'         => sprintf( __( '%1$s | %2$s | Favorites', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
+
+		'link'          => bp_displayed_user_domain() . bp_get_activity_slug() . '/favorites/',
+		'description'   => sprintf( __( "Activity feed of %s's favorites.", 'buddypress' ), bp_get_displayed_user_fullname() ),
+		'activity_args' => 'include=' . $fav_ids
+	) );
+}
+add_action( 'bp_actions', 'bp_activity_action_favorites_feed' );
Index: src/bp-activity/bp-activity-functions.php
===================================================================
--- src/bp-activity/bp-activity-functions.php
+++ src/bp-activity/bp-activity-functions.php
@@ -1418,6 +1418,23 @@
 }
 add_action( 'bp_make_ham_user', 'bp_activity_ham_all_user_data' );
 
+/**
+ * Allow core components and dependent plugins to register activity actions.
+ *
+ * @since 1.2.0
+ *
+ */
+function bp_register_activity_actions() {
+
+	/**
+	 * Fires on bp_init to allow core components and dependent plugins to register activity actions.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_register_activity_actions' );
+}
+add_action( 'bp_init', 'bp_register_activity_actions', 8 );
+
 /**
  * Register the activity stream actions for updates.
  *
@@ -3891,3 +3908,253 @@
 	 */
 	return (bool) apply_filters( 'bp_activity_do_heartbeat', $retval );
 }
+
+
+/**
+ * AJAX endpoint for Suggestions API lookups.
+ *
+ * @since 2.1.0
+ */
+function bp_ajax_get_suggestions() {
+	if ( ! bp_is_user_active() || empty( $_GET['term'] ) || empty( $_GET['type'] ) ) {
+		wp_send_json_error( 'missing_parameter' );
+		exit;
+	}
+
+	$args = array(
+		'term' => sanitize_text_field( $_GET['term'] ),
+		'type' => sanitize_text_field( $_GET['type'] ),
+	);
+
+	// Support per-Group suggestions.
+	if ( ! empty( $_GET['group-id'] ) ) {
+		$args['group_id'] = absint( $_GET['group-id'] );
+	}
+
+	$results = bp_core_get_suggestions( $args );
+
+	if ( is_wp_error( $results ) ) {
+		wp_send_json_error( $results->get_error_message() );
+		exit;
+	}
+
+	wp_send_json_success( $results );
+}
+add_action( 'wp_ajax_bp_get_suggestions', 'bp_ajax_get_suggestions' );
+
+/**
+ * Detect a change in post type status, and initiate an activity update if necessary.
+ *
+ * @since 2.2.0
+ *
+ * @todo Support untrashing better.
+ *
+ * @param string $new_status New status for the post.
+ * @param string $old_status Old status for the post.
+ * @param object $post       Post data.
+ */
+function bp_activity_catch_transition_post_type_status( $new_status, $old_status, $post ) {
+	if ( ! post_type_supports( $post->post_type, 'buddypress-activity' ) ) {
+		return;
+	}
+
+	// This is an edit.
+	if ( $new_status === $old_status ) {
+		// An edit of an existing post should update the existing activity item.
+		if ( $new_status == 'publish' ) {
+			$edit = bp_activity_post_type_update( $post );
+
+			// Post was never recorded into activity stream, so record it now!
+			if ( null === $edit ) {
+				bp_activity_post_type_publish( $post->ID, $post );
+			}
+
+		// Allow plugins to eventually deal with other post statuses.
+		} else {
+			/**
+			 * Fires when editing the post and the new status is not 'publish'.
+			 *
+			 * This is a variable filter that is dependent on the post type
+			 * being untrashed.
+			 *
+			 * @since 2.5.0
+			 *
+			 * @param WP_Post $post Post data.
+			 * @param string $new_status New status for the post.
+			 * @param string $old_status Old status for the post.
+			 */
+			do_action( 'bp_activity_post_type_edit_' . $post->post_type, $post, $new_status, $old_status );
+		}
+
+		return;
+	}
+
+	// Publishing a previously unpublished post.
+	if ( 'publish' === $new_status ) {
+		// Untrashing the post type - nothing here yet.
+		if ( 'trash' == $old_status ) {
+
+			/**
+			 * Fires if untrashing post in a post type.
+			 *
+			 * This is a variable filter that is dependent on the post type
+			 * being untrashed.
+			 *
+			 * @since 2.2.0
+			 *
+			 * @param WP_Post $post Post data.
+			 */
+			do_action( 'bp_activity_post_type_untrash_' . $post->post_type, $post );
+		} else {
+			// Record the post.
+			bp_activity_post_type_publish( $post->ID, $post );
+		}
+
+	// Unpublishing a previously published post.
+	} elseif ( 'publish' === $old_status ) {
+		// Some form of pending status - only remove the activity entry.
+		bp_activity_post_type_unpublish( $post->ID, $post );
+
+	// For any other cases, allow plugins to eventually deal with it.
+	} else {
+		/**
+		 * Fires when the old and the new post status are not 'publish'.
+		 *
+		 * This is a variable filter that is dependent on the post type
+		 * being untrashed.
+		 *
+		 * @since 2.5.0
+		 *
+		 * @param WP_Post $post Post data.
+		 * @param string $new_status New status for the post.
+		 * @param string $old_status Old status for the post.
+		 */
+		do_action( 'bp_activity_post_type_transition_status_' . $post->post_type, $post, $new_status, $old_status );
+	}
+}
+add_action( 'transition_post_status', 'bp_activity_catch_transition_post_type_status', 10, 3 );
+
+/**
+ * When a post type comment status transition occurs, update the relevant activity's status.
+ *
+ * @since 2.5.0
+ *
+ * @param string     $new_status New comment status.
+ * @param string     $old_status Previous comment status.
+ * @param WP_Comment $comment Comment data.
+ */
+function bp_activity_transition_post_type_comment_status( $new_status, $old_status, $comment ) {
+	$post_type = get_post_type( $comment->comment_post_ID );
+	if ( ! $post_type ) {
+		return;
+	}
+
+	// Get the post type tracking args.
+	$activity_post_object = bp_activity_get_post_type_tracking_args( $post_type );
+
+	// Bail if the activity type does not exist
+	if ( empty( $activity_post_object->comments_tracking->action_id ) ) {
+		return false;
+
+	// Set the $activity_comment_object
+	} else {
+		$activity_comment_object = $activity_post_object->comments_tracking;
+	}
+
+	// Init an empty activity ID
+	$activity_id = 0;
+
+	/**
+	 * Activity currently doesn't have any concept of a trash, or an unapproved/approved state.
+	 *
+	 * If a blog comment transitions to a "delete" or "hold" status, delete the activity item.
+	 * If a blog comment transitions to trashed, or spammed, mark the activity as spam.
+	 * If a blog comment transitions to approved (and the activity exists), mark the activity as ham.
+	 * If a blog comment transitions to unapproved (and the activity exists), mark the activity as spam.
+	 * Otherwise, record the comment into the activity stream.
+	 */
+
+	// This clause handles delete/hold.
+	if ( in_array( $new_status, array( 'delete', 'hold' ) ) ) {
+		return bp_activity_post_type_remove_comment( $comment->comment_ID, $activity_post_object );
+
+	// These clauses handle trash, spam, and un-spams.
+	} elseif ( in_array( $new_status, array( 'trash', 'spam', 'unapproved' ) ) ) {
+		$action = 'spam_activity';
+	} elseif ( 'approved' == $new_status ) {
+		$action = 'ham_activity';
+	}
+
+	// Get the activity
+	if ( bp_disable_blogforum_comments() ) {
+		$activity_id = bp_activity_get_activity_id( array(
+			'component'         => $activity_comment_object->component_id,
+			'item_id'           => get_current_blog_id(),
+			'secondary_item_id' => $comment->comment_ID,
+			'type'              => $activity_comment_object->action_id,
+		) );
+	} else {
+		$activity_id = get_comment_meta( $comment->comment_ID, 'bp_activity_comment_id', true );
+	}
+
+	/**
+	 * Leave a chance to plugins to manage activity comments differently.
+	 *
+	 * @since  2.5.0
+	 *
+	 * @param bool        $value       True to override BuddyPress management.
+	 * @param string      $post_type   The post type name.
+	 * @param int         $activity_id The post type activity (0 if not found).
+	 * @param string      $new_status  The new status of the post type comment.
+	 * @param string      $old_status  The old status of the post type comment.
+	 * @param WP_Comment  $comment Comment data.
+	 */
+	if ( true === apply_filters( 'bp_activity_pre_transition_post_type_comment_status', false, $post_type, $activity_id, $new_status, $old_status, $comment ) ) {
+		return false;
+	}
+
+	// Check activity item exists
+	if ( empty( $activity_id ) ) {
+		// If no activity exists, but the comment has been approved, record it into the activity table.
+		if ( 'approved' == $new_status ) {
+			return bp_activity_post_type_comment( $comment->comment_ID, true, $activity_post_object );
+		}
+
+		return;
+	}
+
+	// Create an activity object
+	$activity = new BP_Activity_Activity( $activity_id );
+	if ( empty( $activity->component ) ) {
+		return;
+	}
+
+	// Spam/ham the activity if it's not already in that state
+	if ( 'spam_activity' === $action && ! $activity->is_spam ) {
+		bp_activity_mark_as_spam( $activity );
+	} elseif ( 'ham_activity' == $action) {
+		bp_activity_mark_as_ham( $activity );
+	}
+
+	// Add "new_post_type_comment" to the whitelisted activity types, so that the activity's Akismet history is generated
+	$post_type_comment_action = $activity_comment_object->action_id;
+	$comment_akismet_history = function ( $activity_types ) use ( $post_type_comment_action ) {
+		$activity_types[] = $post_type_comment_action;
+
+		return $activity_types;
+	};
+	add_filter( 'bp_akismet_get_activity_types', $comment_akismet_history );
+
+	// Make sure the activity change won't edit the comment if sync is on
+	remove_action( 'bp_activity_before_save', 'bp_blogs_sync_activity_edit_to_post_comment', 20 );
+
+	// Save the updated activity
+	$activity->save();
+
+	// Restore the action
+	add_action( 'bp_activity_before_save', 'bp_blogs_sync_activity_edit_to_post_comment', 20 );
+
+	// Remove the "new_blog_comment" activity type whitelist so we don't break anything
+	remove_filter( 'bp_akismet_get_activity_types', $comment_akismet_history );
+}
+add_action( 'transition_comment_status', 'bp_activity_transition_post_type_comment_status', 10, 3 );
Index: src/bp-activity/bp-activity-screens.php
===================================================================
--- src/bp-activity/bp-activity-screens.php
+++ src/bp-activity/bp-activity-screens.php
@@ -260,10 +260,7 @@
 			$url = bp_loggedin_user_domain();
 
 		} else {
-			$url = sprintf(
-				wp_login_url( 'wp-login.php?redirect_to=%s' ),
-				esc_url_raw( bp_activity_get_permalink( $action ) )
-			);
+			$url = wp_login_url( bp_activity_get_permalink( $action ) );
 		}
 
 		bp_core_redirect( $url );
@@ -283,80 +280,6 @@
 }
 add_action( 'bp_screens', 'bp_activity_screen_single_activity_permalink' );
 
-/**
- * Add activity notifications settings to the notifications settings page.
- *
- * @since 1.2.0
- *
- */
-function bp_activity_screen_notification_settings() {
-
-	if ( bp_activity_do_mentions() ) {
-		if ( ! $mention = bp_get_user_meta( bp_displayed_user_id(), 'notification_activity_new_mention', true ) ) {
-			$mention = 'yes';
-		}
-	}
-
-	if ( ! $reply = bp_get_user_meta( bp_displayed_user_id(), 'notification_activity_new_reply', true ) ) {
-		$reply = 'yes';
-	}
-
-	?>
-
-	<table class="notification-settings" id="activity-notification-settings">
-		<thead>
-			<tr>
-				<th class="icon">&nbsp;</th>
-				<th class="title"><?php _e( 'Activity', 'buddypress' ) ?></th>
-				<th class="yes"><?php _e( 'Yes', 'buddypress' ) ?></th>
-				<th class="no"><?php _e( 'No', 'buddypress' )?></th>
-			</tr>
-		</thead>
-
-		<tbody>
-			<?php if ( bp_activity_do_mentions() ) : ?>
-				<tr id="activity-notification-settings-mentions">
-					<td>&nbsp;</td>
-					<td><?php printf( __( 'A member mentions you in an update using "@%s"', 'buddypress' ), bp_core_get_username( bp_displayed_user_id() ) ) ?></td>
-					<td class="yes"><input type="radio" name="notifications[notification_activity_new_mention]" id="notification-activity-new-mention-yes" value="yes" <?php checked( $mention, 'yes', true ) ?>/><label for="notification-activity-new-mention-yes" class="bp-screen-reader-text"><?php
-						/* translators: accessibility text */
-						_e( 'Yes, send email', 'buddypress' );
-					?></label></td>
-					<td class="no"><input type="radio" name="notifications[notification_activity_new_mention]" id="notification-activity-new-mention-no" value="no" <?php checked( $mention, 'no', true ) ?>/><label for="notification-activity-new-mention-no" class="bp-screen-reader-text"><?php
-						/* translators: accessibility text */
-						_e( 'No, do not send email', 'buddypress' );
-					?></label></td>
-				</tr>
-			<?php endif; ?>
-
-			<tr id="activity-notification-settings-replies">
-				<td>&nbsp;</td>
-				<td><?php _e( "A member replies to an update or comment you've posted", 'buddypress' ) ?></td>
-				<td class="yes"><input type="radio" name="notifications[notification_activity_new_reply]" id="notification-activity-new-reply-yes" value="yes" <?php checked( $reply, 'yes', true ) ?>/><label for="notification-activity-new-reply-yes" class="bp-screen-reader-text"><?php
-					/* translators: accessibility text */
-					_e( 'Yes, send email', 'buddypress' );
-				?></label></td>
-				<td class="no"><input type="radio" name="notifications[notification_activity_new_reply]" id="notification-activity-new-reply-no" value="no" <?php checked( $reply, 'no', true ) ?>/><label for="notification-activity-new-reply-no" class="bp-screen-reader-text"><?php
-					/* translators: accessibility text */
-					_e( 'No, do not send email', 'buddypress' );
-				?></label></td>
-			</tr>
-
-			<?php
-
-			/**
-			 * Fires inside the closing </tbody> tag for activity screen notification settings.
-			 *
-			 * @since 1.2.0
-			 */
-			do_action( 'bp_activity_screen_notification_settings' ) ?>
-		</tbody>
-	</table>
-
-<?php
-}
-add_action( 'bp_notification_settings', 'bp_activity_screen_notification_settings', 1 );
-
 /** Theme Compatibility *******************************************************/
 
 new BP_Activity_Theme_Compat();
Index: src/bp-activity/bp-activity-settings.php
new file mode 100644
===================================================================
--- /dev/null
+++ src/bp-activity/bp-activity-settings.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * BuddyPress Activity Settings.
+ *
+ * @package BuddyPress
+ * @subpackage ActivitySettings
+ * @since 2.9.0
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Add activity notifications settings to the notifications settings page.
+ *
+ * @since 1.2.0
+ *
+ */
+function bp_activity_screen_notification_settings() {
+
+	if ( bp_activity_do_mentions() ) {
+		if ( ! $mention = bp_get_user_meta( bp_displayed_user_id(), 'notification_activity_new_mention', true ) ) {
+			$mention = 'yes';
+		}
+	}
+
+	if ( ! $reply = bp_get_user_meta( bp_displayed_user_id(), 'notification_activity_new_reply', true ) ) {
+		$reply = 'yes';
+	}
+
+	?>
+
+	<table class="notification-settings" id="activity-notification-settings">
+		<thead>
+			<tr>
+				<th class="icon">&nbsp;</th>
+				<th class="title"><?php _e( 'Activity', 'buddypress' ) ?></th>
+				<th class="yes"><?php _e( 'Yes', 'buddypress' ) ?></th>
+				<th class="no"><?php _e( 'No', 'buddypress' )?></th>
+			</tr>
+		</thead>
+
+		<tbody>
+			<?php if ( bp_activity_do_mentions() ) : ?>
+				<tr id="activity-notification-settings-mentions">
+					<td>&nbsp;</td>
+					<td><?php printf( __( 'A member mentions you in an update using "@%s"', 'buddypress' ), bp_core_get_username( bp_displayed_user_id() ) ) ?></td>
+					<td class="yes"><input type="radio" name="notifications[notification_activity_new_mention]" id="notification-activity-new-mention-yes" value="yes" <?php checked( $mention, 'yes', true ) ?>/><label for="notification-activity-new-mention-yes" class="bp-screen-reader-text"><?php
+						/* translators: accessibility text */
+						_e( 'Yes, send email', 'buddypress' );
+					?></label></td>
+					<td class="no"><input type="radio" name="notifications[notification_activity_new_mention]" id="notification-activity-new-mention-no" value="no" <?php checked( $mention, 'no', true ) ?>/><label for="notification-activity-new-mention-no" class="bp-screen-reader-text"><?php
+						/* translators: accessibility text */
+						_e( 'No, do not send email', 'buddypress' );
+					?></label></td>
+				</tr>
+			<?php endif; ?>
+
+			<tr id="activity-notification-settings-replies">
+				<td>&nbsp;</td>
+				<td><?php _e( "A member replies to an update or comment you've posted", 'buddypress' ) ?></td>
+				<td class="yes"><input type="radio" name="notifications[notification_activity_new_reply]" id="notification-activity-new-reply-yes" value="yes" <?php checked( $reply, 'yes', true ) ?>/><label for="notification-activity-new-reply-yes" class="bp-screen-reader-text"><?php
+					/* translators: accessibility text */
+					_e( 'Yes, send email', 'buddypress' );
+				?></label></td>
+				<td class="no"><input type="radio" name="notifications[notification_activity_new_reply]" id="notification-activity-new-reply-no" value="no" <?php checked( $reply, 'no', true ) ?>/><label for="notification-activity-new-reply-no" class="bp-screen-reader-text"><?php
+					/* translators: accessibility text */
+					_e( 'No, do not send email', 'buddypress' );
+				?></label></td>
+			</tr>
+
+			<?php
+
+			/**
+			 * Fires inside the closing </tbody> tag for activity screen notification settings.
+			 *
+			 * @since 1.2.0
+			 */
+			do_action( 'bp_activity_screen_notification_settings' ) ?>
+		</tbody>
+	</table>
+
+<?php
+}
+add_action( 'bp_notification_settings', 'bp_activity_screen_notification_settings', 1 );
\ No newline at end of file
Index: src/bp-activity/classes/class-bp-activity-component.php
===================================================================
--- src/bp-activity/classes/class-bp-activity-component.php
+++ src/bp-activity/classes/class-bp-activity-component.php
@@ -51,8 +51,6 @@
 		// Files to include.
 		$includes = array(
 			'cssjs',
-			'actions',
-			'screens',
 			'filters',
 			'adminbar',
 			'template',
@@ -85,6 +83,35 @@
 		parent::includes( $includes );
 	}
 
+	/**
+	 * Late includes method.
+	 *
+	 * Only load up certain code when on specific pages.
+	 *
+	 * @since 3.0.0
+	 */
+	public function late_includes() {
+		/*
+		 * Laod activity action and screen code if PHPUnit isn't running.
+		 *
+		 * For PHPUnit, we load these files in tests/phpunit/includes/install.php.
+		 */
+		if ( bp_is_current_component( 'activity' ) && ! defined( 'BP_TESTS_DIR' ) ) {
+			require $this->path . 'bp-activity/bp-activity-actions.php';
+			require $this->path . 'bp-activity/bp-activity-screens.php';
+		}
+
+		// Activity notifications HTML table.
+		if ( bp_is_user_settings_notifications() ) {
+			require $this->path . 'bp-activity/bp-activity-settings.php';
+		}
+
+		// RSS feeds.
+		if ( bp_is_current_action( 'feed' ) || bp_is_action_variable( 'feed', 0 ) ) {
+			require $this->path . 'bp-activity/bp-activity-feeds.php';
+		}
+	}
+
 	/**
 	 * Set up component global variables.
 	 *
Index: src/bp-core/bp-core-actions.php
===================================================================
--- src/bp-core/bp-core-actions.php
+++ src/bp-core/bp-core-actions.php
@@ -85,6 +85,14 @@
  */
 add_action( 'bp_register_taxonomies', 'bp_register_member_types' );
 
+/**
+ * Late includes.
+ *
+ * Run after the canonical stack is setup to allow for conditional includes
+ * on certain pages.
+ */
+add_action( 'bp_setup_canonical_stack', 'bp_late_include', 20 );
+
 /**
  * The bp_template_redirect hook - Attached to 'template_redirect' above.
  *
Index: src/bp-core/bp-core-dependency.php
===================================================================
--- src/bp-core/bp-core-dependency.php
+++ src/bp-core/bp-core-dependency.php
@@ -33,6 +33,23 @@
 	do_action( 'bp_include' );
 }
 
+/**
+ * Fire the 'bp_late_include' action for loading conditional files.
+ *
+ * @since 3.0.0
+ */
+function bp_late_include() {
+
+	/**
+	 * Fires the 'bp_late_include' action.
+	 *
+	 * Allow for conditional includes on certain pages.
+	 *
+	 * @since 3.0.0
+	 */
+	do_action( 'bp_late_include' );
+}
+
 /**
  * Fire the 'bp_setup_components' action, where plugins should initialize components.
  *
Index: src/bp-core/classes/class-bp-component.php
===================================================================
--- src/bp-core/classes/class-bp-component.php
+++ src/bp-core/classes/class-bp-component.php
@@ -393,6 +393,17 @@
 		do_action( 'bp_' . $this->id . '_includes' );
 	}
 
+	/**
+	 * Late includes method.
+	 *
+	 * Components should include files here only on specific pages using
+	 * conditionals such as {@link bp_is_current_component()}. Intentionally left
+	 * empty.
+	 *
+	 * @since 3.0.0
+	 */
+	public function late_includes() {}
+
 	/**
 	 * Set up the actions.
 	 *
@@ -414,6 +425,9 @@
 		// extending this base class.
 		add_action( 'bp_include',                array( $this, 'includes'               ), 8 );
 
+		// Load files conditionally, based on certain pages.
+		add_action( 'bp_late_include',           array( $this, 'late_includes'          ) );
+
 		// Setup navigation.
 		add_action( 'bp_setup_nav',              array( $this, 'setup_nav'              ), 10 );
 
Index: tests/phpunit/includes/loader.php
===================================================================
--- tests/phpunit/includes/loader.php
+++ tests/phpunit/includes/loader.php
@@ -13,3 +13,14 @@
 	return 'BP_UnitTest_Mailer';
 }
 tests_add_filter( 'bp_send_email_delivery_class', '_bp_mock_mailer' );
+
+/**
+ * Load up activity action and screen code.
+ *
+ * In BuddyPress, this is loaded conditionally, but PHPUnit needs all files
+ * loaded at the same time to prevent weird load order issues.
+ */
+add_action( 'bp_activity_includes', function() {
+	require buddypress()->plugin_dir . 'bp-activity/bp-activity-actions.php';
+	require buddypress()->plugin_dir . 'bp-activity/bp-activity-screens.php';
+} );
\ No newline at end of file
