@@ -814,6 +814,13 @@ public function test_mention_with_array() {
814814 'object ' => array (
815815 'id ' => 'https://example.com/post/1 ' ,
816816 'content ' => 'Test mention ' ,
817+ 'tag ' => array (
818+ array (
819+ 'type ' => 'Mention ' ,
820+ 'href ' => Actors::get_by_id ( $ user_id )->get_id (),
821+ 'name ' => '@test ' ,
822+ ),
823+ ),
817824 ),
818825 'cc ' => array ( Actors::get_by_id ( $ user_id )->get_id () ),
819826 );
@@ -935,6 +942,13 @@ public function test_mention_filters_recipients() {
935942 'object ' => array (
936943 'id ' => 'https://example.com/post/1 ' ,
937944 'content ' => 'Test mention ' ,
945+ 'tag ' => array (
946+ array (
947+ 'type ' => 'Mention ' ,
948+ 'href ' => Actors::get_by_id ( $ user_id )->get_id (),
949+ 'name ' => '@test ' ,
950+ ),
951+ ),
938952 ),
939953 // Only user_id is in CC, not other_user_id.
940954 'cc ' => array ( Actors::get_by_id ( $ user_id )->get_id () ),
@@ -1115,4 +1129,122 @@ public function test_respect_existing_notification_settings() {
11151129 // Clean up.
11161130 \delete_option ( 'activitypub_create_posts ' );
11171131 }
1132+
1133+ /**
1134+ * Test that users in CC without actual mention tags do not receive mention notifications.
1135+ *
1136+ * This tests the bug fix where users added to CC (e.g., because they follow the actor)
1137+ * were incorrectly receiving mention notifications even when not actually mentioned.
1138+ *
1139+ * @covers ::mention
1140+ */
1141+ public function test_mention_requires_tag_not_just_cc () {
1142+ $ user_id = self ::$ user_id ;
1143+
1144+ // Activity with user in CC but NOT mentioned in tags.
1145+ $ activity = array (
1146+ 'actor ' => 'https://example.com/sports-account ' ,
1147+ 'object ' => array (
1148+ 'id ' => 'https://example.com/sports-account/posts/123 ' ,
1149+ 'type ' => 'Note ' ,
1150+ 'content ' => '<p>Join @user1 and @user2 on our stream...</p> ' ,
1151+ 'tag ' => array (
1152+ // Other users mentioned, but NOT the local user.
1153+ array (
1154+ 'type ' => 'Mention ' ,
1155+ 'href ' => 'https://example.com/user1 ' ,
1156+ 1157+ ),
1158+ array (
1159+ 'type ' => 'Mention ' ,
1160+ 'href ' => 'https://example.com/user2 ' ,
1161+ 1162+ ),
1163+ ),
1164+ ),
1165+ // User is in CC (e.g., because they follow the actor).
1166+ 'cc ' => array ( Actors::get_by_id ( $ user_id )->get_id () ),
1167+ );
1168+
1169+ // Mock remote metadata.
1170+ $ metadata_filter = function () {
1171+ return array (
1172+ 'name ' => 'Sports Account ' ,
1173+ 'url ' => 'https://example.com/sports-account ' ,
1174+ );
1175+ };
1176+ \add_filter ( 'pre_get_remote_metadata_by_actor ' , $ metadata_filter );
1177+
1178+ $ mock = new \MockAction ();
1179+ \add_filter ( 'wp_mail ' , array ( $ mock , 'filter ' ), 1 );
1180+
1181+ // Trigger mention notification.
1182+ Mailer::mention ( $ activity , $ user_id );
1183+
1184+ // Should NOT send any email because user is not actually mentioned in tags.
1185+ $ this ->assertEquals ( 0 , $ mock ->get_call_count (), 'User in CC without mention tag should not receive notification ' );
1186+
1187+ // Clean up.
1188+ \remove_filter ( 'pre_get_remote_metadata_by_actor ' , $ metadata_filter );
1189+ \remove_filter ( 'wp_mail ' , array ( $ mock , 'filter ' ), 1 );
1190+ }
1191+
1192+ /**
1193+ * Test that users with actual mention tags DO receive mention notifications.
1194+ *
1195+ * @covers ::mention
1196+ */
1197+ public function test_mention_with_tag_sends_notification () {
1198+ $ user_id = self ::$ user_id ;
1199+
1200+ // Activity with user properly mentioned in both CC and tags.
1201+ $ activity = array (
1202+ 'actor ' => 'https://example.com/author ' ,
1203+ 'object ' => array (
1204+ 'id ' => 'https://example.com/post/1 ' ,
1205+ 'type ' => 'Note ' ,
1206+ 'content ' => '<p>Hello @testuser, how are you?</p> ' ,
1207+ 'tag ' => array (
1208+ array (
1209+ 'type ' => 'Mention ' ,
1210+ 'href ' => Actors::get_by_id ( $ user_id )->get_id (),
1211+ 'name ' => '@testuser ' ,
1212+ ),
1213+ ),
1214+ ),
1215+ 'cc ' => array ( Actors::get_by_id ( $ user_id )->get_id () ),
1216+ );
1217+
1218+ // Mock remote metadata.
1219+ $ metadata_filter = function () {
1220+ return array (
1221+ 'name ' => 'Test Author ' ,
1222+ 'url ' => 'https://example.com/author ' ,
1223+ );
1224+ };
1225+ \add_filter ( 'pre_get_remote_metadata_by_actor ' , $ metadata_filter );
1226+
1227+ $ mock = new \MockAction ();
1228+ \add_filter ( 'wp_mail ' , array ( $ mock , 'filter ' ), 1 );
1229+
1230+ // Capture email.
1231+ $ mail_filter = function ( $ args ) use ( $ user_id ) {
1232+ $ this ->assertStringContainsString ( 'Mention ' , $ args ['subject ' ] );
1233+ $ this ->assertStringContainsString ( 'Test Author ' , $ args ['subject ' ] );
1234+ $ this ->assertEquals ( \get_user_by ( 'id ' , $ user_id )->user_email , $ args ['to ' ] );
1235+ return $ args ;
1236+ };
1237+ \add_filter ( 'wp_mail ' , $ mail_filter );
1238+
1239+ // Trigger mention notification.
1240+ Mailer::mention ( $ activity , $ user_id );
1241+
1242+ // Should send 1 email because user is properly mentioned.
1243+ $ this ->assertEquals ( 1 , $ mock ->get_call_count (), 'User properly mentioned in tags should receive notification ' );
1244+
1245+ // Clean up.
1246+ \remove_filter ( 'pre_get_remote_metadata_by_actor ' , $ metadata_filter );
1247+ \remove_filter ( 'wp_mail ' , array ( $ mock , 'filter ' ), 1 );
1248+ \remove_filter ( 'wp_mail ' , $ mail_filter );
1249+ }
11181250}
0 commit comments