@@ -31,6 +31,7 @@ public static function init() {
3131 Scheduler::register_async_batch_callback ( 'activitypub_update_comment_counts ' , array ( self ::class, 'update_comment_counts ' ) );
3232 Scheduler::register_async_batch_callback ( 'activitypub_create_post_outbox_items ' , array ( self ::class, 'create_post_outbox_items ' ) );
3333 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 ' ) );
3435 }
3536
3637 /**
@@ -211,6 +212,7 @@ public static function maybe_migrate() {
211212
212213 if ( \version_compare ( $ version_from_db , 'unreleased ' , '< ' ) ) {
213214 self ::clean_up_inbox ();
215+ \wp_schedule_single_event ( \time (), 'activitypub_migrate_avatar_to_remote_actors ' );
214216 }
215217
216218 // Ensure all required cron schedules are registered.
@@ -1079,4 +1081,78 @@ private static function clean_up_inbox() {
10791081 \wp_delete_post ( $ post_id , true );
10801082 }
10811083 }
1084+
1085+ /**
1086+ * Migrate avatar URLs from comment meta to remote actors in batches.
1087+ *
1088+ * This migration:
1089+ * 1. Finds all comments with ActivityPub protocol and avatar_url meta
1090+ * 2. Looks up the remote actor by comment_author_url
1091+ * 3. Adds _activitypub_remote_actor_id to comment meta
1092+ * 4. Stores avatar_url in remote actor post meta
1093+ *
1094+ * Note: We don't use offset because as we add _activitypub_remote_actor_id,
1095+ * comments are filtered out of the query. We just keep fetching the next
1096+ * batch until no more comments match the criteria.
1097+ *
1098+ * @param int $batch_size Optional. Number of comments to process per batch. Default 50.
1099+ * @return array|null Array with batch size if there are more comments to process, null otherwise.
1100+ */
1101+ public static function migrate_avatar_to_remote_actors ( $ batch_size = 50 ) {
1102+ global $ wpdb ;
1103+
1104+ /*
1105+ * Get comments with avatar_url meta that don't have _activitypub_remote_actor_id yet.
1106+ * Uses conditional aggregation to reduce JOINs from 3 to 1, improving query performance.
1107+ * Filters meta_key before GROUP BY to reduce rows processed during aggregation.
1108+ * No offset needed - as we process comments, they're filtered out by the HAVING clause.
1109+ */
1110+ $ comments = $ wpdb ->get_results ( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1111+ $ wpdb ->prepare (
1112+ "SELECT c.comment_ID, c.comment_author_url,
1113+ MAX(CASE WHEN cm.meta_key = 'avatar_url' THEN cm.meta_value END) AS avatar_url,
1114+ MAX(CASE WHEN cm.meta_key = 'protocol' THEN cm.meta_value END) AS protocol,
1115+ MAX(CASE WHEN cm.meta_key = '_activitypub_remote_actor_id' THEN cm.meta_value END) AS remote_actor_id
1116+ FROM {$ wpdb ->comments } c
1117+ INNER JOIN {$ wpdb ->commentmeta } cm ON c.comment_ID = cm.comment_id
1118+ WHERE cm.meta_key IN ('avatar_url', 'protocol', '_activitypub_remote_actor_id')
1119+ GROUP BY c.comment_ID, c.comment_author_url
1120+ HAVING protocol = 'activitypub'
1121+ AND avatar_url IS NOT NULL
1122+ AND (remote_actor_id IS NULL OR remote_actor_id = '')
1123+ LIMIT %d " ,
1124+ $ batch_size
1125+ )
1126+ );
1127+
1128+ foreach ( $ comments as $ comment ) {
1129+ if ( empty ( $ comment ->comment_author_url ) ) {
1130+ continue ;
1131+ }
1132+
1133+ // Try to get the remote actor by URI.
1134+ $ remote_actor = Remote_Actors::fetch_by_uri ( $ comment ->comment_author_url );
1135+
1136+ // If we have a valid remote actor, store the reference.
1137+ if ( ! \is_wp_error ( $ remote_actor ) ) {
1138+ // Add _activitypub_remote_actor_id to comment meta.
1139+ \add_comment_meta ( $ comment ->comment_ID , '_activitypub_remote_actor_id ' , $ remote_actor ->ID , true );
1140+
1141+ // Ensure avatar is stored on remote actor if not already present.
1142+ $ existing_avatar = \get_post_meta ( $ remote_actor ->ID , '_activitypub_avatar_url ' , true );
1143+ if ( empty ( $ existing_avatar ) && ! empty ( $ comment ->avatar_url ) ) {
1144+ \update_post_meta ( $ remote_actor ->ID , '_activitypub_avatar_url ' , \esc_url_raw ( $ comment ->avatar_url ) );
1145+ }
1146+ }
1147+ }
1148+
1149+ // Return batch info if there are more comments to process.
1150+ if ( count ( $ comments ) === $ batch_size ) {
1151+ return array (
1152+ 'batch_size ' => $ batch_size ,
1153+ );
1154+ }
1155+
1156+ return null ;
1157+ }
10821158}
0 commit comments