Skip to content

Commit 6da5de1

Browse files
committed
Fix false mention notifications for users in CC without mention tags
Users were incorrectly receiving mention email notifications when they appeared in the CC field of an activity but were not actually mentioned in the content. This commonly occurred when users followed an actor and were added to CC for federation purposes. The fix verifies that a user's actor ID appears in the activity's tag array with type "Mention" before sending a mention notification, rather than just checking if they're in the CC field. Changes: - Add validation in Mailer::mention() to check for actual mention tags - Add object_to_uri() support for Mention type objects - Add test case for users in CC without mention tags (should not notify) - Add test case for users properly mentioned (should notify)
1 parent 3ce8dc6 commit 6da5de1

File tree

3 files changed

+126
-2
lines changed

3 files changed

+126
-2
lines changed

includes/class-mailer.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,13 @@ public static function mention( $activity, $user_ids ) {
349349
}
350350

351351
$actor_id = $actor->get_id();
352-
if ( \in_array( $actor_id, (array) $activity['cc'], true ) ) {
353-
$recipients[ $user_id ] = $actor_id;
352+
if ( \in_array( $actor_id, (array) $activity['cc'], true ) && isset( $activity['object']['tag'] ) ) {
353+
foreach ( (array) $activity['object']['tag'] as $tag ) {
354+
if ( object_to_uri( $tag ) === $actor_id ) {
355+
$recipients[ $user_id ] = $actor_id;
356+
break;
357+
}
358+
}
354359
}
355360
}
356361

includes/functions.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,7 @@ function object_to_uri( $data ) {
808808
$data = object_to_uri( $data['url'] );
809809
break;
810810
case 'Link':
811+
case 'Mention':
811812
$data = $data['href'];
812813
break;
813814
default:

tests/phpunit/tests/includes/class-test-mailer.php

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,4 +1135,122 @@ public function test_respect_existing_notification_settings() {
11351135
// Clean up.
11361136
\delete_option( 'activitypub_create_posts' );
11371137
}
1138+
1139+
/**
1140+
* Test that users in CC without actual mention tags do not receive mention notifications.
1141+
*
1142+
* This tests the bug fix where users added to CC (e.g., because they follow the actor)
1143+
* were incorrectly receiving mention notifications even when not actually mentioned.
1144+
*
1145+
* @covers ::mention
1146+
*/
1147+
public function test_mention_requires_tag_not_just_cc() {
1148+
$user_id = self::$user_id;
1149+
1150+
// Activity with user in CC but NOT mentioned in tags.
1151+
$activity = array(
1152+
'actor' => 'https://example.com/sports-account',
1153+
'object' => array(
1154+
'id' => 'https://example.com/sports-account/posts/123',
1155+
'type' => 'Note',
1156+
'content' => '<p>Join @user1 and @user2 on our stream...</p>',
1157+
'tag' => array(
1158+
// Other users mentioned, but NOT the local user.
1159+
array(
1160+
'type' => 'Mention',
1161+
'href' => 'https://example.com/user1',
1162+
'name' => '[email protected]',
1163+
),
1164+
array(
1165+
'type' => 'Mention',
1166+
'href' => 'https://example.com/user2',
1167+
'name' => '[email protected]',
1168+
),
1169+
),
1170+
),
1171+
// User is in CC (e.g., because they follow the actor).
1172+
'cc' => array( Actors::get_by_id( $user_id )->get_id() ),
1173+
);
1174+
1175+
// Mock remote metadata.
1176+
$metadata_filter = function () {
1177+
return array(
1178+
'name' => 'Sports Account',
1179+
'url' => 'https://example.com/sports-account',
1180+
);
1181+
};
1182+
\add_filter( 'pre_get_remote_metadata_by_actor', $metadata_filter );
1183+
1184+
$mock = new \MockAction();
1185+
\add_filter( 'wp_mail', array( $mock, 'filter' ), 1 );
1186+
1187+
// Trigger mention notification.
1188+
Mailer::mention( $activity, $user_id );
1189+
1190+
// Should NOT send any email because user is not actually mentioned in tags.
1191+
$this->assertEquals( 0, $mock->get_call_count(), 'User in CC without mention tag should not receive notification' );
1192+
1193+
// Clean up.
1194+
\remove_filter( 'pre_get_remote_metadata_by_actor', $metadata_filter );
1195+
\remove_filter( 'wp_mail', array( $mock, 'filter' ), 1 );
1196+
}
1197+
1198+
/**
1199+
* Test that users with actual mention tags DO receive mention notifications.
1200+
*
1201+
* @covers ::mention
1202+
*/
1203+
public function test_mention_with_tag_sends_notification() {
1204+
$user_id = self::$user_id;
1205+
1206+
// Activity with user properly mentioned in both CC and tags.
1207+
$activity = array(
1208+
'actor' => 'https://example.com/author',
1209+
'object' => array(
1210+
'id' => 'https://example.com/post/1',
1211+
'type' => 'Note',
1212+
'content' => '<p>Hello @testuser, how are you?</p>',
1213+
'tag' => array(
1214+
array(
1215+
'type' => 'Mention',
1216+
'href' => Actors::get_by_id( $user_id )->get_id(),
1217+
'name' => '@testuser',
1218+
),
1219+
),
1220+
),
1221+
'cc' => array( Actors::get_by_id( $user_id )->get_id() ),
1222+
);
1223+
1224+
// Mock remote metadata.
1225+
$metadata_filter = function () {
1226+
return array(
1227+
'name' => 'Test Author',
1228+
'url' => 'https://example.com/author',
1229+
);
1230+
};
1231+
\add_filter( 'pre_get_remote_metadata_by_actor', $metadata_filter );
1232+
1233+
$mock = new \MockAction();
1234+
\add_filter( 'wp_mail', array( $mock, 'filter' ), 1 );
1235+
1236+
// Capture email.
1237+
$mail_filter = function ( $args ) use ( $user_id ) {
1238+
$this->assertStringContainsString( 'Mention', $args['subject'] );
1239+
$this->assertStringContainsString( 'Test Author', $args['subject'] );
1240+
$this->assertEquals( \get_user_by( 'id', $user_id )->user_email, $args['to'] );
1241+
return $args;
1242+
};
1243+
\add_filter( 'wp_mail', $mail_filter );
1244+
1245+
// Trigger mention notification.
1246+
Mailer::mention( $activity, $user_id );
1247+
1248+
// Should send 1 email because user is properly mentioned.
1249+
$this->assertEquals( 1, $mock->get_call_count(), 'User properly mentioned in tags should receive notification' );
1250+
1251+
// Clean up.
1252+
\remove_filter( 'pre_get_remote_metadata_by_actor', $metadata_filter );
1253+
\remove_filter( 'wp_mail', array( $mock, 'filter' ), 1 );
1254+
\remove_filter( 'wp_mail', $mail_filter );
1255+
}
11381256
}

0 commit comments

Comments
 (0)