PluginProbe
ActivityPub / 1.0.0
ActivityPub v1.0.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
← All changes | includes/class-scheduler.php +84 -580 8.3.01.0.0 View file →
@@ -1,653 +1,157 @@
1 1 <?php
2 -/**
3 - * Scheduler class file.
4 - *
5 - * @package Activitypub
6 - */
7 2
8 3 namespace Activitypub;
9 4
10 -use Activitypub\Activity\Activity;
11 -use Activitypub\Activity\Base_Object;
12 -use Activitypub\Collection\Actors;
13 -use Activitypub\Collection\Inbox;
14 -use Activitypub\Collection\Outbox;
15 -use Activitypub\Collection\Remote_Actors;
16 -use Activitypub\Collection\Remote_Posts;
17 -use Activitypub\Scheduler\Actor;
18 -use Activitypub\Scheduler\Collection_Sync;
19 -use Activitypub\Scheduler\Comment;
20 -use Activitypub\Scheduler\Post;
21 -use Activitypub\Scheduler\Statistics;
5 +use Activitypub\Collection\Users;
6 +use Activitypub\Collection\Followers;
7 +use Activitypub\Transformer\Post;
22 8
23 9 /**
24 - * Scheduler class.
10 + * ActivityPub Scheduler Class
25 11 *
26 12 * @author Matthias Pfefferle
27 13 */
28 14 class Scheduler {
29 -
30 15 /**
31 - * Scheduled events with their recurrence.
32 - *
33 - * @var array
16 + * Initialize the class, registering WordPress hooks
34 17 */
35 - const SCHEDULES = array(
36 - 'activitypub_update_remote_actors' => 'hourly',
37 - 'activitypub_cleanup_remote_actors' => 'daily',
38 - 'activitypub_reprocess_outbox' => 'hourly',
39 - 'activitypub_outbox_purge' => 'daily',
40 - 'activitypub_inbox_purge' => 'daily',
41 - 'activitypub_ap_post_purge' => 'daily',
42 - 'activitypub_tombstone_purge' => 'daily',
43 - 'activitypub_sync_blocklist_subscriptions' => 'weekly',
44 - );
45 -
46 - /**
47 - * Allowed batch callbacks.
48 - *
49 - * @var array
50 - */
51 - private static $batch_callbacks = array();
52 -
53 - /**
54 - * Get the pause between async batches (in seconds).
55 - *
56 - * @return int The pause in seconds.
57 - */
58 - public static function get_retry_delay() {
59 - /**
60 - * Filters the pause between async batches (in seconds).
61 - *
62 - * @param int $async_batch_pause The pause in seconds. Default 30.
63 - */
64 - return apply_filters( 'activitypub_scheduler_async_batch_pause', 30 );
65 - }
66 -
67 - /**
68 - * Initialize the class, registering WordPress hooks.
69 - */
70 18 public static function init() {
71 - self::register_schedulers();
19 + \add_action( 'transition_post_status', array( self::class, 'schedule_post_activity' ), 33, 3 );
72 20
73 - // Custom cron schedules.
74 - \add_filter( 'cron_schedules', array( self::class, 'add_cron_schedules' ) );
21 + \add_action( 'activitypub_update_followers', array( self::class, 'update_followers' ) );
22 + \add_action( 'activitypub_cleanup_followers', array( self::class, 'cleanup_followers' ) );
75 23
76 - // Follower Cleanups.
77 - \add_action( 'activitypub_update_remote_actors', array( self::class, 'update_remote_actors' ) );
78 - \add_action( 'activitypub_cleanup_remote_actors', array( self::class, 'cleanup_remote_actors' ) );
79 -
80 - // Event callbacks.
81 - \add_action( 'activitypub_async_batch', array( self::class, 'async_batch' ), 10, 99 );
82 - \add_action( 'activitypub_reprocess_outbox', array( self::class, 'reprocess_outbox' ) );
83 - \add_action( 'activitypub_outbox_purge', array( self::class, 'purge_outbox' ) );
84 - \add_action( 'activitypub_inbox_purge', array( self::class, 'purge_inbox' ) );
85 - \add_action( 'activitypub_ap_post_purge', array( self::class, 'purge_ap_posts' ) );
86 - \add_action( 'activitypub_tombstone_purge', array( self::class, 'purge_tombstones' ) );
87 - \add_action( 'activitypub_inbox_create_item', array( self::class, 'process_inbox_activity' ) );
88 - \add_action( 'activitypub_sync_blocklist_subscriptions', array( Blocklist_Subscriptions::class, 'sync_all' ) );
89 -
90 - \add_action( 'post_activitypub_add_to_outbox', array( self::class, 'schedule_outbox_activity_for_federation' ) );
91 - \add_action( 'post_activitypub_add_to_outbox', array( self::class, 'schedule_announce_activity' ), 10, 4 );
92 -
93 - \add_action( 'update_option_activitypub_outbox_purge_days', array( self::class, 'update_outbox_purge_schedule' ), 10, 2 );
94 - \add_action( 'update_option_activitypub_inbox_purge_days', array( self::class, 'update_inbox_purge_schedule' ), 10, 2 );
95 - \add_action( 'update_option_activitypub_ap_post_purge_days', array( self::class, 'update_ap_post_purge_schedule' ), 10, 2 );
24 + \add_action( 'admin_init', array( self::class, 'schedule_migration' ) );
96 25 }
97 26
98 27 /**
99 - * Register handlers.
100 - */
101 - public static function register_schedulers() {
102 - Post::init();
103 - Actor::init();
104 - Collection_Sync::init();
105 - Comment::init();
106 - Statistics::init();
107 -
108 - /**
109 - * Register additional schedulers.
110 - *
111 - * @since 5.0.0
112 - */
113 - \do_action( 'activitypub_register_schedulers' );
114 - }
115 -
116 - /**
117 - * Add custom cron schedules.
28 + * Schedule all ActivityPub schedules.
118 29 *
119 - * @param array $schedules Existing cron schedules.
120 - *
121 - * @return array Modified cron schedules.
30 + * @return void
122 31 */
123 - public static function add_cron_schedules( $schedules ) {
124 - $schedules['monthly'] = array(
125 - 'interval' => MONTH_IN_SECONDS,
126 - 'display' => \__( 'Once Monthly', 'activitypub' ),
127 - );
128 -
129 - $schedules['yearly'] = array(
130 - 'interval' => YEAR_IN_SECONDS,
131 - 'display' => \__( 'Once Yearly', 'activitypub' ),
132 - );
133 -
134 - return $schedules;
135 - }
136 -
137 - /**
138 - * Register a batch callback for async processing.
139 - *
140 - * @param string $hook The cron event hook name.
141 - * @param callable $callback The callback to execute.
142 - */
143 - public static function register_async_batch_callback( $hook, $callback ) {
144 - if ( \did_action( 'init' ) && ! \doing_action( 'init' ) ) {
145 - \_doing_it_wrong( __METHOD__, 'Async batch callbacks should be registered before or during the init action.', '7.5.0' );
146 - return;
147 - }
148 -
149 - if ( ! \is_callable( $callback ) ) {
150 - return;
151 - }
152 -
153 - self::$batch_callbacks[ $hook ] = $callback;
154 -
155 - // Register the WordPress action hook to trigger async_batch.
156 - \add_action( $hook, array( self::class, 'async_batch' ), 10, 99 );
157 - }
158 -
159 - /**
160 - * Schedule all ActivityPub schedules.
161 - */
162 32 public static function register_schedules() {
163 - foreach ( self::SCHEDULES as $hook => $recurrence ) {
164 - if ( ! \wp_next_scheduled( $hook ) ) {
165 - \wp_schedule_event( time(), $recurrence, $hook );
166 - }
33 + if ( ! \wp_next_scheduled( 'activitypub_update_followers' ) ) {
34 + \wp_schedule_event( time(), 'hourly', 'activitypub_update_followers' );
167 35 }
168 36
169 - // Schedule monthly stats collection for the 1st of each month.
170 - if ( ! \wp_next_scheduled( 'activitypub_collect_monthly_stats' ) ) {
171 - // Calculate next 1st of month at 2:00 AM.
172 - $next_first = self::get_next_first_of_month();
173 - \wp_schedule_event( $next_first, 'monthly', 'activitypub_collect_monthly_stats' );
37 + if ( ! \wp_next_scheduled( 'activitypub_cleanup_followers' ) ) {
38 + \wp_schedule_event( time(), 'daily', 'activitypub_cleanup_followers' );
174 39 }
175 -
176 - // Schedule annual stats compilation for December 1st (wrapped notification).
177 - if ( ! \wp_next_scheduled( 'activitypub_compile_annual_stats' ) ) {
178 - $next_december = self::get_next_december_first();
179 - \wp_schedule_event( $next_december, 'yearly', 'activitypub_compile_annual_stats' );
180 - }
181 40 }
182 41
183 42 /**
184 - * Un-schedule all ActivityPub schedules.
43 + * Unscedule all ActivityPub schedules.
185 44 *
186 45 * @return void
187 46 */
188 47 public static function deregister_schedules() {
189 - foreach ( array_keys( self::SCHEDULES ) as $hook ) {
190 - \wp_unschedule_hook( $hook );
191 - }
48 + wp_unschedule_hook( 'activitypub_update_followers' );
49 + wp_unschedule_hook( 'activitypub_cleanup_followers' );
50 + }
192 51
193 - // Statistics schedules.
194 - \wp_unschedule_hook( 'activitypub_collect_monthly_stats' );
195 - \wp_unschedule_hook( 'activitypub_compile_annual_stats' );
196 - }
197 52
198 53 /**
199 - * Get the next 1st of month timestamp.
54 + * Schedule Activities.
200 55 *
201 - * @return int Unix timestamp of next 1st of month at 2:00 AM.
56 + * @param string $new_status New post status.
57 + * @param string $old_status Old post status.
58 + * @param WP_Post $post Post object.
202 59 */
203 - private static function get_next_first_of_month() {
204 - $now = \current_time( 'timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
205 - $next_month = \strtotime( 'first day of next month 02:00:00', $now );
60 + public static function schedule_post_activity( $new_status, $old_status, $post ) {
61 + // Do not send activities if post is password protected.
62 + if ( \post_password_required( $post ) ) {
63 + return;
64 + }
206 65
207 - return $next_month;
208 - }
66 + // Check if post-type supports ActivityPub.
67 + $post_types = \get_post_types_by_support( 'activitypub' );
68 + if ( ! \in_array( $post->post_type, $post_types, true ) ) {
69 + return;
70 + }
209 71
210 - /**
211 - * Get the next December 1st timestamp for wrapped notification.
212 - *
213 - * @return int Unix timestamp of next December 1st at 3:00 AM.
214 - */
215 - private static function get_next_december_first() {
216 - $now = \current_time( 'timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
217 - $year = (int) \gmdate( 'Y', $now );
72 + $type = false;
218 73
219 - // Get December 1st 3:00 AM for this year.
220 - $this_year_dec_first = \strtotime( sprintf( '%d-12-01 03:00:00', $year ) );
74 + if ( 'publish' === $new_status && 'publish' !== $old_status ) {
75 + $type = 'Create';
76 + } elseif ( 'publish' === $new_status ) {
77 + $type = 'Update';
78 + } elseif ( 'trash' === $new_status ) {
79 + $type = 'Delete';
80 + }
221 81
222 - // If we're already past this year's December 1st, schedule for next year.
223 - if ( $now >= $this_year_dec_first ) {
224 - return \strtotime( sprintf( '%d-12-01 03:00:00', $year + 1 ) );
82 + if ( ! $type ) {
83 + return;
225 84 }
226 85
227 - return $this_year_dec_first;
228 - }
86 + \wp_schedule_single_event(
87 + \time(),
88 + 'activitypub_send_activity',
89 + array( $post, $type )
90 + );
229 91
230 - /**
231 - * Unschedule events for an outbox item.
232 - *
233 - * @param int $outbox_item_id The outbox item ID.
234 - */
235 - public static function unschedule_events_for_item( $outbox_item_id ) {
236 - $event_args = array(
237 - $outbox_item_id,
238 - Dispatcher::get_batch_size(),
239 - \get_post_meta( $outbox_item_id, '_activitypub_outbox_offset', true ) ?: 0, // phpcs:ignore
92 + \wp_schedule_single_event(
93 + \time(),
94 + sprintf(
95 + 'activitypub_send_%s_activity',
96 + \strtolower( $type )
97 + ),
98 + array( $post )
240 99 );
241 -
242 - \delete_post_meta( $outbox_item_id, '_activitypub_outbox_offset' );
243 -
244 - $timestamp = \wp_next_scheduled( 'activitypub_process_outbox', array( $outbox_item_id ) );
245 - \wp_unschedule_event( $timestamp, 'activitypub_process_outbox', array( $outbox_item_id ) );
246 -
247 - $timestamp = \wp_next_scheduled( 'activitypub_send_activity', $event_args );
248 - \wp_unschedule_event( $timestamp, 'activitypub_send_activity', $event_args );
249 -
250 - // Invalidate any retries for this outbox item.
251 - foreach ( _get_cron_array() as $timestamp => $cron ) {
252 - if ( ! isset( $cron['activitypub_retry_activity'] ) ) {
253 - continue;
254 - }
255 -
256 - foreach ( $cron['activitypub_retry_activity'] as $event ) {
257 - if ( isset( $event['args'][1] ) && $outbox_item_id === $event['args'][1] ) {
258 - \wp_unschedule_event( $timestamp, 'activitypub_retry_activity', $event['args'] );
259 - }
260 - }
261 - }
262 100 }
263 101
264 102 /**
265 - * Update remote Actors.
103 + * Update followers
104 + *
105 + * @return void
266 106 */
267 - public static function update_remote_actors() {
268 - $number = 5;
107 + public static function update_followers() {
108 + $followers = Followers::get_outdated_followers();
269 109
270 - if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
271 - $number = 50;
272 - }
110 + foreach ( $followers as $follower ) {
111 + $meta = get_remote_metadata_by_actor( $follower->get_url(), true );
273 112
274 - /**
275 - * Filter the number of remote Actors to update.
276 - *
277 - * @param int $number The number of remote Actors to update.
278 - */
279 - $number = apply_filters( 'activitypub_update_remote_actors_number', $number );
280 - $actors = Remote_Actors::get_outdated( $number );
281 -
282 - foreach ( $actors as $actor ) {
283 - $meta = get_remote_metadata_by_actor( $actor->guid, false );
284 -
285 113 if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
286 - Remote_Actors::add_error( $actor->ID, 'Failed to fetch or parse metadata' );
114 + Followers::add_error( $follower->get__id(), $meta );
287 115 } else {
288 - $id = Remote_Actors::upsert( $meta );
289 - if ( \is_wp_error( $id ) ) {
290 - continue;
291 - }
292 - Remote_Actors::clear_errors( $id );
116 + $follower->from_array( $meta );
117 + $follower->update();
293 118 }
294 119 }
295 120 }
296 121
297 122 /**
298 - * Cleanup remote Actors.
123 + * Cleanup followers
124 + *
125 + * @return void
299 126 */
300 - public static function cleanup_remote_actors() {
301 - $number = 5;
127 + public static function cleanup_followers() {
128 + $followers = Followers::get_faulty_followers();
302 129
303 - if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
304 - $number = 50;
305 - }
130 + foreach ( $followers as $follower ) {
131 + $meta = get_remote_metadata_by_actor( $follower->get_url(), true );
306 132
307 - /**
308 - * Filter the number of remote Actors to clean up.
309 - *
310 - * @param int $number The number of remote Actors to clean up.
311 - */
312 - $number = apply_filters( 'activitypub_cleanup_remote_actors_number', $number );
313 - $actors = Remote_Actors::get_faulty( $number );
314 -
315 - foreach ( $actors as $actor ) {
316 - $meta = get_remote_metadata_by_actor( $actor->guid, false );
317 -
318 - if ( Tombstone::exists( $meta ) ) {
319 - \wp_delete_post( $actor->ID );
320 - } elseif ( empty( $meta ) || ! is_array( $meta ) || \is_wp_error( $meta ) ) {
321 - if ( Remote_Actors::count_errors( $actor->ID ) >= 5 ) {
322 - \wp_schedule_single_event( \time(), 'activitypub_delete_remote_actor_interactions', array( $actor->guid ) );
323 - \wp_schedule_single_event( \time(), 'activitypub_delete_remote_actor_posts', array( $actor->guid ) );
324 - \wp_delete_post( $actor->ID );
133 + if ( is_tombstone( $meta ) ) {
134 + $follower->delete();
135 + } elseif ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
136 + if ( $follower->count_errors() >= 5 ) {
137 + $follower->delete();
325 138 } else {
326 - Remote_Actors::add_error( $actor->ID, $meta );
139 + Followers::add_error( $follower->get__id(), $meta );
327 140 }
328 141 } else {
329 - $id = Remote_Actors::upsert( $meta );
330 - if ( \is_wp_error( $id ) ) {
331 - Remote_Actors::add_error( $actor->ID, $id );
332 - } else {
333 - Remote_Actors::clear_errors( $actor->ID );
334 - }
142 + $follower->reset_errors();
335 143 }
336 144 }
337 145 }
338 146
339 147 /**
340 - * Schedule the outbox item for federation.
148 + * Schedule migration if DB-Version is not up to date.
341 149 *
342 - * @param int $id The ID of the outbox item.
343 - * @param int $offset The offset to add to the scheduled time. Default 3 seconds.
150 + * @return void
344 151 */
345 - public static function schedule_outbox_activity_for_federation( $id, $offset = 3 ) {
346 - $hook = 'activitypub_process_outbox';
347 - $args = array( $id );
348 -
349 - if ( false === wp_next_scheduled( $hook, $args ) ) {
350 - \wp_schedule_single_event(
351 - \time() + $offset,
352 - $hook,
353 - $args
354 - );
152 + public static function schedule_migration() {
153 + if ( ! \wp_next_scheduled( 'activitypub_schedule_migration' ) && ! Migration::is_latest_version() ) {
154 + \wp_schedule_single_event( \time(), 'activitypub_schedule_migration' );
355 155 }
356 - }
357 -
358 - /**
359 - * Reprocess the outbox.
360 - */
361 - public static function reprocess_outbox() {
362 - $ids = \get_posts(
363 - array(
364 - 'post_type' => Outbox::POST_TYPE,
365 - 'post_status' => 'pending',
366 - 'posts_per_page' => 10,
367 - 'fields' => 'ids',
368 - )
369 - );
370 -
371 - foreach ( $ids as $id ) {
372 - // Bail if there is a pending batch.
373 - $offset = \get_post_meta( $id, '_activitypub_outbox_offset', true ) ?: 0; // phpcs:ignore
374 - if ( \wp_next_scheduled( 'activitypub_send_activity', array( $id, Dispatcher::get_batch_size(), $offset ) ) ) {
375 - return;
376 - }
377 -
378 - // Bail if there is a batch in progress.
379 - $key = \md5( \serialize( $id ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
380 - if ( self::is_locked( $key ) ) {
381 - return;
382 - }
383 -
384 - self::schedule_outbox_activity_for_federation( $id );
385 - }
386 - }
387 -
388 - /**
389 - * Purge outbox items based on a schedule.
390 - */
391 - public static function purge_outbox() {
392 - Outbox::purge( \get_option( 'activitypub_outbox_purge_days', ACTIVITYPUB_OUTBOX_PURGE_DAYS ) );
393 - }
394 -
395 - /**
396 - * Purge inbox items based on a schedule.
397 - */
398 - public static function purge_inbox() {
399 - Inbox::purge( \get_option( 'activitypub_inbox_purge_days', ACTIVITYPUB_INBOX_PURGE_DAYS ) );
400 - }
401 -
402 - /**
403 - * Purge remote posts based on a schedule.
404 - */
405 - public static function purge_ap_posts() {
406 - Remote_Posts::purge( \get_option( 'activitypub_ap_post_purge_days', ACTIVITYPUB_AP_POST_PURGE_DAYS ) );
407 - }
408 -
409 - /**
410 - * Daily cron handler that purges expired tombstones.
411 - *
412 - * Retention is non-urgent: large backlogs (e.g. after retention is first enforced)
413 - * drain across multiple daily runs.
414 - *
415 - * @since 8.3.0
416 - */
417 - public static function purge_tombstones() {
418 - \Activitypub\Tombstone::purge();
419 - }
420 -
421 - /**
422 - * Process cached inbox activity.
423 - *
424 - * Retrieves all collected user IDs for an activity and processes them together.
425 - *
426 - * @param string $activity_id The activity ID.
427 - */
428 - public static function process_inbox_activity( $activity_id ) {
429 - // Deduplicate if multiple inbox items were created due to race condition.
430 - $inbox_item = Inbox::deduplicate( $activity_id );
431 - if ( ! $inbox_item ) {
432 - return;
433 - }
434 -
435 - $data = \json_decode( $inbox_item->post_content, true );
436 - // Reconstruct activity from inbox post.
437 - $activity = Activity::init_from_array( $data );
438 - $type = \Activitypub\camel_to_snake_case( $activity->get_type() );
439 - $context = Inbox::CONTEXT_INBOX;
440 - $user_ids = Inbox::get_recipients( $inbox_item->ID );
441 -
442 - /**
443 - * Fires after any ActivityPub Inbox activity has been handled, regardless of activity type.
444 - *
445 - * This hook is triggered for all activity types processed by the inbox handler.
446 - *
447 - * @param array $data The data array.
448 - * @param array $user_ids The user IDs.
449 - * @param string $type The type of the activity.
450 - * @param Activity $activity The Activity object.
451 - * @param int $result The ID of the inbox item that was created, or WP_Error if failed.
452 - * @param string $context The context of the request ('inbox' or 'shared_inbox').
453 - */
454 - \do_action( 'activitypub_handled_inbox', $data, $user_ids, $type, $activity, $inbox_item->ID, $context );
455 -
456 - /**
457 - * Fires after an ActivityPub Inbox activity has been handled.
458 - *
459 - * @param array $data The data array.
460 - * @param array $user_ids The user IDs.
461 - * @param Activity $activity The Activity object.
462 - * @param int $result The ID of the inbox item that was created, or WP_Error if failed.
463 - * @param string $context The context of the request ('inbox' or 'shared_inbox').
464 - */
465 - \do_action( 'activitypub_handled_inbox_' . $type, $data, $user_ids, $activity, $inbox_item->ID, $context );
466 - }
467 -
468 - /**
469 - * Update schedules when outbox purge days settings change.
470 - *
471 - * @param int $old_value The old value.
472 - * @param int $value The new value.
473 - */
474 - public static function update_outbox_purge_schedule( $old_value, $value ) {
475 - if ( 0 === (int) $value ) {
476 - \wp_clear_scheduled_hook( 'activitypub_outbox_purge' );
477 - } elseif ( ! \wp_next_scheduled( 'activitypub_outbox_purge' ) ) {
478 - \wp_schedule_event( \time(), 'daily', 'activitypub_outbox_purge' );
479 - }
480 - }
481 -
482 - /**
483 - * Update schedules when inbox purge days settings change.
484 - *
485 - * @param int $old_value The old value.
486 - * @param int $value The new value.
487 - */
488 - public static function update_inbox_purge_schedule( $old_value, $value ) {
489 - if ( 0 === (int) $value ) {
490 - \wp_clear_scheduled_hook( 'activitypub_inbox_purge' );
491 - } elseif ( ! \wp_next_scheduled( 'activitypub_inbox_purge' ) ) {
492 - \wp_schedule_event( \time(), 'daily', 'activitypub_inbox_purge' );
493 - }
494 - }
495 -
496 - /**
497 - * Update schedules when remote posts purge days settings change.
498 - *
499 - * @param int $old_value The old value.
500 - * @param int $value The new value.
501 - */
502 - public static function update_ap_post_purge_schedule( $old_value, $value ) {
503 - if ( 0 === (int) $value ) {
504 - \wp_clear_scheduled_hook( 'activitypub_ap_post_purge' );
505 - } elseif ( ! \wp_next_scheduled( 'activitypub_ap_post_purge' ) ) {
506 - \wp_schedule_event( \time(), 'daily', 'activitypub_ap_post_purge' );
507 - }
508 - }
509 -
510 - /**
511 - * Asynchronously runs batch processing routines.
512 - *
513 - * The batching part is optional and only comes into play if the callback returns anything.
514 - * Beyond that it's a helper to run a callback asynchronously with locking to prevent simultaneous processing.
515 - *
516 - * @params mixed ...$args Optional. Parameters that get passed to the callback.
517 - */
518 - public static function async_batch() {
519 - $args = \func_get_args(); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue
520 - $callback = self::$batch_callbacks[ \current_action() ] ?? $args[0] ?? null;
521 - if ( ! \is_callable( $callback ) ) {
522 - \_doing_it_wrong( __METHOD__, 'There must be a valid callback associated with the current action.', '5.2.0' );
523 - return;
524 - }
525 -
526 - $key = \md5( \serialize( $callback ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
527 -
528 - // Bail if the existing lock is still valid.
529 - if ( self::is_locked( $key ) ) {
530 - \wp_schedule_single_event( \time() + MINUTE_IN_SECONDS, \current_action(), $args );
531 - return;
532 - }
533 -
534 - self::lock( $key );
535 -
536 - if ( \is_callable( $args[0] ?? null ) ) {
537 - $callback = \array_shift( $args ); // Remove $callback from arguments.
538 - }
539 - $next = \call_user_func_array( $callback, $args );
540 -
541 - self::unlock( $key );
542 -
543 - if ( ! empty( $next ) ) {
544 - // Schedule the next run, adding the result to the arguments.
545 - \wp_schedule_single_event( \time() + self::get_retry_delay(), \current_action(), \array_values( $next ) );
546 - }
547 - }
548 -
549 - /**
550 - * Locks the async batch process for individual callbacks to prevent simultaneous processing.
551 - *
552 - * @param string $key Serialized callback name.
553 - * @return bool|int True if the lock was successful, timestamp of existing lock otherwise.
554 - */
555 - public static function lock( $key ) {
556 - global $wpdb;
557 -
558 - // Try to lock.
559 - $lock_result = (bool) $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", 'activitypub_async_batch_' . $key, \time() ) ); // phpcs:ignore WordPress.DB
560 -
561 - if ( ! $lock_result ) {
562 - $lock_result = \get_option( 'activitypub_async_batch_' . $key );
563 - }
564 -
565 - return $lock_result;
566 - }
567 -
568 - /**
569 - * Unlocks processing for the async batch callback.
570 - *
571 - * @param string $key Serialized callback name.
572 - */
573 - public static function unlock( $key ) {
574 - \delete_option( 'activitypub_async_batch_' . $key );
575 - }
576 -
577 - /**
578 - * Whether the async batch callback is locked.
579 - *
580 - * @param string $key Serialized callback name.
581 - * @return boolean
582 - */
583 - public static function is_locked( $key ) {
584 - $lock = \get_option( 'activitypub_async_batch_' . $key );
585 -
586 - if ( ! $lock ) {
587 - return false;
588 - }
589 -
590 - $lock = (int) $lock;
591 -
592 - if ( $lock < \time() - 1800 ) {
593 - self::unlock( $key );
594 - return false;
595 - }
596 -
597 - return true;
598 - }
599 -
600 - /**
601 - * Send announces.
602 - *
603 - * @param int $outbox_activity_id The outbox activity ID.
604 - * @param Activity $activity The activity object.
605 - * @param int $actor_id The actor ID.
606 - * @param int $content_visibility The content visibility.
607 - */
608 - public static function schedule_announce_activity( $outbox_activity_id, $activity, $actor_id, $content_visibility ) {
609 - // Only if we're in both Blog and User modes.
610 - if ( ACTIVITYPUB_ACTOR_AND_BLOG_MODE !== \get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE ) ) {
611 - return;
612 - }
613 -
614 - // Only if this isn't the Blog Actor.
615 - if ( Actors::BLOG_USER_ID === $actor_id ) {
616 - return;
617 - }
618 -
619 - // Only if the content is public or quiet public.
620 - if ( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC !== $content_visibility ) {
621 - return;
622 - }
623 -
624 - // Only if the activity is a Create.
625 - if ( 'Create' !== $activity->get_type() ) {
626 - return;
627 - }
628 -
629 - if ( ! is_object( $activity->get_object() ) ) {
630 - return;
631 - }
632 -
633 - // Check if the object is an article, image, audio, video, event, or document and ignore profile updates and other activities.
634 - if ( ! in_array( $activity->get_object()->get_type(), Base_Object::TYPES, true ) ) {
635 - return;
636 - }
637 -
638 - $announce = new Activity();
639 - $announce->set_type( 'Announce' );
640 - $announce->set_actor( Actors::get_by_id( Actors::BLOG_USER_ID )->get_id() );
641 - $announce->set_object( $activity );
642 - $announce->add_cc( object_to_uri( $activity->get_actor() ) );
643 -
644 - $outbox_activity_id = Outbox::add( $announce, Actors::BLOG_USER_ID );
645 -
646 - if ( ! $outbox_activity_id ) {
647 - return;
648 - }
649 -
650 - // Schedule the outbox item for federation.
651 - self::schedule_outbox_activity_for_federation( $outbox_activity_id, 120 );
652 156 }
653 157 }