From 3912b78e0679bfe9ff2fa84d4f09fe3452d97fa9 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 10 Nov 2025 11:28:37 -0700 Subject: [PATCH 01/20] Trash child notes when a top level note is trashed --- src/wp-includes/comment.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index a6deb2db599f5..02f47fc51fd3a 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -1574,6 +1574,7 @@ function wp_delete_comment( $comment_id, $force_delete = false ) { * If Trash is disabled, comment is permanently deleted. * * @since 2.9.0 + * @since 6.9.1 Any child notes are deleted when deleting a note. * * @param int|WP_Comment $comment_id Comment ID or WP_Comment object. * @return bool True on success, false on failure. @@ -1616,12 +1617,36 @@ function wp_trash_comment( $comment_id ) { */ do_action( 'trashed_comment', $comment->comment_ID, $comment ); + // For top level 'note' type comments, also trash any children as well. + if ( 'note' === $comment->comment_type && 0 === (int) $comment->comment_parent ) { + wp_trash_comment_children( $comment->comment_ID ); + } + return true; } return false; } +/** + * Delete all of a note's children (replies). + * + * @since 6.9.1 + * + * @param int $comment_id The comment ID. + */ +function wp_trash_comment_children( $comment_id ) { + $children = get_comments( array( + 'parent' => $comment_id, + 'status' => 'all', + 'type' => 'note', + 'fields' => 'ids', + ) ); + foreach ( $children as $child_id ) { + wp_trash_comment( $child_id ); + } +} + /** * Removes a comment from the Trash * From 05e669f90f70b713f68a4db8cc32ab669f842ffd Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 10 Nov 2025 11:39:59 -0700 Subject: [PATCH 02/20] Add tests for child note deletion --- tests/phpunit/tests/comment.php | 221 ++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) diff --git a/tests/phpunit/tests/comment.php b/tests/phpunit/tests/comment.php index a8f15e1deaec3..ee885ccf05412 100644 --- a/tests/phpunit/tests/comment.php +++ b/tests/phpunit/tests/comment.php @@ -1672,4 +1672,225 @@ public function test_unspam_should_invalidate_comment_cache() { $this->assertSame( '1', $comment->comment_approved ); } + + /** + * Tests that trashing a top-level note also trashes all direct child notes. + * + * @covers ::wp_trash_comment + * @covers ::wp_trash_comment_children + */ + public function test_wp_trash_comment_trashes_child_notes() { + // Create a parent note (top-level, comment_parent=0). + $parent_note = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => 0, + 'comment_approved' => '1', + ) + ); + + // Create child notes under the parent. + $child_note_1 = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => $parent_note, + 'comment_approved' => '1', + ) + ); + + $child_note_2 = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => $parent_note, + 'comment_approved' => '1', + ) + ); + + $child_note_3 = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => $parent_note, + 'comment_approved' => '1', + ) + ); + + // Verify initial state - all notes are approved. + $this->assertSame( '1', get_comment( $parent_note )->comment_approved ); + $this->assertSame( '1', get_comment( $child_note_1 )->comment_approved ); + $this->assertSame( '1', get_comment( $child_note_2 )->comment_approved ); + $this->assertSame( '1', get_comment( $child_note_3 )->comment_approved ); + + // Trash the parent note. + wp_trash_comment( $parent_note ); + + // Verify parent note is trashed. + $this->assertSame( 'trash', get_comment( $parent_note )->comment_approved ); + + // Verify all child notes are also trashed. + $this->assertSame( 'trash', get_comment( $child_note_1 )->comment_approved ); + $this->assertSame( 'trash', get_comment( $child_note_2 )->comment_approved ); + $this->assertSame( 'trash', get_comment( $child_note_3 )->comment_approved ); + } + + /** + * Tests that trashing a regular comment does NOT trash its children. + * + * @covers ::wp_trash_comment + */ + public function test_wp_trash_comment_does_not_trash_child_comments() { + // Create a parent comment (default type='comment'). + $parent_comment = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'comment', + 'comment_parent' => 0, + 'comment_approved' => '1', + ) + ); + + // Create child comments under the parent. + $child_comment_1 = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'comment', + 'comment_parent' => $parent_comment, + 'comment_approved' => '1', + ) + ); + + $child_comment_2 = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'comment', + 'comment_parent' => $parent_comment, + 'comment_approved' => '1', + ) + ); + + // Verify initial state - all comments are approved. + $this->assertSame( '1', get_comment( $parent_comment )->comment_approved ); + $this->assertSame( '1', get_comment( $child_comment_1 )->comment_approved ); + $this->assertSame( '1', get_comment( $child_comment_2 )->comment_approved ); + + // Trash the parent comment. + wp_trash_comment( $parent_comment ); + + // Verify parent comment is trashed. + $this->assertSame( 'trash', get_comment( $parent_comment )->comment_approved ); + + // Verify child comments are NOT trashed (maintaining existing behavior). + $this->assertSame( '1', get_comment( $child_comment_1 )->comment_approved ); + $this->assertSame( '1', get_comment( $child_comment_2 )->comment_approved ); + } + + /** + * Tests that trashing a child note does not affect parent or siblings. + * + * @covers ::wp_trash_comment + */ + public function test_wp_trash_comment_child_note_does_not_affect_parent_or_siblings() { + // Create a parent note. + $parent_note = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => 0, + 'comment_approved' => '1', + ) + ); + + // Create multiple child notes. + $child_note_1 = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => $parent_note, + 'comment_approved' => '1', + ) + ); + + $child_note_2 = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => $parent_note, + 'comment_approved' => '1', + ) + ); + + $child_note_3 = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => $parent_note, + 'comment_approved' => '1', + ) + ); + + // Trash only one child note. + wp_trash_comment( $child_note_2 ); + + // Verify the parent note is still approved. + $this->assertSame( '1', get_comment( $parent_note )->comment_approved ); + + // Verify the trashed child is trashed. + $this->assertSame( 'trash', get_comment( $child_note_2 )->comment_approved ); + + // Verify sibling notes are still approved. + $this->assertSame( '1', get_comment( $child_note_1 )->comment_approved ); + $this->assertSame( '1', get_comment( $child_note_3 )->comment_approved ); + } + + /** + * Tests that only top-level notes trigger child deletion. + * + * @covers ::wp_trash_comment + */ + public function test_wp_trash_comment_only_top_level_notes_trigger_child_deletion() { + // Create a parent note. + $parent_note = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => 0, + 'comment_approved' => '1', + ) + ); + + // Create a child note (not top-level, has comment_parent > 0). + $child_note = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => $parent_note, + 'comment_approved' => '1', + ) + ); + + // Create a sibling note (also not top-level). + $sibling_note = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => $parent_note, + 'comment_approved' => '1', + ) + ); + + // Trash the child note (which has comment_parent > 0). + wp_trash_comment( $child_note ); + + // Verify the child note is trashed. + $this->assertSame( 'trash', get_comment( $child_note )->comment_approved ); + + // Verify the parent note is NOT trashed. + $this->assertSame( '1', get_comment( $parent_note )->comment_approved ); + + // Verify the sibling note is NOT trashed (no cascade since child is not top-level). + $this->assertSame( '1', get_comment( $sibling_note )->comment_approved ); + } } From edfe39672afec1222c47bbb74c8916e2405f3c91 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 10 Nov 2025 11:42:03 -0700 Subject: [PATCH 03/20] phpcbf --- src/wp-includes/comment.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 02f47fc51fd3a..805633472b383 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -1636,12 +1636,14 @@ function wp_trash_comment( $comment_id ) { * @param int $comment_id The comment ID. */ function wp_trash_comment_children( $comment_id ) { - $children = get_comments( array( - 'parent' => $comment_id, - 'status' => 'all', - 'type' => 'note', - 'fields' => 'ids', - ) ); + $children = get_comments( + array( + 'parent' => $comment_id, + 'status' => 'all', + 'type' => 'note', + 'fields' => 'ids', + ) + ); foreach ( $children as $child_id ) { wp_trash_comment( $child_id ); } From 6f09761dcc3201824476f5a6ac9a6ae606baba22 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 11 Nov 2025 07:47:57 -0700 Subject: [PATCH 04/20] Update src/wp-includes/comment.php Co-authored-by: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> --- 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 805633472b383..4afb7ef7cee2e 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -1574,7 +1574,7 @@ function wp_delete_comment( $comment_id, $force_delete = false ) { * If Trash is disabled, comment is permanently deleted. * * @since 2.9.0 - * @since 6.9.1 Any child notes are deleted when deleting a note. + * @since 6.9.0 Any child notes are deleted when deleting a note. * * @param int|WP_Comment $comment_id Comment ID or WP_Comment object. * @return bool True on success, false on failure. From f0d2076a18e21021fec6dd66a17fea40f565d7a9 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 11 Nov 2025 07:48:03 -0700 Subject: [PATCH 05/20] Update src/wp-includes/comment.php Co-authored-by: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> --- 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 4afb7ef7cee2e..37e918314147a 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -1631,7 +1631,7 @@ function wp_trash_comment( $comment_id ) { /** * Delete all of a note's children (replies). * - * @since 6.9.1 + * @since 6.9.0 * * @param int $comment_id The comment ID. */ From d6cf076c4eb7b0a92a305c550d502ae2f562111e Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 11 Nov 2025 07:53:25 -0700 Subject: [PATCH 06/20] =?UTF-8?q?Check=20child=20deletion=20with=20both=20?= =?UTF-8?q?=E2=80=980=E2=80=99=20and=20=E2=80=981=E2=80=99=20statuses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/phpunit/tests/comment.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/phpunit/tests/comment.php b/tests/phpunit/tests/comment.php index ee885ccf05412..2f39f51f4e3b2 100644 --- a/tests/phpunit/tests/comment.php +++ b/tests/phpunit/tests/comment.php @@ -1678,15 +1678,16 @@ public function test_unspam_should_invalidate_comment_cache() { * * @covers ::wp_trash_comment * @covers ::wp_trash_comment_children + * @dataProvider data_comment_approved_statuses */ - public function test_wp_trash_comment_trashes_child_notes() { + public function test_wp_trash_comment_trashes_child_notes( $approved_status ) { // Create a parent note (top-level, comment_parent=0). $parent_note = self::factory()->comment->create( array( 'comment_post_ID' => self::$post_id, 'comment_type' => 'note', 'comment_parent' => 0, - 'comment_approved' => '1', + 'comment_approved' => $approved_status, ) ); @@ -1696,7 +1697,7 @@ public function test_wp_trash_comment_trashes_child_notes() { 'comment_post_ID' => self::$post_id, 'comment_type' => 'note', 'comment_parent' => $parent_note, - 'comment_approved' => '1', + 'comment_approved' => $approved_status, ) ); @@ -1705,7 +1706,7 @@ public function test_wp_trash_comment_trashes_child_notes() { 'comment_post_ID' => self::$post_id, 'comment_type' => 'note', 'comment_parent' => $parent_note, - 'comment_approved' => '1', + 'comment_approved' => $approved_status, ) ); @@ -1714,16 +1715,10 @@ public function test_wp_trash_comment_trashes_child_notes() { 'comment_post_ID' => self::$post_id, 'comment_type' => 'note', 'comment_parent' => $parent_note, - 'comment_approved' => '1', + 'comment_approved' => $approved_status, ) ); - // Verify initial state - all notes are approved. - $this->assertSame( '1', get_comment( $parent_note )->comment_approved ); - $this->assertSame( '1', get_comment( $child_note_1 )->comment_approved ); - $this->assertSame( '1', get_comment( $child_note_2 )->comment_approved ); - $this->assertSame( '1', get_comment( $child_note_3 )->comment_approved ); - // Trash the parent note. wp_trash_comment( $parent_note ); @@ -1736,6 +1731,16 @@ public function test_wp_trash_comment_trashes_child_notes() { $this->assertSame( 'trash', get_comment( $child_note_3 )->comment_approved ); } + /** + * Data provider for test_wp_trash_comment_trashes_child_notes. + */ + public function data_comment_approved_statuses() { + return array( + array( '1' ), + array( '0' ), + ); + } + /** * Tests that trashing a regular comment does NOT trash its children. * @@ -1771,11 +1776,6 @@ public function test_wp_trash_comment_does_not_trash_child_comments() { ) ); - // Verify initial state - all comments are approved. - $this->assertSame( '1', get_comment( $parent_comment )->comment_approved ); - $this->assertSame( '1', get_comment( $child_comment_1 )->comment_approved ); - $this->assertSame( '1', get_comment( $child_comment_2 )->comment_approved ); - // Trash the parent comment. wp_trash_comment( $parent_comment ); From 4c0cc8ae7522ab05264e36d0cb6c036f997c54eb Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 11 Nov 2025 08:01:54 -0700 Subject: [PATCH 07/20] Include all decendents, return success status --- src/wp-includes/comment.php | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 37e918314147a..0f08f01afe8b9 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -1619,7 +1619,7 @@ function wp_trash_comment( $comment_id ) { // For top level 'note' type comments, also trash any children as well. if ( 'note' === $comment->comment_type && 0 === (int) $comment->comment_parent ) { - wp_trash_comment_children( $comment->comment_ID ); + wp_trash_note_descendants( $comment->comment_ID ); } return true; @@ -1629,24 +1629,33 @@ function wp_trash_comment( $comment_id ) { } /** - * Delete all of a note's children (replies). + * Trash all of a note's descendants (replies). * * @since 6.9.0 * * @param int $comment_id The comment ID. + * @return bool Whether the descendants were successfully trashed. */ -function wp_trash_comment_children( $comment_id ) { - $children = get_comments( +function wp_trash_note_descendants( $comment_id ) { + $comment = get_comment( $comment_id ); + if ( ! $comment ) { + return false; + } + $children = $comment->get_children( array( - 'parent' => $comment_id, + 'fields' => 'ids', 'status' => 'all', 'type' => 'note', - 'fields' => 'ids', ) ); + + $success = true; foreach ( $children as $child_id ) { - wp_trash_comment( $child_id ); + if ( ! wp_trash_comment( $child_id ) ) { + $success = false; + } } + return $success; } /** From f5c64bab114ff5a98884b39cc7b4ef8771f3cb90 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 11 Nov 2025 08:08:57 -0700 Subject: [PATCH 08/20] Delete descendent notes immediately when EMPTY_TRASH_DAYS is 0 --- src/wp-includes/comment.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 0f08f01afe8b9..58316fce670d5 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -1581,6 +1581,7 @@ function wp_delete_comment( $comment_id, $force_delete = false ) { */ function wp_trash_comment( $comment_id ) { if ( ! EMPTY_TRASH_DAYS ) { + wp_trash_note_descendants( $comment_id ); return wp_delete_comment( $comment_id, true ); } @@ -1629,7 +1630,7 @@ function wp_trash_comment( $comment_id ) { } /** - * Trash all of a note's descendants (replies). + * Trash all of a note's descendants or delete immaediately if EMPTY_TRASH_DAYS is 0. * * @since 6.9.0 * @@ -1651,8 +1652,15 @@ function wp_trash_note_descendants( $comment_id ) { $success = true; foreach ( $children as $child_id ) { - if ( ! wp_trash_comment( $child_id ) ) { - $success = false; + if ( ! EMPTY_TRASH_DAYS ) { + // If trash is disabled, delete the comment permanently. + if ( ! wp_delete_comment( $child_id, true ) ) { + $success = false; + } + } else { + if ( ! wp_trash_comment( $child_id ) ) { + $success = false; + } } } return $success; From d30b1bb4b87f76cd9352dda6046f52a47bbc13d6 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 11 Nov 2025 08:10:49 -0700 Subject: [PATCH 09/20] phpcbf --- 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 58316fce670d5..0cc9c6830fa58 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -1657,7 +1657,7 @@ function wp_trash_note_descendants( $comment_id ) { if ( ! wp_delete_comment( $child_id, true ) ) { $success = false; } - } else { + } else { if ( ! wp_trash_comment( $child_id ) ) { $success = false; } From 5aa9e223278ee182ee4f9685009b4245337b4d87 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 11 Nov 2025 08:36:36 -0700 Subject: [PATCH 10/20] Inline descendent deletion/trash handling --- src/wp-includes/comment.php | 78 ++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 0cc9c6830fa58..f2715147dc9a4 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -1581,8 +1581,27 @@ function wp_delete_comment( $comment_id, $force_delete = false ) { */ function wp_trash_comment( $comment_id ) { if ( ! EMPTY_TRASH_DAYS ) { - wp_trash_note_descendants( $comment_id ); - return wp_delete_comment( $comment_id, true ); + $comment = get_comment( $comment_id ); + $success = wp_delete_comment( $comment_id, true ); + // Also delete descendants of top level 'note' type comments. + if ( $comment && 'note' === $comment->comment_type && 0 === (int) $comment->comment_parent ) { + $children = $comment->get_children( + array( + 'fields' => 'ids', + 'status' => 'all', + 'type' => 'note', + ) + ); + + $success = true; + foreach ( $children as $child_id ) { + if ( ! wp_delete_comment( $child_id, true ) ) { + $success = false; + } + } + } + + return $success; } $comment = get_comment( $comment_id ); @@ -1618,9 +1637,23 @@ function wp_trash_comment( $comment_id ) { */ do_action( 'trashed_comment', $comment->comment_ID, $comment ); - // For top level 'note' type comments, also trash any children as well. + // For top level 'note' type comments, also trash any descendants as well. if ( 'note' === $comment->comment_type && 0 === (int) $comment->comment_parent ) { - wp_trash_note_descendants( $comment->comment_ID ); + $children = $comment->get_children( + array( + 'fields' => 'ids', + 'status' => 'all', + 'type' => 'note', + ) + ); + + $success = true; + foreach ( $children as $child_id ) { + if ( ! wp_trash_comment( $child_id ) ) { + $success = false; + } + } + return $success; } return true; @@ -1629,43 +1662,6 @@ function wp_trash_comment( $comment_id ) { return false; } -/** - * Trash all of a note's descendants or delete immaediately if EMPTY_TRASH_DAYS is 0. - * - * @since 6.9.0 - * - * @param int $comment_id The comment ID. - * @return bool Whether the descendants were successfully trashed. - */ -function wp_trash_note_descendants( $comment_id ) { - $comment = get_comment( $comment_id ); - if ( ! $comment ) { - return false; - } - $children = $comment->get_children( - array( - 'fields' => 'ids', - 'status' => 'all', - 'type' => 'note', - ) - ); - - $success = true; - foreach ( $children as $child_id ) { - if ( ! EMPTY_TRASH_DAYS ) { - // If trash is disabled, delete the comment permanently. - if ( ! wp_delete_comment( $child_id, true ) ) { - $success = false; - } - } else { - if ( ! wp_trash_comment( $child_id ) ) { - $success = false; - } - } - } - return $success; -} - /** * Removes a comment from the Trash * From fc017d87ce038c81c3298cc65fa594773115bb43 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 11 Nov 2025 12:01:12 -0700 Subject: [PATCH 11/20] Test wp_trash_comment deletes a note and it's descendants when EMPTY_TRASH_DAYS is set to 0. --- tests/phpunit/tests/comment.php | 55 +++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/phpunit/tests/comment.php b/tests/phpunit/tests/comment.php index 2f39f51f4e3b2..65319445ecfaf 100644 --- a/tests/phpunit/tests/comment.php +++ b/tests/phpunit/tests/comment.php @@ -1893,4 +1893,59 @@ public function test_wp_trash_comment_only_top_level_notes_trigger_child_deletio // Verify the sibling note is NOT trashed (no cascade since child is not top-level). $this->assertSame( '1', get_comment( $sibling_note )->comment_approved ); } + + /** + * Test wp_trash_comment deletes a note and it's descendants when EMPTY_TRASH_DAYS is set to 0. + */ + public function test_wp_delete_comment_deletes_descendants_when_empty_trash_days_is_zero() { + // Set EMPTY_TRASH_DAYS to 0 to disable trashing. + define( 'EMPTY_TRASH_DAYS', 0 ); + $parent_note = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => 0, + 'comment_approved' => '1', + ) + ); + + // Create child notes under the parent. + $child_note_1 = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => $parent_note, + 'comment_approved' => '1', + ) + ); + + $child_note_2 = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => $parent_note, + 'comment_approved' => '1', + ) + ); + + $child_note_3 = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => $parent_note, + 'comment_approved' => $approved_status, + ) + ); + + // Trash the parent note. + wp_trash_comment( $parent_note ); + + // Verify that the parent note is actually deleted. + $this->assertNull( get_comment( $parent_note ) ); + + // Verify that the child notes have also been deleted. + $this->assertNull( get_comment( $child_note_1 ) ); + $this->assertNull( get_comment( $child_note_2 ) ); + $this->assertNull( get_comment( $child_note_3 ) ); + } } From dcb4fa3507465133af1056a942806f1a0d0d081a Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 11 Nov 2025 12:05:19 -0700 Subject: [PATCH 12/20] Test that all descendants including grandchildren are trashed --- tests/phpunit/tests/comment.php | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/phpunit/tests/comment.php b/tests/phpunit/tests/comment.php index 65319445ecfaf..94097ffcdf9ed 100644 --- a/tests/phpunit/tests/comment.php +++ b/tests/phpunit/tests/comment.php @@ -1947,5 +1947,49 @@ public function test_wp_delete_comment_deletes_descendants_when_empty_trash_days $this->assertNull( get_comment( $child_note_1 ) ); $this->assertNull( get_comment( $child_note_2 ) ); $this->assertNull( get_comment( $child_note_3 ) ); + + } + + /** + * Test that all descendants including grandchildren are trashed when + * a parent note is trashed. Note that grandchildren are not currently + * possible, but the code should support them. + */ + public function test_wp_trash_comment_trashes_all_descendants_including_grandchildren() { + // Create a parent note. + $parent_note = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => 0, + 'comment_approved' => '1', + ) + ); + // Create a child note. + $child_note = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => $parent_note, + 'comment_approved' => '1', + ) + ); + // Create a grandchild note. + $grandchild_note = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => 'note', + 'comment_parent' => $child_note, + 'comment_approved' => '1', + ) + ); + + // Trash the parent note. + wp_trash_comment( $parent_note ); + + // Verify that all notes are trashed. + $this->assertSame( 'trash', get_comment( $parent_note )->comment_approved ); + $this->assertSame( 'trash', get_comment( $child_note )->comment_approved ); + $this->assertSame( 'trash', get_comment( $grandchild_note )->comment_approved ); } } From baaf885d7e07d305eb01e09148127be19b4dc9e7 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 11 Nov 2025 12:09:03 -0700 Subject: [PATCH 13/20] phpcbf --- tests/phpunit/tests/comment.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/phpunit/tests/comment.php b/tests/phpunit/tests/comment.php index 94097ffcdf9ed..5ff6d386acabe 100644 --- a/tests/phpunit/tests/comment.php +++ b/tests/phpunit/tests/comment.php @@ -1947,7 +1947,6 @@ public function test_wp_delete_comment_deletes_descendants_when_empty_trash_days $this->assertNull( get_comment( $child_note_1 ) ); $this->assertNull( get_comment( $child_note_2 ) ); $this->assertNull( get_comment( $child_note_3 ) ); - } /** From 5e30cb3c54c174dba1b52200a5c1d40e37a136ed Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 11 Nov 2025 15:16:18 -0700 Subject: [PATCH 14/20] enable setting EMPTY_TRASH_DAYS --- tests/phpunit/tests/comment.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/phpunit/tests/comment.php b/tests/phpunit/tests/comment.php index 5ff6d386acabe..ae89344ef05c4 100644 --- a/tests/phpunit/tests/comment.php +++ b/tests/phpunit/tests/comment.php @@ -1896,6 +1896,9 @@ public function test_wp_trash_comment_only_top_level_notes_trigger_child_deletio /** * Test wp_trash_comment deletes a note and it's descendants when EMPTY_TRASH_DAYS is set to 0. + * + * @runInSeparateProcess + * @preserveGlobalState disabled */ public function test_wp_delete_comment_deletes_descendants_when_empty_trash_days_is_zero() { // Set EMPTY_TRASH_DAYS to 0 to disable trashing. From 85e04ee96a5dc77d8c19bb308fdc6980de967f87 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 11 Nov 2025 16:49:41 -0700 Subject: [PATCH 15/20] remove test_wp_delete_comment_deletes_descendants_when_empty_trash_days_is_zero --- tests/phpunit/tests/comment.php | 58 --------------------------------- 1 file changed, 58 deletions(-) diff --git a/tests/phpunit/tests/comment.php b/tests/phpunit/tests/comment.php index ae89344ef05c4..22afcf40b2b16 100644 --- a/tests/phpunit/tests/comment.php +++ b/tests/phpunit/tests/comment.php @@ -1894,64 +1894,6 @@ public function test_wp_trash_comment_only_top_level_notes_trigger_child_deletio $this->assertSame( '1', get_comment( $sibling_note )->comment_approved ); } - /** - * Test wp_trash_comment deletes a note and it's descendants when EMPTY_TRASH_DAYS is set to 0. - * - * @runInSeparateProcess - * @preserveGlobalState disabled - */ - public function test_wp_delete_comment_deletes_descendants_when_empty_trash_days_is_zero() { - // Set EMPTY_TRASH_DAYS to 0 to disable trashing. - define( 'EMPTY_TRASH_DAYS', 0 ); - $parent_note = self::factory()->comment->create( - array( - 'comment_post_ID' => self::$post_id, - 'comment_type' => 'note', - 'comment_parent' => 0, - 'comment_approved' => '1', - ) - ); - - // Create child notes under the parent. - $child_note_1 = self::factory()->comment->create( - array( - 'comment_post_ID' => self::$post_id, - 'comment_type' => 'note', - 'comment_parent' => $parent_note, - 'comment_approved' => '1', - ) - ); - - $child_note_2 = self::factory()->comment->create( - array( - 'comment_post_ID' => self::$post_id, - 'comment_type' => 'note', - 'comment_parent' => $parent_note, - 'comment_approved' => '1', - ) - ); - - $child_note_3 = self::factory()->comment->create( - array( - 'comment_post_ID' => self::$post_id, - 'comment_type' => 'note', - 'comment_parent' => $parent_note, - 'comment_approved' => $approved_status, - ) - ); - - // Trash the parent note. - wp_trash_comment( $parent_note ); - - // Verify that the parent note is actually deleted. - $this->assertNull( get_comment( $parent_note ) ); - - // Verify that the child notes have also been deleted. - $this->assertNull( get_comment( $child_note_1 ) ); - $this->assertNull( get_comment( $child_note_2 ) ); - $this->assertNull( get_comment( $child_note_3 ) ); - } - /** * Test that all descendants including grandchildren are trashed when * a parent note is trashed. Note that grandchildren are not currently From e09bfd8ccd166062bbeb8c0a20d00ace54a37cf8 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 11 Nov 2025 20:43:51 -0700 Subject: [PATCH 16/20] remove grandchildren test --- tests/phpunit/tests/comment.php | 43 --------------------------------- 1 file changed, 43 deletions(-) diff --git a/tests/phpunit/tests/comment.php b/tests/phpunit/tests/comment.php index 22afcf40b2b16..2f39f51f4e3b2 100644 --- a/tests/phpunit/tests/comment.php +++ b/tests/phpunit/tests/comment.php @@ -1893,47 +1893,4 @@ public function test_wp_trash_comment_only_top_level_notes_trigger_child_deletio // Verify the sibling note is NOT trashed (no cascade since child is not top-level). $this->assertSame( '1', get_comment( $sibling_note )->comment_approved ); } - - /** - * Test that all descendants including grandchildren are trashed when - * a parent note is trashed. Note that grandchildren are not currently - * possible, but the code should support them. - */ - public function test_wp_trash_comment_trashes_all_descendants_including_grandchildren() { - // Create a parent note. - $parent_note = self::factory()->comment->create( - array( - 'comment_post_ID' => self::$post_id, - 'comment_type' => 'note', - 'comment_parent' => 0, - 'comment_approved' => '1', - ) - ); - // Create a child note. - $child_note = self::factory()->comment->create( - array( - 'comment_post_ID' => self::$post_id, - 'comment_type' => 'note', - 'comment_parent' => $parent_note, - 'comment_approved' => '1', - ) - ); - // Create a grandchild note. - $grandchild_note = self::factory()->comment->create( - array( - 'comment_post_ID' => self::$post_id, - 'comment_type' => 'note', - 'comment_parent' => $child_note, - 'comment_approved' => '1', - ) - ); - - // Trash the parent note. - wp_trash_comment( $parent_note ); - - // Verify that all notes are trashed. - $this->assertSame( 'trash', get_comment( $parent_note )->comment_approved ); - $this->assertSame( 'trash', get_comment( $child_note )->comment_approved ); - $this->assertSame( 'trash', get_comment( $grandchild_note )->comment_approved ); - } } From 4b44a798660e557c9fbd654dcd34571a7a00c40b Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 11 Nov 2025 20:44:03 -0700 Subject: [PATCH 17/20] Cleanup --- 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 f2715147dc9a4..2bc9a9a2c2467 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -1583,7 +1583,7 @@ function wp_trash_comment( $comment_id ) { if ( ! EMPTY_TRASH_DAYS ) { $comment = get_comment( $comment_id ); $success = wp_delete_comment( $comment_id, true ); - // Also delete descendants of top level 'note' type comments. + // Also delete children of top level 'note' type comments. if ( $comment && 'note' === $comment->comment_type && 0 === (int) $comment->comment_parent ) { $children = $comment->get_children( array( @@ -1637,7 +1637,7 @@ function wp_trash_comment( $comment_id ) { */ do_action( 'trashed_comment', $comment->comment_ID, $comment ); - // For top level 'note' type comments, also trash any descendants as well. + // For top level 'note' type comments, also trash children. if ( 'note' === $comment->comment_type && 0 === (int) $comment->comment_parent ) { $children = $comment->get_children( array( From 5800263c8dadfcbbcf73b0bd496a13921c3727d9 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 12 Nov 2025 08:21:47 -0700 Subject: [PATCH 18/20] Update tests/phpunit/tests/comment.php Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com> --- tests/phpunit/tests/comment.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/phpunit/tests/comment.php b/tests/phpunit/tests/comment.php index 2f39f51f4e3b2..5c5ff192bf831 100644 --- a/tests/phpunit/tests/comment.php +++ b/tests/phpunit/tests/comment.php @@ -1677,7 +1677,6 @@ public function test_unspam_should_invalidate_comment_cache() { * Tests that trashing a top-level note also trashes all direct child notes. * * @covers ::wp_trash_comment - * @covers ::wp_trash_comment_children * @dataProvider data_comment_approved_statuses */ public function test_wp_trash_comment_trashes_child_notes( $approved_status ) { From a14f8483f5cee038e4bebac9ed95b68cfbc9cee1 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 13 Nov 2025 10:14:46 -0700 Subject: [PATCH 19/20] Return early if wp_delete_comment fails --- src/wp-includes/comment.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 2812c075f807b..6d9824c907f59 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -1583,6 +1583,11 @@ function wp_trash_comment( $comment_id ) { if ( ! EMPTY_TRASH_DAYS ) { $comment = get_comment( $comment_id ); $success = wp_delete_comment( $comment_id, true ); + + if ( ! $success ) { + return false; + } + // Also delete children of top level 'note' type comments. if ( $comment && 'note' === $comment->comment_type && 0 === (int) $comment->comment_parent ) { $children = $comment->get_children( From b9c878c86fa7a3363305055ff3c471252ce6eb7f Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 13 Nov 2025 10:16:09 -0700 Subject: [PATCH 20/20] No need to set success, already true here --- src/wp-includes/comment.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 6d9824c907f59..a2d7409d0c0de 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -1598,7 +1598,6 @@ function wp_trash_comment( $comment_id ) { ) ); - $success = true; foreach ( $children as $child_id ) { if ( ! wp_delete_comment( $child_id, true ) ) { $success = false;