PluginProbe
ActivityPub / 5.7.0
ActivityPub v5.7.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/collection/class-outbox.php +66 -337 9.2.05.7.0 View file →
@@ -6,17 +6,14 @@
6 6 */
7 7
8 8 namespace Activitypub\Collection;
9 9
10 +use Activitypub\Dispatcher;
11 +use Activitypub\Scheduler;
10 12 use Activitypub\Activity\Activity;
11 13 use Activitypub\Activity\Base_Object;
12 -use Activitypub\OAuth\Server;
13 -use Activitypub\Scheduler;
14 -use Activitypub\Webfinger;
15 14
16 15 use function Activitypub\add_to_outbox;
17 -use function Activitypub\object_to_uri;
18 -use function Activitypub\user_can_act_as_blog;
19 16
20 17 /**
21 18 * ActivityPub Outbox Collection
22 19 *
@@ -22,48 +19,11 @@
22 19 *
23 20 * @link https://www.w3.org/TR/activitypub/#outbox
24 21 */
25 22 class Outbox {
26 - /**
27 - * The post type for the objects.
28 - *
29 - * @var string
30 - */
31 23 const POST_TYPE = 'ap_outbox';
32 24
33 25 /**
34 - * Maximum number of outbox items to keep.
35 - *
36 - * When the total count exceeds this, the oldest items are purged
37 - * regardless of their age. Acts as a safety net for runaway growth.
38 - *
39 - * @var int
40 - */
41 - const MAX_ITEMS = 5000;
42 -
43 - /**
44 - * Activity types included in the outbox collection listing.
45 - *
46 - * @var string[]
47 - */
48 - const ACTIVITY_TYPES = array( 'Announce', 'Arrive', 'Create', 'Like', 'Update' );
49 -
50 -
51 - /**
52 - * Number of items to process per batch during purge.
53 - *
54 - * @var int
55 - */
56 - const PURGE_BATCH_SIZE = 100;
57 -
58 - /**
59 - * Maximum seconds a purge run may take before yielding.
60 - *
61 - * @var int
62 - */
63 - const PURGE_TIMEOUT = 30;
64 -
65 - /**
66 26 * Add an Item to the outbox.
67 27 *
68 28 * @param Activity $activity Full Activity object that will be added to the outbox.
69 29 * @param int $user_id The real or imaginary user ID of the actor that published the activity that will be added to the outbox.
@@ -72,45 +32,24 @@
72 32 * @return false|int|\WP_Error The added item or an error.
73 33 */
74 34 public static function add( Activity $activity, $user_id, $visibility = ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC ) {
75 35 $actor_type = Actors::get_type_by_id( $user_id );
36 + $object_id = self::get_object_id( $activity );
37 + $title = self::get_object_title( $activity->get_object() );
76 38
77 39 if ( ! $activity->get_actor() ) {
78 40 $activity->set_actor( Actors::get_by_id( $user_id )->get_id() );
79 41 }
80 42
81 - $object_id = object_to_uri( self::get_object_id( $activity ) );
82 - $title = self::get_object_title( $activity->get_object() );
83 -
84 - if ( ! $object_id || ! \is_string( $object_id ) ) {
85 - return new \WP_Error(
86 - 'activitypub_outbox_invalid_object_id',
87 - \__( 'Unable to determine an object ID for this activity.', 'activitypub' ),
88 - array( 'status' => 400 )
89 - );
90 - }
91 -
92 - if ( ! \filter_var( $object_id, FILTER_VALIDATE_URL ) ) {
93 - $object_id = Webfinger::resolve( $object_id );
94 - }
95 -
96 - if ( \is_wp_error( $object_id ) ) {
97 - return $object_id;
98 - }
99 -
100 - // Save activity in the context of an activitypub request.
101 - \add_filter( 'activitypub_is_activitypub_request', '__return_true' );
102 -
103 43 $outbox_item = array(
104 44 'post_type' => self::POST_TYPE,
105 - 'post_title' => \sprintf(
45 + 'post_title' => sprintf(
106 46 /* translators: 1. Activity type, 2. Object Title or Excerpt */
107 - \__( '[%1$s] %2$s', 'activitypub' ),
47 + __( '[%1$s] %2$s', 'activitypub' ),
108 48 $activity->get_type(),
109 49 \wp_trim_words( $title, 5 )
110 50 ),
111 - // Persist the blind audience so later dispatch can compute recipients from `bto`/`bcc`.
112 - 'post_content' => \wp_slash( $activity->to_json( true, true ) ),
51 + 'post_content' => wp_slash( $activity->to_json() ),
113 52 // ensure that user ID is not below 0.
114 53 'post_author' => \max( $user_id, 0 ),
115 54 'post_status' => 'pending',
116 55 'meta_input' => array(
@@ -120,10 +59,8 @@
120 59 'activitypub_content_visibility' => $visibility,
121 60 ),
122 61 );
123 62
124 - \remove_filter( 'activitypub_is_activitypub_request', '__return_true' );
125 -
126 63 $has_kses = false !== \has_filter( 'content_save_pre', 'wp_filter_post_kses' );
127 64 if ( $has_kses ) {
128 65 // Prevent KSES from corrupting JSON in post_content.
129 66 \kses_remove_filters();
@@ -137,9 +74,9 @@
137 74
138 75 \wp_update_post(
139 76 array(
140 77 'ID' => $id,
141 - 'post_content' => \wp_slash( $activity->to_json( true, true ) ),
78 + 'post_content' => \wp_slash( $activity->to_json() ),
142 79 )
143 80 );
144 81 }
145 82
@@ -154,40 +91,26 @@
154 91 if ( ! $id ) {
155 92 return false;
156 93 }
157 94
158 - self::delete_superseded_items( $object_id, $activity->get_type(), $id );
95 + self::invalidate_existing_items( $object_id, $activity->get_type(), $id );
159 96
160 97 return $id;
161 98 }
162 99
163 100 /**
164 - * Delete pending outbox items that have been superseded by a newer item.
101 + * Invalidate existing outbox items with the same activity type and object ID
102 + * by setting their status to 'publish'.
165 103 *
166 - * For most activity types, only items with the same type and object ID are
167 - * deleted. Delete activities are a special case: they supersede all pending
168 - * items for the same object regardless of type.
104 + * @param string $object_id The ID of the activity object.
105 + * @param string $activity_type The type of the activity.
106 + * @param int $current_id The ID of the current outbox item to exclude.
169 107 *
170 - * Unschedules all federation events before deleting each item.
171 - * Skips Follow, Announce, Accept, and Reject activities, as those are
172 - * independent per-request responses that must not cancel each other.
173 - *
174 - * @param string $object_id The ActivityPub object ID (URL).
175 - * @param string $activity_type The activity type (e.g. 'Create', 'Update', 'Delete').
176 - * @param int $exclude_id The ID of the newly added outbox item to keep.
177 - *
178 108 * @return void
179 109 */
180 - private static function delete_superseded_items( $object_id, $activity_type, $exclude_id ) {
181 - /*
182 - * Do not delete items for Follow, Announce, Accept, or Reject activities.
183 - * Follow activities from different users share the same object ID but are
184 - * independent and must survive until their Accept is received.
185 - * Accept/Reject are per-request responses (e.g. to individual incoming
186 - * QuoteRequests) and must not cancel each other even when they share
187 - * the same object ID.
188 - */
189 - if ( \in_array( $activity_type, array( 'Follow', 'Announce', 'Accept', 'Reject' ), true ) ) {
110 + private static function invalidate_existing_items( $object_id, $activity_type, $current_id ) {
111 + // Do not invalidate items for Announce activities.
112 + if ( 'Announce' === $activity_type ) {
190 113 return;
191 114 }
192 115
193 116 $meta_query = array(
@@ -196,45 +119,21 @@
196 119 'value' => $object_id,
197 120 ),
198 121 );
199 122
200 - /*
201 - * Same-type pending items are always superseded. A confirmed
202 - * re-publish (Create) additionally invalidates a pending Delete so
203 - * we do not send both Delete and Create for the same object.
204 - *
205 - * Update is intentionally NOT in this list: it must not cancel a
206 - * pending Delete, or an unrelated edit could flip a hidden object back
207 - * to federated. An Update for an already-deleted object is rejected
208 - * upstream in `add_to_outbox()`, and the scheduler re-publish path emits
209 - * a Create (not an Update), so that legitimate path still cancels Delete.
210 - */
123 + // For non-Delete activities, only invalidate items of the same type.
211 124 if ( 'Delete' !== $activity_type ) {
212 - $types = 'Create' === $activity_type
213 - ? array( 'Create', 'Delete' )
214 - : array( $activity_type );
215 -
216 125 $meta_query[] = array(
217 - 'key' => '_activitypub_activity_type',
218 - 'value' => $types,
219 - 'compare' => 'IN',
126 + 'key' => '_activitypub_activity_type',
127 + 'value' => $activity_type,
220 128 );
221 129 }
222 130
223 - /*
224 - * Delete wipes the entire outbox history for the object — any
225 - * already-sent Create/Update/etc. is now stale and a redelivery
226 - * retry would resurrect content we are tearing down. Other
227 - * activity types only invalidate pending peers.
228 - */
229 - $status_filter = 'Delete' === $activity_type ? 'any' : 'pending';
230 -
231 - $existing_items = \get_posts(
131 + $existing_items = get_posts(
232 132 array(
233 133 'post_type' => self::POST_TYPE,
234 - 'post_status' => $status_filter,
235 - 'exclude' => array( $exclude_id ),
236 - 'numberposts' => -1,
134 + 'post_status' => 'pending',
135 + 'exclude' => array( $current_id ),
237 136 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
238 137 'meta_query' => $meta_query,
239 138 'fields' => 'ids',
240 139 )
@@ -240,10 +139,23 @@
240 139 )
241 140 );
242 141
243 142 foreach ( $existing_items as $existing_item_id ) {
244 - Scheduler::unschedule_events_for_item( $existing_item_id );
245 - \wp_delete_post( $existing_item_id, true );
143 + $event_args = array(
144 + Dispatcher::$callback,
145 + $existing_item_id,
146 + Dispatcher::$batch_size,
147 + \get_post_meta( $existing_item_id, '_activitypub_outbox_offset', true ) ?: 0, // phpcs:ignore
148 + );
149 +
150 + $timestamp = \wp_next_scheduled( 'activitypub_async_batch', $event_args );
151 + \wp_unschedule_event( $timestamp, 'activitypub_async_batch', $event_args );
152 +
153 + $timestamp = \wp_next_scheduled( 'activitypub_process_outbox', array( $existing_item_id ) );
154 + \wp_unschedule_event( $timestamp, 'activitypub_process_outbox', array( $existing_item_id ) );
155 +
156 + \wp_publish_post( $existing_item_id );
157 + \delete_post_meta( $existing_item_id, '_activitypub_outbox_offset' );
246 158 }
247 159 }
248 160
249 161 /**
@@ -250,18 +162,14 @@
250 162 * Creates an Undo activity.
251 163 *
252 164 * @param int|\WP_Post $outbox_item The Outbox post or post ID.
253 165 *
254 - * @return int|bool|\WP_Error The ID of the outbox item or false on failure.
166 + * @return int|bool The ID of the outbox item or false on failure.
255 167 */
256 168 public static function undo( $outbox_item ) {
257 - $outbox_item = \get_post( $outbox_item );
169 + $outbox_item = get_post( $outbox_item );
258 170 $activity = self::get_activity( $outbox_item );
259 171
260 - if ( \is_wp_error( $activity ) ) {
261 - return $activity;
262 - }
263 -
264 172 $type = 'Undo';
265 173 if ( 'Create' === $activity->get_type() ) {
266 174 $type = 'Delete';
267 175 } elseif ( 'Add' === $activity->get_type() ) {
@@ -267,74 +175,12 @@
267 175 } elseif ( 'Add' === $activity->get_type() ) {
268 176 $type = 'Remove';
269 177 }
270 178
271 - $visibility = \get_post_meta( $outbox_item->ID, 'activitypub_content_visibility', true );
272 -
273 - return add_to_outbox( $activity, $type, $outbox_item->post_author, $visibility );
179 + return add_to_outbox( $activity, $type, $outbox_item->post_author );
274 180 }
275 181
276 182 /**
277 - * Get an outbox item by object ID and activity type.
278 - *
279 - * @param string $object_id The ActivityPub object ID.
280 - * @param string $activity_type The activity type (Create, Update, etc.).
281 - *
282 - * @return \WP_Post|null The outbox item or null if not found.
283 - */
284 - public static function get_by_object_id( $object_id, $activity_type ) {
285 - $outbox_items = \get_posts(
286 - array(
287 - 'post_type' => self::POST_TYPE,
288 - 'post_status' => 'any',
289 - 'posts_per_page' => 1,
290 - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
291 - 'meta_query' => array(
292 - array(
293 - 'key' => '_activitypub_object_id',
294 - 'value' => $object_id,
295 - ),
296 - array(
297 - 'key' => '_activitypub_activity_type',
298 - 'value' => $activity_type,
299 - ),
300 - ),
301 - )
302 - );
303 -
304 - return ! empty( $outbox_items ) ? $outbox_items[0] : null;
305 - }
306 -
307 - /**
308 - * Get an outbox item by its GUID.
309 - *
310 - * @param string $guid The GUID of the outbox item.
311 - *
312 - * @return \WP_Post|\WP_Error The outbox item or WP_Error.
313 - */
314 - public static function get_by_guid( $guid ) {
315 - global $wpdb;
316 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
317 - $post_id = $wpdb->get_var(
318 - $wpdb->prepare(
319 - "SELECT ID FROM $wpdb->posts WHERE guid=%s AND post_type=%s",
320 - \esc_url( $guid ),
321 - self::POST_TYPE
322 - )
323 - );
324 -
325 - if ( ! $post_id ) {
326 - return new \WP_Error(
327 - 'activitypub_outbox_item_not_found',
328 - \__( 'Outbox item not found', 'activitypub' ),
329 - array( 'status' => 404 )
330 - );
331 - }
332 -
333 - return \get_post( $post_id );
334 - }
335 -
336 - /**
337 183 * Reschedule an activity.
338 184 *
339 185 * @param int|\WP_Post $outbox_item The Outbox post or post ID.
340 186 *
@@ -340,14 +186,14 @@
340 186 *
341 187 * @return bool True if the activity was rescheduled, false otherwise.
342 188 */
343 189 public static function reschedule( $outbox_item ) {
344 - $outbox_item = \get_post( $outbox_item );
190 + $outbox_item = get_post( $outbox_item );
345 191
346 192 $outbox_item->post_status = 'pending';
347 - $outbox_item->post_date = \current_time( 'mysql' );
193 + $outbox_item->post_date = current_time( 'mysql' );
348 194
349 - \wp_update_post( $outbox_item );
195 + wp_update_post( $outbox_item );
350 196
351 197 Scheduler::schedule_outbox_activity_for_federation( $outbox_item->ID );
352 198
353 199 return true;
@@ -359,16 +205,12 @@
359 205 * @param int|\WP_Post $outbox_item The Outbox post or post ID.
360 206 * @return Activity|\WP_Error The Activity object or WP_Error.
361 207 */
362 208 public static function get_activity( $outbox_item ) {
363 - $outbox_item = \get_post( $outbox_item );
364 -
365 - if ( ! $outbox_item ) {
366 - return new \WP_Error(
367 - 'activitypub_outbox_item_not_found',
368 - \__( 'Outbox item not found.', 'activitypub' ),
369 - array( 'status' => 404 )
370 - );
209 + $outbox_item = get_post( $outbox_item );
210 + $actor = self::get_actor( $outbox_item );
211 + if ( is_wp_error( $actor ) ) {
212 + return $actor;
371 213 }
372 214
373 215 $activity_object = \json_decode( $outbox_item->post_content, true );
374 216 $type = \get_post_meta( $outbox_item->ID, '_activitypub_activity_type', true );
@@ -375,20 +217,11 @@
375 217
376 218 if ( $activity_object['type'] === $type ) {
377 219 $activity = Activity::init_from_array( $activity_object );
378 220 if ( ! $activity->get_actor() ) {
379 - $actor = self::get_actor( $outbox_item );
380 - if ( \is_wp_error( $actor ) ) {
381 - return $actor;
382 - }
383 221 $activity->set_actor( $actor->get_id() );
384 222 }
385 223 } else {
386 - $actor = self::get_actor( $outbox_item );
387 - if ( \is_wp_error( $actor ) ) {
388 - return $actor;
389 - }
390 -
391 224 $activity = new Activity();
392 225 $activity->set_type( $type );
393 226 $activity->set_id( $outbox_item->guid );
394 227 $activity->set_actor( $actor->get_id() );
@@ -396,9 +229,9 @@
396 229 $activity->set_object( $activity_object );
397 230 }
398 231
399 232 if ( 'Update' === $type ) {
400 - $activity->set_updated( \gmdate( ACTIVITYPUB_DATE_TIME_RFC3339, \strtotime( $outbox_item->post_modified ) ) );
233 + $activity->set_updated( gmdate( ACTIVITYPUB_DATE_TIME_RFC3339, strtotime( $outbox_item->post_modified ) ) );
401 234 }
402 235
403 236 /**
404 237 * Filters the Activity object before it is returned.
@@ -405,9 +238,9 @@
405 238 *
406 239 * @param Activity $activity The Activity object.
407 240 * @param \WP_Post $outbox_item The outbox item post object.
408 241 */
409 - return \apply_filters( 'activitypub_get_outbox_activity', $activity, $outbox_item );
242 + return apply_filters( 'activitypub_get_outbox_activity', $activity, $outbox_item );
410 243 }
411 244
412 245 /**
413 246 * Get the Actor object from the Outbox item.
@@ -422,8 +255,11 @@
422 255 switch ( $actor_type ) {
423 256 case 'blog':
424 257 $actor_id = Actors::BLOG_USER_ID;
425 258 break;
259 + case 'application':
260 + $actor_id = Actors::APPLICATION_USER_ID;
261 + break;
426 262 case 'user':
427 263 default:
428 264 $actor_id = $outbox_item->post_author;
429 265 break;
@@ -447,45 +283,19 @@
447 283 if ( 'ap_outbox' !== $outbox_item->post_type ) {
448 284 return new \WP_Error( 'invalid_outbox_item', 'Invalid Outbox item.' );
449 285 }
450 286
451 - // Authenticate via Bearer token for non-REST requests (e.g. permalink access).
452 - if ( \get_option( 'activitypub_api', false ) && ! \is_user_logged_in() && ! \wp_is_serving_rest_request() ) {
453 - Server::authenticate_oauth( null );
454 - }
455 -
456 - /*
457 - * Allow the author to view their own outbox items regardless of visibility.
458 - * The `is_user_logged_in()` guard prevents anonymous visitors from matching
459 - * the blog actor's items (where both `get_current_user_id()` and `post_author`
460 - * are `0`), which would otherwise expose private activities at their permalink.
461 - *
462 - * Users authorized to act as the blog actor are treated as the author of
463 - * blog-actor items so they can read the same private outbox they can post to.
464 - */
465 - if ( \is_user_logged_in() ) {
466 - $author = (int) $outbox_item->post_author;
467 -
468 - if ( \get_current_user_id() === $author ) {
469 - return self::get_activity( $outbox_item );
470 - }
471 -
472 - if ( Actors::BLOG_USER_ID === $author && user_can_act_as_blog() ) {
473 - return self::get_activity( $outbox_item );
474 - }
475 - }
476 -
477 287 // Check if Outbox Activity is public.
478 288 $visibility = \get_post_meta( $outbox_item->ID, 'activitypub_content_visibility', true );
479 289
480 - if ( ! \in_array( $visibility, array( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC, ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC ), true ) ) {
290 + if ( ! in_array( $visibility, array( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC, ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC ), true ) ) {
481 291 return new \WP_Error( 'private_outbox_item', 'Not a public Outbox item.' );
482 292 }
483 293
484 - $activity_types = \apply_filters( 'rest_activitypub_outbox_activity_types', self::ACTIVITY_TYPES );
294 + $activity_types = \apply_filters( 'rest_activitypub_outbox_activity_types', array( 'Announce', 'Create', 'Like', 'Update' ) );
485 295 $activity_type = \get_post_meta( $outbox_item->ID, '_activitypub_activity_type', true );
486 296
487 - if ( ! \in_array( $activity_type, $activity_types, true ) ) {
297 + if ( ! in_array( $activity_type, $activity_types, true ) ) {
488 298 return new \WP_Error( 'private_outbox_item', 'Not public Outbox item type.' );
489 299 }
490 300
491 301 return self::get_activity( $outbox_item );
@@ -495,32 +305,28 @@
495 305 * Get the object ID of an activity.
496 306 *
497 307 * @param Activity|Base_Object|string $data The activity object.
498 308 *
499 - * @return string|null The object ID.
309 + * @return string The object ID.
500 310 */
501 311 private static function get_object_id( $data ) {
502 312 $object = $data->get_object();
503 313
504 - if ( \is_object( $object ) ) {
314 + if ( is_object( $object ) ) {
505 315 return self::get_object_id( $object );
506 316 }
507 317
508 - if ( \is_string( $object ) ) {
318 + if ( is_string( $object ) ) {
509 319 return $object;
510 320 }
511 321
512 - if ( $data->get_id() ) {
513 - return $data->get_id();
514 - }
515 -
516 - return object_to_uri( $data->get_actor() );
322 + return $data->get_id() ?? $data->get_actor();
517 323 }
518 324
519 325 /**
520 326 * Get the title of an activity recursively.
521 327 *
522 - * @param Activity|Base_Object $activity_object The activity object.
328 + * @param Base_Object $activity_object The activity object.
523 329 *
524 330 * @return string The title.
525 331 */
526 332 private static function get_object_title( $activity_object ) {
@@ -527,96 +333,19 @@
527 333 if ( ! $activity_object ) {
528 334 return '';
529 335 }
530 336
531 - if ( \is_string( $activity_object ) ) {
532 - $post_id = \url_to_postid( $activity_object );
337 + if ( is_string( $activity_object ) ) {
338 + $post_id = url_to_postid( $activity_object );
533 339
534 - return $post_id ? \get_the_title( $post_id ) : '';
340 + return $post_id ? get_the_title( $post_id ) : '';
535 341 }
536 342
537 - $title = $activity_object->get_name() ?: $activity_object->get_content();
343 + $title = $activity_object->get_name() ?? $activity_object->get_content();
538 344
539 345 if ( ! $title && $activity_object->get_object() instanceof Base_Object ) {
540 - $title = $activity_object->get_object()->get_name() ?: $activity_object->get_object()->get_content();
346 + $title = $activity_object->get_object()->get_name() ?? $activity_object->get_object()->get_content();
541 347 }
542 348
543 349 return $title;
544 - }
545 -
546 - /**
547 - * Purge old outbox items.
548 - *
549 - * Deletes outbox items older than the specified number of days,
550 - * except for Follow activities which are always preserved.
551 - * Also enforces a hard cap on total items via MAX_ITEMS.
552 - *
553 - * @param int $days Number of days to keep items. Items older than this will be deleted.
554 - *
555 - * @return int The number of items deleted.
556 - */
557 - public static function purge( $days ) {
558 - if ( $days <= 0 ) {
559 - return 0;
560 - }
561 -
562 - $counts = \wp_count_posts( self::POST_TYPE );
563 - $total = 0;
564 - foreach ( $counts as $count ) {
565 - $total += (int) $count;
566 - }
567 -
568 - if ( $total <= 20 ) {
569 - return 0;
570 - }
571 -
572 - $deleted = 0;
573 - $cutoff = \gmdate( 'Y-m-d', \time() - ( $days * DAY_IN_SECONDS ) );
574 - $start_time = \time();
575 -
576 - // If total exceeds the hard cap, drop the date filter to purge oldest items first.
577 - $overflow = $total > self::MAX_ITEMS;
578 - $date_query = array(
579 - array(
580 - 'before' => $cutoff,
581 - ),
582 - );
583 -
584 - $query_args = array(
585 - 'post_type' => self::POST_TYPE,
586 - 'post_status' => 'any',
587 - 'fields' => 'ids',
588 - 'numberposts' => self::PURGE_BATCH_SIZE,
589 - 'orderby' => 'date',
590 - 'order' => 'ASC',
591 - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
592 - 'meta_query' => array(
593 - array(
594 - 'key' => '_activitypub_activity_type',
595 - 'value' => 'Follow',
596 - 'compare' => '!=',
597 - ),
598 - ),
599 - );
600 -
601 - if ( ! $overflow ) {
602 - $query_args['date_query'] = $date_query;
603 - }
604 -
605 - do {
606 - $post_ids = \get_posts( $query_args );
607 -
608 - foreach ( $post_ids as $post_id ) {
609 - \wp_delete_post( $post_id, true );
610 - ++$deleted;
611 - }
612 -
613 - // Once we're back under the cap, re-apply the date filter.
614 - if ( $overflow && ( $total - $deleted ) <= self::MAX_ITEMS ) {
615 - $overflow = false;
616 - $query_args['date_query'] = $date_query;
617 - }
618 - } while ( ! empty( $post_ids ) && ( \time() - $start_time ) < self::PURGE_TIMEOUT );
619 -
620 - return $deleted;
621 350 }
622 351 }