Skip to content

Commit af80c99

Browse files
authored
Merge branch 'trunk' into add/notification-settings
2 parents 44d57f2 + 03131d7 commit af80c99

6 files changed

+182
-72
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
* Add a filter to allow modifying the ActivityPub preview template
1213
* `@mentions` in the JSON representation of the reply
1314
* Add settings to enable/disable e-mail notifications for new followers and direct messages
1415

1516
### Improved
1617

18+
* Direct Messages: Test for the user being in the to field
1719
* Direct Messages: Improve HTML to e-mail text conversion
1820

1921
## [4.5.1] - 2024-12-18

includes/class-activitypub.php

+8-3
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,15 @@ public static function render_activitypub_template( $template ) {
102102
} elseif ( is_comment() ) {
103103
$activitypub_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/comment-json.php';
104104
} elseif ( \is_singular() && ! is_post_disabled( \get_the_ID() ) ) {
105-
$preview = \get_query_var( 'preview' );
106-
if ( $preview ) {
105+
if ( \get_query_var( 'preview' ) ) {
107106
\define( 'ACTIVITYPUB_PREVIEW', true );
108-
$activitypub_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/post-preview.php';
107+
108+
/**
109+
* Filter the template used for the ActivityPub preview.
110+
*
111+
* @param string $activitypub_template Absolute path to the template file.
112+
*/
113+
$activitypub_template = apply_filters( 'activitypub_preview_template', ACTIVITYPUB_PLUGIN_DIR . '/templates/post-preview.php' );
109114
} else {
110115
$activitypub_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/post-json.php';
111116
}

includes/class-mailer.php

+7-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Activitypub;
99

1010
use Activitypub\Collection\Actors;
11+
use Activitypub\Model\User;
1112

1213
/**
1314
* Mailer Class.
@@ -152,8 +153,12 @@ public static function new_follower( $notification ) {
152153
* @param int $user_id The id of the local blog-user.
153154
*/
154155
public static function direct_message( $activity, $user_id ) {
155-
// Check if Activity is public or not.
156-
if ( is_activity_public( $activity ) ) {
156+
if (
157+
is_activity_public( $activity ) ||
158+
// Only accept messages that have the user in the "to" field.
159+
empty( $activity['to'] ) ||
160+
! in_array( Actors::get_by_id( $user_id )->get_id(), (array) $activity['to'], true )
161+
) {
157162
return;
158163
}
159164

readme.txt

+2
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,11 @@ For reasons of data protection, it is not possible to see the followers of other
134134

135135
= Unreleased =
136136

137+
* Added: A filter to allow modifying the ActivityPub preview template
137138
* Added: `@mentions` in the JSON representation of the reply
138139
* Added: Settings to enable/disable e-mail notifications for new followers and direct messages
139140
* Improved: HTML to e-mail text conversion
141+
* Improved: Direct Messages: Test for the user being in the to field
140142

141143
= 4.5.1 =
142144

tests/includes/class-test-activitypub.php

+30
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,34 @@ public function test_post_type_support() {
2626
$this->assertContains( 'post', \get_post_types_by_support( 'activitypub' ) );
2727
$this->assertContains( 'page', \get_post_types_by_support( 'activitypub' ) );
2828
}
29+
30+
/**
31+
* Test activitypub_preview_template filter.
32+
*
33+
* @covers ::render_activitypub_template
34+
*/
35+
public function test_preview_template_filter() {
36+
// Create a test post.
37+
$post_id = self::factory()->post->create();
38+
$this->go_to( get_permalink( $post_id ) );
39+
40+
// Simulate ActivityPub request and preview mode.
41+
$_SERVER['HTTP_ACCEPT'] = 'application/activity+json';
42+
\set_query_var( 'preview', true );
43+
44+
// Add filter before testing.
45+
\add_filter(
46+
'activitypub_preview_template',
47+
function () {
48+
return '/custom/template.php';
49+
}
50+
);
51+
52+
// Test that the filter is applied.
53+
$template = \Activitypub\Activitypub::render_activitypub_template( 'original.php' );
54+
$this->assertEquals( '/custom/template.php', $template, 'Custom preview template should be used when filter is applied.' );
55+
56+
// Clean up.
57+
unset( $_SERVER['HTTP_ACCEPT'] );
58+
}
2959
}

tests/includes/class-test-mailer.php

+133-67
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Activitypub\Tests;
99

1010
use Activitypub\Mailer;
11+
use Activitypub\Collection\Actors;
1112
use Activitypub\Notification;
1213
use WP_UnitTestCase;
1314

@@ -222,21 +223,121 @@ public function test_init() {
222223
delete_option( 'activitypub_mailer_new_dm' );
223224
}
224225

226+
/**
227+
* Data provider for direct message notification.
228+
*
229+
* @return array
230+
*/
231+
public function direct_message_provider() {
232+
return array(
233+
'to' => array(
234+
true,
235+
array(
236+
'actor' => 'https://example.com/author',
237+
'object' => array(
238+
'content' => 'Test direct message',
239+
),
240+
'to' => array( 'user_url' ),
241+
),
242+
),
243+
'none' => array(
244+
false,
245+
array(
246+
'actor' => 'https://example.com/author',
247+
'object' => array(
248+
'content' => 'Test direct message',
249+
),
250+
),
251+
),
252+
'public+reply' => array(
253+
false,
254+
array(
255+
'actor' => 'https://example.com/author',
256+
'object' => array(
257+
'content' => 'Test public reply',
258+
'inReplyTo' => 'https://example.com/post/1',
259+
),
260+
'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
261+
),
262+
),
263+
'public+reply+cc' => array(
264+
false,
265+
array(
266+
'actor' => 'https://example.com/author',
267+
'object' => array(
268+
'content' => 'Test public reply',
269+
'inReplyTo' => 'https://example.com/post/1',
270+
),
271+
'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
272+
'cc' => array( 'user_url' ),
273+
),
274+
),
275+
'public+followers' => array(
276+
false,
277+
array(
278+
'actor' => 'https://example.com/author',
279+
'object' => array(
280+
'content' => 'Test public activity',
281+
'inReplyTo' => null,
282+
),
283+
'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
284+
'cc' => array( 'https://example.com/followers' ),
285+
),
286+
),
287+
'followers' => array(
288+
false,
289+
array(
290+
'actor' => 'https://example.com/author',
291+
'object' => array(
292+
'content' => 'Test activity just to followers',
293+
'inReplyTo' => null,
294+
),
295+
'to' => array( 'https://example.com/followers' ),
296+
),
297+
),
298+
'reply+cc' => array(
299+
false,
300+
array(
301+
'actor' => 'https://example.com/author',
302+
'object' => array(
303+
'content' => 'Reply activity to me and to followers',
304+
'inReplyTo' => 'https://example.com/post/1',
305+
),
306+
'to' => array( 'https://example.com/followers' ),
307+
'cc' => array( 'user_url' ),
308+
),
309+
),
310+
);
311+
}
312+
225313
/**
226314
* Test direct message notification.
227315
*
316+
* @param bool $send_email Whether email should be sent.
317+
* @param array $activity Activity object.
318+
* @dataProvider direct_message_provider
228319
* @covers ::direct_message
229320
*/
230-
public function test_direct_message() {
321+
public function test_direct_message( $send_email, $activity ) {
231322
$user_id = self::$user_id;
232323
$mock = new \MockAction();
233324

234-
$activity = array(
235-
'actor' => 'https://example.com/author',
236-
'object' => array(
237-
'content' => 'Test direct message',
238-
),
239-
);
325+
// We need to replace back in the user URL because the user_id is not available in the data provider.
326+
$replace = function ( $url ) use ( $user_id ) {
327+
if ( 'user_url' === $url ) {
328+
return Actors::get_by_id( $user_id )->get_id();
329+
330+
}
331+
return $url;
332+
};
333+
334+
foreach ( $activity as $key => $value ) {
335+
if ( is_array( $value ) ) {
336+
$activity[ $key ] = array_map( $replace, $value );
337+
} else {
338+
$activity[ $key ] = $replace( $value );
339+
}
340+
}
240341

241342
// Mock remote metadata.
242343
add_filter(
@@ -250,69 +351,33 @@ function () {
250351
);
251352
add_filter( 'wp_mail', array( $mock, 'filter' ), 1 );
252353

253-
// Capture email.
254-
add_filter(
255-
'wp_mail',
256-
function ( $args ) use ( $user_id ) {
257-
$this->assertStringContainsString( 'Direct Message', $args['subject'] );
258-
$this->assertStringContainsString( 'Test Sender', $args['subject'] );
259-
$this->assertStringContainsString( 'Test direct message', $args['message'] );
260-
$this->assertStringContainsString( 'https://example.com/author', $args['message'] );
261-
$this->assertEquals( get_user_by( 'id', $user_id )->user_email, $args['to'] );
262-
return $args;
263-
}
264-
);
354+
if ( $send_email ) {
355+
// Capture email.
356+
add_filter(
357+
'wp_mail',
358+
function ( $args ) use ( $user_id, $activity ) {
359+
$this->assertStringContainsString( 'Direct Message', $args['subject'] );
360+
$this->assertStringContainsString( 'Test Sender', $args['subject'] );
361+
$this->assertStringContainsString( $activity['object']['content'], $args['message'] );
362+
$this->assertStringContainsString( 'https://example.com/author', $args['message'] );
363+
$this->assertEquals( get_user_by( 'id', $user_id )->user_email, $args['to'] );
364+
return $args;
365+
}
366+
);
367+
} else {
368+
add_filter(
369+
'wp_mail',
370+
function ( $args ) {
371+
$this->fail( 'Email should not be sent for public activity' );
372+
return $args;
373+
}
374+
);
375+
376+
}
265377

266378
Mailer::direct_message( $activity, $user_id );
267379

268-
// Test public activity (should not send email).
269-
$public_activity = array(
270-
'actor' => 'https://example.com/author',
271-
'object' => array(
272-
'content' => 'Test public reply',
273-
'inReplyTo' => 'https://example.com/post/1',
274-
),
275-
'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
276-
);
277-
278-
// Reset email capture.
279-
remove_all_filters( 'wp_mail' );
280-
add_filter( 'wp_mail', array( $mock, 'filter' ), 1 );
281-
add_filter(
282-
'wp_mail',
283-
function ( $args ) {
284-
$this->fail( 'Email should not be sent for public activity' );
285-
return $args;
286-
}
287-
);
288-
289-
Mailer::direct_message( $public_activity, $user_id );
290-
291-
// Test public activity (should not send email).
292-
$public_activity = array(
293-
'actor' => 'https://example.com/author',
294-
'object' => array(
295-
'content' => 'Test public activity',
296-
'inReplyTo' => null,
297-
),
298-
'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
299-
'cc' => array( 'https://example.com/followers' ),
300-
);
301-
302-
// Reset email capture.
303-
remove_all_filters( 'wp_mail' );
304-
add_filter( 'wp_mail', array( $mock, 'filter' ), 1 );
305-
add_filter(
306-
'wp_mail',
307-
function ( $args ) {
308-
$this->fail( 'Email should not be sent for public activity' );
309-
return $args;
310-
}
311-
);
312-
313-
Mailer::direct_message( $public_activity, $user_id );
314-
315-
$this->assertEquals( 1, $mock->get_call_count() );
380+
$this->assertEquals( $send_email ? 1 : 0, $mock->get_call_count() );
316381

317382
// Clean up.
318383
remove_all_filters( 'pre_get_remote_metadata_by_actor' );
@@ -355,6 +420,7 @@ public function test_direct_message_text( $text, $expected ) {
355420
'object' => array(
356421
'content' => $text,
357422
),
423+
'to' => array( Actors::get_by_id( $user_id )->get_id() ),
358424
);
359425

360426
// Mock remote metadata.

0 commit comments

Comments
 (0)