From eee5a9fbbf7bb68b6835558a05232c16b72ecf07 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Fri, 7 Nov 2025 09:27:28 -0700 Subject: [PATCH 01/13] Use comment status and type to set $maybe_notify default --- src/wp-includes/comment.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 70d78ed33c848..73fd0a7d599d5 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -2476,6 +2476,13 @@ function wp_new_comment_notify_postauthor( $comment_id ) { $maybe_notify = $is_note ? get_option( 'wp_notes_notify', 1 ) : get_option( 'comments_notify' ); + // By default, only notify for approved comments and notes. + if ( + ! isset( $comment->comment_approved ) || + ( '1' !== $comment->comment_approved && ! $is_note ) ) { + $maybe_notify = false; + } + /** * Filters whether to send the post author new comment notification emails, * overriding the site setting. @@ -2495,13 +2502,6 @@ function wp_new_comment_notify_postauthor( $comment_id ) { return false; } - // Send notifications for approved comments and all notes. - if ( - ! isset( $comment->comment_approved ) || - ( '1' !== $comment->comment_approved && ! $is_note ) ) { - return false; - } - return wp_notify_postauthor( $comment_id ); } From 19a8db7e95f373a7865307936bbf6719e696da76 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Fri, 7 Nov 2025 09:36:31 -0700 Subject: [PATCH 02/13] Update filter doc block --- src/wp-includes/comment.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 73fd0a7d599d5..a9174be04b6bb 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -2484,8 +2484,9 @@ function wp_new_comment_notify_postauthor( $comment_id ) { } /** - * Filters whether to send the post author new comment notification emails, - * overriding the site setting. + * Filters whether to send the post author new comment and note notification emails, + * overriding the site settings and defaults. By default, notifications are sent for + * all notes and for approved comments. * * @since 4.4.0 * From a45b3b4a2406c44b5d95825e9383f1568dd02fac Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Mon, 10 Nov 2025 14:27:00 -0700 Subject: [PATCH 03/13] Update comment.php Co-authored-by: Aaron Jorbin --- src/wp-includes/comment.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index a9174be04b6bb..d9a9bd1b8d318 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -2489,6 +2489,7 @@ function wp_new_comment_notify_postauthor( $comment_id ) { * all notes and for approved comments. * * @since 4.4.0 + * @since 6.9.0 Comment approval status is checked before this filter. * * @param bool $maybe_notify Whether to notify the post author about the new comment. * @param int $comment_id The ID of the comment for the notification. From 7c217dd49c831ce7cb6e0fc800e0bc95a67311c4 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 25 Feb 2026 16:24:19 +0700 Subject: [PATCH 04/13] Apply suggestion from @westonruter Co-authored-by: Weston Ruter --- src/wp-includes/comment.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index d9a9bd1b8d318..150cdd4700e63 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -2474,13 +2474,16 @@ function wp_new_comment_notify_postauthor( $comment_id ) { $comment = get_comment( $comment_id ); $is_note = ( $comment && 'note' === $comment->comment_type ); - $maybe_notify = $is_note ? get_option( 'wp_notes_notify', 1 ) : get_option( 'comments_notify' ); - // By default, only notify for approved comments and notes. if ( ! isset( $comment->comment_approved ) || - ( '1' !== $comment->comment_approved && ! $is_note ) ) { - $maybe_notify = false; + ( '1' !== $comment->comment_approved && ! $is_note ) + ) { + $maybe_notify = false; + } else if ( $is_note ) { + $maybe_notify = get_option( 'wp_notes_notify', 1 ); + } else { + $maybe_notify = get_option( 'comments_notify' ); } /** @@ -2489,7 +2492,7 @@ function wp_new_comment_notify_postauthor( $comment_id ) { * all notes and for approved comments. * * @since 4.4.0 - * @since 6.9.0 Comment approval status is checked before this filter. + * @since 6.9.0 Comment approval status is checked before this filter. * * @param bool $maybe_notify Whether to notify the post author about the new comment. * @param int $comment_id The ID of the comment for the notification. From 9adf0e575f7ac1bbf05e557e8694bbf7c2f588c6 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 26 Feb 2026 10:49:31 +0700 Subject: [PATCH 05/13] Apply suggestions from Weston Co-authored-by: Weston Ruter --- src/wp-includes/comment.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 150cdd4700e63..49ea28e3f0a60 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -2472,13 +2472,13 @@ function wp_new_comment_notify_moderator( $comment_id ) { */ function wp_new_comment_notify_postauthor( $comment_id ) { $comment = get_comment( $comment_id ); + if ( ! ( $comment instanceof WP_Comment ) ) { + return false; + } $is_note = ( $comment && 'note' === $comment->comment_type ); // By default, only notify for approved comments and notes. - if ( - ! isset( $comment->comment_approved ) || - ( '1' !== $comment->comment_approved && ! $is_note ) - ) { + if ( '1' !== $comment->comment_approved && ! $is_note ) { $maybe_notify = false; } else if ( $is_note ) { $maybe_notify = get_option( 'wp_notes_notify', 1 ); From bc0918c2568680f18b60088a7e66891f1feedb64 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 17 Mar 2026 10:54:35 -0700 Subject: [PATCH 06/13] Update src/wp-includes/comment.php Co-authored-by: Weston Ruter --- src/wp-includes/comment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 75933a9e6f8f7..bb9f855cd2189 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -2475,7 +2475,7 @@ function wp_new_comment_notify_postauthor( $comment_id ) { if ( ! ( $comment instanceof WP_Comment ) ) { return false; } - $is_note = ( $comment && 'note' === $comment->comment_type ); + $is_note = ( 'note' === $comment->comment_type ); // By default, only notify for approved comments and notes. if ( '1' !== $comment->comment_approved && ! $is_note ) { From e74d3cfaed1130f401237c0a378a9d49ec1007a6 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 17 Mar 2026 11:05:49 -0700 Subject: [PATCH 07/13] Use comment object ID after get_comment() call The get_comment() return value is filtered, so reassign $comment_id from the comment object to ensure consistency between the property checks and subsequent function calls. Also removes a redundant null check on $comment. --- src/wp-includes/comment.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index bb9f855cd2189..02616ede5c533 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -2475,7 +2475,8 @@ function wp_new_comment_notify_postauthor( $comment_id ) { if ( ! ( $comment instanceof WP_Comment ) ) { return false; } - $is_note = ( 'note' === $comment->comment_type ); + $comment_id = $comment->comment_ID; + $is_note = ( 'note' === $comment->comment_type ); // By default, only notify for approved comments and notes. if ( '1' !== $comment->comment_approved && ! $is_note ) { From 422599af4c2d2c0aa7d8b72fc0446ad873779213 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 14 Jul 2026 10:49:12 -0700 Subject: [PATCH 08/13] Use elseif and correct the @since version for the 7.1 milestone --- src/wp-includes/comment.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index ca141a8e00e21..ece5ed80caf6a 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -2481,7 +2481,7 @@ function wp_new_comment_notify_postauthor( $comment_id ) { // By default, only notify for approved comments and notes. if ( '1' !== $comment->comment_approved && ! $is_note ) { $maybe_notify = false; - } else if ( $is_note ) { + } elseif ( $is_note ) { $maybe_notify = get_option( 'wp_notes_notify', 1 ); } else { $maybe_notify = get_option( 'comments_notify' ); @@ -2493,7 +2493,7 @@ function wp_new_comment_notify_postauthor( $comment_id ) { * all notes and for approved comments. * * @since 4.4.0 - * @since 6.9.0 Comment approval status is checked before this filter. + * @since 7.1.0 Comment approval status is checked before this filter. * * @param bool $maybe_notify Whether to notify the post author about the new comment. * @param int $comment_id The ID of the comment for the notification. From a7620c129d331a1a29ebfce43e37db1f4539b1f2 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 14 Jul 2026 10:49:12 -0700 Subject: [PATCH 09/13] Add tests proving the notify_post_author filter has full control The filter override and default-value tests fail against trunk, where the approval check runs after the filter and silently vetoes a true result for unapproved comments. --- tests/phpunit/tests/comment.php | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/phpunit/tests/comment.php b/tests/phpunit/tests/comment.php index 11e78140f1020..370fe43025e59 100644 --- a/tests/phpunit/tests/comment.php +++ b/tests/phpunit/tests/comment.php @@ -1109,6 +1109,77 @@ public function test_wp_new_comment_notify_postauthor_should_not_send_email_when $this->assertFalse( $sent ); } + /** + * @ticket 64217 + * + * @covers ::wp_new_comment_notify_postauthor + */ + public function test_wp_new_comment_notify_postauthor_filter_should_receive_false_for_unapproved_comment() { + $c = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_approved' => '0', + ) + ); + + update_option( 'comments_notify', 1 ); + + $maybe_notify = null; + add_filter( + 'notify_post_author', + static function ( $value ) use ( &$maybe_notify ) { + $maybe_notify = $value; + return $value; + } + ); + + $sent = wp_new_comment_notify_postauthor( $c ); + + $this->assertFalse( $maybe_notify, 'The filter should receive a default of false for an unapproved comment.' ); + $this->assertFalse( $sent, 'No notification should be sent for an unapproved comment by default.' ); + } + + /** + * @ticket 64217 + * + * @covers ::wp_new_comment_notify_postauthor + */ + public function test_wp_new_comment_notify_postauthor_filter_should_override_unapproved_comment() { + $c = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_approved' => '0', + ) + ); + + add_filter( 'notify_post_author', '__return_true' ); + + $sent = wp_new_comment_notify_postauthor( $c ); + + $this->assertTrue( $sent, 'The notify_post_author filter should be able to force a notification for an unapproved comment.' ); + } + + /** + * @ticket 64217 + * + * @covers ::wp_new_comment_notify_postauthor + */ + public function test_wp_new_comment_notify_postauthor_should_not_send_email_for_invalid_comment() { + $filter_fired = false; + add_filter( + 'notify_post_author', + static function ( $value ) use ( &$filter_fired ) { + $filter_fired = true; + return $value; + } + ); + + $sent = wp_new_comment_notify_postauthor( 0 ); + + $this->assertFalse( $sent, 'No notification should be sent for an invalid comment ID.' ); + $this->assertFalse( $filter_fired, 'The notify_post_author filter should not fire for an invalid comment ID.' ); + } + /** * @ticket 43805 * From 310d6d060c081cc8e19bb776692a53feaa7bf1c4 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 14 Jul 2026 14:40:46 -0700 Subject: [PATCH 10/13] Fix the invalid-comment test to avoid the global comment fallback The test passed 0 as an invalid comment ID, but get_comment() treats any empty ID as a request for the global $comment, which earlier tests in the same file leave populated - so the instanceof guard never triggered and the filter fired. Use a non-existent ID instead, which bypasses the fallback and exercises the early return. Test-only change. --- tests/phpunit/tests/comment.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/comment.php b/tests/phpunit/tests/comment.php index 370fe43025e59..cdd2a9243688b 100644 --- a/tests/phpunit/tests/comment.php +++ b/tests/phpunit/tests/comment.php @@ -1174,7 +1174,8 @@ static function ( $value ) use ( &$filter_fired ) { } ); - $sent = wp_new_comment_notify_postauthor( 0 ); + // An empty ID such as 0 would fall back to the global comment in get_comment(). + $sent = wp_new_comment_notify_postauthor( PHP_INT_MAX ); $this->assertFalse( $sent, 'No notification should be sent for an invalid comment ID.' ); $this->assertFalse( $filter_fired, 'The notify_post_author filter should not fire for an invalid comment ID.' ); From a878e69c28ec8eb2455caf71832da352bcddf318 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 14 Jul 2026 19:29:55 -0700 Subject: [PATCH 11/13] Document the 7.1 behavior changes to wp_new_comment_notify_postauthor() The approval check moving ahead of the notify_post_author filter and the early return for invalid comment IDs are both observable to extenders, so they need @since 7.1.0 entries on the function as well as the filter. Also moves the function description above the @since tag per the inline documentation standards. --- src/wp-includes/comment.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index ece5ed80caf6a..ac6a16d4f956d 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -2462,11 +2462,14 @@ function wp_new_comment_notify_moderator( $comment_id ) { /** * Sends a notification of a new comment to the post author. * - * @since 4.4.0 - * * Uses the {@see 'notify_post_author'} filter to determine whether the post author * should be notified when a new comment is added, overriding site setting. * + * @since 4.4.0 + * @since 7.1.0 The comment approval status is now checked before the + * {@see 'notify_post_author'} filter, and invalid comment IDs + * return false without firing the filter. + * * @param int $comment_id Comment ID. * @return bool True on success, false on failure. */ @@ -2493,7 +2496,8 @@ function wp_new_comment_notify_postauthor( $comment_id ) { * all notes and for approved comments. * * @since 4.4.0 - * @since 7.1.0 Comment approval status is checked before this filter. + * @since 7.1.0 Comment approval status is checked before this filter, + * and the filter no longer fires for invalid comment IDs. * * @param bool $maybe_notify Whether to notify the post author about the new comment. * @param int $comment_id The ID of the comment for the notification. @@ -4095,7 +4099,7 @@ function wp_comments_personal_data_eraser( $email_address, $page = 1 ) { $anonymized_comment['comment_author_url'] = ''; $anonymized_comment['user_id'] = 0; - $comment_id = (int) $comment->comment_ID; + $comment_id = $comment->comment_ID; /** * Filters whether to anonymize the comment. From 2f8b7019abdfa149643e2b76a47e0053112993d3 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 14 Jul 2026 19:29:55 -0700 Subject: [PATCH 12/13] Cast the comment ID to int before passing it to the filter WP_Comment::$comment_ID is a numeric string, while both hooked call sites previously delivered an int and the filter docblock documents the param as int. Casting preserves strict comparisons in existing filter callbacks and keeps the documented type accurate. --- src/wp-includes/comment.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index ac6a16d4f956d..a29edfc4b9fe8 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -2478,7 +2478,7 @@ function wp_new_comment_notify_postauthor( $comment_id ) { if ( ! ( $comment instanceof WP_Comment ) ) { return false; } - $comment_id = $comment->comment_ID; + $comment_id = (int) $comment->comment_ID; $is_note = ( 'note' === $comment->comment_type ); // By default, only notify for approved comments and notes. @@ -4099,7 +4099,7 @@ function wp_comments_personal_data_eraser( $email_address, $page = 1 ) { $anonymized_comment['comment_author_url'] = ''; $anonymized_comment['user_id'] = 0; - $comment_id = $comment->comment_ID; + $comment_id = (int) $comment->comment_ID; /** * Filters whether to anonymize the comment. From 0afaf8e6cf130df455d9a0deb537a80aa915b703 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 14 Jul 2026 19:30:06 -0700 Subject: [PATCH 13/13] Add tests for the unapproved note and comments_notify defaults Protects the two remaining default paths of the reordered logic: an unapproved note must still default to notifying (the note carve-out), and an approved comment's default must follow the comments_notify option. --- tests/phpunit/tests/comment.php | 59 +++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/phpunit/tests/comment.php b/tests/phpunit/tests/comment.php index cdd2a9243688b..2b7a704e2808e 100644 --- a/tests/phpunit/tests/comment.php +++ b/tests/phpunit/tests/comment.php @@ -1181,6 +1181,65 @@ static function ( $value ) use ( &$filter_fired ) { $this->assertFalse( $filter_fired, 'The notify_post_author filter should not fire for an invalid comment ID.' ); } + /** + * @ticket 64217 + * + * @covers ::wp_new_comment_notify_postauthor + */ + public function test_wp_new_comment_notify_postauthor_filter_should_receive_truthy_default_for_unapproved_note() { + $c = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_approved' => '0', + ) + ); + + update_option( 'wp_notes_notify', 1 ); + + $maybe_notify = null; + add_filter( + 'notify_post_author', + static function ( $value ) use ( &$maybe_notify ) { + $maybe_notify = $value; + return false; + } + ); + + wp_new_comment_notify_postauthor( $c ); + + $this->assertTrue( (bool) $maybe_notify, 'The filter should receive a truthy default for an unapproved note.' ); + } + + /** + * @ticket 64217 + * + * @covers ::wp_new_comment_notify_postauthor + */ + public function test_wp_new_comment_notify_postauthor_filter_should_receive_option_value_for_approved_comment() { + $c = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + ) + ); + + update_option( 'comments_notify', 0 ); + + $maybe_notify = null; + add_filter( + 'notify_post_author', + static function ( $value ) use ( &$maybe_notify ) { + $maybe_notify = $value; + return $value; + } + ); + + $sent = wp_new_comment_notify_postauthor( $c ); + + $this->assertFalse( (bool) $maybe_notify, 'The filter should receive the comments_notify option value as the default for an approved comment.' ); + $this->assertFalse( $sent, 'No notification should be sent for an approved comment when comments_notify is disabled.' ); + } + /** * @ticket 43805 *