From 118e4a7f22b998658754fea06cdc08799f15c5aa Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Fri, 28 Feb 2025 10:52:28 -0600 Subject: [PATCH 01/18] Combine sanitization functions --- CHANGELOG.md | 1 + includes/class-sanitize.php | 70 +++++++++++++ includes/wp-admin/class-settings.php | 44 +-------- readme.txt | 1 + tests/includes/class-test-sanitize.php | 132 +++++++++++++++++++++++++ 5 files changed, 207 insertions(+), 41 deletions(-) create mode 100644 includes/class-sanitize.php create mode 100644 tests/includes/class-test-sanitize.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 7af50a343..1c81d1e15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * Upgrade script to fix Follower json representations with unescaped backslashes. +* Centralized place for sanitization functions. ### Changed diff --git a/includes/class-sanitize.php b/includes/class-sanitize.php new file mode 100644 index 000000000..b2e588e1d --- /dev/null +++ b/includes/class-sanitize.php @@ -0,0 +1,70 @@ + $sanitized, + 'search_columns' => array( 'user_login', 'user_nicename' ), + 'number' => 1, + 'hide_empty' => true, + 'fields' => 'ID', + ) + ); + + if ( $user->get_results() ) { + \add_settings_error( + 'activitypub_blog_identifier', + 'activitypub_blog_identifier', + \esc_html__( 'You cannot use an existing author’s name for the blog profile ID.', 'activitypub' ) + ); + + return Blog::get_default_username(); + } + + return $sanitized; + } +} diff --git a/includes/wp-admin/class-settings.php b/includes/wp-admin/class-settings.php index 41be113a6..60207936f 100644 --- a/includes/wp-admin/class-settings.php +++ b/includes/wp-admin/class-settings.php @@ -9,6 +9,7 @@ use Activitypub\Collection\Actors; use Activitypub\Model\Blog; +use Activitypub\Sanitize; use function Activitypub\is_user_disabled; /** @@ -137,12 +138,7 @@ public static function register_settings() { 'description' => \__( 'Websites allowed to credit you.', 'activitypub' ), 'default' => \Activitypub\home_host(), 'sanitize_callback' => function ( $value ) { - $value = explode( PHP_EOL, $value ); - $value = array_filter( array_map( 'trim', $value ) ); - $value = array_filter( array_map( 'esc_attr', $value ) ); - $value = implode( PHP_EOL, $value ); - - return $value; + return implode( PHP_EOL, Sanitize::url_list( $value ) ); }, ) ); @@ -197,41 +193,7 @@ public static function register_settings() { 'description' => \esc_html__( 'The Identifier of the Blog-User', 'activitypub' ), 'show_in_rest' => true, 'default' => Blog::get_default_username(), - 'sanitize_callback' => function ( $value ) { - // Hack to allow dots in the username. - $parts = explode( '.', $value ); - $sanitized = array(); - - foreach ( $parts as $part ) { - $sanitized[] = \sanitize_title( $part ); - } - - $sanitized = implode( '.', $sanitized ); - - // Check for login or nicename. - $user = new \WP_User_Query( - array( - 'search' => $sanitized, - 'search_columns' => array( 'user_login', 'user_nicename' ), - 'number' => 1, - 'hide_empty' => true, - 'fields' => 'ID', - ) - ); - - if ( $user->results ) { - add_settings_error( - 'activitypub_blog_identifier', - 'activitypub_blog_identifier', - \esc_html__( 'You cannot use an existing author\'s name for the blog profile ID.', 'activitypub' ), - 'error' - ); - - return Blog::get_default_username(); - } - - return $sanitized; - }, + 'sanitize_callback' => array( Sanitize::class, 'blog_identifier' ), ) ); diff --git a/readme.txt b/readme.txt index 7f9e1bb7e..ab6009b0a 100644 --- a/readme.txt +++ b/readme.txt @@ -132,6 +132,7 @@ For reasons of data protection, it is not possible to see the followers of other = Unreleased = * Added: Upgrade script to fix Follower json representations with unescaped backslashes. +* Added: Centralized place for sanitization functions. * Changed: Bumped minimum required WordPress version to 6.4. * Changed: Use a later hook for Posts to get published to the Outbox, to get sure all `post_meta`s and `taxonomy`s are set stored properly. * Changed: Use webfinger as author email for comments from the Fediverse. diff --git a/tests/includes/class-test-sanitize.php b/tests/includes/class-test-sanitize.php new file mode 100644 index 000000000..dc6c06a2b --- /dev/null +++ b/tests/includes/class-test-sanitize.php @@ -0,0 +1,132 @@ + array( + array( + 'https://example.com', + 'https://example.com', + 'not-a-url', + 'https://wordpress.org', + ), + array( + 'https://example.com', + 'http://not-a-url', + 'https://wordpress.org', + ), + ), + 'mixed_urls_in_string_whitespace' => array( + "https://example.com\nnot-a-url\nhttps://wordpress.org ", + array( + 'https://example.com', + 'http://not-a-url', + 'https://wordpress.org', + ), + ), + 'special_characters' => array( + array( + 'https://example.com/path with spaces ', + 'https://example.com/über/path', + 'https://example.com/path?param=value¶m2=value2#section', + ), + array( + 'https://example.com/path%20with%20spaces', + 'https://example.com/über/path', + 'https://example.com/path?param=value¶m2=value2#section', + ), + ), + 'empty_array' => array( array(), array() ), + ); + } + + /** + * Test url_list with various inputs. + * + * @dataProvider url_list_provider + * @covers ::url_list + * + * @param mixed $input Input value. + * @param array $expected Expected output. + */ + public function test_url_list( $input, $expected ) { + $this->assertEquals( $expected, Sanitize::url_list( $input ) ); + } + + /** + * Data provider for blog identifier tests. + * + * @return array Test data. + */ + public function blog_identifier_provider() { + return array( + 'simple_string' => array( 'test-Blog', 'test-blog' ), + 'with_spaces' => array( 'test blog', 'test-blog' ), + 'with_dots' => array( 'test.blog', 'test.blog' ), + 'special_chars' => array( 'test@#$%^&*blog', 'testblog' ), + 'multiple_dots' => array( 'test.blog.name', 'test.blog.name' ), + ); + } + + /** + * Test blog_identifier with various inputs. + * + * @dataProvider blog_identifier_provider + * @covers ::blog_identifier + * + * @param string $input Input value. + * @param string $expected Expected output. + */ + public function test_blog_identifier( $input, $expected ) { + $this->assertEquals( $expected, Sanitize::blog_identifier( $input ) ); + } + + /** + * Test blog_identifier with an existing username. + * + * @covers ::blog_identifier + */ + public function test_blog_identifier_with_existing_user() { + $user_id = self::factory()->user->create( + array( + 'user_login' => 'existing-user', + 'user_nicename' => 'test-nicename', + ) + ); + + $result = Sanitize::blog_identifier( 'existing-user' ); + + $this->assertEquals( \Activitypub\Model\Blog::get_default_username(), $result ); + $this->assertNotEmpty( get_settings_errors( 'activitypub_blog_identifier' ) ); + + // Reset. + $GLOBALS['wp_settings_errors'] = array(); + + $result = Sanitize::blog_identifier( 'test-nicename' ); + + $this->assertEquals( \Activitypub\Model\Blog::get_default_username(), $result ); + $this->assertNotEmpty( get_settings_errors( 'activitypub_blog_identifier' ) ); + + \wp_delete_user( $user_id ); + } +} From d3027b4564bd54bd0c859111c1bc450006245677 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Fri, 28 Feb 2025 11:33:42 -0600 Subject: [PATCH 02/18] Add host_list sanitization --- includes/class-sanitize.php | 28 +++++++++++++++++ includes/wp-admin/class-settings.php | 4 +-- tests/includes/class-test-sanitize.php | 43 ++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/includes/class-sanitize.php b/includes/class-sanitize.php index b2e588e1d..c669490ee 100644 --- a/includes/class-sanitize.php +++ b/includes/class-sanitize.php @@ -32,6 +32,34 @@ public static function url_list( $value ) { return \array_values( $value ); } + /** + * Sanitize a list of hosts. + * + * @param string $value The value to sanitize. + * @return string The sanitized list of hosts. + */ + public static function host_list( $value ) { + $value = \explode( PHP_EOL, $value ); + $value = \array_map( + function ( $host ) { + $host = \trim( $host ); + $host = \strtolower( $host ); + $host = \set_url_scheme( $host ); + $host = \sanitize_url( $host, array( 'http', 'https' ) ); + + // Remove protocol. + if ( \str_contains( $host, 'http' ) ) { + $host = \wp_parse_url( $host, PHP_URL_HOST ); + } + + return \filter_var( $host, FILTER_VALIDATE_DOMAIN ); + }, + $value + ); + + return \implode( PHP_EOL, \array_filter( $value ) ); + } + /** * Sanitize a blog identifier. * diff --git a/includes/wp-admin/class-settings.php b/includes/wp-admin/class-settings.php index 60207936f..42805140c 100644 --- a/includes/wp-admin/class-settings.php +++ b/includes/wp-admin/class-settings.php @@ -137,9 +137,7 @@ public static function register_settings() { 'type' => 'string', 'description' => \__( 'Websites allowed to credit you.', 'activitypub' ), 'default' => \Activitypub\home_host(), - 'sanitize_callback' => function ( $value ) { - return implode( PHP_EOL, Sanitize::url_list( $value ) ); - }, + 'sanitize_callback' => array( Sanitize::class, 'host_list' ), ) ); diff --git a/tests/includes/class-test-sanitize.php b/tests/includes/class-test-sanitize.php index dc6c06a2b..514cdb5bc 100644 --- a/tests/includes/class-test-sanitize.php +++ b/tests/includes/class-test-sanitize.php @@ -73,6 +73,49 @@ public function test_url_list( $input, $expected ) { $this->assertEquals( $expected, Sanitize::url_list( $input ) ); } + /** + * Data provider for host list tests. + * + * @return array Test data. + */ + public function host_list_provider() { + return array( + 'single_valid_host' => array( + 'example.com', + 'example.com', + ), + 'multiple_valid_hosts' => array( + "ftp://example.com\nhttp://wordpress.org\nhttps://test.example.com", + "example.com\nwordpress.org\ntest.example.com", + ), + 'mixed_case_hosts' => array( + "ExAmPlE.cOm\nWoRdPrEsS.oRg", + "example.com\nwordpress.org", + ), + 'invalid_hosts' => array( + " not-a-domain\n\nexample.com\n\t@invalid.com", + "not-a-domain\nexample.com\ninvalid.com", + ), + 'empty_string' => array( + '', + '', + ), + ); + } + + /** + * Test host_list with various inputs. + * + * @dataProvider host_list_provider + * @covers ::host_list + * + * @param string $input Input value. + * @param string $expected Expected output. + */ + public function test_host_list( $input, $expected ) { + $this->assertEquals( $expected, Sanitize::host_list( $input ) ); + } + /** * Data provider for blog identifier tests. * From a86918648578c073cacf5b3d818bcd1def462b62 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 7 Feb 2025 17:28:45 +0100 Subject: [PATCH 03/18] add settings --- includes/wp-admin/class-admin.php | 217 ++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) diff --git a/includes/wp-admin/class-admin.php b/includes/wp-admin/class-admin.php index b23844350..6bd571413 100644 --- a/includes/wp-admin/class-admin.php +++ b/includes/wp-admin/class-admin.php @@ -117,6 +117,223 @@ public static function followers_list_page() { } } + /** + * Register ActivityPub settings + */ + public static function register_settings() { + \register_setting( + 'activitypub', + 'activitypub_post_content_type', + array( + 'type' => 'string', + 'description' => \__( 'Use title and link, summary, full or custom content', 'activitypub' ), + 'show_in_rest' => array( + 'schema' => array( + 'enum' => array( + 'title', + 'excerpt', + 'content', + ), + ), + ), + 'default' => 'content', + ) + ); + \register_setting( + 'activitypub', + 'activitypub_custom_post_content', + array( + 'type' => 'string', + 'description' => \__( 'Define your own custom post template', 'activitypub' ), + 'show_in_rest' => true, + 'default' => ACTIVITYPUB_CUSTOM_POST_CONTENT, + ) + ); + \register_setting( + 'activitypub', + 'activitypub_max_image_attachments', + array( + 'type' => 'integer', + 'description' => \__( 'Number of images to attach to posts.', 'activitypub' ), + 'default' => ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS, + ) + ); + \register_setting( + 'activitypub', + 'activitypub_object_type', + array( + 'type' => 'string', + 'description' => \__( 'The Activity-Object-Type', 'activitypub' ), + 'show_in_rest' => array( + 'schema' => array( + 'enum' => array( + 'note', + 'wordpress-post-format', + ), + ), + ), + 'default' => ACTIVITYPUB_DEFAULT_OBJECT_TYPE, + ) + ); + \register_setting( + 'activitypub', + 'activitypub_use_hashtags', + array( + 'type' => 'boolean', + 'description' => \__( 'Add hashtags in the content as native tags and replace the #tag with the tag-link', 'activitypub' ), + 'default' => '0', + ) + ); + \register_setting( + 'activitypub', + 'activitypub_use_opengraph', + array( + 'type' => 'boolean', + 'description' => \__( 'Automatically add "fediverse:creator" OpenGraph tags for Authors and the Blog-User.', 'activitypub' ), + 'default' => '1', + ) + ); + \register_setting( + 'activitypub', + 'activitypub_support_post_types', + array( + 'type' => 'string', + 'description' => \esc_html__( 'Enable ActivityPub support for post types', 'activitypub' ), + 'show_in_rest' => true, + 'default' => array( 'post' ), + ) + ); + \register_setting( + 'activitypub', + 'activitypub_actor_mode', + array( + 'type' => 'integer', + 'description' => \__( 'Choose your preferred Actor-Mode.', 'activitypub' ), + 'default' => ACTIVITYPUB_ACTOR_MODE, + ) + ); + + \register_setting( + 'activitypub', + 'activitypub_attribution_domains', + array( + 'type' => 'string', + 'description' => \__( 'Websites allowed to credit you.', 'activitypub' ), + 'default' => home_host(), + 'sanitize_callback' => function ( $value ) { + $value = explode( PHP_EOL, $value ); + $value = array_filter( array_map( 'trim', $value ) ); + $value = array_filter( array_map( 'esc_attr', $value ) ); + $value = implode( PHP_EOL, $value ); + + return $value; + }, + ) + ); + + \register_setting( + 'activitypub', + 'activitypub_authorized_fetch', + array( + 'type' => 'boolean', + 'description' => \__( 'Require HTTP signature authentication.', 'activitypub' ), + 'default' => false, + ) + ); + + \register_setting( + 'activitypub', + 'activitypub_mailer_new_follower', + array( + 'type' => 'boolean', + 'description' => \__( 'Send notifications via e-mail when a new follower is added.', 'activitypub' ), + 'default' => '0', + ) + ); + \register_setting( + 'activitypub', + 'activitypub_mailer_new_dm', + array( + 'type' => 'boolean', + 'description' => \__( 'Send notifications via e-mail when a direct message is received.', 'activitypub' ), + 'default' => '0', + ) + ); + + // Blog-User Settings. + \register_setting( + 'activitypub_blog', + 'activitypub_blog_description', + array( + 'type' => 'string', + 'description' => \esc_html__( 'The Description of the Blog-User', 'activitypub' ), + 'show_in_rest' => true, + 'default' => '', + ) + ); + \register_setting( + 'activitypub_blog', + 'activitypub_blog_identifier', + array( + 'type' => 'string', + 'description' => \esc_html__( 'The Identifier of the Blog-User', 'activitypub' ), + 'show_in_rest' => true, + 'default' => Blog::get_default_username(), + 'sanitize_callback' => function ( $value ) { + // Hack to allow dots in the username. + $parts = explode( '.', $value ); + $sanitized = array(); + + foreach ( $parts as $part ) { + $sanitized[] = \sanitize_title( $part ); + } + + $sanitized = implode( '.', $sanitized ); + + // Check for login or nicename. + $user = new WP_User_Query( + array( + 'search' => $sanitized, + 'search_columns' => array( 'user_login', 'user_nicename' ), + 'number' => 1, + 'hide_empty' => true, + 'fields' => 'ID', + ) + ); + + if ( $user->results ) { + add_settings_error( + 'activitypub_blog_identifier', + 'activitypub_blog_identifier', + \esc_html__( 'You cannot use an existing author\'s name for the blog profile ID.', 'activitypub' ), + 'error' + ); + + return Blog::get_default_username(); + } + + return $sanitized; + }, + ) + ); + \register_setting( + 'activitypub_blog', + 'activitypub_header_image', + array( + 'type' => 'integer', + 'description' => \__( 'The Attachment-ID of the Sites Header-Image', 'activitypub' ), + 'default' => null, + ) + ); + } + + /** + * Adds the ActivityPub settings to the Help tab. + */ + public static function add_settings_help_tab() { + require_once ACTIVITYPUB_PLUGIN_DIR . 'includes/help.php'; + } + /** * Adds the follower list to the Help tab. */ From a0e95fcebc84f8dcd6e21a19e02cc4951bdfac89 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 28 Feb 2025 12:14:17 +0100 Subject: [PATCH 04/18] Add relay settings --- includes/wp-admin/class-admin.php | 210 --------------------------- includes/wp-admin/class-settings.php | 22 +++ 2 files changed, 22 insertions(+), 210 deletions(-) diff --git a/includes/wp-admin/class-admin.php b/includes/wp-admin/class-admin.php index 6bd571413..9561ef7e0 100644 --- a/includes/wp-admin/class-admin.php +++ b/includes/wp-admin/class-admin.php @@ -117,216 +117,6 @@ public static function followers_list_page() { } } - /** - * Register ActivityPub settings - */ - public static function register_settings() { - \register_setting( - 'activitypub', - 'activitypub_post_content_type', - array( - 'type' => 'string', - 'description' => \__( 'Use title and link, summary, full or custom content', 'activitypub' ), - 'show_in_rest' => array( - 'schema' => array( - 'enum' => array( - 'title', - 'excerpt', - 'content', - ), - ), - ), - 'default' => 'content', - ) - ); - \register_setting( - 'activitypub', - 'activitypub_custom_post_content', - array( - 'type' => 'string', - 'description' => \__( 'Define your own custom post template', 'activitypub' ), - 'show_in_rest' => true, - 'default' => ACTIVITYPUB_CUSTOM_POST_CONTENT, - ) - ); - \register_setting( - 'activitypub', - 'activitypub_max_image_attachments', - array( - 'type' => 'integer', - 'description' => \__( 'Number of images to attach to posts.', 'activitypub' ), - 'default' => ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS, - ) - ); - \register_setting( - 'activitypub', - 'activitypub_object_type', - array( - 'type' => 'string', - 'description' => \__( 'The Activity-Object-Type', 'activitypub' ), - 'show_in_rest' => array( - 'schema' => array( - 'enum' => array( - 'note', - 'wordpress-post-format', - ), - ), - ), - 'default' => ACTIVITYPUB_DEFAULT_OBJECT_TYPE, - ) - ); - \register_setting( - 'activitypub', - 'activitypub_use_hashtags', - array( - 'type' => 'boolean', - 'description' => \__( 'Add hashtags in the content as native tags and replace the #tag with the tag-link', 'activitypub' ), - 'default' => '0', - ) - ); - \register_setting( - 'activitypub', - 'activitypub_use_opengraph', - array( - 'type' => 'boolean', - 'description' => \__( 'Automatically add "fediverse:creator" OpenGraph tags for Authors and the Blog-User.', 'activitypub' ), - 'default' => '1', - ) - ); - \register_setting( - 'activitypub', - 'activitypub_support_post_types', - array( - 'type' => 'string', - 'description' => \esc_html__( 'Enable ActivityPub support for post types', 'activitypub' ), - 'show_in_rest' => true, - 'default' => array( 'post' ), - ) - ); - \register_setting( - 'activitypub', - 'activitypub_actor_mode', - array( - 'type' => 'integer', - 'description' => \__( 'Choose your preferred Actor-Mode.', 'activitypub' ), - 'default' => ACTIVITYPUB_ACTOR_MODE, - ) - ); - - \register_setting( - 'activitypub', - 'activitypub_attribution_domains', - array( - 'type' => 'string', - 'description' => \__( 'Websites allowed to credit you.', 'activitypub' ), - 'default' => home_host(), - 'sanitize_callback' => function ( $value ) { - $value = explode( PHP_EOL, $value ); - $value = array_filter( array_map( 'trim', $value ) ); - $value = array_filter( array_map( 'esc_attr', $value ) ); - $value = implode( PHP_EOL, $value ); - - return $value; - }, - ) - ); - - \register_setting( - 'activitypub', - 'activitypub_authorized_fetch', - array( - 'type' => 'boolean', - 'description' => \__( 'Require HTTP signature authentication.', 'activitypub' ), - 'default' => false, - ) - ); - - \register_setting( - 'activitypub', - 'activitypub_mailer_new_follower', - array( - 'type' => 'boolean', - 'description' => \__( 'Send notifications via e-mail when a new follower is added.', 'activitypub' ), - 'default' => '0', - ) - ); - \register_setting( - 'activitypub', - 'activitypub_mailer_new_dm', - array( - 'type' => 'boolean', - 'description' => \__( 'Send notifications via e-mail when a direct message is received.', 'activitypub' ), - 'default' => '0', - ) - ); - - // Blog-User Settings. - \register_setting( - 'activitypub_blog', - 'activitypub_blog_description', - array( - 'type' => 'string', - 'description' => \esc_html__( 'The Description of the Blog-User', 'activitypub' ), - 'show_in_rest' => true, - 'default' => '', - ) - ); - \register_setting( - 'activitypub_blog', - 'activitypub_blog_identifier', - array( - 'type' => 'string', - 'description' => \esc_html__( 'The Identifier of the Blog-User', 'activitypub' ), - 'show_in_rest' => true, - 'default' => Blog::get_default_username(), - 'sanitize_callback' => function ( $value ) { - // Hack to allow dots in the username. - $parts = explode( '.', $value ); - $sanitized = array(); - - foreach ( $parts as $part ) { - $sanitized[] = \sanitize_title( $part ); - } - - $sanitized = implode( '.', $sanitized ); - - // Check for login or nicename. - $user = new WP_User_Query( - array( - 'search' => $sanitized, - 'search_columns' => array( 'user_login', 'user_nicename' ), - 'number' => 1, - 'hide_empty' => true, - 'fields' => 'ID', - ) - ); - - if ( $user->results ) { - add_settings_error( - 'activitypub_blog_identifier', - 'activitypub_blog_identifier', - \esc_html__( 'You cannot use an existing author\'s name for the blog profile ID.', 'activitypub' ), - 'error' - ); - - return Blog::get_default_username(); - } - - return $sanitized; - }, - ) - ); - \register_setting( - 'activitypub_blog', - 'activitypub_header_image', - array( - 'type' => 'integer', - 'description' => \__( 'The Attachment-ID of the Sites Header-Image', 'activitypub' ), - 'default' => null, - ) - ); - } - /** * Adds the ActivityPub settings to the Help tab. */ diff --git a/includes/wp-admin/class-settings.php b/includes/wp-admin/class-settings.php index 42805140c..b377b2bf9 100644 --- a/includes/wp-admin/class-settings.php +++ b/includes/wp-admin/class-settings.php @@ -171,6 +171,28 @@ public static function register_settings() { ) ); + + \register_setting( + 'activitypub', + 'activitypub_relays', + array( + 'type' => 'array', + 'description' => \__( 'Relays', 'activitypub' ), + 'default' => array(), + 'sanitize_callback' => function ( $value ) { + if ( ! is_array( $value ) ) { + $value = explode( PHP_EOL, $value ); + } + + $value = array_map( 'trim', $value ); + $value = array_filter( $value ); + $value = array_map( 'esc_url', $value ); + + return $value; + }, + ) + ); + // Blog-User Settings. \register_setting( 'activitypub_blog', From 85049d0329bbb34a0a1ce7f211a59ce1d12efc9e Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 28 Feb 2025 12:34:02 +0100 Subject: [PATCH 05/18] Add settings --- includes/wp-admin/class-admin.php | 7 ---- includes/wp-admin/class-settings-fields.php | 43 ++++++++++++++++++++- includes/wp-admin/class-settings.php | 1 - 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/includes/wp-admin/class-admin.php b/includes/wp-admin/class-admin.php index 9561ef7e0..b23844350 100644 --- a/includes/wp-admin/class-admin.php +++ b/includes/wp-admin/class-admin.php @@ -117,13 +117,6 @@ public static function followers_list_page() { } } - /** - * Adds the ActivityPub settings to the Help tab. - */ - public static function add_settings_help_tab() { - require_once ACTIVITYPUB_PLUGIN_DIR . 'includes/help.php'; - } - /** * Adds the follower list to the Help tab. */ diff --git a/includes/wp-admin/class-settings-fields.php b/includes/wp-admin/class-settings-fields.php index d1749dbae..191e2eb41 100644 --- a/includes/wp-admin/class-settings-fields.php +++ b/includes/wp-admin/class-settings-fields.php @@ -51,6 +51,13 @@ public static function register_settings_fields() { 'activitypub_settings' ); + add_settings_section( + 'activitypub_server', + __( 'Server', 'activitypub' ), + '__return_empty_string', + 'activitypub_settings' + ); + // Add settings fields. add_settings_field( 'activitypub_actor_mode', @@ -129,7 +136,16 @@ public static function register_settings_fields() { __( 'Blocklist', 'activitypub' ), array( self::class, 'render_blocklist_field' ), 'activitypub_settings', - 'activitypub_general' + 'activitypub_server' + ); + + add_settings_field( + 'activitypub_relays', + __( 'Relays', 'activitypub' ), + array( self::class, 'render_relays_field' ), + 'activitypub_settings', + 'activitypub_server', + array( 'label_for' => 'activitypub_relays' ) ); add_settings_field( @@ -137,7 +153,7 @@ public static function register_settings_fields() { __( 'Outbox Retention Period', 'activitypub' ), array( self::class, 'render_outbox_purge_days_field' ), 'activitypub_settings', - 'activitypub_general', + 'activitypub_server', array( 'label_for' => 'activitypub_outbox_purge_days' ) ); @@ -460,4 +476,27 @@ public static function render_authorized_fetch_field() {

