From 5d7118d63eb281cf03033013cf4cc662010ff48a Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Wed, 23 Nov 2016 16:14:08 +0000 Subject: [PATCH] REST API: Add support for comments of password-protected posts. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Core requires the post password to view and create comments on password protected posts, so we must support a “password” param on the comments endpoint when fetch comments for a specific post and creating a comment on a password protected post. Props flixos90, jnylen0. Fixes #38692. git-svn-id: https://develop.svn.wordpress.org/trunk@39349 602fd350-edb4-49c9-b593-d223f7449a82 --- .../class-wp-rest-comments-controller.php | 66 +++++++--- .../class-wp-rest-posts-controller.php | 4 +- .../rest-api/rest-comments-controller.php | 121 ++++++++++++++++++ 3 files changed, 172 insertions(+), 19 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php index 9747130912..b07ced4458 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php @@ -69,6 +69,10 @@ class WP_REST_Comments_Controller extends WP_REST_Controller { 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + 'password' => array( + 'description' => __( 'The password for the post if it is password protected.' ), + 'type' => 'string', + ), ), ), array( @@ -87,6 +91,10 @@ class WP_REST_Comments_Controller extends WP_REST_Controller { 'default' => false, 'description' => __( 'Whether to bypass trash and force deletion.' ), ), + 'password' => array( + 'description' => __( 'The password for the post if it is password protected.' ), + 'type' => 'string', + ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), @@ -108,7 +116,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller { foreach ( (array) $request['post'] as $post_id ) { $post = get_post( $post_id ); - if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post ) ) { + if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post, $request ) ) { return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } elseif ( 0 === $post_id && ! current_user_can( 'moderate_comments' ) ) { return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to read comments without a post.' ), array( 'status' => rest_authorization_required_code() ) ); @@ -242,7 +250,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller { $comments = array(); foreach ( $query_result as $comment ) { - if ( ! $this->check_read_permission( $comment ) ) { + if ( ! $this->check_read_permission( $comment, $request ) ) { continue; } @@ -309,18 +317,18 @@ class WP_REST_Comments_Controller extends WP_REST_Controller { return true; } - if ( ! $this->check_read_permission( $comment ) ) { - return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to read this comment.' ), array( 'status' => rest_authorization_required_code() ) ); + if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) { + return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit comments.' ), array( 'status' => rest_authorization_required_code() ) ); } $post = get_post( $comment->comment_post_ID ); - if ( $post && ! $this->check_read_post_permission( $post ) ) { - return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); + if ( ! $this->check_read_permission( $comment, $request ) ) { + return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to read this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } - if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) { - return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit comments.' ), array( 'status' => rest_authorization_required_code() ) ); + if ( $post && ! $this->check_read_post_permission( $post, $request ) ) { + return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; @@ -433,7 +441,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller { return new WP_Error( 'rest_comment_trash_post', __( 'Sorry, you are not allowed to create a comment on this post.' ), array( 'status' => 403 ) ); } - if ( ! $this->check_read_post_permission( $post ) ) { + if ( ! $this->check_read_post_permission( $post, $request ) ) { return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } @@ -1412,6 +1420,11 @@ class WP_REST_Comments_Controller extends WP_REST_Controller { 'validate_callback' => 'rest_validate_request_arg', ); + $query_params['password'] = array( + 'description' => __( 'The password for the post if it is password protected.' ), + 'type' => 'string', + ); + /** * Filter collection parameters for the comments controller. * @@ -1481,18 +1494,36 @@ class WP_REST_Comments_Controller extends WP_REST_Controller { * @since 4.7.0 * @access protected * - * @param WP_Post $post Post Object. + * @param WP_Post $post Post object. + * @param WP_REST_Request $request Request data to check. * @return bool Whether post can be read. */ - protected function check_read_post_permission( $post ) { + protected function check_read_post_permission( $post, $request ) { $posts_controller = new WP_REST_Posts_Controller( $post->post_type ); $post_type = get_post_type_object( $post->post_type ); - if ( post_password_required( $post ) ) { - return current_user_can( $post_type->cap->edit_post, $post->ID ); + $has_password_filter = false; + + // Only check password if a specific post was queried for or a single comment + $requested_post = ! empty( $request['post'] ) && 1 === count( $request['post'] ); + $requested_comment = ! empty( $request['id'] ); + if ( ( $requested_post || $requested_comment ) && $posts_controller->can_access_password_content( $post, $request ) ) { + add_filter( 'post_password_required', '__return_false' ); + + $has_password_filter = true; } - return $posts_controller->check_read_permission( $post ); + if ( post_password_required( $post ) ) { + $result = current_user_can( $post_type->cap->edit_post, $post->ID ); + } else { + $result = $posts_controller->check_read_permission( $post ); + } + + if ( $has_password_filter ) { + remove_filter( 'post_password_required', '__return_false' ); + } + + return $result; } /** @@ -1501,14 +1532,15 @@ class WP_REST_Comments_Controller extends WP_REST_Controller { * @since 4.7.0 * @access protected * - * @param WP_Comment $comment Comment object. + * @param WP_Comment $comment Comment object. + * @param WP_REST_Request $request Request data to check. * @return bool Whether the comment can be read. */ - protected function check_read_permission( $comment ) { + protected function check_read_permission( $comment, $request ) { if ( ! empty( $comment->comment_post_ID ) ) { $post = get_post( $comment->comment_post_ID ); if ( $post ) { - if ( $this->check_read_post_permission( $post ) && 1 === (int) $comment->comment_approved ) { + if ( $this->check_read_post_permission( $post, $request ) && 1 === (int) $comment->comment_approved ) { return true; } } diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 6a7e843409..df8ac1a892 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -383,13 +383,13 @@ class WP_REST_Posts_Controller extends WP_REST_Controller { * check in core with a filter. * * @since 4.7.0 - * @access protected + * @access public * * @param WP_Post $post Post to check against. * @param WP_REST_Request $request Request data to check. * @return bool True if the user can access password-protected content, otherwise false. */ - protected function can_access_password_content( $post, $request ) { + public function can_access_password_content( $post, $request ) { if ( empty( $post->post_password ) ) { // No filter required. return false; diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index 25ee36ec44..112a26c525 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -146,6 +146,7 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase 'page', 'parent', 'parent_exclude', + 'password', 'per_page', 'post', 'search', @@ -167,6 +168,69 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase $this->assertCount( 7, $comments ); } + /** + * @ticket 38692 + */ + public function test_get_items_with_password() { + wp_set_current_user( 0 ); + + $args = array( + 'comment_approved' => 1, + 'comment_post_ID' => self::$password_id, + ); + $password_comment = $this->factory->comment->create( $args ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); + $request->set_param( 'password', 'toomanysecrets' ); + $request->set_param( 'post', self::$password_id ); + + $response = $this->server->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + + $collection_data = $response->get_data(); + $this->assertTrue( in_array( $password_comment, wp_list_pluck( $collection_data, 'id' ), true ) ); + } + + /** + * @ticket 38692 + */ + public function test_get_items_with_password_without_post() { + wp_set_current_user( 0 ); + $args = array( + 'comment_approved' => 1, + 'comment_post_ID' => self::$password_id, + ); + $password_comment = $this->factory->comment->create( $args ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); + $request->set_param( 'password', 'toomanysecrets' ); + + $response = $this->server->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + + $collection_data = $response->get_data(); + $this->assertFalse( in_array( $password_comment, wp_list_pluck( $collection_data, 'id' ), true ) ); + } + + /** + * @ticket 38692 + */ + public function test_get_items_with_password_with_multiple_post() { + wp_set_current_user( 0 ); + $args = array( + 'comment_approved' => 1, + 'comment_post_ID' => self::$password_id, + ); + $password_comment = $this->factory->comment->create( $args ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); + $request->set_param( 'password', 'toomanysecrets' ); + $request->set_param( 'post', array( self::$password_id, self::$post_id ) ); + + $response = $this->server->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read_post', $response, 401 ); + } + public function test_get_password_items_without_edit_post_permission() { wp_set_current_user( 0 ); @@ -853,6 +917,25 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase $this->assertErrorResponse( 'rest_cannot_read', $response, 403 ); } + /** + * @ticket 38692 + */ + public function test_get_comment_with_password_with_valid_password() { + wp_set_current_user( self::$subscriber_id ); + + $args = array( + 'comment_approved' => 1, + 'comment_post_ID' => self::$password_id, + ); + $password_comment = $this->factory->comment->create( $args ); + + $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%s', $password_comment ) ); + $request->set_param( 'password', 'toomanysecrets' ); + + $response = $this->server->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + } + public function test_create_item() { wp_set_current_user( self::$admin_id ); @@ -1727,6 +1810,44 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase $this->assertErrorResponse( 'comment_content_column_length', $response, 400 ); } + public function test_create_comment_without_password() { + wp_set_current_user( self::$subscriber_id ); + + $params = array( + 'post' => self::$password_id, + 'author_name' => 'Bleeding Gums Murphy', + 'author_email' => 'murphy@gingivitis.com', + 'author_url' => 'http://jazz.gingivitis.com', + 'content' => 'This isn\'t a saxophone. It\'s an umbrella.', + ); + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + + $request->add_header( 'content-type', 'application/json' ); + $request->set_body( wp_json_encode( $params ) ); + $response = $this->server->dispatch( $request ); + + $this->assertErrorResponse( 'rest_cannot_read_post', $response, 403 ); + } + + public function test_create_comment_with_password() { + add_filter( 'rest_allow_anonymous_comments', '__return_true' ); + + $params = array( + 'post' => self::$password_id, + 'author_name' => 'Bleeding Gums Murphy', + 'author_email' => 'murphy@gingivitis.com', + 'author_url' => 'http://jazz.gingivitis.com', + 'content' => 'This isn\'t a saxophone. It\'s an umbrella.', + 'password' => 'toomanysecrets', + ); + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + + $request->add_header( 'content-type', 'application/json' ); + $request->set_body( wp_json_encode( $params ) ); + $response = $this->server->dispatch( $request ); + $this->assertEquals( 201, $response->get_status() ); + } + public function test_update_item() { $post_id = $this->factory->post->create();