From 4e95a1d48d5efa02942471d9ebcd676b8e4a4d48 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Tue, 4 Feb 2025 14:06:24 +0100 Subject: [PATCH 01/39] Make it "work" --- includes/class-dispatcher.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index 8a6628a39..2353305c9 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -290,13 +290,14 @@ public static function add_inboxes_of_replied_urls( $inboxes, $actor_id, $activi } /** - * Adds Blog Actor inboxes to Updates so the Blog User's followers are notified of edits. + * Default filter to add Inboxes of the Blog User in dual mode. * * @deprecated Unreleased Use {@see Followers::maybe_add_inboxes_of_blog_user} instead. * * @param array $inboxes The list of Inboxes. * @param int $actor_id The WordPress Actor-ID. * @param Activity $activity The ActivityPub Activity. + * @param int $actor_id The WordPress Actor ID. * * @return array The filtered Inboxes. */ From 72101182129f494d854d24ce969bee0cfe3272cd Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Wed, 5 Feb 2025 11:13:27 +0100 Subject: [PATCH 02/39] Updated approach --- includes/class-dispatcher.php | 1 - 1 file changed, 1 deletion(-) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index 2353305c9..e6b9fb914 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -297,7 +297,6 @@ public static function add_inboxes_of_replied_urls( $inboxes, $actor_id, $activi * @param array $inboxes The list of Inboxes. * @param int $actor_id The WordPress Actor-ID. * @param Activity $activity The ActivityPub Activity. - * @param int $actor_id The WordPress Actor ID. * * @return array The filtered Inboxes. */ From ae480a3191d7d500bd57260a7fc32d99f5cac358 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Thu, 6 Feb 2025 10:04:59 +0100 Subject: [PATCH 03/39] Account for in-progress batches when reprocessing --- includes/class-dispatcher.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index e6b9fb914..b59abbf92 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -34,6 +34,13 @@ class Dispatcher { */ public static $callback = array( self::class, 'send_to_followers' ); + /** + * Callback for the async batch processing. + * + * @var array + */ + public static $callback = array( self::class, 'send_to_followers' ); + /** * Initialize the class, registering WordPress hooks. */ From 8a3af5b5a4c7d87c5b99f80eccdd6d7d451dccae Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Thu, 6 Feb 2025 10:43:24 +0100 Subject: [PATCH 04/39] Fix tests --- tests/includes/class-test-dispatcher.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/includes/class-test-dispatcher.php b/tests/includes/class-test-dispatcher.php index 68b90c8b1..f31f0d5af 100644 --- a/tests/includes/class-test-dispatcher.php +++ b/tests/includes/class-test-dispatcher.php @@ -91,6 +91,14 @@ public function test_process_outbox() { $outbox_item = $this->get_latest_outbox_item( \add_query_arg( 'p', $post_id, \home_url( '/' ) ) ); + $type = \get_post_meta( $outbox_item->ID, '_activitypub_activity_type', true ); + $activity = new Activity(); + $activity->set_type( $type ); + $activity->set_id( $outbox_item->guid ); + // Pre-fill the Activity with data (for example cc and to). + $activity->set_object( \json_decode( $outbox_item->post_content, true ) ); + $activity->set_actor( Actors::get_by_id( $outbox_item->post_author )->get_id() ); + Dispatcher::process_outbox( $outbox_item->ID ); $this->assertNotFalse( From d588ce9466bbb6f65bce1181b7f9ad86b7070df1 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 6 Feb 2025 11:23:57 +0100 Subject: [PATCH 05/39] Add/sent to inboxes actions (#1278) * add some actions * fix phpdoc * Use "Unreleased" instead of version number * Add readme * Switch actions props @obenland * renamed action names * fix phpdoc * fix phpdoc * fix phpdoc * fix namings * fix phpcs issues --- CHANGELOG.md | 6 ++++++ includes/class-dispatcher.php | 7 ------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67b54ee49..c7e9a5494 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * No longer creates Outbox items when importing content/users. * Fix NodeInfo 2.0 URL to be HTTP instead of HTTPS. +## [Unreleased] + +### Added + +* Batch Outbox-Processing. + ## [5.0.0] - 2025-02-03 ### Changed diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index b59abbf92..e6b9fb914 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -34,13 +34,6 @@ class Dispatcher { */ public static $callback = array( self::class, 'send_to_followers' ); - /** - * Callback for the async batch processing. - * - * @var array - */ - public static $callback = array( self::class, 'send_to_followers' ); - /** * Initialize the class, registering WordPress hooks. */ From d3049a9c77d2bee5e70908a36727b0b774b3767b Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Thu, 6 Feb 2025 15:01:18 +0100 Subject: [PATCH 06/39] Publish post after processing interactees if they don't get sent to followers --- includes/class-dispatcher.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index e6b9fb914..59e157ae8 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -207,6 +207,10 @@ private static function send_to_interactees( $activity, $actor_id, $outbox_item */ \do_action( 'activitypub_sent_to_inbox', $result, $inbox, $json, $actor_id, $outbox_item->ID ); } + + if ( ! self::should_send_to_followers( $activity, $actor_id, $outbox_item ) ) { + \wp_publish_post( $outbox_item ); + } } /** From 478a1fcc6fe69fbda7f806e91ca830e9c02e9d23 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Thu, 6 Feb 2025 15:05:21 +0100 Subject: [PATCH 07/39] Revert unnecessary docs change --- includes/class-dispatcher.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index 59e157ae8..70895d7fd 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -294,7 +294,7 @@ public static function add_inboxes_of_replied_urls( $inboxes, $actor_id, $activi } /** - * Default filter to add Inboxes of the Blog User in dual mode. + * Adds Blog Actor inboxes to Updates so the Blog User's followers are notified of edits. * * @deprecated Unreleased Use {@see Followers::maybe_add_inboxes_of_blog_user} instead. * From e6a75cdb07e8d4e2456d078b6aac99d6744d30ca Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 6 Feb 2025 16:52:03 +0100 Subject: [PATCH 08/39] store object id as meta --- includes/collection/class-outbox.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index b0d762455..da0384686 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -48,6 +48,7 @@ public static function add( $activity_object, $activity_type, $user_id, $content 'meta_input' => array( '_activitypub_activity_type' => $activity_type, '_activitypub_activity_actor' => $actor_type, + '_activitypub_object_id' => $activity_object->get_id(), 'activitypub_content_visibility' => $content_visibility, ), ); From 1a0d39a87d0d612e7906bc188b9bad3ab8074ce2 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Fri, 7 Feb 2025 09:55:45 +0100 Subject: [PATCH 09/39] Revert object id meta We'll do that in a separate PR --- includes/collection/class-outbox.php | 1 - 1 file changed, 1 deletion(-) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index da0384686..b0d762455 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -48,7 +48,6 @@ public static function add( $activity_object, $activity_type, $user_id, $content 'meta_input' => array( '_activitypub_activity_type' => $activity_type, '_activitypub_activity_actor' => $actor_type, - '_activitypub_object_id' => $activity_object->get_id(), 'activitypub_content_visibility' => $content_visibility, ), ); From 628f0bfe867dfe46cccdc7e558b0f7a74d2b4f3d Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Fri, 7 Feb 2025 10:25:21 +0100 Subject: [PATCH 10/39] With meta now registered, it shouldn't need a fallback --- includes/class-dispatcher.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index 70895d7fd..15b1334da 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -98,7 +98,7 @@ public static function process_outbox( $id ) { $actor->get__id(), $outbox_item->ID, self::$batch_size, - \get_post_meta( $outbox_item->ID, '_activitypub_outbox_offset', true ) ?: 0, // phpcs:ignore + \get_post_meta( $outbox_item->ID, '_activitypub_outbox_offset', true ), ) ); } else { From 2beeb99f1990783440e15c384cd6003d16b485de Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Fri, 7 Feb 2025 10:21:47 +0100 Subject: [PATCH 11/39] Stream: Only surface errors in Outbox processing (#1240) * Stream: Only surface errors in Outbox processing Also adds support for comment and user types. * Add changelog * fix readme * update to new batch processing * fix phpcs * fix phpcs * re-use wordings from the rest controllers * fix phpcs * restructure the output to match the errors * revert latest changes * Fixed changelog --------- Co-authored-by: Matthias Pfefferle --- CHANGELOG.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7e9a5494..67b54ee49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,12 +40,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * No longer creates Outbox items when importing content/users. * Fix NodeInfo 2.0 URL to be HTTP instead of HTTPS. -## [Unreleased] - -### Added - -* Batch Outbox-Processing. - ## [5.0.0] - 2025-02-03 ### Changed From a7c036d03bb1dc6455d6a2ca69a127fdfc62e7e0 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Fri, 7 Feb 2025 10:29:04 +0100 Subject: [PATCH 12/39] Move publish_post to calling function Makes it a bit easier to see what's happening when reading the code. --- includes/class-dispatcher.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index 15b1334da..213b27789 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -207,10 +207,6 @@ private static function send_to_interactees( $activity, $actor_id, $outbox_item */ \do_action( 'activitypub_sent_to_inbox', $result, $inbox, $json, $actor_id, $outbox_item->ID ); } - - if ( ! self::should_send_to_followers( $activity, $actor_id, $outbox_item ) ) { - \wp_publish_post( $outbox_item ); - } } /** From 706c2460e936105a0420a06b7f95e1c7a7dff8f4 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Fri, 7 Feb 2025 10:51:10 +0100 Subject: [PATCH 13/39] Restore fallback for tests --- includes/class-dispatcher.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index 213b27789..8a6628a39 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -98,7 +98,7 @@ public static function process_outbox( $id ) { $actor->get__id(), $outbox_item->ID, self::$batch_size, - \get_post_meta( $outbox_item->ID, '_activitypub_outbox_offset', true ), + \get_post_meta( $outbox_item->ID, '_activitypub_outbox_offset', true ) ?: 0, // phpcs:ignore ) ); } else { From d889ed37104acb77ab8bc65bf8fb3eafe2d25bfb Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Fri, 7 Feb 2025 12:11:21 +0100 Subject: [PATCH 14/39] Outbox Batch: Only pass outbox id to jobs (#1285) * Outbox Batch: Only pass outbox id to jobs * Remove unnecessary imports --- tests/includes/class-test-dispatcher.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/includes/class-test-dispatcher.php b/tests/includes/class-test-dispatcher.php index f31f0d5af..68b90c8b1 100644 --- a/tests/includes/class-test-dispatcher.php +++ b/tests/includes/class-test-dispatcher.php @@ -91,14 +91,6 @@ public function test_process_outbox() { $outbox_item = $this->get_latest_outbox_item( \add_query_arg( 'p', $post_id, \home_url( '/' ) ) ); - $type = \get_post_meta( $outbox_item->ID, '_activitypub_activity_type', true ); - $activity = new Activity(); - $activity->set_type( $type ); - $activity->set_id( $outbox_item->guid ); - // Pre-fill the Activity with data (for example cc and to). - $activity->set_object( \json_decode( $outbox_item->post_content, true ) ); - $activity->set_actor( Actors::get_by_id( $outbox_item->post_author )->get_id() ); - Dispatcher::process_outbox( $outbox_item->ID ); $this->assertNotFalse( From e885e811563f3d2fdb9cff16709d42c22941ac29 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 7 Feb 2025 12:44:37 +0100 Subject: [PATCH 15/39] Improve Outbox Data This allows us to filter by ID and to better debug by title. --- includes/class-activitypub.php | 11 +++++++++++ includes/collection/class-outbox.php | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php index e15f5b949..a07d26fcc 100644 --- a/includes/class-activitypub.php +++ b/includes/class-activitypub.php @@ -634,6 +634,17 @@ private static function register_post_types() { ) ); + \register_post_meta( + Outbox::POST_TYPE, + '_activitypub_object_id', + array( + 'type' => 'string', + 'single' => true, + 'description' => 'The ID (ActivityPub URI) of the object that the outbox item is about.', + 'sanitize_callback' => 'sanitize_url', + ) + ); + \register_post_meta( Outbox::POST_TYPE, 'activitypub_content_visibility', diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index b0d762455..aa66cde7f 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -40,12 +40,19 @@ public static function add( $activity_object, $activity_type, $user_id, $content $outbox_item = array( 'post_type' => self::POST_TYPE, - 'post_title' => $activity_object->get_id(), + 'post_title' => sprintf( + /* translators: 1. Activity type, 2. Object type, 3. Object Title or Excerpt */ + __( '[%1$s] %2$s: %3$s', 'activitypub' ), + $activity_type, + $activity_object->get_type(), + \wp_trim_words( $activity_object->get_name() ?? $activity_object->get_content(), 5 ) + ), 'post_content' => wp_slash( $activity_object->to_json() ), // ensure that user ID is not below 0. 'post_author' => \max( $user_id, 0 ), 'post_status' => 'pending', 'meta_input' => array( + '_activitypub_object_id' => $activity_object->get_id(), '_activitypub_activity_type' => $activity_type, '_activitypub_activity_actor' => $actor_type, 'activitypub_content_visibility' => $content_visibility, From 519c3f1a8fee3d7c71e645321819f39ca2c60734 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 7 Feb 2025 14:10:17 +0100 Subject: [PATCH 16/39] Fix tests --- tests/includes/scheduler/class-test-actor.php | 15 ++++++++++----- tests/includes/scheduler/class-test-comment.php | 6 ++++-- tests/includes/scheduler/class-test-post.php | 3 ++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/includes/scheduler/class-test-actor.php b/tests/includes/scheduler/class-test-actor.php index 599e9c45c..25dec9011 100644 --- a/tests/includes/scheduler/class-test-actor.php +++ b/tests/includes/scheduler/class-test-actor.php @@ -67,7 +67,8 @@ public function test_user_meta_update( $meta_key ) { $activitpub_id = Actors::get_by_id( self::$user_id )->get_id(); $post = $this->get_latest_outbox_item( $activitpub_id ); - $this->assertSame( $activitpub_id, $post->post_title ); + $id = \get_post_meta( $post->ID, '_activitypub_object_id', true ); + $this->assertSame( $activitpub_id, $id ); } /** @@ -80,7 +81,8 @@ public function test_user_update() { $activitpub_id = Actors::get_by_id( self::$user_id )->get_id(); $post = $this->get_latest_outbox_item( $activitpub_id ); - $this->assertSame( $activitpub_id, $post->post_title ); + $id = \get_post_meta( $post->ID, '_activitypub_object_id', true ); + $this->assertSame( $activitpub_id, $id ); } /** @@ -95,7 +97,8 @@ public function test_blog_user_update() { $activitpub_id = Actors::get_by_id( Actors::BLOG_USER_ID )->get_id(); $post = $this->get_latest_outbox_item( $activitpub_id ); - $this->assertSame( $activitpub_id, $post->post_title ); + $id = \get_post_meta( $post->ID, '_activitypub_object_id', true ); + $this->assertSame( $activitpub_id, $id ); $this->assertSame( $test_value, $result ); } @@ -229,7 +232,8 @@ public function test_schedule_post_activity_extra_fields() { $activitpub_id = Actors::get_by_id( self::$user_id )->get_id(); $post = $this->get_latest_outbox_item( $activitpub_id ); - $this->assertSame( $activitpub_id, $post->post_title ); + $id = \get_post_meta( $post->ID, '_activitypub_object_id', true ); + $this->assertSame( $activitpub_id, $id ); \wp_delete_post( $post_id, true ); } @@ -245,7 +249,8 @@ public function test_schedule_post_activity_extra_field_blog() { $activitpub_id = Actors::get_by_id( Actors::BLOG_USER_ID )->get_id(); $post = $this->get_latest_outbox_item( $activitpub_id ); - $this->assertSame( $activitpub_id, $post->post_title ); + $id = \get_post_meta( $post->ID, '_activitypub_object_id', true ); + $this->assertSame( $activitpub_id, $id ); // Clean up. \wp_delete_post( $blog_post_id, true ); diff --git a/tests/includes/scheduler/class-test-comment.php b/tests/includes/scheduler/class-test-comment.php index 28cfa82ad..b65bebca4 100644 --- a/tests/includes/scheduler/class-test-comment.php +++ b/tests/includes/scheduler/class-test-comment.php @@ -46,7 +46,8 @@ public function test_schedule_comment_activity_on_approval() { wp_set_comment_status( $comment_id, 'approve' ); $post = $this->get_latest_outbox_item( $activitpub_id ); - $this->assertSame( $activitpub_id, $post->post_title ); + $id = \get_post_meta( $post->ID, '_activitypub_object_id', true ); + $this->assertSame( $activitpub_id, $id ); wp_delete_comment( $comment_id, true ); } @@ -65,7 +66,8 @@ public function test_schedule_comment_activity_on_insert() { $activitpub_id = \Activitypub\Comment::generate_id( $comment_id ); $post = $this->get_latest_outbox_item( $activitpub_id ); - $this->assertSame( $activitpub_id, $post->post_title ); + $id = \get_post_meta( $post->ID, '_activitypub_object_id', true ); + $this->assertSame( $activitpub_id, $id ); wp_delete_comment( $comment_id, true ); } diff --git a/tests/includes/scheduler/class-test-post.php b/tests/includes/scheduler/class-test-post.php index 082bfb50e..ed8e2fcd8 100644 --- a/tests/includes/scheduler/class-test-post.php +++ b/tests/includes/scheduler/class-test-post.php @@ -56,7 +56,8 @@ public function test_schedule_post_activity_regular_post() { $activitpub_id = \add_query_arg( 'p', $post_id, \home_url( '/' ) ); $post = $this->get_latest_outbox_item( $activitpub_id ); - $this->assertSame( $activitpub_id, $post->post_title ); + $id = \get_post_meta( $post->ID, '_activitypub_object_id', true ); + $this->assertSame( $activitpub_id, $id ); \wp_delete_post( $post_id, true ); } From 90187c84c719d0b652bfd989d7ef9eb75bd3de66 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 7 Feb 2025 14:40:58 +0100 Subject: [PATCH 17/39] invalidate old unprocessed items --- includes/collection/class-outbox.php | 38 +++++++++++++ tests/includes/class-test-scheduler.php | 14 +++-- .../includes/collection/class-test-outbox.php | 57 +++++++++++++++++++ 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index aa66cde7f..e249cdc64 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -79,6 +79,44 @@ public static function add( $activity_object, $activity_type, $user_id, $content return false; } + self::invalidate_existing_items( $activity_object->get_id(), $activity_type, $id ); + return $id; } + + /** + * Invalidate existing outbox items with the same activity type and object ID + * by setting their status to 'publish'. + * + * @param string $object_id The ID of the activity object. + * @param string $activity_type The type of the activity. + * @param int $current_id The ID of the current outbox item to exclude. + * + * @return void + */ + private static function invalidate_existing_items( $object_id, $activity_type, $current_id ) { + $existing_items = get_posts(array( + 'post_type' => self::POST_TYPE, + 'post_status' => 'pending', + 'exclude' => array( $current_id ), + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => '_activitypub_object_id', + 'value' => $object_id, + ), + array( + 'key' => '_activitypub_activity_type', + 'value' => $activity_type, + ) + ), + 'fields' => 'ids', + )); + + if ($existing_items) { + foreach ($existing_items as $existing_item_id) { + \wp_publish_post( $existing_item_id ); + } + } + } } diff --git a/tests/includes/class-test-scheduler.php b/tests/includes/class-test-scheduler.php index 0a423dc38..7bc023737 100644 --- a/tests/includes/class-test-scheduler.php +++ b/tests/includes/class-test-scheduler.php @@ -68,6 +68,13 @@ public function test_reprocess_outbox() { ); } + $pending_ids[] = Outbox::add( + $activity_object, + 'Update', + self::$user_id, + ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC + ); + // Track scheduled events. $scheduled_events = array(); add_filter( @@ -84,10 +91,9 @@ function ( $event ) use ( &$scheduled_events ) { Scheduler::reprocess_outbox(); // Verify each pending activity was scheduled. - $this->assertCount( 3, $scheduled_events, 'Should schedule 3 activities for processing' ); - foreach ( $pending_ids as $id ) { - $this->assertContains( $id, $scheduled_events, "Activity $id should be scheduled" ); - } + $this->assertCount( 2, $scheduled_events, 'Should schedule 2 activities for processing' ); + $this->assertNotContains( $pending_ids[0], $scheduled_events, "Activity $pending_ids[0] should be scheduled" ); + $this->assertContains( $pending_ids[3], $scheduled_events, "Activity $pending_ids[3] should be scheduled" ); // Test with published activities (should not be scheduled). $published_id = Outbox::add( diff --git a/tests/includes/collection/class-test-outbox.php b/tests/includes/collection/class-test-outbox.php index 36fa1c537..e7d9bfffe 100644 --- a/tests/includes/collection/class-test-outbox.php +++ b/tests/includes/collection/class-test-outbox.php @@ -118,4 +118,61 @@ public function author_object_provider() { array( ACTIVITYPUB_ACTOR_MODE, 90210, false ), ); } + + /** + * Test invalidating existing outbox items. + */ + public function test_invalidate_existing_items() { + $object = $this->get_dummy_activity_object(); + $activity_type = 'Create'; + + // Create first outbox item + $first_id = \Activitypub\add_to_outbox( $object, $activity_type, 1 ); + $this->assertNotFalse( $first_id ); + $this->assertEquals( 'pending', get_post_status( $first_id ) ); + + // Create second outbox item with same object_id and activity_type + $second_id = \Activitypub\add_to_outbox( $object, $activity_type, 1 ); + $this->assertNotFalse( $second_id ); + + // First item should now be published (invalidated) + $this->assertEquals( 'publish', get_post_status( $first_id ) ); + // New item should still be pending + $this->assertEquals( 'pending', get_post_status( $second_id ) ); + } + + /** + * Test that only items with matching object_id and activity_type are invalidated. + */ + public function test_selective_invalidation() { + $object1 = $this->get_dummy_activity_object(); + $object2 = $this->get_dummy_activity_object(); + $object2->set_id( 'https://example.com/different-object' ); + + // Create items with different combinations + $item1 = \Activitypub\add_to_outbox( $object1, 'Create', 1 ); // Should be invalidated + $item2 = \Activitypub\add_to_outbox( $object2, 'Create', 1 ); // Should stay pending (different object) + $item3 = \Activitypub\add_to_outbox( $object1, 'Update', 1 ); // Should stay pending (different activity) + + // Add new item that should trigger invalidation of item1 + $new_item = \Activitypub\add_to_outbox( $object1, 'Create', 1 ); + + $this->assertEquals( 'publish', get_post_status( $item1 ) ); + $this->assertEquals( 'pending', get_post_status( $item2 ) ); + $this->assertEquals( 'pending', get_post_status( $item3 ) ); + $this->assertEquals( 'pending', get_post_status( $new_item ) ); + } + + /** + * Helper method to create a dummy activity object for testing. + * + * @return \Activitypub\Activity\Base_Object + */ + private function get_dummy_activity_object() { + $object = new \Activitypub\Activity\Base_Object(); + $object->set_id( 'https://example.com/test-object' ); + $object->set_type( 'Note' ); + $object->set_content( 'Test content' ); + return $object; + } } From 5e7a48fd47abd862c680275357a71515e26970b4 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 7 Feb 2025 14:56:01 +0100 Subject: [PATCH 18/39] ignore type on `Delete` --- includes/collection/class-outbox.php | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index e249cdc64..c8309e490 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -95,21 +95,26 @@ public static function add( $activity_object, $activity_type, $user_id, $content * @return void */ private static function invalidate_existing_items( $object_id, $activity_type, $current_id ) { + $meta_query = array( + array( + 'key' => '_activitypub_object_id', + 'value' => $object_id, + ), + ); + + // For non-Delete activities, only invalidate items of the same type + if ( 'Delete' !== $activity_type ) { + $meta_query[] = array( + 'key' => '_activitypub_activity_type', + 'value' => $activity_type, + ); + } + $existing_items = get_posts(array( 'post_type' => self::POST_TYPE, 'post_status' => 'pending', 'exclude' => array( $current_id ), - 'meta_query' => array( - 'relation' => 'AND', - array( - 'key' => '_activitypub_object_id', - 'value' => $object_id, - ), - array( - 'key' => '_activitypub_activity_type', - 'value' => $activity_type, - ) - ), + 'meta_query' => $meta_query, 'fields' => 'ids', )); From e4727ce545da573e65429f2bb529828c50e33725 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 7 Feb 2025 14:58:49 +0100 Subject: [PATCH 19/39] Fix phpcs --- includes/collection/class-outbox.php | 27 ++++++----- .../includes/collection/class-test-outbox.php | 46 +++++++++++++++---- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index c8309e490..d00b702c2 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -97,29 +97,32 @@ public static function add( $activity_object, $activity_type, $user_id, $content private static function invalidate_existing_items( $object_id, $activity_type, $current_id ) { $meta_query = array( array( - 'key' => '_activitypub_object_id', + 'key' => '_activitypub_object_id', 'value' => $object_id, ), ); - // For non-Delete activities, only invalidate items of the same type + // For non-Delete activities, only invalidate items of the same type. if ( 'Delete' !== $activity_type ) { $meta_query[] = array( - 'key' => '_activitypub_activity_type', + 'key' => '_activitypub_activity_type', 'value' => $activity_type, ); } - $existing_items = get_posts(array( - 'post_type' => self::POST_TYPE, - 'post_status' => 'pending', - 'exclude' => array( $current_id ), - 'meta_query' => $meta_query, - 'fields' => 'ids', - )); + $existing_items = get_posts( + array( + 'post_type' => self::POST_TYPE, + 'post_status' => 'pending', + 'exclude' => array( $current_id ), + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query + 'meta_query' => $meta_query, + 'fields' => 'ids', + ) + ); - if ($existing_items) { - foreach ($existing_items as $existing_item_id) { + if ( $existing_items ) { + foreach ( $existing_items as $existing_item_id ) { \wp_publish_post( $existing_item_id ); } } diff --git a/tests/includes/collection/class-test-outbox.php b/tests/includes/collection/class-test-outbox.php index e7d9bfffe..2cf1ec564 100644 --- a/tests/includes/collection/class-test-outbox.php +++ b/tests/includes/collection/class-test-outbox.php @@ -123,21 +123,21 @@ public function author_object_provider() { * Test invalidating existing outbox items. */ public function test_invalidate_existing_items() { - $object = $this->get_dummy_activity_object(); + $object = $this->get_dummy_activity_object(); $activity_type = 'Create'; - // Create first outbox item + // Create first outbox item. $first_id = \Activitypub\add_to_outbox( $object, $activity_type, 1 ); $this->assertNotFalse( $first_id ); $this->assertEquals( 'pending', get_post_status( $first_id ) ); - // Create second outbox item with same object_id and activity_type + // Create second outbox item with same object_id and activity_type. $second_id = \Activitypub\add_to_outbox( $object, $activity_type, 1 ); $this->assertNotFalse( $second_id ); - // First item should now be published (invalidated) + // First item should now be published (invalidated). $this->assertEquals( 'publish', get_post_status( $first_id ) ); - // New item should still be pending + // New item should still be pending. $this->assertEquals( 'pending', get_post_status( $second_id ) ); } @@ -149,12 +149,12 @@ public function test_selective_invalidation() { $object2 = $this->get_dummy_activity_object(); $object2->set_id( 'https://example.com/different-object' ); - // Create items with different combinations - $item1 = \Activitypub\add_to_outbox( $object1, 'Create', 1 ); // Should be invalidated - $item2 = \Activitypub\add_to_outbox( $object2, 'Create', 1 ); // Should stay pending (different object) - $item3 = \Activitypub\add_to_outbox( $object1, 'Update', 1 ); // Should stay pending (different activity) + // Create items with different combinations. + $item1 = \Activitypub\add_to_outbox( $object1, 'Create', 1 ); // Should be invalidated. + $item2 = \Activitypub\add_to_outbox( $object2, 'Create', 1 ); // Should stay pending (different object). + $item3 = \Activitypub\add_to_outbox( $object1, 'Update', 1 ); // Should stay pending (different activity). - // Add new item that should trigger invalidation of item1 + // Add new item that should trigger invalidation of item1. $new_item = \Activitypub\add_to_outbox( $object1, 'Create', 1 ); $this->assertEquals( 'publish', get_post_status( $item1 ) ); @@ -163,6 +163,32 @@ public function test_selective_invalidation() { $this->assertEquals( 'pending', get_post_status( $new_item ) ); } + /** + * Test that Delete activities invalidate all existing items for the object. + */ + public function test_delete_invalidates_all_activities() { + $object = $this->get_dummy_activity_object(); + + // Create items with different activity types. + $create_id = \Activitypub\add_to_outbox( $object, 'Create', 1 ); + $update_id = \Activitypub\add_to_outbox( $object, 'Update', 1 ); + $like_id = \Activitypub\add_to_outbox( $object, 'Like', 1 ); + + $this->assertEquals( 'pending', get_post_status( $create_id ) ); + $this->assertEquals( 'pending', get_post_status( $update_id ) ); + $this->assertEquals( 'pending', get_post_status( $like_id ) ); + + // Add Delete activity. + $delete_id = \Activitypub\add_to_outbox( $object, 'Delete', 1 ); + + // All previous activities should be published (invalidated). + $this->assertEquals( 'publish', get_post_status( $create_id ) ); + $this->assertEquals( 'publish', get_post_status( $update_id ) ); + $this->assertEquals( 'publish', get_post_status( $like_id ) ); + // Delete activity should still be pending. + $this->assertEquals( 'pending', get_post_status( $delete_id ) ); + } + /** * Helper method to create a dummy activity object for testing. * From 39a3a1ca7c6e9efe00a80c5eeba34ef1ea1508a1 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 7 Feb 2025 15:37:06 +0100 Subject: [PATCH 20/39] delete offset --- includes/class-dispatcher.php | 1 + includes/collection/class-outbox.php | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index 8a6628a39..b9fb198cd 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -104,6 +104,7 @@ public static function process_outbox( $id ) { } else { // No followers to process for this update. We're done. \wp_publish_post( $outbox_item ); + \delete_post_meta( $outbox_item->ID, '_activitypub_outbox_offset' ); } } diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index d00b702c2..e8332e4c0 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -121,10 +121,9 @@ private static function invalidate_existing_items( $object_id, $activity_type, $ ) ); - if ( $existing_items ) { - foreach ( $existing_items as $existing_item_id ) { - \wp_publish_post( $existing_item_id ); - } + foreach ( $existing_items as $existing_item_id ) { + \wp_publish_post( $existing_item_id ); + \delete_post_meta( $existing_item_id, '_activitypub_outbox_offset' ); } } } From 7976eec3f549d6d1c3d241320bfeeda486bc488b Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 7 Feb 2025 16:25:25 +0100 Subject: [PATCH 21/39] remove actor id from the schedule --- includes/class-dispatcher.php | 17 ++++++++--------- tests/includes/class-test-dispatcher.php | 1 - 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index b9fb198cd..d9013976e 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -95,7 +95,6 @@ public static function process_outbox( $id ) { 'activitypub_async_batch', array( self::$callback, - $actor->get__id(), $outbox_item->ID, self::$batch_size, \get_post_meta( $outbox_item->ID, '_activitypub_outbox_offset', true ) ?: 0, // phpcs:ignore @@ -111,20 +110,20 @@ public static function process_outbox( $id ) { /** * Asynchronously runs batch processing routines. * - * @param int $actor_id The actor ID. * @param int $outbox_item_id The Outbox item ID. * @param int $batch_size Optional. The batch size. Default 50. * @param int $offset Optional. The offset. Default 0. * * @return array|void The next batch of followers to process, or void if done. */ - public static function send_to_followers( $actor_id, $outbox_item_id, $batch_size = 50, $offset = 0 ) { + public static function send_to_followers( $outbox_item_id, $batch_size = 50, $offset = 0 ) { $activity = self::get_activity( $outbox_item_id ); + $actor = self::get_actor( \get_post( $outbox_item_id ) ); $json = $activity->to_json(); - $inboxes = Followers::get_inboxes_for_activity( $json, $actor_id, $batch_size, $offset ); + $inboxes = Followers::get_inboxes_for_activity( $json, $actor->get__id(), $batch_size, $offset ); foreach ( $inboxes as $inbox ) { - $result = safe_remote_post( $inbox, $json, $actor_id ); + $result = safe_remote_post( $inbox, $json, $actor->get__id() ); /** * Fires after an Activity has been sent to an inbox. @@ -135,7 +134,7 @@ public static function send_to_followers( $actor_id, $outbox_item_id, $batch_siz * @param int $actor_id The actor ID. * @param int $outbox_item_id The Outbox item ID. */ - \do_action( 'activitypub_sent_to_inbox', $result, $inbox, $json, $actor_id, $outbox_item_id ); + \do_action( 'activitypub_sent_to_inbox', $result, $inbox, $json, $actor->get__id(), $outbox_item_id ); } if ( is_countable( $inboxes ) && count( $inboxes ) < self::$batch_size ) { @@ -151,7 +150,7 @@ public static function send_to_followers( $actor_id, $outbox_item_id, $batch_siz * @param int $batch_size The batch size. * @param int $offset The offset. */ - \do_action( 'activitypub_outbox_processing_complete', $inboxes, $json, $actor_id, $outbox_item_id, $batch_size, $offset ); + \do_action( 'activitypub_outbox_processing_complete', $inboxes, $json, $actor->get__id(), $outbox_item_id, $batch_size, $offset ); // No more followers to process for this update. \wp_publish_post( $outbox_item_id ); @@ -168,9 +167,9 @@ public static function send_to_followers( $actor_id, $outbox_item_id, $batch_siz * @param int $batch_size The batch size. * @param int $offset The offset. */ - \do_action( 'activitypub_outbox_processing_batch_complete', $inboxes, $json, $actor_id, $outbox_item_id, $batch_size, $offset ); + \do_action( 'activitypub_outbox_processing_batch_complete', $inboxes, $json, $actor->get__id(), $outbox_item_id, $batch_size, $offset ); - return array( $actor_id, $outbox_item_id, $batch_size, $offset + $batch_size ); + return array( $outbox_item_id, $batch_size, $offset + $batch_size ); } } diff --git a/tests/includes/class-test-dispatcher.php b/tests/includes/class-test-dispatcher.php index 68b90c8b1..cf48b1c9b 100644 --- a/tests/includes/class-test-dispatcher.php +++ b/tests/includes/class-test-dispatcher.php @@ -98,7 +98,6 @@ public function test_process_outbox() { 'activitypub_async_batch', array( Dispatcher::$callback, - self::$user_id, $outbox_item->ID, Dispatcher::$batch_size, 0, From 1a99f44f6958ee7479cb746b953ad80c5cb2ce66 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 11 Feb 2025 15:07:59 +0100 Subject: [PATCH 22/39] Update includes/collection/class-outbox.php Co-authored-by: Konstantin Obenland --- includes/collection/class-outbox.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index e8332e4c0..b7f3d18d5 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -122,6 +122,14 @@ private static function invalidate_existing_items( $object_id, $activity_type, $ ); foreach ( $existing_items as $existing_item_id ) { + $event_args = array( + Dispatcher::$callback, + $existing_item_id, + Dispatcher::$batch_size, + \get_post_meta( $outbox_item->ID, '_activitypub_outbox_offset', true ) ?: 0, // phpcs:ignore + ); + $timestamp = wp_next_scheduled( 'activitypub_async_batch', $event_args ); + wp_unschedule_event( $timestamp, 'activitypub_async_batch', $event_args ); \wp_publish_post( $existing_item_id ); \delete_post_meta( $existing_item_id, '_activitypub_outbox_offset' ); } From 53bf9c3aeec150de0c0acb015fe776084544fe4d Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 11 Feb 2025 15:10:24 +0100 Subject: [PATCH 23/39] small phpcs changes --- includes/class-dispatcher.php | 1 + includes/collection/class-outbox.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index d9013976e..853fed415 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -373,6 +373,7 @@ private static function get_activity( $outbox_item ) { * Get the Actor object from the Outbox item. * * @param \WP_Post $outbox_item The Outbox post. + * * @return \Activitypub\Model\User|\Activitypub\Model\Blog|\WP_Error The Actor object or WP_Error. */ private static function get_actor( $outbox_item ) { diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index b7f3d18d5..ec95482ba 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -128,7 +128,7 @@ private static function invalidate_existing_items( $object_id, $activity_type, $ Dispatcher::$batch_size, \get_post_meta( $outbox_item->ID, '_activitypub_outbox_offset', true ) ?: 0, // phpcs:ignore ); - $timestamp = wp_next_scheduled( 'activitypub_async_batch', $event_args ); + $timestamp = wp_next_scheduled( 'activitypub_async_batch', $event_args ); wp_unschedule_event( $timestamp, 'activitypub_async_batch', $event_args ); \wp_publish_post( $existing_item_id ); \delete_post_meta( $existing_item_id, '_activitypub_outbox_offset' ); From 5cfcc2b5c383912398f4ab2c027bbbeb29399d99 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Tue, 11 Feb 2025 08:11:00 -0600 Subject: [PATCH 24/39] Update includes/collection/class-outbox.php --- includes/collection/class-outbox.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index ec95482ba..9e88f136a 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -130,6 +130,7 @@ private static function invalidate_existing_items( $object_id, $activity_type, $ ); $timestamp = wp_next_scheduled( 'activitypub_async_batch', $event_args ); wp_unschedule_event( $timestamp, 'activitypub_async_batch', $event_args ); + \wp_publish_post( $existing_item_id ); \delete_post_meta( $existing_item_id, '_activitypub_outbox_offset' ); } From a3521026b482d65f39b32c3250e43324eb7cfd5e Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 11 Feb 2025 15:14:42 +0100 Subject: [PATCH 25/39] Add missing dispatcher class --- includes/collection/class-outbox.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index 9e88f136a..e726d6024 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -7,6 +7,8 @@ namespace Activitypub\Collection; +use Activitypub\Dispatcher; + /** * ActivityPub Outbox Collection * From 117eae62bb1522b58c58b6e779635e3ba1a88ce9 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 11 Feb 2025 15:20:13 +0100 Subject: [PATCH 26/39] Fix C&P issues --- includes/collection/class-outbox.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index e726d6024..433669e23 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -128,7 +128,7 @@ private static function invalidate_existing_items( $object_id, $activity_type, $ Dispatcher::$callback, $existing_item_id, Dispatcher::$batch_size, - \get_post_meta( $outbox_item->ID, '_activitypub_outbox_offset', true ) ?: 0, // phpcs:ignore + \get_post_meta( $current_id, '_activitypub_outbox_offset', true ) ?: 0, // phpcs:ignore ); $timestamp = wp_next_scheduled( 'activitypub_async_batch', $event_args ); wp_unschedule_event( $timestamp, 'activitypub_async_batch', $event_args ); From b8fa40fd3ab6a5ea54f702922ed4f9cb1a5cbeee Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Tue, 11 Feb 2025 14:15:49 -0600 Subject: [PATCH 27/39] Get offset for existing item --- includes/collection/class-outbox.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index 433669e23..f914b58f6 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -128,7 +128,7 @@ private static function invalidate_existing_items( $object_id, $activity_type, $ Dispatcher::$callback, $existing_item_id, Dispatcher::$batch_size, - \get_post_meta( $current_id, '_activitypub_outbox_offset', true ) ?: 0, // phpcs:ignore + \get_post_meta( $existing_item_id, '_activitypub_outbox_offset', true ) ?: 0, // phpcs:ignore ); $timestamp = wp_next_scheduled( 'activitypub_async_batch', $event_args ); wp_unschedule_event( $timestamp, 'activitypub_async_batch', $event_args ); From c4130edb47d30b319dfd82dda1746d9c2990ab17 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Tue, 11 Feb 2025 14:45:12 -0600 Subject: [PATCH 28/39] Populate title for reposts --- includes/collection/class-outbox.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index f914b58f6..f6feebc8e 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -40,6 +40,11 @@ public static function add( $activity_object, $activity_type, $user_id, $content break; } + $title = $activity_object->get_name() ?? $activity_object->get_content(); + if ( empty( $title ) && $activity_object->get_object() ) { + $title = $activity_object->get_object()->get_name() ?? $activity_object->get_object()->get_content(); + } + $outbox_item = array( 'post_type' => self::POST_TYPE, 'post_title' => sprintf( @@ -47,7 +52,7 @@ public static function add( $activity_object, $activity_type, $user_id, $content __( '[%1$s] %2$s: %3$s', 'activitypub' ), $activity_type, $activity_object->get_type(), - \wp_trim_words( $activity_object->get_name() ?? $activity_object->get_content(), 5 ) + \wp_trim_words( $title, 5 ) ), 'post_content' => wp_slash( $activity_object->to_json() ), // ensure that user ID is not below 0. From 343a3c19647e21a9845c0f8cf50aec7988c2b1e2 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Tue, 11 Feb 2025 14:53:25 -0600 Subject: [PATCH 29/39] use instanceof check --- includes/collection/class-outbox.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index f6feebc8e..14c4b37fe 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -41,7 +41,7 @@ public static function add( $activity_object, $activity_type, $user_id, $content } $title = $activity_object->get_name() ?? $activity_object->get_content(); - if ( empty( $title ) && $activity_object->get_object() ) { + if ( empty( $title ) && $activity_object->get_object() instanceof \Activitypub\Activity\Base_Object ) { $title = $activity_object->get_object()->get_name() ?? $activity_object->get_object()->get_content(); } From 3bd359a04a49343fcf0e023495ee9f39350efbf1 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Tue, 11 Feb 2025 14:56:34 -0600 Subject: [PATCH 30/39] Also remove process_outbox events --- includes/collection/class-outbox.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index 14c4b37fe..369851e42 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -137,6 +137,8 @@ private static function invalidate_existing_items( $object_id, $activity_type, $ ); $timestamp = wp_next_scheduled( 'activitypub_async_batch', $event_args ); wp_unschedule_event( $timestamp, 'activitypub_async_batch', $event_args ); + $timestamp = wp_next_scheduled( 'activitypub_process_outbox', array( $existing_item_id ) ); + wp_unschedule_event( $timestamp, 'activitypub_process_outbox', array( $existing_item_id ) ); \wp_publish_post( $existing_item_id ); \delete_post_meta( $existing_item_id, '_activitypub_outbox_offset' ); From 118d521b7e405321df97df82e1809eccc5af989d Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Tue, 11 Feb 2025 15:11:35 -0600 Subject: [PATCH 31/39] backslashes --- includes/collection/class-outbox.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index 369851e42..12b2ed861 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -135,10 +135,10 @@ private static function invalidate_existing_items( $object_id, $activity_type, $ Dispatcher::$batch_size, \get_post_meta( $existing_item_id, '_activitypub_outbox_offset', true ) ?: 0, // phpcs:ignore ); - $timestamp = wp_next_scheduled( 'activitypub_async_batch', $event_args ); - wp_unschedule_event( $timestamp, 'activitypub_async_batch', $event_args ); - $timestamp = wp_next_scheduled( 'activitypub_process_outbox', array( $existing_item_id ) ); - wp_unschedule_event( $timestamp, 'activitypub_process_outbox', array( $existing_item_id ) ); + $timestamp = \wp_next_scheduled( 'activitypub_async_batch', $event_args ); + \wp_unschedule_event( $timestamp, 'activitypub_async_batch', $event_args ); + $timestamp = \wp_next_scheduled( 'activitypub_process_outbox', array( $existing_item_id ) ); + \wp_unschedule_event( $timestamp, 'activitypub_process_outbox', array( $existing_item_id ) ); \wp_publish_post( $existing_item_id ); \delete_post_meta( $existing_item_id, '_activitypub_outbox_offset' ); From c0a2f54bf50c3b1705ae656109438818a599e575 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Wed, 12 Feb 2025 12:57:46 +0100 Subject: [PATCH 32/39] Move announce scheduler, because it also announces comments --- includes/class-scheduler.php | 59 ++++++++++++++++++++++++++++++- includes/scheduler/class-post.php | 56 ----------------------------- 2 files changed, 58 insertions(+), 57 deletions(-) diff --git a/includes/class-scheduler.php b/includes/class-scheduler.php index bdf13bf71..024a94d46 100644 --- a/includes/class-scheduler.php +++ b/includes/class-scheduler.php @@ -10,9 +10,10 @@ use Activitypub\Scheduler\Post; use Activitypub\Scheduler\Actor; use Activitypub\Scheduler\Comment; +use Activitypub\Collection\Actors; use Activitypub\Collection\Outbox; use Activitypub\Collection\Followers; - +use Activitypub\Transformer\Factory; /** * Scheduler class. * @@ -34,6 +35,7 @@ public static function init() { \add_action( 'activitypub_reprocess_outbox', array( self::class, 'reprocess_outbox' ) ); \add_action( 'post_activitypub_add_to_outbox', array( self::class, 'schedule_outbox_activity_for_federation' ) ); + \add_action( 'post_activitypub_add_to_outbox', array( self::class, 'schedule_announce_activity' ), 10, 4 ); } /** @@ -313,4 +315,59 @@ private static function next_scheduled_hook( $hook ) { return $next; } + + /** + * Send announces. + * + * @param int $outbox_activity_id The outbox activity ID. + * @param Activity $activity_object The activity object. + * @param int $actor_id The actor ID. + * @param int $content_visibility The content visibility. + */ + public static function schedule_announce_activity( $outbox_activity_id, $activity_object, $actor_id, $content_visibility ) { + // Only if we're in both Blog and User modes. + if ( ACTIVITYPUB_ACTOR_AND_BLOG_MODE !== \get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE ) ) { + return; + } + + // Only if this isn't the Blog Actor. + if ( Actors::BLOG_USER_ID === $actor_id ) { + return; + } + + // Only if the content is public or quiet public. + if ( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC !== $content_visibility ) { + return; + } + + $activity_type = \get_post_meta( $outbox_activity_id, '_activitypub_activity_type', true ); + + // Only if the activity is a Create, Update or Delete. + if ( ! in_array( $activity_type, array( 'Create', 'Update', 'Delete' ), true ) ) { + return; + } + + // Check if the object is an article, image, audio, video, event or document and ignore profile updates and other activities. + if ( ! in_array( $activity_object->get_type(), array( 'Note', 'Article', 'Image', 'Audio', 'Video', 'Event', 'Document' ), true ) ) { + return; + } + + $transformer = Factory::get_transformer( $activity_object ); + if ( ! $transformer || \is_wp_error( $transformer ) ) { + return; + } + + $post = get_post( $outbox_activity_id ); + $activity = $transformer->to_activity( $activity_type ); + $activity->set_id( $post->guid ); + + $outbox_activity_id = Outbox::add( $activity, 'Announce', Actors::BLOG_USER_ID ); + + if ( ! $outbox_activity_id ) { + return; + } + + // Schedule the outbox item for federation. + self::schedule_outbox_activity_for_federation( $outbox_activity_id ); + } } diff --git a/includes/scheduler/class-post.php b/includes/scheduler/class-post.php index e78c94951..3041dcf5a 100644 --- a/includes/scheduler/class-post.php +++ b/includes/scheduler/class-post.php @@ -7,11 +7,8 @@ namespace Activitypub\Scheduler; -use Activitypub\Activity\Activity; use Activitypub\Scheduler; use Activitypub\Collection\Outbox; -use Activitypub\Collection\Actors; -use Activitypub\Transformer\Factory; use function Activitypub\add_to_outbox; use function Activitypub\is_post_disabled; @@ -33,8 +30,6 @@ public static function init() { \add_action( 'edit_attachment', array( self::class, 'transition_attachment_status' ) ); \add_action( 'delete_attachment', array( self::class, 'transition_attachment_status' ) ); - \add_action( 'post_activitypub_add_to_outbox', array( self::class, 'schedule_announce_activity' ), 10, 4 ); - // Get all post types that support ActivityPub. $post_types = \get_post_types_by_support( 'activitypub' ); @@ -108,57 +103,6 @@ public static function schedule_post_activity( $new_status, $old_status, $post ) add_to_outbox( $post, $type, $post->post_author ); } - /** - * Send announces. - * - * @param int $outbox_activity_id The outbox activity ID. - * @param Activity $activity_object The activity object. - * @param int $actor_id The actor ID. - * @param int $content_visibility The content visibility. - */ - public static function schedule_announce_activity( $outbox_activity_id, $activity_object, $actor_id, $content_visibility ) { - // Only if we're in both Blog and User modes. - if ( ACTIVITYPUB_ACTOR_AND_BLOG_MODE !== \get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE ) ) { - return; - } - - // Only if this isn't the Blog Actor. - if ( Actors::BLOG_USER_ID === $actor_id ) { - return; - } - - // Only if the content is public or quiet public. - if ( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC !== $content_visibility ) { - return; - } - - $activity_type = \get_post_meta( $outbox_activity_id, '_activitypub_activity_type', true ); - - // Only if the activity is a Create, Update or Delete. - if ( ! in_array( $activity_type, array( 'Create', 'Update', 'Delete' ), true ) ) { - return; - } - - // Check if the object is an article, image, audio, video, event or document and ignore profile updates and other activities. - if ( ! in_array( $activity_object->get_type(), array( 'Note', 'Article', 'Image', 'Audio', 'Video', 'Event', 'Document' ), true ) ) { - return; - } - - $transformer = Factory::get_transformer( $activity_object ); - if ( ! $transformer || \is_wp_error( $transformer ) ) { - return; - } - - $outbox_activity_id = Outbox::add( $transformer->to_activity( $activity_type ), 'Announce', Actors::BLOG_USER_ID ); - - if ( ! $outbox_activity_id ) { - return; - } - - // Schedule the outbox item for federation. - Scheduler::schedule_outbox_activity_for_federation( $outbox_activity_id ); - } - /** * Filter the post data before it is inserted via the REST API. * From 9791ef4bb707297c1530f4d79fa63daf5110ca52 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Wed, 12 Feb 2025 12:58:10 +0100 Subject: [PATCH 33/39] change checks a bit --- includes/collection/class-outbox.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index 12b2ed861..fab0f610c 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -9,6 +9,8 @@ use Activitypub\Dispatcher; +use function Activitypub\is_activity; + /** * ActivityPub Outbox Collection * @@ -41,7 +43,7 @@ public static function add( $activity_object, $activity_type, $user_id, $content } $title = $activity_object->get_name() ?? $activity_object->get_content(); - if ( empty( $title ) && $activity_object->get_object() instanceof \Activitypub\Activity\Base_Object ) { + if ( ! $title && is_activity( $activity_object ) && $activity_object->get_object() instanceof \Activitypub\Activity\Base_Object ) { $title = $activity_object->get_object()->get_name() ?? $activity_object->get_object()->get_content(); } @@ -135,8 +137,10 @@ private static function invalidate_existing_items( $object_id, $activity_type, $ Dispatcher::$batch_size, \get_post_meta( $existing_item_id, '_activitypub_outbox_offset', true ) ?: 0, // phpcs:ignore ); - $timestamp = \wp_next_scheduled( 'activitypub_async_batch', $event_args ); + + $timestamp = \wp_next_scheduled( 'activitypub_async_batch', $event_args ); \wp_unschedule_event( $timestamp, 'activitypub_async_batch', $event_args ); + $timestamp = \wp_next_scheduled( 'activitypub_process_outbox', array( $existing_item_id ) ); \wp_unschedule_event( $timestamp, 'activitypub_process_outbox', array( $existing_item_id ) ); From 181622ee1814ee0ab75e89be021d9e9c2f37b628 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Wed, 12 Feb 2025 12:58:21 +0100 Subject: [PATCH 34/39] add item for debugging --- includes/debug.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/includes/debug.php b/includes/debug.php index b289c80d2..c692de15e 100644 --- a/includes/debug.php +++ b/includes/debug.php @@ -20,3 +20,23 @@ function allow_localhost( $parsed_args ) { return $parsed_args; } add_filter( 'http_request_args', '\Activitypub\allow_localhost' ); + +/** + * Debug the outbox post type. + * + * @param array $args The arguments for the post type. + * @param string $post_type The post type. + * + * @return array The arguments for the post type. + */ +function debug_outbox_post_type( $args, $post_type ) { + if ( 'ap_outbox' !== $post_type ) { + return $args; + } + + $args['show_ui'] = true; + $args['menu_icon'] = 'dashicons-upload'; + + return $args; +} +add_filter( 'register_post_type_args', '\Activitypub\debug_outbox_post_type', 10, 2 ); From 7ea0b15d8b5d11c597353815d56935e33583bc25 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Wed, 12 Feb 2025 13:31:10 +0100 Subject: [PATCH 35/39] add some debug data --- includes/debug.php | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/includes/debug.php b/includes/debug.php index c692de15e..6d6c27e86 100644 --- a/includes/debug.php +++ b/includes/debug.php @@ -19,7 +19,7 @@ function allow_localhost( $parsed_args ) { return $parsed_args; } -add_filter( 'http_request_args', '\Activitypub\allow_localhost' ); +\add_filter( 'http_request_args', '\Activitypub\allow_localhost' ); /** * Debug the outbox post type. @@ -39,4 +39,41 @@ function debug_outbox_post_type( $args, $post_type ) { return $args; } -add_filter( 'register_post_type_args', '\Activitypub\debug_outbox_post_type', 10, 2 ); +\add_filter( 'register_post_type_args', '\Activitypub\debug_outbox_post_type', 10, 2 ); + +/** + * Debug the outbox post type column. + * + * @param array $columns The columns. + * @param string $post_type The post type. + * + * @return array The updated columns. + */ +function debug_outbox_post_type_column( $columns, $post_type ) { + if ( 'ap_outbox' !== $post_type ) { + return $columns; + } + + $columns['ap_outbox_meta'] = "Meta"; + + return $columns; +} +\add_filter( 'manage_posts_columns', '\Activitypub\debug_outbox_post_type_column', 10, 2 ); + +/** + * Debug the outbox post type meta. + * + * @param string $column_name The column name. + * @param int $post_id The post ID. + * + * @return void + */ +function manage_posts_custom_column( $column_name, $post_id ) { + if ( 'ap_outbox_meta' === $column_name ) { + $meta = get_post_meta( $post_id ); + foreach ( $meta as $key => $value ) { + echo $key . ': ' . $value[0] . '
'; + } + } +} +\add_action( 'manage_posts_custom_column', '\Activitypub\manage_posts_custom_column', 10, 2 ); From 57d3b96d7956f4184cfe96d318defd5af6c9ac44 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Wed, 12 Feb 2025 13:31:20 +0100 Subject: [PATCH 36/39] invalidate announces --- includes/collection/class-outbox.php | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index fab0f610c..90d07f610 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -42,9 +42,12 @@ public static function add( $activity_object, $activity_type, $user_id, $content break; } - $title = $activity_object->get_name() ?? $activity_object->get_content(); + $title = $activity_object->get_name() ?? $activity_object->get_content(); + $activitypub_object_id = $activity_object->get_id(); + if ( ! $title && is_activity( $activity_object ) && $activity_object->get_object() instanceof \Activitypub\Activity\Base_Object ) { - $title = $activity_object->get_object()->get_name() ?? $activity_object->get_object()->get_content(); + $title = $activity_object->get_object()->get_name() ?? $activity_object->get_object()->get_content(); + $activitypub_object_id = $activity_object->get_object()->get_id(); } $outbox_item = array( @@ -61,10 +64,10 @@ public static function add( $activity_object, $activity_type, $user_id, $content 'post_author' => \max( $user_id, 0 ), 'post_status' => 'pending', 'meta_input' => array( - '_activitypub_object_id' => $activity_object->get_id(), - '_activitypub_activity_type' => $activity_type, - '_activitypub_activity_actor' => $actor_type, - 'activitypub_content_visibility' => $content_visibility, + '_activitypub_object_id' => \esc_url_raw( $activitypub_object_id ), + '_activitypub_activity_type' => \esc_attr( $activity_type ), + '_activitypub_activity_actor' => \esc_attr( $actor_type ), + 'activitypub_content_visibility' => \esc_attr( $content_visibility ), ), ); @@ -88,7 +91,7 @@ public static function add( $activity_object, $activity_type, $user_id, $content return false; } - self::invalidate_existing_items( $activity_object->get_id(), $activity_type, $id ); + self::invalidate_existing_items( $activitypub_object_id, $activity_type, $id ); return $id; } @@ -107,7 +110,7 @@ private static function invalidate_existing_items( $object_id, $activity_type, $ $meta_query = array( array( 'key' => '_activitypub_object_id', - 'value' => $object_id, + 'value' => \esc_url_raw( $object_id ), ), ); @@ -115,7 +118,7 @@ private static function invalidate_existing_items( $object_id, $activity_type, $ if ( 'Delete' !== $activity_type ) { $meta_query[] = array( 'key' => '_activitypub_activity_type', - 'value' => $activity_type, + 'value' => \esc_attr( $activity_type ), ); } From b33699e9aa357bb4d8fb5c37457c0771deaea48f Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Wed, 12 Feb 2025 13:32:55 +0100 Subject: [PATCH 37/39] fix phpcs --- includes/collection/class-outbox.php | 2 +- includes/debug.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index 90d07f610..4842e842c 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -42,7 +42,7 @@ public static function add( $activity_object, $activity_type, $user_id, $content break; } - $title = $activity_object->get_name() ?? $activity_object->get_content(); + $title = $activity_object->get_name() ?? $activity_object->get_content(); $activitypub_object_id = $activity_object->get_id(); if ( ! $title && is_activity( $activity_object ) && $activity_object->get_object() instanceof \Activitypub\Activity\Base_Object ) { diff --git a/includes/debug.php b/includes/debug.php index 6d6c27e86..bc96e969c 100644 --- a/includes/debug.php +++ b/includes/debug.php @@ -54,7 +54,7 @@ function debug_outbox_post_type_column( $columns, $post_type ) { return $columns; } - $columns['ap_outbox_meta'] = "Meta"; + $columns['ap_outbox_meta'] = 'Meta'; return $columns; } @@ -70,9 +70,9 @@ function debug_outbox_post_type_column( $columns, $post_type ) { */ function manage_posts_custom_column( $column_name, $post_id ) { if ( 'ap_outbox_meta' === $column_name ) { - $meta = get_post_meta( $post_id ); + $meta = \get_post_meta( $post_id ); foreach ( $meta as $key => $value ) { - echo $key . ': ' . $value[0] . '
'; + echo \esc_attr( $key ) . ': ' . \esc_html( $value[0] ) . '
'; } } } From 2a9f084bc8805666ff4bc45166b372e52ea3f918 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Wed, 12 Feb 2025 14:36:00 +0100 Subject: [PATCH 38/39] remove escaping --- includes/collection/class-outbox.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index 4842e842c..7be7680d1 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -64,10 +64,10 @@ public static function add( $activity_object, $activity_type, $user_id, $content 'post_author' => \max( $user_id, 0 ), 'post_status' => 'pending', 'meta_input' => array( - '_activitypub_object_id' => \esc_url_raw( $activitypub_object_id ), - '_activitypub_activity_type' => \esc_attr( $activity_type ), - '_activitypub_activity_actor' => \esc_attr( $actor_type ), - 'activitypub_content_visibility' => \esc_attr( $content_visibility ), + '_activitypub_object_id' => $activitypub_object_id, + '_activitypub_activity_type' => $activity_type, + '_activitypub_activity_actor' => $actor_type, + 'activitypub_content_visibility' => $content_visibility, ), ); From 5c3d8b153b1b926f35a4334b71d9160a879c2c74 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Wed, 12 Feb 2025 14:45:50 +0100 Subject: [PATCH 39/39] remove escaping --- includes/collection/class-outbox.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/collection/class-outbox.php b/includes/collection/class-outbox.php index 7be7680d1..9a91587cb 100644 --- a/includes/collection/class-outbox.php +++ b/includes/collection/class-outbox.php @@ -110,7 +110,7 @@ private static function invalidate_existing_items( $object_id, $activity_type, $ $meta_query = array( array( 'key' => '_activitypub_object_id', - 'value' => \esc_url_raw( $object_id ), + 'value' => $object_id, ), ); @@ -118,7 +118,7 @@ private static function invalidate_existing_items( $object_id, $activity_type, $ if ( 'Delete' !== $activity_type ) { $meta_query[] = array( 'key' => '_activitypub_activity_type', - 'value' => \esc_attr( $activity_type ), + 'value' => $activity_type, ); }