From fef0a4af70da656c88e645a132c3c222093b6407 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 23 Sep 2025 10:31:04 +0200 Subject: [PATCH 1/3] Refactor Mention class to use Remote_Actors for actor data Replaces direct metadata fetching with Remote_Actors::fetch_by_uri in Mention class methods. Updates logic for resolving actor URLs and inboxes, and adjusts test mocks and filters to match the new approach. This change improves consistency and leverages the Remote_Actors abstraction for remote actor data handling. --- includes/class-mention.php | 49 ++++++++++++--------------- tests/includes/class-test-mention.php | 13 ++++--- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/includes/class-mention.php b/includes/class-mention.php index a1343c3a6..0237160cc 100644 --- a/includes/class-mention.php +++ b/includes/class-mention.php @@ -7,7 +7,7 @@ namespace Activitypub; -use WP_Error; +use Activitypub\Collection\Remote_Actors; /** * ActivityPub Mention Class. @@ -65,27 +65,24 @@ public static function the_content( $the_content ) { * @return string The final string. */ public static function replace_with_links( $result ) { - $metadata = get_remote_metadata_by_actor( $result[0] ); - - if ( - ! empty( $metadata ) && - ! is_wp_error( $metadata ) && - ( ! empty( $metadata['id'] ) || ! empty( $metadata['url'] ) ) - ) { - $username = ltrim( $result[0], '@' ); - if ( ! empty( $metadata['name'] ) ) { - $username = $metadata['name']; - } - if ( ! empty( $metadata['preferredUsername'] ) ) { - $username = $metadata['preferredUsername']; - } + $post = Remote_Actors::fetch_by_uri( $result[0] ); + + if ( \is_wp_error( $post ) ) { + return $result[0]; + } - $url = isset( $metadata['url'] ) ? object_to_uri( $metadata['url'] ) : object_to_uri( $metadata['id'] ); + $actor = \json_decode( $post->post_content, true ); + $url = object_to_uri( $actor ); - return \sprintf( '@%2$s', esc_url( $url ), esc_html( $username ) ); + $username = ltrim( $result[0], '@' ); + if ( ! empty( $actor['name'] ) ) { + $username = $actor['name']; + } + if ( ! empty( $actor['preferredUsername'] ) ) { + $username = $actor['preferredUsername']; } - return $result[0]; + return \sprintf( '@%2$s', esc_url( $url ), esc_html( $username ) ); } /** @@ -117,21 +114,19 @@ public static function get_inboxes( $mentioned ) { * @return string|WP_Error The Inbox-URL or WP_Error if not found. */ public static function get_inbox_by_mentioned_actor( $actor ) { - $metadata = get_remote_metadata_by_actor( $actor ); + $post = Remote_Actors::fetch_by_uri( $actor ); - if ( \is_wp_error( $metadata ) ) { - return $metadata; + if ( \is_wp_error( $post ) ) { + return $post; } - if ( isset( $metadata['endpoints']['sharedInbox'] ) ) { - return $metadata['endpoints']['sharedInbox']; - } + $inbox = \get_post_meta( $post->ID, '_activitypub_inbox', true ); - if ( \array_key_exists( 'inbox', $metadata ) ) { - return $metadata['inbox']; + if ( ! $inbox ) { + return new \WP_Error( 'activitypub_no_inbox', \__( 'No "Inbox" found', 'activitypub' ), $actor ); } - return new WP_Error( 'activitypub_no_inbox', \__( 'No "Inbox" found', 'activitypub' ), $metadata ); + return $inbox; } /** diff --git a/tests/includes/class-test-mention.php b/tests/includes/class-test-mention.php index 71f149759..295b2b94e 100644 --- a/tests/includes/class-test-mention.php +++ b/tests/includes/class-test-mention.php @@ -23,9 +23,12 @@ class Test_Mention extends \WP_UnitTestCase { */ public static $actors = array( 'username@example.org' => array( - 'id' => 'https://example.org/users/username', - 'url' => 'https://example.org/users/username', - 'name' => 'username', + 'id' => 'https://example.org/users/username', + 'type' => 'Person', + 'url' => 'https://example.org/users/username', + 'name' => 'username', + 'preferredUsername' => 'username', + 'inbox' => 'https://example.org/users/username/inbox', ), ); @@ -34,7 +37,7 @@ class Test_Mention extends \WP_UnitTestCase { */ public function set_up() { parent::set_up(); - add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 ); + add_filter( 'activitypub_pre_http_get_remote_object', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 ); add_filter( 'pre_http_request', array( $this, 'pre_http_request' ), 10, 3 ); } @@ -42,7 +45,7 @@ public function set_up() { * Tear down the test case. */ public function tear_down() { - remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) ); + remove_filter( 'activitypub_pre_http_get_remote_object', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) ); remove_filter( 'pre_http_request', array( $this, 'pre_http_request' ) ); parent::tear_down(); } From d51ef79eb4d182b52b9246fbd95dd5c4b5b0b228 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 23 Sep 2025 11:32:10 +0200 Subject: [PATCH 2/3] Refactor actor metadata handling in Interactions Replaces direct calls to get_remote_metadata_by_actor with Remote_Actors::fetch_by_uri and updates usage throughout Interactions. Updates tests to use the 'activitypub_pre_http_get_remote_object' filter instead of 'pre_get_remote_metadata_by_actor'. Also improves handling of actor data and short ternary usage for compatibility with coding standards. --- includes/collection/class-interactions.php | 44 ++++++++----------- includes/collection/class-remote-actors.php | 2 +- .../collection/class-test-interactions.php | 40 ++++++++++------- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/includes/collection/class-interactions.php b/includes/collection/class-interactions.php index 68be8a12e..64bd35b65 100644 --- a/includes/collection/class-interactions.php +++ b/includes/collection/class-interactions.php @@ -7,11 +7,9 @@ namespace Activitypub\Collection; +use Activitypub\Collection\Remote_Actors; use Activitypub\Comment; -use Activitypub\Webfinger; -use WP_Comment_Query; -use function Activitypub\get_remote_metadata_by_actor; use function Activitypub\is_post_disabled; use function Activitypub\object_id_to_comment; use function Activitypub\object_to_uri; @@ -67,7 +65,8 @@ public static function add_comment( $activity ) { * @return array|string|int|\WP_Error|false The comment data or false on failure. */ public static function update_comment( $activity ) { - $meta = get_remote_metadata_by_actor( $activity['actor'] ); + $url = object_to_uri( $activity['actor'] ); + $post = Remote_Actors::fetch_by_uri( $url ); // Determine comment_ID. $comment = object_id_to_comment( \esc_url_raw( $activity['object']['id'] ) ); @@ -78,7 +77,7 @@ public static function update_comment( $activity ) { } // Found a local comment id. - $comment_data['comment_author'] = \esc_attr( $meta['name'] ?? $meta['preferredUsername'] ); + $comment_data['comment_author'] = \esc_attr( $post->post_title ); $comment_data['comment_content'] = \addslashes( $activity['object']['content'] ); return self::persist( $comment_data, self::UPDATE ); @@ -157,7 +156,7 @@ public static function get_interaction_by_id( $url ) { ), ); - $query = new WP_Comment_Query( $args ); + $query = new \WP_Comment_Query( $args ); return $query->comments; } @@ -169,16 +168,17 @@ public static function get_interaction_by_id( $url ) { * @return array The interactions as WP_Comment objects. */ public static function get_interactions_by_actor( $actor ) { - $meta = get_remote_metadata_by_actor( $actor ); + $url = object_to_uri( $actor ); + $post = Remote_Actors::fetch_by_uri( $url ); // Get URL, because $actor seems to be the ID. - if ( $meta && ! is_wp_error( $meta ) && isset( $meta['url'] ) ) { - $actor = object_to_uri( $meta['url'] ); + if ( \is_wp_error( $post ) ) { + return array(); } $args = array( 'nopaging' => true, - 'author_url' => $actor, + 'author_url' => $post->guid, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( array( @@ -226,34 +226,28 @@ public static function allowed_comment_html( $allowed_tags, $context = '' ) { */ public static function activity_to_comment( $activity ) { $comment_content = null; - $actor = object_to_uri( $activity['actor'] ?? null ); - $actor = get_remote_metadata_by_actor( $actor ); + $actor_id = object_to_uri( $activity['actor'] ?? null ); + $post = Remote_Actors::fetch_by_uri( $actor_id ); // Check Actor-Meta. - if ( ! $actor || is_wp_error( $actor ) ) { + if ( \is_wp_error( $post ) ) { return false; } - // Check Actor-Name. - $comment_author = null; - if ( ! empty( $actor['name'] ) ) { - $comment_author = $actor['name']; - } elseif ( ! empty( $actor['preferredUsername'] ) ) { - $comment_author = $actor['preferredUsername']; - } + $comment_author = $post->post_title; + $actor = json_decode( $post->post_content, true ); + $url = object_to_uri( $actor['url'] ?? $post->guid ); if ( empty( $comment_author ) && \get_option( 'require_name_email' ) ) { return false; } - $url = object_to_uri( $actor['url'] ?? $actor['id'] ); - if ( isset( $activity['object']['content'] ) ) { $comment_content = \addslashes( $activity['object']['content'] ); } - $webfinger = Webfinger::uri_to_acct( $url ); - if ( is_wp_error( $webfinger ) ) { + $webfinger = Remote_Actors::get_acct( $post->ID ); + if ( \is_wp_error( $webfinger ) ) { $webfinger = ''; } else { $webfinger = str_replace( 'acct:', '', $webfinger ); @@ -262,7 +256,7 @@ public static function activity_to_comment( $activity ) { $date = $activity['object']['published'] ?? 'now'; $comment_data = array( - 'comment_author' => $comment_author ?? __( 'Anonymous', 'activitypub' ), + 'comment_author' => $comment_author ?: __( 'Anonymous', 'activitypub' ), // phpcs:ignore Universal.Operators.DisallowShortTernary 'comment_author_url' => \esc_url_raw( $url ), 'comment_content' => $comment_content, 'comment_type' => 'comment', diff --git a/includes/collection/class-remote-actors.php b/includes/collection/class-remote-actors.php index 5924ce425..f0d254877 100644 --- a/includes/collection/class-remote-actors.php +++ b/includes/collection/class-remote-actors.php @@ -423,7 +423,7 @@ private static function prepare_custom_post_type( $actor ) { return array( 'guid' => \esc_url_raw( $actor->get_id() ), - 'post_title' => \wp_strip_all_tags( \wp_slash( $actor->get_name() ?? $actor->get_preferred_username() ) ), + 'post_title' => \wp_strip_all_tags( \wp_slash( $actor->get_name() ?: $actor->get_preferred_username() ) ), // phpcs:ignore Universal.Operators.DisallowShortTernary 'post_author' => 0, 'post_type' => self::POST_TYPE, 'post_content' => \wp_slash( $actor->to_json() ), diff --git a/tests/includes/collection/class-test-interactions.php b/tests/includes/collection/class-test-interactions.php index 34bc339ca..89fa41d0a 100644 --- a/tests/includes/collection/class-test-interactions.php +++ b/tests/includes/collection/class-test-interactions.php @@ -97,7 +97,7 @@ public static function wpSetUpBeforeClass( $factory ) { public function set_up() { parent::set_up(); - \add_filter( 'pre_get_remote_metadata_by_actor', array( __CLASS__, 'get_remote_metadata_by_actor' ), 0, 2 ); + \add_filter( 'activitypub_pre_http_get_remote_object', array( __CLASS__, 'get_remote_metadata_by_actor' ), 0, 2 ); } /** @@ -117,12 +117,14 @@ public static function wpTearDownAfterClass() { */ public static function get_remote_metadata_by_actor( $value, $actor ) { return array( - 'name' => 'Example User', - 'icon' => array( + 'name' => 'Example User', + 'type' => 'Person', + 'icon' => array( 'url' => 'https://example.com/icon', ), - 'url' => $actor, - 'id' => 'http://example.org/users/example', + 'url' => $actor, + 'id' => 'http://example.org/users/example', + 'inbox' => $actor . '/inbox', ); } @@ -183,7 +185,7 @@ public function test_handle_create_basic() { $this->assertEquals( self::$user_url, $basic_comment['comment_author_url'] ); $this->assertEquals( 'example', $basic_comment['comment_content'] ); $this->assertEquals( 'comment', $basic_comment['comment_type'] ); - $this->assertEquals( '', $basic_comment['comment_author_email'] ); + $this->assertEquals( 'example@example.org', $basic_comment['comment_author_email'] ); $this->assertEquals( 0, $basic_comment['comment_parent'] ); $this->assertEquals( 'https://example.com/123', get_comment_meta( $basic_comment_id, 'source_id', true ) ); $this->assertEquals( 'https://example.com/example', get_comment_meta( $basic_comment_id, 'source_url', true ) ); @@ -337,7 +339,7 @@ public function test_add_comment_disabled_post() { // Mock actor metadata. add_filter( - 'pre_get_remote_metadata_by_actor', + 'activitypub_pre_http_get_remote_object', function () { return array( 'name' => 'Test User', @@ -353,7 +355,7 @@ function () { $this->assertFalse( $result, 'Comment should not be added to disabled post' ); // Clean up. - remove_all_filters( 'pre_get_remote_metadata_by_actor' ); + remove_all_filters( 'activitypub_pre_http_get_remote_object' ); wp_delete_post( $disabled_post_id, true ); } @@ -375,7 +377,7 @@ public function test_add_comment_outbox_post() { // Mock actor metadata. add_filter( - 'pre_get_remote_metadata_by_actor', + 'activitypub_pre_http_get_remote_object', function () { return array( 'name' => 'Test User', @@ -390,7 +392,7 @@ function () { $result = Interactions::add_comment( $activity ); $this->assertFalse( $result, 'Comment should not be added to disabled post' ); - remove_all_filters( 'pre_get_remote_metadata_by_actor' ); + remove_all_filters( 'activitypub_pre_http_get_remote_object' ); } /** @@ -418,7 +420,7 @@ public function test_add_reaction_disabled_post() { // Mock actor metadata. add_filter( - 'pre_get_remote_metadata_by_actor', + 'activitypub_pre_http_get_remote_object', function () { return array( 'name' => 'Test User', @@ -434,7 +436,7 @@ function () { $this->assertFalse( $result, 'Reaction should not be added to disabled post' ); // Clean up. - remove_all_filters( 'pre_get_remote_metadata_by_actor' ); + remove_all_filters( 'activitypub_pre_http_get_remote_object' ); wp_delete_post( $disabled_post_id, true ); } @@ -453,7 +455,7 @@ public function test_add_reaction_outbox_post() { // Mock actor metadata. add_filter( - 'pre_get_remote_metadata_by_actor', + 'activitypub_pre_http_get_remote_object', function () { return array( 'name' => 'Test User', @@ -468,7 +470,7 @@ function () { $result = Interactions::add_reaction( $activity ); $this->assertFalse( $result, 'Reaction should not be added to disabled post' ); - remove_all_filters( 'pre_get_remote_metadata_by_actor' ); + remove_all_filters( 'activitypub_pre_http_get_remote_object' ); } /** @@ -560,7 +562,7 @@ public function test_activity_to_comment_author() { ); // Mock actor metadata. - \add_filter( 'pre_get_remote_metadata_by_actor', array( $this, 'actor_meta_data_comment_author' ), 10, 2 ); + \add_filter( 'activitypub_pre_http_get_remote_object', array( $this, 'actor_meta_data_comment_author' ), 10, 2 ); // No name => preferredUsername. $comment_data = Interactions::activity_to_comment( $activity ); @@ -582,7 +584,7 @@ public function test_activity_to_comment_author() { $comment_data = Interactions::activity_to_comment( $activity ); $this->assertSame( \__( 'Anonymous', 'activitypub' ), $comment_data['comment_author'] ); - \remove_filter( 'pre_get_remote_metadata_by_actor', array( $this, 'actor_meta_data_comment_author' ) ); + \remove_filter( 'activitypub_pre_http_get_remote_object', array( $this, 'actor_meta_data_comment_author' ) ); \update_option( 'require_name_email', '1' ); } @@ -597,26 +599,32 @@ public function test_activity_to_comment_author() { public function actor_meta_data_comment_author( $response, $url ) { if ( 'https://example.com/users/tester_no_name' === $url ) { $response = array( + 'type' => 'Person', 'name' => '', 'preferredUsername' => 'test', 'id' => 'https://example.com/users/test', 'url' => 'https://example.com/@test', + 'inbox' => 'https://example.com/users/test/inbox', ); } if ( 'https://example.com/users/tester_no_preferredUsername' === $url ) { $response = array( + 'type' => 'Person', 'name' => 'Test User', 'preferredUsername' => '', 'id' => 'https://example.com/users/test', 'url' => 'https://example.com/@test', + 'inbox' => 'https://example.com/users/test/inbox', ); } if ( 'https://example.com/users/tester_anonymous' === $url ) { $response = array( + 'type' => 'Person', 'name' => '', 'preferredUsername' => '', 'id' => 'https://example.com/users/test', 'url' => 'https://example.com/@test', + 'inbox' => 'https://example.com/users/test/inbox', ); } From d46e93f92b05e33a963a84dc1d4776dc6831a891 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 23 Sep 2025 14:36:59 +0200 Subject: [PATCH 3/3] Fix mention username rendering and resolve actor URIs Simplifies mention link rendering by using the matched username directly. Adds support in Remote_Actors::fetch_by_uri to resolve usernames to actor URIs using Webfinger, improving actor lookup reliability. --- includes/class-mention.php | 10 +--------- includes/collection/class-remote-actors.php | 8 ++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/includes/class-mention.php b/includes/class-mention.php index 0237160cc..bc32b8d25 100644 --- a/includes/class-mention.php +++ b/includes/class-mention.php @@ -74,15 +74,7 @@ public static function replace_with_links( $result ) { $actor = \json_decode( $post->post_content, true ); $url = object_to_uri( $actor ); - $username = ltrim( $result[0], '@' ); - if ( ! empty( $actor['name'] ) ) { - $username = $actor['name']; - } - if ( ! empty( $actor['preferredUsername'] ) ) { - $username = $actor['preferredUsername']; - } - - return \sprintf( '@%2$s', esc_url( $url ), esc_html( $username ) ); + return \sprintf( '@%2$s', \esc_url( $url ), \esc_html( $result[1] ) ); } /** diff --git a/includes/collection/class-remote-actors.php b/includes/collection/class-remote-actors.php index f0d254877..18afe13d1 100644 --- a/includes/collection/class-remote-actors.php +++ b/includes/collection/class-remote-actors.php @@ -210,6 +210,14 @@ public static function get_by_uri( $actor_uri ) { * @return \WP_Post|\WP_Error Post object or WP_Error if not found. */ public static function fetch_by_uri( $actor_uri ) { + if ( \preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $actor_uri ) ) { + $actor_uri = Webfinger::resolve( $actor_uri ); + } + + if ( \is_wp_error( $actor_uri ) ) { + return $actor_uri; + } + $post = self::get_by_uri( $actor_uri ); if ( ! \is_wp_error( $post ) ) {