Changeset 63012
- Timestamp:
- 08/04/2026 09:30:53 PM (3 weeks ago)
- Location:
- trunk
- Files:
-
- 1 added
- 2 edited
-
src/wp-includes/comment.php (modified) (1 diff)
-
src/wp-includes/default-filters.php (modified) (1 diff)
-
tests/phpunit/tests/comment/wpNotifyNoteMentions.php (added)
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/comment.php
r62928 r63012 2558 2558 2559 2559 /** 2560 * Extracts the mentioned user IDs from note content. 2561 * 2562 * Mentions are stored as chips carrying the `wp-note-mention` class plus a 2563 * `user-N` class token holding the mentioned user's ID: 2564 * `<span class="wp-note-mention user-N">@Name</span>`. Only elements that 2565 * carry both classes are treated as mentions. 2566 * 2567 * @since 7.1.0 2568 * 2569 * @param string $content Note (comment) content, as stored. 2570 * @return int[] Unique, positive mentioned user IDs. 2571 * @phpstan-return list<positive-int> 2572 */ 2573 function wp_get_note_mentioned_user_ids( string $content ): array { 2574 if ( ! str_contains( $content, 'wp-note-mention' ) ) { 2575 return array(); 2576 } 2577 2578 $user_ids = array(); 2579 $processor = new WP_HTML_Tag_Processor( $content ); 2580 while ( 2581 $processor->next_tag( 2582 array( 2583 'tag_name' => 'SPAN', 2584 'class_name' => 'wp-note-mention', 2585 ) 2586 ) 2587 ) { 2588 foreach ( $processor->class_list() as $class_name ) { 2589 if ( 1 === preg_match( '/^user-(\d+)$/', $class_name, $matches ) ) { 2590 $user_id = (int) $matches[1]; 2591 if ( $user_id > 0 ) { 2592 $user_ids[] = $user_id; 2593 } 2594 break; 2595 } 2596 } 2597 } 2598 2599 return array_values( array_unique( $user_ids, SORT_NUMERIC ) ); 2600 } 2601 2602 /** 2603 * Notifies mentioned users about a new note. 2604 * 2605 * Runs on {@see 'rest_insert_comment'} alongside the post author notification. 2606 * The recipient set is the users mentioned in this note, minus the note's own 2607 * author (a user is not notified about their own note) and the post author, 2608 * who is already notified about every note by 2609 * {@see wp_new_comment_via_rest_notify_postauthor()}. 2610 * 2611 * Only fires when a note is created, not when an existing one is edited, so 2612 * correcting a note does not re-notify everyone who already received it. 2613 * 2614 * @since 7.1.0 2615 * 2616 * @param WP_Comment|null $comment The note that was just inserted. (May only be null as an edge case.) 2617 * @param mixed $request The REST request. Unused. 2618 * @param bool $creating Whether this is a create (true) or update (false). 2619 */ 2620 function wp_notify_note_mentions( ?WP_Comment $comment, $request = null, bool $creating = true ): void { 2621 if ( ! $creating || ! $comment ) { 2622 return; 2623 } 2624 2625 if ( 'note' !== $comment->comment_type ) { 2626 return; 2627 } 2628 2629 // Share the single user-facing notes notification preference. 2630 if ( ! get_option( 'wp_notes_notify', 1 ) ) { 2631 return; 2632 } 2633 2634 $mentioned = wp_get_note_mentioned_user_ids( $comment->comment_content ); 2635 2636 $author_id = (int) $comment->user_id; 2637 $comment_post_id = (int) $comment->comment_post_ID; 2638 $post = $comment_post_id ? get_post( $comment_post_id ) : null; 2639 $post_author_id = $post ? (int) $post->post_author : 0; 2640 2641 /* 2642 * The recipient set is bounded and small (one note's mentions), so emails 2643 * are sent synchronously here. If notification volume ever warrants it, 2644 * the right fix is to offload delivery to a background queue rather than 2645 * throttle within the request. 2646 */ 2647 foreach ( $mentioned as $user_id ) { 2648 // Never notify the author about their own note. 2649 if ( $user_id === $author_id ) { 2650 continue; 2651 } 2652 2653 // The post author is already notified of every note. 2654 if ( $user_id === $post_author_id ) { 2655 continue; 2656 } 2657 2658 $user = get_userdata( $user_id ); 2659 if ( ! $user || empty( $user->user_email ) ) { 2660 continue; 2661 } 2662 2663 /* 2664 * Only notify users who can actually read the note. Notes are 2665 * internal: WP_REST_Comments_Controller::check_read_permission() 2666 * only exposes a note to its author or to users who can edit it, so 2667 * the email audience is held to the same bar. A plain read_post 2668 * check would leak note content to, for example, subscribers on a 2669 * public post, who cannot see the note in the editor. 2670 */ 2671 if ( ! user_can( $user_id, 'edit_comment', $comment->comment_ID ) ) { 2672 continue; 2673 } 2674 2675 wp_send_note_notification( $user, $comment, $post ); 2676 } 2677 } 2678 2679 /** 2680 * Sends a single note mention notification email. 2681 * 2682 * The email is composed in the recipient's locale, matching how other 2683 * user-directed notifications are composed, and links to the post editor the 2684 * same way the post author's note notification does. 2685 * 2686 * @since 7.1.0 2687 * 2688 * @param WP_User $user The recipient. 2689 * @param WP_Comment $comment The note that triggered the notification. 2690 * @param WP_Post|null $post The post the note belongs to. 2691 * @return bool Whether the email was accepted for delivery by {@see wp_mail()}. 2692 */ 2693 function wp_send_note_notification( WP_User $user, WP_Comment $comment, ?WP_Post $post ): bool { 2694 $switched_locale = switch_to_user_locale( $user->ID ); 2695 2696 /* 2697 * The site title and the post title are escaped on the way into the database, 2698 * and note content is stored as HTML. Both are reversed once here for the 2699 * plain text arena of emails. Decoding a second time would go too far and 2700 * resolve entities the author meant to be read literally. 2701 */ 2702 $blogname = wp_specialchars_decode( get_bloginfo( 'name', 'display' ), ENT_QUOTES ); 2703 $post_title = $post ? wp_specialchars_decode( get_the_title( $post ), ENT_QUOTES ) : ''; 2704 $author_name = $comment->comment_author ? $comment->comment_author : __( 'Someone' ); 2705 $content = wp_specialchars_decode( wp_strip_all_tags( $comment->comment_content ) ); 2706 2707 /* 2708 * The rest of the message is composed for the recipient, and so is the editor 2709 * link: get_edit_post_link() answers for whoever is current, which here is the 2710 * note's author over REST and nobody at all under WP-Cron. 2711 */ 2712 $edit_link = ''; 2713 if ( $post ) { 2714 $previous_user_id = get_current_user_id(); 2715 wp_set_current_user( $user->ID ); 2716 $edit_link = (string) get_edit_post_link( $post->ID, 'url' ); 2717 wp_set_current_user( $previous_user_id ); 2718 } 2719 2720 /* translators: 1: Note author's name, 2: Post title. */ 2721 $message = sprintf( __( '%1$s mentioned you in a note on "%2$s".' ), $author_name, $post_title ); 2722 /* translators: Note mention notification email subject. 1: Site title, 2: Post title. */ 2723 $subject = sprintf( __( '[%1$s] You were mentioned in a note on "%2$s"' ), $blogname, $post_title ); 2724 2725 $lines = array( $message, '' ); 2726 if ( '' !== $content ) { 2727 $lines[] = $content; 2728 } 2729 if ( $edit_link ) { 2730 $lines[] = ''; 2731 $lines[] = __( 'Edit This' ) . ': ' . $edit_link; 2732 } 2733 2734 // Declared explicitly so a filtered default cannot turn the message into HTML. 2735 $headers = 'Content-Type: text/plain; charset="' . get_option( 'blog_charset' ) . '"'; 2736 2737 $sent = wp_mail( $user->user_email, $subject, implode( "\n", $lines ), $headers ); 2738 2739 if ( $switched_locale ) { 2740 restore_previous_locale(); 2741 } 2742 2743 return $sent; 2744 } 2745 2746 /** 2560 2747 * Sets the status of a comment. 2561 2748 * -
trunk/src/wp-includes/default-filters.php
r62970 r63012 537 537 add_action( 'comment_post', 'wp_new_comment_notify_postauthor' ); 538 538 add_action( 'rest_insert_comment', 'wp_new_comment_via_rest_notify_postauthor' ); 539 add_action( 'rest_insert_comment', 'wp_notify_note_mentions', 10, 3 ); 539 540 add_action( 'after_password_reset', 'wp_password_change_notification' ); 540 541 add_action( 'register_new_user', 'wp_send_new_user_notifications' );
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)