Skip to content

Commit 1325dea

Browse files
committed
Comments: Prevent approved comments from being moved under pending parents.
An approved comment must have an approved parent, otherwise it drops out of the public comment list when its pending parent is not loaded. `edit_comment()` now rejects a pending parent whenever the child's final status is approved, using the `comment_approved` value submitted in the same update. The edit screen offers only approved parents when editing an approved comment, while pending comments may still use either. PHPUnit tests cover the parent selector, the server-side validation, and status changes made during the update. Developed in: WordPress#12639 Follow-up to [62673]. Props tyxla, youknowriad. Fixes #65688. git-svn-id: https://develop.svn.wordpress.org/trunk@62826 602fd350-edb4-49c9-b593-d223f7449a82
1 parent e420723 commit 1325dea

4 files changed

Lines changed: 111 additions & 7 deletions

File tree

src/wp-admin/edit-form-comment.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,15 @@
221221
if ( $comment_threading_enabled ) {
222222
$max_thread_depth = (int) get_option( 'thread_comments_depth' );
223223

224+
// Pending comments may be nested under pending parents, but approved comments require publicly visible parents.
225+
$parent_statuses = '1' === $comment->comment_approved ? 'approve' : array( 'approve', 'hold' );
226+
224227
// Limit the number of comments to keep memory usage and the size of the dropdown reasonable on busy posts.
225228
$post_comments = get_comments(
226229
array(
227230
'post_id' => $comment->comment_post_ID,
228231
'type' => 'comment',
229-
'status' => array( 'approve', 'hold' ),
232+
'status' => $parent_statuses,
230233
'orderby' => 'comment_date_gmt',
231234
'order' => 'DESC',
232235
'number' => 100,

src/wp-admin/includes/comment.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,18 @@ function edit_comment() {
9292
return new WP_Error( 'comment_parent_invalid', __( 'A comment cannot be a reply to itself.' ) );
9393
}
9494

95-
$parent = get_comment( $comment_parent );
95+
$parent = get_comment( $comment_parent );
96+
$parent_status = $parent ? wp_get_comment_status( $parent ) : false;
97+
$comment_status = $_POST['comment_approved'] ?? $comment->comment_approved;
98+
$comment_is_approved = in_array( $comment_status, array( 1, '1', 'approve' ), true );
9699

97-
// The parent must be a comment of the same type, on the same post, and not in the Trash or marked as spam.
100+
// The parent must be a comment of the same type, on the same post, and publicly visible if the comment is approved.
98101
if (
99102
! $parent
100103
|| (int) $parent->comment_post_ID !== (int) $comment->comment_post_ID
101104
|| $parent->comment_type !== $comment->comment_type
102-
|| in_array( wp_get_comment_status( $parent ), array( 'spam', 'trash' ), true )
105+
|| in_array( $parent_status, array( 'spam', 'trash' ), true )
106+
|| ( $comment_is_approved && 'approved' !== $parent_status )
103107
) {
104108
return new WP_Error( 'comment_parent_invalid', __( 'Invalid parent comment.' ) );
105109
}

tests/phpunit/tests/admin/EditFormComment_Test.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ public function test_should_list_valid_parents_and_exclude_invalid_ones() {
9696
'comment_approved' => 'trash',
9797
)
9898
);
99+
$pending_id = self::factory()->comment->create(
100+
array(
101+
'comment_post_ID' => self::$post_id,
102+
'comment_approved' => '0',
103+
)
104+
);
99105
$comment_id = self::factory()->comment->create( array( 'comment_post_ID' => self::$post_id ) );
100106
$child_id = self::factory()->comment->create(
101107
array(
@@ -113,6 +119,29 @@ public function test_should_list_valid_parents_and_exclude_invalid_ones() {
113119
$this->assertStringNotContainsString( "value='{$child_id}'", $dropdown, 'A reply to the comment being edited should not be listed.' );
114120
$this->assertStringNotContainsString( "value='{$spam_id}'", $dropdown, 'A spam comment should not be listed.' );
115121
$this->assertStringNotContainsString( "value='{$trash_id}'", $dropdown, 'A trashed comment should not be listed.' );
122+
$this->assertStringNotContainsString( "value='{$pending_id}'", $dropdown, 'A pending comment should not be listed for an approved comment.' );
123+
}
124+
125+
/**
126+
* @ticket 65688
127+
*/
128+
public function test_should_list_pending_parent_for_pending_comment() {
129+
$parent_id = self::factory()->comment->create(
130+
array(
131+
'comment_post_ID' => self::$post_id,
132+
'comment_approved' => '0',
133+
)
134+
);
135+
$comment_id = self::factory()->comment->create(
136+
array(
137+
'comment_post_ID' => self::$post_id,
138+
'comment_approved' => '0',
139+
)
140+
);
141+
142+
$dropdown = $this->get_parent_dropdown( $comment_id );
143+
144+
$this->assertStringContainsString( "value='{$parent_id}'", $dropdown, 'A pending comment should be listed for another pending comment.' );
116145
}
117146

118147
/**

tests/phpunit/tests/admin/includes/comment/EditComment_Test.php

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,21 @@ public function tear_down() {
4949
/**
5050
* Calls edit_comment() with a comment ID and a new parent, as submitted from the Edit Comment screen.
5151
*
52-
* @param int $comment_id Comment ID.
53-
* @param int $comment_parent New parent comment ID.
52+
* @param int $comment_id Comment ID.
53+
* @param int $comment_parent New parent comment ID.
54+
* @param string|null $comment_status Optional. New comment status.
5455
* @return int|WP_Error The edit_comment() return value.
5556
*/
56-
private function update_comment_parent( $comment_id, $comment_parent ) {
57+
private function update_comment_parent( $comment_id, $comment_parent, $comment_status = null ) {
5758
$_POST = array(
5859
'comment_ID' => $comment_id,
5960
'comment_parent' => $comment_parent,
6061
);
6162

63+
if ( null !== $comment_status ) {
64+
$_POST['comment_status'] = $comment_status;
65+
}
66+
6267
return edit_comment();
6368
}
6469

@@ -75,6 +80,69 @@ public function test_should_update_comment_parent() {
7580
$this->assertSame( (string) $parent_id, get_comment( $comment_id )->comment_parent );
7681
}
7782

83+
/**
84+
* @ticket 65688
85+
*/
86+
public function test_should_reject_unapproved_parent_for_approved_comment() {
87+
$parent_id = self::factory()->comment->create(
88+
array(
89+
'comment_post_ID' => self::$post_id,
90+
'comment_approved' => '0',
91+
)
92+
);
93+
$comment_id = self::factory()->comment->create( array( 'comment_post_ID' => self::$post_id ) );
94+
95+
$result = $this->update_comment_parent( $comment_id, $parent_id );
96+
97+
$this->assertWPError( $result );
98+
$this->assertSame( 'comment_parent_invalid', $result->get_error_code() );
99+
$this->assertSame( '0', get_comment( $comment_id )->comment_parent );
100+
}
101+
102+
/**
103+
* @ticket 65688
104+
*/
105+
public function test_should_reject_unapproved_parent_when_comment_is_approved_in_same_update() {
106+
$parent_id = self::factory()->comment->create(
107+
array(
108+
'comment_post_ID' => self::$post_id,
109+
'comment_approved' => '0',
110+
)
111+
);
112+
$comment_id = self::factory()->comment->create(
113+
array(
114+
'comment_post_ID' => self::$post_id,
115+
'comment_approved' => '0',
116+
)
117+
);
118+
119+
$result = $this->update_comment_parent( $comment_id, $parent_id, '1' );
120+
121+
$this->assertWPError( $result );
122+
$this->assertSame( 'comment_parent_invalid', $result->get_error_code() );
123+
$this->assertSame( '0', get_comment( $comment_id )->comment_approved );
124+
$this->assertSame( '0', get_comment( $comment_id )->comment_parent );
125+
}
126+
127+
/**
128+
* @ticket 65688
129+
*/
130+
public function test_should_allow_unapproved_parent_when_comment_is_unapproved_in_same_update() {
131+
$parent_id = self::factory()->comment->create(
132+
array(
133+
'comment_post_ID' => self::$post_id,
134+
'comment_approved' => '0',
135+
)
136+
);
137+
$comment_id = self::factory()->comment->create( array( 'comment_post_ID' => self::$post_id ) );
138+
139+
$result = $this->update_comment_parent( $comment_id, $parent_id, '0' );
140+
141+
$this->assertSame( 1, $result );
142+
$this->assertSame( '0', get_comment( $comment_id )->comment_approved );
143+
$this->assertSame( (string) $parent_id, get_comment( $comment_id )->comment_parent );
144+
}
145+
78146
/**
79147
* @ticket 65570
80148
*/

0 commit comments

Comments
 (0)