PluginProbe
ActivityPub / 7.8.4
ActivityPub v7.8.4
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.8.4, at includes/class-cli.php

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