PluginProbe
ActivityPub / 7.6.0
ActivityPub v7.6.0
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
activitypub / includes / class-cli.php

class-cli.php in ActivityPub 7.6.0, at includes/class-cli.php

573 lines 15.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WP-CLI file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub;
9
10 use Activitypub\Activity\Activity;
11 use Activitypub\Collection\Actors;
12 use Activitypub\Collection\Outbox;
13 use Activitypub\Scheduler\Actor;
14
15 /**
16 * WP-CLI commands.
17 *
18 * @package Activitypub
19 */
20 class Cli extends \WP_CLI_Command {
21
22 /**
23 * Remove the entire blog from the Fediverse.
24 *
25 * This command permanently removes your blog from ActivityPub networks by sending
26 * Delete activities to all followers. This action is IRREVERSIBLE.
27 *
28 * ## OPTIONS
29 *
30 * [--status]
31 * : Check the status of the self-destruct process instead of running it.
32 * Use this to monitor progress after initiating the deletion process.
33 *
34 * [--yes]
35 * : Skip the confirmation prompt and proceed with deletion immediately.
36 * Use with extreme caution as this bypasses all safety checks.
37 *
38 * ## EXAMPLES
39 *
40 * # Start the self-destruct process (with confirmation prompt)
41 * $ wp activitypub self_destruct
42 *
43 * # Check the status of an ongoing self-destruct process
44 * $ wp activitypub self_destruct --status
45 *
46 * # Force deletion without confirmation (dangerous!)
47 * $ wp activitypub self_destruct --yes
48 *
49 * ## WHAT THIS DOES
50 *
51 * - Finds all users with ActivityPub capabilities
52 * - Creates Delete activities for each user
53 * - Sends these activities to all followers
54 * - Removes your blog from ActivityPub discovery
55 * - Sets a flag to track completion status
56 *
57 * ## IMPORTANT NOTES
58 *
59 * - This action cannot be undone
60 * - Keep the ActivityPub plugin active during the process
61 * - The process may take several minutes to complete
62 * - You will be notified when the process finishes
63 *
64 * @param array|null $args The positional arguments (unused).
65 * @param array|null $assoc_args The associative arguments (--status, --yes).
66 *
67 * @return void
68 */
69 public function self_destruct( $args, $assoc_args = array() ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
70 // Check if --status flag is provided.
71 if ( isset( $assoc_args['status'] ) ) {
72 $this->show_self_destruct_status();
73 return;
74 }
75
76 // Check if self-destruct has already been run.
77 if ( \get_option( 'activitypub_self_destruct' ) ) {
78 \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' ) );
79 return;
80 }
81
82 $this->execute_self_destruct( $assoc_args );
83 }
84
85 /**
86 * Execute the self-destruct process.
87 *
88 * This method handles the actual deletion process:
89 * 1. Displays warning and confirmation prompt
90 * 2. Retrieves all ActivityPub-capable users
91 * 3. Creates and schedules Delete activities for each user
92 * 4. Sets the self-destruct flag for status tracking
93 * 5. Provides progress feedback and completion instructions
94 *
95 * @param array $assoc_args The associative arguments from WP-CLI.
96 *
97 * @return void
98 */
99 private function execute_self_destruct( $assoc_args ) {
100 $this->display_self_destruct_warning();
101 \WP_CLI::confirm( 'Are you absolutely sure you want to continue?', $assoc_args );
102
103 $user_ids = $this->get_activitypub_users();
104 if ( empty( $user_ids ) ) {
105 \WP_CLI::warning( 'No ActivityPub users found. Nothing to delete.' );
106 return;
107 }
108
109 $processed = $this->process_user_deletions( $user_ids );
110 $this->display_completion_message( $processed );
111 }
112
113 /**
114 * Display the self-destruct warning message.
115 *
116 * @return void
117 */
118 private function display_self_destruct_warning() {
119 \WP_CLI::line( \WP_CLI::colorize( '%R⚠️ DESTRUCTIVE OPERATION ⚠️%n' ) );
120 \WP_CLI::line( '' );
121
122 $question = 'You are about to delete your blog from the Fediverse. This action is IRREVERSIBLE and will:';
123 \WP_CLI::line( \WP_CLI::colorize( "%y{$question}%n" ) );
124 \WP_CLI::line( \WP_CLI::colorize( '%y• Send Delete activities to all followers%n' ) );
125 \WP_CLI::line( \WP_CLI::colorize( '%y• Remove your blog from ActivityPub networks%n' ) );
126 \WP_CLI::line( '' );
127 }
128
129 /**
130 * Get all users with ActivityPub capabilities.
131 *
132 * @return array Array of user IDs with ActivityPub capabilities.
133 */
134 private function get_activitypub_users() {
135 return \get_users(
136 array(
137 'fields' => 'ID',
138 'capability__in' => array( 'activitypub' ),
139 )
140 );
141 }
142
143 /**
144 * Process user deletions and create Delete activities.
145 *
146 * @param array $user_ids Array of user IDs to process.
147 *
148 * @return int Number of users successfully processed.
149 */
150 private function process_user_deletions( $user_ids ) {
151 $user_count = \count( $user_ids );
152 \WP_CLI::line( \WP_CLI::colorize( '%GStarting Fediverse deletion process...%n' ) );
153 \WP_CLI::line( \WP_CLI::colorize( "%BFound {$user_count} ActivityPub user(s) to process:%n" ) );
154 \WP_CLI::line( '' );
155
156 // Set the self-destruct flag.
157 \update_option( 'activitypub_self_destruct', true );
158
159 $processed = 0;
160 foreach ( $user_ids as $user_id ) {
161 if ( $this->create_delete_activity_for_user( $user_id, $processed, $user_count ) ) {
162 ++$processed;
163 }
164 }
165
166 \WP_CLI::line( '' );
167
168 if ( 0 === $processed ) {
169 \WP_CLI::error( 'Failed to schedule any deletions. Please check your configuration.' );
170 }
171
172 return $processed;
173 }
174
175 /**
176 * Create a Delete activity for a specific user.
177 *
178 * @param int $user_id The user ID to process.
179 * @param int $processed Number of users already processed.
180 * @param int $user_count Total number of users to process.
181 *
182 * @return bool True if the activity was created successfully, false otherwise.
183 */
184 private function create_delete_activity_for_user( $user_id, $processed, $user_count ) {
185 $actor = Actors::get_by_id( $user_id );
186
187 if ( ! $actor ) {
188 \WP_CLI::line( \WP_CLI::colorize( "%R✗ Failed to load user ID: {$user_id}%n" ) );
189 return false;
190 }
191
192 $activity = new Activity();
193 $activity->set_actor( $actor->get_id() );
194 $activity->set_object( $actor->get_id() );
195 $activity->set_type( 'Delete' );
196
197 $result = add_to_outbox( $activity, null, $user_id );
198 if ( is_wp_error( $result ) ) {
199 \WP_CLI::line( \WP_CLI::colorize( "%R✗ Failed to schedule deletion for: %B{$actor->get_name()}%n - {$result->get_error_message()}" ) );
200 return false;
201 }
202
203 $current = $processed + 1;
204 \WP_CLI::line( \WP_CLI::colorize( "%G✓%n [{$current}/{$user_count}] Scheduled deletion for: %B{$actor->get_name()}%n" ) );
205 return true;
206 }
207
208 /**
209 * Display the completion message after processing.
210 *
211 * @param int $processed Number of users successfully processed.
212 *
213 * @return void
214 */
215 private function display_completion_message( $processed ) {
216 if ( 0 === $processed ) {
217 return; // Error already displayed in process_user_deletions.
218 }
219
220 \WP_CLI::success( "Successfully scheduled {$processed} user(s) for Fediverse deletion." );
221 \WP_CLI::line( '' );
222 \WP_CLI::line( \WP_CLI::colorize( '%Y📋 Next Steps:%n' ) );
223 \WP_CLI::line( \WP_CLI::colorize( '%Y• Keep the ActivityPub plugin active%n' ) );
224 \WP_CLI::line( \WP_CLI::colorize( '%Y• Delete activities will be sent automatically%n' ) );
225 \WP_CLI::line( \WP_CLI::colorize( '%Y• Process may take several minutes to complete%n' ) );
226 \WP_CLI::line( \WP_CLI::colorize( '%Y• The plugin will notify you when the process is done.%n' ) );
227 \WP_CLI::line( '' );
228 }
229
230 /**
231 * Show the status of the self-destruct process.
232 *
233 * Checks the current state of the self-destruct process by:
234 * - Verifying if the process has been initiated
235 * - Counting remaining pending Delete activities
236 * - Displaying appropriate status messages and progress
237 * - Providing guidance on next steps
238 *
239 * Status can be:
240 * - NOT STARTED: Process hasn't been initiated
241 * - IN PROGRESS: Delete activities are still being processed
242 * - COMPLETED: All Delete activities have been sent
243 *
244 * @return void
245 */
246 private function show_self_destruct_status() {
247 // Only proceed if self-destruct is active.
248 if ( ! \get_option( 'activitypub_self_destruct', false ) ) {
249 \WP_CLI::line( \WP_CLI::colorize( '%C❌ Status: NOT STARTED%n' ) );
250 \WP_CLI::line( \WP_CLI::colorize( '%CThe self-destruct process has not been initiated.%n' ) );
251 \WP_CLI::line( '' );
252 \WP_CLI::line( \WP_CLI::colorize( '%CTo start the process, run:%n %Bwp activitypub self_destruct%n' ) );
253 \WP_CLI::line( '' );
254 return;
255 }
256
257 \WP_CLI::line( \WP_CLI::colorize( '%B🔍 Self-Destruct Status Check%n' ) );
258 \WP_CLI::line( '' );
259
260 // Check if there are any more pending Delete activities for self-destruct.
261 $pending_deletes = \get_posts(
262 array(
263 'post_type' => Outbox::POST_TYPE,
264 'post_status' => 'pending',
265 'posts_per_page' => -1,
266 'fields' => 'ids',
267 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
268 'meta_query' => array(
269 array(
270 'key' => '_activitypub_activity_type',
271 'value' => 'Delete',
272 ),
273 ),
274 )
275 );
276
277 // Get count of pending Delete activities.
278 $pending_count = count( $pending_deletes );
279
280 // If no more pending Delete activities, self-destruct is complete.
281 if ( 0 === $pending_count ) {
282 \WP_CLI::line( \WP_CLI::colorize( '%G�
283 Status: COMPLETED%n' ) );
284 \WP_CLI::line( \WP_CLI::colorize( '%GYour blog has been successfully removed from the Fediverse.%n' ) );
285 \WP_CLI::line( '' );
286 \WP_CLI::line( \WP_CLI::colorize( '%Y📋 What happened:%n' ) );
287 \WP_CLI::line( \WP_CLI::colorize( '%Y• Delete activities were sent to all followers%n' ) );
288 \WP_CLI::line( \WP_CLI::colorize( '%Y• Your blog is no longer discoverable on ActivityPub networks%n' ) );
289 \WP_CLI::line( \WP_CLI::colorize( '%Y• The self-destruct process has finished%n' ) );
290 } else {
291 \WP_CLI::line( \WP_CLI::colorize( '%Y⏳ Status: IN PROGRESS%n' ) );
292 \WP_CLI::line( \WP_CLI::colorize( '%YThe self-destruct process is currently running.%n' ) );
293 \WP_CLI::line( '' );
294
295 \WP_CLI::line( \WP_CLI::colorize( "%YProgress: {$pending_count} Delete Activities still pending%n" ) );
296
297 \WP_CLI::line( '' );
298 \WP_CLI::line( \WP_CLI::colorize( '%YNote: The process may take several minutes to complete.%n' ) );
299 }
300
301 \WP_CLI::line( '' );
302 }
303
304 /**
305 * Delete or Update a Post, Page, Custom Post Type or Attachment.
306 *
307 * ## OPTIONS
308 *
309 * <action>
310 * : The action to perform. Either `delete` or `update`.
311 * ---
312 * options:
313 * - delete
314 * - update
315 * ---
316 *
317 * <id>
318 * : The id of the Post, Page, Custom Post Type or Attachment.
319 *
320 * ## EXAMPLES
321 *
322 * $ wp activitypub post delete 1
323 *
324 * @synopsis <action> <id>
325 *
326 * @param array $args The arguments.
327 */
328 public function post( $args ) {
329 $post = get_post( $args[1] );
330
331 if ( ! $post ) {
332 \WP_CLI::error( 'Post not found.' );
333 }
334
335 switch ( $args[0] ) {
336 case 'delete':
337 \WP_CLI::confirm( 'Do you really want to delete the (Custom) Post with the ID: ' . $args[1] );
338 add_to_outbox( $post, 'Delete', $post->post_author );
339 \WP_CLI::success( '"Delete" activity is queued.' );
340 break;
341 case 'update':
342 add_to_outbox( $post, 'Update', $post->post_author );
343 \WP_CLI::success( '"Update" activity is queued.' );
344 break;
345 default:
346 \WP_CLI::error( 'Unknown action.' );
347 }
348 }
349
350 /**
351 * Delete or Update a Comment.
352 *
353 * ## OPTIONS
354 *
355 * <action>
356 * : The action to perform. Either `delete` or `update`.
357 * ---
358 * options:
359 * - delete
360 * - update
361 * ---
362 *
363 * <id>
364 * : The id of the Comment.
365 *
366 * ## EXAMPLES
367 *
368 * $ wp activitypub comment delete 1
369 *
370 * @synopsis <action> <id>
371 *
372 * @param array $args The arguments.
373 */
374 public function comment( $args ) {
375 $comment = get_comment( $args[1] );
376
377 if ( ! $comment ) {
378 \WP_CLI::error( 'Comment not found.' );
379 }
380
381 if ( was_comment_received( $comment ) ) {
382 \WP_CLI::error( 'This comment was received via ActivityPub and cannot be deleted or updated.' );
383 }
384
385 switch ( $args[0] ) {
386 case 'delete':
387 \WP_CLI::confirm( 'Do you really want to delete the Comment with the ID: ' . $args[1] );
388 add_to_outbox( $comment, 'Delete', $comment->user_id );
389 \WP_CLI::success( '"Delete" activity is queued.' );
390 break;
391 case 'update':
392 add_to_outbox( $comment, 'Update', $comment->user_id );
393 \WP_CLI::success( '"Update" activity is queued.' );
394 break;
395 default:
396 \WP_CLI::error( 'Unknown action.' );
397 }
398 }
399
400 /**
401 * Delete or Update an Actor.
402 *
403 * ## OPTIONS
404 *
405 * <action>
406 * : The action to perform. Either `delete` or `update`.
407 * ---
408 * options:
409 * - delete
410 * - update
411 * ---
412 *
413 * <id>
414 * : The id of the Actor.
415 *
416 * ## EXAMPLES
417 *
418 * $ wp activitypub actor delete 1
419 *
420 * @synopsis <action> <id>
421 *
422 * @param array $args The arguments.
423 */
424 public function actor( $args ) {
425 if ( Actors::APPLICATION_USER_ID === (int) $args[1] ) {
426 \WP_CLI::error( 'You cannot delete the application actor.' );
427 }
428
429 switch ( $args[0] ) {
430 case 'delete':
431 \add_filter( 'activitypub_user_can_activitypub', '__return_true' );
432 Actor::schedule_user_delete( $args[1] );
433 \remove_filter( 'activitypub_user_can_activitypub', '__return_true' );
434 \WP_CLI::success( '"Delete" activity is queued.' );
435 break;
436 case 'update':
437 Actor::schedule_profile_update( $args[1] );
438 \WP_CLI::success( '"Update" activity is queued.' );
439 break;
440 default:
441 \WP_CLI::error( 'Unknown action.' );
442 }
443 }
444
445 /**
446 * Undo an activity that was sent to the Fediverse.
447 *
448 * ## OPTIONS
449 *
450 * <outbox_item_id>
451 * The ID or URL of the outbox item to undo.
452 *
453 * ## EXAMPLES
454 *
455 * $ wp activitypub undo 123
456 * $ wp activitypub undo "https://example.com/?post_type=ap_outbox&p=123"
457 *
458 * @synopsis <outbox_item_id>
459 *
460 * @param array $args The arguments.
461 */
462 public function undo( $args ) {
463 $outbox_item_id = $args[0];
464 if ( ! is_numeric( $outbox_item_id ) ) {
465 $outbox_item_id = url_to_postid( $outbox_item_id );
466 }
467
468 $outbox_item_id = get_post( $outbox_item_id );
469 if ( ! $outbox_item_id ) {
470 \WP_CLI::error( 'Activity not found.' );
471 }
472
473 $undo_id = Outbox::undo( $outbox_item_id );
474 if ( ! $undo_id ) {
475 \WP_CLI::error( 'Failed to undo activity.' );
476 }
477 \WP_CLI::success( 'Undo activity scheduled.' );
478 }
479
480 /**
481 * Re-Schedule an activity that was sent to the Fediverse before.
482 *
483 * ## OPTIONS
484 *
485 * <outbox_item_id>
486 * The ID or URL of the outbox item to reschedule.
487 *
488 * ## EXAMPLES
489 *
490 * $ wp activitypub reschedule 123
491 * $ wp activitypub reschedule "https://example.com/?post_type=ap_outbox&p=123"
492 *
493 * @synopsis <outbox_item_id>
494 *
495 * @param array $args The arguments.
496 */
497 public function reschedule( $args ) {
498 $outbox_item_id = $args[0];
499 if ( ! is_numeric( $outbox_item_id ) ) {
500 $outbox_item_id = url_to_postid( $outbox_item_id );
501 }
502
503 $outbox_item_id = get_post( $outbox_item_id );
504 if ( ! $outbox_item_id ) {
505 \WP_CLI::error( 'Activity not found.' );
506 }
507
508 Outbox::reschedule( $outbox_item_id );
509
510 \WP_CLI::success( 'Rescheduled activity.' );
511 }
512
513 /**
514 * Move the blog to a new URL.
515 *
516 * ## OPTIONS
517 *
518 * <from>
519 * The current URL of the blog.
520 *
521 * <to>
522 * The new URL of the blog.
523 *
524 * ## EXAMPLES
525 *
526 * $ wp activitypub move https://example.com/ https://newsite.com/
527 *
528 * @synopsis <from> <to>
529 *
530 * @param array $args The arguments.
531 */
532 public function move( $args ) {
533 $from = $args[0];
534 $to = $args[1];
535
536 $outbox_item_id = Move::account( $from, $to );
537
538 if ( is_wp_error( $outbox_item_id ) ) {
539 \WP_CLI::error( $outbox_item_id->get_error_message() );
540 } else {
541 \WP_CLI::success( 'Move Scheduled.' );
542 }
543 }
544
545 /**
546 * Follow a user.
547 *
548 * ## OPTIONS
549 *
550 * <remote-user>
551 * The remote user to follow.
552 *
553 * ## EXAMPLES
554 *
555 * $ wp activitypub follow https://example.com/@user
556 * $ wp --user=pfefferle activitypub follow https://example.com/@user
557 *
558 * @synopsis <remote_user>
559 *
560 * @param array $args The arguments.
561 */
562 public function follow( $args ) {
563 $user_id = \get_current_user_id();
564 $follow = follow( $args[0], $user_id );
565
566 if ( is_wp_error( $follow ) ) {
567 \WP_CLI::error( $follow->get_error_message() );
568 } else {
569 \WP_CLI::success( 'Follow Scheduled.' );
570 }
571 }
572 }
573