Skip to content

Commit 6984d23

Browse files
authored
Delete: Self-Destruct (#2046)
1 parent 5d3301a commit 6984d23

File tree

3 files changed

+294
-5
lines changed

3 files changed

+294
-5
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: added
3+
4+
Adds a self-destruct feature to remove a blog from the Fediverse by sending Delete activities to followers.

includes/class-cli.php

Lines changed: 272 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
namespace Activitypub;
99

10+
use Activitypub\Activity\Activity;
11+
use Activitypub\Collection\Actors;
1012
use Activitypub\Collection\Outbox;
1113

1214
/**
@@ -19,17 +21,282 @@ class Cli extends \WP_CLI_Command {
1921
/**
2022
* Remove the entire blog from the Fediverse.
2123
*
24+
* This command permanently removes your blog from ActivityPub networks by sending
25+
* Delete activities to all followers. This action is IRREVERSIBLE.
26+
*
27+
* ## OPTIONS
28+
*
29+
* [--status]
30+
* : Check the status of the self-destruct process instead of running it.
31+
* Use this to monitor progress after initiating the deletion process.
32+
*
33+
* [--yes]
34+
* : Skip the confirmation prompt and proceed with deletion immediately.
35+
* Use with extreme caution as this bypasses all safety checks.
36+
*
2237
* ## EXAMPLES
2338
*
24-
* $ wp activitypub self-destruct
39+
* # Start the self-destruct process (with confirmation prompt)
40+
* $ wp activitypub self_destruct
41+
*
42+
* # Check the status of an ongoing self-destruct process
43+
* $ wp activitypub self_destruct --status
44+
*
45+
* # Force deletion without confirmation (dangerous!)
46+
* $ wp activitypub self_destruct --yes
47+
*
48+
* ## WHAT THIS DOES
49+
*
50+
* - Finds all users with ActivityPub capabilities
51+
* - Creates Delete activities for each user
52+
* - Sends these activities to all followers
53+
* - Removes your blog from ActivityPub discovery
54+
* - Sets a flag to track completion status
55+
*
56+
* ## IMPORTANT NOTES
57+
*
58+
* - This action cannot be undone
59+
* - Keep the ActivityPub plugin active during the process
60+
* - The process may take several minutes to complete
61+
* - You will be notified when the process finishes
62+
*
63+
* @param array|null $args The positional arguments (unused).
64+
* @param array|null $assoc_args The associative arguments (--status, --yes).
65+
*
66+
* @return void
67+
*/
68+
public function self_destruct( $args, $assoc_args = array() ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
69+
// Check if --status flag is provided.
70+
if ( isset( $assoc_args['status'] ) ) {
71+
$this->show_self_destruct_status();
72+
return;
73+
}
74+
75+
// Check if self-destruct has already been run.
76+
if ( \get_option( 'activitypub_self_destruct' ) ) {
77+
\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' ) );
78+
return;
79+
}
80+
81+
$this->execute_self_destruct( $assoc_args );
82+
}
83+
84+
/**
85+
* Execute the self-destruct process.
86+
*
87+
* This method handles the actual deletion process:
88+
* 1. Displays warning and confirmation prompt
89+
* 2. Retrieves all ActivityPub-capable users
90+
* 3. Creates and schedules Delete activities for each user
91+
* 4. Sets the self-destruct flag for status tracking
92+
* 5. Provides progress feedback and completion instructions
93+
*
94+
* @param array $assoc_args The associative arguments from WP-CLI.
95+
*
96+
* @return void
97+
*/
98+
private function execute_self_destruct( $assoc_args ) {
99+
$this->display_self_destruct_warning();
100+
\WP_CLI::confirm( 'Are you absolutely sure you want to continue?', $assoc_args );
101+
102+
$user_ids = $this->get_activitypub_users();
103+
if ( empty( $user_ids ) ) {
104+
\WP_CLI::warning( 'No ActivityPub users found. Nothing to delete.' );
105+
return;
106+
}
107+
108+
$processed = $this->process_user_deletions( $user_ids );
109+
$this->display_completion_message( $processed );
110+
}
111+
112+
/**
113+
* Display the self-destruct warning message.
114+
*
115+
* @return void
116+
*/
117+
private function display_self_destruct_warning() {
118+
\WP_CLI::line( \WP_CLI::colorize( '%R⚠️ DESTRUCTIVE OPERATION ⚠️%n' ) );
119+
\WP_CLI::line( '' );
120+
121+
$question = 'You are about to delete your blog from the Fediverse. This action is IRREVERSIBLE and will:';
122+
\WP_CLI::line( \WP_CLI::colorize( "%y{$question}%n" ) );
123+
\WP_CLI::line( \WP_CLI::colorize( '%y• Send Delete activities to all followers%n' ) );
124+
\WP_CLI::line( \WP_CLI::colorize( '%y• Remove your blog from ActivityPub networks%n' ) );
125+
\WP_CLI::line( '' );
126+
}
127+
128+
/**
129+
* Get all users with ActivityPub capabilities.
130+
*
131+
* @return array Array of user IDs with ActivityPub capabilities.
132+
*/
133+
private function get_activitypub_users() {
134+
return \get_users(
135+
array(
136+
'fields' => 'ID',
137+
'capability__in' => array( 'activitypub' ),
138+
)
139+
);
140+
}
141+
142+
/**
143+
* Process user deletions and create Delete activities.
144+
*
145+
* @param array $user_ids Array of user IDs to process.
25146
*
26-
* @param array|null $args The arguments.
27-
* @param array|null $assoc_args The associative arguments.
147+
* @return int Number of users successfully processed.
148+
*/
149+
private function process_user_deletions( $user_ids ) {
150+
$user_count = \count( $user_ids );
151+
\WP_CLI::line( \WP_CLI::colorize( '%GStarting Fediverse deletion process...%n' ) );
152+
\WP_CLI::line( \WP_CLI::colorize( "%BFound {$user_count} ActivityPub user(s) to process:%n" ) );
153+
\WP_CLI::line( '' );
154+
155+
// Set the self-destruct flag.
156+
\update_option( 'activitypub_self_destruct', true );
157+
158+
$processed = 0;
159+
foreach ( $user_ids as $user_id ) {
160+
if ( $this->create_delete_activity_for_user( $user_id, $processed, $user_count ) ) {
161+
++$processed;
162+
}
163+
}
164+
165+
\WP_CLI::line( '' );
166+
167+
if ( 0 === $processed ) {
168+
\WP_CLI::error( 'Failed to schedule any deletions. Please check your configuration.' );
169+
}
170+
171+
return $processed;
172+
}
173+
174+
/**
175+
* Create a Delete activity for a specific user.
176+
*
177+
* @param int $user_id The user ID to process.
178+
* @param int $processed Number of users already processed.
179+
* @param int $user_count Total number of users to process.
180+
*
181+
* @return bool True if the activity was created successfully, false otherwise.
182+
*/
183+
private function create_delete_activity_for_user( $user_id, $processed, $user_count ) {
184+
$actor = Actors::get_by_id( $user_id );
185+
186+
if ( ! $actor ) {
187+
\WP_CLI::line( \WP_CLI::colorize( "%R✗ Failed to load user ID: {$user_id}%n" ) );
188+
return false;
189+
}
190+
191+
$activity = new Activity();
192+
$activity->set_actor( $actor->get_id() );
193+
$activity->set_object( $actor->get_id() );
194+
$activity->set_type( 'Delete' );
195+
196+
$result = add_to_outbox( $activity, null, $user_id );
197+
if ( is_wp_error( $result ) ) {
198+
\WP_CLI::line( \WP_CLI::colorize( "%R✗ Failed to schedule deletion for: %B{$actor->get_name()}%n - {$result->get_error_message()}" ) );
199+
return false;
200+
}
201+
202+
$current = $processed + 1;
203+
\WP_CLI::line( \WP_CLI::colorize( "%G✓%n [{$current}/{$user_count}] Scheduled deletion for: %B{$actor->get_name()}%n" ) );
204+
return true;
205+
}
206+
207+
/**
208+
* Display the completion message after processing.
209+
*
210+
* @param int $processed Number of users successfully processed.
211+
*
212+
* @return void
213+
*/
214+
private function display_completion_message( $processed ) {
215+
if ( 0 === $processed ) {
216+
return; // Error already displayed in process_user_deletions.
217+
}
218+
219+
\WP_CLI::success( "Successfully scheduled {$processed} user(s) for Fediverse deletion." );
220+
\WP_CLI::line( '' );
221+
\WP_CLI::line( \WP_CLI::colorize( '%Y📋 Next Steps:%n' ) );
222+
\WP_CLI::line( \WP_CLI::colorize( '%Y• Keep the ActivityPub plugin active%n' ) );
223+
\WP_CLI::line( \WP_CLI::colorize( '%Y• Delete activities will be sent automatically%n' ) );
224+
\WP_CLI::line( \WP_CLI::colorize( '%Y• Process may take several minutes to complete%n' ) );
225+
\WP_CLI::line( \WP_CLI::colorize( '%Y• The plugin will notify you when the process is done.%n' ) );
226+
\WP_CLI::line( '' );
227+
}
228+
229+
/**
230+
* Show the status of the self-destruct process.
231+
*
232+
* Checks the current state of the self-destruct process by:
233+
* - Verifying if the process has been initiated
234+
* - Counting remaining pending Delete activities
235+
* - Displaying appropriate status messages and progress
236+
* - Providing guidance on next steps
237+
*
238+
* Status can be:
239+
* - NOT STARTED: Process hasn't been initiated
240+
* - IN PROGRESS: Delete activities are still being processed
241+
* - COMPLETED: All Delete activities have been sent
28242
*
29243
* @return void
30244
*/
31-
public function self_destruct( $args, $assoc_args ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
32-
\WP_CLI::warning( 'Self-Destructing is not implemented yet.' );
245+
private function show_self_destruct_status() {
246+
// Only proceed if self-destruct is active.
247+
if ( ! \get_option( 'activitypub_self_destruct', false ) ) {
248+
\WP_CLI::line( \WP_CLI::colorize( '%C❌ Status: NOT STARTED%n' ) );
249+
\WP_CLI::line( \WP_CLI::colorize( '%CThe self-destruct process has not been initiated.%n' ) );
250+
\WP_CLI::line( '' );
251+
\WP_CLI::line( \WP_CLI::colorize( '%CTo start the process, run:%n %Bwp activitypub self_destruct%n' ) );
252+
\WP_CLI::line( '' );
253+
return;
254+
}
255+
256+
\WP_CLI::line( \WP_CLI::colorize( '%B🔍 Self-Destruct Status Check%n' ) );
257+
\WP_CLI::line( '' );
258+
259+
// Check if there are any more pending Delete activities for self-destruct.
260+
$pending_deletes = \get_posts(
261+
array(
262+
'post_type' => Outbox::POST_TYPE,
263+
'post_status' => 'pending',
264+
'posts_per_page' => -1,
265+
'fields' => 'ids',
266+
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
267+
'meta_query' => array(
268+
array(
269+
'key' => '_activitypub_activity_type',
270+
'value' => 'Delete',
271+
),
272+
),
273+
)
274+
);
275+
276+
// Get count of pending Delete activities.
277+
$pending_count = count( $pending_deletes );
278+
279+
// If no more pending Delete activities, self-destruct is complete.
280+
if ( 0 === $pending_count ) {
281+
\WP_CLI::line( \WP_CLI::colorize( '%G✅ Status: COMPLETED%n' ) );
282+
\WP_CLI::line( \WP_CLI::colorize( '%GYour blog has been successfully removed from the Fediverse.%n' ) );
283+
\WP_CLI::line( '' );
284+
\WP_CLI::line( \WP_CLI::colorize( '%Y📋 What happened:%n' ) );
285+
\WP_CLI::line( \WP_CLI::colorize( '%Y• Delete activities were sent to all followers%n' ) );
286+
\WP_CLI::line( \WP_CLI::colorize( '%Y• Your blog is no longer discoverable on ActivityPub networks%n' ) );
287+
\WP_CLI::line( \WP_CLI::colorize( '%Y• The self-destruct process has finished%n' ) );
288+
} else {
289+
\WP_CLI::line( \WP_CLI::colorize( '%Y⏳ Status: IN PROGRESS%n' ) );
290+
\WP_CLI::line( \WP_CLI::colorize( '%YThe self-destruct process is currently running.%n' ) );
291+
\WP_CLI::line( '' );
292+
293+
\WP_CLI::line( \WP_CLI::colorize( "%YProgress: {$pending_count} Delete Activities still pending%n" ) );
294+
295+
\WP_CLI::line( '' );
296+
\WP_CLI::line( \WP_CLI::colorize( '%YNote: The process may take several minutes to complete.%n' ) );
297+
}
298+
299+
\WP_CLI::line( '' );
33300
}
34301

35302
/**

includes/wp-admin/class-admin.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,24 @@ public static function admin_notices() {
8181
if ( ! $current_screen ) {
8282
return;
8383
}
84+
85+
// Check for self-destruct completion notice.
86+
$self_destruct_complete = \get_option( 'activitypub_self_destruct_complete' );
87+
if ( $self_destruct_complete ) {
88+
// Show the notice only once, then remove it.
89+
\delete_option( 'activitypub_self_destruct_complete' );
90+
?>
91+
<div class="notice notice-success is-dismissible">
92+
<p>
93+
<strong><?php esc_html_e( 'ActivityPub Self-Destruct Complete!', 'activitypub' ); ?></strong>
94+
</p>
95+
<p>
96+
<?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' ); ?>
97+
</p>
98+
</div>
99+
<?php
100+
}
101+
84102
if ( 'edit' === $current_screen->base && Extra_Fields::is_extra_fields_post_type( $current_screen->post_type ) ) {
85103
?>
86104
<div class="notice" style="margin: 0; background: none; border: none; box-shadow: none; padding: 15px 0 0 0; font-size: 14px;">

0 commit comments

Comments
 (0)