Skip to content

Add in_reply_to Actor to Activity audience #1711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/changelog/1711-from-description
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

The actor of the replied-to post is now included in cc or to based on the post's visibility.
11 changes: 11 additions & 0 deletions includes/class-http.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,17 @@ public static function generate_cache_key( $url ) {
* @return array|WP_Error The Object data as array or WP_Error on failure.
*/
public static function get_remote_object( $url_or_object, $cached = true ) {
/**
* Filters the preemptive return value of a remote object request.
*
* @param array|string|null $response The response.
* @param array|string|null $url_or_object The Object or the Object URL.
*/
$response = apply_filters( 'activitypub_pre_http_get_remote_object', null, $url_or_object );
if ( null !== $response ) {
return $response;
}

$url = object_to_uri( $url_or_object );

if ( preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $url ) ) {
Expand Down
36 changes: 30 additions & 6 deletions includes/transformer/class-base.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@

namespace Activitypub\Transformer;

use Activitypub\Http;
use Activitypub\Activity\Activity;
use Activitypub\Collection\Actors;
use Activitypub\Activity\Base_Object;

use function Activitypub\object_to_uri;

/**
* WordPress Base Transformer.
*
Expand Down Expand Up @@ -117,7 +120,7 @@ protected function transform_object_properties( $activity_object ) {
/**
* Transform the item into an ActivityPub Object.
*
* @return Base_Object|object The Activity-Object.
* @return Base_Object The Activity-Object.
*/
public function to_object() {
$activity_object = new Base_Object();
Expand Down Expand Up @@ -162,28 +165,40 @@ public function set_content_visibility( $content_visibility ) {
* @return Base_Object The ActivityPub Object.
*/
protected function set_audience( $activity_object ) {
$public = 'https://www.w3.org/ns/activitystreams#Public';
$actor = Actors::get_by_resource( $this->get_attributed_to() );
if ( ! $actor || is_wp_error( $actor ) ) {
$followers = null;
} else {
$public = 'https://www.w3.org/ns/activitystreams#Public';
$followers = null;
$replied_to = null;

$actor = Actors::get_by_resource( $this->get_attributed_to() );
if ( ! \is_wp_error( $actor ) ) {
$followers = $actor->get_followers();
}

$mentions = array_values( $this->get_mentions() );

if ( $this->get_in_reply_to() ) {
$object = Http::get_remote_object( $this->get_in_reply_to() );
if ( $object && ! \is_wp_error( $object ) && isset( $object['attributedTo'] ) ) {
$replied_to = array( object_to_uri( $object['attributedTo'] ) );
}
}

switch ( $this->get_content_visibility() ) {
case ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC:
$activity_object->add_to( $public );
$activity_object->add_cc( $followers );
$activity_object->add_cc( $mentions );
$activity_object->add_cc( $replied_to );
break;
case ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC:
$activity_object->add_to( $followers );
$activity_object->add_to( $mentions );
$activity_object->add_to( $replied_to );
$activity_object->add_cc( $public );
break;
case ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE:
$activity_object->add_to( $mentions );
$activity_object->add_to( $replied_to );
}

return $activity_object;
Expand Down Expand Up @@ -379,4 +394,13 @@ protected function get_mentions() {
$this->item
);
}

/**
* Returns the in reply to.
*
* @return string|array|null The in reply to.
*/
protected function get_in_reply_to() {
return null;
}
}
2 changes: 1 addition & 1 deletion includes/transformer/class-post.php
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ public function generate_reply_link( $block_content, $block ) {
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-inreplyto
*
* @return string|null The in-reply-to URL of the post.
* @return string|array|null The in-reply-to URL of the post.
*/
protected function get_in_reply_to() {
if ( ! site_supports_blocks() ) {
Expand Down
146 changes: 145 additions & 1 deletion tests/includes/transformer/class-test-base.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@

namespace Activitypub\Tests\Transformer;

use Activitypub\Http;
use Activitypub\Activity\Base_Object;
use Activitypub\Activity\Generic_Object;
use Activitypub\Transformer\Base;

use Activitypub\Transformer\Post;
/**
* Test class for Base Transformer.
*
Expand Down Expand Up @@ -70,4 +72,146 @@ public function get_object_var_keys() {
// Assert that the sensitive property could be set to false.
$this->assertFalse( $transformed_object->get_sensitive(), 'The sensitive property should be set to false.' );
}

/**
* Test that the audience is set correctly.
*
* @dataProvider data_provider_set_audience
*
* @covers ::set_audience
*
* @param string $content_visibility The content visibility.
* @param array $object_attributes The object attributes.
* @param array $expected_audience The expected audience.
*/
public function test_set_audience( $content_visibility, $object_attributes, $expected_audience ) {
$post_id = $this->factory->post->create(
array(
'post_title' => 'Test Post',
'post_content' => 'Test content that is longer than the note length limit',
)
);
$post = \get_post( $post_id );

$function = function ( $response, $url_or_object ) {
if ( ! str_contains( $url_or_object, 'reply' ) ) {
return null;
}

return array(
'attributedTo' => $url_or_object,
);
};
\add_filter( 'activitypub_pre_http_get_remote_object', $function, 10, 2 );

$getter_methods = array_map(
function ( $k ) {
return 'get_' . $k;
},
array_keys( $object_attributes )
);

$transformer = $this->getMockBuilder( Post::class )
->setConstructorArgs( array( $post ) )
->onlyMethods( $getter_methods )
->getMock();

$transformer->set_content_visibility( $content_visibility );

foreach ( $object_attributes as $key => $value ) {
$transformer->method( 'get_' . $key )->willReturn( $value );
}

$reflection = new \ReflectionObject( $transformer );
$method = $reflection->getMethod( 'set_audience' );
$method->setAccessible( true );

$transformed_object = $method->invoke( $transformer, new Generic_Object() );

$this->assertEquals( $expected_audience['to'], $transformed_object->get_to() );
$this->assertEquals( $expected_audience['cc'], $transformed_object->get_cc() );

\wp_delete_post( $post_id );
\remove_filter( 'activitypub_pre_http_get_remote_object', $function );
}

/**
* Data provider for test_set_audience.
*
* @return array[]
*/
public function data_provider_set_audience() {
return array(
array(
ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC,
array(
'in_reply_to' => 'https://example.com/in-reply-to',
'mentions' => array(
'https://example.com/mentions' => 'https://example.com/mentions',
),
),
array(
'to' => array(
'https://www.w3.org/ns/activitystreams#Public',
),
'cc' => array(
'https://example.com/mentions',
'https://example.com/in-reply-to',
),
),
),
array(
ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC,
array(
'in_reply_to' => 'https://example.com/in-reply-to',
'mentions' => array(
'https://example.com/mentions' => 'https://example.com/mentions',
),
),
array(
'to' => array(
'https://www.w3.org/ns/activitystreams#Public',
),
'cc' => array(
'https://example.com/mentions',
'https://example.com/in-reply-to',
),
),
),
array(
ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC,
array(
'in_reply_to' => 'https://example.com/in-reply-to',
'mentions' => array(
'https://example.com/mentions' => 'https://example.com/mentions',
),
),
array(
'cc' => array(
'https://www.w3.org/ns/activitystreams#Public',
),
'to' => array(
'https://example.com/mentions',
'https://example.com/in-reply-to',
),
),
),
array(
ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE,
array(
'in_reply_to' => 'https://example.com/in-reply-to',
'mentions' => array(
'https://example.com/mentions' => 'https://example.com/mentions',
),
),
array(
'to' => array(
'https://example.com/mentions',
'https://example.com/in-reply-to',
),
'cc' => null,
),
),
);
}
}
Loading