1212use Activitypub \Comment ;
1313use Activitypub \Model \Blog ;
1414use Activitypub \Moderation ;
15+ use Activitypub \Scheduler \Actor ;
1516
1617use function Activitypub \count_followers ;
1718use function Activitypub \get_content_visibility ;
@@ -52,6 +53,9 @@ public static function init() {
5253 \add_filter ( 'bulk_actions-users ' , array ( self ::class, 'user_bulk_options ' ) );
5354 \add_filter ( 'handle_bulk_actions-users ' , array ( self ::class, 'handle_bulk_request ' ), 10 , 3 );
5455
56+ \add_action ( 'admin_post_delete_actor_confirmed ' , array ( self ::class, 'handle_bulk_actor_delete_confirmation ' ) );
57+ \add_action ( 'admin_action_activitypub_confirm_removal ' , array ( self ::class, 'handle_bulk_actor_delete_page ' ) );
58+
5559 if ( user_can_activitypub ( \get_current_user_id () ) ) {
5660 \add_action ( 'show_user_profile ' , array ( self ::class, 'add_profile ' ) );
5761 }
@@ -582,7 +586,8 @@ public static function user_bulk_options( $actions ) {
582586 * Handle bulk activitypub requests.
583587 *
584588 * * `add_activitypub_cap` - Add the activitypub capability to the selected users.
585- * * `remove_activitypub_cap` - Remove the activitypub capability from the selected users.
589+ * * `remove_activitypub_cap` - Remove the activitypub capability from the selected users (redirects to confirmation page).
590+ * * `delete_actor_confirmed` - Actually remove the capability after confirmation.
586591 *
587592 * @param string $send_back The URL to send the user back to.
588593 * @param string $action The requested action.
@@ -591,20 +596,172 @@ public static function user_bulk_options( $actions ) {
591596 * @return string The URL to send the user back to.
592597 */
593598 public static function handle_bulk_request ( $ send_back , $ action , $ users ) {
594- if (
595- 'remove_activitypub_cap ' !== $ action &&
596- 'add_activitypub_cap ' !== $ action
597- ) {
598- return $ send_back ;
599+ switch ( $ action ) {
600+ case 'add_activitypub_cap ' :
601+ foreach ( $ users as $ user_id ) {
602+ $ user = new \WP_User ( $ user_id );
603+ $ user ->add_cap ( 'activitypub ' );
604+ }
605+ return $ send_back ;
606+ case 'remove_activitypub_cap ' :
607+ $ removed_count = 0 ;
608+
609+ // Remove capabilities immediately.
610+ foreach ( $ users as $ key => $ user_id ) {
611+ $ user = new \WP_User ( $ user_id );
612+
613+ // Check if user has ActivityPub capability.
614+ if ( ! $ user ->has_cap ( 'activitypub ' ) ) {
615+ unset( $ users [ $ key ] );
616+ continue ;
617+ }
618+
619+ // Remove the capability.
620+ $ user ->remove_cap ( 'activitypub ' );
621+
622+ // Force cache refresh for user capabilities.
623+ \wp_cache_delete ( $ user_id , 'users ' );
624+ \wp_cache_delete ( $ user_id , 'user_meta ' );
625+
626+ ++$ removed_count ;
627+ }
628+
629+ // Build the query args with proper array handling for fediverse deletion confirmation.
630+ $ query_args = array (
631+ 'action ' => 'activitypub_confirm_removal ' ,
632+ 'send_back ' => \rawurlencode ( $ send_back ),
633+ );
634+
635+ // Add user IDs as separate parameters.
636+ foreach ( $ users as $ index => $ user_id ) {
637+ $ query_args [ sprintf ( 'users[%d] ' , $ index ) ] = absint ( $ user_id );
638+ }
639+
640+ $ confirmation_url = \add_query_arg ( $ query_args , \admin_url ( 'users.php ' ) );
641+
642+ // Force redirect instead of just returning URL.
643+ \wp_safe_redirect ( $ confirmation_url );
644+ exit ;
645+ case 'delete_actor_confirmed ' :
646+ // Use unified method with no fediverse deletion (keep).
647+ return self ::process_capability_removal ( $ users , 'keep ' , $ send_back );
648+ default :
649+ return $ send_back ;
599650 }
651+ }
600652
601- foreach ( $ users as $ user_id ) {
602- $ user = new \WP_User ( $ user_id );
603- if ( 'add_activitypub_cap ' === $ action ) {
604- $ user ->add_cap ( 'activitypub ' );
605- } elseif ( 'remove_activitypub_cap ' === $ action ) {
606- $ user ->remove_cap ( 'activitypub ' );
607- }
653+ /**
654+ * Handle the bulk capability removal page request directly.
655+ */
656+ public static function handle_bulk_actor_delete_page () {
657+
658+ // Check permissions.
659+ if ( ! \current_user_can ( 'edit_users ' ) ) {
660+ \wp_die ( \esc_html__ ( 'You do not have sufficient permissions to access this page. ' , 'activitypub ' ) );
661+ }
662+
663+ // Get parameters.
664+ // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
665+ $ users = \wp_unslash ( $ _GET ['users ' ] ?? array () );
666+ // phpcs:ignore WordPress.Security.NonceVerification
667+ $ send_back = \urldecode ( \sanitize_text_field ( \wp_unslash ( $ _GET ['send_back ' ] ?? '' ) ) );
668+
669+ // Sanitize user IDs.
670+ $ users = \array_map ( 'absint ' , (array ) $ users );
671+ $ users = \array_filter ( $ users );
672+
673+ // Validate send_back URL.
674+ if ( empty ( $ send_back ) ) {
675+ $ send_back = \admin_url ( 'users.php ' );
676+ }
677+
678+ // Load template and exit to prevent WordPress from trying to load other admin pages.
679+ \load_template (
680+ ACTIVITYPUB_PLUGIN_DIR . 'templates/bulk-actor-delete-confirmation.php ' ,
681+ false ,
682+ array (
683+ 'users ' => $ users ,
684+ 'send_back ' => $ send_back ,
685+ )
686+ );
687+ exit ;
688+ }
689+
690+
691+ /**
692+ * Handle the bulk capability removal confirmation form submission.
693+ */
694+ public static function handle_bulk_actor_delete_confirmation () {
695+ // Verify nonce.
696+ if ( ! \wp_verify_nonce ( \sanitize_text_field ( \wp_unslash ( $ _POST ['_wpnonce ' ] ?? '' ) ), 'bulk-users ' ) ) {
697+ \wp_die ( \esc_html__ ( 'Security check failed. ' , 'activitypub ' ) );
698+ }
699+
700+ // Check permissions.
701+ if ( ! \current_user_can ( 'edit_users ' ) ) {
702+ \wp_die ( \esc_html__ ( 'You do not have sufficient permissions to perform this action. ' , 'activitypub ' ) );
703+ }
704+
705+ // Get form data.
706+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
707+ $ selected_users = \wp_unslash ( $ _POST ['selected_users ' ] ?? array () );
708+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
709+ $ remove_from_fediverse = \wp_unslash ( $ _POST ['remove_from_fediverse ' ] ?? array () );
710+ $ send_back = \esc_url_raw ( \wp_unslash ( $ _POST ['send_back ' ] ?? '' ) );
711+
712+ // Sanitize user IDs.
713+ $ selected_users = \array_map ( 'absint ' , (array ) $ selected_users );
714+ $ selected_users = \array_filter ( $ selected_users );
715+
716+ if ( empty ( $ selected_users ) ) {
717+ \wp_safe_redirect ( $ send_back );
718+ exit ;
719+ }
720+
721+ // Process capability removal using unified method.
722+ $ result = self ::process_capability_removal ( $ selected_users , $ remove_from_fediverse , $ send_back );
723+
724+ // Redirect back.
725+ \wp_safe_redirect ( $ result );
726+ exit ;
727+ }
728+
729+
730+ /**
731+ * Process fediverse deletion for users (capabilities already removed).
732+ *
733+ * @param array $users Array of user IDs.
734+ * @param array|string $remove_from_fediverse Array of user IDs to delete from fediverse, or 'delete'/'keep' for all users.
735+ * @param string $send_back URL to redirect back to.
736+ *
737+ * @return string The URL to redirect to.
738+ */
739+ public static function process_capability_removal ( $ users , $ remove_from_fediverse , $ send_back ) {
740+ // Normalize fediverse removal parameter.
741+ if ( is_string ( $ remove_from_fediverse ) ) {
742+ // Legacy format: 'delete' or 'keep' for all users.
743+ $ delete_all = ( 'delete ' === $ remove_from_fediverse );
744+ $ users_to_delete = $ delete_all ? $ users : array ();
745+ } else {
746+ // New format: array of specific user IDs to delete from fediverse.
747+ $ remove_from_fediverse = \array_map ( 'absint ' , (array ) $ remove_from_fediverse );
748+ $ users_to_delete = \array_filter ( $ remove_from_fediverse );
749+ }
750+
751+ // Schedule delete activities for users who should be removed from fediverse.
752+ if ( ! empty ( $ users_to_delete ) ) {
753+ // Temporarily bypass capability checks for delete activity scheduling since capabilities were already removed.
754+ \add_filter ( 'activitypub_user_can_activitypub ' , '__return_true ' );
755+
756+ \array_map (
757+ array (
758+ Actor::class,
759+ 'schedule_user_delete ' ,
760+ ),
761+ $ users_to_delete
762+ );
763+
764+ \remove_filter ( 'activitypub_user_can_activitypub ' , '__return_true ' );
608765 }
609766
610767 return $ send_back ;
0 commit comments