draft for managing follow requests
Some checks failed
PHP_CodeSniffer / phpcs (push) Has been cancelled
Unit Testing / phpunit (5.6, 6.2) (push) Has been cancelled
Unit Testing / phpunit (7.0) (push) Has been cancelled
Unit Testing / phpunit (7.2) (push) Has been cancelled
Unit Testing / phpunit (7.3) (push) Has been cancelled
Unit Testing / phpunit (7.4) (push) Has been cancelled
Unit Testing / phpunit (8.0) (push) Has been cancelled
Unit Testing / phpunit (8.1) (push) Has been cancelled
Unit Testing / phpunit (8.2) (push) Has been cancelled
Unit Testing / phpunit (latest) (push) Has been cancelled

- the application actor is managed manually by default
- no admin-options are included yet
- the old new follower table only is used hardcoded by the application actor
- no admin notifications are send yet
- todo: a lot more
This commit is contained in:
André Menrath 2023-12-25 22:35:04 +01:00
parent 616667b0ba
commit 03b6a8e598
15 changed files with 749 additions and 88 deletions

View file

@ -179,6 +179,14 @@ add_action(
0
);
add_action(
'wp_ajax_activitypub_handle_follow_request',
function () {
$wp_list_table = new \Activitypub\Table\Follow_Requests();
$wp_list_table->ajax_response();
}
);
/**
* `get_plugin_data` wrapper
*

View file

@ -197,3 +197,36 @@ input.blog-user-identifier {
border-bottom: none;
margin-bottom: 0;
}
.activitypub-settings-label {
display: inline-block;
padding: .25em .4em;
font-size: 85%;
font-weight: 700;
line-height: 1.25;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: .25rem;
}
.activitypub-settings-label-success {
color: #fff;
background-color: #28a745;
}
.activitypub-settings-label-warning {
color: #212529;
background-color: #ffc107;
}
.activitypub-settings-label-danger {
color: #fff;
background-color: #dc3545;
}
.activitypub-settings-action-buttons {
display: flex;
gap: 5px;
flex-wrap: nowrap;
}

View file

@ -1,4 +1,5 @@
jQuery( function( $ ) {
const { __ } = wp.i18n;
// Accordion handling in various areas.
$( '.activitypub-settings-accordion' ).on( 'click', '.activitypub-settings-accordion-trigger', function() {
var isExpanded = ( 'true' === $( this ).attr( 'aria-expanded' ) );
@ -18,4 +19,50 @@ jQuery( function( $ ) {
}, 1200 );
} );
$( '.activitypub-settings-action-buttons' ).on( 'click', '.button', function() {
var button = $ (this );
var actionValue = button.data('action');
window.console.log( actionValue );
$.ajax({
type: 'POST',
url: actionValue,
success: function ( response ) {
var statusText = button.closest( 'td' ).siblings( '.column-status' ).children( 'span' ).first();
if ( 'deleted' === response ) {
button.closest( 'tr' ).remove();
}
if ( 'approved' === response ) {
button.parent().find( '[data-action*="follow_action=reject"] ').attr( 'type', 'button' );
button.parent().find( '[data-action*="follow_action=delete"]' ).attr( 'type', 'hidden' );
statusText.text( __( 'Approved', 'activitypub' ) );
statusText.removeClass( 'activitypub-settings-label-danger' );
statusText.removeClass( 'activitypub-settings-label-warning' );
statusText.addClass( 'activitypub-settings-label-success' );
}
if ( 'rejected' === response ) {
// TODO: clarify this behavior together with Mobilizon and others.
button.closest( 'tr' ).remove();
// statusText.text( __( 'Rejected', 'activitypub' ) );
// statusText.removeClass( 'activitypub-settings-label-success' );
// statusText.removeClass( 'activitypub-settings-label-warning' );
// statusText.addClass( 'activitypub-settings-label-danger' );
// button.parent().find( '[data-action*="follow_action=approve"]' ).attr( 'type', 'button' );
// button.parent().find( '[data-action*="follow_action=delete"]' ).attr( 'type', 'button' );
}
button.attr( 'type', 'hidden' );
// Check if table is completely empty.
var tbody = button.closest( 'tbody' );
if ( 0 == tbody.find( 'tr' ).length ) {
var text = __( 'No items found.', 'core' );
var newRow = $('<tr>').append($('<td>', { class: 'colspanchange', colspan: 7, text: text }));
tbody.append(newRow);
tbody.append("Some appended text.");
}
},
error: function ( error ) {
// TODO: Handle the error
}
});
} );
} );

View file

@ -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 );

View file

@ -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 );
}
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace Activitypub\Collection;
use WP_Error;
use WP_Query;
use Activitypub\Http;
use Activitypub\Webfinger;
use Activitypub\Activity\Base_Object;
use Activitypub\Model\Follower;
use function Activitypub\is_tombstone;
use function Activitypub\get_remote_metadata_by_actor;
/**
* ActivityPub Follow Requests Collection
*
* @author André Menrath
*/
class Follow_Requests {
/**
* Get a follow request together with information from the follower.
*
* @param int $user_id The ID of the WordPress User
* @param int $follower_id The follower ID
*
* @return \Activitypub\Model\Follow|null The Follower object or null
*/
public static function get_follow_requests_for_user( $user_id, $per_page, $page, $args ) {
$order = isset($args['order']) && strtolower($args['order']) === 'asc' ? 'ASC' : 'DESC';
$orderby = isset($args['orderby']) ? sanitize_text_field($args['orderby']) : 'published';
global $wpdb;
$follow_requests = $wpdb->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' );
}
}

View file

