', { class: 'colspanchange', colspan: 7, text: text }));
+ tbody.append(newRow);
+ tbody.append("Some appended text.");
+ }
+ },
+ error: function ( error ) {
+ // TODO: Handle the error
+ }
+ });
+ } );
} );
diff --git a/includes/class-activity-dispatcher.php b/includes/class-activity-dispatcher.php
index 0e955e4..b21da0f 100644
--- a/includes/class-activity-dispatcher.php
+++ b/includes/class-activity-dispatcher.php
@@ -41,7 +41,7 @@ class Activity_Dispatcher {
* @return void
*/
public static function send_activity_or_announce( $wp_object, $type ) {
- // check if a migration is needed before sending new posts
+ // check if a migration is needed before sending new post
Migration::maybe_migrate();
if ( is_user_type_disabled( 'blog' ) ) {
@@ -96,7 +96,7 @@ class Activity_Dispatcher {
return;
}
- $transformer = Factory::instance()->get_transformer( $wp_object );
+ $transformer = Factory::get_transformer( $wp_object );
if ( null !== $user_id && Users::APPLICATION_USER_ID !== $user_id ) {
$transformer->change_wp_user_id( $user_id );
diff --git a/includes/class-admin.php b/includes/class-admin.php
index dca3aee..31e1c11 100644
--- a/includes/class-admin.php
+++ b/includes/class-admin.php
@@ -95,7 +95,7 @@ class Admin {
\load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/settings.php' );
break;
case 'followers':
- \load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/blog-user-followers-list.php' );
+ \load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/admin-followers-list.php' );
break;
case 'welcome':
default:
@@ -300,8 +300,8 @@ class Admin {
public static function enqueue_scripts( $hook_suffix ) {
if ( false !== strpos( $hook_suffix, 'activitypub' ) ) {
- wp_enqueue_style( 'activitypub-admin-styles', plugins_url( 'assets/css/activitypub-admin.css', ACTIVITYPUB_PLUGIN_FILE ), array(), '1.0.0' );
- wp_enqueue_script( 'activitypub-admin-styles', plugins_url( 'assets/js/activitypub-admin.js', ACTIVITYPUB_PLUGIN_FILE ), array( 'jquery' ), '1.0.0', false );
+ wp_enqueue_style( 'activitypub-admin-styles', plugins_url( 'assets/css/activitypub-admin.css', ACTIVITYPUB_PLUGIN_FILE ), array(), '1.0.1' );
+ wp_enqueue_script( 'activitypub-admin-styles', plugins_url( 'assets/js/activitypub-admin.js', ACTIVITYPUB_PLUGIN_FILE ), array( 'jquery' ), '1.0.1', false );
}
}
}
diff --git a/includes/collection/class-follow-requests.php b/includes/collection/class-follow-requests.php
new file mode 100644
index 0000000..7f75d48
--- /dev/null
+++ b/includes/collection/class-follow-requests.php
@@ -0,0 +1,53 @@
+get_results(
+ $wpdb->prepare(
+ "SELECT SQL_CALC_FOUND_ROWS follow_request.ID AS id, follow_request.post_date AS published, follow_request.guid, follow_request.post_status AS 'status', follower.post_title AS 'post_title', follower.guid AS follower_guid, follower.id AS follower_id, follower.post_modified AS follower_modified
+ FROM {$wpdb->posts} AS follow_request
+ LEFT JOIN {$wpdb->posts} AS follower ON follow_request.post_parent = follower.ID
+ LEFT JOIN {$wpdb->postmeta} AS meta ON follow_request.ID = meta.post_id
+ WHERE follow_request.post_type = 'ap_follow_request'
+ AND meta.meta_key = 'activitypub_user_id'
+ AND meta.meta_value = %s
+ ORDER BY {$orderby} {$order}
+ LIMIT %d OFFSET %d",
+ $user_id,
+ $per_page,
+ 0
+ )
+ );
+ $total_items = $wpdb->get_var("SELECT FOUND_ROWS()");
+
+ return compact( 'follow_requests', 'total_items' );
+ }
+}
diff --git a/includes/handler/class-follow.php b/includes/handler/class-follow.php
index 810680b..a14b36f 100644
--- a/includes/handler/class-follow.php
+++ b/includes/handler/class-follow.php
@@ -5,6 +5,7 @@ use Activitypub\Http;
use Activitypub\Activity\Activity;
use Activitypub\Collection\Users;
use Activitypub\Collection\Followers;
+use Activitypub\Model\Follow_Request;
/**
* Handle Follow requests
@@ -20,7 +21,7 @@ class Follow {
);
\add_action(
- 'activitypub_followers_post_follow',
+ 'activitypub_send_follow_response',
array( self::class, 'send_follow_response' ),
10,
4
@@ -50,60 +51,42 @@ class Follow {
$activity['actor']
);
- do_action(
- 'activitypub_followers_post_follow',
- $activity['actor'],
- $activity,
- $user_id,
- $follower
- );
- }
-
- /**
- * Send Accept response
- *
- * @param string $actor The Actor URL
- * @param array $object The Activity object
- * @param int $user_id The ID of the WordPress User
- * @param Activitypub\Model\Follower $follower The Follower object
- *
- * @return void
- */
- public static function send_follow_response( $actor, $object, $user_id, $follower ) {
if ( \is_wp_error( $follower ) ) {
- // it is not even possible to send a "Reject" because
+ // it is not even possible to send a "Reject" or "Accept" because
// we can not get the Remote-Inbox
return;
}
+
+ // save follow request by this follower
+ $follow_request = Follow_Request::save( $follower, $user_id, $activity['id'] );
- // only send minimal data
- $object = array_intersect_key(
- $object,
- array_flip(
- array(
- 'id',
- 'type',
- 'actor',
- 'object',
- )
- )
- );
+ if ( ! $user->get_manually_approves_followers() ) {
+ $follow_request->accept();
+ }
+
+ }
- $user = Users::get_by_id( $user_id );
-
- // get inbox
- $inbox = $follower->get_shared_inbox();
-
- // send "Accept" activity
+ /**
+ * Send Follow response
+ *
+ * @param Activitypub\Model\User $user The Target Users ActivityPub object
+ * @param Activitypub\Model\Follower $follower The Followers ActivityPub object
+ * @param array|object $object The ActivityPub follow object
+ * @param string $type The reponse object type: 'Accept' or 'Reject'
+ *
+ * @return void
+ */
+ public static function send_follow_response( $user, $follower, $object, $type ) {
+ // send activity
$activity = new Activity();
- $activity->set_type( 'Accept' );
+ $activity->set_type( $type );
$activity->set_object( $object );
$activity->set_actor( $user->get_id() );
- $activity->set_to( $actor );
- $activity->set_id( $user->get_id() . '#follow-' . \preg_replace( '~^https?://~', '', $actor ) . '-' . \time() );
+ $activity->set_to( $follower->get_id() );
+ $activity->set_id( $user->get_id() . '#accept-' . \preg_replace( '~^https?://~', '', $follower->get_id() ) . '-' . \time() );
$activity = $activity->to_json();
- Http::post( $inbox, $activity, $user_id );
+ Http::post( $follower->get_shared_inbox(), $activity, $user->get__id() );
}
}
diff --git a/includes/handler/class-undo.php b/includes/handler/class-undo.php
index 74d3dca..70d6c4e 100644
--- a/includes/handler/class-undo.php
+++ b/includes/handler/class-undo.php
@@ -2,7 +2,7 @@
namespace Activitypub\Handler;
use Activitypub\Collection\Users;
-use Activitypub\Collection\Followers;
+use Activitypub\Model\Follow_Request;
/**
* Handle Undo requests
@@ -39,9 +39,8 @@ class Undo {
return;
}
- $user_id = $user->get__id();
-
- Followers::remove_follower( $user_id, $activity['actor'] );
+ $follow_request = Follow_Request::get_from_array( $activity['object'] );
+ $follow_request->delete();
}
}
}
diff --git a/includes/model/class-follow-request.php b/includes/model/class-follow-request.php
new file mode 100644
index 0000000..be83c79
--- /dev/null
+++ b/includes/model/class-follow-request.php
@@ -0,0 +1,215 @@
+get__id() ) {
+ $follow_request->set__id( self::get_follow_request_id_by_uri( $follow_request->get_id() ) );
+ }
+ return $follow_request;
+ }
+
+ /**
+ * Retrieve the WordPress post id of the follow request by its id (URI)
+ */
+ public static function get_follow_request_id_by_uri( $uri ) {
+ global $wpdb;
+ return $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE guid=%s", esc_sql( $uri ) ) );
+ }
+
+
+ /**
+ * Check if the follow request is valid which means it fits to the already stored data.
+ *
+ * @param Follow_Request $follow_request The follow request to be checked.
+ * @return bool Whether the follow request is valid.
+ */
+ public static function is_valid( $follow_request ) {
+ if ( self::class != get_class( $follow_request ) ) {
+ return false;
+ }
+ if ( 'Follow' != $follow_request->get_type() ) {
+ return false;
+ }
+
+ $id = self::get_follow_request_id_by_uri( $follow_request->get_id() );
+ if ( ! $id || is_wp_error( $id ) ) {
+ return false;
+ }
+ if ( self::FOLLOW_REQUEST_POST_TYPE != get_post_type( $id) ) {
+ return false;
+ }
+
+ $post = get_post( $id );
+ $follower = get_post_parent( $id );
+ if ( $follower->guid != $follow_request->get_actor() ) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @param int $id
+ * @return Follow_Request $follow_request
+ */
+ public static function from_wp_id( $id ) {
+ if ( self::FOLLOW_REQUEST_POST_TYPE != get_post_type( $id ) ){
+ return;
+ }
+ $post = get_post( $id );
+
+ $follow_request = new static;
+ $follow_request->set_id( $post->guid );
+ $follow_request->set__id( $post->ID );
+ $follow_request->set_type( 'Follow' );
+
+ return $follow_request;
+ }
+
+ /**
+ * Save the current Follower-Object.
+ *
+ * @param Follower $follower
+ *
+ * @return Follow_Request|WP_Error The Follow_Request or an WP_Error.
+ */
+ public static function save( $follower, $user_id, $activity_id ) {
+ $follower_id = $follower->get__id();
+ $meta_input = array(
+ 'activitypub_user_id' => $user_id,
+ );
+
+ $args = array(
+ 'guid' => $activity_id,
+ 'post_author' => 0,
+ 'post_type' => self::FOLLOW_REQUEST_POST_TYPE,
+ 'post_status' => 'pending',
+ 'post_parent' => $follower_id,
+ 'meta_input' => $meta_input,
+ 'mime_type' => 'text/plain'
+ );
+
+ $post_id = wp_insert_post( $args );
+
+
+ return self::from_wp_id( $post_id );
+ }
+
+ /**
+ * Check if the user is allowed to handle this follow request.
+ *
+ * Usually needed for the ajax functions.
+ * @return bool Whether the user is allowed.
+ */
+ public function can_handle_follow_request() {
+ $target_actor = get_post_meta( $this->get__id(), 'activitypub_user_id');
+ if ( get_current_user_id() == $target_actor || current_user_can( 'manage_options' )) {
+ return true;
+ }
+ }
+
+ /**
+ * Reject the follow request
+ */
+ public function reject() {
+ wp_update_post(
+ array(
+ 'ID' => $this->get__id(),
+ 'post_status' => 'rejected',
+ )
+ );
+ $this->send_response( 'Reject' );
+ $this->delete();
+ }
+
+ /**
+ * Approve the follow request
+ */
+ public function approve() {
+ wp_update_post(
+ array(
+ 'ID' => $this->get__id(),
+ 'post_status' => 'approved',
+ )
+ );
+ $this->send_response( 'Accept' );
+ }
+
+ /**
+ * Delete the follow request
+ *
+ * This should only be called after it has been rejected.
+ */
+ public function delete() {
+ wp_delete_post( $this->get__id() );
+ }
+
+ /**
+ * Prepere the sending of the follow request response and hand it over to the sending handler.
+ */
+ public function send_response( $type ) {
+ $user_id = get_post_meta( $this->get__id(), 'activitypub_user_id')[0];
+ $user = Users::get_by_id( $user_id );
+
+ $follower_id = wp_get_post_parent_id( $this->get__id() );
+ $follower = Follower::init_from_cpt( get_post( $follower_id ) );
+
+ $actor = $follower->get_id();
+
+ $object = array(
+ 'id' => $this->get_id(),
+ 'type' => $this->get_type(),
+ 'actor' => $actor,
+ 'object' => $user,
+ );
+
+ do_action( 'activitypub_send_follow_response', $user, $follower, $object, $type);
+ }
+}
diff --git a/includes/model/class-follower.php b/includes/model/class-follower.php
index b2833e9..591f01a 100644
--- a/includes/model/class-follower.php
+++ b/includes/model/class-follower.php
@@ -19,7 +19,7 @@ use Activitypub\Collection\Followers;
*/
class Follower extends Actor {
/**
- * The complete Remote-Profile of the Follower
+ * The complete Remote-Profile of the Follower.
*
* @var array
*/
@@ -34,6 +34,13 @@ class Follower extends Actor {
return get_post_meta( $this->_id, 'activitypub_errors' );
}
+ /**
+ * Getter function for the internal WordPress id.
+ */
+ public function get__id() {
+ return $this->_id;
+ }
+
/**
* Get the Summary.
*
@@ -304,9 +311,9 @@ class Follower extends Actor {
/**
* Convert a Custom-Post-Type input to an Activitypub\Model\Follower.
*
- * @return string The JSON string.
+ * @param WP_Post The JSON string.
*
- * @return array Activitypub\Model\Follower
+ * @return \Activitypub\Model\Follower $object
*/
public static function init_from_cpt( $post ) {
$actor_json = get_post_meta( $post->ID, 'activitypub_actor_json', true );
diff --git a/includes/table/class-follow-requests.php b/includes/table/class-follow-requests.php
new file mode 100644
index 0000000..5cbd965
--- /dev/null
+++ b/includes/table/class-follow-requests.php
@@ -0,0 +1,290 @@
+user_id = $user_id;
+ } else {
+ $this->user_id = \get_current_user_id();
+ }
+
+ parent::__construct(
+ array(
+ 'singular' => \__( 'Follower', 'activitypub' ),
+ 'plural' => \__( 'Followers', 'activitypub' ),
+ 'ajax' => true,
+ )
+ );
+ }
+
+ public function get_columns() {
+ return array(
+ 'cb' => '',
+ 'action' => \__( 'Action', 'activitypub' ),
+ 'name' => \__( 'Name', 'activitypub' ),
+ 'url' => \__( 'URL', 'activitypub' ),
+ 'status' => \__( 'Status', 'activitypub' ),
+ 'published' => \__( 'Created', 'activitypub' ),
+ 'modified' => \__( 'Last updated', 'activitypub' ),
+ );
+ }
+
+ public function get_sortable_columns() {
+ $sortable_columns = array(
+ 'status' => array( 'status', false),
+ 'name' => array( 'name', true ),
+ 'modified' => array( 'modified', false ),
+ 'published' => array( 'published', false ),
+ );
+
+ return $sortable_columns;
+ }
+
+ public function prepare_items() {
+ $columns = $this->get_columns();
+ $hidden = array();
+
+ $this->process_action();
+ $this->_column_headers = array( $columns, $hidden, $this->get_sortable_columns() );
+
+ $page_num = $this->get_pagenum();
+ $per_page = 20;
+
+ $args = array();
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( isset( $_GET['orderby'] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $args['orderby'] = sanitize_text_field( wp_unslash( $_GET['orderby'] ) );
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( isset( $_GET['order'] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $args['order'] = sanitize_text_field( wp_unslash( $_GET['order'] ) );
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( isset( $_GET['s'] ) && isset( $_REQUEST['_wpnonce'] ) ) {
+ $nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
+ if ( wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $args['s'] = sanitize_text_field( wp_unslash( $_GET['s'] ) );
+ }
+ }
+
+ $follow_requests_with_count = FollowerRequestCollection::get_follow_requests_for_user( $this->user_id, $per_page, $page_num, $args );
+
+ $follow_requests = $follow_requests_with_count['follow_requests'];
+ $counter = $follow_requests_with_count['total_items'];
+ $this->follow_requests_count = $counter;
+
+ $this->items = array();
+ $this->set_pagination_args(
+ array(
+ 'total_items' => $counter,
+ 'total_pages' => ceil( $counter / $per_page ),
+ 'per_page' => $per_page,
+ )
+ );
+
+ foreach ( $follow_requests as $follow_request ) {
+ $item = array(
+ 'status' => esc_attr( $follow_request->status ),
+ 'name' => esc_attr( $follow_request->post_title ),
+ 'url' => esc_attr( $follow_request->follower_guid ),
+ 'guid' => esc_attr( $follow_request->guid ),
+ 'id' => esc_attr( $follow_request->id ),
+ 'published' => esc_attr( $follow_request->published ),
+ 'modified' => esc_attr( $follow_request->follower_modified ),
+ );
+
+ $this->items[] = $item;
+ }
+ }
+
+ public function get_bulk_actions() {
+ return array(
+ 'reject' => __( 'Reject', 'activitypub' ),
+ 'approve' => __( 'Approve', 'activitypub' ),
+ );
+ }
+
+ public function column_default( $item, $column_name ) {
+ if ( ! array_key_exists( $column_name, $item ) ) {
+ return __( 'None', 'activitypub' );
+ }
+ return $item[ $column_name ];
+ }
+
+ public function column_status( $item ) {
+ $status = $item['status'];
+ switch ( $status ) {
+ case 'approved':
+ $color = 'success';
+ break;
+ case 'pending':
+ $color = 'warning';
+ break;
+ case 'rejected':
+ $color = 'danger';
+ break;
+ default:
+ $color = 'warning';
+ }
+ return sprintf(
+ '%s',
+ $color,
+ ucfirst( $status )
+ );
+ }
+
+ public function column_avatar( $item ) {
+ return sprintf(
+ '',
+ $item['icon']
+ );
+ }
+
+ public function column_url( $item ) {
+ return sprintf(
+ '%s',
+ $item['url'],
+ $item['url']
+ );
+ }
+
+ public function column_cb( $item ) {
+ return sprintf( '', esc_attr( $item['id'] ) );
+ }
+
+ public function ajax_response() {
+ $follow_action = $_REQUEST['follow_action'];
+ $id = $_REQUEST['follow_request'];
+ wp_verify_nonce( $_REQUEST['_wpnonce'], "activitypub_{$follow_action}_follow_request" );
+ $follow_request = Follow_Request::from_wp_id( $id );
+ if ( $follow_request->can_handle_follow_request() ) {
+ switch ( $follow_action ) {
+ case 'approve':
+ $follow_request->approve();
+ wp_die( 'approved' );
+ case 'reject':
+ $follow_request->reject();
+ wp_die( 'rejected' );
+ case 'delete':
+ $follow_request->delete();
+ wp_die( 'deleted' );
+ }
+ }
+ return;
+ }
+
+ private static function display_follow_request_action_button( $id, $follow_action, $display = true ) {
+ $url = add_query_arg(
+ array(
+ 'follow_request' => $id,
+ 'action' => 'activitypub_handle_follow_request',
+ 'follow_action' => $follow_action,
+ '_wpnonce' => wp_create_nonce( "activitypub_{$follow_action}_follow_request" ),
+ ),
+ admin_url( 'admin-ajax.php' )
+ );
+ if ( $display ) {
+ $type = 'button';
+ } else {
+ $type = 'hidden';
+ }
+ printf(
+ '',
+ esc_attr( $type ),
+ esc_attr__( ucfirst( $follow_action ), 'activitypub' ),
+ esc_url( $url )
+ );
+ }
+
+ public function column_action($item) {
+ $status = $item['status'];
+
+ printf('
');
+
+ // TODO this can be written smarter, but at least it is readable.
+ if ( 'pending' === $status ) {
+ self::display_follow_request_action_button( $item['id'], 'approve');
+ self::display_follow_request_action_button( $item['id'], 'reject');
+ self::display_follow_request_action_button( $item['id'], 'delete', false);
+ }
+
+ if ( 'approved' === $status ) {
+ self::display_follow_request_action_button( $item['id'], 'approve', false);
+ self::display_follow_request_action_button( $item['id'], 'reject');
+ self::display_follow_request_action_button( $item['id'], 'delete', false);
+ }
+
+ if ( 'rejected' === $status ) {
+ self::display_follow_request_action_button( $item['id'], 'approve', false); // TODO: Clarify with Mobilizon
+ self::display_follow_request_action_button( $item['id'], 'reject', false);
+ self::display_follow_request_action_button( $item['id'], 'delete');
+ }
+
+ printf('
');
+ }
+
+ public function process_action() {
+ if ( ! isset( $_REQUEST['follow_requests'] ) || ! isset( $_REQUEST['_wpnonce'] ) ) {
+ return false;
+ }
+ $nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
+ if ( ! wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
+ return false;
+ }
+
+ if ( ! current_user_can( 'edit_user', $this->user_id ) ) {
+ return false;
+ }
+
+ $follow_requests = $_REQUEST['follow_requests']; // phpcs:ignore
+
+ switch ( $this->current_action() ) {
+ case 'reject':
+ if ( ! is_array( $follow_requests ) ) {
+ $follow_requests = array( $follow_requests );
+ }
+ foreach ( $follow_requests as $follow_request ) {
+ Follow_Request::from_wp_id( $follow_request )->reject();
+ }
+ break;
+ case 'approve':
+ if ( ! is_array( $follow_requests ) ) {
+ $follow_requests = array( $follow_requests );
+ }
+ foreach ( $follow_requests as $follow_request ) {
+ Follow_Request::from_wp_id( $follow_request )->approve();
+ }
+ break;
+ }
+ }
+
+ public function follow_requests_count() {
+ return $this->follow_requests_count;
+ }
+}
diff --git a/includes/table/class-followers.php b/includes/table/class-followers.php
index 76b1f83..df9747b 100644
--- a/includes/table/class-followers.php
+++ b/includes/table/class-followers.php
@@ -16,7 +16,7 @@ class Followers extends WP_List_Table {
public function __construct() {
if ( get_current_screen()->id === 'settings_page_activitypub' ) {
- $this->user_id = Users::APPLICATION_USER_ID;
+ $this->user_id = Users::BLOG_USER_ID;
} else {
$this->user_id = \get_current_user_id();
}
diff --git a/templates/admin-followers-list.php b/templates/admin-followers-list.php
new file mode 100644
index 0000000..216102c
--- /dev/null
+++ b/templates/admin-followers-list.php
@@ -0,0 +1,57 @@
+ '',
+ 'welcome' => '',
+ 'followers' => 'active',
+ )
+);
+
+// Draw the follow table for the blog user if it is activated.
+if ( ! \Activitypub\is_user_disabled( \Activitypub\Collection\Users::BLOG_USER_ID ) ) :
+ $table = new \Activitypub\Table\Followers();
+ $follower_count = $table->get_user_count();
+ // translators: The follower count.
+ $followers_template = _n( 'Your blog profile currently has %s follower.', 'Your blog profile currently has %s followers.', $follower_count, 'activitypub' );
+ ?>
+
+
+
+
+
+
+prepare_items();
+$follow_requests_count = $table->follow_requests_count;
+// translators: The follower count.
+$followers_template = _n( 'Your WordPress site currently has %s follow request.', 'Your WordPress site currently has %s follow requests.', $follow_requests_count, 'activitypub' );
+?>
+
+
+
+
+
diff --git a/templates/admin-header.php b/templates/admin-header.php
index 67b91ba..49fe53c 100644
--- a/templates/admin-header.php
+++ b/templates/admin-header.php
@@ -15,13 +15,10 @@
-
-
-
diff --git a/templates/blog-user-followers-list.php b/templates/blog-user-followers-list.php
deleted file mode 100644
index 1eaa7ee..0000000
--- a/templates/blog-user-followers-list.php
+++ /dev/null
@@ -1,28 +0,0 @@
- '',
- 'welcome' => '',
- 'followers' => 'active',
- )
-);
-$table = new \Activitypub\Table\Followers();
-$follower_count = $table->get_user_count();
-// translators: The follower count.
-$followers_template = _n( 'Your blog profile currently has %s follower.', 'Your blog profile currently has %s followers.', $follower_count, 'activitypub' );
-?>
-