diff --git a/.github/changelog/2448-from-description b/.github/changelog/2448-from-description new file mode 100644 index 0000000000..f5320084d3 --- /dev/null +++ b/.github/changelog/2448-from-description @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Fixed compatibility with Pixelfed and similar platforms by treating activities without recipients as public, ensuring boosts and reposts work correctly. diff --git a/includes/functions.php b/includes/functions.php index f236c15c1d..34db3b0f08 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -588,6 +588,12 @@ function get_activity_visibility( $activity ) { return ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC; } + // Activities with no recipients are treated as public. + $recipients = extract_recipients_from_activity( $activity ); + if ( empty( $recipients ) ) { + return ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC; + } + return ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE; } @@ -607,6 +613,10 @@ function is_activity_public( $data ) { $recipients = extract_recipients_from_activity( $data ); + if ( empty( $recipients ) ) { + return true; + } + return ! empty( array_intersect( $recipients, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) ); } diff --git a/tests/phpunit/tests/includes/class-test-functions.php b/tests/phpunit/tests/includes/class-test-functions.php index 7fd00663a2..072a0999c5 100644 --- a/tests/phpunit/tests/includes/class-test-functions.php +++ b/tests/phpunit/tests/includes/class-test-functions.php @@ -1003,7 +1003,7 @@ public function public_activity_provider() { 'monkey' => 'https://www.w3.org/ns/activitystreams#Public', ), ), - false, + true, ), array( array( @@ -1334,13 +1334,13 @@ public function visibility_data_provider() { 'expected' => ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC, 'description' => 'Public visibility via as:Public identifier', ), - // Empty activity. + // Empty activity - no recipients means public. array( 'activity' => array( 'type' => 'Create', ), - 'expected' => ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE, - 'description' => 'Empty activity defaults to private', + 'expected' => ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC, + 'description' => 'Empty activity (no recipients) is treated as public', ), ); } diff --git a/tests/phpunit/tests/includes/collection/class-test-inbox.php b/tests/phpunit/tests/includes/collection/class-test-inbox.php index a135f33423..02aef2b40c 100644 --- a/tests/phpunit/tests/includes/collection/class-test-inbox.php +++ b/tests/phpunit/tests/includes/collection/class-test-inbox.php @@ -75,9 +75,9 @@ public function test_add_activity_with_post_meta() { $remote_actor_meta = \get_post_meta( $inbox_id, '_activitypub_activity_remote_actor', true ); $this->assertEquals( 'https://remote.example.com/users/testuser', $remote_actor_meta ); - // Test activitypub_content_visibility meta. + // Activities with no recipients are treated as public. $visibility_meta = \get_post_meta( $inbox_id, 'activitypub_content_visibility', true ); - $this->assertEquals( ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE, $visibility_meta ); + $this->assertEquals( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC, $visibility_meta ); } /** @@ -723,4 +723,60 @@ public function test_deduplicate_non_existent() { $result = Inbox::deduplicate( 'https://remote.example.com/activities/non-existent' ); $this->assertFalse( $result ); } + + /** + * Test adding Like activity with trailing slash in object URL. + * + * This test verifies that Like activities from Pixelfed and other platforms + * that include trailing slashes in object URLs are stored correctly in the inbox. + * + * @covers ::add + */ + public function test_add_like_activity_with_trailing_slash() { + // Create a post to be liked. + $post_id = self::factory()->post->create( + array( + 'post_title' => 'Test Post for Like', + 'post_content' => 'Test content', + 'post_status' => 'publish', + ) + ); + $post_permalink = \get_permalink( $post_id ); + + // Create a Like activity with trailing slash in object URL (as Pixelfed sends). + $activity = new Activity(); + $activity->set_id( 'https://pixelfed.social/users/pfefferle#likes/30434186' ); + $activity->set_type( 'Like' ); + $activity->set_actor( 'https://pixelfed.social/users/pfefferle' ); + $activity->set_object( $post_permalink . '/' ); // Add trailing slash. + + $user_id = 1; + + // Add activity to inbox. + $inbox_id = Inbox::add( $activity, $user_id ); + + $this->assertIsInt( $inbox_id ); + $this->assertGreaterThan( 0, $inbox_id ); + + // Verify the post was created. + $post = \get_post( $inbox_id ); + $this->assertInstanceOf( 'WP_Post', $post ); + $this->assertEquals( Inbox::POST_TYPE, $post->post_type ); + + // Test _activitypub_object_id meta - should preserve the trailing slash as-is. + $object_id_meta = \get_post_meta( $inbox_id, '_activitypub_object_id', true ); + $this->assertEquals( $post_permalink . '/', $object_id_meta ); + + // Test _activitypub_activity_type meta. + $activity_type_meta = \get_post_meta( $inbox_id, '_activitypub_activity_type', true ); + $this->assertEquals( 'Like', $activity_type_meta ); + + // Test _activitypub_user_id meta. + $user_id_meta = \get_post_meta( $inbox_id, '_activitypub_user_id', true ); + $this->assertEquals( $user_id, $user_id_meta ); + + // Test _activitypub_activity_remote_actor meta. + $remote_actor_meta = \get_post_meta( $inbox_id, '_activitypub_activity_remote_actor', true ); + $this->assertEquals( 'https://pixelfed.social/users/pfefferle', $remote_actor_meta ); + } } diff --git a/tests/phpunit/tests/includes/handler/class-test-like.php b/tests/phpunit/tests/includes/handler/class-test-like.php index c01cf1d5aa..8159b8e44a 100644 --- a/tests/phpunit/tests/includes/handler/class-test-like.php +++ b/tests/phpunit/tests/includes/handler/class-test-like.php @@ -185,6 +185,50 @@ public function handle_like_provider() { ); } + /** + * Test Like activity with trailing slash in object URL. + * + * This test verifies that Like activities from Pixelfed and other platforms + * that include trailing slashes in object URLs are processed correctly. + * + * @covers ::handle_like + * @covers \Activitypub\Collection\Interactions::add_reaction + */ + public function test_handle_like_with_trailing_slash() { + // Create activity with trailing slash in object URL (as Pixelfed sends). + $activity = array( + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => 'https://pixelfed.social/users/pfefferle#likes/30434186', + 'type' => 'Like', + 'actor' => $this->user_url, + 'object' => $this->post_permalink . '/', // Add trailing slash. + ); + + // Get comment count before. + $comments_before = \get_comments( + array( + 'type' => 'like', + 'post_id' => $this->post_id, + ) + ); + $count_before = count( $comments_before ); + + // Process the like. + Like::handle_like( $activity, $this->user_id ); + + // Check that comment was created despite trailing slash. + $comments_after = \get_comments( + array( + 'type' => 'like', + 'post_id' => $this->post_id, + ) + ); + $count_after = count( $comments_after ); + + $this->assertEquals( $count_before + 1, $count_after, 'Like with trailing slash should create comment' ); + $this->assertInstanceOf( 'WP_Comment', $comments_after[0], 'Should create WP_Comment object' ); + } + /** * Test duplicate like handling. * diff --git a/tests/phpunit/tests/includes/rest/class-test-inbox-controller.php b/tests/phpunit/tests/includes/rest/class-test-inbox-controller.php index e16bb23a49..a90be7f7fa 100644 --- a/tests/phpunit/tests/includes/rest/class-test-inbox-controller.php +++ b/tests/phpunit/tests/includes/rest/class-test-inbox-controller.php @@ -475,7 +475,9 @@ public function test_get_local_recipients_no_recipients() { $method->setAccessible( true ); $result = $method->invoke( $this->inbox_controller, $activity ); - $this->assertEmpty( $result, 'Should return empty array when no recipients' ); + // Activities with no recipients are treated as public and delivered to all local actors. + $this->assertNotEmpty( $result, 'Should return all local actors when no recipients (treated as public)' ); + $this->assertEquals( Actors::get_all_ids(), $result, 'Should match all local actor IDs' ); } /**