Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c3a65ff
Init
pfefferle Jul 30, 2025
6aedee8
Merge branch 'trunk' into add/self-destruct
pfefferle Aug 4, 2025
473b795
Enhance self_destruct CLI command for user deletion
pfefferle Aug 6, 2025
d5e3566
Improve CLI self-destruct messaging and process
pfefferle Aug 6, 2025
6baccc0
Merge branch 'trunk' into add/self-destruct
pfefferle Aug 6, 2025
ed88240
Add self-destruct completion notification for ActivityPub
pfefferle Aug 6, 2025
14d363e
Update self-destruct completion hook and logic
pfefferle Aug 6, 2025
e60da0b
Update includes/class-cli.php
pfefferle Aug 6, 2025
a44cdda
Add changelog
matticbot Aug 6, 2025
68d3f74
Refactor outbox deletion to use Activity object
pfefferle Aug 6, 2025
aebbe8a
Merge branch 'trunk' into add/self-destruct
pfefferle Aug 7, 2025
f1f03b6
Merge branch 'trunk' into add/self-destruct
pfefferle Aug 26, 2025
2c14691
Refactor and enhance self-destruct CLI command
pfefferle Aug 26, 2025
1d6c851
Add missing periods to inline comments
pfefferle Aug 26, 2025
7c963a2
Update includes/class-cli.php
pfefferle Aug 26, 2025
fce5300
Update includes/class-cli.php
pfefferle Aug 26, 2025
36bb6ab
Update includes/class-cli.php
pfefferle Aug 26, 2025
99e7644
Fix array alignment in meta_query for CLI command
pfefferle Aug 26, 2025
46c4be0
Remove emojis from CLI progress messages
pfefferle Aug 26, 2025
a5a6160
Improve CLI self-destruct process instructions
pfefferle Aug 26, 2025
4047836
Merge branch 'trunk' into add/self-destruct
pfefferle Aug 27, 2025
43e95d2
Fix incorrect import in class-cli.php
pfefferle Aug 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/changelog/2046-from-description
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Adds a self-destruct feature to remove a blog from the Fediverse by sending Delete activities to followers.
284 changes: 279 additions & 5 deletions includes/class-cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@

namespace Activitypub;

use Activitypub\Activity\Activity;
use Activitypub\Collection\Actors;
use Activitypub\Collection\Followers;
use Activitypub\Collection\Outbox;
use Activitypub\Scheduler;

/**
* WP-CLI commands.
Expand All @@ -21,17 +23,289 @@ class Cli extends \WP_CLI_Command {
/**
* Remove the entire blog from the Fediverse.
*
* This command permanently removes your blog from ActivityPub networks by sending
* Delete activities to all followers. This action is IRREVERSIBLE.
*
* ## OPTIONS
*
* [--status]
* : Check the status of the self-destruct process instead of running it.
* Use this to monitor progress after initiating the deletion process.
*
* [--yes]
* : Skip the confirmation prompt and proceed with deletion immediately.
* Use with extreme caution as this bypasses all safety checks.
*
* ## EXAMPLES
*
* $ wp activitypub self-destruct
* # Start the self-destruct process (with confirmation prompt)
* $ wp activitypub self_destruct
*
* # Check the status of an ongoing self-destruct process
* $ wp activitypub self_destruct --status
*
* # Force deletion without confirmation (dangerous!)
* $ wp activitypub self_destruct --yes
*
* ## WHAT THIS DOES
*
* - Finds all users with ActivityPub capabilities
* - Creates Delete activities for each user
* - Sends these activities to all followers
* - Removes your blog from ActivityPub discovery
* - Sets a flag to track completion status
*
* ## IMPORTANT NOTES
*
* - This action cannot be undone
* - Keep the ActivityPub plugin active during the process
* - The process may take several minutes to complete
* - You will be notified when the process finishes
*
* @param array|null $args The positional arguments (unused).
* @param array|null $assoc_args The associative arguments (--status, --yes).
*
* @return void
*/
public function self_destruct( $args, $assoc_args = array() ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
// Check if --status flag is provided.
if ( isset( $assoc_args['status'] ) ) {
$this->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.
*
* @return void
*/
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 );
$this->display_completion_message( $processed );
}

/**
* Display the self-destruct warning message.
*
* @return void
*/
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( '' );
}

/**
* 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.
*
* @param array|null $args The arguments.
* @param array|null $assoc_args The associative arguments.
* @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.
*
* @return void
*/
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
*
* @return void
*/
public function self_destruct( $args, $assoc_args ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
\WP_CLI::warning( 'Self-Destructing is not implemented yet.' );
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( '%C💡 To start the process, run:%n' ) );
\WP_CLI::line( \WP_CLI::colorize( '%C wp 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',
'compare' => '=',
),
),
)
);

// 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( '' );

if ( $pending_count > 0 ) {
\WP_CLI::line( \WP_CLI::colorize( "%Y📊 Progress: {$pending_count} Delete Activities still pending%n" ) );
} else {
\WP_CLI::line( \WP_CLI::colorize( '%Y📊 Progress: All Delete Activities have been processed%n' ) );
\WP_CLI::line( \WP_CLI::colorize( '%Y⚠️ Completion notification may be pending%n' ) );
}

\WP_CLI::line( '' );
\WP_CLI::line( \WP_CLI::colorize( '%Y💡 Note: The process may take several minutes to complete.%n' ) );
}

\WP_CLI::line( '' );
}

/**
Expand Down
18 changes: 18 additions & 0 deletions includes/wp-admin/class-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ public static function admin_notices() {
if ( ! $current_screen ) {
return;
}

// Check for self-destruct completion notice.
$self_destruct_complete = \get_option( 'activitypub_self_destruct_complete' );
if ( $self_destruct_complete ) {
// Show the notice only once, then remove it.
\delete_option( 'activitypub_self_destruct_complete' );
?>
<div class="notice notice-success is-dismissible">
<p>
<strong><?php esc_html_e( 'ActivityPub Self-Destruct Complete!', 'activitypub' ); ?></strong>
</p>
<p>
<?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' ); ?>
</p>
</div>
<?php
}

if ( 'edit' === $current_screen->base && Extra_Fields::is_extra_fields_post_type( $current_screen->post_type ) ) {
?>
<div class="notice" style="margin: 0; background: none; border: none; box-shadow: none; padding: 15px 0 0 0; font-size: 14px;">
Expand Down
Loading