Skip to content
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

Include @mentions in the JSON representation of the reply #1086

Merged
merged 15 commits into from
Dec 19, 2024
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

* `@mentions` in the JSON representation of the reply

### Improved

* Direct Messages: Improve HTML to e-mail text conversion
Expand Down
94 changes: 75 additions & 19 deletions includes/transformer/class-comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,31 +53,30 @@ public function change_wp_user_id( $user_id ) {
* @return \Activitypub\Activity\Base_Object The ActivityPub Object.
*/
public function to_object() {
$comment = $this->wp_object;
$object = parent::to_object();

$object->set_url( $this->get_id() );
$object->set_type( 'Note' );
$object = parent::to_object();

$content = $this->get_content();
$at_replies = '';
$reply_context = $this->extract_reply_context( array() );

foreach ( $reply_context as $acct => $url ) {
$at_replies .= sprintf(
'<a class="u-mention mention" href="%s">%s</a> ',
esc_url( $url ),
esc_html( $acct )
);
}

$published = \strtotime( $comment->comment_date_gmt );
$object->set_published( \gmdate( 'Y-m-d\TH:i:s\Z', $published ) );
$at_replies = trim( $at_replies );

$updated = \get_comment_meta( $comment->comment_ID, 'activitypub_comment_modified', true );
if ( $updated > $published ) {
$object->set_updated( \gmdate( 'Y-m-d\TH:i:s\Z', $updated ) );
if ( $at_replies ) {
$content = sprintf( '<p>%s</p>%s', $at_replies, $content );
}

$object->set_content( $content );
$object->set_content_map(
array(
$this->get_locale() => $this->get_content(),
)
);
$path = sprintf( 'actors/%d/followers', intval( $comment->comment_author ) );

$object->set_to(
array(
'https://www.w3.org/ns/activitystreams#Public',
get_rest_url_by_path( $path ),
$this->get_locale() => $content,
)
);

Expand Down Expand Up @@ -308,4 +307,61 @@ public function get_locale() {
*/
return apply_filters( 'activitypub_comment_locale', $lang, $comment_id, $this->wp_object );
}

/**
* Returns the updated date of the comment.
*
* @return string|null The updated date of the comment.
*/
public function get_updated() {
$updated = \get_comment_meta( $this->wp_object->comment_ID, 'activitypub_comment_modified', true );
$published = \get_comment_meta( $this->wp_object->comment_ID, 'activitypub_comment_published', true );

if ( $updated > $published ) {
return \gmdate( 'Y-m-d\TH:i:s\Z', $updated );
}

return null;
}

/**
* Returns the published date of the comment.
*
* @return string The published date of the comment.
*/
public function get_published() {
return \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( $this->wp_object->comment_date_gmt ) );
}

/**
* Returns the URL of the comment.
*
* @return string The URL of the comment.
*/
public function get_url() {
return $this->get_id();
}

/**
* Returns the type of the comment.
*
* @return string The type of the comment.
*/
public function get_type() {
return 'Note';
}

/**
* Returns the to of the comment.
*
* @return array The to of the comment.
*/
public function get_to() {
$path = sprintf( 'actors/%d/followers', intval( $this->wp_object->comment_author ) );

return array(
'https://www.w3.org/ns/activitystreams#Public',
get_rest_url_by_path( $path ),
);
}
}
1 change: 1 addition & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ For reasons of data protection, it is not possible to see the followers of other

= 4.5.1 =

* Added: `@mentions` in the JSON representation of the reply
* Improved: Reactions block: Remove the `wp-block-editor` dependency for frontend views
* Fixed: Direct Messages: Don't send notification for received public activities

Expand Down
157 changes: 157 additions & 0 deletions tests/includes/transformer/class-test-comment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php
/**
* Test file for Comment transformer.
*
* @package ActivityPub
*/

namespace Activitypub\Tests\Transformer;

use Activitypub\Transformer\Comment;

/**
* Test class for Comment Transformer.
*
* @coversDefaultClass \Activitypub\Transformer\Comment
*/
class Test_Comment extends \WP_UnitTestCase {
/**
* Test post ID.
*
* @var int
*/
protected static $post_id;

/**
* Create fake data before tests run.
*
* @param \WP_UnitTest_Factory $factory Helper that creates fake data.
*/
public static function wpSetUpBeforeClass( $factory ) {
self::$post_id = $factory->post->create();

// Mock the WebFinger wp_safe_remote_get.
add_filter( 'pre_http_request', array( self::class, 'pre_http_request' ), 10, 3 );
}

/**
* Clean up after tests.
*/
public static function wpTearDownAfterClass() {
wp_delete_post( self::$post_id, true );
remove_filter( 'pre_http_request', array( self::class, 'pre_http_request' ) );
}

/**
* Test content generation with reply context.
*
* @covers ::to_object
*/
public function test_content_with_reply_context() {
// Create a parent ActivityPub comment.
$parent_comment_id = self::factory()->comment->create(
array(
'comment_post_ID' => self::$post_id,
'comment_author_url' => 'https://remote.example/@author',
'comment_meta' => array(
'protocol' => 'activitypub',
),
)
);

// Create a reply comment.
$reply_comment_id = self::factory()->comment->create(
array(
'comment_post_ID' => self::$post_id,
'comment_parent' => $parent_comment_id,
'comment_author_url' => 'https://example.net/@remote',
'comment_meta' => array(
'protocol' => 'activitypub',
),
)
);

// Create a reply comment.
$test_comment_id = self::factory()->comment->create(
array(
'comment_post_ID' => self::$post_id,
'comment_parent' => $reply_comment_id,
'comment_author_url' => 'https://example.com/@test',
)
);

// Transform comment to ActivityPub object.
$comment = get_comment( $test_comment_id );
$transformer = new Comment( $comment );
$object = $transformer->to_object();

// Get the content.
$content = $object->get_content();

// Test that reply context is added.
$this->assertEquals( '<p><a class="u-mention mention" href="https://example.net/@remote">@remote@example.net</a> <a class="u-mention mention" href="https://remote.example/@author">@author@remote.example</a></p><p>This is a comment</p>', $content );

pfefferle marked this conversation as resolved.
Show resolved Hide resolved
// Clean up.
wp_delete_comment( $reply_comment_id, true );
wp_delete_comment( $parent_comment_id, true );
wp_delete_comment( $test_comment_id, true );
}

/**
* Test content generation with reply context.
*
* @param mixed $data The response data.
* @param array $parsed_args The request arguments.
* @param string $url The request URL.
* @return mixed The response data.
*/
public static function pre_http_request( $data, $parsed_args, $url ) {
if ( str_starts_with( $url, 'https://remote.example' ) ) {
return self::dummy_response(
wp_json_encode(
array(
'subject' => 'acct:author@remote.example',
'links' => array(
'self' => array( 'href' => 'https://remote.example/@author' ),
),
)
)
);
}

if ( str_starts_with( $url, 'https://example.net/' ) ) {
return self::dummy_response(
wp_json_encode(
array(
'subject' => 'https://example.net/@remote',
'aliases' => array(
'acct:remote@example.net',
),
'links' => array(
'self' => array( 'href' => 'https://example.net/@remote' ),
),
)
)
);
}

return $data;
}

/**
* Create a dummy response.
*
* @param string $body The body of the response.
*
* @return array The dummy response.
*/
private static function dummy_response( $body ) {
return array(
'headers' => array(),
'body' => $body,
'response' => array( 'code' => 200 ),
'cookies' => array(),
'filename' => null,
);
}
}
Loading