Skip to content

Commit cc40881

Browse files
authored
Merge branch 'trunk' into improve-direct-message-targeting
2 parents 5db6876 + 56308cd commit cc40881

File tree

4 files changed

+237
-19
lines changed

4 files changed

+237
-19
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
* `@mentions` in the JSON representation of the reply
13+
1014
### Improved
1115

1216
* Direct Messages: Test for the user being in the to field

includes/transformer/class-comment.php

+75-19
Original file line numberDiff line numberDiff line change
@@ -53,31 +53,30 @@ public function change_wp_user_id( $user_id ) {
5353
* @return \Activitypub\Activity\Base_Object The ActivityPub Object.
5454
*/
5555
public function to_object() {
56-
$comment = $this->wp_object;
57-
$object = parent::to_object();
58-
59-
$object->set_url( $this->get_id() );
60-
$object->set_type( 'Note' );
56+
$object = parent::to_object();
57+
58+
$content = $this->get_content();
59+
$at_replies = '';
60+
$reply_context = $this->extract_reply_context( array() );
61+
62+
foreach ( $reply_context as $acct => $url ) {
63+
$at_replies .= sprintf(
64+
'<a class="u-mention mention" href="%s">%s</a> ',
65+
esc_url( $url ),
66+
esc_html( $acct )
67+
);
68+
}
6169

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

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

76+
$object->set_content( $content );
7077
$object->set_content_map(
7178
array(
72-
$this->get_locale() => $this->get_content(),
73-
)
74-
);
75-
$path = sprintf( 'actors/%d/followers', intval( $comment->comment_author ) );
76-
77-
$object->set_to(
78-
array(
79-
'https://www.w3.org/ns/activitystreams#Public',
80-
get_rest_url_by_path( $path ),
79+
$this->get_locale() => $content,
8180
)
8281
);
8382

@@ -308,4 +307,61 @@ public function get_locale() {
308307
*/
309308
return apply_filters( 'activitypub_comment_locale', $lang, $comment_id, $this->wp_object );
310309
}
310+
311+
/**
312+
* Returns the updated date of the comment.
313+
*
314+
* @return string|null The updated date of the comment.
315+
*/
316+
public function get_updated() {
317+
$updated = \get_comment_meta( $this->wp_object->comment_ID, 'activitypub_comment_modified', true );
318+
$published = \get_comment_meta( $this->wp_object->comment_ID, 'activitypub_comment_published', true );
319+
320+
if ( $updated > $published ) {
321+
return \gmdate( 'Y-m-d\TH:i:s\Z', $updated );
322+
}
323+
324+
return null;
325+
}
326+
327+
/**
328+
* Returns the published date of the comment.
329+
*
330+
* @return string The published date of the comment.
331+
*/
332+
public function get_published() {
333+
return \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( $this->wp_object->comment_date_gmt ) );
334+
}
335+
336+
/**
337+
* Returns the URL of the comment.
338+
*
339+
* @return string The URL of the comment.
340+
*/
341+
public function get_url() {
342+
return $this->get_id();
343+
}
344+
345+
/**
346+
* Returns the type of the comment.
347+
*
348+
* @return string The type of the comment.
349+
*/
350+
public function get_type() {
351+
return 'Note';
352+
}
353+
354+
/**
355+
* Returns the to of the comment.
356+
*
357+
* @return array The to of the comment.
358+
*/
359+
public function get_to() {
360+
$path = sprintf( 'actors/%d/followers', intval( $this->wp_object->comment_author ) );
361+
362+
return array(
363+
'https://www.w3.org/ns/activitystreams#Public',
364+
get_rest_url_by_path( $path ),
365+
);
366+
}
311367
}

readme.txt

+1
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ For reasons of data protection, it is not possible to see the followers of other
139139

