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