| @@ -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; |