PluginProbe
ActivityPub / 5.7.0
ActivityPub v5.7.0
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 +120 -408 8.2.15.7.0 View file →
@@ -9,11 +9,9 @@
9 9
10 10 use Activitypub\Collection\Actors;
11 11 use Activitypub\Collection\Extra_Fields;
12 12 use Activitypub\Collection\Followers;
13 -use Activitypub\Collection\Following;
14 13 use Activitypub\Collection\Outbox;
15 -use Activitypub\Collection\Remote_Actors;
16 14 use Activitypub\Transformer\Factory;
17 15
18 16 /**
19 17 * ActivityPub Migration Class
@@ -24,17 +22,29 @@
24 22 /**
25 23 * Initialize the class, registering WordPress hooks.
26 24 */
27 25 public static function init() {
26 + \add_action( 'activitypub_migrate', array( self::class, 'async_migration' ) );
27 + \add_action( 'activitypub_upgrade', array( self::class, 'async_upgrade' ), 10, 99 );
28 + \add_action( 'activitypub_update_comment_counts', array( self::class, 'update_comment_counts' ), 10, 2 );
29 +
28 30 self::maybe_migrate();
31 + }
29 32
30 - Scheduler::register_async_batch_callback( 'activitypub_migrate_from_0_17', array( self::class, 'migrate_from_0_17' ) );
31 - Scheduler::register_async_batch_callback( 'activitypub_update_comment_counts', array( self::class, 'update_comment_counts' ) );
32 - Scheduler::register_async_batch_callback( 'activitypub_create_post_outbox_items', array( self::class, 'create_post_outbox_items' ) );
33 - Scheduler::register_async_batch_callback( 'activitypub_create_comment_outbox_items', array( self::class, 'create_comment_outbox_items' ) );
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' ) );
33 + /**
34 + * Get the target version.
35 + *
36 + * This is the version that the database structure will be updated to.
37 + * It is the same as the plugin version.
38 + *
39 + * @deprecated 4.2.0 Use constant ACTIVITYPUB_PLUGIN_VERSION directly.
40 + *
41 + * @return string The target version.
42 + */
43 + public static function get_target_version() {
44 + _deprecated_function( __FUNCTION__, '4.2.0', 'ACTIVITYPUB_PLUGIN_VERSION' );
45 +
46 + return ACTIVITYPUB_PLUGIN_VERSION;
37 47 }
38 48
39 49 /**
40 50 * The current version of the database structure.
@@ -126,14 +136,15 @@
126 136 self::add_default_settings();
127 137 $version_from_db = ACTIVITYPUB_PLUGIN_VERSION;
128 138 }
129 139
140 + // Schedule the async migration.
141 + if ( ! \wp_next_scheduled( 'activitypub_migrate', $version_from_db ) ) {
142 + \wp_schedule_single_event( \time(), 'activitypub_migrate', array( $version_from_db ) );
143 + }
130 144 if ( \version_compare( $version_from_db, '0.17.0', '<' ) ) {
131 145 self::migrate_from_0_16();
132 146 }
133 - if ( \version_compare( $version_from_db, '1.0.0', '<' ) ) {
134 - \wp_schedule_single_event( \time(), 'activitypub_migrate_from_0_17' );
135 - }
136 147 if ( \version_compare( $version_from_db, '1.3.0', '<' ) ) {
137 148 self::migrate_from_1_2_0();
138 149 }
139 150 if ( \version_compare( $version_from_db, '2.1.0', '<' ) ) {
@@ -159,73 +170,30 @@
159 170 }
160 171 if ( \version_compare( $version_from_db, '4.7.2', '<' ) ) {
161 172 self::migrate_to_4_7_2();
162 173 }
174 + if ( \version_compare( $version_from_db, '4.7.3', '<' ) ) {
175 + add_action( 'init', 'flush_rewrite_rules', 20 );
176 + }
163 177 if ( \version_compare( $version_from_db, '5.0.0', '<' ) ) {
164 178 Scheduler::register_schedules();
165 - \wp_schedule_single_event( \time(), 'activitypub_create_post_outbox_items' );
166 - \wp_schedule_single_event( \time() + 15, 'activitypub_create_comment_outbox_items' );
179 + \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'create_post_outbox_items' ) );
180 + \wp_schedule_single_event( \time() + 15, 'activitypub_upgrade', array( 'create_comment_outbox_items' ) );
181 + add_action( 'init', 'flush_rewrite_rules', 20 );
167 182 }
183 + if ( \version_compare( $version_from_db, '5.2.0', '<' ) ) {
184 + Scheduler::register_schedules();
185 + }
168 186 if ( \version_compare( $version_from_db, '5.4.0', '<' ) ) {
169 187 \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_slashing' ) );
170 188 \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_comment_author_emails' ) );
189 + \add_action( 'init', 'flush_rewrite_rules', 20 );
171 190 }
172 191 if ( \version_compare( $version_from_db, '5.7.0', '<' ) ) {
173 192 self::delete_mastodon_api_orphaned_extra_fields();
174 193 }
175 - if ( \version_compare( $version_from_db, '5.8.0', '<' ) ) {
176 - self::update_notification_options();
177 - }
178 - if ( \version_compare( $version_from_db, '6.0.0', '<' ) ) {
179 - self::migrate_followers_to_ap_actor_cpt();
180 - \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_storage' ) );
181 - }
182 - if ( \version_compare( $version_from_db, '6.0.1', '<' ) ) {
183 - self::migrate_followers_to_ap_actor_cpt();
184 - \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_storage' ) );
185 - }
186 - if ( \version_compare( $version_from_db, '7.0.0', '<' ) ) {
187 - wp_unschedule_hook( 'activitypub_update_followers' );
188 - wp_unschedule_hook( 'activitypub_cleanup_followers' );
189 194
190 - if ( ! \wp_next_scheduled( 'activitypub_update_remote_actors' ) ) {
191 - \wp_schedule_event( time(), 'hourly', 'activitypub_update_remote_actors' );
192 - }
193 -
194 - if ( ! \wp_next_scheduled( 'activitypub_cleanup_remote_actors' ) ) {
195 - \wp_schedule_event( time(), 'daily', 'activitypub_cleanup_remote_actors' );
196 - }
197 - }
198 - if ( \version_compare( $version_from_db, '7.3.0', '<' ) ) {
199 - self::remove_pending_application_user_follow_requests();
200 - }
201 - if ( \version_compare( $version_from_db, '7.5.0', '<' ) ) {
202 - self::sync_jetpack_following_meta();
203 - }
204 - if ( \version_compare( $version_from_db, '7.6.0', '<' ) ) {
205 - self::clean_up_inbox();
206 - \wp_schedule_single_event( \time(), 'activitypub_migrate_avatar_to_remote_actors' );
207 - }
208 - if ( \version_compare( $version_from_db, '7.9.0', '<' ) ) {
209 - \wp_schedule_single_event( \time(), 'activitypub_migrate_actor_emoji' );
210 - }
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' );
214 - }
215 -
216 195 /*
217 - * Defer the flush to late in the `init` cycle (priority 20). Migration::init
218 - * runs at priority 1, which is earlier than most plugins register their
219 - * rewrite rules. Flushing synchronously here would persist a truncated
220 - * ruleset that omits third-party rules added on `init` at priority 10.
221 - */
222 - \add_action( 'init', array( Activitypub::class, 'flush_rewrite_rules' ), 20 );
223 -
224 - // Ensure all required cron schedules are registered.
225 - Scheduler::register_schedules();
226 -
227 - /*
228 196 * Add new update routines above this comment. ^
229 197 *
230 198 * Use 'unreleased' as the version number for new migrations and add tests for the callback directly.
231 199 * The release script will automatically replace it with the actual version number.
@@ -249,8 +217,51 @@
249 217 self::unlock();
250 218 }
251 219
252 220 /**
221 + * Asynchronously migrates the database structure.
222 + *
223 + * @param string $version_from_db The version from which to migrate.
224 + */
225 + public static function async_migration( $version_from_db ) {
226 + if ( \version_compare( $version_from_db, '1.0.0', '<' ) ) {
227 + self::migrate_from_0_17();
228 + }
229 + }
230 +
231 + /**
232 + * Asynchronously runs upgrade routines.
233 + *
234 + * @param callable $callback Callable upgrade routine. Must be a method of this class.
235 + * @params mixed ...$args Optional. Parameters that get passed to the callback.
236 + */
237 + public static function async_upgrade( $callback ) {
238 + $args = \func_get_args();
239 +
240 + // Bail if the existing lock is still valid.
241 + if ( self::is_locked() ) {
242 + \wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'activitypub_upgrade', $args );
243 + return;
244 + }
245 +
246 + self::lock();
247 +
248 + $callback = array_shift( $args ); // Remove $callback from arguments.
249 + $next = \call_user_func_array( array( self::class, $callback ), $args );
250 +
251 + self::unlock();
252 +
253 + if ( ! empty( $next ) ) {
254 + // Schedule the next run, adding the result to the arguments.
255 + \wp_schedule_single_event(
256 + \time() + 30,
257 + 'activitypub_upgrade',
258 + \array_merge( array( $callback ), \array_values( $next ) )
259 + );
260 + }
261 + }
262 +
263 + /**
253 264 * Updates the custom template to use shortcodes instead of the deprecated templates.
254 265 */
255 266 private static function migrate_from_0_16() {
256 267 // Get the custom template.
@@ -295,12 +306,14 @@
295 306 $followers = get_user_meta( $user_id, 'activitypub_followers', true );
296 307
297 308 if ( $followers ) {
298 309 foreach ( $followers as $actor ) {
299 - Followers::add( $user_id, $actor );
310 + Followers::add_follower( $user_id, $actor );
300 311 }
301 312 }
302 313 }
314 +
315 + Activitypub::flush_rewrite_rules();
303 316 }
304 317
305 318 /**
306 319 * Clear the cache after updating to 1.3.0.
@@ -387,9 +400,9 @@
387 400 )
388 401 );
389 402
390 403 foreach ( $users as $user ) {
391 - $followers = Followers::get_many( $user->ID );
404 + $followers = Followers::get_followers( $user->ID );
392 405
393 406 if ( $followers ) {
394 407 \update_user_option( $user->ID, 'activitypub_use_permalink_as_id', '1' );
395 408 }
@@ -394,9 +407,9 @@
394 407 \update_user_option( $user->ID, 'activitypub_use_permalink_as_id', '1' );
395 408 }
396 409 }
397 410
398 - $followers = Followers::get_many( Actors::BLOG_USER_ID );
411 + $followers = Followers::get_followers( Actors::BLOG_USER_ID );
399 412
400 413 if ( $followers ) {
401 414 \update_option( 'activitypub_use_permalink_as_id_for_blog', '1' );
402 415 }
@@ -404,9 +417,9 @@
404 417 self::migrate_actor_mode();
405 418 }
406 419
407 420 /**
408 - * Update to 4.1.0
421 + * Upate to 4.1.0
409 422 *
410 423 * * Migrate the `activitypub_post_content_type` to only use `activitypub_custom_post_content`.
411 424 */
412 425 public static function migrate_to_4_1_0() {
@@ -474,9 +487,9 @@
474 487 public static function migrate_to_4_7_2() {
475 488 global $wpdb;
476 489 // phpcs:ignore WordPress.DB
477 490 $followers = $wpdb->get_col(
478 - $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s", Remote_Actors::POST_TYPE )
491 + $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s", Followers::POST_TYPE )
479 492 );
480 493 foreach ( $followers as $id ) {
481 494 clean_post_cache( $id );
482 495 }
@@ -487,14 +500,27 @@
487 500 *
488 501 * @see Comment::pre_wp_update_comment_count_now()
489 502 * @param int $batch_size Optional. Number of posts to process per batch. Default 100.
490 503 * @param int $offset Optional. Number of posts to skip. Default 0.
491 - *
492 - * @return int[]|void Array with batch size and offset if there are more posts to process.
493 504 */
494 505 public static function update_comment_counts( $batch_size = 100, $offset = 0 ) {
495 506 global $wpdb;
496 507
508 + // Bail if the existing lock is still valid.
509 + if ( self::is_locked() ) {
510 + \wp_schedule_single_event(
511 + time() + ( 5 * MINUTE_IN_SECONDS ),
512 + 'activitypub_update_comment_counts',
513 + array(
514 + 'batch_size' => $batch_size,
515 + 'offset' => $offset,
516 + )
517 + );
518 + return;
519 + }
520 +
521 + self::lock();
522 +
497 523 Comment::register_comment_types();
498 524 $comment_types = Comment::get_comment_type_slugs();
499 525 $type_inclusion = "AND comment_type IN ('" . implode( "','", $comment_types ) . "')";
500 526
@@ -513,10 +539,19 @@
513 539 }
514 540
515 541 if ( count( $post_ids ) === $batch_size ) {
516 542 // Schedule next batch.
517 - return array( $batch_size, $offset + $batch_size );
543 + \wp_schedule_single_event(
544 + time() + MINUTE_IN_SECONDS,
545 + 'activitypub_update_comment_counts',
546 + array(
547 + 'batch_size' => $batch_size,
548 + 'offset' => $offset + $batch_size,
549 + )
550 + );
518 551 }
552 +
553 + self::unlock();
519 554 }
520 555
521 556 /**
522 557 * Create outbox items for posts in batches.
@@ -535,9 +570,9 @@
535 570 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
536 571 'meta_query' => array(
537 572 array(
538 573 'key' => 'activitypub_status',
539 - 'value' => ACTIVITYPUB_OBJECT_STATE_FEDERATED,
574 + 'value' => 'federated',
540 575 ),
541 576 ),
542 577 )
543 578 );
@@ -582,9 +617,9 @@
582 617 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
583 618 'meta_query' => array(
584 619 array(
585 620 'key' => 'activitypub_status',
586 - 'value' => ACTIVITYPUB_OBJECT_STATE_FEDERATED,
621 + 'value' => 'federated',
587 622 ),
588 623 ),
589 624 )
590 625 );
@@ -658,9 +693,8 @@
658 693 $comments = \get_comments(
659 694 array(
660 695 'number' => $batch_size,
661 696 'offset' => $offset,
662 - 'orderby' => 'comment_ID',
663 697 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
664 698 'meta_query' => array(
665 699 array(
666 700 'key' => 'protocol',
@@ -705,8 +739,9 @@
705 739 * Add the ActivityPub capability to all users that can publish posts.
706 740 */
707 741 public static function add_default_settings() {
708 742 self::add_activitypub_capability();
743 + self::add_notification_defaults();
709 744 self::add_default_extra_field();
710 745 }
711 746
712 747 /**
@@ -760,8 +795,16 @@
760 795 }
761 796 }
762 797
763 798 /**
799 + * Add default notification settings.
800 + */
801 + private static function add_notification_defaults() {
802 + \add_option( 'activitypub_mailer_new_follower', '1' );
803 + \add_option( 'activitypub_mailer_new_dm', '1' );
804 + }
805 +
806 + /**
764 807 * Add a default extra field for the user.
765 808 */
766 809 private static function add_default_extra_field() {
767 810 $users = \get_users(
@@ -797,9 +840,9 @@
797 840 );
798 841 }
799 842
800 843 /**
801 - * Rename user meta keys.
844 + * Rename meta keys.
802 845 *
803 846 * @param string $old_key The old comment meta key.
804 847 * @param string $new_key The new comment meta key.
805 848 */
@@ -815,26 +858,8 @@
815 858 );
816 859 }
817 860
818 861 /**
819 - * Update post meta keys.
820 - *
821 - * @param string $old_key The old post meta key.
822 - * @param string $new_key The new post meta key.
823 - */
824 - private static function update_postmeta_key( $old_key, $new_key ) {
825 - global $wpdb;
826 -
827 - $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
828 - $wpdb->postmeta,
829 - array( 'meta_key' => $new_key ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
830 - array( 'meta_key' => $old_key ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
831 - array( '%s' ),
832 - array( '%s' )
833 - );
834 - }
835 -
836 - /**
837 862 * Rename option keys.
838 863 *
839 864 * @param string $old_key The old option key.
840 865 * @param string $new_key The new option key.
@@ -896,319 +921,6 @@
896 921 'post_type' => Extra_Fields::USER_POST_TYPE,
897 922 'post_author' => Actors::BLOG_USER_ID,
898 923 )
899 924 );
900 - }
901 -
902 - /**
903 - * Update notification options.
904 - */
905 - public static function update_notification_options() {
906 - $new_dm = \get_option( 'activitypub_mailer_new_dm', '1' );
907 - $new_follower = \get_option( 'activitypub_mailer_new_follower', '1' );
908 -
909 - // Add the blog user notification options.
910 - \add_option( 'activitypub_blog_user_mailer_new_dm', $new_dm );
911 - \add_option( 'activitypub_blog_user_mailer_new_follower', $new_follower );
912 - \add_option( 'activitypub_blog_user_mailer_new_mention', '1' );
913 -
914 - $user_ids = \get_users(
915 - array(
916 - 'capability__in' => array( 'activitypub' ),
917 - 'fields' => 'id',
918 - )
919 - );
920 -
921 - // Add the actor notification options.
922 - foreach ( $user_ids as $user_id ) {
923 - \update_user_option( $user_id, 'activitypub_mailer_new_dm', $new_dm );
924 - \update_user_option( $user_id, 'activitypub_mailer_new_follower', $new_follower );
925 - \update_user_option( $user_id, 'activitypub_mailer_new_mention', '1' );
926 - }
927 -
928 - // Delete the old notification options.
929 - \delete_option( 'activitypub_mailer_new_dm' );
930 - \delete_option( 'activitypub_mailer_new_follower' );
931 - }
932 -
933 - /**
934 - * Migrate followers to the new CPT.
935 - */
936 - public static function migrate_followers_to_ap_actor_cpt() {
937 - global $wpdb;
938 -
939 - $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
940 - $wpdb->posts,
941 - array( 'post_type' => Remote_Actors::POST_TYPE ),
942 - array( 'post_type' => 'ap_follower' ),
943 - array( '%s' ),
944 - array( '%s' )
945 - );
946 -
947 - self::update_postmeta_key( '_activitypub_user_id', Followers::FOLLOWER_META_KEY );
948 - }
949 -
950 - /**
951 - * Update _activitypub_actor_json meta values to ensure they are properly slashed.
952 - *
953 - * @param int $batch_size Optional. Number of meta values to process per batch. Default 100.
954 - *
955 - * @return array|void Array with batch size and offset if there are more meta values to process, void otherwise.
956 - */
957 - public static function update_actor_json_storage( $batch_size = 100 ) {
958 - global $wpdb;
959 -
960 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery
961 - $meta_values = $wpdb->get_results(
962 - $wpdb->prepare(
963 - "SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = '_activitypub_actor_json' LIMIT %d",
964 - $batch_size
965 - )
966 - );
967 -
968 - $has_kses = false !== \has_filter( 'content_save_pre', 'wp_filter_post_kses' );
969 - if ( $has_kses ) {
970 - // Prevent KSES from corrupting JSON in post_content.
971 - \kses_remove_filters();
972 - }
973 -
974 - foreach ( $meta_values as $meta ) {
975 - $post = \get_post( $meta->post_id );
976 -
977 - if ( ! $post ) {
978 - \delete_post_meta( $meta->post_id, '_activitypub_actor_json' );
979 - continue;
980 - }
981 -
982 - $post_content = \json_decode( $meta->meta_value, true );
983 -
984 - if ( \json_last_error() !== JSON_ERROR_NONE ) {
985 - $post_content = Http::get_remote_object( $post->guid );
986 -
987 - if ( \is_wp_error( $post_content ) ) {
988 - \delete_post_meta( $post->ID, '_activitypub_actor_json' );
989 - continue;
990 - }
991 - }
992 -
993 - \wp_update_post(
994 - array(
995 - 'ID' => $post->ID,
996 - 'post_content' => \wp_slash( \wp_json_encode( $post_content ) ),
997 - )
998 - );
999 -
1000 - \delete_post_meta( $post->ID, '_activitypub_actor_json' );
1001 - }
1002 -
1003 - if ( $has_kses ) {
1004 - // Restore KSES filters.
1005 - \kses_init_filters();
1006 - }
1007 -
1008 - if ( \count( $meta_values ) === $batch_size ) {
1009 - return array(
1010 - 'batch_size' => $batch_size,
1011 - );
1012 - }
1013 - }
1014 -
1015 - /**
1016 - * Removes pending follow requests for the application user.
1017 - */
1018 - public static function remove_pending_application_user_follow_requests() {
1019 - global $wpdb;
1020 -
1021 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1022 - $wpdb->delete(
1023 - $wpdb->postmeta,
1024 - array(
1025 - '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
1027 - )
1028 - );
1029 - }
1030 -
1031 - /**
1032 - * Sync Jetpack meta for all followings.
1033 - *
1034 - * Replays the added_post_meta sync action for Jetpack with the Following::FOLLOWING_META_KEY meta key.
1035 - */
1036 - public static function sync_jetpack_following_meta() {
1037 - if ( ! \class_exists( 'Jetpack' ) || ! \Jetpack::is_connection_ready() ) {
1038 - return;
1039 - }
1040 -
1041 - global $wpdb;
1042 -
1043 - // Get all posts that have the following meta key.
1044 - $posts_with_following = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1045 - $wpdb->prepare(
1046 - "SELECT meta_id, post_id, meta_key, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s",
1047 - Following::FOLLOWING_META_KEY
1048 - ),
1049 - ARRAY_N
1050 - );
1051 -
1052 - // Trigger the added_post_meta action for each following relationship.
1053 - foreach ( $posts_with_following as $meta ) {
1054 - /**
1055 - * Fires when post meta is added.
1056 - *
1057 - * @param int $meta_id ID of the metadata entry.
1058 - * @param int $object_id Post ID.
1059 - * @param string $meta_key Metadata key.
1060 - * @param mixed $meta_value Metadata value.
1061 - */
1062 - \do_action( 'added_post_meta', ...$meta );
1063 - }
1064 - }
1065 -
1066 - /**
1067 - * Clean up inbox items for shared inbox migration.
1068 - *
1069 - * Deletes all existing inbox items to prepare for the new shared inbox structure
1070 - * where activities are stored once with multiple recipients as metadata.
1071 - */
1072 - private static function clean_up_inbox() {
1073 - global $wpdb;
1074 -
1075 - // Get all inbox post IDs.
1076 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1077 - $inbox_ids = $wpdb->get_col(
1078 - $wpdb->prepare(
1079 - "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s",
1080 - \Activitypub\Collection\Inbox::POST_TYPE
1081 - )
1082 - );
1083 -
1084 - // Delete all inbox items and their metadata.
1085 - foreach ( $inbox_ids as $post_id ) {
1086 - \wp_delete_post( $post_id, true );
1087 - }
1088 - }
1089 -
1090 - /**
1091 - * Migrate avatar URLs from comment meta to remote actors in batches.
1092 - *
1093 - * This migration:
1094 - * 1. Finds all comments with ActivityPub protocol and avatar_url meta
1095 - * 2. Looks up the remote actor by comment_author_url
1096 - * 3. Adds _activitypub_remote_actor_id to comment meta
1097 - * 4. Stores avatar_url in remote actor post meta
1098 - *
1099 - * Note: We don't use offset because as we add _activitypub_remote_actor_id,
1100 - * comments are filtered out of the query. We just keep fetching the next
1101 - * batch until no more comments match the criteria.
1102 - *
1103 - * @param int $batch_size Optional. Number of comments to process per batch. Default 50.
1104 - * @return array|null Array with batch size if there are more comments to process, null otherwise.
1105 - */
1106 - public static function migrate_avatar_to_remote_actors( $batch_size = 50 ) {
1107 - global $wpdb;
1108 -
1109 - /*
1110 - * Get comments with avatar_url meta that don't have _activitypub_remote_actor_id yet.
1111 - * Uses conditional aggregation to reduce JOINs from 3 to 1, improving query performance.
1112 - * Filters meta_key before GROUP BY to reduce rows processed during aggregation.
1113 - * No offset needed - as we process comments, they're filtered out by the HAVING clause.
1114 - */
1115 - $comments = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1116 - $wpdb->prepare(
1117 - "SELECT c.comment_ID, c.comment_author_url,
1118 - MAX(CASE WHEN cm.meta_key = 'avatar_url' THEN cm.meta_value END) AS avatar_url,
1119 - MAX(CASE WHEN cm.meta_key = 'protocol' THEN cm.meta_value END) AS protocol,
1120 - MAX(CASE WHEN cm.meta_key = '_activitypub_remote_actor_id' THEN cm.meta_value END) AS remote_actor_id
1121 - FROM {$wpdb->comments} c
1122 - INNER JOIN {$wpdb->commentmeta} cm ON c.comment_ID = cm.comment_id
1123 - WHERE cm.meta_key IN ('avatar_url', 'protocol', '_activitypub_remote_actor_id')
1124 - GROUP BY c.comment_ID, c.comment_author_url
1125 - HAVING protocol = 'activitypub'
1126 - AND avatar_url IS NOT NULL
1127 - AND (remote_actor_id IS NULL OR remote_actor_id = '')
1128 - LIMIT %d",
1129 - $batch_size
1130 - )
1131 - );
1132 -
1133 - foreach ( $comments as $comment ) {
1134 - if ( empty( $comment->comment_author_url ) ) {
1135 - continue;
1136 - }
1137 -
1138 - // Try to get the remote actor by URI.
1139 - $remote_actor = Remote_Actors::fetch_by_uri( $comment->comment_author_url );
1140 -
1141 - // If we have a valid remote actor, store the reference.
1142 - if ( ! \is_wp_error( $remote_actor ) ) {
1143 - // Add _activitypub_remote_actor_id to comment meta.
1144 - \add_comment_meta( $comment->comment_ID, '_activitypub_remote_actor_id', $remote_actor->ID, true );
1145 -
1146 - // Ensure avatar is stored on remote actor if not already present.
1147 - $existing_avatar = \get_post_meta( $remote_actor->ID, '_activitypub_avatar_url', true );
1148 - if ( empty( $existing_avatar ) && ! empty( $comment->avatar_url ) ) {
1149 - \update_post_meta( $remote_actor->ID, '_activitypub_avatar_url', \esc_url_raw( $comment->avatar_url ) );
1150 - }
1151 - }
1152 - }
1153 -
1154 - // Return batch info if there are more comments to process.
1155 - if ( count( $comments ) === $batch_size ) {
1156 - return array(
1157 - 'batch_size' => $batch_size,
1158 - );
1159 - }
1160 -
1161 - return null;
1162 - }
1163 -
1164 - /**
1165 - * Migrate emoji data from stored actor JSON to post meta.
1166 - *
1167 - * This migration:
1168 - * 1. Finds all remote actor posts without _activitypub_emoji meta
1169 - * 2. Extracts emoji from stored JSON in post_content
1170 - * 3. Stores as _activitypub_emoji post meta
1171 - *
1172 - * @param int $batch_size Optional. Number of actors to process per batch. Default 50.
1173 - * @param int $offset Optional. Offset for pagination. Default 0.
1174 - * @return array|null Array with batch size if there are more actors to process, null otherwise.
1175 - */
1176 - public static function migrate_actor_emoji( $batch_size = 50, $offset = 0 ) {
1177 - $actors = \get_posts(
1178 - array(
1179 - 'post_type' => Remote_Actors::POST_TYPE,
1180 - 'posts_per_page' => $batch_size,
1181 - 'offset' => $offset,
1182 - 'post_status' => 'any',
1183 - 'orderby' => 'ID',
1184 - 'order' => 'ASC',
1185 - )
1186 - );
1187 -
1188 - foreach ( $actors as $actor_post ) {
1189 - if ( empty( $actor_post->post_content ) ) {
1190 - continue;
1191 - }
1192 -
1193 - $actor_data = \json_decode( $actor_post->post_content, true );
1194 - if ( ! $actor_data ) {
1195 - continue;
1196 - }
1197 -
1198 - $emoji_meta = Emoji::prepare_actor_meta( $actor_data );
1199 - if ( ! empty( $emoji_meta['_activitypub_emoji'] ) ) {
1200 - \update_post_meta( $actor_post->ID, '_activitypub_emoji', $emoji_meta['_activitypub_emoji'] );
1201 - }
1202 - }
1203 -
1204 - // Return batch info if there are more actors to process.
1205 - if ( count( $actors ) === $batch_size ) {
1206 - return array(
1207 - 'batch_size' => $batch_size,
1208 - 'offset' => $offset + $batch_size,
1209 - );
1210 - }
1211 -
1212 - return null;
1213 925 }
1214 926 }