@ -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;
}
// only send minimal data
$object = array_intersect_key(
$object,
array_flip(
array(
'id',
'type',
'actor',
'object',
)
)
);
// save follow request by this follower
$follow_request = Follow_Request::save( $follower, $user_id, $activity['id'] );
$user = Users::get_by_id( $user_id );
if ( ! $user->get_manually_approves_followers() ) {
$follow_request->accept();
}
// 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() );
}
}

View file

@ -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();
}
}
}

View file

@ -0,0 +1,215 @@
<?php
namespace Activitypub\Model;
use WP_Error;
use Activitypub\Activity\Activity;
use Activitypub\Activity\Base_Object;
use Activitypub\Collection\Users;
use Activitypub\Model\Follower;
use Activitypub\Http;
/**
* ActivityPub Follow Class
*
* This Object represents a Follow object.
* There is no direct reference to a WordPress User here.
*
* @author André Menrath
*
* @see https://www.w3.org/TR/activitypub/#follow-activity-inbox
*/
class Follow_Request extends Base_Object {
const FOLLOW_REQUEST_POST_TYPE = 'ap_follow_request';
/**
* Stores theinternal WordPress post id of the post of type ap_follow_request
*
* @var string
*/
protected $_id;
/**
* The id/URI of the follower
* @var string
*/
protected $actor;
/**
* The internal WordPress post id of the follower
* @var string
*/
protected $_actor;
/**
* @param int $id
* @return Follow_Request $follow_request
*/
public static function get_from_array( $array ) {
$follow_request = self::init_from_array( $array );
if ( ! self::is_valid( $follow_request ) ) {
return;
}
if ( ! $follow_request->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);
}
}

View file

@ -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 );

View file

@ -0,0 +1,290 @@
<?php
namespace Activitypub\Table;
use WP_List_Table;
use Activitypub\Collection\Users;
use Activitypub\Collection\Followers as FollowerCollection;
use Activitypub\Collection\Follow_Requests as FollowerRequestCollection;
use Activitypub\Model\Follow_Request;
use function Activitypub\object_to_uri;
if ( ! \class_exists( '\WP_List_Table' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}
/**
* Table that shows all follow requests for a user and allows handling those requests.
*/
class Follow_Requests extends WP_List_Table {
private $user_id;
public $follow_requests_count = 0;
public function __construct( $user_id = null ) {
if ( $user_id ) {
$this->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' => '<input type="button">',
'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(
'<span class="activitypub-settings-label activitypub-settings-label-%s">%s</span>',
$color,
ucfirst( $status )
);
}
public function column_avatar( $item ) {
return sprintf(
'<img src="%s" width="25px;" />',
$item['icon']
);
}
public function column_url( $item ) {
return sprintf(
'<a href="%s" target="_blank">%s</a>',
$item['url'],
$item['url']
);
}
public function column_cb( $item ) {
return sprintf( '<input type="checkbox" name="follow_requests[]" value="%s" />', 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(
'<input type="%s" class="button" value="%s" data-action="%s">',
esc_attr( $type ),
esc_attr__( ucfirst( $follow_action ), 'activitypub' ),
esc_url( $url )
);
}
public function column_action($item) {
$status = $item['status'];
printf('<div class="activitypub-settings-action-buttons">');
// 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('</div>');
}
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;
}
}

View file

@ -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();
}

View file

@ -0,0 +1,57 @@
<?php
use Activitypub\Collection\Users;
\load_template(
__DIR__ . '/admin-header.php',
true,
array(
'settings' => '',
'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' );
?>
<div class="wrap activitypub-followers-page">
<p><?php \printf( \esc_html( $followers_template ), \esc_attr( $follower_count ) ); ?></p>
<form method="get">
<input type="hidden" name="page" value="activitypub" />
<input type="hidden" name="tab" value="followers" />
<?php
$table->prepare_items();
$table->search_box( 'Search', 'search' );
$table->display();
?>
</form>
</div>
<?php endif;
// Draw the the follow table for the application user with reject and accept options.
$table = new \Activitypub\Table\Follow_Requests( Users::APPLICATION_USER_ID );
$table->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' );
?>
<div class="wrap activitypub-followers-page">
<p><?php \printf( \esc_html( $followers_template ), \esc_attr( $follow_requests_count ) ); ?></p>
<form method="get">
<input type="hidden" name="page" value="activitypub" />
<input type="hidden" name="tab" value="followers" />
<?php
$table->search_box( 'Search', 'search' );
$table->display();
?>
</form>
</div>

View file

@ -15,13 +15,10 @@
<?php \esc_html_e( 'Settings', 'activitypub' ); ?>
</a>
<?php if ( ! \Activitypub\is_user_disabled( \Activitypub\Collection\Users::BLOG_USER_ID ) ) : ?>
<a href="<?php echo \esc_url_raw( admin_url( 'options-general.php?page=activitypub&tab=followers' ) ); ?>" class="activitypub-settings-tab <?php echo \esc_attr( $args['followers'] ); ?>">
<?php \esc_html_e( 'Followers', 'activitypub' ); ?>
</a>
<?php endif; ?>
</nav>
</div>
<hr class="wp-header-end">

View file

@ -1,28 +0,0 @@
<?php
\load_template(
__DIR__ . '/admin-header.php',
true,
array(
'settings' => '',
'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' );
?>
<div class="wrap activitypub-followers-page">
<p><?php \printf( \esc_html( $followers_template ), \esc_attr( $follower_count ) ); ?></p>
<form method="get">
<input type="hidden" name="page" value="activitypub" />
<input type="hidden" name="tab" value="followers" />
<?php
$table->prepare_items();
$table->search_box( 'Search', 'search' );
$table->display();
?>
</form>
</div>