2023-12-25 22:35:04 +01:00
< ? 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 (
2023-12-26 13:50:54 +01:00
'status' => array ( 'status' , false ),
2023-12-25 22:35:04 +01:00
'name' => array ( 'name' , true ),
'modified' => array ( 'modified' , false ),
'published' => array ( 'published' , false ),
);
return $sortable_columns ;
}
2023-12-27 22:41:16 +01:00
/**
* Get follow requests together with information from the follower .
*
* @ param int $user_id The ID of the WordPress User , which may be 0 for the blog and - 1 for the application user
* @ param int $per_page Number of items per page
* @ param int $page_num The current page
* @ param int $args May contain custom ordering or search terms .
*
* @ return array Containing an array of all follow requests and the total numbers .
*/
public static function get_follow_requests_for_user ( $user_id , $per_page , $page_num , $args ) {
$order = isset ( $args [ 'order' ] ) && strtolower ( $args [ 'order' ] ) === 'asc' ? 'ASC' : 'DESC' ;
$orderby = isset ( $args [ 'orderby' ] ) ? sanitize_text_field ( $args [ 'orderby' ] ) : 'published' ;
$search = isset ( $args [ 's' ] ) ? sanitize_text_field ( $args [ 's' ] ) : '' ;
$offset = ( int ) $per_page * ( ( int ) $page_num - 1 );
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 ( follower . post_title LIKE % s OR follower . guid LIKE % s )
AND meta . meta_key = 'activitypub_user_id'
AND meta . meta_value = % s
ORDER BY % s % s
LIMIT % d OFFSET % d " ,
'%' . $wpdb -> esc_like ( $search ) . '%' ,
'%' . $wpdb -> esc_like ( $search ) . '%' ,
$user_id ,
$orderby ,
$order ,
$per_page ,
$offset
)
);
$current_total_items = $wpdb -> get_var ( 'SELECT FOUND_ROWS()' );
// Second step: Get the total rows without the LIMIT
$total_items = $wpdb -> get_var (
$wpdb -> prepare (
" SELECT COUNT(follow_request.ID)
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 " ,
$user_id
)
);
return compact ( 'follow_requests' , 'current_total_items' , 'total_items' );
}
2023-12-25 22:35:04 +01:00
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' ] ) );
}
}
2023-12-27 22:41:16 +01:00
$follow_requests_with_count = self :: get_follow_requests_for_user ( $this -> user_id , $per_page , $page_num , $args );
2023-12-25 22:35:04 +01:00
$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 () {
2023-12-26 13:50:54 +01:00
global $_REQUEST ;
$follow_action = isset ( $_REQUEST [ 'follow_action' ] ) ? sanitize_title ( wp_unslash ( $_REQUEST [ 'follow_action' ] ) ) : null ;
$follow_request_id = isset ( $_REQUEST [ 'follow_request' ] ) ? ( int ) $_REQUEST [ 'follow_request' ] : null ;
2023-12-26 15:55:14 +01:00
$wp_nonce = isset ( $_REQUEST [ '_wpnonce' ] ) ? sanitize_title ( wp_unslash ( $_REQUEST [ '_wpnonce' ] ) ) : null ;
2023-12-26 13:50:54 +01:00
if ( ! $follow_action || ! $follow_request_id || ! $wp_nonce ) {
return ;
}
wp_verify_nonce ( $wp_nonce , " activitypub_ { $follow_action } _follow_request " );
$follow_request = Follow_Request :: from_wp_id ( $follow_request_id );
2023-12-25 22:35:04 +01:00
if ( $follow_request -> can_handle_follow_request () ) {
switch ( $follow_action ) {
case 'approve' :
$follow_request -> approve ();
wp_die ( 'approved' );
2023-12-26 13:50:54 +01:00
break ;
2023-12-25 22:35:04 +01:00
case 'reject' :
$follow_request -> reject ();
wp_die ( 'rejected' );
2023-12-26 13:50:54 +01:00
break ;
2023-12-25 22:35:04 +01:00
case 'delete' :
$follow_request -> delete ();
wp_die ( 'deleted' );
2023-12-26 13:50:54 +01:00
break ;
2023-12-25 22:35:04 +01:00
}
}
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' ;
}
2023-12-26 13:50:54 +01:00
switch ( $follow_action ) {
case 'approve' :
$follow_action_text = __ ( 'Approve' , 'activitypub' );
break ;
case 'delete' :
$follow_action_text = __ ( 'Delete' , 'activitypub' );
break ;
case 'reject' :
$follow_action_text = __ ( 'Reject' , 'activitypub' );
break ;
default :
return ;
}
2023-12-25 22:35:04 +01:00
printf (
'<input type="%s" class="button" value="%s" data-action="%s">' ,
esc_attr ( $type ),
2023-12-26 13:50:54 +01:00
esc_attr ( $follow_action_text ),
2023-12-25 22:35:04 +01:00
esc_url ( $url )
);
}
2023-12-26 13:50:54 +01:00
public function column_action ( $item ) {
2023-12-25 22:35:04 +01:00
$status = $item [ 'status' ];
2023-12-26 13:50:54 +01:00
printf ( '<div class="activitypub-settings-action-buttons">' );
2023-12-25 22:35:04 +01:00
// TODO this can be written smarter, but at least it is readable.
if ( 'pending' === $status ) {
2023-12-26 13:50:54 +01:00
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 );
2023-12-25 22:35:04 +01:00
}
if ( 'approved' === $status ) {
2023-12-26 13:50:54 +01:00
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 );
2023-12-25 22:35:04 +01:00
}
if ( 'rejected' === $status ) {
2023-12-26 13:50:54 +01:00
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' );
2023-12-25 22:35:04 +01:00
}
2023-12-26 13:50:54 +01:00
printf ( '</div>' );
2023-12-25 22:35:04 +01:00
}
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 ;
}
}