12
12
use Activitypub \Comment ;
13
13
use Activitypub \Model \Blog ;
14
14
use Activitypub \Moderation ;
15
+ use Activitypub \Scheduler \Actor ;
15
16
16
17
use function Activitypub \count_followers ;
17
18
use function Activitypub \get_content_visibility ;
@@ -52,6 +53,9 @@ public static function init() {
52
53
\add_filter ( 'bulk_actions-users ' , array ( self ::class, 'user_bulk_options ' ) );
53
54
\add_filter ( 'handle_bulk_actions-users ' , array ( self ::class, 'handle_bulk_request ' ), 10 , 3 );
54
55
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
+
55
59
if ( user_can_activitypub ( \get_current_user_id () ) ) {
56
60
\add_action ( 'show_user_profile ' , array ( self ::class, 'add_profile ' ) );
57
61
}
@@ -582,7 +586,8 @@ public static function user_bulk_options( $actions ) {
582
586
* Handle bulk activitypub requests.
583
587
*
584
588
* * `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.
586
591
*
587
592
* @param string $send_back The URL to send the user back to.
588
593
* @param string $action The requested action.
@@ -591,20 +596,172 @@ public static function user_bulk_options( $actions ) {
591
596
* @return string The URL to send the user back to.
592
597
*/
593
598
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 ;
599
650
}
651
+ }
600
652
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 ' );
608
765
}
609
766
610
767
return $ send_back ;
0 commit comments