From 799beddcb0a21402b7de892b5fa36c56fafaa157 Mon Sep 17 00:00:00 2001
From: Konstantin Obenland <obenland@gmx.de>
Date: Mon, 20 Jan 2025 14:35:17 -0600
Subject: [PATCH] Split upgrade routines for posts and comments

---
 includes/class-migration.php            | 82 ++++++++++++++++---------
 tests/includes/class-test-migration.php | 54 +++++++---------
 2 files changed, 74 insertions(+), 62 deletions(-)

diff --git a/includes/class-migration.php b/includes/class-migration.php
index 220eb296d..07ab053fb 100644
--- a/includes/class-migration.php
+++ b/includes/class-migration.php
@@ -23,8 +23,8 @@ class Migration {
 	 */
 	public static function init() {
 		\add_action( 'activitypub_migrate', array( self::class, 'async_migration' ) );
+		\add_action( 'activitypub_upgrade', array( self::class, 'async_upgrade' ) );
 		\add_action( 'activitypub_update_comment_counts', array( self::class, 'update_comment_counts' ), 10, 2 );
-		\add_action( 'activitypub_create_outbox_items', array( self::class, 'create_outbox_items' ), 10, 2 );
 
 		self::maybe_migrate();
 	}
@@ -171,7 +171,8 @@ public static function maybe_migrate() {
 			self::migrate_to_4_7_2();
 		}
 		if ( \version_compare( $version_from_db, 'unreleased', '<' ) ) {
-			\wp_schedule_single_event( \time() + MINUTE_IN_SECONDS, 'activitypub_create_outbox_items' );
+			\wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'create_post_outbox_items' ) );
+			\wp_schedule_single_event( \time() + 15, 'activitypub_upgrade', array( 'create_comment_outbox_items' ) );
 		}
 
 		/*
@@ -210,6 +211,31 @@ public static function async_migration( $version_from_db ) {
 		}
 	}
 
+	/**
+	 * Asynchronously runs upgrade routines.
+	 *
+	 * @param string $callback The callback to run.
+	 * @param mixed  ...$args  The arguments to pass to the callback.
+	 */
+	public static function async_upgrade( $callback, ...$args ) {
+		// Bail if the existing lock is still valid.
+		if ( self::is_locked() ) {
+			\wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'activitypub_upgrade', \array_merge( array( $callback ), $args ) );
+			return;
+		}
+
+		self::lock();
+
+		$next = \call_user_func_array( array( self::class, $callback ), $args );
+
+		if ( ! empty( $next ) ) {
+			// Schedule the next run, adding the result to the arguments.
+			\wp_schedule_single_event( \time() + 30, 'activitypub_upgrade', \array_merge( array( $callback ), $next ) );
+		}
+
+		self::unlock();
+	}
+
 	/**
 	 * Updates the custom template to use shortcodes instead of the deprecated templates.
 	 */
