PluginProbe
ActivityPub / 7.8.4
ActivityPub v7.8.4
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 +15 -189 9.0.2 → 7.8.4 View file →
@@ -31,11 +31,8 @@
31 31 Scheduler::register_async_batch_callback( 'activitypub_update_comment_counts', array( self::class, 'update_comment_counts' ) );
32 32 Scheduler::register_async_batch_callback( 'activitypub_create_post_outbox_items', array( self::class, 'create_post_outbox_items' ) );
33 33 Scheduler::register_async_batch_callback( 'activitypub_create_comment_outbox_items', array( self::class, 'create_comment_outbox_items' ) );
34 34 Scheduler::register_async_batch_callback( 'activitypub_migrate_avatar_to_remote_actors', array( self::class, 'migrate_avatar_to_remote_actors' ) );
35 - Scheduler::register_async_batch_callback( 'activitypub_migrate_actor_emoji', array( self::class, 'migrate_actor_emoji' ) );
36 - Scheduler::register_async_batch_callback( 'activitypub_backfill_statistics', array( Statistics::class, 'backfill_historical_stats' ) );
37 - Scheduler::register_async_batch_callback( 'activitypub_tombstone_migrate', array( self::class, 'migrate_tombstones_to_cpt' ) );
38 35 }
39 36
40 37 /**
41 38 * The current version of the database structure.
@@ -160,16 +157,21 @@
160 157 }
161 158 if ( \version_compare( $version_from_db, '4.7.2', '<' ) ) {
162 159 self::migrate_to_4_7_2();
163 160 }
161 + if ( \version_compare( $version_from_db, '4.7.3', '<' ) ) {
162 + add_action( 'init', 'flush_rewrite_rules', 20 );
163 + }
164 164 if ( \version_compare( $version_from_db, '5.0.0', '<' ) ) {
165 165 Scheduler::register_schedules();
166 166 \wp_schedule_single_event( \time(), 'activitypub_create_post_outbox_items' );
167 167 \wp_schedule_single_event( \time() + 15, 'activitypub_create_comment_outbox_items' );
168 + add_action( 'init', 'flush_rewrite_rules', 20 );
168 169 }
169 170 if ( \version_compare( $version_from_db, '5.4.0', '<' ) ) {
170 171 \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_slashing' ) );
171 172 \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_comment_author_emails' ) );
173 + \add_action( 'init', 'flush_rewrite_rules', 20 );
172 174 }
173 175 if ( \version_compare( $version_from_db, '5.7.0', '<' ) ) {
174 176 self::delete_mastodon_api_orphaned_extra_fields();
175 177 }
@@ -175,16 +177,19 @@
175 177 }
176 178 if ( \version_compare( $version_from_db, '5.8.0', '<' ) ) {
177 179 self::update_notification_options();
178 180 }
181 +
179 182 if ( \version_compare( $version_from_db, '6.0.0', '<' ) ) {
180 183 self::migrate_followers_to_ap_actor_cpt();
181 184 \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_storage' ) );
182 185 }
186 +
183 187 if ( \version_compare( $version_from_db, '6.0.1', '<' ) ) {
184 188 self::migrate_followers_to_ap_actor_cpt();
185 189 \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_storage' ) );
186 190 }
191 +
187 192 if ( \version_compare( $version_from_db, '7.0.0', '<' ) ) {
188 193 wp_unschedule_hook( 'activitypub_update_followers' );
189 194 wp_unschedule_hook( 'activitypub_cleanup_followers' );
190 195
@@ -195,39 +200,22 @@
195 200 if ( ! \wp_next_scheduled( 'activitypub_cleanup_remote_actors' ) ) {
196 201 \wp_schedule_event( time(), 'daily', 'activitypub_cleanup_remote_actors' );
197 202 }
198 203 }
204 +
199 205 if ( \version_compare( $version_from_db, '7.3.0', '<' ) ) {
200 206 self::remove_pending_application_user_follow_requests();
201 207 }
208 +
202 209 if ( \version_compare( $version_from_db, '7.5.0', '<' ) ) {
203 210 self::sync_jetpack_following_meta();
204 211 }
212 +
205 213 if ( \version_compare( $version_from_db, '7.6.0', '<' ) ) {
206 214 self::clean_up_inbox();
207 215 \wp_schedule_single_event( \time(), 'activitypub_migrate_avatar_to_remote_actors' );
208 216 }
209 - if ( \version_compare( $version_from_db, '7.9.0', '<' ) ) {
210 - \wp_schedule_single_event( \time(), 'activitypub_migrate_actor_emoji' );
211 - }
212 - if ( \version_compare( $version_from_db, '8.1.0', '<' ) && ! \wp_next_scheduled( 'activitypub_backfill_statistics' ) ) {
213 - // Backfill historical statistics data (delay + jitter to avoid load spikes on hosts running many sites).
214 - \wp_schedule_single_event( \time() + HOUR_IN_SECONDS + \wp_rand( 0, 6 * HOUR_IN_SECONDS ), 'activitypub_backfill_statistics' );
215 - }
216 - if ( \version_compare( $version_from_db, '8.3.0', '<' ) ) {
217 - if ( ! \wp_next_scheduled( 'activitypub_tombstone_migrate' ) ) {
218 - \wp_schedule_single_event( \time() + MINUTE_IN_SECONDS, 'activitypub_tombstone_migrate' );
219 - }
220 - }
221 217
222 - /*
223 - * Defer the flush to late in the `init` cycle (priority 20). Migration::init
224 - * runs at priority 1, which is earlier than most plugins register their
225 - * rewrite rules. Flushing synchronously here would persist a truncated
226 - * ruleset that omits third-party rules added on `init` at priority 10.
227 - */
228 - \add_action( 'init', array( Activitypub::class, 'flush_rewrite_rules' ), 20 );
229 -
230 218 // Ensure all required cron schedules are registered.
231 219 Scheduler::register_schedules();
232 220
233 221 /*
@@ -305,8 +293,10 @@
305 293 Followers::add( $user_id, $actor );
306 294 }
307 295 }
308 296 }
297 +
298 + Activitypub::flush_rewrite_rules();
309 299 }
310 300
311 301 /**
312 302 * Clear the cache after updating to 1.3.0.
@@ -541,9 +531,9 @@
541 531 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
542 532 'meta_query' => array(
543 533 array(
544 534 'key' => 'activitypub_status',
545 - 'value' => ACTIVITYPUB_OBJECT_STATE_FEDERATED,
535 + 'value' => 'federated',
546 536 ),
547 537 ),
548 538 )
549 539 );
@@ -588,9 +578,9 @@
588 578 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
589 579 'meta_query' => array(
590 580 array(
591 581 'key' => 'activitypub_status',
592 - 'value' => ACTIVITYPUB_OBJECT_STATE_FEDERATED,
582 + 'value' => 'federated',
593 583 ),
594 584 ),
595 585 )
596 586 );
@@ -1093,121 +1083,8 @@
1093 1083 }
1094 1084 }
1095 1085
1096 1086 /**
1097 - * Migrate URLs from the legacy `activitypub_tombstone_urls` option into the
1098 - * `ap_tombstone` custom post type.
1099 - *
1100 - * Chunked async migration. Locking and rescheduling is handled by
1101 - * Scheduler::async_batch — the callback returns `array( 'batch_size' => N )`
1102 - * to request another run, or `null` when the option is fully drained.
1103 - *
1104 - * Legacy entries are already-normalized strings (no scheme), so we bypass
1105 - * URL validation and insert directly via wp_insert_post.
1106 - *
1107 - * @since 8.3.0
1108 - *
1109 - * @param int $batch_size Optional. Number of URLs to process per call. Default 500.
1110 - * @return array|null Args for the next run, or null when migration is complete.
1111 - */
1112 - public static function migrate_tombstones_to_cpt( $batch_size = 500 ) {
1113 - global $wpdb;
1114 -
1115 - $urls = \get_option( 'activitypub_tombstone_urls', null );
1116 -
1117 - if ( null === $urls || ! \is_array( $urls ) || empty( $urls ) ) {
1118 - \delete_option( 'activitypub_tombstone_urls' );
1119 - return null;
1120 - }
1121 -
1122 - $chunk = \array_slice( $urls, 0, (int) $batch_size );
1123 - $remaining = \array_slice( $urls, (int) $batch_size );
1124 - $progressed = false;
1125 -
1126 - foreach ( $chunk as $normalized ) {
1127 - if ( ! \is_string( $normalized ) || '' === $normalized ) {
1128 - // Drop garbage entries — counts as progress.
1129 - $progressed = true;
1130 - continue;
1131 - }
1132 -
1133 - $hash = \md5( $normalized );
1134 -
1135 - /*
1136 - * Light existence check. `get_page_by_path()` would hydrate a
1137 - * full `WP_Post` per loop iteration; on a large registry that
1138 - * adds up fast. We only need a boolean here.
1139 - */
1140 - $exists = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1141 - $wpdb->prepare(
1142 - "SELECT 1 FROM {$wpdb->posts} WHERE post_type = %s AND post_name = %s LIMIT 1",
1143 - Tombstone::POST_TYPE,
1144 - $hash
1145 - )
1146 - );
1147 - if ( $exists ) {
1148 - $progressed = true;
1149 - continue;
1150 - }
1151 -
1152 - /*
1153 - * `guid` is intentionally omitted: the legacy option only kept
1154 - * the normalized (schemeless) form, so we can't reconstruct the
1155 - * original URL. Storing the schemeless string would be mangled
1156 - * by `esc_url()`. Leave WordPress to auto-generate the guid
1157 - * — it's not used for lookups, only for debugging.
1158 - */
1159 - $result = \wp_insert_post(
1160 - array(
1161 - 'post_type' => Tombstone::POST_TYPE,
1162 - 'post_status' => 'publish',
1163 - 'post_name' => $hash,
1164 - 'post_author' => 0,
1165 - ),
1166 - true
1167 - );
1168 -
1169 - if ( \is_wp_error( $result ) || ! $result ) {
1170 - /*
1171 - * Keep failed inserts in the legacy option so the next batch
1172 - * retries them. `Tombstone::exists_local()` still falls back
1173 - * to the option, so the tombstone remains discoverable.
1174 - */
1175 - $remaining[] = $normalized;
1176 - } else {
1177 - $progressed = true;
1178 - }
1179 - }
1180 -
1181 - if ( empty( $remaining ) ) {
1182 - \delete_option( 'activitypub_tombstone_urls' );
1183 - return null;
1184 - }
1185 -
1186 - /*
1187 - * Disable autoload while we drain. The point of the migration is to
1188 - * stop this option from contributing to `alloptions` pressure, so
1189 - * flip the flag immediately rather than waiting for the option to
1190 - * be fully empty before the relief kicks in.
1191 - */
1192 - \update_option( 'activitypub_tombstone_urls', \array_values( $remaining ), false );
1193 -
1194 - /*
1195 - * If nothing in this batch was drained — every insert errored and
1196 - * nothing was already migrated — halt the scheduler so we don't loop
1197 - * forever on a persistent failure. The legacy option still backs
1198 - * exists_local(), so the data isn't lost; an admin can re-trigger
1199 - * the migration via `wp cron event run activitypub_tombstone_migrate`
1200 - * after fixing the underlying cause.
1201 - */
1202 - if ( ! $progressed ) {
1203 - return null;
1204 - }
1205 -
1206 - return array( 'batch_size' => (int) $batch_size );
1207 - }
1208 -
1209 - /**
1210 1087 * Migrate avatar URLs from comment meta to remote actors in batches.
1211 1088 *
1212 1089 * This migration:
1213 1090 * 1. Finds all comments with ActivityPub protocol and avatar_url meta
@@ -1273,59 +1150,8 @@
1273 1150 // Return batch info if there are more comments to process.
1274 1151 if ( count( $comments ) === $batch_size ) {
1275 1152 return array(
1276 1153 'batch_size' => $batch_size,
1277 - );
1278 - }
1279 -
1280 - return null;
1281 - }
1282 -
1283 - /**
1284 - * Migrate emoji data from stored actor JSON to post meta.
1285 - *
1286 - * This migration:
1287 - * 1. Finds all remote actor posts without _activitypub_emoji meta
1288 - * 2. Extracts emoji from stored JSON in post_content
1289 - * 3. Stores as _activitypub_emoji post meta
1290 - *
1291 - * @param int $batch_size Optional. Number of actors to process per batch. Default 50.
1292 - * @param int $offset Optional. Offset for pagination. Default 0.
1293 - * @return array|null Array with batch size if there are more actors to process, null otherwise.
1294 - */
1295 - public static function migrate_actor_emoji( $batch_size = 50, $offset = 0 ) {
1296 - $actors = \get_posts(
1297 - array(
1298 - 'post_type' => Remote_Actors::POST_TYPE,
1299 - 'posts_per_page' => $batch_size,
1300 - 'offset' => $offset,
1301 - 'post_status' => 'any',
1302 - 'orderby' => 'ID',
1303 - 'order' => 'ASC',
1304 - )
1305 - );
1306 -
1307 - foreach ( $actors as $actor_post ) {
1308 - if ( empty( $actor_post->post_content ) ) {
1309 - continue;
1310 - }
1311 -
1312 - $actor_data = \json_decode( $actor_post->post_content, true );
1313 - if ( ! $actor_data ) {
1314 - continue;
1315 - }
1316 -
1317 - $emoji_meta = Emoji::prepare_actor_meta( $actor_data );
1318 - if ( ! empty( $emoji_meta['_activitypub_emoji'] ) ) {
1319 - \update_post_meta( $actor_post->ID, '_activitypub_emoji', $emoji_meta['_activitypub_emoji'] );
1320 - }
1321 - }
1322 -
1323 - // Return batch info if there are more actors to process.
1324 - if ( count( $actors ) === $batch_size ) {
1325 - return array(
1326 - 'batch_size' => $batch_size,
1327 - 'offset' => $offset + $batch_size,
1328 1154 );
1329 1155 }
1330 1156
1331 1157 return null;