Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
82c76fa
Comments REST endpoint - support for multiple status values
adamsilverstein Sep 14, 2025
1fc983f
Add tests for getting comments by status, statuses or ‘all’
adamsilverstein Sep 14, 2025
4ee6fe8
spacing fixes for linter
adamsilverstein Sep 14, 2025
bdd5a6d
Use count
adamsilverstein Sep 14, 2025
b878d8b
Complete tests for status field
adamsilverstein Sep 15, 2025
f435d93
update wp-api-generated
adamsilverstein Sep 15, 2025
7b22c39
Update tests/phpunit/tests/rest-api/rest-comments-controller.php
adamsilverstein Sep 15, 2025
6225ae6
hello yoda my old friend
adamsilverstein Sep 15, 2025
f121b3e
API handles strings automatically when array type given
adamsilverstein Sep 15, 2025
5fba5f6
Handle multiple status sanitization
adamsilverstein Sep 15, 2025
1ee7a86
Add additional tests
adamsilverstein Sep 15, 2025
3bf3121
Update src/wp-includes/rest-api/endpoints/class-wp-rest-comments-cont…
adamsilverstein Sep 16, 2025
f210ce5
Update tests/phpunit/tests/rest-api/rest-comments-controller.php
adamsilverstein Sep 18, 2025
faca069
Merge trunk into add/comment-rest-api-types-support
adamsilverstein Jul 23, 2026
0426084
Fix comments status permission check for array values
adamsilverstein Jul 23, 2026
c4159ea
Consolidate and expand comment status query tests
adamsilverstein Jul 23, 2026
a2972fd
Update since tag to 7.1.0
adamsilverstein Jul 23, 2026
4b93f1a
Merge branch 'trunk' into add/comment-rest-api-types-support
adamsilverstein Jul 23, 2026
d064fb8
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Jul 23, 2026
5217fd2
Narrow types on sanitize_comment_statuses() method
westonruter Jul 23, 2026
cd4496f
Tighten sanitize_comment_statuses() to return list<non-empty-lowercas…
westonruter Jul 23, 2026
c1c7823
Add typing to data_sanitize_comment_statuses
westonruter Jul 23, 2026
f44de34
Add native type hints to test class properties
westonruter Jul 23, 2026
b9b22de
Address PHPStan issues in tests
westonruter Jul 23, 2026
9a34a07
Apply suggestions from code review
adamsilverstein Jul 23, 2026
f29b134
Cover comma-separated multi-status requests in the authorization test
adamsilverstein Jul 23, 2026
6dd63d2
Preserve a literal '0' status when sanitizing the status parameter
adamsilverstein Jul 23, 2026
79d5aa4
Merge trunk into add/comment-rest-api-types-support
adamsilverstein Sep 28, 2026
303f021
Update @since for sanitize_comment_statuses() to 7.2.0
adamsilverstein Sep 28, 2026
325b6e7
Merge trunk into add/comment-rest-api-types-support
adamsilverstein Sep 30, 2026
f18c8e4
REST API: Make the comments `status` default an array.
adamsilverstein Oct 3, 2026
60db941
Merge remote-tracking branch 'origin/trunk' into pr9870-trunk-merge
adamsilverstein Oct 3, 2026
dffa629
Merge branch 'trunk' into add/comment-rest-api-types-support
adamsilverstein Oct 4, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public function get_items_permissions_check( $request ) {

foreach ( $protected_params as $param ) {
if ( 'status' === $param ) {
if ( 'approve' !== $request[ $param ] ) {
if ( array( 'approve' ) !== $request[ $param ] ) {
Comment thread
adamsilverstein marked this conversation as resolved.
$forbidden_params[] = $param;
}
} elseif ( 'type' === $param ) {
Expand Down Expand Up @@ -200,7 +200,7 @@ public function get_items_permissions_check( $request ) {
if ( ! current_user_can( 'edit_posts' ) ) {
foreach ( $protected_params as $param ) {
if ( 'status' === $param ) {
if ( 'approve' !== $request[ $param ] ) {
if ( array( 'approve' ) !== $request[ $param ] ) {
$forbidden_params[] = $param;
}
} elseif ( 'type' === $param ) {
Expand Down Expand Up @@ -1799,10 +1799,13 @@ public function get_collection_params() {
);

$query_params['status'] = array(
'default' => 'approve',
'description' => __( 'Limit result set to comments assigned a specific status. Requires authorization.' ),
'sanitize_callback' => 'sanitize_key',
'type' => 'string',
'default' => array( 'approve' ),
'description' => __( 'Limit result set to comments assigned one or more statuses. Requires authorization.' ),
'type' => 'array',
'items' => array(
'type' => 'string',
),
'sanitize_callback' => array( $this, 'sanitize_comment_statuses' ),
'validate_callback' => 'rest_validate_request_arg',
Comment thread
adamsilverstein marked this conversation as resolved.
);

Expand Down Expand Up @@ -2064,6 +2067,29 @@ protected function check_is_comment_content_allowed( $prepared_comment ) {
return '' !== $check['comment_content'];
}

/**
* Sanitizes a single comment status or a list of comment statuses.
*
* @since 7.2.0
*
* @param string[]|string $statuses Comment status or array of comment statuses.
* @return string[] Sanitized array of comment statuses.
* @phpstan-return list<non-empty-lowercase-string>
*/
public function sanitize_comment_statuses( $statuses ): array {
$statuses = array_unique( array_map( 'sanitize_key', wp_parse_list( $statuses ) ) );

// Only drop empty values. A literal '0' is a queryable status, as comments are held with `comment_approved` of '0'.
$statuses = array_filter(
$statuses,
static function ( $status ) {
return '' !== $status;
}
);

return array_values( $statuses );
}

/**
* Checks that a post can receive a comment or a note from the current user.
*
Expand Down
170 changes: 149 additions & 21 deletions tests/phpunit/tests/rest-api/rest-comments-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,27 @@
* @group restapi
*/
class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase {
protected static $superadmin_id;
protected static $admin_id;
protected static $editor_id;
protected static $moderator_id;
protected static $contributor_id;
protected static $subscriber_id;
protected static $author_id;
protected static $user_ids = array();

protected static $post_id;
protected static $password_id;
protected static $private_id;
protected static $draft_id;
protected static $trash_id;
protected static $approved_id;
protected static $hold_id;

protected static $comment_ids = array();
protected static $total_comments = 30;
protected static $per_page = 50;
protected static $num_notes = 10;
protected static int $superadmin_id;
protected static int $admin_id;
protected static int $editor_id;
protected static int $moderator_id;
protected static int $contributor_id;
protected static int $subscriber_id;
protected static int $author_id;
protected static array $user_ids = array();

protected static int $post_id;
protected static int $password_id;
protected static int $private_id;
protected static int $draft_id;
protected static int $trash_id;
protected static int $approved_id;
protected static int $hold_id;

protected static array $comment_ids = array();
protected static int $total_comments = 30;
protected static int $per_page = 50;
protected static int $num_notes = 10;

protected $endpoint;

Expand Down Expand Up @@ -245,6 +245,134 @@ public function test_get_items() {
$this->assertCount( self::$total_comments, $comments );
}

/**
* Test getting comments filtered by one or more statuses.
*
* @ticket 63982
*
* @dataProvider data_get_items_by_status
*
* @param string|list<string> $status Status value(s) to filter by.
*/
public function test_get_items_by_status( $status ) {
wp_set_current_user( self::$admin_id );

$request = new WP_REST_Request( 'GET', '/wp/v2/comments' );
$request->set_param( 'status', $status );
$request->set_param( 'per_page', self::$per_page );
$response = rest_get_server()->dispatch( $request );

$this->assertSame( 200, $response->get_status() );

$query = new WP_Comment_Query();
$found = $query->query(
array(
'status' => $status,
'count' => true,
)
);

$data = $response->get_data();
$this->assertIsArray( $data );
$this->assertCount( $found, $data );
}

/**
* Data provider.
*
* @return array<string, array{ 0: string|list<string> }>
*/
public function data_get_items_by_status(): array {
return array(
'a single status' => array( 'approve' ),
'the hold status' => array( 'hold' ),
'the all status' => array( 'all' ),
'an array of statuses' => array( array( 'approve', 'hold' ) ),
'a comma-separated list' => array( 'approve,hold' ),
);
}

/**
* Test that filtering by the default status does not require authorization.
*
* @ticket 63982
*/
public function test_get_items_by_default_status_without_permission() {
$request = new WP_REST_Request( 'GET', '/wp/v2/comments' );
$request->set_param( 'status', 'approve' );
$request->set_param( 'per_page', self::$per_page );
$response = rest_get_server()->dispatch( $request );

$this->assertSame( 200, $response->get_status() );
$data = $response->get_data();
$this->assertIsArray( $data );
$this->assertCount( self::$total_comments, $data );
}

/**
* Test that filtering by multiple statuses requires authorization.
*
* @ticket 63982
*
* @dataProvider data_get_items_by_multiple_statuses_without_permission
*
* @param string|list<string> $status Status value(s) to filter by.
*/
public function test_get_items_by_multiple_statuses_without_permission( $status ) {
$request = new WP_REST_Request( 'GET', '/wp/v2/comments' );
$request->set_param( 'status', $status );
$response = rest_get_server()->dispatch( $request );

$this->assertErrorResponse( 'rest_forbidden_param', $response, 401 );
}

/**
* Data provider.
*
* @return array<string, array{ 0: string|list<string> }>
*/
public function data_get_items_by_multiple_statuses_without_permission(): array {
return array(
'an array of statuses' => array( array( 'approve', 'hold' ) ),
'a comma-separated list' => array( 'approve,hold' ),
);
}

/**
* Test sanitization of the status parameter.
*
* @ticket 63982
*
* @dataProvider data_sanitize_comment_statuses
*
* @param string|string[] $statuses Raw status value.
* @param list<non-empty-lowercase-string> $expected Expected sanitized statuses.
*/
public function test_sanitize_comment_statuses( $statuses, array $expected ) {
$controller = new WP_REST_Comments_Controller();

$this->assertSame( $expected, $controller->sanitize_comment_statuses( $statuses ) );
}

/**
* Data provider.
*
* @return array<string, array{ 0: mixed, 1: list<non-empty-lowercase-string> }>
*/
public function data_sanitize_comment_statuses(): array {
return array(
'a single status' => array( 'approve', array( 'approve' ) ),
'a comma-separated list' => array( 'approve,hold', array( 'approve', 'hold' ) ),
'an array of statuses' => array( array( 'approve', 'hold' ), array( 'approve', 'hold' ) ),
'mixed case statuses' => array( 'APPROVE,Hold', array( 'approve', 'hold' ) ),
'duplicate statuses' => array( array( 'approve', 'approve', 'hold' ), array( 'approve', 'hold' ) ),
'invalid characters' => array( 'howdy&nbsp;admin', array( 'howdynbspadmin' ) ),
'an empty string' => array( '', array() ),
'a literal 0 status' => array( '0', array( '0' ) ),
'0 mixed with a status' => array( '0,approve', array( '0', 'approve' ) ),
);
}

/**
* @ticket 38692
*/
Expand Down
11 changes: 8 additions & 3 deletions tests/qunit/fixtures/wp-api-generated.js
Original file line number Diff line number Diff line change
Expand Up @@ -10588,9 +10588,14 @@ mockedApiResponse.Schema = {
"required": false
},
"status": {
"default": "approve",
"description": "Limit result set to comments assigned a specific status. Requires authorization.",
"type": "string",
"default": [
"approve"
],
"description": "Limit result set to comments assigned one or more statuses. Requires authorization.",
"type": "array",
"items": {
"type": "string"
},
"required": false
},
"type": {
Expand Down
Loading