+

+ Fediverse-Relay distributes content across instances, expanding reach, engagement, and discoverability, especially for smaller instances.', 'activitypub' ), 'default' ); ?> +

+ +

+ Inbox-URLs (e.g. https://relay.example.com/inbox) of the relays you want to use, one per line.', 'activitypub' ), array( 'strong' => array() ) ); ?> + relaylist.com or on FediDB.', 'activitypub' ), 'default' ); ?> +

+ Date: Fri, 28 Feb 2025 13:28:01 +0100 Subject: [PATCH 06/18] Add tests --- includes/class-dispatcher.php | 27 +++++++++++ tests/includes/class-test-dispatcher.php | 60 ++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index 4ef43f886..88d68c13c 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -97,6 +97,9 @@ public static function process_outbox( $id ) { // Send to mentioned and replied-to users. Everyone other than followers. self::send_to_interactees( $activity, $actor->get__id(), $outbox_item ); + // Send to relays. + self::send_to_relays( $activity, $actor, $outbox_item ); + if ( self::should_send_to_followers( $activity, $actor, $outbox_item ) ) { Scheduler::async_batch( self::$callback, @@ -405,4 +408,28 @@ protected static function should_send_to_followers( $activity, $actor, $outbox_i */ return apply_filters( 'activitypub_send_activity_to_followers', $send, $activity, $actor->get__id(), $outbox_item ); } + + /** + * Add Inboxes of Relays. + * + * @param Activity $activity The ActivityPub Activity. + * @param \Activitypub\Model\User|\Activitypub\Model\Blog $actor The Actor object. + * @param \WP_Post $outbox_item The Outbox item. + */ + public static function send_to_relays( $activity, $actor, $outbox_item ) { + // Check if follower endpoint is set. + $cc = $activity->get_cc() ?? array(); + $to = $activity->get_to() ?? array(); + + $audience = array_merge( $cc, $to ); + + // Check if activity is public. + if ( ! in_array( 'https://www.w3.org/ns/activitystreams#Public', $audience, true ) ) { + return; + } + + $relays = \get_option( 'activitypub_relays', array() ); + + self::send_to_inboxes( $relays, $outbox_item->ID ); + } } diff --git a/tests/includes/class-test-dispatcher.php b/tests/includes/class-test-dispatcher.php index cd24a5c42..ba02809f3 100644 --- a/tests/includes/class-test-dispatcher.php +++ b/tests/includes/class-test-dispatcher.php @@ -7,6 +7,7 @@ use Activitypub\Activity\Activity; use Activitypub\Collection\Actors; +use Activitypub\Collection\Outbox; use Activitypub\Collection\Followers; use Activitypub\Dispatcher; @@ -171,6 +172,65 @@ function () use ( $code, $message ) { remove_all_filters( 'pre_http_request' ); } + public function test_send_to_relays() { + global $wp_actions; + + $post_id = self::factory()->post->create( array( 'post_author' => self::$user_id ) ); + $outbox_item = $this->get_latest_outbox_item( \add_query_arg( 'p', $post_id, \home_url( '/' ) ) ); + $fake_request = function () { + return new \WP_Error( 'test', 'test' ); + }; + + add_filter( 'pre_http_request', $fake_request, 10, 3 ); + + Dispatcher::send_to_relays( $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); + + // Test how often the request was sent. + $this->assertEquals( 0, did_action( 'activitypub_sent_to_inbox' ) ); + + $wp_actions = null; + + // Add a relay. + $relays = array( 'https://relay1.example.com/inbox' ); + update_option( 'activitypub_relays', $relays ); + + Dispatcher::send_to_relays( $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); + + // Test how often the request was sent. + $this->assertEquals( 1, did_action( 'activitypub_sent_to_inbox' ) ); + + $wp_actions = null; + + // Add a relay. + $relays = array( 'https://relay1.example.com/inbox', 'https://relay2.example.com/inbox' ); + update_option( 'activitypub_relays', $relays ); + + Dispatcher::send_to_relays( $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); + + // Test how often the request was sent. + $this->assertEquals( 2, did_action( 'activitypub_sent_to_inbox' ) ); + + $wp_actions = null; + + $private_activity = Outbox::get_activity( $outbox_item->ID ); + $private_activity->set_to( null ); + $private_activity->set_cc( null ); + + // Clone object. + $private_activity = clone $private_activity; + + Dispatcher::send_to_relays( $private_activity, Actors::get_by_id( self::$user_id ), $outbox_item ); + + // Test how often the request was sent. + $this->assertEquals( 0, did_action( 'activitypub_sent_to_inbox' ) ); + + \remove_filter( 'pre_http_request', $fake_request, 10 ); + + \delete_option( 'activitypub_relays' ); + \wp_delete_post( $post_id ); + \wp_delete_post( $outbox_item->ID ); + } + /** * Returns a mock of an Activity object. * From 60eb3db526c4abff5fffbb628940933c9d533e47 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 28 Feb 2025 13:28:51 +0100 Subject: [PATCH 07/18] fixed phpcs --- includes/wp-admin/class-settings.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/wp-admin/class-settings.php b/includes/wp-admin/class-settings.php index 2291f66f0..73526d455 100644 --- a/includes/wp-admin/class-settings.php +++ b/includes/wp-admin/class-settings.php @@ -175,9 +175,9 @@ public static function register_settings() { 'activitypub', 'activitypub_relays', array( - 'type' => 'array', - 'description' => \__( 'Relays', 'activitypub' ), - 'default' => array(), + 'type' => 'array', + 'description' => \__( 'Relays', 'activitypub' ), + 'default' => array(), 'sanitize_callback' => function ( $value ) { if ( ! is_array( $value ) ) { $value = explode( PHP_EOL, $value ); From f59c9d6ffbc78ba6244b83d43c14862a8a038331 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 28 Feb 2025 13:29:59 +0100 Subject: [PATCH 08/18] Added changelog --- CHANGELOG.md | 1 + readme.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c81d1e15..7d1057ed7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Upgrade script to fix Follower json representations with unescaped backslashes. * Centralized place for sanitization functions. +* Support for sending Activities to ActivityPub Relays, to improve discoverability of public content. ### Changed diff --git a/readme.txt b/readme.txt index ab6009b0a..b2b5ba3cd 100644 --- a/readme.txt +++ b/readme.txt @@ -133,6 +133,7 @@ For reasons of data protection, it is not possible to see the followers of other * Added: Upgrade script to fix Follower json representations with unescaped backslashes. * Added: Centralized place for sanitization functions. +* Added: Support for sending Activities to ActivityPub Relays, to improve discoverability of public content. * Changed: Bumped minimum required WordPress version to 6.4. * Changed: Use a later hook for Posts to get published to the Outbox, to get sure all `post_meta`s and `taxonomy`s are set stored properly. * Changed: Use webfinger as author email for comments from the Fediverse. From 5400ead7fad7a4dd20e5642e7cffcf1a4c376e59 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 28 Feb 2025 13:34:37 +0100 Subject: [PATCH 09/18] Fix PHPCS --- 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 ba02809f3..d961c56e8 100644 --- a/tests/includes/class-test-dispatcher.php +++ b/tests/includes/class-test-dispatcher.php @@ -172,6 +172,11 @@ function () use ( $code, $message ) { remove_all_filters( 'pre_http_request' ); } + /** + * Test send_to_relays. + * + * @covers ::send_to_relays + */ public function test_send_to_relays() { global $wp_actions; @@ -188,6 +193,7 @@ public function test_send_to_relays() { // Test how often the request was sent. $this->assertEquals( 0, did_action( 'activitypub_sent_to_inbox' ) ); + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $wp_actions = null; // Add a relay. @@ -199,6 +205,7 @@ public function test_send_to_relays() { // Test how often the request was sent. $this->assertEquals( 1, did_action( 'activitypub_sent_to_inbox' ) ); + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $wp_actions = null; // Add a relay. @@ -210,6 +217,7 @@ public function test_send_to_relays() { // Test how often the request was sent. $this->assertEquals( 2, did_action( 'activitypub_sent_to_inbox' ) ); + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $wp_actions = null; $private_activity = Outbox::get_activity( $outbox_item->ID ); From 4734d587d1a7b6977949879165d38fe8706cf5e1 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 28 Feb 2025 13:49:51 +0100 Subject: [PATCH 10/18] check if relay is empty --- includes/class-dispatcher.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index 88d68c13c..c3dbebfa4 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -430,6 +430,10 @@ public static function send_to_relays( $activity, $actor, $outbox_item ) { $relays = \get_option( 'activitypub_relays', array() ); + if ( empty( $relays ) ) { + return; + } + self::send_to_inboxes( $relays, $outbox_item->ID ); } } From 53666012785cb4d1247aadfa5b568495b7f20df0 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 3 Mar 2025 08:34:43 +0100 Subject: [PATCH 11/18] use sanitize class --- includes/wp-admin/class-settings.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/includes/wp-admin/class-settings.php b/includes/wp-admin/class-settings.php index 73526d455..3a42587e2 100644 --- a/includes/wp-admin/class-settings.php +++ b/includes/wp-admin/class-settings.php @@ -178,17 +178,7 @@ public static function register_settings() { 'type' => 'array', 'description' => \__( 'Relays', 'activitypub' ), 'default' => array(), - 'sanitize_callback' => function ( $value ) { - if ( ! is_array( $value ) ) { - $value = explode( PHP_EOL, $value ); - } - - $value = array_map( 'trim', $value ); - $value = array_filter( $value ); - $value = array_map( 'esc_url', $value ); - - return $value; - }, + 'sanitize_callback' => array( Sanitize::class, 'url_list' ), ) ); From be40065fd9db793ca86aa1f372ccd4b1b0d5eb2f Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 3 Mar 2025 09:19:53 +0100 Subject: [PATCH 12/18] use a more generic filter and update the relays feature to use the filter --- includes/class-dispatcher.php | 47 ++++++++++++++---------- integration/class-stream-connector.php | 35 ------------------ tests/includes/class-test-dispatcher.php | 24 +++++++----- 3 files changed, 42 insertions(+), 64 deletions(-) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index c3dbebfa4..37e45376e 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -49,12 +49,13 @@ public static function init() { \add_action( 'activitypub_process_outbox', array( self::class, 'process_outbox' ) ); // Default filters to add Inboxes to sent to. - \add_filter( 'activitypub_interactees_inboxes', array( self::class, 'add_inboxes_by_mentioned_actors' ), 10, 3 ); - \add_filter( 'activitypub_interactees_inboxes', array( self::class, 'add_inboxes_of_replied_urls' ), 10, 3 ); + \add_filter( 'activitypub_custom_inboxes', array( self::class, 'add_inboxes_by_mentioned_actors' ), 10, 3 ); + \add_filter( 'activitypub_custom_inboxes', array( self::class, 'add_inboxes_of_replied_urls' ), 10, 3 ); + \add_filter( 'activitypub_custom_inboxes', array( self::class, 'add_inboxes_of_relays' ), 10, 3 ); // Fallback for `activitypub_send_to_inboxes` filter. \add_filter( - 'activitypub_interactees_inboxes', + 'activitypub_custom_inboxes', function ( $inboxes, $actor_id, $activity ) { /** * Filters the list of interactees inboxes to send the Activity to. @@ -63,9 +64,13 @@ function ( $inboxes, $actor_id, $activity ) { * @param int $actor_id The actor ID. * @param Activity $activity The ActivityPub Activity. * - * @deprecated 5.2.0 Use `activitypub_interactees_inboxes` instead. + * @deprecated 5.2.0 Use `activitypub_custom_inboxes` instead. + * @deprecated 5.4.0 Use `activitypub_custom_inboxes` instead. */ - return \apply_filters_deprecated( 'activitypub_send_to_inboxes', array( $inboxes, $actor_id, $activity ), '5.2.0', 'activitypub_interactees_inboxes' ); + $inboxes = \apply_filters_deprecated( 'activitypub_send_to_inboxes', array( $inboxes, $actor_id, $activity ), '5.2.0', 'activitypub_custom_inboxes' ); + $inboxes = \apply_filters_deprecated( 'activitypub_interactees_inboxes', array( $inboxes, $actor_id, $activity ), '5.4.0', 'activitypub_custom_inboxes' ); + + return $inboxes; }, 10, 3 @@ -95,10 +100,7 @@ public static function process_outbox( $id ) { $activity = Outbox::get_activity( $outbox_item ); // Send to mentioned and replied-to users. Everyone other than followers. - self::send_to_interactees( $activity, $actor->get__id(), $outbox_item ); - - // Send to relays. - self::send_to_relays( $activity, $actor, $outbox_item ); + self::send_to_custom_inboxes( $activity, $actor->get__id(), $outbox_item ); if ( self::should_send_to_followers( $activity, $actor, $outbox_item ) ) { Scheduler::async_batch( @@ -253,13 +255,15 @@ private static function schedule_retry( $retries, $outbox_item_id, $attempt = 1 } /** - * Send an Activity to all followers and mentioned users. + * Send an Activity to a custom list of inboxes, like mentioned users or replied-to posts. + * + * For all custom implementations, please use the `activitypub_custom_inboxes` filter. * * @param Activity $activity The ActivityPub Activity. * @param int $actor_id The actor ID. * @param \WP_Post $outbox_item The WordPress object. */ - private static function send_to_interactees( $activity, $actor_id, $outbox_item = null ) { + private static function send_to_custom_inboxes( $activity, $actor_id, $outbox_item = null ) { /** * Filters the list of inboxes to send the Activity to. * @@ -267,7 +271,7 @@ private static function send_to_interactees( $activity, $actor_id, $outbox_item * @param int $actor_id The actor ID. * @param Activity $activity The ActivityPub Activity. */ - $inboxes = apply_filters( 'activitypub_interactees_inboxes', array(), $actor_id, $activity ); + $inboxes = apply_filters( 'activitypub_custom_inboxes', array(), $actor_id, $activity ); $inboxes = array_unique( $inboxes ); $retries = self::send_to_inboxes( $inboxes, $outbox_item->ID ); @@ -412,11 +416,13 @@ protected static function should_send_to_followers( $activity, $actor, $outbox_i /** * Add Inboxes of Relays. * - * @param Activity $activity The ActivityPub Activity. - * @param \Activitypub\Model\User|\Activitypub\Model\Blog $actor The Actor object. - * @param \WP_Post $outbox_item The Outbox item. + * @param array $inboxes The list of Inboxes. + * @param int $actor_id The Actor-ID. + * @param Activity $activity The ActivityPub Activity. + * + * @return array The filtered Inboxes. */ - public static function send_to_relays( $activity, $actor, $outbox_item ) { + public static function add_inboxes_of_relays( $inboxes, $actor_id, $activity ) { // Check if follower endpoint is set. $cc = $activity->get_cc() ?? array(); $to = $activity->get_to() ?? array(); @@ -425,15 +431,18 @@ public static function send_to_relays( $activity, $actor, $outbox_item ) { // Check if activity is public. if ( ! in_array( 'https://www.w3.org/ns/activitystreams#Public', $audience, true ) ) { - return; + return $inboxes; } $relays = \get_option( 'activitypub_relays', array() ); if ( empty( $relays ) ) { - return; + return $inboxes; } - self::send_to_inboxes( $relays, $outbox_item->ID ); + $inboxes = array_merge( $inboxes, $relays ); + $inboxes = array_unique( $inboxes ); + + return $inboxes; } } diff --git a/integration/class-stream-connector.php b/integration/class-stream-connector.php index 8a7931a97..e59c6009f 100644 --- a/integration/class-stream-connector.php +++ b/integration/class-stream-connector.php @@ -129,41 +129,6 @@ public function callback_activitypub_notification_follow( $notification ) { ); } - /** - * Callback for activitypub_send_to_inboxes. - * - * @param array $result The result of the remote post request. - * @param string $inbox The inbox URL. - * @param string $json The ActivityPub Activity JSON. - * @param int $actor_id The actor ID. - * @param int $outbox_item_id The Outbox item ID. - */ - public function callback_activitypub_sent_to_inbox( $result, $inbox, $json, $actor_id, $outbox_item_id ) { - if ( ! \is_wp_error( $result ) ) { - return; - } - - $outbox_item = \get_post( $outbox_item_id ); - $outbox_data = $this->prepare_outbox_data_for_response( $outbox_item ); - - $this->log( - // translators: 1: post title. - sprintf( __( 'Outbox error for "%1$s"', 'activitypub' ), $outbox_data['title'] ), - array( - 'error' => wp_json_encode( - array( - 'inbox' => $inbox, - 'code' => $result->get_error_code(), - 'message' => $result->get_error_message(), - ) - ), - ), - $outbox_data['id'], - $outbox_data['type'], - 'processed' - ); - } - /** * Callback for activitypub_outbox_processing_complete. * diff --git a/tests/includes/class-test-dispatcher.php b/tests/includes/class-test-dispatcher.php index d961c56e8..722fb985e 100644 --- a/tests/includes/class-test-dispatcher.php +++ b/tests/includes/class-test-dispatcher.php @@ -104,11 +104,11 @@ public function test_process_outbox() { * This test can be removed when the filter is removed. * * @covers ::maybe_add_inboxes_of_blog_user - * @expectedDeprecated activitypub_send_to_inboxes + * @expectedDeprecated activitypub_interactees_inboxes */ public function test_deprecated_filter() { add_filter( - 'activitypub_send_to_inboxes', + 'activitypub_interactees_inboxes', function ( $inboxes ) { $inboxes[] = 'https://example.com/inbox'; @@ -116,10 +116,10 @@ function ( $inboxes ) { } ); - $inboxes = apply_filters( 'activitypub_interactees_inboxes', array(), 1, $this->get_activity_mock() ); + $inboxes = apply_filters( 'activitypub_custom_inboxes', array(), 1, $this->get_activity_mock() ); $this->assertContains( 'https://example.com/inbox', $inboxes ); - remove_all_filters( 'activitypub_send_to_inboxes' ); + remove_all_filters( 'activitypub_interactees_inboxes' ); } /** @@ -173,9 +173,9 @@ function () use ( $code, $message ) { } /** - * Test send_to_relays. + * Test send_to_custom_inboxes. * - * @covers ::send_to_relays + * @covers ::send_to_custom_inboxes */ public function test_send_to_relays() { global $wp_actions; @@ -188,7 +188,11 @@ public function test_send_to_relays() { add_filter( 'pre_http_request', $fake_request, 10, 3 ); - Dispatcher::send_to_relays( $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); + // Make `Dispatcher::send_to_custom_inboxes` a public method. + $send_to_custom_inboxes = new ReflectionMethod( Dispatcher::class, 'send_to_custom_inboxes' ); + $send_to_custom_inboxes->setAccessible( true ); + + $send_to_custom_inboxes->invoke( null, $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); // Test how often the request was sent. $this->assertEquals( 0, did_action( 'activitypub_sent_to_inbox' ) ); @@ -200,7 +204,7 @@ public function test_send_to_relays() { $relays = array( 'https://relay1.example.com/inbox' ); update_option( 'activitypub_relays', $relays ); - Dispatcher::send_to_relays( $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); + $send_to_custom_inboxes->invoke( null, $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); // Test how often the request was sent. $this->assertEquals( 1, did_action( 'activitypub_sent_to_inbox' ) ); @@ -212,7 +216,7 @@ public function test_send_to_relays() { $relays = array( 'https://relay1.example.com/inbox', 'https://relay2.example.com/inbox' ); update_option( 'activitypub_relays', $relays ); - Dispatcher::send_to_relays( $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); + $send_to_custom_inboxes->invoke( null, $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); // Test how often the request was sent. $this->assertEquals( 2, did_action( 'activitypub_sent_to_inbox' ) ); @@ -227,7 +231,7 @@ public function test_send_to_relays() { // Clone object. $private_activity = clone $private_activity; - Dispatcher::send_to_relays( $private_activity, Actors::get_by_id( self::$user_id ), $outbox_item ); + $send_to_custom_inboxes->invoke( null, $private_activity, Actors::get_by_id( self::$user_id ), $outbox_item ); // Test how often the request was sent. $this->assertEquals( 0, did_action( 'activitypub_sent_to_inbox' ) ); From 2384b9f36314dfb5fb3b289595f9becf8fcdb24f Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 3 Mar 2025 19:35:44 +0100 Subject: [PATCH 13/18] add `code` to allowlist props @obenland --- includes/wp-admin/class-settings-fields.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/wp-admin/class-settings-fields.php b/includes/wp-admin/class-settings-fields.php index 0461cb5a6..8bc29f3b0 100644 --- a/includes/wp-admin/class-settings-fields.php +++ b/includes/wp-admin/class-settings-fields.php @@ -500,7 +500,7 @@ class="large-text" rows="5" >

- Inbox-URLs (e.g. https://relay.example.com/inbox) of the relays you want to use, one per line.', 'activitypub' ), array( 'strong' => array() ) ); ?> + Inbox-URLs (e.g. https://relay.example.com/inbox) of the relays you want to use, one per line.', 'activitypub' ), array( 'strong' => array(), 'code' => array() ) ); ?> relaylist.com or on FediDB.', 'activitypub' ), 'default' ); ?>

Date: Mon, 3 Mar 2025 19:37:12 +0100 Subject: [PATCH 14/18] remove duplicate code props @obenland --- includes/class-dispatcher.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index 37e45376e..6cc49400e 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -440,9 +440,6 @@ public static function add_inboxes_of_relays( $inboxes, $actor_id, $activity ) { return $inboxes; } - $inboxes = array_merge( $inboxes, $relays ); - $inboxes = array_unique( $inboxes ); - - return $inboxes; + return array_merge( $inboxes, $relays ); } } From b611e096c7bd692ccfab2df881d9a6c796bfdcef Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 3 Mar 2025 19:39:01 +0100 Subject: [PATCH 15/18] move description below textbox props @obenland --- includes/wp-admin/class-settings-fields.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/wp-admin/class-settings-fields.php b/includes/wp-admin/class-settings-fields.php index 8bc29f3b0..d5ef32897 100644 --- a/includes/wp-admin/class-settings-fields.php +++ b/includes/wp-admin/class-settings-fields.php @@ -489,9 +489,6 @@ public static function render_authorized_fetch_field() { public static function render_relays_field() { $value = get_option( 'activitypub_relays', array() ); ?> -

- Fediverse-Relay distributes content across instances, expanding reach, engagement, and discoverability, especially for smaller instances.', 'activitypub' ), 'default' ); ?> -

+

+ Fediverse-Relay distributes content across instances, expanding reach, engagement, and discoverability, especially for smaller instances.', 'activitypub' ), 'default' ); ?> +

Inbox-URLs (e.g. https://relay.example.com/inbox) of the relays you want to use, one per line.', 'activitypub' ), array( 'strong' => array(), 'code' => array() ) ); ?> relaylist.com or on FediDB.', 'activitypub' ), 'default' ); ?> From 553a1cdbde12667b4f53ac5a9351d3139ee60b1c Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 3 Mar 2025 19:39:42 +0100 Subject: [PATCH 16/18] Fix phpcs --- includes/wp-admin/class-settings-fields.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/includes/wp-admin/class-settings-fields.php b/includes/wp-admin/class-settings-fields.php index d5ef32897..e1f356ef8 100644 --- a/includes/wp-admin/class-settings-fields.php +++ b/includes/wp-admin/class-settings-fields.php @@ -500,7 +500,15 @@ class="large-text" Fediverse-Relay distributes content across instances, expanding reach, engagement, and discoverability, especially for smaller instances.', 'activitypub' ), 'default' ); ?>

- Inbox-URLs (e.g. https://relay.example.com/inbox) of the relays you want to use, one per line.', 'activitypub' ), array( 'strong' => array(), 'code' => array() ) ); ?> + Inbox-URLs (e.g. https://relay.example.com/inbox) of the relays you want to use, one per line.', 'activitypub' ), + array( + 'strong' => array(), + 'code' => array(), + ) + ); + ?> relaylist.com or on FediDB.', 'activitypub' ), 'default' ); ?>

Date: Tue, 4 Mar 2025 17:33:54 +0100 Subject: [PATCH 17/18] rename filter --- includes/class-dispatcher.php | 20 ++++++++++---------- tests/includes/class-test-dispatcher.php | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index 6cc49400e..bb80b2db5 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -49,13 +49,13 @@ public static function init() { \add_action( 'activitypub_process_outbox', array( self::class, 'process_outbox' ) ); // Default filters to add Inboxes to sent to. - \add_filter( 'activitypub_custom_inboxes', array( self::class, 'add_inboxes_by_mentioned_actors' ), 10, 3 ); - \add_filter( 'activitypub_custom_inboxes', array( self::class, 'add_inboxes_of_replied_urls' ), 10, 3 ); - \add_filter( 'activitypub_custom_inboxes', array( self::class, 'add_inboxes_of_relays' ), 10, 3 ); + \add_filter( 'activitypub_additional_inboxes', array( self::class, 'add_inboxes_by_mentioned_actors' ), 10, 3 ); + \add_filter( 'activitypub_additional_inboxes', array( self::class, 'add_inboxes_of_replied_urls' ), 10, 3 ); + \add_filter( 'activitypub_additional_inboxes', array( self::class, 'add_inboxes_of_relays' ), 10, 3 ); // Fallback for `activitypub_send_to_inboxes` filter. \add_filter( - 'activitypub_custom_inboxes', + 'activitypub_additional_inboxes', function ( $inboxes, $actor_id, $activity ) { /** * Filters the list of interactees inboxes to send the Activity to. @@ -64,11 +64,11 @@ function ( $inboxes, $actor_id, $activity ) { * @param int $actor_id The actor ID. * @param Activity $activity The ActivityPub Activity. * - * @deprecated 5.2.0 Use `activitypub_custom_inboxes` instead. - * @deprecated 5.4.0 Use `activitypub_custom_inboxes` instead. + * @deprecated 5.2.0 Use `activitypub_additional_inboxes` instead. + * @deprecated 5.4.0 Use `activitypub_additional_inboxes` instead. */ - $inboxes = \apply_filters_deprecated( 'activitypub_send_to_inboxes', array( $inboxes, $actor_id, $activity ), '5.2.0', 'activitypub_custom_inboxes' ); - $inboxes = \apply_filters_deprecated( 'activitypub_interactees_inboxes', array( $inboxes, $actor_id, $activity ), '5.4.0', 'activitypub_custom_inboxes' ); + $inboxes = \apply_filters_deprecated( 'activitypub_send_to_inboxes', array( $inboxes, $actor_id, $activity ), '5.2.0', 'activitypub_additional_inboxes' ); + $inboxes = \apply_filters_deprecated( 'activitypub_interactees_inboxes', array( $inboxes, $actor_id, $activity ), '5.4.0', 'activitypub_additional_inboxes' ); return $inboxes; }, @@ -257,7 +257,7 @@ private static function schedule_retry( $retries, $outbox_item_id, $attempt = 1 /** * Send an Activity to a custom list of inboxes, like mentioned users or replied-to posts. * - * For all custom implementations, please use the `activitypub_custom_inboxes` filter. + * For all custom implementations, please use the `activitypub_additional_inboxes` filter. * * @param Activity $activity The ActivityPub Activity. * @param int $actor_id The actor ID. @@ -271,7 +271,7 @@ private static function send_to_custom_inboxes( $activity, $actor_id, $outbox_it * @param int $actor_id The actor ID. * @param Activity $activity The ActivityPub Activity. */ - $inboxes = apply_filters( 'activitypub_custom_inboxes', array(), $actor_id, $activity ); + $inboxes = apply_filters( 'activitypub_additional_inboxes', array(), $actor_id, $activity ); $inboxes = array_unique( $inboxes ); $retries = self::send_to_inboxes( $inboxes, $outbox_item->ID ); diff --git a/tests/includes/class-test-dispatcher.php b/tests/includes/class-test-dispatcher.php index 722fb985e..8b0c179ff 100644 --- a/tests/includes/class-test-dispatcher.php +++ b/tests/includes/class-test-dispatcher.php @@ -116,7 +116,7 @@ function ( $inboxes ) { } ); - $inboxes = apply_filters( 'activitypub_custom_inboxes', array(), 1, $this->get_activity_mock() ); + $inboxes = apply_filters( 'activitypub_additional_inboxes', array(), 1, $this->get_activity_mock() ); $this->assertContains( 'https://example.com/inbox', $inboxes ); remove_all_filters( 'activitypub_interactees_inboxes' ); From 4b3f3a6e7da20c6c2e3a4ebd8468962b5bd78f92 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 4 Mar 2025 18:58:00 +0100 Subject: [PATCH 18/18] rename function --- includes/class-dispatcher.php | 4 ++-- tests/includes/class-test-dispatcher.php | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/includes/class-dispatcher.php b/includes/class-dispatcher.php index bb80b2db5..6fdfbd707 100644 --- a/includes/class-dispatcher.php +++ b/includes/class-dispatcher.php @@ -100,7 +100,7 @@ public static function process_outbox( $id ) { $activity = Outbox::get_activity( $outbox_item ); // Send to mentioned and replied-to users. Everyone other than followers. - self::send_to_custom_inboxes( $activity, $actor->get__id(), $outbox_item ); + self::send_to_additional_inboxes( $activity, $actor->get__id(), $outbox_item ); if ( self::should_send_to_followers( $activity, $actor, $outbox_item ) ) { Scheduler::async_batch( @@ -263,7 +263,7 @@ private static function schedule_retry( $retries, $outbox_item_id, $attempt = 1 * @param int $actor_id The actor ID. * @param \WP_Post $outbox_item The WordPress object. */ - private static function send_to_custom_inboxes( $activity, $actor_id, $outbox_item = null ) { + private static function send_to_additional_inboxes( $activity, $actor_id, $outbox_item = null ) { /** * Filters the list of inboxes to send the Activity to. * diff --git a/tests/includes/class-test-dispatcher.php b/tests/includes/class-test-dispatcher.php index 8b0c179ff..296bd63d2 100644 --- a/tests/includes/class-test-dispatcher.php +++ b/tests/includes/class-test-dispatcher.php @@ -173,9 +173,9 @@ function () use ( $code, $message ) { } /** - * Test send_to_custom_inboxes. + * Test send_to_additional_inboxes. * - * @covers ::send_to_custom_inboxes + * @covers ::send_to_additional_inboxes */ public function test_send_to_relays() { global $wp_actions; @@ -188,11 +188,11 @@ public function test_send_to_relays() { add_filter( 'pre_http_request', $fake_request, 10, 3 ); - // Make `Dispatcher::send_to_custom_inboxes` a public method. - $send_to_custom_inboxes = new ReflectionMethod( Dispatcher::class, 'send_to_custom_inboxes' ); - $send_to_custom_inboxes->setAccessible( true ); + // Make `Dispatcher::send_to_additional_inboxes` a public method. + $send_to_additional_inboxes = new ReflectionMethod( Dispatcher::class, 'send_to_additional_inboxes' ); + $send_to_additional_inboxes->setAccessible( true ); - $send_to_custom_inboxes->invoke( null, $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); + $send_to_additional_inboxes->invoke( null, $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); // Test how often the request was sent. $this->assertEquals( 0, did_action( 'activitypub_sent_to_inbox' ) ); @@ -204,7 +204,7 @@ public function test_send_to_relays() { $relays = array( 'https://relay1.example.com/inbox' ); update_option( 'activitypub_relays', $relays ); - $send_to_custom_inboxes->invoke( null, $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); + $send_to_additional_inboxes->invoke( null, $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); // Test how often the request was sent. $this->assertEquals( 1, did_action( 'activitypub_sent_to_inbox' ) ); @@ -216,7 +216,7 @@ public function test_send_to_relays() { $relays = array( 'https://relay1.example.com/inbox', 'https://relay2.example.com/inbox' ); update_option( 'activitypub_relays', $relays ); - $send_to_custom_inboxes->invoke( null, $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); + $send_to_additional_inboxes->invoke( null, $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item ); // Test how often the request was sent. $this->assertEquals( 2, did_action( 'activitypub_sent_to_inbox' ) ); @@ -231,7 +231,7 @@ public function test_send_to_relays() { // Clone object. $private_activity = clone $private_activity; - $send_to_custom_inboxes->invoke( null, $private_activity, Actors::get_by_id( self::$user_id ), $outbox_item ); + $send_to_additional_inboxes->invoke( null, $private_activity, Actors::get_by_id( self::$user_id ), $outbox_item ); // Test how often the request was sent. $this->assertEquals( 0, did_action( 'activitypub_sent_to_inbox' ) );