| @@ -32,10 +32,8 @@ | ||
| 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 | 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 | 36 | } |
| 39 | 37 | |
| 40 | 38 | /** |
| 41 | 39 | * The current version of the database structure. |
| @@ -160,16 +158,21 @@ | ||
| 160 | 158 | } |
| 161 | 159 | if ( \version_compare( $version_from_db, '4.7.2', '<' ) ) { |
| 162 | 160 | self::migrate_to_4_7_2(); |
| 163 | 161 | } |
| 162 | + if ( \version_compare( $version_from_db, '4.7.3', '<' ) ) { | |
| 163 | + add_action( 'init', 'flush_rewrite_rules', 20 ); | |
| 164 | + } | |
| 164 | 165 | if ( \version_compare( $version_from_db, '5.0.0', '<' ) ) { |
| 165 | 166 | Scheduler::register_schedules(); |
| 166 | 167 | \wp_schedule_single_event( \time(), 'activitypub_create_post_outbox_items' ); |
| 167 | 168 | \wp_schedule_single_event( \time() + 15, 'activitypub_create_comment_outbox_items' ); |
| 169 | + add_action( 'init', 'flush_rewrite_rules', 20 ); | |
| 168 | 170 | } |
| 169 | 171 | if ( \version_compare( $version_from_db, '5.4.0', '<' ) ) { |
| 170 | 172 | \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_slashing' ) ); |
| 171 | 173 | \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_comment_author_emails' ) ); |
| 174 | + \add_action( 'init', 'flush_rewrite_rules', 20 ); | |
| 172 | 175 | } |
| 173 | 176 | if ( \version_compare( $version_from_db, '5.7.0', '<' ) ) { |
| 174 | 177 | self::delete_mastodon_api_orphaned_extra_fields(); |
| 175 | 178 | } |
| @@ -175,16 +178,19 @@ | ||
| 175 | 178 | } |
| 176 | 179 | if ( \version_compare( $version_from_db, '5.8.0', '<' ) ) { |
| 177 | 180 | self::update_notification_options(); |
| 178 | 181 | } |
| 182 | + | |
| 179 | 183 | if ( \version_compare( $version_from_db, '6.0.0', '<' ) ) { |
| 180 | 184 | self::migrate_followers_to_ap_actor_cpt(); |
| 181 | 185 | \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_storage' ) ); |
| 182 | 186 | } |
| 187 | + | |
| 183 | 188 | if ( \version_compare( $version_from_db, '6.0.1', '<' ) ) { |
| 184 | 189 | self::migrate_followers_to_ap_actor_cpt(); |
| 185 | 190 | \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_storage' ) ); |
| 186 | 191 | } |
| 192 | + | |
| 187 | 193 | if ( \version_compare( $version_from_db, '7.0.0', '<' ) ) { |
| 188 | 194 | wp_unschedule_hook( 'activitypub_update_followers' ); |
| 189 | 195 | wp_unschedule_hook( 'activitypub_cleanup_followers' ); |
| 190 | 196 | |
| @@ -195,39 +201,30 @@ | ||
| 195 | 201 | if ( ! \wp_next_scheduled( 'activitypub_cleanup_remote_actors' ) ) { |
| 196 | 202 | \wp_schedule_event( time(), 'daily', 'activitypub_cleanup_remote_actors' ); |
| 197 | 203 | } |
| 198 | 204 | } |
| 205 | + | |
| 199 | 206 | if ( \version_compare( $version_from_db, '7.3.0', '<' ) ) { |
| 200 | 207 | self::remove_pending_application_user_follow_requests(); |
| 201 | 208 | } |
| 209 | + | |
| 202 | 210 | if ( \version_compare( $version_from_db, '7.5.0', '<' ) ) { |
| 203 | 211 | self::sync_jetpack_following_meta(); |
| 204 | 212 | } |
| 213 | + | |
| 205 | 214 | if ( \version_compare( $version_from_db, '7.6.0', '<' ) ) { |
| 206 | 215 | self::clean_up_inbox(); |
| 207 | 216 | \wp_schedule_single_event( \time(), 'activitypub_migrate_avatar_to_remote_actors' ); |
| 208 | 217 | } |
| 218 | + | |
| 209 | 219 | if ( \version_compare( $version_from_db, '7.9.0', '<' ) ) { |
| 210 | 220 | \wp_schedule_single_event( \time(), 'activitypub_migrate_actor_emoji' ); |
| 211 | 221 | } |
| 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' ); | |
| 222 | + | |
| 223 | + if ( \version_compare( $version_from_db, '8.0.0', '<' ) ) { | |
| 224 | + Activitypub::flush_rewrite_rules(); | |
| 215 | 225 | } |
| 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 | 226 | |
| 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 | 227 | // Ensure all required cron schedules are registered. |
| 231 | 228 | Scheduler::register_schedules(); |
| 232 | 229 | |
| 233 | 230 | /* |
| @@ -305,8 +302,10 @@ | ||
| 305 | 302 | Followers::add( $user_id, $actor ); |
| 306 | 303 | } |
| 307 | 304 | } |
| 308 | 305 | } |
| 306 | + | |
| 307 | + Activitypub::flush_rewrite_rules(); | |
| 309 | 308 | } |
| 310 | 309 | |
| 311 | 310 | /** |
| 312 | 311 | * Clear the cache after updating to 1.3.0. |
| @@ -1090,121 +1089,8 @@ | ||
| 1090 | 1089 | // Delete all inbox items and their metadata. |
| 1091 | 1090 | foreach ( $inbox_ids as $post_id ) { |
| 1092 | 1091 | \wp_delete_post( $post_id, true ); |
| 1093 | 1092 | } |
| 1094 | - } | |
| 1095 | - | |
| 1096 | - /** | |
| 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 | 1093 | } |
| 1208 | 1094 | |
| 1209 | 1095 | /** |
| 1210 | 1096 | * Migrate avatar URLs from comment meta to remote actors in batches. |