140140
= 4.5.1 =
141141

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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
<?php
2+
/**
3+
* Test file for Comment transformer.
4+
*
5+
* @package ActivityPub
6+
*/
7+
8+
namespace Activitypub\Tests\Transformer;
9+
10+
use Activitypub\Transformer\Comment;
11+
12+
/**
13+
* Test class for Comment Transformer.
14+
*
15+
* @coversDefaultClass \Activitypub\Transformer\Comment
16+
*/
17+
class Test_Comment extends \WP_UnitTestCase {
18+
/**
19+
* Test post ID.
20+
*
21+
* @var int
22+
*/
23+
protected static $post_id;
24+
25+
/**
26+
* Create fake data before tests run.
27+
*
28+
* @param \WP_UnitTest_Factory $factory Helper that creates fake data.
29+
*/
30+
public static function wpSetUpBeforeClass( $factory ) {
31+
self::$post_id = $factory->post->create();
32+
33+
// Mock the WebFinger wp_safe_remote_get.
34+
add_filter( 'pre_http_request', array( self::class, 'pre_http_request' ), 10, 3 );
35+
}
36+
37+
/**
38+
* Clean up after tests.
39+
*/
40+
public static function wpTearDownAfterClass() {
41+
wp_delete_post( self::$post_id, true );
42+
remove_filter( 'pre_http_request', array( self::class, 'pre_http_request' ) );
43+
}
44+
45+
/**
46+
* Test content generation with reply context.
47+
*
48+
* @covers ::to_object
49+
*/
50+
public function test_content_with_reply_context() {
51+
// Create a parent ActivityPub comment.
52+
$parent_comment_id = self::factory()->comment->create(
53+
array(
54+
'comment_post_ID' => self::$post_id,
55+
'comment_author_url' => 'https://remote.example/@author',
56+
'comment_meta' => array(
57+
'protocol' => 'activitypub',
58+
),
59+
)
60+
);
61+
62+
// Create a reply comment.
63+
$reply_comment_id = self::factory()->comment->create(
64+
array(
65+
'comment_post_ID' => self::$post_id,
66+
'comment_parent' => $parent_comment_id,
67+
'comment_author_url' => 'https://example.net/@remote',
68+
'comment_meta' => array(
69+
'protocol' => 'activitypub',
70+
),
71+
)
72+
);
73+
74+
// Create a reply comment.
75+
$test_comment_id = self::factory()->comment->create(
76+
array(
77+
'comment_post_ID' => self::$post_id,
78+
'comment_parent' => $reply_comment_id,
79+
'comment_author_url' => 'https://example.com/@test',
80+
)
81+
);
82+
83+
// Transform comment to ActivityPub object.
84+
$comment = get_comment( $test_comment_id );
85+
$transformer = new Comment( $comment );
86+
$object = $transformer->to_object();
87+
88+
// Get the content.
89+
$content = $object->get_content();
90+
91+
// Test that reply context is added.
92+
$this->assertEquals( '<p><a class="u-mention mention" href="https://example.net/@remote">@[email protected]</a> <a class="u-mention mention" href="https://remote.example/@author">@[email protected]</a></p><p>This is a comment</p>', $content );
93+
94+
// Clean up.
95+
wp_delete_comment( $reply_comment_id, true );
96+
wp_delete_comment( $parent_comment_id, true );
97+
wp_delete_comment( $test_comment_id, true );
98+
}
99+
100+
/**
101+
* Test content generation with reply context.
102+
*
103+
* @param mixed $data The response data.
104+
* @param array $parsed_args The request arguments.
105+
* @param string $url The request URL.
106+
* @return mixed The response data.
107+
*/
108+
public static function pre_http_request( $data, $parsed_args, $url ) {
109+
if ( str_starts_with( $url, 'https://remote.example' ) ) {
110+
return self::dummy_response(
111+
wp_json_encode(
112+
array(
113+
'subject' => 'acct:[email protected]',
114+
'links' => array(
115+
'self' => array( 'href' => 'https://remote.example/@author' ),
116+
),
117+
)
118+
)
119+
);
120+
}
121+
122+
if ( str_starts_with( $url, 'https://example.net/' ) ) {
123+
return self::dummy_response(
124+
wp_json_encode(
125+
array(
126+
'subject' => 'https://example.net/@remote',
127+
'aliases' => array(
128+
129+
),
130+
'links' => array(
131+
'self' => array( 'href' => 'https://example.net/@remote' ),
132+
),
133+
)
134+
)
135+
);
136+
}
137+
138+
return $data;
139+
}
140+
141+
/**
142+
* Create a dummy response.
143+
*
144+
* @param string $body The body of the response.
145+
*
146+
* @return array The dummy response.
147+
*/
148+
private static function dummy_response( $body ) {
149+
return array(
150+
'headers' => array(),
151+
'body' => $body,
152+
'response' => array( 'code' => 200 ),
153+
'cookies' => array(),
154+
'filename' => null,
155+
);
156+
}
157+
}

0 commit comments

Comments
 (0)