show_self_destruct_status(); return; } // Check if self-destruct has already been run. if ( \get_option( 'activitypub_self_destruct' ) ) { \WP_CLI::error( 'Self-destruct has already been initiated. The process may still be running or has completed.' . PHP_EOL . \WP_CLI::colorize( 'To check the status, run: %Bwp activitypub self-destruct --status%n' ) ); return; } $this->execute_self_destruct( $assoc_args ); } /** * Execute the self-destruct process. * * This method handles the actual deletion process: * 1. Displays warning and confirmation prompt * 2. Retrieves all ActivityPub-capable users * 3. Creates and schedules Delete activities for each user * 4. Sets the self-destruct flag for status tracking * 5. Provides progress feedback and completion instructions * * @param array $assoc_args The associative arguments from WP-CLI. */ private function execute_self_destruct( $assoc_args ) { $this->display_self_destruct_warning(); \WP_CLI::confirm( 'Are you absolutely sure you want to continue?', $assoc_args ); $user_ids = $this->get_activitypub_users(); if ( empty( $user_ids ) ) { \WP_CLI::warning( 'No ActivityPub users found. Nothing to delete.' ); return; } $processed = $this->process_user_deletions( $user_ids ); // Delete all remote posts. $deleted_posts = Remote_Posts::delete_all(); if ( $deleted_posts > 0 ) { \WP_CLI::line( \WP_CLI::colorize( "%G✓%n Deleted {$deleted_posts} remote post(s)." ) ); } $this->display_completion_message( $processed ); } /** * Display the self-destruct warning message. */ private function display_self_destruct_warning() { \WP_CLI::line( \WP_CLI::colorize( '%R⚠️ DESTRUCTIVE OPERATION ⚠️%n' ) ); \WP_CLI::line( '' ); $question = 'You are about to delete your blog from the Fediverse. This action is IRREVERSIBLE and will:'; \WP_CLI::line( \WP_CLI::colorize( "%y{$question}%n" ) ); \WP_CLI::line( \WP_CLI::colorize( '%y• Send Delete activities to all followers%n' ) ); \WP_CLI::line( \WP_CLI::colorize( '%y• Remove your blog from ActivityPub networks%n' ) ); \WP_CLI::line( \WP_CLI::colorize( '%y• Delete all cached remote posts%n' ) ); \WP_CLI::line( '' ); } /** * Get all users with ActivityPub capabilities. * * @return array Array of user IDs with ActivityPub capabilities. */ private function get_activitypub_users() { return \get_users( array( 'fields' => 'ID', 'capability__in' => array( 'activitypub' ), ) ); } /** * Process user deletions and create Delete activities. * * @param array $user_ids Array of user IDs to process. * * @return int Number of users successfully processed. */ private function process_user_deletions( $user_ids ) { $user_count = \count( $user_ids ); \WP_CLI::line( \WP_CLI::colorize( '%GStarting Fediverse deletion process...%n' ) ); \WP_CLI::line( \WP_CLI::colorize( "%BFound {$user_count} ActivityPub user(s) to process:%n" ) ); \WP_CLI::line( '' ); // Set the self-destruct flag. \update_option( 'activitypub_self_destruct', true ); $processed = 0; foreach ( $user_ids as $user_id ) { if ( $this->create_delete_activity_for_user( $user_id, $processed, $user_count ) ) { ++$processed; } } \WP_CLI::line( '' ); if ( 0 === $processed ) { \WP_CLI::error( 'Failed to schedule any deletions. Please check your configuration.' ); } return $processed; } /** * Create a Delete activity for a specific user. * * @param int $user_id The user ID to process. * @param int $processed Number of users already processed. * @param int $user_count Total number of users to process. * * @return bool True if the activity was created successfully, false otherwise. */ private function create_delete_activity_for_user( $user_id, $processed, $user_count ) { $actor = Actors::get_by_id( $user_id ); if ( ! $actor ) { \WP_CLI::line( \WP_CLI::colorize( "%R✗ Failed to load user ID: {$user_id}%n" ) ); return false; } $activity = new Activity(); $activity->set_actor( $actor->get_id() ); $activity->set_object( $actor->get_id() ); $activity->set_type( 'Delete' ); $result = add_to_outbox( $activity, null, $user_id ); if ( \is_wp_error( $result ) ) { \WP_CLI::line( \WP_CLI::colorize( "%R✗ Failed to schedule deletion for: %B{$actor->get_name()}%n - {$result->get_error_message()}" ) ); return false; } $current = $processed + 1; \WP_CLI::line( \WP_CLI::colorize( "%G✓%n [{$current}/{$user_count}] Scheduled deletion for: %B{$actor->get_name()}%n" ) ); return true; } /** * Display the completion message after processing. * * @param int $processed Number of users successfully processed. */ private function display_completion_message( $processed ) { if ( 0 === $processed ) { return; // Error already displayed in process_user_deletions. } \WP_CLI::success( "Successfully scheduled {$processed} user(s) for Fediverse deletion." ); \WP_CLI::line( '' ); \WP_CLI::line( \WP_CLI::colorize( '%Y📋 Next Steps:%n' ) ); \WP_CLI::line( \WP_CLI::colorize( '%Y• Keep the ActivityPub plugin active%n' ) ); \WP_CLI::line( \WP_CLI::colorize( '%Y• Delete activities will be sent automatically%n' ) ); \WP_CLI::line( \WP_CLI::colorize( '%Y• Process may take several minutes to complete%n' ) ); \WP_CLI::line( \WP_CLI::colorize( '%Y• The plugin will notify you when the process is done.%n' ) ); \WP_CLI::line( '' ); } /** * Show the status of the self-destruct process. * * Checks the current state of the self-destruct process by: * - Verifying if the process has been initiated * - Counting remaining pending Delete activities * - Displaying appropriate status messages and progress * - Providing guidance on next steps * * Status can be: * - NOT STARTED: Process hasn't been initiated * - IN PROGRESS: Delete activities are still being processed * - COMPLETED: All Delete activities have been sent */ private function show_self_destruct_status() { // Only proceed if self-destruct is active. if ( ! \get_option( 'activitypub_self_destruct', false ) ) { \WP_CLI::line( \WP_CLI::colorize( '%C❌ Status: NOT STARTED%n' ) ); \WP_CLI::line( \WP_CLI::colorize( '%CThe self-destruct process has not been initiated.%n' ) ); \WP_CLI::line( '' ); \WP_CLI::line( \WP_CLI::colorize( '%CTo start the process, run:%n %Bwp activitypub self-destruct%n' ) ); \WP_CLI::line( '' ); return; } \WP_CLI::line( \WP_CLI::colorize( '%B🔍 Self-Destruct Status Check%n' ) ); \WP_CLI::line( '' ); // Check if there are any more pending Delete activities for self-destruct. $pending_deletes = \get_posts( array( 'post_type' => Outbox::POST_TYPE, 'post_status' => 'pending', 'posts_per_page' => -1, 'fields' => 'ids', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( array( 'key' => '_activitypub_activity_type', 'value' => 'Delete', ), ), ) ); // Get count of pending Delete activities. $pending_count = count( $pending_deletes ); // If no more pending Delete activities, self-destruct is complete. if ( 0 === $pending_count ) { \WP_CLI::line( \WP_CLI::colorize( '%G✅ Status: COMPLETED%n' ) ); \WP_CLI::line( \WP_CLI::colorize( '%GYour blog has been successfully removed from the Fediverse.%n' ) ); \WP_CLI::line( '' ); \WP_CLI::line( \WP_CLI::colorize( '%Y📋 What happened:%n' ) ); \WP_CLI::line( \WP_CLI::colorize( '%Y• Delete activities were sent to all followers%n' ) ); \WP_CLI::line( \WP_CLI::colorize( '%Y• Your blog is no longer discoverable on ActivityPub networks%n' ) ); \WP_CLI::line( \WP_CLI::colorize( '%Y• The self-destruct process has finished%n' ) ); } else { \WP_CLI::line( \WP_CLI::colorize( '%Y⏳ Status: IN PROGRESS%n' ) ); \WP_CLI::line( \WP_CLI::colorize( '%YThe self-destruct process is currently running.%n' ) ); \WP_CLI::line( '' ); \WP_CLI::line( \WP_CLI::colorize( "%YProgress: {$pending_count} Delete Activities still pending%n" ) ); \WP_CLI::line( '' ); \WP_CLI::line( \WP_CLI::colorize( '%YNote: The process may take several minutes to complete.%n' ) ); } \WP_CLI::line( '' ); } }