Skip to content

Commit 865bb9e

Browse files
authored
Fix Mailer callbacks to handle array of user IDs (#2415)
1 parent 7ef339c commit 865bb9e

File tree

2 files changed

+418
-97
lines changed

2 files changed

+418
-97
lines changed

includes/class-mailer.php

Lines changed: 143 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)