PluginProbe
ActivityPub / 9.0.1
ActivityPub v9.0.1
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-scheduler.php

class-scheduler.php in ActivityPub 9.0.1, at includes/class-scheduler.php

717 lines 22.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Scheduler class file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub;
9
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;
22
23 /**
24 * Scheduler class.
25 *
26 * @author Matthias Pfefferle
27 */
28 class Scheduler {
29
30 /**
31 * Scheduled events with their recurrence.
32 *
33 * @var array
34 */
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 * @param string|false|null $hook Optional. The async batch hook being scheduled. Default current action.
57 *
58 * @return int The pause in seconds.
59 */
60 public static function get_retry_delay( $hook = null ) {
61 if ( null === $hook ) {
62 $hook = \current_action();
63 }
64
65 /**
66 * Filters the pause between async batches (in seconds).
67 *
68 * @param int $async_batch_pause The pause in seconds. Default 30.
69 * @param string|false|null $hook The async batch hook being scheduled.
70 */
71 return apply_filters( 'activitypub_scheduler_async_batch_pause', 30, $hook );
72 }
73
74 /**
75 * Initialize the class, registering WordPress hooks.
76 */
77 public static function init() {
78 self::register_schedulers();
79
80 // Custom cron schedules.
81 \add_filter( 'cron_schedules', array( self::class, 'add_cron_schedules' ) );
82
83 // Follower Cleanups.
84 \add_action( 'activitypub_update_remote_actors', array( self::class, 'update_remote_actors' ) );
85 \add_action( 'activitypub_cleanup_remote_actors', array( self::class, 'cleanup_remote_actors' ) );
86
87 // Event callbacks.
88 \add_action( 'activitypub_async_batch', array( self::class, 'async_batch' ), 10, 99 );
89 \add_action( 'activitypub_reprocess_outbox', array( self::class, 'reprocess_outbox' ) );
90 \add_action( 'activitypub_outbox_purge', array( self::class, 'purge_outbox' ) );
91 \add_action( 'activitypub_inbox_purge', array( self::class, 'purge_inbox' ) );
92 \add_action( 'activitypub_ap_post_purge', array( self::class, 'purge_ap_posts' ) );
93 \add_action( 'activitypub_tombstone_purge', array( self::class, 'purge_tombstones' ) );
94 \add_action( 'activitypub_inbox_create_item', array( self::class, 'process_inbox_activity' ) );
95 \add_action( 'activitypub_sync_blocklist_subscriptions', array( Blocklist_Subscriptions::class, 'sync_all' ) );
96
97 \add_action( 'post_activitypub_add_to_outbox', array( self::class, 'schedule_outbox_activity_for_federation' ) );
98 \add_action( 'post_activitypub_add_to_outbox', array( self::class, 'schedule_announce_activity' ), 10, 4 );
99
100 \add_action( 'update_option_activitypub_outbox_purge_days', array( self::class, 'update_outbox_purge_schedule' ), 10, 2 );
101 \add_action( 'update_option_activitypub_inbox_purge_days', array( self::class, 'update_inbox_purge_schedule' ), 10, 2 );
102 \add_action( 'update_option_activitypub_ap_post_purge_days', array( self::class, 'update_ap_post_purge_schedule' ), 10, 2 );
103 }
104
105 /**
106 * Register handlers.
107 */
108 public static function register_schedulers() {
109 Post::init();
110 Actor::init();
111 Collection_Sync::init();
112 Comment::init();
113 Statistics::init();
114
115 /**
116 * Register additional schedulers.
117 *
118 * @since 5.0.0
119 */
120 \do_action( 'activitypub_register_schedulers' );
121 }
122
123 /**
124 * Add custom cron schedules.
125 *
126 * @param array $schedules Existing cron schedules.
127 *
128 * @return array Modified cron schedules.
129 */
130 public static function add_cron_schedules( $schedules ) {
131 $schedules['monthly'] = array(
132 'interval' => MONTH_IN_SECONDS,
133 'display' => \__( 'Once Monthly', 'activitypub' ),
134 );
135
136 $schedules['yearly'] = array(
137 'interval' => YEAR_IN_SECONDS,
138 'display' => \__( 'Once Yearly', 'activitypub' ),
139 );
140
141 return $schedules;
142 }
143
144 /**
145 * Register a batch callback for async processing.
146 *
147 * @param string $hook The cron event hook name.
148 * @param callable $callback The callback to execute.
149 */
150 public static function register_async_batch_callback( $hook, $callback ) {
151 if ( \did_action( 'init' ) && ! \doing_action( 'init' ) ) {
152 \_doing_it_wrong( __METHOD__, 'Async batch callbacks should be registered before or during the init action.', '7.5.0' );
153 return;
154 }
155
156 if ( ! \is_callable( $callback ) ) {
157 return;
158 }
159
160 self::$batch_callbacks[ $hook ] = $callback;
161
162 // Register the WordPress action hook to trigger async_batch.
163 \add_action( $hook, array( self::class, 'async_batch' ), 10, 99 );
164 }
165
166 /**
167 * Schedule all ActivityPub schedules.
168 */
169 public static function register_schedules() {
170 foreach ( self::SCHEDULES as $hook => $recurrence ) {
171 if ( ! \wp_next_scheduled( $hook ) ) {
172 \wp_schedule_event( time(), $recurrence, $hook );
173 }
174 }
175
176 // Schedule monthly stats collection for the 1st of each month.
177 if ( ! \wp_next_scheduled( 'activitypub_collect_monthly_stats' ) ) {
178 // Calculate next 1st of month at 2:00 AM.
179 $next_first = self::get_next_first_of_month();
180 \wp_schedule_event( $next_first, 'monthly', 'activitypub_collect_monthly_stats' );
181 }
182
183 // Schedule annual stats compilation for December 1st (wrapped notification).
184 if ( ! \wp_next_scheduled( 'activitypub_compile_annual_stats' ) ) {
185 $next_december = self::get_next_december_first();
186 \wp_schedule_event( $next_december, 'yearly', 'activitypub_compile_annual_stats' );
187 }
188 }
189
190 /**
191 * Un-schedule all ActivityPub schedules.
192 *
193 * @return void
194 */
195 public static function deregister_schedules() {
196 foreach ( array_keys( self::SCHEDULES ) as $hook ) {
197 \wp_unschedule_hook( $hook );
198 }
199
200 // Statistics schedules.
201 \wp_unschedule_hook( 'activitypub_collect_monthly_stats' );
202 \wp_unschedule_hook( 'activitypub_compile_annual_stats' );
203 }
204
205 /**
206 * Get the next 1st of month timestamp.
207 *
208 * @return int Unix timestamp of next 1st of month at 2:00 AM.
209 */
210 private static function get_next_first_of_month() {
211 $now = \current_time( 'timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
212 $next_month = \strtotime( 'first day of next month 02:00:00', $now );
213
214 return $next_month;
215 }
216
217 /**
218 * Get the next December 1st timestamp for wrapped notification.
219 *
220 * @return int Unix timestamp of next December 1st at 3:00 AM.
221 */
222 private static function get_next_december_first() {
223 $now = \current_time( 'timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
224 $year = (int) \gmdate( 'Y', $now );
225
226 // Get December 1st 3:00 AM for this year.
227 $this_year_dec_first = \strtotime( sprintf( '%d-12-01 03:00:00', $year ) );
228
229 // If we're already past this year's December 1st, schedule for next year.
230 if ( $now >= $this_year_dec_first ) {
231 return \strtotime( sprintf( '%d-12-01 03:00:00', $year + 1 ) );
232 }
233
234 return $this_year_dec_first;
235 }
236
237 /**
238 * Unschedule events for an outbox item.
239 *
240 * @param int $outbox_item_id The outbox item ID.
241 */
242 public static function unschedule_events_for_item( $outbox_item_id ) {
243 \delete_post_meta( $outbox_item_id, '_activitypub_outbox_offset' );
244
245 $timestamp = \wp_next_scheduled( 'activitypub_process_outbox', array( $outbox_item_id ) );
246 \wp_unschedule_event( $timestamp, 'activitypub_process_outbox', array( $outbox_item_id ) );
247
248 self::unschedule_outbox_delivery_batches( $outbox_item_id );
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 }
263
264 /**
265 * Update remote Actors.
266 */
267 public static function update_remote_actors() {
268 $number = 5;
269
270 if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
271 $number = 50;
272 }
273
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 if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
286 Remote_Actors::add_error( $actor->ID, 'Failed to fetch or parse metadata' );
287 } else {
288 $id = Remote_Actors::upsert( $meta );
289 if ( \is_wp_error( $id ) ) {
290 continue;
291 }
292 Remote_Actors::clear_errors( $id );
293 }
294 }
295 }
296
297 /**
298 * Cleanup remote Actors.
299 */
300 public static function cleanup_remote_actors() {
301 $number = 5;
302
303 if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
304 $number = 50;
305 }
306
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 );
325 } else {
326 Remote_Actors::add_error( $actor->ID, $meta );
327 }
328 } 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 }
335 }
336 }
337 }
338
339 /**
340 * Schedule the outbox item for federation.
341 *
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.
344 */
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 );
355 }
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 ( self::has_scheduled_outbox_delivery_batch( $id, $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() ), \current_action(), \array_values( $next ) );
546 }
547 }
548
549 /**
550 * Whether an outbox item already has a scheduled delivery batch at an offset.
551 *
552 * @param int $outbox_item_id The outbox item ID.
553 * @param int $offset The delivery offset.
554 *
555 * @return bool True when a matching delivery batch is scheduled.
556 */
557 private static function has_scheduled_outbox_delivery_batch( $outbox_item_id, $offset ) {
558 return ! empty( self::get_scheduled_outbox_delivery_batches( $outbox_item_id, $offset ) );
559 }
560
561 /**
562 * Unschedule all pending delivery batches for an outbox item.
563 *
564 * @param int $outbox_item_id The outbox item ID.
565 */
566 private static function unschedule_outbox_delivery_batches( $outbox_item_id ) {
567 foreach ( self::get_scheduled_outbox_delivery_batches( $outbox_item_id ) as $event ) {
568 \wp_unschedule_event( $event['timestamp'], 'activitypub_send_activity', $event['args'] );
569 }
570 }
571
572 /**
573 * Get scheduled delivery batches for an outbox item.
574 *
575 * The batch size is deliberately ignored because scheduled events may
576 * retain an older value after the admin changes the distribution mode.
577 *
578 * @param int $outbox_item_id The outbox item ID.
579 * @param int|null $offset Optional. Restrict results to this delivery offset.
580 *
581 * @return array Scheduled events with timestamp and args.
582 */
583 private static function get_scheduled_outbox_delivery_batches( $outbox_item_id, $offset = null ) {
584 $events = array();
585
586 foreach ( \_get_cron_array() as $timestamp => $cron ) {
587 if ( empty( $cron['activitypub_send_activity'] ) ) {
588 continue;
589 }
590
591 foreach ( $cron['activitypub_send_activity'] as $event ) {
592 $args = $event['args'] ?? array();
593
594 if ( ! isset( $args[0] ) || (int) $outbox_item_id !== (int) $args[0] ) {
595 continue;
596 }
597
598 if ( null !== $offset && (int) ( $args[2] ?? 0 ) !== (int) $offset ) {
599 continue;
600 }
601
602 $events[] = array(
603 'timestamp' => $timestamp,
604 'args' => $args,
605 );
606 }
607 }
608
609 return $events;
610 }
611
612 /**
613 * Locks the async batch process for individual callbacks to prevent simultaneous processing.
614 *
615 * @param string $key Serialized callback name.
616 * @return bool|int True if the lock was successful, timestamp of existing lock otherwise.
617 */
618 public static function lock( $key ) {
619 global $wpdb;
620
621 // Try to lock.
622 $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
623
624 if ( ! $lock_result ) {
625 $lock_result = \get_option( 'activitypub_async_batch_' . $key );
626 }
627
628 return $lock_result;
629 }
630
631 /**
632 * Unlocks processing for the async batch callback.
633 *
634 * @param string $key Serialized callback name.
635 */
636 public static function unlock( $key ) {
637 \delete_option( 'activitypub_async_batch_' . $key );
638 }
639
640 /**
641 * Whether the async batch callback is locked.
642 *
643 * @param string $key Serialized callback name.
644 * @return boolean
645 */
646 public static function is_locked( $key ) {
647 $lock = \get_option( 'activitypub_async_batch_' . $key );
648
649 if ( ! $lock ) {
650 return false;
651 }
652
653 $lock = (int) $lock;
654
655 if ( $lock < \time() - 1800 ) {
656 self::unlock( $key );
657 return false;
658 }
659
660 return true;
661 }
662
663 /**
664 * Send announces.
665 *
666 * @param int $outbox_activity_id The outbox activity ID.
667 * @param Activity $activity The activity object.
668 * @param int $actor_id The actor ID.
669 * @param int $content_visibility The content visibility.
670 */
671 public static function schedule_announce_activity( $outbox_activity_id, $activity, $actor_id, $content_visibility ) {
672 // Only if we're in both Blog and User modes.
673 if ( ACTIVITYPUB_ACTOR_AND_BLOG_MODE !== \get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE ) ) {
674 return;
675 }
676
677 // Only if this isn't the Blog Actor.
678 if ( Actors::BLOG_USER_ID === $actor_id ) {
679 return;
680 }
681
682 // Only if the content is public or quiet public.
683 if ( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC !== $content_visibility ) {
684 return;
685 }
686
687 // Only if the activity is a Create.
688 if ( 'Create' !== $activity->get_type() ) {
689 return;
690 }
691
692 if ( ! is_object( $activity->get_object() ) ) {
693 return;
694 }
695
696 // Check if the object is an article, image, audio, video, event, or document and ignore profile updates and other activities.
697 if ( ! in_array( $activity->get_object()->get_type(), Base_Object::TYPES, true ) ) {
698 return;
699 }
700
701 $announce = new Activity();
702 $announce->set_type( 'Announce' );
703 $announce->set_actor( Actors::get_by_id( Actors::BLOG_USER_ID )->get_id() );
704 $announce->set_object( $activity );
705 $announce->add_cc( object_to_uri( $activity->get_actor() ) );
706
707 $outbox_activity_id = Outbox::add( $announce, Actors::BLOG_USER_ID );
708
709 if ( ! $outbox_activity_id ) {
710 return;
711 }
712
713 // Schedule the outbox item for federation.
714 self::schedule_outbox_activity_for_federation( $outbox_activity_id, 120 );
715 }
716 }
717