77
88namespace Activitypub ;
99
10+ use Activitypub \Activity \Activity ;
11+ use Activitypub \Collection \Actors ;
1012use 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 /**
0 commit comments