7
7
8
8
namespace Activitypub ;
9
9
10
+ use Activitypub \Activity \Activity ;
11
+ use Activitypub \Collection \Actors ;
10
12
use Activitypub \Collection \Outbox ;
11
13
12
14
/**
@@ -19,17 +21,282 @@ class Cli extends \WP_CLI_Command {
19
21
/**
20
22
* Remove the entire blog from the Fediverse.
21
23
*
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
+ *
22
37
* ## EXAMPLES
23
38
*
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.
25
146
*
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
28
242
*
29
243
* @return void
30
244
*/
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 ( '' );
33
300
}
34
301
35
302
/**
0 commit comments