| @@ -10,8 +10,9 @@ | ||
| 10 | 10 | use Activitypub\Collection\Actors; |
| 11 | 11 | use Activitypub\Collection\Extra_Fields; |
| 12 | 12 | use Activitypub\Collection\Followers; |
| 13 | 13 | use Activitypub\Collection\Following; |
| 14 | +use Activitypub\Collection\Inbox; | |
| 14 | 15 | use Activitypub\Collection\Outbox; |
| 15 | 16 | use Activitypub\Collection\Remote_Actors; |
| 16 | 17 | use Activitypub\Transformer\Factory; |
| 17 | 18 | |
| @@ -33,8 +34,9 @@ | ||
| 33 | 34 | Scheduler::register_async_batch_callback( 'activitypub_create_comment_outbox_items', array( self::class, 'create_comment_outbox_items' ) ); |
| 34 | 35 | Scheduler::register_async_batch_callback( 'activitypub_migrate_avatar_to_remote_actors', array( self::class, 'migrate_avatar_to_remote_actors' ) ); |
| 35 | 36 | Scheduler::register_async_batch_callback( 'activitypub_migrate_actor_emoji', array( self::class, 'migrate_actor_emoji' ) ); |
| 36 | 37 | Scheduler::register_async_batch_callback( 'activitypub_backfill_statistics', array( Statistics::class, 'backfill_historical_stats' ) ); |
| 38 | + Scheduler::register_async_batch_callback( 'activitypub_tombstone_migrate', array( self::class, 'migrate_tombstones_to_cpt' ) ); | |
| 37 | 39 | } |
| 38 | 40 | |
| 39 | 41 | /** |
| 40 | 42 | * The current version of the database structure. |
| @@ -41,9 +43,9 @@ | ||
| 41 | 43 | * |
| 42 | 44 | * @return string The current version. |
| 43 | 45 | */ |
| 44 | 46 | public static function get_version() { |
| 45 | - return get_option( 'activitypub_db_version', 0 ); | |
| 47 | + return \get_option( 'activitypub_db_version', 0 ); | |
| 46 | 48 | } |
| 47 | 49 | |
| 48 | 50 | /** |
| 49 | 51 | * Locks the database migration process to prevent simultaneous migrations. |
| @@ -183,17 +185,17 @@ | ||
| 183 | 185 | self::migrate_followers_to_ap_actor_cpt(); |
| 184 | 186 | \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_storage' ) ); |
| 185 | 187 | } |
| 186 | 188 | if ( \version_compare( $version_from_db, '7.0.0', '<' ) ) { |
| 187 | - wp_unschedule_hook( 'activitypub_update_followers' ); | |
| 188 | - wp_unschedule_hook( 'activitypub_cleanup_followers' ); | |
| 189 | + \wp_unschedule_hook( 'activitypub_update_followers' ); | |
| 190 | + \wp_unschedule_hook( 'activitypub_cleanup_followers' ); | |
| 189 | 191 | |
| 190 | 192 | if ( ! \wp_next_scheduled( 'activitypub_update_remote_actors' ) ) { |
| 191 | - \wp_schedule_event( time(), 'hourly', 'activitypub_update_remote_actors' ); | |
| 193 | + \wp_schedule_event( \time(), 'hourly', 'activitypub_update_remote_actors' ); | |
| 192 | 194 | } |
| 193 | 195 | |
| 194 | 196 | if ( ! \wp_next_scheduled( 'activitypub_cleanup_remote_actors' ) ) { |
| 195 | - \wp_schedule_event( time(), 'daily', 'activitypub_cleanup_remote_actors' ); | |
| 197 | + \wp_schedule_event( \time(), 'daily', 'activitypub_cleanup_remote_actors' ); | |
| 196 | 198 | } |
| 197 | 199 | } |
| 198 | 200 | if ( \version_compare( $version_from_db, '7.3.0', '<' ) ) { |
| 199 | 201 | self::remove_pending_application_user_follow_requests(); |
| @@ -207,13 +209,23 @@ | ||
| 207 | 209 | } |
| 208 | 210 | if ( \version_compare( $version_from_db, '7.9.0', '<' ) ) { |
| 209 | 211 | \wp_schedule_single_event( \time(), 'activitypub_migrate_actor_emoji' ); |
| 210 | 212 | } |
| 211 | - if ( \version_compare( $version_from_db, '8.1.0', '<' ) ) { | |
| 212 | - // Backfill historical statistics data (delay to avoid load immediately after upgrade). | |
| 213 | - \wp_schedule_single_event( \time() + HOUR_IN_SECONDS, 'activitypub_backfill_statistics' ); | |
| 213 | + if ( \version_compare( $version_from_db, '8.1.0', '<' ) && ! \wp_next_scheduled( 'activitypub_backfill_statistics' ) ) { | |
| 214 | + // Backfill historical statistics data (delay + jitter to avoid load spikes on hosts running many sites). | |
| 215 | + \wp_schedule_single_event( \time() + HOUR_IN_SECONDS + \wp_rand( 0, 6 * HOUR_IN_SECONDS ), 'activitypub_backfill_statistics' ); | |
| 214 | 216 | } |
| 215 | 217 | |
| 218 | + if ( \version_compare( $version_from_db, '8.3.0', '<' ) ) { | |
| 219 | + if ( ! \wp_next_scheduled( 'activitypub_tombstone_migrate' ) ) { | |
| 220 | + \wp_schedule_single_event( \time() + MINUTE_IN_SECONDS, 'activitypub_tombstone_migrate' ); | |
| 221 | + } | |
| 222 | + } | |
| 223 | + if ( \version_compare( $version_from_db, '9.1.0', '<' ) ) { | |
| 224 | + self::migrate_application_keypair_option(); | |
| 225 | + self::delete_application_outbox_items(); | |
| 226 | + } | |
| 227 | + | |
| 216 | 228 | /* |
| 217 | 229 | * Defer the flush to late in the `init` cycle (priority 20). Migration::init |
| 218 | 230 | * runs at priority 1, which is earlier than most plugins register their |
| 219 | 231 | * rewrite rules. Flushing synchronously here would persist a truncated |
| @@ -290,10 +302,10 @@ | ||
| 290 | 302 | * Updates the DB-schema of the followers-list. |
| 291 | 303 | */ |
| 292 | 304 | public static function migrate_from_0_17() { |
| 293 | 305 | // Migrate followers. |
| 294 | - foreach ( get_users( array( 'fields' => 'ID' ) ) as $user_id ) { | |
| 295 | - $followers = get_user_meta( $user_id, 'activitypub_followers', true ); | |
| 306 | + foreach ( \get_users( array( 'fields' => 'ID' ) ) as $user_id ) { | |
| 307 | + $followers = \get_user_meta( $user_id, 'activitypub_followers', true ); | |
| 296 | 308 | |
| 297 | 309 | if ( $followers ) { |
| 298 | 310 | foreach ( $followers as $actor ) { |
| 299 | 311 | Followers::add( $user_id, $actor ); |
| @@ -313,9 +325,9 @@ | ||
| 313 | 325 | ) |
| 314 | 326 | ); |
| 315 | 327 | |
| 316 | 328 | foreach ( $user_ids as $user_id ) { |
| 317 | - wp_cache_delete( sprintf( Followers::CACHE_KEY_INBOXES, $user_id ), 'activitypub' ); | |
| 329 | + \wp_cache_delete( \sprintf( Followers::CACHE_KEY_INBOXES, $user_id ), 'activitypub' ); | |
| 318 | 330 | } |
| 319 | 331 | } |
| 320 | 332 | |
| 321 | 333 | /** |
| @@ -321,15 +333,15 @@ | ||
| 321 | 333 | /** |
| 322 | 334 | * Unschedule Hooks after updating to 2.0.0. |
| 323 | 335 | */ |
| 324 | 336 | private static function migrate_from_2_0_0() { |
| 325 | - wp_clear_scheduled_hook( 'activitypub_send_post_activity' ); | |
| 326 | - wp_clear_scheduled_hook( 'activitypub_send_update_activity' ); | |
| 327 | - wp_clear_scheduled_hook( 'activitypub_send_delete_activity' ); | |
| 337 | + \wp_clear_scheduled_hook( 'activitypub_send_post_activity' ); | |
| 338 | + \wp_clear_scheduled_hook( 'activitypub_send_update_activity' ); | |
| 339 | + \wp_clear_scheduled_hook( 'activitypub_send_delete_activity' ); | |
| 328 | 340 | |
| 329 | - wp_unschedule_hook( 'activitypub_send_post_activity' ); | |
| 330 | - wp_unschedule_hook( 'activitypub_send_update_activity' ); | |
| 331 | - wp_unschedule_hook( 'activitypub_send_delete_activity' ); | |
| 341 | + \wp_unschedule_hook( 'activitypub_send_post_activity' ); | |
| 342 | + \wp_unschedule_hook( 'activitypub_send_update_activity' ); | |
| 343 | + \wp_unschedule_hook( 'activitypub_send_delete_activity' ); | |
| 332 | 344 | |
| 333 | 345 | $object_type = \get_option( 'activitypub_object_type', ACTIVITYPUB_DEFAULT_OBJECT_TYPE ); |
| 334 | 346 | if ( 'article' === $object_type ) { |
| 335 | 347 | \update_option( 'activitypub_object_type', 'wordpress-post-format' ); |
| @@ -348,9 +360,9 @@ | ||
| 348 | 360 | /** |
| 349 | 361 | * Rename DB fields. |
| 350 | 362 | */ |
| 351 | 363 | private static function migrate_from_2_6_0() { |
| 352 | - wp_cache_flush(); | |
| 364 | + \wp_cache_flush(); | |
| 353 | 365 | |
| 354 | 366 | self::update_usermeta_key( 'activitypub_user_description', 'activitypub_description' ); |
| 355 | 367 | |
| 356 | 368 | self::update_options_key( 'activitypub_blog_user_description', 'activitypub_blog_description' ); |
| @@ -364,9 +376,9 @@ | ||
| 364 | 376 | private static function migrate_to_4_0_0() { |
| 365 | 377 | $latest_post_id = 0; |
| 366 | 378 | |
| 367 | 379 | // Get the ID of the latest blog post and save it to the options table. |
| 368 | - $latest_post = get_posts( | |
| 380 | + $latest_post = \get_posts( | |
| 369 | 381 | array( |
| 370 | 382 | 'numberposts' => 1, |
| 371 | 383 | 'orderby' => 'ID', |
| 372 | 384 | 'order' => 'DESC', |
| @@ -477,9 +489,9 @@ | ||
| 477 | 489 | $followers = $wpdb->get_col( |
| 478 | 490 | $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s", Remote_Actors::POST_TYPE ) |
| 479 | 491 | ); |
| 480 | 492 | foreach ( $followers as $id ) { |
| 481 | - clean_post_cache( $id ); | |
| 493 | + \clean_post_cache( $id ); | |
| 482 | 494 | } |
| 483 | 495 | } |
| 484 | 496 | |
| 485 | 497 | /** |
| @@ -495,9 +507,9 @@ | ||
| 495 | 507 | global $wpdb; |
| 496 | 508 | |
| 497 | 509 | Comment::register_comment_types(); |
| 498 | 510 | $comment_types = Comment::get_comment_type_slugs(); |
| 499 | - $type_inclusion = "AND comment_type IN ('" . implode( "','", $comment_types ) . "')"; | |
| 511 | + $type_inclusion = "AND comment_type IN ('" . \implode( "','", $comment_types ) . "')"; | |
| 500 | 512 | |
| 501 | 513 | // Get and process this batch. |
| 502 | 514 | $post_ids = $wpdb->get_col( // phpcs:ignore WordPress.DB |
| 503 | 515 | $wpdb->prepare( |
| @@ -511,9 +523,9 @@ | ||
| 511 | 523 | foreach ( $post_ids as $post_id ) { |
| 512 | 524 | \wp_update_comment_count_now( $post_id ); |
| 513 | 525 | } |
| 514 | 526 | |
| 515 | - if ( count( $post_ids ) === $batch_size ) { | |
| 527 | + if ( \count( $post_ids ) === $batch_size ) { | |
| 516 | 528 | // Schedule next batch. |
| 517 | 529 | return array( $batch_size, $offset + $batch_size ); |
| 518 | 530 | } |
| 519 | 531 | } |
| @@ -555,9 +567,9 @@ | ||
| 555 | 567 | self::add_to_outbox( $post, 'Update', $post->post_author, $visibility ); |
| 556 | 568 | } |
| 557 | 569 | } |
| 558 | 570 | |
| 559 | - if ( count( $posts ) === $batch_size ) { | |
| 571 | + if ( \count( $posts ) === $batch_size ) { | |
| 560 | 572 | return array( |
| 561 | 573 | 'batch_size' => $batch_size, |
| 562 | 574 | 'offset' => $offset + $batch_size, |
| 563 | 575 | ); |
| @@ -592,9 +604,9 @@ | ||
| 592 | 604 | foreach ( $comments as $comment ) { |
| 593 | 605 | self::add_to_outbox( $comment, 'Create', $comment->user_id ); |
| 594 | 606 | } |
| 595 | 607 | |
| 596 | - if ( count( $comments ) === $batch_size ) { | |
| 608 | + if ( \count( $comments ) === $batch_size ) { | |
| 597 | 609 | return array( |
| 598 | 610 | 'batch_size' => $batch_size, |
| 599 | 611 | 'offset' => $offset + $batch_size, |
| 600 | 612 | ); |
| @@ -688,9 +700,9 @@ | ||
| 688 | 700 | ) |
| 689 | 701 | ); |
| 690 | 702 | } |
| 691 | 703 | |
| 692 | - if ( count( $comments ) === $batch_size ) { | |
| 704 | + if ( \count( $comments ) === $batch_size ) { | |
| 693 | 705 | return array( |
| 694 | 706 | 'batch_size' => $batch_size, |
| 695 | 707 | 'offset' => $offset + $batch_size, |
| 696 | 708 | ); |
| @@ -1022,9 +1034,9 @@ | ||
| 1022 | 1034 | $wpdb->delete( |
| 1023 | 1035 | $wpdb->postmeta, |
| 1024 | 1036 | array( |
| 1025 | 1037 | 'meta_key' => '_activitypub_following', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key |
| 1026 | - 'meta_value' => Actors::APPLICATION_USER_ID, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value | |
| 1038 | + 'meta_value' => -1, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value | |
| 1027 | 1039 | ) |
| 1028 | 1040 | ); |
| 1029 | 1041 | } |
| 1030 | 1042 | |
| @@ -1076,9 +1088,9 @@ | ||
| 1076 | 1088 | // phpcs:ignore WordPress.DB.DirectDatabaseQuery |
| 1077 | 1089 | $inbox_ids = $wpdb->get_col( |
| 1078 | 1090 | $wpdb->prepare( |
| 1079 | 1091 | "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s", |
| 1080 | - \Activitypub\Collection\Inbox::POST_TYPE | |
| 1092 | + Inbox::POST_TYPE | |
| 1081 | 1093 | ) |
| 1082 | 1094 | ); |
| 1083 | 1095 | |
| 1084 | 1096 | // Delete all inbox items and their metadata. |
| @@ -1087,8 +1099,121 @@ | ||
| 1087 | 1099 | } |
| 1088 | 1100 | } |
| 1089 | 1101 | |
| 1090 | 1102 | /** |
| 1103 | + * Migrate URLs from the legacy `activitypub_tombstone_urls` option into the | |
| 1104 | + * `ap_tombstone` custom post type. | |
| 1105 | + * | |
| 1106 | + * Chunked async migration. Locking and rescheduling is handled by | |
| 1107 | + * Scheduler::async_batch — the callback returns `array( 'batch_size' => N )` | |
| 1108 | + * to request another run, or `null` when the option is fully drained. | |
| 1109 | + * | |
| 1110 | + * Legacy entries are already-normalized strings (no scheme), so we bypass | |
| 1111 | + * URL validation and insert directly via wp_insert_post. | |
| 1112 | + * | |
| 1113 | + * @since 8.3.0 | |
| 1114 | + * | |
| 1115 | + * @param int $batch_size Optional. Number of URLs to process per call. Default 500. | |
| 1116 | + * @return array|null Args for the next run, or null when migration is complete. | |
| 1117 | + */ | |
| 1118 | + public static function migrate_tombstones_to_cpt( $batch_size = 500 ) { | |
| 1119 | + global $wpdb; | |
| 1120 | + | |
| 1121 | + $urls = \get_option( 'activitypub_tombstone_urls', null ); | |
| 1122 | + | |
| 1123 | + if ( null === $urls || ! \is_array( $urls ) || empty( $urls ) ) { | |
| 1124 | + \delete_option( 'activitypub_tombstone_urls' ); | |
| 1125 | + return null; | |
| 1126 | + } | |
| 1127 | + | |
| 1128 | + $chunk = \array_slice( $urls, 0, (int) $batch_size ); | |
| 1129 | + $remaining = \array_slice( $urls, (int) $batch_size ); | |
| 1130 | + $progressed = false; | |
| 1131 | + | |
| 1132 | + foreach ( $chunk as $normalized ) { | |
| 1133 | + if ( ! \is_string( $normalized ) || '' === $normalized ) { | |
| 1134 | + // Drop garbage entries — counts as progress. | |
| 1135 | + $progressed = true; | |
| 1136 | + continue; | |
| 1137 | + } | |
| 1138 | + | |
| 1139 | + $hash = \md5( $normalized ); | |
| 1140 | + | |
| 1141 | + /* | |
| 1142 | + * Light existence check. `get_page_by_path()` would hydrate a | |
| 1143 | + * full `WP_Post` per loop iteration; on a large registry that | |
| 1144 | + * adds up fast. We only need a boolean here. | |
| 1145 | + */ | |
| 1146 | + $exists = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery | |
| 1147 | + $wpdb->prepare( | |
| 1148 | + "SELECT 1 FROM {$wpdb->posts} WHERE post_type = %s AND post_name = %s LIMIT 1", | |
| 1149 | + Tombstone::POST_TYPE, | |
| 1150 | + $hash | |
| 1151 | + ) | |
| 1152 | + ); | |
| 1153 | + if ( $exists ) { | |
| 1154 | + $progressed = true; | |
| 1155 | + continue; | |
| 1156 | + } | |
| 1157 | + | |
| 1158 | + /* | |
| 1159 | + * `guid` is intentionally omitted: the legacy option only kept | |
| 1160 | + * the normalized (schemeless) form, so we can't reconstruct the | |
| 1161 | + * original URL. Storing the schemeless string would be mangled | |
| 1162 | + * by `esc_url()`. Leave WordPress to auto-generate the guid | |
| 1163 | + * — it's not used for lookups, only for debugging. | |
| 1164 | + */ | |
| 1165 | + $result = \wp_insert_post( | |
| 1166 | + array( | |
| 1167 | + 'post_type' => Tombstone::POST_TYPE, | |
| 1168 | + 'post_status' => 'publish', | |
| 1169 | + 'post_name' => $hash, | |
| 1170 | + 'post_author' => 0, | |
| 1171 | + ), | |
| 1172 | + true | |
| 1173 | + ); | |
| 1174 | + | |
| 1175 | + if ( \is_wp_error( $result ) || ! $result ) { | |
| 1176 | + /* | |
| 1177 | + * Keep failed inserts in the legacy option so the next batch | |
| 1178 | + * retries them. `Tombstone::exists_local()` still falls back | |
| 1179 | + * to the option, so the tombstone remains discoverable. | |
| 1180 | + */ | |
| 1181 | + $remaining[] = $normalized; | |
| 1182 | + } else { | |
| 1183 | + $progressed = true; | |
| 1184 | + } | |
| 1185 | + } | |
| 1186 | + | |
| 1187 | + if ( empty( $remaining ) ) { | |
| 1188 | + \delete_option( 'activitypub_tombstone_urls' ); | |
| 1189 | + return null; | |
| 1190 | + } | |
| 1191 | + | |
| 1192 | + /* | |
| 1193 | + * Disable autoload while we drain. The point of the migration is to | |
| 1194 | + * stop this option from contributing to `alloptions` pressure, so | |
| 1195 | + * flip the flag immediately rather than waiting for the option to | |
| 1196 | + * be fully empty before the relief kicks in. | |
| 1197 | + */ | |
| 1198 | + \update_option( 'activitypub_tombstone_urls', \array_values( $remaining ), false ); | |
| 1199 | + | |
| 1200 | + /* | |
| 1201 | + * If nothing in this batch was drained — every insert errored and | |
| 1202 | + * nothing was already migrated — halt the scheduler so we don't loop | |
| 1203 | + * forever on a persistent failure. The legacy option still backs | |
| 1204 | + * exists_local(), so the data isn't lost; an admin can re-trigger | |
| 1205 | + * the migration via `wp cron event run activitypub_tombstone_migrate` | |
| 1206 | + * after fixing the underlying cause. | |
| 1207 | + */ | |
| 1208 | + if ( ! $progressed ) { | |
| 1209 | + return null; | |
| 1210 | + } | |
| 1211 | + | |
| 1212 | + return array( 'batch_size' => (int) $batch_size ); | |
| 1213 | + } | |
| 1214 | + | |
| 1215 | + /** | |
| 1091 | 1216 | * Migrate avatar URLs from comment meta to remote actors in batches. |
| 1092 | 1217 | * |
| 1093 | 1218 | * This migration: |
| 1094 | 1219 | * 1. Finds all comments with ActivityPub protocol and avatar_url meta |
| @@ -1151,9 +1276,9 @@ | ||
| 1151 | 1276 | } |
| 1152 | 1277 | } |
| 1153 | 1278 | |
| 1154 | 1279 | // Return batch info if there are more comments to process. |
| 1155 | - if ( count( $comments ) === $batch_size ) { | |
| 1280 | + if ( \count( $comments ) === $batch_size ) { | |
| 1156 | 1281 | return array( |
| 1157 | 1282 | 'batch_size' => $batch_size, |
| 1158 | 1283 | ); |
| 1159 | 1284 | } |
| @@ -1201,9 +1326,9 @@ | ||
| 1201 | 1326 | } |
| 1202 | 1327 | } |
| 1203 | 1328 | |
| 1204 | 1329 | // Return batch info if there are more actors to process. |
| 1205 | - if ( count( $actors ) === $batch_size ) { | |
| 1330 | + if ( \count( $actors ) === $batch_size ) { | |
| 1206 | 1331 | return array( |
| 1207 | 1332 | 'batch_size' => $batch_size, |
| 1208 | 1333 | 'offset' => $offset + $batch_size, |
| 1209 | 1334 | ); |
| @@ -1209,6 +1334,64 @@ | ||
| 1209 | 1334 | ); |
| 1210 | 1335 | } |
| 1211 | 1336 | |
| 1212 | 1337 | return null; |
| 1338 | + } | |
| 1339 | + | |
| 1340 | + /** | |
| 1341 | + * Migrate the Application key pair option from the old name to the new name. | |
| 1342 | + * | |
| 1343 | + * Renames `activitypub_keypair_for_-1` to `activitypub_application_keypair`. | |
| 1344 | + * Older separate key options (activitypub_application_user_public_key / | |
| 1345 | + * activitypub_application_user_private_key) are migrated lazily on first read. | |
| 1346 | + * | |
| 1347 | + * @since 9.1.0 | |
| 1348 | + */ | |
| 1349 | + public static function migrate_application_keypair_option() { | |
| 1350 | + self::update_options_key( 'activitypub_keypair_for_-1', Application::KEYPAIR_OPTION_KEY ); | |
| 1351 | + | |
| 1352 | + // The raw rename bypasses the options API, so drop only the two stale option caches (plus the autoload bucket) instead of flushing everything. | |
| 1353 | + \wp_cache_delete( 'activitypub_keypair_for_-1', 'options' ); | |
| 1354 | + \wp_cache_delete( Application::KEYPAIR_OPTION_KEY, 'options' ); | |
| 1355 | + \wp_cache_delete( 'alloptions', 'options' ); | |
| 1356 | + | |
| 1357 | + /* | |
| 1358 | + * If an early Application::get_keypair() read already created the destination | |
| 1359 | + * option, the rename above is a no-op blocked by the unique `option_name`, | |
| 1360 | + * leaving the legacy row behind. Drop it once the destination is in place. | |
| 1361 | + */ | |
| 1362 | + if ( false !== \get_option( Application::KEYPAIR_OPTION_KEY, false ) ) { | |
| 1363 | + \delete_option( 'activitypub_keypair_for_-1' ); | |
| 1364 | + } | |
| 1365 | + } | |
| 1366 | + | |
| 1367 | + /** | |
| 1368 | + * Delete outbox items that belonged to the Application actor. | |
| 1369 | + * | |
| 1370 | + * The Application used to queue Reject activities through the Outbox as user | |
| 1371 | + * ID -1. It no longer dispatches activities, so any pending items are | |
| 1372 | + * undeliverable and are removed. | |
| 1373 | + * | |
| 1374 | + * @since 9.1.0 | |
| 1375 | + */ | |
| 1376 | + public static function delete_application_outbox_items() { | |
| 1377 | + $items = \get_posts( | |
| 1378 | + array( | |
| 1379 | + 'post_type' => Outbox::POST_TYPE, | |
| 1380 | + 'post_status' => 'any', | |
| 1381 | + 'nopaging' => true, | |
| 1382 | + 'fields' => 'ids', | |
| 1383 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query | |
| 1384 | + 'meta_query' => array( | |
| 1385 | + array( | |
| 1386 | + 'key' => '_activitypub_activity_actor', | |
| 1387 | + 'value' => 'application', | |
| 1388 | + ), | |
| 1389 | + ), | |
| 1390 | + ) | |
| 1391 | + ); | |
| 1392 | + | |
| 1393 | + foreach ( $items as $item_id ) { | |
| 1394 | + \wp_delete_post( $item_id, true ); | |
| 1395 | + } | |
| 1213 | 1396 | } |
| 1214 | 1397 | } |