PluginProbe
ActivityPub / 3.2.4
ActivityPub v3.2.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 +89 -963 8.2.13.2.4 View file →
@@ -1,20 +1,10 @@
1 1 <?php
2 -/**
3 - * Migration class file.
4 - *
5 - * @package Activitypub
6 - */
7 -
8 2 namespace Activitypub;
9 3
10 -use Activitypub\Collection\Actors;
11 -use Activitypub\Collection\Extra_Fields;
4 +use Activitypub\Activitypub;
5 +use Activitypub\Model\Blog;
12 6 use Activitypub\Collection\Followers;
13 -use Activitypub\Collection\Following;
14 -use Activitypub\Collection\Outbox;
15 -use Activitypub\Collection\Remote_Actors;
16 -use Activitypub\Transformer\Factory;
17 7
18 8 /**
19 9 * ActivityPub Migration Class
20 10 *
@@ -21,20 +11,26 @@
21 11 * @author Matthias Pfefferle
22 12 */
23 13 class Migration {
24 14 /**
25 - * Initialize the class, registering WordPress hooks.
15 + * Initialize the class, registering WordPress hooks
26 16 */
27 17 public static function init() {
18 + \add_action( 'activitypub_migrate', array( self::class, 'async_migration' ) );
19 +
28 20 self::maybe_migrate();
21 + }
29 22
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' ) );
23 + /**
24 + * Get the target version.
25 + *
26 + * This is the version that the database structure will be updated to.
27 + * It is the same as the plugin version.
28 + *
29 + * @return string The target version.
30 + */
31 + public static function get_target_version() {
32 + return get_plugin_version();
37 33 }
38 34
39 35 /**
40 36 * The current version of the database structure.
@@ -47,25 +43,18 @@
47 43
48 44 /**
49 45 * Locks the database migration process to prevent simultaneous migrations.
50 46 *
51 - * @return bool|int True if the lock was successful, timestamp of existing lock otherwise.
47 + * @return void
52 48 */
53 49 public static function lock() {
54 - global $wpdb;
55 -
56 - // Try to lock.
57 - $lock_result = (bool) $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", 'activitypub_migration_lock', \time() ) ); // phpcs:ignore WordPress.DB
58 -
59 - if ( ! $lock_result ) {
60 - $lock_result = \get_option( 'activitypub_migration_lock' );
61 - }
62 -
63 - return $lock_result;
50 + \update_option( 'activitypub_migration_lock', \time() );
64 51 }
65 52
66 53 /**
67 54 * Unlocks the database migration process.
55 + *
56 + * @return void
68 57 */
69 58 public static function unlock() {
70 59 \delete_option( 'activitypub_migration_lock' );
71 60 }
@@ -97,11 +86,11 @@
97 86 *
98 87 * @return bool True if the database structure is up to date, false otherwise.
99 88 */
100 89 public static function is_latest_version() {
101 - return (bool) \version_compare(
90 + return (bool) version_compare(
102 91 self::get_version(),
103 - ACTIVITYPUB_PLUGIN_VERSION,
92 + self::get_target_version(),
104 93 '=='
105 94 );
106 95 }
107 96
@@ -120,147 +109,61 @@
120 109 self::lock();
121 110
122 111 $version_from_db = self::get_version();
123 112
124 - // Check for initial migration.
113 + // check for inital migration
125 114 if ( ! $version_from_db ) {
126 115 self::add_default_settings();
127 - $version_from_db = ACTIVITYPUB_PLUGIN_VERSION;
116 + $version_from_db = self::get_target_version();
128 117 }
129 118
130 - if ( \version_compare( $version_from_db, '0.17.0', '<' ) ) {
119 + // schedule the async migration
120 + if ( ! \wp_next_scheduled( 'activitypub_migrate', $version_from_db ) ) {
121 + \wp_schedule_single_event( \time(), 'activitypub_migrate', array( $version_from_db ) );
122 + }
123 + if ( version_compare( $version_from_db, '0.17.0', '<' ) ) {
131 124 self::migrate_from_0_16();
132 125 }
133 - if ( \version_compare( $version_from_db, '1.0.0', '<' ) ) {
134 - \wp_schedule_single_event( \time(), 'activitypub_migrate_from_0_17' );
135 - }
136 - if ( \version_compare( $version_from_db, '1.3.0', '<' ) ) {
126 + if ( version_compare( $version_from_db, '1.3.0', '<' ) ) {
137 127 self::migrate_from_1_2_0();
138 128 }
139 - if ( \version_compare( $version_from_db, '2.1.0', '<' ) ) {
129 + if ( version_compare( $version_from_db, '2.1.0', '<' ) ) {
140 130 self::migrate_from_2_0_0();
141 131 }
142 - if ( \version_compare( $version_from_db, '2.3.0', '<' ) ) {
132 + if ( version_compare( $version_from_db, '2.3.0', '<' ) ) {
143 133 self::migrate_from_2_2_0();
144 134 }
145 - if ( \version_compare( $version_from_db, '3.0.0', '<' ) ) {
135 + if ( version_compare( $version_from_db, '3.0.0', '<' ) ) {
146 136 self::migrate_from_2_6_0();
147 137 }
148 - if ( \version_compare( $version_from_db, '4.0.0', '<' ) ) {
149 - self::migrate_to_4_0_0();
150 - }
151 - if ( \version_compare( $version_from_db, '4.1.0', '<' ) ) {
152 - self::migrate_to_4_1_0();
153 - }
154 - if ( \version_compare( $version_from_db, '4.5.0', '<' ) ) {
155 - \wp_schedule_single_event( \time() + MINUTE_IN_SECONDS, 'activitypub_update_comment_counts' );
156 - }
157 - if ( \version_compare( $version_from_db, '4.7.1', '<' ) ) {
158 - self::migrate_to_4_7_1();
159 - }
160 - if ( \version_compare( $version_from_db, '4.7.2', '<' ) ) {
161 - self::migrate_to_4_7_2();
162 - }
163 - if ( \version_compare( $version_from_db, '5.0.0', '<' ) ) {
164 - 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' );
167 - }
168 - if ( \version_compare( $version_from_db, '5.4.0', '<' ) ) {
169 - \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_slashing' ) );
170 - \wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_comment_author_emails' ) );
171 - }
172 - if ( \version_compare( $version_from_db, '5.7.0', '<' ) ) {
173 - self::delete_mastodon_api_orphaned_extra_fields();
174 - }
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 138
190 - if ( ! \wp_next_scheduled( 'activitypub_update_remote_actors' ) ) {
191 - \wp_schedule_event( time(), 'hourly', 'activitypub_update_remote_actors' );
192 - }
139 + update_option( 'activitypub_db_version', self::get_target_version() );
193 140
194 - if ( ! \wp_next_scheduled( 'activitypub_cleanup_remote_actors' ) ) {
195 - \wp_schedule_event( time(), 'daily', 'activitypub_cleanup_remote_actors' );
196 - }
141 + self::unlock();
142 + }
143 +
144 + /**
145 + * Asynchronously migrates the database structure.
146 + *
147 + * @param string $version_from_db The version from which to migrate.
148 + */
149 + public static function async_migration( $version_from_db ) {
150 + if ( version_compare( $version_from_db, '1.0.0', '<' ) ) {
151 + self::migrate_from_0_17();
197 152 }
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 - /*
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 - * Add new update routines above this comment. ^
229 - *
230 - * Use 'unreleased' as the version number for new migrations and add tests for the callback directly.
231 - * The release script will automatically replace it with the actual version number.
232 - * Example:
233 - *
234 - * if ( \version_compare( $version_from_db, 'unreleased', '<' ) ) {
235 - * // Update routine.
236 - * }
237 - */
238 -
239 - /**
240 - * Fires when the system has to be migrated.
241 - *
242 - * @param string $version_from_db The version from which to migrate.
243 - * @param string $target_version The target version to migrate to.
244 - */
245 - \do_action( 'activitypub_migrate', $version_from_db, ACTIVITYPUB_PLUGIN_VERSION );
246 -
247 - \update_option( 'activitypub_db_version', ACTIVITYPUB_PLUGIN_VERSION );
248 -
249 - self::unlock();
250 153 }
251 154
252 155 /**
253 156 * Updates the custom template to use shortcodes instead of the deprecated templates.
157 + *
158 + * @return void
254 159 */
255 160 private static function migrate_from_0_16() {
256 161 // Get the custom template.
257 162 $old_content = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
258 163
259 - /*
260 - * If the old content exists but is a blank string, we're going to need a flag to updated it even
261 - * after setting it to the default contents.
262 - */
164 + // If the old content exists but is a blank string, we're going to need a flag to updated it even
165 + // after setting it to the default contents.
263 166 $need_update = false;
264 167
265 168 // If the old contents is blank, use the defaults.
266 169 if ( '' === $old_content ) {
@@ -286,25 +189,31 @@
286 189 }
287 190 }
288 191
289 192 /**
290 - * Updates the DB-schema of the followers-list.
193 + * Updates the DB-schema of the followers-list
194 + *
195 + * @return void
291 196 */
292 197 public static function migrate_from_0_17() {
293 - // Migrate followers.
198 + // migrate followers
294 199 foreach ( get_users( array( 'fields' => 'ID' ) ) as $user_id ) {
295 200 $followers = get_user_meta( $user_id, 'activitypub_followers', true );
296 201
297 202 if ( $followers ) {
298 203 foreach ( $followers as $actor ) {
299 - Followers::add( $user_id, $actor );
204 + Followers::add_follower( $user_id, $actor );
300 205 }
301 206 }
302 207 }
208 +
209 + Activitypub::flush_rewrite_rules();
303 210 }
304 211
305 212 /**
306 - * Clear the cache after updating to 1.3.0.
213 + * Clear the cache after updating to 1.3.0
214 + *
215 + * @return void
307 216 */
308 217 private static function migrate_from_1_2_0() {
309 218 $user_ids = \get_users(
310 219 array(
@@ -318,9 +227,11 @@
318 227 }
319 228 }
320 229
321 230 /**
322 - * Unschedule Hooks after updating to 2.0.0.
231 + * Unschedule Hooks after updating to 2.0.0
232 + *
233 + * @return void
323 234 */
324 235 private static function migrate_from_2_0_0() {
325 236 wp_clear_scheduled_hook( 'activitypub_send_post_activity' );
326 237 wp_clear_scheduled_hook( 'activitypub_send_update_activity' );
@@ -337,17 +248,21 @@
337 248 }
338 249
339 250 /**
340 251 * Add the ActivityPub capability to all users that can publish posts
341 - * Delete old meta to store followers.
252 + * Delete old meta to store followers
253 + *
254 + * @return void
342 255 */
343 256 private static function migrate_from_2_2_0() {
344 - // Add the ActivityPub capability to all users that can publish posts.
257 + // add the ActivityPub capability to all users that can publish posts
345 258 self::add_activitypub_capability();
346 259 }
347 260
348 261 /**
349 - * Rename DB fields.
262 + * Rename DB fields
263 + *
264 + * @return void
350 265 */
351 266 private static function migrate_from_2_6_0() {
352 267 wp_cache_flush();
353 268
@@ -357,398 +272,25 @@
357 272 self::update_options_key( 'activitypub_blog_user_identifier', 'activitypub_blog_identifier' );
358 273 }
359 274
360 275 /**
361 - * * Update actor-mode settings.
362 - * * Get the ID of the latest blog post and save it to the options table.
363 - */
364 - private static function migrate_to_4_0_0() {
365 - $latest_post_id = 0;
366 -
367 - // Get the ID of the latest blog post and save it to the options table.
368 - $latest_post = get_posts(
369 - array(
370 - 'numberposts' => 1,
371 - 'orderby' => 'ID',
372 - 'order' => 'DESC',
373 - 'post_type' => 'any',
374 - 'post_status' => 'publish',
375 - )
376 - );
377 -
378 - if ( $latest_post ) {
379 - $latest_post_id = $latest_post[0]->ID;
380 - }
381 -
382 - \update_option( 'activitypub_last_post_with_permalink_as_id', $latest_post_id );
383 -
384 - $users = \get_users(
385 - array(
386 - 'capability__in' => array( 'activitypub' ),
387 - )
388 - );
389 -
390 - foreach ( $users as $user ) {
391 - $followers = Followers::get_many( $user->ID );
392 -
393 - if ( $followers ) {
394 - \update_user_option( $user->ID, 'activitypub_use_permalink_as_id', '1' );
395 - }
396 - }
397 -
398 - $followers = Followers::get_many( Actors::BLOG_USER_ID );
399 -
400 - if ( $followers ) {
401 - \update_option( 'activitypub_use_permalink_as_id_for_blog', '1' );
402 - }
403 -
404 - self::migrate_actor_mode();
405 - }
406 -
407 - /**
408 - * Update to 4.1.0
276 + * Set the defaults needed for the plugin to work
409 277 *
410 - * * Migrate the `activitypub_post_content_type` to only use `activitypub_custom_post_content`.
411 - */
412 - public static function migrate_to_4_1_0() {
413 - $content_type = \get_option( 'activitypub_post_content_type' );
414 -
415 - switch ( $content_type ) {
416 - case 'excerpt':
417 - $template = "[ap_excerpt]\n\n[ap_permalink type=\"html\"]";
418 - break;
419 - case 'title':
420 - $template = "[ap_title type=\"html\"]\n\n[ap_permalink type=\"html\"]";
421 - break;
422 - case 'content':
423 - $template = "[ap_content]\n\n[ap_permalink type=\"html\"]\n\n[ap_hashtags]";
424 - break;
425 - case 'custom':
426 - $template = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
427 - break;
428 - default:
429 - $template = ACTIVITYPUB_CUSTOM_POST_CONTENT;
430 - break;
431 - }
432 -
433 - \update_option( 'activitypub_custom_post_content', $template );
434 -
435 - \delete_option( 'activitypub_post_content_type' );
436 -
437 - $object_type = \get_option( 'activitypub_object_type', false );
438 - if ( ! $object_type ) {
439 - \update_option( 'activitypub_object_type', 'note' );
440 - }
441 -
442 - // Clean up empty visibility meta.
443 - global $wpdb;
444 - $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
445 - "DELETE FROM $wpdb->postmeta
446 - WHERE meta_key = 'activitypub_content_visibility'
447 - AND (meta_value IS NULL OR meta_value = '')"
448 - );
449 - }
450 -
451 - /**
452 - * Updates post meta keys to be prefixed with an underscore.
453 - */
454 - public static function migrate_to_4_7_1() {
455 - global $wpdb;
456 -
457 - $meta_keys = array(
458 - 'activitypub_actor_json',
459 - 'activitypub_canonical_url',
460 - 'activitypub_errors',
461 - 'activitypub_inbox',
462 - 'activitypub_user_id',
463 - );
464 -
465 - foreach ( $meta_keys as $meta_key ) {
466 - // phpcs:ignore WordPress.DB
467 - $wpdb->update( $wpdb->postmeta, array( 'meta_key' => '_' . $meta_key ), array( 'meta_key' => $meta_key ) );
468 - }
469 - }
470 -
471 - /**
472 - * Clears the post cache for Followers, we should have done this in 4.7.1 when we renamed those keys.
473 - */
474 - public static function migrate_to_4_7_2() {
475 - global $wpdb;
476 - // phpcs:ignore WordPress.DB
477 - $followers = $wpdb->get_col(
478 - $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s", Remote_Actors::POST_TYPE )
479 - );
480 - foreach ( $followers as $id ) {
481 - clean_post_cache( $id );
482 - }
483 - }
484 -
485 - /**
486 - * Update comment counts for posts in batches.
278 + * * Add the ActivityPub capability to all users that can publish posts
487 279 *
488 - * @see Comment::pre_wp_update_comment_count_now()
489 - * @param int $batch_size Optional. Number of posts to process per batch. Default 100.
490 - * @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.
280 + * @return void
493 281 */
494 - public static function update_comment_counts( $batch_size = 100, $offset = 0 ) {
495 - global $wpdb;
496 -
497 - Comment::register_comment_types();
498 - $comment_types = Comment::get_comment_type_slugs();
499 - $type_inclusion = "AND comment_type IN ('" . implode( "','", $comment_types ) . "')";
500 -
501 - // Get and process this batch.
502 - $post_ids = $wpdb->get_col( // phpcs:ignore WordPress.DB
503 - $wpdb->prepare(
504 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
505 - "SELECT DISTINCT comment_post_ID FROM {$wpdb->comments} WHERE comment_approved = '1' {$type_inclusion} ORDER BY comment_post_ID LIMIT %d OFFSET %d",
506 - $batch_size,
507 - $offset
508 - )
509 - );
510 -
511 - foreach ( $post_ids as $post_id ) {
512 - \wp_update_comment_count_now( $post_id );
513 - }
514 -
515 - if ( count( $post_ids ) === $batch_size ) {
516 - // Schedule next batch.
517 - return array( $batch_size, $offset + $batch_size );
518 - }
519 - }
520 -
521 - /**
522 - * Create outbox items for posts in batches.
523 - *
524 - * @param int $batch_size Optional. Number of posts to process per batch. Default 50.
525 - * @param int $offset Optional. Number of posts to skip. Default 0.
526 - * @return array|null Array with batch size and offset if there are more posts to process, null otherwise.
527 - */
528 - public static function create_post_outbox_items( $batch_size = 50, $offset = 0 ) {
529 - $posts = \get_posts(
530 - array(
531 - // our own `ap_outbox` will be excluded from `any` by virtue of its `exclude_from_search` arg.
532 - 'post_type' => 'any',
533 - 'posts_per_page' => $batch_size,
534 - 'offset' => $offset,
535 - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
536 - 'meta_query' => array(
537 - array(
538 - 'key' => 'activitypub_status',
539 - 'value' => ACTIVITYPUB_OBJECT_STATE_FEDERATED,
540 - ),
541 - ),
542 - )
543 - );
544 -
545 - // Avoid multiple queries for post meta.
546 - \update_postmeta_cache( \wp_list_pluck( $posts, 'ID' ) );
547 -
548 - foreach ( $posts as $post ) {
549 - $visibility = \get_post_meta( $post->ID, 'activitypub_content_visibility', true );
550 -
551 - self::add_to_outbox( $post, 'Create', $post->post_author, $visibility );
552 -
553 - // Add Update activity when the post has been modified.
554 - if ( $post->post_modified !== $post->post_date ) {
555 - self::add_to_outbox( $post, 'Update', $post->post_author, $visibility );
556 - }
557 - }
558 -
559 - if ( count( $posts ) === $batch_size ) {
560 - return array(
561 - 'batch_size' => $batch_size,
562 - 'offset' => $offset + $batch_size,
563 - );
564 - }
565 -
566 - return null;
567 - }
568 -
569 - /**
570 - * Create outbox items for comments in batches.
571 - *
572 - * @param int $batch_size Optional. Number of posts to process per batch. Default 50.
573 - * @param int $offset Optional. Number of posts to skip. Default 0.
574 - * @return array|null Array with batch size and offset if there are more posts to process, null otherwise.
575 - */
576 - public static function create_comment_outbox_items( $batch_size = 50, $offset = 0 ) {
577 - $comments = \get_comments(
578 - array(
579 - 'author__not_in' => array( 0 ), // Limit to comments by registered users.
580 - 'number' => $batch_size,
581 - 'offset' => $offset,
582 - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
583 - 'meta_query' => array(
584 - array(
585 - 'key' => 'activitypub_status',
586 - 'value' => ACTIVITYPUB_OBJECT_STATE_FEDERATED,
587 - ),
588 - ),
589 - )
590 - );
591 -
592 - foreach ( $comments as $comment ) {
593 - self::add_to_outbox( $comment, 'Create', $comment->user_id );
594 - }
595 -
596 - if ( count( $comments ) === $batch_size ) {
597 - return array(
598 - 'batch_size' => $batch_size,
599 - 'offset' => $offset + $batch_size,
600 - );
601 - }
602 -
603 - return null;
604 - }
605 -
606 - /**
607 - * Update _activitypub_actor_json meta values to ensure they are properly slashed.
608 - *
609 - * @param int $batch_size Optional. Number of meta values to process per batch. Default 100.
610 - * @param int $offset Optional. Number of meta values to skip. Default 0.
611 - * @return array|null Array with batch size and offset if there are more meta values to process, null otherwise.
612 - */
613 - public static function update_actor_json_slashing( $batch_size = 100, $offset = 0 ) {
614 - global $wpdb;
615 -
616 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery
617 - $meta_values = $wpdb->get_results(
618 - $wpdb->prepare(
619 - "SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = '_activitypub_actor_json' LIMIT %d OFFSET %d",
620 - $batch_size,
621 - $offset
622 - )
623 - );
624 -
625 - foreach ( $meta_values as $meta ) {
626 - $json = \json_decode( $meta->meta_value, true );
627 -
628 - // If json_decode fails, try adding slashes.
629 - if ( null === $json && \json_last_error() !== JSON_ERROR_NONE ) {
630 - $escaped_value = \preg_replace( '#\\\\(?!["\\\\/bfnrtu])#', '\\\\\\\\', $meta->meta_value );
631 - $json = \json_decode( $escaped_value, true );
632 -
633 - // Update the meta if json_decode succeeds with slashes.
634 - if ( null !== $json && \json_last_error() === JSON_ERROR_NONE ) {
635 - \update_post_meta( $meta->post_id, '_activitypub_actor_json', \wp_slash( $escaped_value ) );
636 - }
637 - }
638 - }
639 -
640 - if ( \count( $meta_values ) === $batch_size ) {
641 - return array(
642 - 'batch_size' => $batch_size,
643 - 'offset' => $offset + $batch_size,
644 - );
645 - }
646 -
647 - return null;
648 - }
649 -
650 - /**
651 - * Update comment author emails with webfinger addresses for ActivityPub comments.
652 - *
653 - * @param int $batch_size Optional. Number of comments to process per batch. Default 50.
654 - * @param int $offset Optional. Number of comments to skip. Default 0.
655 - * @return array|null Array with batch size and offset if there are more comments to process, null otherwise.
656 - */
657 - public static function update_comment_author_emails( $batch_size = 50, $offset = 0 ) {
658 - $comments = \get_comments(
659 - array(
660 - 'number' => $batch_size,
661 - 'offset' => $offset,
662 - 'orderby' => 'comment_ID',
663 - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
664 - 'meta_query' => array(
665 - array(
666 - 'key' => 'protocol',
667 - 'value' => 'activitypub',
668 - ),
669 - ),
670 - )
671 - );
672 -
673 - foreach ( $comments as $comment ) {
674 - $comment_author_url = $comment->comment_author_url;
675 - if ( empty( $comment_author_url ) ) {
676 - continue;
677 - }
678 -
679 - $webfinger = Webfinger::uri_to_acct( $comment_author_url );
680 - if ( \is_wp_error( $webfinger ) ) {
681 - continue;
682 - }
683 -
684 - \wp_update_comment(
685 - array(
686 - 'comment_ID' => $comment->comment_ID,
687 - 'comment_author_email' => \str_replace( 'acct:', '', $webfinger ),
688 - )
689 - );
690 - }
691 -
692 - if ( count( $comments ) === $batch_size ) {
693 - return array(
694 - 'batch_size' => $batch_size,
695 - 'offset' => $offset + $batch_size,
696 - );
697 - }
698 -
699 - return null;
700 - }
701 -
702 - /**
703 - * Set the defaults needed for the plugin to work.
704 - *
705 - * Add the ActivityPub capability to all users that can publish posts.
706 - */
707 282 public static function add_default_settings() {
708 283 self::add_activitypub_capability();
709 - self::add_default_extra_field();
710 284 }
711 285
712 286 /**
713 - * Add an activity to the outbox without federating it.
287 + * Add the ActivityPub capability to all users that can publish posts
714 288 *
715 - * @param \WP_Post|\WP_Comment $comment The comment or post object.
716 - * @param string $activity_type The type of activity.
717 - * @param int $user_id The user ID.
718 - * @param string $visibility Optional. The visibility of the content. Default 'public'.
289 + * @return void
719 290 */
720 - private static function add_to_outbox( $comment, $activity_type, $user_id, $visibility = ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC ) {
721 - $transformer = Factory::get_transformer( $comment );
722 - if ( ! $transformer || \is_wp_error( $transformer ) ) {
723 - return;
724 - }
725 -
726 - $activity = $transformer->to_activity( $activity_type );
727 - if ( ! $activity || \is_wp_error( $activity ) ) {
728 - return;
729 - }
730 -
731 - // If the user is disabled, fall back to the blog user when available.
732 - if ( ! user_can_activitypub( $user_id ) ) {
733 - if ( user_can_activitypub( Actors::BLOG_USER_ID ) ) {
734 - $user_id = Actors::BLOG_USER_ID;
735 - } else {
736 - return;
737 - }
738 - }
739 -
740 - $post_id = Outbox::add( $activity, $user_id, $visibility );
741 -
742 - // Immediately set to publish, no federation needed.
743 - \wp_publish_post( $post_id );
744 - }
745 -
746 - /**
747 - * Add the ActivityPub capability to all users that can publish posts.
748 - */
749 291 private static function add_activitypub_capability() {
750 - // Get all WP_User objects that can publish posts.
292 + // get all WP_User objects that can publish posts
751 293 $users = \get_users(
752 294 array(
753 295 'capability__in' => array( 'publish_posts' ),
754 296 )
@@ -753,9 +295,9 @@
753 295 'capability__in' => array( 'publish_posts' ),
754 296 )
755 297 );
756 298
757 - // Add ActivityPub capability to all users that can publish posts.
299 + // add ActivityPub capability to all users that can publish posts
758 300 foreach ( $users as $user ) {
759 301 $user->add_cap( 'activitypub' );
760 302 }
761 303 }
@@ -760,57 +302,20 @@
760 302 }
761 303 }
762 304
763 305 /**
764 - * Add a default extra field for the user.
765 - */
766 - private static function add_default_extra_field() {
767 - $users = \get_users(
768 - array(
769 - 'capability__in' => array( 'activitypub' ),
770 - )
771 - );
772 -
773 - $title = \__( 'Powered by', 'activitypub' );
774 - $content = 'WordPress';
775 -
776 - // Add a default extra field for each user.
777 - foreach ( $users as $user ) {
778 - \wp_insert_post(
779 - array(
780 - 'post_type' => Extra_Fields::USER_POST_TYPE,
781 - 'post_author' => $user->ID,
782 - 'post_status' => 'publish',
783 - 'post_title' => $title,
784 - 'post_content' => $content,
785 - )
786 - );
787 - }
788 -
789 - \wp_insert_post(
790 - array(
791 - 'post_type' => Extra_Fields::BLOG_POST_TYPE,
792 - 'post_author' => 0,
793 - 'post_status' => 'publish',
794 - 'post_title' => $title,
795 - 'post_content' => $content,
796 - )
797 - );
798 - }
799 -
800 - /**
801 - * Rename user meta keys.
306 + * Rename meta keys.
802 307 *
803 - * @param string $old_key The old comment meta key.
804 - * @param string $new_key The new comment meta key.
308 + * @param string $old The old commentmeta key
309 + * @param string $new The new commentmeta key
805 310 */
806 - private static function update_usermeta_key( $old_key, $new_key ) {
311 + private static function update_usermeta_key( $old, $new ) { // phpcs:ignore
807 312 global $wpdb;
808 313
809 - $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
314 + $wpdb->update( // phpcs:ignore
810 315 $wpdb->usermeta,
811 - array( 'meta_key' => $new_key ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
812 - array( 'meta_key' => $old_key ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
316 + array( 'meta_key' => $new ), // phpcs:ignore
317 + array( 'meta_key' => $old ), // phpcs:ignore
813 318 array( '%s' ),
814 319 array( '%s' )
815 320 );
816 321 }
@@ -815,400 +320,21 @@
815 320 );
816 321 }
817 322
818 323 /**
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 324 * Rename option keys.
838 325 *
839 - * @param string $old_key The old option key.
840 - * @param string $new_key The new option key.
326 + * @param string $old The old option key
327 + * @param string $new The new option key
841 328 */
842 - private static function update_options_key( $old_key, $new_key ) {
329 + private static function update_options_key( $old, $new ) { // phpcs:ignore
843 330 global $wpdb;
844 331
845 - $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
332 + $wpdb->update( // phpcs:ignore
846 333 $wpdb->options,
847 - array( 'option_name' => $new_key ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
848 - array( 'option_name' => $old_key ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
334 + array( 'option_name' => $new ), // phpcs:ignore
335 + array( 'option_name' => $old ), // phpcs:ignore
849 336 array( '%s' ),
850 337 array( '%s' )
851 338 );
852 - }
853 -
854 - /**
855 - * Migrate the actor mode settings.
856 - */
857 - public static function migrate_actor_mode() {
858 - $blog_profile = \get_option( 'activitypub_enable_blog_user', '0' );
859 - $author_profiles = \get_option( 'activitypub_enable_users', '1' );
860 -
861 - if (
862 - '1' === $blog_profile &&
863 - '1' === $author_profiles
864 - ) {
865 - \update_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_AND_BLOG_MODE );
866 - } elseif (
867 - '1' === $blog_profile &&
868 - '1' !== $author_profiles
869 - ) {
870 - \update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
871 - } elseif (
872 - '1' !== $blog_profile &&
873 - '1' === $author_profiles
874 - ) {
875 - \update_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE );
876 - }
877 - }
878 -
879 - /**
880 - * Deletes user extra fields where the author is the blog user.
881 - *
882 - * These extra fields were created when the Enable Mastodon Apps integration passed
883 - * an author_url instead of a user_id to the mastodon_api_account filter. This caused
884 - * Extra_Fields::default_actor_extra_fields() to run but fail to cache the fact it ran
885 - * for non-existent users. The result is a number of user extra fields with no author.
886 - *
887 - * @ticket https://github.com/Automattic/wordpress-activitypub/pull/1554
888 - */
889 - public static function delete_mastodon_api_orphaned_extra_fields() {
890 - global $wpdb;
891 -
892 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery
893 - $wpdb->delete(
894 - $wpdb->posts,
895 - array(
896 - 'post_type' => Extra_Fields::USER_POST_TYPE,
897 - 'post_author' => Actors::BLOG_USER_ID,
898 - )
899 - );
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 339 }
1214 340 }