PluginProbe
ActivityPub / 8.0.2
ActivityPub v8.0.2
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
← All changes | includes/class-migration.php +42 -220 9.2.08.0.2 View file →
@@ -10,9 +10,8 @@
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;
15 14 use Activitypub\Collection\Outbox;
16 15 use Activitypub\Collection\Remote_Actors;
17 16 use Activitypub\Transformer\Factory;
18 17
@@ -33,10 +32,8 @@
33 32 Scheduler::register_async_batch_callback( 'activitypub_create_post_outbox_items', array( self::class, 'create_post_outbox_items' ) );
34 33 Scheduler::register_async_batch_callback( 'activitypub_create_comment_outbox_items', array( self::class, 'create_comment_outbox_items' ) );
35 34 Scheduler::register_async_batch_callback( 'activitypub_migrate_avatar_to_remote_actors', array( self::class, 'migrate_avatar_to_remote_actors' ) );
36 35 Scheduler::register_async_batch_callback( 'activitypub_migrate_actor_emoji', array( self::class, 'migrate_actor_emoji' ) );
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' ) );
39 36 }
40 37
41 38 /**
42 39 * The current version of the database structure.
@@ -43,9 +40,9 @@
43 40 *
44 41 * @return string The current version.
45 42 */
46 43 public static function get_version() {
47 - return \get_option( 'activitypub_db_version', 0 );
44 + return get_option( 'activitypub_db_version', 0 );
48 45 }
49 46
50 47 /**
51 48 * Locks the database migration process to prevent simultaneous migrations.
@@ -161,16 +158,21 @@
161 158 }
162 159 if ( \version_compare( $version_from_db, '4.7.2', '<' ) ) {
163 160 self::migrate_to_4_7_2();
164 161 }
162 + if ( \version_compare( $version_from_db, '4.7.3', '<' ) ) {
163 + add_action( 'init', 'flush_rewrite_rules', 20 );
164 + }
165 165 if ( \version_compare( $version_from_db, '5.0.0', '<' ) ) {
166 166 Scheduler::register_schedules();
167 167 \wp_schedule_single_event( \time(), 'activitypub_create_post_outbox_items' );
168 168 \wp_schedule_single_event( \time() + 15, 'activitypub_create_comment_outbox_items' );
169 + add_action( 'init', 'flush_rewrite_rules', 20 );
169 170 }
170 171 if ( \version_compare( $version_from_db, '5.4.0', '<' ) ) {
171 172 \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_slashing' ) );
172 173 \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_comment_author_emails' ) );
174 + \add_action( 'init', 'flush_rewrite_rules', 20 );
173 175 }
174 176 if ( \version_compare( $version_from_db, '5.7.0', '<' ) ) {
175 177 self::delete_mastodon_api_orphaned_extra_fields();
176 178 }
@@ -176,64 +178,53 @@
176 178 }
177 179 if ( \version_compare( $version_from_db, '5.8.0', '<' ) ) {
178 180 self::update_notification_options();
179 181 }
182 +
180 183 if ( \version_compare( $version_from_db, '6.0.0', '<' ) ) {
181 184 self::migrate_followers_to_ap_actor_cpt();
182 185 \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_storage' ) );
183 186 }
187 +
184 188 if ( \version_compare( $version_from_db, '6.0.1', '<' ) ) {
185 189 self::migrate_followers_to_ap_actor_cpt();
186 190 \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_storage' ) );
187 191 }
192 +
188 193 if ( \version_compare( $version_from_db, '7.0.0', '<' ) ) {
189 - \wp_unschedule_hook( 'activitypub_update_followers' );
190 - \wp_unschedule_hook( 'activitypub_cleanup_followers' );
194 + wp_unschedule_hook( 'activitypub_update_followers' );
195 + wp_unschedule_hook( 'activitypub_cleanup_followers' );
191 196
192 197 if ( ! \wp_next_scheduled( 'activitypub_update_remote_actors' ) ) {
193 - \wp_schedule_event( \time(), 'hourly', 'activitypub_update_remote_actors' );
198 + \wp_schedule_event( time(), 'hourly', 'activitypub_update_remote_actors' );
194 199 }
195 200
196 201 if ( ! \wp_next_scheduled( 'activitypub_cleanup_remote_actors' ) ) {
197 - \wp_schedule_event( \time(), 'daily', 'activitypub_cleanup_remote_actors' );
202 + \wp_schedule_event( time(), 'daily', 'activitypub_cleanup_remote_actors' );
198 203 }
199 204 }
205 +
200 206 if ( \version_compare( $version_from_db, '7.3.0', '<' ) ) {
201 207 self::remove_pending_application_user_follow_requests();
202 208 }
209 +
203 210 if ( \version_compare( $version_from_db, '7.5.0', '<' ) ) {
204 211 self::sync_jetpack_following_meta();
205 212 }
213 +
206 214 if ( \version_compare( $version_from_db, '7.6.0', '<' ) ) {
207 215 self::clean_up_inbox();
208 216 \wp_schedule_single_event( \time(), 'activitypub_migrate_avatar_to_remote_actors' );
209 217 }
218 +
210 219 if ( \version_compare( $version_from_db, '7.9.0', '<' ) ) {
211 220 \wp_schedule_single_event( \time(), 'activitypub_migrate_actor_emoji' );
212 221 }
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' );
216 - }
217 222
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 - }
223 + if ( \version_compare( $version_from_db, '8.0.0', '<' ) ) {
224 + Activitypub::flush_rewrite_rules();
222 225 }
223 - if ( \version_compare( $version_from_db, '9.1.0', '<' ) ) {
224 - self::migrate_application_keypair_option();
225 - self::delete_application_outbox_items();
226 - }
227 226
228 - /*
229 - * Defer the flush to late in the `init` cycle (priority 20). Migration::init
230 - * runs at priority 1, which is earlier than most plugins register their
231 - * rewrite rules. Flushing synchronously here would persist a truncated
232 - * ruleset that omits third-party rules added on `init` at priority 10.
233 - */
234 - \add_action( 'init', array( Activitypub::class, 'flush_rewrite_rules' ), 20 );
235 -
236 227 // Ensure all required cron schedules are registered.
237 228 Scheduler::register_schedules();
238 229
239 230 /*
@@ -302,10 +293,10 @@
302 293 * Updates the DB-schema of the followers-list.
303 294 */
304 295 public static function migrate_from_0_17() {
305 296 // Migrate followers.
306 - foreach ( \get_users( array( 'fields' => 'ID' ) ) as $user_id ) {
307 - $followers = \get_user_meta( $user_id, 'activitypub_followers', true );
297 + foreach ( get_users( array( 'fields' => 'ID' ) ) as $user_id ) {
298 + $followers = get_user_meta( $user_id, 'activitypub_followers', true );
308 299
309 300 if ( $followers ) {
310 301 foreach ( $followers as $actor ) {
311 302 Followers::add( $user_id, $actor );
@@ -311,8 +302,10 @@
311 302 Followers::add( $user_id, $actor );
312 303 }
313 304 }
314 305 }
306 +
307 + Activitypub::flush_rewrite_rules();
315 308 }
316 309
317 310 /**
318 311 * Clear the cache after updating to 1.3.0.
@@ -325,9 +318,9 @@
325 318 )
326 319 );
327 320
328 321 foreach ( $user_ids as $user_id ) {
329 - \wp_cache_delete( \sprintf( Followers::CACHE_KEY_INBOXES, $user_id ), 'activitypub' );
322 + wp_cache_delete( sprintf( Followers::CACHE_KEY_INBOXES, $user_id ), 'activitypub' );
330 323 }
331 324 }
332 325
333 326 /**
@@ -333,15 +326,15 @@
333 326 /**
334 327 * Unschedule Hooks after updating to 2.0.0.
335 328 */
336 329 private static function migrate_from_2_0_0() {
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' );
330 + wp_clear_scheduled_hook( 'activitypub_send_post_activity' );
331 + wp_clear_scheduled_hook( 'activitypub_send_update_activity' );
332 + wp_clear_scheduled_hook( 'activitypub_send_delete_activity' );
340 333
341 - \wp_unschedule_hook( 'activitypub_send_post_activity' );
342 - \wp_unschedule_hook( 'activitypub_send_update_activity' );
343 - \wp_unschedule_hook( 'activitypub_send_delete_activity' );
334 + wp_unschedule_hook( 'activitypub_send_post_activity' );
335 + wp_unschedule_hook( 'activitypub_send_update_activity' );
336 + wp_unschedule_hook( 'activitypub_send_delete_activity' );
344 337
345 338 $object_type = \get_option( 'activitypub_object_type', ACTIVITYPUB_DEFAULT_OBJECT_TYPE );
346 339 if ( 'article' === $object_type ) {
347 340 \update_option( 'activitypub_object_type', 'wordpress-post-format' );
@@ -360,9 +353,9 @@
360 353 /**
361 354 * Rename DB fields.
362 355 */
363 356 private static function migrate_from_2_6_0() {
364 - \wp_cache_flush();
357 + wp_cache_flush();
365 358
366 359 self::update_usermeta_key( 'activitypub_user_description', 'activitypub_description' );
367 360
368 361 self::update_options_key( 'activitypub_blog_user_description', 'activitypub_blog_description' );
@@ -376,9 +369,9 @@
376 369 private static function migrate_to_4_0_0() {
377 370 $latest_post_id = 0;
378 371
379 372 // Get the ID of the latest blog post and save it to the options table.
380 - $latest_post = \get_posts(
373 + $latest_post = get_posts(
381 374 array(
382 375 'numberposts' => 1,
383 376 'orderby' => 'ID',
384 377 'order' => 'DESC',
@@ -489,9 +482,9 @@
489 482 $followers = $wpdb->get_col(
490 483 $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s", Remote_Actors::POST_TYPE )
491 484 );
492 485 foreach ( $followers as $id ) {
493 - \clean_post_cache( $id );
486 + clean_post_cache( $id );
494 487 }
495 488 }
496 489
497 490 /**
@@ -507,9 +500,9 @@
507 500 global $wpdb;
508 501
509 502 Comment::register_comment_types();
510 503 $comment_types = Comment::get_comment_type_slugs();
511 - $type_inclusion = "AND comment_type IN ('" . \implode( "','", $comment_types ) . "')";
504 + $type_inclusion = "AND comment_type IN ('" . implode( "','", $comment_types ) . "')";
512 505
513 506 // Get and process this batch.
514 507 $post_ids = $wpdb->get_col( // phpcs:ignore WordPress.DB
515 508 $wpdb->prepare(
@@ -523,9 +516,9 @@
523 516 foreach ( $post_ids as $post_id ) {
524 517 \wp_update_comment_count_now( $post_id );
525 518 }
526 519
527 - if ( \count( $post_ids ) === $batch_size ) {
520 + if ( count( $post_ids ) === $batch_size ) {
528 521 // Schedule next batch.
529 522 return array( $batch_size, $offset + $batch_size );
530 523 }
531 524 }
@@ -567,9 +560,9 @@
567 560 self::add_to_outbox( $post, 'Update', $post->post_author, $visibility );
568 561 }
569 562 }
570 563
571 - if ( \count( $posts ) === $batch_size ) {
564 + if ( count( $posts ) === $batch_size ) {
572 565 return array(
573 566 'batch_size' => $batch_size,
574 567 'offset' => $offset + $batch_size,
575 568 );
@@ -604,9 +597,9 @@
604 597 foreach ( $comments as $comment ) {
605 598 self::add_to_outbox( $comment, 'Create', $comment->user_id );
606 599 }
607 600
608 - if ( \count( $comments ) === $batch_size ) {
601 + if ( count( $comments ) === $batch_size ) {
609 602 return array(
610 603 'batch_size' => $batch_size,
611 604 'offset' => $offset + $batch_size,
612 605 );
@@ -700,9 +693,9 @@
700 693 )
701 694 );
702 695 }
703 696
704 - if ( \count( $comments ) === $batch_size ) {
697 + if ( count( $comments ) === $batch_size ) {
705 698 return array(
706 699 'batch_size' => $batch_size,
707 700 'offset' => $offset + $batch_size,
708 701 );
@@ -1034,9 +1027,9 @@
1034 1027 $wpdb->delete(
1035 1028 $wpdb->postmeta,
1036 1029 array(
1037 1030 'meta_key' => '_activitypub_following', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
1038 - 'meta_value' => -1, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
1031 + 'meta_value' => Actors::APPLICATION_USER_ID, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
1039 1032 )
1040 1033 );
1041 1034 }
1042 1035
@@ -1088,9 +1081,9 @@
1088 1081 // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1089 1082 $inbox_ids = $wpdb->get_col(
1090 1083 $wpdb->prepare(
1091 1084 "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s",
1092 - Inbox::POST_TYPE
1085 + \Activitypub\Collection\Inbox::POST_TYPE
1093 1086 )
1094 1087 );
1095 1088
1096 1089 // Delete all inbox items and their metadata.
@@ -1099,121 +1092,8 @@
1099 1092 }
1100 1093 }
1101 1094
1102 1095 /**
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 - /**
1216 1096 * Migrate avatar URLs from comment meta to remote actors in batches.
1217 1097 *
1218 1098 * This migration:
1219 1099 * 1. Finds all comments with ActivityPub protocol and avatar_url meta
@@ -1276,9 +1156,9 @@
1276 1156 }
1277 1157 }
1278 1158
1279 1159 // Return batch info if there are more comments to process.
1280 - if ( \count( $comments ) === $batch_size ) {
1160 + if ( count( $comments ) === $batch_size ) {
1281 1161 return array(
1282 1162 'batch_size' => $batch_size,
1283 1163 );
1284 1164 }
@@ -1326,9 +1206,9 @@
1326 1206 }
1327 1207 }
1328 1208
1329 1209 // Return batch info if there are more actors to process.
1330 - if ( \count( $actors ) === $batch_size ) {
1210 + if ( count( $actors ) === $batch_size ) {
1331 1211 return array(
1332 1212 'batch_size' => $batch_size,
1333 1213 'offset' => $offset + $batch_size,
1334 1214 );
@@ -1334,64 +1214,6 @@
1334 1214 );
1335 1215 }
1336 1216
1337 1217 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 - }
1396 1218 }
1397 1219 }