Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions src/wp-includes/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,10 @@ function create_initial_rest_routes() {
$controller = new WP_REST_Comments_Controller();
$controller->register_routes();

// Notes.
$controller = new WP_REST_Notes_Controller();
$controller->register_routes();

$search_handlers = array(
new WP_REST_Post_Search_Handler(),
new WP_REST_Term_Search_Handler(),
Expand Down
183 changes: 54 additions & 129 deletions src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,6 @@ public function register_routes() {
* @return true|WP_Error True if the request has read access, error object otherwise.
*/
public function get_items_permissions_check( $request ) {
$is_note = 'note' === $request['type'];
$is_edit_context = 'edit' === $request['context'];
$protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' );
$forbidden_params = array();

if ( ! empty( $request['post'] ) ) {
foreach ( (array) $request['post'] as $post_id ) {
$post = get_post( $post_id );
Expand All @@ -145,51 +140,10 @@ public function get_items_permissions_check( $request ) {
array( 'status' => rest_authorization_required_code() )
);
}

if ( $post && $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) {
if ( current_user_can( 'edit_post', $post->ID ) ) {
return new WP_Error(
'rest_comment_not_supported_post_type',
__( 'Sorry, this post type does not support notes.' ),
array( 'status' => 403 )
);
}

foreach ( $protected_params as $param ) {
if ( 'status' === $param ) {
if ( 'approve' !== $request[ $param ] ) {
$forbidden_params[] = $param;
}
} elseif ( 'type' === $param ) {
if ( 'comment' !== $request[ $param ] ) {
$forbidden_params[] = $param;
}
} elseif ( ! empty( $request[ $param ] ) ) {
$forbidden_params[] = $param;
}
}
return new WP_Error(
'rest_forbidden_param',
/* translators: %s: List of forbidden parameters. */
sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ),
array( 'status' => rest_authorization_required_code() )
);
}
}
}

// Re-map edit context capabilities when requesting `note` for a post.
if ( $is_edit_context && $is_note && ! empty( $request['post'] ) ) {
foreach ( (array) $request['post'] as $post_id ) {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return new WP_Error(
'rest_forbidden_context',
__( 'Sorry, you are not allowed to edit comments.' ),
array( 'status' => rest_authorization_required_code() )
);
}
}
} elseif ( $is_edit_context && ! current_user_can( 'moderate_comments' ) ) {
if ( 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) {
return new WP_Error(
'rest_forbidden_context',
__( 'Sorry, you are not allowed to edit comments.' ),
Expand All @@ -198,6 +152,9 @@ public function get_items_permissions_check( $request ) {
}

if ( ! current_user_can( 'edit_posts' ) ) {
$protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' );
$forbidden_params = array();

foreach ( $protected_params as $param ) {
if ( 'status' === $param ) {
if ( 'approve' !== $request[ $param ] ) {
Expand Down Expand Up @@ -437,9 +394,7 @@ public function get_item_permissions_check( $request ) {
return $comment;
}

// Re-map edit context capabilities when requesting `note` type.
$edit_cap = 'note' === $comment->comment_type ? array( 'edit_comment', $comment->comment_ID ) : array( 'moderate_comments' );
if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( ...$edit_cap ) ) {
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.' ),
Expand Down Expand Up @@ -497,16 +452,6 @@ public function get_item( $request ) {
* @return true|WP_Error True if the request has access to create items, error object otherwise.
*/
public function create_item_permissions_check( $request ) {
$is_note = ! empty( $request['type'] ) && 'note' === $request['type'];

if ( ! is_user_logged_in() && $is_note ) {
return new WP_Error(
'rest_comment_login_required',
__( 'Sorry, you must be logged in to comment.' ),
array( 'status' => 401 )
);
}

if ( ! is_user_logged_in() ) {
if ( get_option( 'comment_registration' ) ) {
return new WP_Error(
Expand Down Expand Up @@ -560,16 +505,7 @@ public function create_item_permissions_check( $request ) {
}
}

if ( $is_note && ! empty( $request['post'] ) && ! current_user_can( 'edit_post', (int) $request['post'] ) ) {
return new WP_Error(
'rest_cannot_create_note',
__( 'Sorry, you are not allowed to create notes for this post.' ),
array( 'status' => rest_authorization_required_code() )
);
}

$edit_cap = $is_note ? array( 'edit_post', (int) $request['post'] ) : array( 'moderate_comments' );
if ( isset( $request['status'] ) && ! current_user_can( ...$edit_cap ) ) {
if ( isset( $request['status'] ) && ! current_user_can( 'moderate_comments' ) ) {
return new WP_Error(
'rest_comment_invalid_status',
/* translators: %s: Request parameter. */
Expand All @@ -596,15 +532,7 @@ public function create_item_permissions_check( $request ) {
);
}

if ( $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) {
return new WP_Error(
'rest_comment_not_supported_post_type',
__( 'Sorry, this post type does not support notes.' ),
array( 'status' => 403 )
);
}

if ( 'draft' === $post->post_status && ! $is_note ) {
if ( 'draft' === $post->post_status ) {
return new WP_Error(
'rest_comment_draft_post',
__( 'Sorry, you are not allowed to create a comment on this post.' ),
Expand All @@ -628,7 +556,7 @@ public function create_item_permissions_check( $request ) {
);
}

if ( ! comments_open( $post->ID ) && ! $is_note ) {
if ( ! comments_open( $post->ID ) ) {
return new WP_Error(
'rest_comment_closed',
__( 'Sorry, comments are closed for this item.' ),
Expand Down Expand Up @@ -657,7 +585,7 @@ public function create_item( $request ) {
}

// Do not allow comments to be created with a non-core type.
if ( ! empty( $request['type'] ) && ! in_array( $request['type'], array( 'comment', 'note' ), true ) ) {
if ( ! empty( $request['type'] ) && ! in_array( $request['type'], $this->get_allowed_comment_types(), true ) ) {
return new WP_Error(
'rest_invalid_comment_type',
__( 'Cannot create a comment with that type.' ),
Expand All @@ -676,10 +604,7 @@ public function create_item( $request ) {
$prepared_comment['comment_content'] = '';
}

// Include note metadata into check_is_comment_content_allowed.
if ( isset( $request['meta']['_wp_note_status'] ) ) {
$prepared_comment['meta']['_wp_note_status'] = $request['meta']['_wp_note_status'];
}
$prepared_comment = $this->prepare_comment_for_content_check( $prepared_comment, $request );

if ( ! $this->check_is_comment_content_allowed( $prepared_comment ) ) {
return new WP_Error(
Expand Down Expand Up @@ -743,11 +668,7 @@ public function create_item( $request ) {
);
}

// Don't check for duplicates or flooding for notes.
$prepared_comment['comment_approved'] =
'note' === $prepared_comment['comment_type'] ?
'1' :
wp_allow_comment( $prepared_comment, true );
$prepared_comment['comment_approved'] = $this->determine_comment_approval( $prepared_comment );

if ( is_wp_error( $prepared_comment['comment_approved'] ) ) {
$error_code = $prepared_comment['comment_approved']->get_error_code();
Expand Down Expand Up @@ -1304,22 +1225,6 @@ protected function prepare_links( $comment ) {
);
}

// Embedding children for notes requires `type` and `status` inheritance.
if ( isset( $links['children'] ) && 'note' === $comment->comment_type ) {
$args = array(
'parent' => $comment->comment_ID,
'type' => $comment->comment_type,
'status' => 'all',
);

$rest_url = add_query_arg( $args, rest_url( $this->namespace . '/' . $this->rest_base ) );

$links['children'] = array(
'href' => $rest_url,
'embeddable' => true,
);
}

return $links;
}

Expand Down Expand Up @@ -1919,6 +1824,13 @@ protected function check_read_post_permission( $post, $request ) {
* @return bool Whether the comment can be read.
*/
protected function check_read_permission( $comment, $request ) {
/*
* Notes stay out of the "approved comment on a readable post" shortcut.
* Every note is stored approved - `approved` means resolved, not public -
* so the shortcut would hand notes to anonymous readers. This is the one
* piece of note handling the comments controller has to keep: a note is a
* comment row, and the comments routes can still address it by ID.
*/
if ( 'note' !== $comment->comment_type && ! empty( $comment->comment_post_ID ) ) {
$post = get_post( $comment->comment_post_ID );
if ( $post ) {
Expand Down Expand Up @@ -2024,16 +1936,6 @@ protected function check_is_comment_content_allowed( $prepared_comment ) {
return true;
}

// Allow empty notes only when resolution metadata is valid.
if (
isset( $check['comment_type'] ) &&
'note' === $check['comment_type'] &&
isset( $check['meta']['_wp_note_status'] ) &&
in_array( $check['meta']['_wp_note_status'], array( 'resolved', 'reopen' ), true )
) {
return true;
}

/*
* Do not allow a comment to be created with missing or empty
* comment_content. See wp_handle_comment_submission().
Expand All @@ -2042,22 +1944,45 @@ protected function check_is_comment_content_allowed( $prepared_comment ) {
}

/**
* Check if post type supports notes.
* Retrieves the comment types this controller may create.
*
* Subclasses serving a different comment type, such as
* WP_REST_Notes_Controller, name that type here.
*
* @since 7.2.0
*
* @param string $post_type Post type name.
* @return bool True if post type supports notes, false otherwise.
* @return string[] Comment types accepted by the create route.
*/
private function check_post_type_supports_notes( $post_type ) {
$supports = get_all_post_type_supports( $post_type );

if ( ! isset( $supports['editor'] ) ) {
return false;
}
protected function get_allowed_comment_types() {
return array( 'comment' );
}

if ( ! is_array( $supports['editor'] ) ) {
return false;
}
/**
* Adds any extra request values the content check needs.
*
* Prepared comments carry only the columns headed for the comments table.
* A subclass whose content rules depend on something else, such as the meta
* a note is resolved with, copies that value across here.
*
* @since 7.2.0
*
* @param array $prepared_comment Prepared comment data.
* @param WP_REST_Request $request Full details about the request.
* @return array Prepared comment data for the content check.
*/
protected function prepare_comment_for_content_check( $prepared_comment, $request ) {
return $prepared_comment;
}

return array_any( $supports['editor'], fn( $item ) => ! empty( $item['notes'] ) );
/**
* Determines the approval status a new comment is created with.
*
* @since 7.2.0
*
* @param array $prepared_comment Prepared comment data.
* @return int|string|WP_Error The approval status, or WP_Error on failure.
*/
protected function determine_comment_approval( $prepared_comment ) {
return wp_allow_comment( $prepared_comment, true );
}
}
Loading
Loading