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