PluginProbe
ActivityPub / 5.7.0
ActivityPub v5.7.0
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
← All changes | includes/wp-admin/class-admin.php +88 -757 8.2.15.7.0 View file →
@@ -6,18 +6,11 @@
6 6 */
7 7
8 8 namespace Activitypub\WP_Admin;
9 9
10 -use Activitypub\Blocklist_Subscriptions;
10 +use Activitypub\Comment;
11 11 use Activitypub\Collection\Actors;
12 12 use Activitypub\Collection\Extra_Fields;
13 -use Activitypub\Comment;
14 -use Activitypub\Moderation;
15 -use Activitypub\OAuth\Client;
16 -use Activitypub\OAuth\Token;
17 -use Activitypub\Scheduler\Actor;
18 -use Activitypub\Tombstone;
19 -
20 13 use function Activitypub\count_followers;
21 14 use function Activitypub\get_content_visibility;
22 15 use function Activitypub\is_user_type_disabled;
23 16 use function Activitypub\site_supports_blocks;
@@ -55,16 +48,10 @@
55 48 \add_filter( 'manage_users_custom_column', array( self::class, 'manage_users_custom_column' ), 10, 3 );
56 49 \add_filter( 'bulk_actions-users', array( self::class, 'user_bulk_options' ) );
57 50 \add_filter( 'handle_bulk_actions-users', array( self::class, 'handle_bulk_request' ), 10, 3 );
58 51
59 - \add_action( 'admin_post_delete_actor_confirmed', array( self::class, 'handle_bulk_actor_delete_confirmation' ) );
60 - \add_action( 'admin_action_activitypub_confirm_removal', array( self::class, 'handle_bulk_actor_delete_page' ) );
61 -
62 52 if ( user_can_activitypub( \get_current_user_id() ) ) {
63 53 \add_action( 'show_user_profile', array( self::class, 'add_profile' ) );
64 - if ( \get_option( 'activitypub_api', false ) ) {
65 - \add_action( 'show_user_profile', array( User_Settings_Fields::class, 'connected_apps_section' ) );
66 - }
67 54 }
68 55
69 56 \add_filter( 'dashboard_glance_items', array( self::class, 'dashboard_glance_items' ) );
70 57 \add_filter( 'plugin_action_links_' . ACTIVITYPUB_PLUGIN_BASENAME, array( self::class, 'add_plugin_settings_link' ) );
@@ -72,21 +59,8 @@
72 59
73 60 if ( site_supports_blocks() ) {
74 61 \add_action( 'tool_box', array( self::class, 'tool_box' ) );
75 62 }
76 -
77 - \add_action( 'admin_print_scripts-profile.php', array( self::class, 'enqueue_moderation_scripts' ) );
78 - \add_action( 'admin_print_scripts-profile.php', array( self::class, 'enqueue_connected_apps_scripts' ) );
79 - \add_action( 'admin_print_scripts-settings_page_activitypub', array( self::class, 'enqueue_moderation_scripts' ) );
80 - \add_action( 'admin_print_footer_scripts-settings_page_activitypub', array( self::class, 'open_help_tab' ) );
81 -
82 - \add_action( 'wp_ajax_activitypub_moderation_settings', array( self::class, 'ajax_moderation_settings' ) );
83 - \add_action( 'wp_ajax_activitypub_blocklist_subscription', array( self::class, 'ajax_blocklist_subscription' ) );
84 - \add_action( 'wp_ajax_activitypub_register_oauth_client', array( self::class, 'ajax_register_oauth_client' ) );
85 - \add_action( 'wp_ajax_activitypub_delete_oauth_client', array( self::class, 'ajax_delete_oauth_client' ) );
86 - \add_action( 'wp_ajax_activitypub_delete_all_oauth_clients', array( self::class, 'ajax_delete_all_oauth_clients' ) );
87 - \add_action( 'wp_ajax_activitypub_revoke_oauth_token', array( self::class, 'ajax_revoke_oauth_token' ) );
88 - \add_action( 'wp_ajax_activitypub_revoke_all_oauth_tokens', array( self::class, 'ajax_revoke_all_oauth_tokens' ) );
89 63 }
90 64
91 65 /**
92 66 * Display admin menu notices about configuration problems or conflicts.
@@ -92,30 +66,11 @@
92 66 * Display admin menu notices about configuration problems or conflicts.
93 67 */
94 68 public static function admin_notices() {
95 69 $current_screen = get_current_screen();
96 -
97 70 if ( ! $current_screen ) {
98 71 return;
99 72 }
100 -
101 - // Check for self-destruct completion notice.
102 - $self_destruct_complete = \get_option( 'activitypub_self_destruct_complete' );
103 - if ( $self_destruct_complete ) {
104 - // Show the notice only once, then remove it.
105 - \delete_option( 'activitypub_self_destruct_complete' );
106 - ?>
107 - <div class="notice notice-success is-dismissible">
108 - <p>
109 - <strong><?php esc_html_e( 'ActivityPub Self-Destruct Complete!', 'activitypub' ); ?></strong>
110 - </p>
111 - <p>
112 - <?php esc_html_e( 'All Delete activities have been successfully sent to the Fediverse. Your blog is no longer discoverable via ActivityPub and all followers have been notified of the deletion.', 'activitypub' ); ?>
113 - </p>
114 - </div>
115 - <?php
116 - }
117 -
118 73 if ( 'edit' === $current_screen->base && Extra_Fields::is_extra_fields_post_type( $current_screen->post_type ) ) {
119 74 ?>
120 75 <div class="notice" style="margin: 0; background: none; border: none; box-shadow: none; padding: 15px 0 0 0; font-size: 14px;">
121 76 <?php
@@ -126,78 +81,41 @@
126 81 }
127 82 }
128 83
129 84 /**
130 - * Load user settings page.
85 + * Display one admin menu notice about configuration problems or conflicts.
86 + *
87 + * @param string $admin_notice The notice to display.
88 + * @param string $level The level of the notice (error, warning, success, info).
131 89 */
132 - public static function followers_list_page() {
133 - // User has to be able to publish posts.
134 - if ( user_can_activitypub( \get_current_user_id() ) ) {
135 - \load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/followers-list.php' );
136 - }
137 - }
90 + private static function show_admin_notice( $admin_notice, $level ) {
91 + ?>
138 92
139 - /**
140 - * Load user following list page.
141 - */
142 - public static function following_list_page() {
143 - // User has to be able to publish posts.
144 - if ( user_can_activitypub( \get_current_user_id() ) ) {
145 - \load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/following-list.php' );
146 - }
93 + <div class="notice notice-<?php echo esc_attr( $level ); ?>">
94 + <p><?php echo wp_kses( $admin_notice, 'data' ); ?></p>
95 + </div>
96 +
97 + <?php
147 98 }
148 99
149 100 /**
150 - * Load blocked actors page.
101 + * Load user settings page
151 102 */
152 - public static function blocked_actors_list_page() {
103 + public static function followers_list_page() {
153 104 // User has to be able to publish posts.
154 105 if ( user_can_activitypub( \get_current_user_id() ) ) {
155 - \load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/blocked-actors-list.php' );
106 + \load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/user-followers-list.php' );
156 107 }
157 108 }
158 109
159 110 /**
160 - * Creates the followers and following list tables in ActivityPub settings.
111 + * Adds the follower list to the Help tab.
161 112 */
162 - public static function add_settings_list_tables() {
163 - $tab = \sanitize_text_field( \wp_unslash( $_GET['tab'] ?? 'welcome' ) ); // phpcs:ignore WordPress.Security.NonceVerification
164 -
165 - switch ( $tab ) {
166 - case 'followers':
167 - self::add_followers_list_table();
168 - break;
169 - case 'following':
170 - self::add_following_list_table();
171 - break;
172 - case 'blocked-actors':
173 - self::add_blocked_actors_list_table();
174 - break;
175 - }
113 + public static function add_followers_list_help_tab() {
114 + // todo.
176 115 }
177 116
178 117 /**
179 - * Creates the followers list table.
180 - */
181 - public static function add_followers_list_table() {
182 - $GLOBALS['followers_list_table'] = new Table\Followers();
183 - }
184 -
185 - /**
186 - * Creates the following list table.
187 - */
188 - public static function add_following_list_table() {
189 - $GLOBALS['following_list_table'] = new Table\Following();
190 - }
191 -
192 - /**
193 - * Creates the blocked actors list table.
194 - */
195 - public static function add_blocked_actors_list_table() {
196 - $GLOBALS['blocked_actors_list_table'] = new Table\Blocked_Actors();
197 - }
198 -
199 - /**
200 118 * Render user settings.
201 119 */
202 120 public static function add_profile() {
203 121 wp_enqueue_media();
@@ -226,47 +144,27 @@
226 144 ) {
227 145 return;
228 146 }
229 147
230 - // User options that should be processed with `sanitize_textarea_field()`.
231 - $textarea_field_user_options = array(
232 - 'activitypub_also_known_as',
233 - 'activitypub_description',
234 - );
235 -
236 - foreach ( $textarea_field_user_options as $option ) {
237 - if ( ! empty( $_POST[ $option ] ) ) {
238 - \update_user_option( $user_id, $option, sanitize_textarea_field( wp_unslash( $_POST[ $option ] ) ) );
239 - } else {
240 - \delete_user_option( $user_id, $option );
241 - }
148 + $description = ! empty( $_POST['activitypub_description'] ) ? sanitize_textarea_field( wp_unslash( $_POST['activitypub_description'] ) ) : false;
149 + if ( $description ) {
150 + \update_user_option( $user_id, 'activitypub_description', $description );
151 + } else {
152 + \delete_user_option( $user_id, 'activitypub_description' );
242 153 }
243 154
244 - // User options that should be processed with `sanitize_text_field()`.
245 - $text_field_user_options = array(
246 - 'activitypub_header_image',
247 - );
248 -
249 - foreach ( $text_field_user_options as $option ) {
250 - if ( ! empty( $_POST[ $option ] ) ) {
251 - \update_user_option( $user_id, $option, sanitize_text_field( wp_unslash( $_POST[ $option ] ) ) );
252 - } else {
253 - \delete_user_option( $user_id, $option );
254 - }
155 + $header_image = ! empty( $_POST['activitypub_header_image'] ) ? sanitize_text_field( wp_unslash( $_POST['activitypub_header_image'] ) ) : false;
156 + if ( $header_image && \wp_attachment_is_image( $header_image ) ) {
157 + \update_user_option( $user_id, 'activitypub_header_image', $header_image );
158 + } else {
159 + \delete_user_option( $user_id, 'activitypub_header_image' );
255 160 }
256 161
257 - // User options that have a default value and therefore can't be empty (Empty triggers the default value).
258 - $required_user_options = array(
259 - 'activitypub_hide_social_graph',
260 - 'activitypub_mailer_new_dm',
261 - 'activitypub_mailer_new_follower',
262 - 'activitypub_mailer_new_mention',
263 - 'activitypub_mailer_annual_report',
264 - 'activitypub_mailer_monthly_report',
265 - );
266 -
267 - foreach ( $required_user_options as $option ) {
268 - \update_user_option( $user_id, $option, sanitize_text_field( wp_unslash( $_POST[ $option ] ?? 0 ) ) );
162 + $also_known_as = ! empty( $_POST['activitypub_blog_user_also_known_as'] ) ? \sanitize_textarea_field( wp_unslash( $_POST['activitypub_blog_user_also_known_as'] ) ) : false;
163 + if ( $also_known_as ) {
164 + \update_user_option( $user_id, 'activitypub_also_known_as', $also_known_as );
165 + } else {
166 + \delete_user_option( $user_id, 'activitypub_also_known_as' );
269 167 }
270 168 }
271 169
272 170 /**
@@ -285,31 +183,9 @@
285 183 ACTIVITYPUB_PLUGIN_VERSION,
286 184 false
287 185 );
288 186
289 - // Register and enqueue command palette integration.
290 - if ( user_can_activitypub( \get_current_user_id() ) || \current_user_can( 'manage_options' ) ) {
291 - $asset_data = include ACTIVITYPUB_PLUGIN_DIR . 'build/command-palette/plugin.asset.php';
292 - wp_enqueue_script(
293 - 'activitypub-command-palette',
294 - plugins_url( 'build/command-palette/plugin.js', ACTIVITYPUB_PLUGIN_FILE ),
295 - $asset_data['dependencies'],
296 - $asset_data['version'],
297 - true
298 - );
299 -
300 - wp_localize_script(
301 - 'activitypub-command-palette',
302 - 'activitypubCommandPalette',
303 - array(
304 - 'followingEnabled' => '1' === \get_option( 'activitypub_following_ui', '0' ),
305 - 'actorMode' => \get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE ),
306 - 'canManageOptions' => \current_user_can( 'manage_options' ),
307 - )
308 - );
309 - }
310 -
311 - if ( false !== strpos( $hook_suffix, 'activitypub' ) && 'dashboard_page_activitypub-social-web' !== $hook_suffix ) {
187 + if ( false !== strpos( $hook_suffix, 'activitypub' ) ) {
312 188 wp_enqueue_style(
313 189 'activitypub-admin-styles',
314 190 plugins_url(
315 191 'assets/css/activitypub-admin.css',
@@ -323,17 +199,12 @@
323 199 plugins_url(
324 200 'assets/js/activitypub-admin.js',
325 201 ACTIVITYPUB_PLUGIN_FILE
326 202 ),
327 - array( 'jquery', 'wp-util' ),
203 + array( 'jquery' ),
328 204 ACTIVITYPUB_PLUGIN_VERSION,
329 205 false
330 206 );
331 -
332 - // Plugin cards in help tab.
333 - \wp_enqueue_script( 'plugin-install' );
334 - \add_thickbox();
335 - \wp_enqueue_script( 'updates' );
336 207 }
337 208
338 209 if ( 'index.php' === $hook_suffix ) {
339 210 wp_enqueue_style(
@@ -345,114 +216,33 @@
345 216 array(),
346 217 ACTIVITYPUB_PLUGIN_VERSION
347 218 );
348 219 }
349 -
350 - if ( 'edit-comments.php' === $hook_suffix ) {
351 - \wp_add_inline_style(
352 - 'wp-emoji-styles',
353 - '.column-author img.emoji { float: none; }'
354 - );
355 - }
356 220 }
357 221
358 222 /**
359 - * Enqueue moderation admin scripts.
360 - */
361 - public static function enqueue_moderation_scripts() {
362 - \wp_enqueue_script(
363 - 'activitypub-moderation-admin',
364 - ACTIVITYPUB_PLUGIN_URL . 'assets/js/activitypub-moderation-admin.js',
365 - array( 'jquery', 'wp-util', 'wp-a11y', 'wp-i18n' ),
366 - ACTIVITYPUB_PLUGIN_VERSION,
367 - true
368 - );
369 -
370 - \wp_set_script_translations(
371 - 'activitypub-moderation-admin',
372 - 'activitypub',
373 - ACTIVITYPUB_PLUGIN_DIR . 'languages'
374 - );
375 -
376 - // Localize script with translations and nonces.
377 - \wp_localize_script(
378 - 'activitypub-moderation-admin',
379 - 'activitypubModerationL10n',
380 - array(
381 - 'nonce' => \wp_create_nonce( 'activitypub_moderation_settings' ),
382 - )
383 - );
384 - }
385 -
386 - /**
387 - * Enqueue connected apps admin scripts on the profile page.
388 - *
389 - * @since 8.1.0
390 - */
391 - public static function enqueue_connected_apps_scripts() {
392 - \wp_enqueue_script(
393 - 'activitypub-connected-apps',
394 - ACTIVITYPUB_PLUGIN_URL . 'assets/js/activitypub-connected-apps.js',
395 - array( 'jquery' ),
396 - ACTIVITYPUB_PLUGIN_VERSION,
397 - true
398 - );
399 -
400 - \wp_localize_script(
401 - 'activitypub-connected-apps',
402 - 'activitypubConnectedApps',
403 - array(
404 - 'ajaxUrl' => \admin_url( 'admin-ajax.php' ),
405 - 'nonce' => \wp_create_nonce( 'activitypub_connected_apps' ),
406 - 'confirm' => \__( 'Are you sure you want to revoke this application token? This action cannot be undone.', 'activitypub' ),
407 - 'confirmAll' => \__( 'Are you sure you want to revoke all connected applications? This action cannot be undone.', 'activitypub' ),
408 - 'confirmDelete' => \__( 'Are you sure you want to delete this application? This action cannot be undone.', 'activitypub' ),
409 - 'confirmDeleteAll' => \__( 'Are you sure you want to delete all registered applications? This action cannot be undone.', 'activitypub' ),
410 - 'registerError' => \__( 'Failed to register application.', 'activitypub' ),
411 - 'deleteLabel' => \__( 'Delete', 'activitypub' ),
412 - 'dismiss' => \__( 'Dismiss this notice.', 'activitypub' ),
413 - 'clientIdLabel' => \__( 'Your new Client ID:', 'activitypub' ),
414 - 'clientSecretLabel' => \__( 'Your new Client Secret:', 'activitypub' ),
415 - 'copy' => \__( 'Copy', 'activitypub' ),
416 - 'copied' => \__( 'Copied!', 'activitypub' ),
417 - 'saveWarning' => \__( 'Be sure to save this in a safe location. You will not be able to retrieve it.', 'activitypub' ),
418 - 'appRevoked' => \__( 'Application token revoked.', 'activitypub' ),
419 - 'allAppsRevoked' => \__( 'All application tokens revoked.', 'activitypub' ),
420 - 'appDeleted' => \__( 'Application deleted.', 'activitypub' ),
421 - 'allAppsDeleted' => \__( 'All registered applications deleted.', 'activitypub' ),
422 - )
423 - );
424 - }
425 -
426 - /**
427 223 * Hook into the edit_comment functionality.
428 224 *
429 225 * Disables the edit_comment capability for federated comments.
430 226 */
431 227 public static function edit_comment() {
432 - // phpcs:ignore WordPress.Security.NonceVerification
433 - $comment_id = \absint( $_GET['c'] ?? 0 );
434 - if ( Comment::was_received( $comment_id ) ) {
435 - $path = 'edit-comments.php';
228 + // Disable the edit_comment capability for federated comments.
229 + \add_filter(
230 + 'user_has_cap',
231 + function ( $allcaps, $caps, $arg ) {
232 + if ( 'edit_comment' !== $arg[0] ) {
233 + return $allcaps;
234 + }
436 235
437 - switch ( \wp_get_comment_status( $comment_id ) ) { // phpcs:ignore WordPress.Security.NonceVerification
438 - case 'spam':
439 - $path = 'edit-comments.php?comment_status=spam';
440 - break;
236 + if ( was_comment_received( $arg[2] ) ) {
237 + return false;
238 + }
441 239
442 - case 'trash':
443 - $path = 'edit-comments.php?comment_status=trash';
444 - break;
445 -
446 - case 'unapproved':
447 - $path = 'edit-comments.php?comment_status=moderated';
448 - break;
449 - }
450 -
451 - // Redirect to the appropriate comments page.
452 - \wp_safe_redirect( \admin_url( $path ) );
453 - exit;
454 - }
240 + return $allcaps;
241 + },
242 + 1,
243 + 3
244 + );
455 245 }
456 246
457 247 /**
458 248 * Hook into the edit_post functionality.
@@ -462,24 +252,24 @@
462 252 public static function edit_post() {
463 253 // Disable the edit_post capability for federated posts.
464 254 \add_filter(
465 255 'user_has_cap',
466 - static function ( $all_caps, $caps, $arg ) {
256 + function ( $allcaps, $caps, $arg ) {
467 257 if ( 'edit_post' !== $arg[0] ) {
468 - return $all_caps;
258 + return $allcaps;
469 259 }
470 260
471 261 $post = get_post( $arg[2] );
472 262
473 263 if ( ! Extra_Fields::is_extra_field_post_type( $post->post_type ) ) {
474 - return $all_caps;
264 + return $allcaps;
475 265 }
476 266
477 - if ( get_current_user_id() !== (int) $post->post_author ) {
267 + if ( (int) get_current_user_id() !== (int) $post->post_author ) {
478 268 return false;
479 269 }
480 270
481 - return $all_caps;
271 + return $allcaps;
482 272 },
483 273 1,
484 274 3
485 275 );
@@ -488,14 +278,24 @@
488 278 /**
489 279 * Add ActivityPub specific actions/filters to the post list view.
490 280 */
491 281 public static function list_posts() {
282 + // Show only the user's extra fields.
283 + \add_action(
284 + 'pre_get_posts',
285 + function ( $query ) {
286 + if ( $query->get( 'post_type' ) === 'ap_extrafield' ) {
287 + $query->set( 'author', get_current_user_id() );
288 + }
289 + }
290 + );
291 +
492 292 // Remove all views for the extra fields.
493 293 $screen_id = get_current_screen()->id;
494 294
495 295 add_filter(
496 296 "views_{$screen_id}",
497 - static function ( $views ) {
297 + function ( $views ) {
498 298 if ( Extra_Fields::is_extra_fields_post_type( get_current_screen()->post_type ) ) {
499 299 return array();
500 300 }
501 301
@@ -513,9 +313,10 @@
513 313 * @return array The modified actions.
514 314 */
515 315 public static function comment_row_actions( $actions, $comment ) {
516 316 if ( was_comment_received( $comment ) ) {
517 - unset( $actions['edit'], $actions['quickedit'] );
317 + unset( $actions['edit'] );
318 + unset( $actions['quickedit'] );
518 319 }
519 320
520 321 if ( in_array( get_comment_type( $comment ), Comment::get_comment_type_slugs(), true ) ) {
521 322 unset( $actions['reply'] );
@@ -660,196 +461,37 @@
660 461 /**
661 462 * Handle bulk activitypub requests.
662 463 *
663 464 * * `add_activitypub_cap` - Add the activitypub capability to the selected users.
664 - * * `remove_activitypub_cap` - Remove the activitypub capability from the selected users (redirects to confirmation page).
665 - * * `delete_actor_confirmed` - Actually remove the capability after confirmation.
465 + * * `remove_activitypub_cap` - Remove the activitypub capability from the selected users.
666 466 *
667 - * @param string $send_back The URL to send the user back to.
668 - * @param string $action The requested action.
669 - * @param array $users The selected users.
467 + * @param string $sendback The URL to send the user back to.
468 + * @param string $action The requested action.
469 + * @param array $users The selected users.
670 470 *
671 471 * @return string The URL to send the user back to.
672 472 */
673 - public static function handle_bulk_request( $send_back, $action, $users ) {
674 - switch ( $action ) {
675 - case 'add_activitypub_cap':
676 - foreach ( $users as $user_id ) {
677 - $user = new \WP_User( $user_id );
678 - $user->add_cap( 'activitypub' );
679 -
680 - // Remove user from tombstone registry if they were previously buried.
681 - $actor = Actors::get_by_id( $user_id );
682 - if ( ! \is_wp_error( $actor ) ) {
683 - Tombstone::remove( $actor->get_id(), $actor->get_url() );
684 - }
685 - }
686 - return $send_back;
687 - case 'remove_activitypub_cap':
688 - $removed_count = 0;
689 -
690 - // Remove capabilities immediately.
691 - foreach ( $users as $key => $user_id ) {
692 - $user = new \WP_User( $user_id );
693 -
694 - // Check if user has ActivityPub capability.
695 - if ( ! $user->has_cap( 'activitypub' ) ) {
696 - unset( $users[ $key ] );
697 - continue;
698 - }
699 -
700 - // Remove the capability.
701 - $user->remove_cap( 'activitypub' );
702 -
703 - // Force cache refresh for user capabilities.
704 - \wp_cache_delete( $user_id, 'users' );
705 - \wp_cache_delete( $user_id, 'user_meta' );
706 -
707 - ++$removed_count;
708 - }
709 -
710 - // Build the query args with proper array handling for fediverse deletion confirmation.
711 - $query_args = array(
712 - 'action' => 'activitypub_confirm_removal',
713 - 'send_back' => \rawurlencode( $send_back ),
714 - );
715 -
716 - // Add user IDs as separate parameters.
717 - foreach ( $users as $index => $user_id ) {
718 - $query_args[ sprintf( 'users[%d]', $index ) ] = absint( $user_id );
719 - }
720 -
721 - $confirmation_url = \add_query_arg( $query_args, \admin_url( 'users.php' ) );
722 -
723 - // Force redirect instead of just returning URL.
724 - \wp_safe_redirect( $confirmation_url );
725 - exit;
726 - case 'delete_actor_confirmed':
727 - // Use unified method with no fediverse deletion (keep).
728 - return self::process_capability_removal( $users, 'keep', $send_back );
729 - default:
730 - return $send_back;
473 + public static function handle_bulk_request( $sendback, $action, $users ) {
474 + if (
475 + 'remove_activitypub_cap' !== $action &&
476 + 'add_activitypub_cap' !== $action
477 + ) {
478 + return $sendback;
731 479 }
732 - }
733 480
734 - /**
735 - * Handle the bulk capability removal page request directly.
736 - */
737 - public static function handle_bulk_actor_delete_page() {
738 -
739 - // Check permissions.
740 - if ( ! \current_user_can( 'edit_users' ) ) {
741 - \wp_die( \esc_html__( 'You do not have sufficient permissions to access this page.', 'activitypub' ) );
481 + foreach ( $users as $user_id ) {
482 + $user = new \WP_User( $user_id );
483 + if ( 'add_activitypub_cap' === $action ) {
484 + $user->add_cap( 'activitypub' );
485 + } elseif ( 'remove_activitypub_cap' === $action ) {
486 + $user->remove_cap( 'activitypub' );
487 + }
742 488 }
743 489
744 - // Get parameters.
745 - // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
746 - $users = \wp_unslash( $_GET['users'] ?? array() );
747 - // phpcs:ignore WordPress.Security.NonceVerification
748 - $send_back = \urldecode( \sanitize_text_field( \wp_unslash( $_GET['send_back'] ?? '' ) ) );
749 -
750 - // Sanitize user IDs.
751 - $users = \array_map( 'absint', (array) $users );
752 - $users = \array_filter( $users );
753 -
754 - // Validate send_back URL.
755 - if ( empty( $send_back ) ) {
756 - $send_back = \admin_url( 'users.php' );
757 - }
758 -
759 - // Load template and exit to prevent WordPress from trying to load other admin pages.
760 - \load_template(
761 - ACTIVITYPUB_PLUGIN_DIR . 'templates/bulk-actor-delete-confirmation.php',
762 - false,
763 - array(
764 - 'users' => $users,
765 - 'send_back' => $send_back,
766 - )
767 - );
768 - exit;
490 + return $sendback;
769 491 }
770 492
771 -
772 493 /**
773 - * Handle the bulk capability removal confirmation form submission.
774 - */
775 - public static function handle_bulk_actor_delete_confirmation() {
776 - // Verify nonce.
777 - if ( ! \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ?? '' ) ), 'bulk-users' ) ) {
778 - \wp_die( \esc_html__( 'Security check failed.', 'activitypub' ) );
779 - }
780 -
781 - // Check permissions.
782 - if ( ! \current_user_can( 'edit_users' ) ) {
783 - \wp_die( \esc_html__( 'You do not have sufficient permissions to perform this action.', 'activitypub' ) );
784 - }
785 -
786 - // Get form data.
787 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
788 - $selected_users = \wp_unslash( $_POST['selected_users'] ?? array() );
789 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
790 - $remove_from_fediverse = \wp_unslash( $_POST['remove_from_fediverse'] ?? array() );
791 - $send_back = \esc_url_raw( \wp_unslash( $_POST['send_back'] ?? '' ) );
792 -
793 - // Sanitize user IDs.
794 - $selected_users = \array_map( 'absint', (array) $selected_users );
795 - $selected_users = \array_filter( $selected_users );
796 -
797 - if ( empty( $selected_users ) ) {
798 - \wp_safe_redirect( $send_back );
799 - exit;
800 - }
801 -
802 - // Process capability removal using unified method.
803 - $result = self::process_capability_removal( $selected_users, $remove_from_fediverse, $send_back );
804 -
805 - // Redirect back.
806 - \wp_safe_redirect( $result );
807 - exit;
808 - }
809 -
810 -
811 - /**
812 - * Process fediverse deletion for users (capabilities already removed).
813 - *
814 - * @param array $users Array of user IDs.
815 - * @param array|string $remove_from_fediverse Array of user IDs to delete from fediverse, or 'delete'/'keep' for all users.
816 - * @param string $send_back URL to redirect back to.
817 - *
818 - * @return string The URL to redirect to.
819 - */
820 - public static function process_capability_removal( $users, $remove_from_fediverse, $send_back ) {
821 - // Normalize fediverse removal parameter.
822 - if ( is_string( $remove_from_fediverse ) ) {
823 - // Legacy format: 'delete' or 'keep' for all users.
824 - $delete_all = ( 'delete' === $remove_from_fediverse );
825 - $users_to_delete = $delete_all ? $users : array();
826 - } else {
827 - // New format: array of specific user IDs to delete from fediverse.
828 - $remove_from_fediverse = \array_map( 'absint', (array) $remove_from_fediverse );
829 - $users_to_delete = \array_filter( $remove_from_fediverse );
830 - }
831 -
832 - // Schedule delete activities for users who should be removed from fediverse.
833 - if ( ! empty( $users_to_delete ) ) {
834 - // Temporarily bypass capability checks for delete activity scheduling since capabilities were already removed.
835 - \add_filter( 'activitypub_user_can_activitypub', '__return_true' );
836 -
837 - \array_map(
838 - array(
839 - Actor::class,
840 - 'schedule_user_delete',
841 - ),
842 - $users_to_delete
843 - );
844 -
845 - \remove_filter( 'activitypub_user_can_activitypub', '__return_true' );
846 - }
847 -
848 - return $send_back;
849 - }
850 -
851 - /**
852 494 * Add ActivityPub infos to the dashboard glance items.
853 495 *
854 496 * @param array $items The existing glance items.
855 497 *
@@ -855,9 +497,9 @@
855 497 *
856 498 * @return array The extended glance items.
857 499 */
858 500 public static function dashboard_glance_items( $items ) {
859 - \add_filter( 'number_format_i18n', '\Activitypub\custom_large_numbers', 10, 2 );
501 + \add_filter( 'number_format_i18n', '\Activitypub\custom_large_numbers', 10, 3 );
860 502
861 503 if ( user_can_activitypub( \get_current_user_id() ) ) {
862 504 $follower_count = sprintf(
863 505 // translators: %s: number of followers.
@@ -961,321 +603,10 @@
961 603 echo '<br>' . wp_strip_all_tags( $update->upgrade_notice ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
962 604 }
963 605
964 606 /**
965 - * Adds meta box on wp-admin/tools.php.
607 + * Adds metabox on wp-admin/tools.php.
966 608 */
967 609 public static function tool_box() {
968 610 \load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/toolbox.php' );
969 - }
970 -
971 - /**
972 - * Open the help tab.
973 - *
974 - * This function is used to open the help tab,
975 - * it is triggered by the hash in the URL.
976 - */
977 - public static function open_help_tab() {
978 - // get all tabs registered for the ActivityPub settings page.
979 - $tabs = \get_current_screen()->get_help_tabs();
980 - $ids = \array_values( \wp_list_pluck( $tabs, 'id' ) );
981 - $ids = \array_map(
982 - static function ( $id ) {
983 - return '#tab-link-' . $id;
984 - },
985 - $ids
986 - );
987 - ?>
988 - <script type="text/javascript">
989 - function activitypub_open_help_tab(event) {
990 - const allowed_ids = <?php echo \wp_json_encode( $ids ); ?>;
991 -
992 - if ( allowed_ids.includes( window.location.hash ) ) {
993 - const delay = ( event && event.type === 'hashchange' ) ? 0 : 200;
994 -
995 - setTimeout( function() {
996 - document.getElementById( 'contextual-help-link' ).click();
997 - document.querySelector( window.location.hash + ' > a[href^="#tab-panel-"]' ).click();
998 - }, delay );
999 - }
1000 - }
1001 - window.addEventListener( 'DOMContentLoaded', activitypub_open_help_tab );
1002 - window.addEventListener( 'hashchange', activitypub_open_help_tab );
1003 - </script>
1004 - <?php
1005 - }
1006 -
1007 - /**
1008 - * AJAX handler for moderation settings (add/remove blocks).
1009 - */
1010 - public static function ajax_moderation_settings() {
1011 - $context = \sanitize_text_field( \wp_unslash( $_POST['context'] ?? '' ) );
1012 - $operation = \sanitize_text_field( \wp_unslash( $_POST['operation'] ?? '' ) );
1013 - $type = \sanitize_text_field( \wp_unslash( $_POST['type'] ?? '' ) );
1014 - $value = \sanitize_text_field( \wp_unslash( $_POST['value'] ?? '' ) );
1015 -
1016 - // Validate required parameters.
1017 - if ( ! in_array( $context, array( 'user', 'site' ), true ) || ! in_array( $operation, array( 'add', 'remove' ), true ) ) {
1018 - \wp_send_json_error( array( 'message' => \__( 'Invalid context or action.', 'activitypub' ) ) );
1019 - }
1020 -
1021 - if ( empty( $type ) || empty( $value ) || ! in_array( $type, array( 'domain', 'keyword' ), true ) ) {
1022 - \wp_send_json_error( array( 'message' => \__( 'Invalid parameters.', 'activitypub' ) ) );
1023 - }
1024 -
1025 - // Verify nonce for all operations.
1026 - if ( ! \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ?? '' ) ), 'activitypub_moderation_settings' ) ) {
1027 - \wp_send_json_error( array( 'message' => \__( 'Invalid nonce.', 'activitypub' ) ) );
1028 - }
1029 -
1030 - if ( ! \current_user_can( 'manage_options' ) ) {
1031 - \wp_send_json_error( array( 'message' => \__( 'You do not have permission to perform this action.', 'activitypub' ) ) );
1032 - }
1033 -
1034 - if ( 'user' === $context ) {
1035 - $user_id = (int) ( \sanitize_text_field( \wp_unslash( $_POST['user_id'] ?? 0 ) ) );
1036 -
1037 - // Check permissions.
1038 - if ( \get_current_user_id() !== $user_id ) {
1039 - \wp_send_json_error( array( 'message' => \__( 'You do not have permission to perform this action.', 'activitypub' ) ) );
1040 - }
1041 -
1042 - if ( ! $user_id ) {
1043 - \wp_send_json_error( array( 'message' => \__( 'Invalid user ID.', 'activitypub' ) ) );
1044 - }
1045 -
1046 - if ( 'add' === $operation ) {
1047 - $success = Moderation::add_user_block( $user_id, $type, $value );
1048 - $error_message = \__( 'Failed to add block.', 'activitypub' );
1049 - } else {
1050 - $success = Moderation::remove_user_block( $user_id, $type, $value );
1051 - $error_message = \__( 'Failed to remove block.', 'activitypub' );
1052 - }
1053 - } elseif ( 'add' === $operation ) {
1054 - $success = Moderation::add_site_block( $type, $value );
1055 - $error_message = \__( 'Failed to add block.', 'activitypub' );
1056 - } else {
1057 - $success = Moderation::remove_site_block( $type, $value );
1058 - $error_message = \__( 'Failed to remove block.', 'activitypub' );
1059 - }
1060 -
1061 - if ( $success ) {
1062 - \wp_send_json_success();
1063 - } else {
1064 - \wp_send_json_error( array( 'message' => $error_message ) );
1065 - }
1066 - }
1067 -
1068 - /**
1069 - * AJAX handler for blocklist subscriptions (add/remove).
1070 - */
1071 - public static function ajax_blocklist_subscription() {
1072 - $operation = \sanitize_text_field( \wp_unslash( $_POST['operation'] ?? '' ) );
1073 - $url = \sanitize_url( \wp_unslash( $_POST['url'] ?? '' ) );
1074 -
1075 - // Validate required parameters.
1076 - if ( ! \in_array( $operation, array( 'add', 'remove' ), true ) ) {
1077 - \wp_send_json_error( array( 'message' => \__( 'Invalid operation.', 'activitypub' ) ) );
1078 - }
1079 -
1080 - if ( empty( $url ) ) {
1081 - \wp_send_json_error( array( 'message' => \__( 'Invalid URL.', 'activitypub' ) ) );
1082 - }
1083 -
1084 - // Verify nonce.
1085 - if ( ! \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ?? '' ) ), 'activitypub_moderation_settings' ) ) {
1086 - \wp_send_json_error( array( 'message' => \__( 'Invalid nonce.', 'activitypub' ) ) );
1087 - }
1088 -
1089 - if ( ! \current_user_can( 'manage_options' ) ) {
1090 - \wp_send_json_error( array( 'message' => \__( 'You do not have permission to perform this action.', 'activitypub' ) ) );
1091 - }
1092 -
1093 - if ( 'add' === $operation ) {
1094 - // First add the subscription (validates URL format).
1095 - if ( ! Blocklist_Subscriptions::add( $url ) ) {
1096 - \wp_send_json_error( array( 'message' => \__( 'Invalid URL.', 'activitypub' ) ) );
1097 - }
1098 -
1099 - // Then sync to validate it works and import domains.
1100 - $result = Blocklist_Subscriptions::sync( $url );
1101 - if ( false === $result ) {
1102 - // Remove the subscription since sync failed.
1103 - Blocklist_Subscriptions::remove( $url );
1104 - \wp_send_json_error( array( 'message' => \__( 'Failed to fetch blocklist. The URL may be unreachable or not contain valid domains.', 'activitypub' ) ) );
1105 - }
1106 -
1107 - \wp_send_json_success();
1108 - } elseif ( Blocklist_Subscriptions::remove( $url ) ) {
1109 - \wp_send_json_success();
1110 - } else {
1111 - \wp_send_json_error( array( 'message' => \__( 'Failed to remove subscription.', 'activitypub' ) ) );
1112 - }
1113 - }
1114 -
1115 - /**
1116 - * AJAX handler for registering a new OAuth client from the user profile.
1117 - *
1118 - * @since 8.1.0
1119 - */
1120 - public static function ajax_register_oauth_client() {
1121 - // Verify nonce.
1122 - if ( ! \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ?? '' ) ), 'activitypub_connected_apps' ) ) {
1123 - \wp_send_json_error( array( 'message' => \__( 'Invalid nonce.', 'activitypub' ) ) );
1124 - }
1125 -
1126 - if ( ! \current_user_can( 'manage_options' ) ) {
1127 - \wp_send_json_error( array( 'message' => \__( 'You do not have permission to perform this action.', 'activitypub' ) ) );
1128 - }
1129 -
1130 - $name = \sanitize_text_field( \wp_unslash( $_POST['name'] ?? '' ) );
1131 - $redirect_uri = \sanitize_url( \wp_unslash( $_POST['redirect_uri'] ?? '' ) );
1132 -
1133 - if ( empty( $name ) ) {
1134 - \wp_send_json_error( array( 'message' => \__( 'Application name is required.', 'activitypub' ) ) );
1135 - }
1136 -
1137 - if ( empty( $redirect_uri ) ) {
1138 - \wp_send_json_error( array( 'message' => \__( 'Redirect URI is required.', 'activitypub' ) ) );
1139 - }
1140 -
1141 - $result = Client::register(
1142 - array(
1143 - 'name' => $name,
1144 - 'redirect_uris' => array( $redirect_uri ),
1145 - 'is_public' => false,
1146 - )
1147 - );
1148 -
1149 - if ( \is_wp_error( $result ) ) {
1150 - \wp_send_json_error( array( 'message' => $result->get_error_message() ) );
1151 - }
1152 -
1153 - $data = array(
1154 - 'client_id' => $result['client_id'],
1155 - 'created' => \date_i18n( \get_option( 'date_format' ) ),
1156 - );
1157 -
1158 - if ( ! empty( $result['client_secret'] ) ) {
1159 - $data['client_secret'] = $result['client_secret'];
1160 - }
1161 -
1162 - \wp_send_json_success( $data );
1163 - }
1164 -
1165 - /**
1166 - * AJAX handler for deleting a registered OAuth client.
1167 - *
1168 - * @since 8.1.0
1169 - */
1170 - public static function ajax_delete_oauth_client() {
1171 - // Verify nonce.
1172 - if ( ! \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ?? '' ) ), 'activitypub_connected_apps' ) ) {
1173 - \wp_send_json_error( array( 'message' => \__( 'Invalid nonce.', 'activitypub' ) ) );
1174 - }
1175 -
1176 - if ( ! \current_user_can( 'manage_options' ) ) {
1177 - \wp_send_json_error( array( 'message' => \__( 'You do not have permission to perform this action.', 'activitypub' ) ) );
1178 - }
1179 -
1180 - $client_id = \sanitize_text_field( \wp_unslash( $_POST['client_id'] ?? '' ) );
1181 -
1182 - if ( empty( $client_id ) ) {
1183 - \wp_send_json_error( array( 'message' => \__( 'Invalid client ID.', 'activitypub' ) ) );
1184 - }
1185 -
1186 - $deleted = Client::delete( $client_id );
1187 -
1188 - if ( ! $deleted ) {
1189 - \wp_send_json_error( array( 'message' => \__( 'Failed to delete application.', 'activitypub' ) ) );
1190 - }
1191 -
1192 - \wp_send_json_success( array( 'deleted' => true ) );
1193 - }
1194 -
1195 - /**
1196 - * AJAX handler for deleting all manually registered OAuth clients.
1197 - *
1198 - * @since 8.1.0
1199 - */
1200 - public static function ajax_delete_all_oauth_clients() {
1201 - // Verify nonce.
1202 - if ( ! \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ?? '' ) ), 'activitypub_connected_apps' ) ) {
1203 - \wp_send_json_error( array( 'message' => \__( 'Invalid nonce.', 'activitypub' ) ) );
1204 - }
1205 -
1206 - if ( ! \current_user_can( 'manage_options' ) ) {
1207 - \wp_send_json_error( array( 'message' => \__( 'You do not have permission to perform this action.', 'activitypub' ) ) );
1208 - }
1209 -
1210 - $clients = Client::get_manually_registered();
1211 -
1212 - foreach ( $clients as $client ) {
1213 - Client::delete( $client->get_client_id() );
1214 - }
1215 -
1216 - \wp_send_json_success( array( 'deleted' => ! empty( $clients ) ) );
1217 - }
1218 -
1219 - /**
1220 - * AJAX handler for revoking an OAuth token from the user profile.
1221 - *
1222 - * Follows the WordPress core Application Passwords pattern.
1223 - *
1224 - * @since 8.1.0
1225 - */
1226 - public static function ajax_revoke_oauth_token() {
1227 - // Verify nonce.
1228 - if ( ! \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ?? '' ) ), 'activitypub_connected_apps' ) ) {
1229 - \wp_send_json_error( array( 'message' => \__( 'Invalid nonce.', 'activitypub' ) ) );
1230 - }
1231 -
1232 - if ( ! \current_user_can( 'read' ) ) {
1233 - \wp_send_json_error( array( 'message' => \__( 'You do not have permission to perform this action.', 'activitypub' ) ) );
1234 - }
1235 -
1236 - $meta_key = \sanitize_text_field( \wp_unslash( $_POST['meta_key'] ?? '' ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Not a DB query parameter.
1237 -
1238 - // Verify the meta key belongs to our token prefix.
1239 - if ( 0 !== strpos( $meta_key, Token::META_PREFIX ) ) {
1240 - \wp_send_json_error( array( 'message' => \__( 'Invalid token.', 'activitypub' ) ) );
1241 - }
1242 -
1243 - $user_id = \get_current_user_id();
1244 - $token_data = \get_user_meta( $user_id, $meta_key, true );
1245 -
1246 - // Verify the token belongs to the current user.
1247 - if ( empty( $token_data ) || ! is_array( $token_data ) ) {
1248 - \wp_send_json_error( array( 'message' => \__( 'Token not found.', 'activitypub' ) ) );
1249 - }
1250 -
1251 - // Delete the token.
1252 - \delete_user_meta( $user_id, $meta_key );
1253 -
1254 - // Delete the associated refresh token index.
1255 - if ( ! empty( $token_data['refresh_token_hash'] ) ) {
1256 - \delete_user_meta( $user_id, Token::REFRESH_INDEX_PREFIX . $token_data['refresh_token_hash'] );
1257 - }
1258 -
1259 - \wp_send_json_success( array( 'deleted' => true ) );
1260 - }
1261 -
1262 - /**
1263 - * AJAX handler for revoking all OAuth tokens for the current user.
1264 - *
1265 - * @since 8.1.0
1266 - */
1267 - public static function ajax_revoke_all_oauth_tokens() {
1268 - // Verify nonce.
1269 - if ( ! \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ?? '' ) ), 'activitypub_connected_apps' ) ) {
1270 - \wp_send_json_error( array( 'message' => \__( 'Invalid nonce.', 'activitypub' ) ) );
1271 - }
1272 -
1273 - if ( ! \current_user_can( 'read' ) ) {
1274 - \wp_send_json_error( array( 'message' => \__( 'You do not have permission to perform this action.', 'activitypub' ) ) );
1275 - }
1276 -
1277 - $count = Token::revoke_all_for_user( \get_current_user_id() );
1278 -
1279 - \wp_send_json_success( array( 'deleted' => $count > 0 ) );
1280 611 }
1281 612 }