@@ -128,16 +128,19 @@ public static function comment_notification_text( $message, $comment_id ) {
128128 /**
129129 * Send a notification email for every new follower.
130130 *
131- * @param array $activity The activity object.
132- * @param int $user_id The id of the local blog-user.
133- * @param bool $success True on success, false otherwise.
131+ * @param array $activity The activity object.
132+ * @param int|int[] $user_ids The id(s) of the local blog-user(s) .
133+ * @param bool $success True on success, false otherwise.
134134 */
135- public static function new_follower ( $ activity , $ user_id , $ success ) {
135+ public static function new_follower ( $ activity , $ user_ids , $ success ) {
136136 // Only send notification if the follow was successful.
137137 if ( ! $ success ) {
138138 return ;
139139 }
140140
141+ // Extract the user ID (follows are always for a single user).
142+ $ user_id = \is_array ( $ user_ids ) ? \reset ( $ user_ids ) : $ user_ids ;
143+
141144 // Do not send notifications to the Application user.
142145 if ( Actors::APPLICATION_USER_ID === $ user_id ) {
143146 return ;
@@ -219,157 +222,200 @@ public static function new_follower( $activity, $user_id, $success ) {
219222 /**
220223 * Send a direct message.
221224 *
222- * @param array $activity The activity object.
223- * @param int $user_id The id of the local blog-user.
225+ * @param array $activity The activity object.
226+ * @param int|int[] $user_ids The id(s) of the local blog-user(s) .
224227 */
225- public static function direct_message ( $ activity , $ user_id ) {
226- if (
227- is_activity_public ( $ activity ) ||
228- // Only accept messages that have the user in the "to" field.
229- empty ( $ activity ['to ' ] ) ||
230- ! in_array ( Actors::get_by_id ( $ user_id )->get_id (), (array ) $ activity ['to ' ], true )
231- ) {
228+ public static function direct_message ( $ activity , $ user_ids ) {
229+ // Early return if activity is public or has no recipients.
230+ if ( is_activity_public ( $ activity ) || empty ( $ activity ['to ' ] ) ) {
232231 return ;
233232 }
234233
235- if ( $ user_id > Actors::BLOG_USER_ID ) {
236- if ( ! \get_user_option ( 'activitypub_mailer_new_dm ' , $ user_id ) ) {
237- return ;
234+ // Normalize to array.
235+ $ user_ids = (array ) $ user_ids ;
236+
237+ // Build a map of user_id => actor_id and filter to only users in the "to" field.
238+ $ recipients = array ();
239+ foreach ( $ user_ids as $ user_id ) {
240+ $ actor = Actors::get_by_id ( $ user_id );
241+ if ( \is_wp_error ( $ actor ) ) {
242+ continue ;
238243 }
239244
240- $ email = \get_userdata ( $ user_id )->user_email ;
241- } else {
242- if ( '1 ' !== \get_option ( 'activitypub_blog_user_mailer_new_dm ' , '1 ' ) ) {
243- return ;
245+ $ actor_id = $ actor ->get_id ();
246+ if ( \in_array ( $ actor_id , (array ) $ activity ['to ' ], true ) ) {
247+ $ recipients [ $ user_id ] = $ actor_id ;
244248 }
249+ }
245250
246- $ email = \get_option ( 'admin_email ' );
251+ // No matching recipients.
252+ if ( empty ( $ recipients ) ) {
253+ return ;
247254 }
248255
256+ // Get actor metadata once (shared for all emails).
249257 $ actor = get_remote_metadata_by_actor ( $ activity ['actor ' ] );
250-
251258 if ( ! $ actor || \is_wp_error ( $ actor ) || empty ( $ activity ['object ' ]['content ' ] ) ) {
252259 return ;
253260 }
254261
255262 $ actor = self ::normalize_actor ( $ actor );
256263
257- $ template_args = array (
258- 'activity ' => $ activity ,
259- 'actor ' => $ actor ,
260- 'user_id ' => $ user_id ,
261- );
264+ // Send email to each recipient.
265+ foreach ( $ recipients as $ user_id => $ actor_id ) {
266+ // Check user preferences.
267+ if ( $ user_id > Actors::BLOG_USER_ID ) {
268+ if ( ! \get_user_option ( 'activitypub_mailer_new_dm ' , $ user_id ) ) {
269+ continue ;
270+ }
262271
263- /* translators: 1: Blog name, 2 Actor name */
264- $ subject = \sprintf ( \esc_html__ ( '[%1$s] Direct Message from: %2$s ' , 'activitypub ' ), \esc_html ( \get_option ( 'blogname ' ) ), \esc_html ( $ actor ['name ' ] ) );
272+ $ email = \get_userdata ( $ user_id )->user_email ;
273+ } else {
274+ if ( '1 ' !== \get_option ( 'activitypub_blog_user_mailer_new_dm ' , '1 ' ) ) {
275+ continue ;
276+ }
265277
266- \ob_start ();
267- \load_template ( ACTIVITYPUB_PLUGIN_DIR . 'templates/emails/new-dm.php ' , false , $ template_args );
268- $ html_message = \ob_get_clean ();
278+ $ email = \get_option ( 'admin_email ' );
279+ }
269280
270- $ alt_function = function ( $ mailer ) use ( $ actor , $ activity ) {
271- $ content = \html_entity_decode (
272- \wp_strip_all_tags (
273- str_replace ( '</p> ' , PHP_EOL . PHP_EOL , $ activity ['object ' ]['content ' ] )
274- ),
275- ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401
281+ $ template_args = array (
282+ 'activity ' => $ activity ,
283+ 'actor ' => $ actor ,
284+ 'user_id ' => $ user_id ,
276285 );
277286
278- /* translators: Actor name */
279- $ message = \sprintf ( \esc_html__ ( 'New Direct Message: %s ' , 'activitypub ' ), $ content ) . "\r\n\r\n" ;
280- /* translators: Actor name */
281- $ message .= \sprintf ( \esc_html__ ( 'From: %s ' , 'activitypub ' ), \esc_html ( $ actor ['name ' ] ) ) . "\r\n" ;
282- /* translators: Message URL */
283- $ message .= \sprintf ( \esc_html__ ( 'URL: %s ' , 'activitypub ' ), \esc_url ( $ activity ['object ' ]['id ' ] ) ) . "\r\n\r\n" ;
287+ /* translators: 1: Blog name, 2 Actor name */
288+ $ subject = \sprintf ( \esc_html__ ( '[%1$s] Direct Message from: %2$s ' , 'activitypub ' ), \esc_html ( \get_option ( 'blogname ' ) ), \esc_html ( $ actor ['name ' ] ) );
284289
285- $ mailer ->{ ' AltBody ' } = $ message ;
286- } ;
287- \add_action ( ' phpmailer_init ' , $ alt_function );
290+ \ob_start () ;
291+ \load_template ( ACTIVITYPUB_PLUGIN_DIR . ' templates/emails/new-dm.php ' , false , $ template_args ) ;
292+ $ html_message = \ob_get_clean ( );
288293
289- \wp_mail ( $ email , $ subject , $ html_message , array ( 'Content-type: text/html ' ) );
294+ $ alt_function = function ( $ mailer ) use ( $ actor , $ activity ) {
295+ $ content = \html_entity_decode (
296+ \wp_strip_all_tags (
297+ str_replace ( '</p> ' , PHP_EOL . PHP_EOL , $ activity ['object ' ]['content ' ] )
298+ ),
299+ ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401
300+ );
290301
291- \remove_action ( 'phpmailer_init ' , $ alt_function );
302+ /* translators: Actor name */
303+ $ message = \sprintf ( \esc_html__ ( 'New Direct Message: %s ' , 'activitypub ' ), $ content ) . "\r\n\r\n" ;
304+ /* translators: Actor name */
305+ $ message .= \sprintf ( \esc_html__ ( 'From: %s ' , 'activitypub ' ), \esc_html ( $ actor ['name ' ] ) ) . "\r\n" ;
306+ /* translators: Message URL */
307+ $ message .= \sprintf ( \esc_html__ ( 'URL: %s ' , 'activitypub ' ), \esc_url ( $ activity ['object ' ]['id ' ] ) ) . "\r\n\r\n" ;
308+
309+ $ mailer ->{'AltBody ' } = $ message ;
310+ };
311+ \add_action ( 'phpmailer_init ' , $ alt_function );
312+
313+ \wp_mail ( $ email , $ subject , $ html_message , array ( 'Content-type: text/html ' ) );
314+
315+ \remove_action ( 'phpmailer_init ' , $ alt_function );
316+ }
292317 }
293318
294319 /**
295320 * Send a mention notification.
296321 *
297- * @param array $activity The activity object.
298- * @param int $user_id The id of the local blog-user.
322+ * @param array $activity The activity object.
323+ * @param int|int[] $user_ids The id(s) of the local blog-user(s) .
299324 */
300- public static function mention ( $ activity , $ user_id ) {
301- if (
302- // Only accept messages that have the user in the "cc" field.
303- empty ( $ activity ['cc ' ] ) ||
304- ! in_array ( Actors::get_by_id ( $ user_id )->get_id (), (array ) $ activity ['cc ' ], true )
305- ) {
325+ public static function mention ( $ activity , $ user_ids ) {
326+ // Early return if activity has no cc recipients.
327+ if ( empty ( $ activity ['cc ' ] ) ) {
306328 return ;
307329 }
308330
309- if (
310- // Do not send a mention notification if the activity is a reply to a local post or comment.
311- is_activity_reply ( $ activity ) &&
312- object_id_to_comment ( $ activity ['object ' ]['id ' ] )
313- ) {
331+ // Do not send a mention notification if the activity is a reply to a local post or comment.
332+ if ( is_activity_reply ( $ activity ) && object_id_to_comment ( $ activity ['object ' ]['id ' ] ) ) {
314333 return ;
315334 }
316335
317- if ( $ user_id > Actors::BLOG_USER_ID ) {
318- if ( ! \get_user_option ( 'activitypub_mailer_new_mention ' , $ user_id ) ) {
319- return ;
336+ // Normalize to array.
337+ $ user_ids = (array ) $ user_ids ;
338+
339+ // Build a map of user_id => actor_id and filter to only users in the "cc" field.
340+ $ recipients = array ();
341+ foreach ( $ user_ids as $ user_id ) {
342+ $ actor = Actors::get_by_id ( $ user_id );
343+ if ( \is_wp_error ( $ actor ) ) {
344+ continue ;
320345 }
321346
322- $ email = \get_userdata ( $ user_id )->user_email ;
323- } else {
324- if ( '1 ' !== \get_option ( 'activitypub_blog_user_mailer_new_mention ' , '1 ' ) ) {
325- return ;
347+ $ actor_id = $ actor ->get_id ();
348+ if ( \in_array ( $ actor_id , (array ) $ activity ['cc ' ], true ) ) {
349+ $ recipients [ $ user_id ] = $ actor_id ;
326350 }
351+ }
327352
328- $ email = \get_option ( 'admin_email ' );
353+ // No matching recipients.
354+ if ( empty ( $ recipients ) ) {
355+ return ;
329356 }
330357
358+ // Get actor metadata once (shared for all emails).
331359 $ actor = get_remote_metadata_by_actor ( $ activity ['actor ' ] );
332360 if ( \is_wp_error ( $ actor ) ) {
333361 return ;
334362 }
335363
336364 $ actor = self ::normalize_actor ( $ actor );
337365
338- $ template_args = array (
339- 'activity ' => $ activity ,
340- 'actor ' => $ actor ,
341- 'user_id ' => $ user_id ,
342- );
366+ // Send email to each recipient.
367+ foreach ( $ recipients as $ user_id => $ actor_id ) {
368+ // Check user preferences.
369+ if ( $ user_id > Actors::BLOG_USER_ID ) {
370+ if ( ! \get_user_option ( 'activitypub_mailer_new_mention ' , $ user_id ) ) {
371+ continue ;
372+ }
343373
344- /* translators: 1: Blog name, 2 Actor name */
345- $ subject = \sprintf ( \esc_html__ ( '[%1$s] Mention from: %2$s ' , 'activitypub ' ), \esc_html ( \get_option ( 'blogname ' ) ), \esc_html ( $ actor ['name ' ] ) );
374+ $ email = \get_userdata ( $ user_id )->user_email ;
375+ } else {
376+ if ( '1 ' !== \get_option ( 'activitypub_blog_user_mailer_new_mention ' , '1 ' ) ) {
377+ continue ;
378+ }
346379
347- \ob_start ();
348- \load_template ( ACTIVITYPUB_PLUGIN_DIR . 'templates/emails/new-mention.php ' , false , $ template_args );
349- $ html_message = \ob_get_clean ();
380+ $ email = \get_option ( 'admin_email ' );
381+ }
350382
351- $ alt_function = function ( $ mailer ) use ( $ actor , $ activity ) {
352- $ content = \html_entity_decode (
353- \wp_strip_all_tags (
354- str_replace ( '</p> ' , PHP_EOL . PHP_EOL , $ activity ['object ' ]['content ' ] )
355- ),
356- ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401
383+ $ template_args = array (
384+ 'activity ' => $ activity ,
385+ 'actor ' => $ actor ,
386+ 'user_id ' => $ user_id ,
357387 );
358388
359- /* translators: Message content */
360- $ message = \sprintf ( \esc_html__ ( 'New Mention: %s ' , 'activitypub ' ), $ content ) . "\r\n\r\n" ;
361- /* translators: Actor name */
362- $ message .= \sprintf ( \esc_html__ ( 'From: %s ' , 'activitypub ' ), \esc_html ( $ actor ['name ' ] ) ) . "\r\n" ;
363- /* translators: Message URL */
364- $ message .= \sprintf ( \esc_html__ ( 'URL: %s ' , 'activitypub ' ), \esc_url ( $ activity ['object ' ]['id ' ] ) ) . "\r\n\r\n" ;
389+ /* translators: 1: Blog name, 2 Actor name */
390+ $ subject = \sprintf ( \esc_html__ ( '[%1$s] Mention from: %2$s ' , 'activitypub ' ), \esc_html ( \get_option ( 'blogname ' ) ), \esc_html ( $ actor ['name ' ] ) );
365391
366- $ mailer ->{ ' AltBody ' } = $ message ;
367- } ;
368- \add_action ( ' phpmailer_init ' , $ alt_function );
392+ \ob_start () ;
393+ \load_template ( ACTIVITYPUB_PLUGIN_DIR . ' templates/emails/new-mention.php ' , false , $ template_args ) ;
394+ $ html_message = \ob_get_clean ( );
369395
370- \wp_mail ( $ email , $ subject , $ html_message , array ( 'Content-type: text/html ' ) );
396+ $ alt_function = function ( $ mailer ) use ( $ actor , $ activity ) {
397+ $ content = \html_entity_decode (
398+ \wp_strip_all_tags (
399+ str_replace ( '</p> ' , PHP_EOL . PHP_EOL , $ activity ['object ' ]['content ' ] )
400+ ),
401+ ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401
402+ );
371403
372- \remove_action ( 'phpmailer_init ' , $ alt_function );
404+ /* translators: Message content */
405+ $ message = \sprintf ( \esc_html__ ( 'New Mention: %s ' , 'activitypub ' ), $ content ) . "\r\n\r\n" ;
406+ /* translators: Actor name */
407+ $ message .= \sprintf ( \esc_html__ ( 'From: %s ' , 'activitypub ' ), \esc_html ( $ actor ['name ' ] ) ) . "\r\n" ;
408+ /* translators: Message URL */
409+ $ message .= \sprintf ( \esc_html__ ( 'URL: %s ' , 'activitypub ' ), \esc_url ( $ activity ['object ' ]['id ' ] ) ) . "\r\n\r\n" ;
410+
411+ $ mailer ->{'AltBody ' } = $ message ;
412+ };
413+ \add_action ( 'phpmailer_init ' , $ alt_function );
414+
415+ \wp_mail ( $ email , $ subject , $ html_message , array ( 'Content-type: text/html ' ) );
416+
417+ \remove_action ( 'phpmailer_init ' , $ alt_function );
418+ }
373419 }
374420
375421 /**
0 commit comments