@@ -508,24 +534,9 @@ public static function update_comment_counts( $batch_size = 100, $offset = 0 ) {
 	 *
 	 * @param int $batch_size Optional. Number of posts to process per batch. Default 100.
 	 * @param int $offset     Optional. Number of posts to skip. Default 0.
+	 * @return array|null Array with batch size and offset if there are more posts to process, null otherwise.
 	 */
-	public static function create_outbox_items( $batch_size = 100, $offset = 0 ) {
-
-		// Bail if the existing lock is still valid.
-		if ( self::is_locked() ) {
-			\wp_schedule_single_event(
-				time() + ( 5 * MINUTE_IN_SECONDS ),
-				'activitypub_create_outbox_items',
-				array(
-					'batch_size' => $batch_size,
-					'offset'     => $offset,
-				)
-			);
-			return;
-		}
-
-		self::lock();
-
+	public static function create_post_outbox_items( $batch_size = 100, $offset = 0 ) {
 		$post_types = \get_option( 'activitypub_support_post_types', array( 'post' ) );
 		$posts      = \get_posts(
 			array(
@@ -562,6 +573,24 @@ public static function create_outbox_items( $batch_size = 100, $offset = 0 ) {
 			}
 		}
 
+		if ( count( $posts ) === $batch_size ) {
+			return array(
+				'batch_size' => $batch_size,
+				'offset'     => $offset + $batch_size,
+			);
+		}
+
+		return null;
+	}
+
+	/**
+	 * Create outbox items for comments in batches.
+	 *
+	 * @param int $batch_size Optional. Number of posts to process per batch. Default 100.
+	 * @param int $offset     Optional. Number of posts to skip. Default 0.
+	 * @return array|null Array with batch size and offset if there are more posts to process, null otherwise.
+	 */
+	public static function create_comment_outbox_items( $batch_size = 100, $offset = 0 ) {
 		$comments = \get_comments(
 			array(
 				'author__not_in' => array( 0 ), // Limit to comments by registered users.
@@ -574,19 +603,14 @@ public static function create_outbox_items( $batch_size = 100, $offset = 0 ) {
 			self::add_to_outbox( $comment, 'Create', $comment->user_id );
 		}
 
-		if ( count( $posts ) === $batch_size || count( $comments ) === $batch_size ) {
-			// Schedule next batch.
-			\wp_schedule_single_event(
-				time() + MINUTE_IN_SECONDS,
-				'activitypub_create_outbox_items',
-				array(
-					'batch_size' => $batch_size,
-					'offset'     => $offset + $batch_size,
-				)
+		if ( count( $comments ) === $batch_size ) {
+			return array(
+				'batch_size' => $batch_size,
+				'offset'     => $offset + $batch_size,
 			);
 		}
 
-		self::unlock();
+		return null;
 	}
 
 	/**
diff --git a/tests/includes/class-test-migration.php b/tests/includes/class-test-migration.php
index 3b2a14c39..daf41d56e 100644
--- a/tests/includes/class-test-migration.php
+++ b/tests/includes/class-test-migration.php
@@ -437,14 +437,14 @@ public function test_update_comment_counts_with_existing_valid_lock() {
 	}
 
 	/**
-	 * Test create outbox items.
+	 * Test create post outbox items.
 	 *
-	 * @covers ::create_outbox_items
+	 * @covers ::create_post_outbox_items
 	 */
 	public function test_create_outbox_items() {
 		// Run migration.
 		add_filter( 'pre_schedule_event', '__return_false' );
-		Migration::create_outbox_items( 10, 0 );
+		Migration::create_post_outbox_items( 10, 0 );
 		remove_filter( 'pre_schedule_event', '__return_false' );
 
 		// Get outbox items.
@@ -456,36 +456,26 @@ public function test_create_outbox_items() {
 			)
 		);
 
-		// Should now have 6 outbox items total, 4 post Create, 1 post Update, and 1 comment Create.
-		$this->assertEquals( 6, count( $outbox_items ) );
-
-		// Verify first post create activity.
-		$this->assertEquals( 'Create', \get_post_meta( $outbox_items[0]->ID, '_activitypub_activity_type', true ) );
-		$this->assertEquals( '', \get_post_meta( $outbox_items[0]->ID, 'activitypub_content_visibility', true ) );
-
-		// Verify second post update activity.
-		$this->assertEquals( 'Update', \get_post_meta( $outbox_items[1]->ID, '_activitypub_activity_type', true ) );
-		$this->assertEquals( '', \get_post_meta( $outbox_items[1]->ID, 'activitypub_content_visibility', true ) );
-
-		// Verify second post create activity.
-		$this->assertEquals( 'Create', \get_post_meta( $outbox_items[2]->ID, '_activitypub_activity_type', true ) );
-		$this->assertEquals( '', \get_post_meta( $outbox_items[2]->ID, 'activitypub_content_visibility', true ) );
-
-		// Verify comment create activity.
-		$this->assertEquals( 'Create', \get_post_meta( $outbox_items[3]->ID, '_activitypub_activity_type', true ) );
-		$this->assertEquals( '', \get_post_meta( $outbox_items[3]->ID, 'activitypub_content_visibility', true ) );
+		// Should now have 5 outbox items total, 4 post Create, 1 post Update.
+		$this->assertEquals( 5, count( $outbox_items ) );
 	}
 
 	/**
-	 * Test create outbox items with batching.
+	 * Test create post outbox items with batching.
 	 *
-	 * @covers ::create_outbox_items
+	 * @covers ::create_post_outbox_items
 	 */
 	public function test_create_outbox_items_batching() {
-		add_filter( 'pre_schedule_event', '__return_false' );
-
 		// Run migration with batch size of 2.
-		Migration::create_outbox_items( 2, 0 );
+		$next = Migration::create_post_outbox_items( 2, 0 );
+
+		$this->assertSame(
+			array(
+				'batch_size' => 2,
+				'offset'     => 2,
+			),
+			$next
+		);
 
 		// Get outbox items.
 		$outbox_items = \get_posts(
@@ -496,11 +486,11 @@ public function test_create_outbox_items_batching() {
 			)
 		);
 
-		// Should have 3 outbox items, 2 post Create and 1 comment Create.
-		$this->assertEquals( 3, count( $outbox_items ) );
+		// Should have 2 outbox items.
+		$this->assertEquals( 2, count( $outbox_items ) );
 
 		// Run migration with next batch.
-		Migration::create_outbox_items( 2, 2 );
+		Migration::create_post_outbox_items( 2, 2 );
 
 		// Get outbox items again.
 		$outbox_items = \get_posts(
@@ -511,9 +501,7 @@ public function test_create_outbox_items_batching() {
 			)
 		);
 
-		// Should now have 6 outbox items total, 4 post Create, 1 post Update, and 1 comment Create.
-		$this->assertEquals( 6, count( $outbox_items ) );
-
-		remove_filter( 'pre_schedule_event', '__return_false' );
+		// Should now have 5 outbox items total, 4 post Create, 1 post Update.
+		$this->assertEquals( 5, count( $outbox_items ) );
 	}
 }