Skip to content

Commit f7b7c87

Browse files
authored
Followers: Row action and update notice (#1933)
1 parent 0c65014 commit f7b7c87

File tree

2 files changed

+258
-65
lines changed

2 files changed

+258
-65
lines changed

includes/table/class-followers.php

Lines changed: 123 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,95 @@ public function __construct() {
4444
'ajax' => false,
4545
)
4646
);
47+
48+
\add_action( 'load-' . get_current_screen()->id, array( $this, 'process_action' ), 20 );
49+
\add_action( 'admin_notices', array( $this, 'process_admin_notices' ) );
50+
}
51+
52+
/**
53+
* Process action.
54+
*/
55+
public function process_action() {
56+
if ( ! \current_user_can( 'edit_user', $this->user_id ) ) {
57+
return;
58+
}
59+
60+
switch ( $this->current_action() ) {
61+
case 'delete':
62+
// Handle single follower deletion.
63+
if ( isset( $_GET['follower'], $_GET['_wpnonce'] ) ) {
64+
$follower = \esc_url_raw( \wp_unslash( $_GET['follower'] ) );
65+
$nonce = \sanitize_text_field( \wp_unslash( $_GET['_wpnonce'] ) );
66+
67+
if ( \wp_verify_nonce( $nonce, 'delete-follower_' . $follower ) ) {
68+
Follower_Collection::remove_follower( $this->user_id, $follower );
69+
70+
$redirect_args = array(
71+
'updated' => 'true',
72+
'action' => 'deleted',
73+
);
74+
75+
\wp_safe_redirect( \add_query_arg( $redirect_args ) );
76+
exit;
77+
}
78+
}
79+
80+
// Handle bulk actions.
81+
if ( isset( $_REQUEST['followers'], $_REQUEST['_wpnonce'] ) ) {
82+
$nonce = \sanitize_text_field( \wp_unslash( $_REQUEST['_wpnonce'] ) );
83+
84+
if ( \wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
85+
$followers = \array_map( 'esc_url_raw', \wp_unslash( $_REQUEST['followers'] ) );
86+
foreach ( $followers as $follower ) {
87+
Follower_Collection::remove_follower( $this->user_id, $follower );
88+
}
89+
90+
$redirect_args = array(
91+
'updated' => 'true',
92+
'action' => 'all_deleted',
93+
'count' => \count( $followers ),
94+
);
95+
96+
\wp_safe_redirect( \add_query_arg( $redirect_args ) );
97+
exit;
98+
}
99+
}
100+
break;
101+
102+
default:
103+
break;
104+
}
105+
}
106+
107+
/**
108+
* Process admin notices based on query parameters.
109+
*/
110+
public function process_admin_notices() {
111+
if ( isset( $_REQUEST['updated'] ) && 'true' === $_REQUEST['updated'] && ! empty( $_REQUEST['action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
112+
$message = '';
113+
switch ( $_REQUEST['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification
114+
case 'deleted':
115+
$message = \__( 'Follower deleted.', 'activitypub' );
116+
break;
117+
case 'all_deleted':
118+
$count = \absint( $_REQUEST['count'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification
119+
/* translators: %d: Number of followers deleted. */
120+
$message = \_n( '%d follower deleted.', '%d followers deleted.', $count, 'activitypub' );
121+
$message = \sprintf( $message, \number_format_i18n( $count ) );
122+
break;
123+
}
124+
125+
if ( ! empty( $message ) ) {
126+
\wp_admin_notice(
127+
$message,
128+
array(
129+
'type' => 'success',
130+
'dismissible' => true,
131+
'id' => 'message',
132+
)
133+
);
134+
}
135+
}
47136
}
48137

49138
/**
@@ -77,12 +166,9 @@ public function get_sortable_columns() {
77166
* Prepare items.
78167
*/
79168
public function prepare_items() {
80-
$this->process_action();
81-
82169
$page_num = $this->get_pagenum();
83170
$per_page = $this->get_items_per_page( 'activitypub_followers_per_page' );
84-
85-
$args = array();
171+
$args = array();
86172

87173
// phpcs:disable WordPress.Security.NonceVerification.Recommended
88174
if ( isset( $_GET['orderby'] ) ) {
@@ -233,35 +319,43 @@ public function column_modified( $item ) {
233319
}
234320

235321
/**
236-
* Process action.
322+
* Message to be displayed when there are no followers.
237323
*/
238-
public function process_action() {
239-
if ( ! isset( $_REQUEST['followers'], $_REQUEST['_wpnonce'] ) ) {
240-
return;
241-
}
242-
243-
$nonce = \sanitize_text_field( \wp_unslash( $_REQUEST['_wpnonce'] ) );
244-
if ( ! \wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
245-
return;
246-
}
247-
248-
if ( ! \current_user_can( 'edit_user', $this->user_id ) ) {
249-
return;
250-
}
251-
252-
if ( $this->current_action() === 'delete' ) {
253-
$followers = \array_map( 'esc_url_raw', \wp_unslash( $_REQUEST['followers'] ) );
254-
255-
foreach ( $followers as $follower ) {
256-
Follower_Collection::remove_follower( $this->user_id, $follower );
257-
}
258-
}
324+
public function no_items() {
325+
\esc_html_e( 'No followers found.', 'activitypub' );
259326
}
260327

261328
/**
262-
* Message to be displayed when there are no followers.
329+
* Handles the row actions for each follower item.
330+
*
331+
* @param array $item The current follower item.
332+
* @param string $column_name The current column name.
333+
* @param string $primary The primary column name.
334+
* @return string HTML for the row actions.
263335
*/
264-
public function no_items() {
265-
\esc_html_e( 'No followers found.', 'activitypub' );
336+
protected function handle_row_actions( $item, $column_name, $primary ) {
337+
if ( $column_name !== $primary ) {
338+
return '';
339+
}
340+
341+
$actions = array(
342+
'delete' => sprintf(
343+
'<a href="%s" aria-label="%s">%s</a>',
344+
\wp_nonce_url(
345+
\add_query_arg(
346+
array(
347+
'action' => 'delete',
348+
'follower' => $item['identifier'],
349+
)
350+
),
351+
'delete-follower_' . $item['identifier']
352+
),
353+
/* translators: %s: username. */
354+
\esc_attr( \sprintf( \__( 'Delete %s', 'activitypub' ), $item['username'] ) ),
355+
\esc_html__( 'Delete', 'activitypub' )
356+
),
357+
);
358+
359+
return $this->row_actions( $actions );
266360
}
267361
}

includes/table/class-following.php

Lines changed: 135 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,105 @@ public function __construct() {
4444
'ajax' => false,
4545
)
4646
);
47+
48+
\add_action( 'load-' . get_current_screen()->id, array( $this, 'process_action' ), 20 );
49+
\add_action( 'admin_notices', array( $this, 'process_admin_notices' ) );
50+
}
51+
52+
/**
53+
* Process action.
54+
*/
55+
public function process_action() {
56+
if ( ! \current_user_can( 'edit_user', $this->user_id ) ) {
57+
return;
58+
}
59+
60+
switch ( $this->current_action() ) {
61+
case 'delete':
62+
// Handle single follower deletion.
63+
if ( isset( $_GET['follower'], $_GET['_wpnonce'] ) ) {
64+
$follower = \esc_url_raw( \wp_unslash( $_GET['follower'] ) );
65+
$nonce = \sanitize_text_field( \wp_unslash( $_GET['_wpnonce'] ) );
66+
67+
if ( \wp_verify_nonce( $nonce, 'delete-follower_' . $follower ) ) {
68+
$actor = Actors::get_remote_by_uri( $follower );
69+
if ( \is_wp_error( $actor ) ) {
70+
break;
71+
}
72+
73+
Following_Collection::unfollow( $actor, $this->user_id );
74+
75+
$redirect_args = array(
76+
'updated' => 'true',
77+
'action' => 'deleted',
78+
);
79+
80+
\wp_safe_redirect( \add_query_arg( $redirect_args ) );
81+
exit;
82+
}
83+
}
84+
85+
// Handle bulk actions.
86+
if ( isset( $_REQUEST['following'], $_REQUEST['_wpnonce'] ) ) {
87+
$nonce = \sanitize_text_field( \wp_unslash( $_REQUEST['_wpnonce'] ) );
88+
89+
if ( \wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
90+
$following = array_map( 'esc_url_raw', \wp_unslash( $_REQUEST['following'] ) );
91+
92+
foreach ( $following as $actor_id ) {
93+
$actor = Actors::get_remote_by_uri( $actor_id );
94+
if ( \is_wp_error( $actor ) ) {
95+
continue;
96+
}
97+
Following_Collection::unfollow( $actor, $this->user_id );
98+
}
99+
100+
$redirect_args = array(
101+
'updated' => 'true',
102+
'action' => 'all_deleted',
103+
'count' => \count( $following ),
104+
);
105+
106+
\wp_safe_redirect( \add_query_arg( $redirect_args ) );
107+
exit;
108+
}
109+
}
110+
break;
111+
112+
default:
113+
break;
114+
}
115+
}
116+
117+
/**
118+
* Process admin notices based on query parameters.
119+
*/
120+
public function process_admin_notices() {
121+
if ( isset( $_REQUEST['updated'] ) && 'true' === $_REQUEST['updated'] && ! empty( $_REQUEST['action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
122+
$message = '';
123+
switch ( $_REQUEST['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification
124+
case 'deleted':
125+
$message = \__( 'Account unfollowed.', 'activitypub' );
126+
break;
127+
case 'all_deleted':
128+
$count = \absint( $_REQUEST['count'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification
129+
/* translators: %d: Number of accounts unfollowed. */
130+
$message = \_n( '%d account unfollowed.', '%d accounts unfollowed.', $count, 'activitypub' );
131+
$message = \sprintf( $message, \number_format_i18n( $count ) );
132+
break;
133+
}
134+
135+
if ( ! empty( $message ) ) {
136+
\wp_admin_notice(
137+
$message,
138+
array(
139+
'type' => 'success',
140+
'dismissible' => true,
141+
'id' => 'message',
142+
)
143+
);
144+
}
145+
}
47146
}
48147

49148
/**
@@ -77,14 +176,10 @@ public function get_sortable_columns() {
77176
* Prepare items.
78177
*/
79178
public function prepare_items() {
80-
$status = Following_Collection::ALL;
81-
82-
$this->process_action();
83-
179+
$status = Following_Collection::ALL;
84180
$page_num = $this->get_pagenum();
85181
$per_page = $this->get_items_per_page( 'activitypub_following_per_page' );
86-
87-
$args = array();
182+
$args = array();
88183

89184
// phpcs:disable WordPress.Security.NonceVerification.Recommended
90185
if ( isset( $_GET['orderby'] ) ) {
@@ -292,36 +387,6 @@ public function column_modified( $item ) {
292387
);
293388
}
294389

295-
/**
296-
* Process action.
297-
*/
298-
public function process_action() {
299-
if ( ! isset( $_REQUEST['following'], $_REQUEST['_wpnonce'] ) ) {
300-
return;
301-
}
302-
303-
$nonce = \sanitize_text_field( \wp_unslash( $_REQUEST['_wpnonce'] ) );
304-
if ( ! \wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
305-
return;
306-
}
307-
308-
if ( ! \current_user_can( 'edit_user', $this->user_id ) ) {
309-
return;
310-
}
311-
312-
if ( $this->current_action() === 'delete' ) {
313-
$following = array_map( 'esc_url_raw', \wp_unslash( $_REQUEST['following'] ) );
314-
315-
foreach ( $following as $actor_id ) {
316-
$actor = Actors::get_remote_by_uri( $actor_id );
317-
if ( \is_wp_error( $actor ) ) {
318-
continue;
319-
}
320-
Following_Collection::unfollow( $actor, $this->user_id );
321-
}
322-
}
323-
}
324-
325390
/**
326391
* Message to be displayed when there are no followings.
327392
*/
@@ -342,4 +407,38 @@ public function single_row( $item ) {
342407
$this->single_row_columns( $item );
343408
\printf( "</tr>\n" );
344409
}
410+
411+
/**
412+
* Handles the row actions for each following item.
413+
*
414+
* @param array $item The current following item.
415+
* @param string $column_name The current column name.
416+
* @param string $primary The primary column name.
417+
* @return string HTML for the row actions.
418+
*/
419+
protected function handle_row_actions( $item, $column_name, $primary ) {
420+
if ( $column_name !== $primary ) {
421+
return '';
422+
}
423+
424+
$actions = array(
425+
'unfollow' => sprintf(
426+
'<a href="%s" aria-label="%s">%s</a>',
427+
\wp_nonce_url(
428+
\add_query_arg(
429+
array(
430+
'action' => 'delete',
431+
'follower' => $item['identifier'],
432+
)
433+
),
434+
'delete-follower_' . $item['identifier']
435+
),
436+
/* translators: %s: username. */
437+
\esc_attr( \sprintf( \__( 'Unfollow %s', 'activitypub' ), $item['username'] ) ),
438+
\esc_html__( 'Unfollow', 'activitypub' )
439+
),
440+
);
441+
442+
return $this->row_actions( $actions );
443+
}
345444
}

0 commit comments

Comments
 (0)