Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3912b78
Trash child notes when a top level note is trashed
adamsilverstein Nov 10, 2025
05e669f
Add tests for child note deletion
adamsilverstein Nov 10, 2025
edfe396
phpcbf
adamsilverstein Nov 10, 2025
67014be
Merge branch 'trunk' into add/trash-child-notes-with-parent
adamsilverstein Nov 10, 2025
6f09761
Update src/wp-includes/comment.php
adamsilverstein Nov 11, 2025
f0d2076
Update src/wp-includes/comment.php
adamsilverstein Nov 11, 2025
d6cf076
Check child deletion with both ‘0’ and ‘1’ statuses
adamsilverstein Nov 11, 2025
4c0cc8a
Include all decendents, return success status
adamsilverstein Nov 11, 2025
f5c64ba
Delete descendent notes immediately when EMPTY_TRASH_DAYS is 0
adamsilverstein Nov 11, 2025
d30b1bb
phpcbf
adamsilverstein Nov 11, 2025
5aa9e22
Inline descendent deletion/trash handling
adamsilverstein Nov 11, 2025
fc017d8
Test wp_trash_comment deletes a note and it's descendants when EMPTY_…
adamsilverstein Nov 11, 2025
dcb4fa3
Test that all descendants including grandchildren are trashed
adamsilverstein Nov 11, 2025
baaf885
phpcbf
adamsilverstein Nov 11, 2025
5e30cb3
enable setting EMPTY_TRASH_DAYS
adamsilverstein Nov 11, 2025
85e04ee
remove test_wp_delete_comment_deletes_descendants_when_empty_trash_da…
adamsilverstein Nov 11, 2025
e09bfd8
remove grandchildren test
adamsilverstein Nov 12, 2025
4b44a79
Cleanup
adamsilverstein Nov 12, 2025
5800263
Update tests/phpunit/tests/comment.php
adamsilverstein Nov 12, 2025
cef8390
Merge branch 'trunk' into add/trash-child-notes-with-parent
adamsilverstein Nov 13, 2025
a14f848
Return early if wp_delete_comment fails
adamsilverstein Nov 13, 2025
b9c878c
No need to set success, already true here
adamsilverstein Nov 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/wp-includes/comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -1616,12 +1617,38 @@ 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
*
Expand Down
221 changes: 221 additions & 0 deletions tests/phpunit/tests/comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
}
Loading