From e08eea23664814a61387e35cdba9d6036f0a98e7 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 16:29:44 +0900 Subject: [PATCH 01/36] Update comment REST API --- .../class-wp-rest-comments-controller.php | 103 +++++++++++++++--- 1 file changed, 87 insertions(+), 16 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 a0b68759f9942..f57c7638537f5 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 @@ -123,11 +123,21 @@ 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 = ! empty( $request['type'] ) && 'note' === $request['type']; + $is_edit_context = ! empty( $request['context'] ) && 'edit' === $request['context']; if ( ! empty( $request['post'] ) ) { foreach ( (array) $request['post'] as $post_id ) { $post = get_post( $post_id ); + if ( $post && $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 ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post, $request ) ) { return new WP_Error( 'rest_cannot_read_post', @@ -144,7 +154,18 @@ public function get_items_permissions_check( $request ) { } } - if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) { + // 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' ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit comments.' ), @@ -394,7 +415,9 @@ public function get_item_permissions_check( $request ) { return $comment; } - if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) { + // 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 ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit comments.' ), @@ -452,6 +475,16 @@ 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( @@ -505,7 +538,8 @@ public function create_item_permissions_check( $request ) { } } - if ( isset( $request['status'] ) && ! current_user_can( 'moderate_comments' ) ) { + $edit_cap = $is_note ? array( 'edit_post', (int) $request['post'] ) : array( 'moderate_comments' ); + if ( isset( $request['status'] ) && ! current_user_can( ...$edit_cap ) ) { return new WP_Error( 'rest_comment_invalid_status', /* translators: %s: Request parameter. */ @@ -532,7 +566,15 @@ public function create_item_permissions_check( $request ) { ); } - if ( 'draft' === $post->post_status ) { + 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 ) { return new WP_Error( 'rest_comment_draft_post', __( 'Sorry, you are not allowed to create a comment on this post.' ), @@ -556,7 +598,7 @@ public function create_item_permissions_check( $request ) { ); } - if ( ! comments_open( $post->ID ) ) { + if ( ! comments_open( $post->ID ) && ! $is_note ) { return new WP_Error( 'rest_comment_closed', __( 'Sorry, comments are closed for this item.' ), @@ -584,26 +626,22 @@ public function create_item( $request ) { ); } - // Do not allow comments to be created with a non-default type. - if ( ! empty( $request['type'] ) && 'comment' !== $request['type'] ) { - return new WP_Error( - 'rest_invalid_comment_type', - __( 'Cannot create a comment with that type.' ), - array( 'status' => 400 ) - ); - } - $prepared_comment = $this->prepare_item_for_database( $request ); if ( is_wp_error( $prepared_comment ) ) { return $prepared_comment; } - $prepared_comment['comment_type'] = 'comment'; + $prepared_comment['comment_type'] = $request['type']; if ( ! isset( $prepared_comment['comment_content'] ) ) { $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']; + } + if ( ! $this->check_is_comment_content_allowed( $prepared_comment ) ) { return new WP_Error( 'rest_comment_content_invalid', @@ -1518,8 +1556,9 @@ public function get_item_schema() { 'type' => array( 'description' => __( 'Type of the comment.' ), 'type' => 'string', + 'enum' => array( 'comment', 'note' ), + 'default' => 'comment', 'context' => array( 'view', 'edit', 'embed' ), - 'readonly' => true, ), ), ); @@ -1894,6 +1933,28 @@ public function check_comment_author_email( $value, $request, $param ) { return $email; } + /** + * Check if post type supports block comments. + * + * @param string $post_type Post type name. + * @return bool True if post type supports block comments, false otherwise. + */ + private function check_post_type_supports_notes( $post_type ) { + $supports = get_all_post_type_supports( $post_type ); + if ( ! isset( $supports['editor'] ) ) { + return false; + } + if ( ! is_array( $supports['editor'] ) ) { + return false; + } + foreach ( $supports['editor'] as $item ) { + if ( is_array( $item ) && isset( $item['notes'] ) && true === $item['notes'] ) { + return true; + } + } + return true; + } + /** * If empty comments are not allowed, checks if the provided comment content is not empty. * @@ -1922,6 +1983,16 @@ protected function check_is_comment_content_allowed( $prepared_comment ) { return true; } + // Allow empty block comments 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(). From c00b9e1a43f8394b2eb57f64b9c56d7d5bd0eb59 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 16:39:33 +0900 Subject: [PATCH 02/36] Add post type support --- src/wp-includes/post.php | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index a2d8fba9db341..5b5762aa7d4fb 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -37,7 +37,18 @@ function create_initial_post_types() { 'rewrite' => false, 'query_var' => false, 'delete_with_user' => true, - 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions', 'post-formats' ), + 'supports' => array( + 'title', + 'author', + 'thumbnail', + 'excerpt', + 'trackbacks', + 'custom-fields', + 'comments', + 'revisions', + 'post-formats', + array( 'editor' => array( 'notes' => true ) ), + ), 'show_in_rest' => true, 'rest_base' => 'posts', 'rest_controller_class' => 'WP_REST_Posts_Controller', @@ -62,7 +73,16 @@ function create_initial_post_types() { 'rewrite' => false, 'query_var' => false, 'delete_with_user' => true, - 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'page-attributes', 'custom-fields', 'comments', 'revisions' ), + 'supports' => array( + 'title', + 'author', + 'thumbnail', + 'page-attributes', + 'custom-fields', + 'comments', + 'revisions', + array( 'editor' => array( 'notes' => true ) ), + ), 'show_in_rest' => true, 'rest_base' => 'pages', 'rest_controller_class' => 'WP_REST_Posts_Controller', From c1aebc0544bb2c03f59b8272c57b4810032000ff Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 16:43:15 +0900 Subject: [PATCH 03/36] Register metadata --- src/wp-includes/comment.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index aabe9f60dbb4a..661ff03871488 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -4103,3 +4103,30 @@ function _wp_check_for_scheduled_update_comment_type() { wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'wp_update_comment_type_batch' ); } } + +/** + * Register notes metadata for notes status. + * + * @since 6.9.0 + */ +function wp_register_notes_metadata() { + register_meta( + 'comment', + '_wp_note_status', + array( + 'type' => 'string', + 'description' => __( 'Note resolution status' ), + 'single' => true, + 'show_in_rest' => array( + 'schema' => array( + 'type' => 'string', + 'enum' => array( 'resolved', 'reopen' ), + ), + ), + 'auth_callback' => function ( $allowed, $meta_key, $object_id ) { + return current_user_can( 'edit_comment', $object_id ); + }, + ) + ); +} +add_action( 'init', 'wp_register_notes_metadata' ); From 495504c9eff571f77dca8e789cd643f5a7d6aa54 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 16:47:35 +0900 Subject: [PATCH 04/36] Update avatar comment type --- src/wp-includes/link-template.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/link-template.php b/src/wp-includes/link-template.php index 5870a068a0a89..525a9efe53eb8 100644 --- a/src/wp-includes/link-template.php +++ b/src/wp-includes/link-template.php @@ -4343,9 +4343,11 @@ function is_avatar_comment_type( $comment_type ) { * * @since 3.0.0 * - * @param array $types An array of content types. Default only contains 'comment'. + * @since 6.9.0 The 'note' comment type was added. + * + * @param array $types An array of content types. Default contains 'comment' and 'note'. */ - $allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment' ) ); + $allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment', 'note' ) ); return in_array( $comment_type, (array) $allowed_comment_types, true ); } From f5207cf66fa8330b19e5c309867c64954edb7878 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 16:51:28 +0900 Subject: [PATCH 05/36] Allow duplicate notes for resolution purposes --- src/wp-includes/comment.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 661ff03871488..b16acd0ff3cdd 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -726,6 +726,11 @@ function wp_allow_comment( $commentdata, $wp_error = false ) { */ $dupe_id = apply_filters( 'duplicate_comment_id', $dupe_id, $commentdata ); + // Allow duplicate notes for resolution purposes. + if ( $dupe_id && isset( $commentdata['comment_type'] ) && 'note' === $commentdata['comment_type'] ) { + $dupe_id = false; + } + if ( $dupe_id ) { /** * Fires immediately after a duplicate comment is detected. From 954f534ffc3448fec947b193b11cee28c7da304d Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 16:57:06 +0900 Subject: [PATCH 06/36] Exclude notes from admin comments query --- src/wp-includes/class-wp-comment-query.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/wp-includes/class-wp-comment-query.php b/src/wp-includes/class-wp-comment-query.php index 6c813c7e1a7db..4ae45f72563ec 100644 --- a/src/wp-includes/class-wp-comment-query.php +++ b/src/wp-includes/class-wp-comment-query.php @@ -939,6 +939,16 @@ protected function get_comment_ids() { */ $clauses = apply_filters_ref_array( 'comments_clauses', array( compact( $pieces ), &$this ) ); + // Exclude notes from admin comment queries. + if ( is_admin() && isset( $this->query_vars['type'] ) && '' === $this->query_vars['type'] ) { + global $wpdb; + if ( isset( $clauses['where'] ) && ! empty( $clauses['where'] ) ) { + $clauses['where'] .= " AND {$wpdb->comments}.comment_type != 'note'"; + } else { + $clauses['where'] = "{$wpdb->comments}.comment_type != 'note'"; + } + } + $fields = isset( $clauses['fields'] ) ? $clauses['fields'] : ''; $join = isset( $clauses['join'] ) ? $clauses['join'] : ''; $where = isset( $clauses['where'] ) ? $clauses['where'] : ''; From 7d1e549040d81de261ef6371edb42454a399be16 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 17:12:43 +0900 Subject: [PATCH 07/36] Exclude note type comment from comment count query --- src/wp-admin/includes/comment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-admin/includes/comment.php b/src/wp-admin/includes/comment.php index ad8b653161ba3..d38ab9447cf95 100644 --- a/src/wp-admin/includes/comment.php +++ b/src/wp-admin/includes/comment.php @@ -157,7 +157,7 @@ function get_pending_comments_num( $post_id ) { $post_id_array = array_map( 'intval', $post_id_array ); $post_id_in = "'" . implode( "', '", $post_id_array ) . "'"; - $pending = $wpdb->get_results( "SELECT comment_post_ID, COUNT(comment_ID) as num_comments FROM $wpdb->comments WHERE comment_post_ID IN ( $post_id_in ) AND comment_approved = '0' GROUP BY comment_post_ID", ARRAY_A ); + $pending = $wpdb->get_results( "SELECT comment_post_ID, COUNT(comment_ID) as num_comments FROM $wpdb->comments WHERE comment_post_ID IN ( $post_id_in ) AND comment_approved = '0' AND comment_type != 'note' GROUP BY comment_post_ID", ARRAY_A ); if ( $single ) { if ( empty( $pending ) ) { From 3e4347be94b6c22b996baa935f549647f8a53ab0 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 17:20:50 +0900 Subject: [PATCH 08/36] Extend post_type_supports function to check sub-features --- src/wp-includes/post.php | 24 ++++++++++++++++++ .../class-wp-rest-comments-controller.php | 25 ++----------------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 5b5762aa7d4fb..4db61b42f77e3 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2337,6 +2337,9 @@ function get_all_post_type_supports( $post_type ) { * Checks a post type's support for a given feature. * * @since 3.0.0 + * @since 6.9.0 Added support for sub-features. + * To check a sub-feature, pass a slash-delimited feature string + * like 'editor/notes'. * * @global array $_wp_post_type_features * @@ -2347,6 +2350,27 @@ function get_all_post_type_supports( $post_type ) { function post_type_supports( $post_type, $feature ) { global $_wp_post_type_features; + if ( str_contains( $feature, '/' ) ) { + $parts = explode( '/', $feature, 2 ); + $feature = $parts[0]; + $sub_feature = $parts[1]; + $supports = get_all_post_type_supports( $post_type ); + + if ( ! isset( $supports[ $parent_feature ] ) ) { + return false; + } + if ( ! is_array( $supports[ $parent_feature ] ) ) { + return false; + } + foreach ( $supports[ $parent_feature ] as $item ) { + if ( is_array( $item ) && isset( $item[ $sub_feature ] ) && true === $item[ $sub_feature ] ) { + return true; + } + } + + return true; + } + return ( isset( $_wp_post_type_features[ $post_type ][ $feature ] ) ); } 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 f57c7638537f5..522e1d819ed31 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 @@ -130,7 +130,7 @@ public function get_items_permissions_check( $request ) { foreach ( (array) $request['post'] as $post_id ) { $post = get_post( $post_id ); - if ( $post && $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) { + if ( $post && $is_note && ! post_type_supports( $post->post_type, 'editor/notes' ) ) { return new WP_Error( 'rest_comment_not_supported_post_type', __( 'Sorry, this post type does not support notes.' ), @@ -566,7 +566,7 @@ public function create_item_permissions_check( $request ) { ); } - if ( $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) { + if ( $is_note && ! post_type_supports( $post->post_type, 'editor/notes' ) ) { return new WP_Error( 'rest_comment_not_supported_post_type', __( 'Sorry, this post type does not support notes.' ), @@ -1933,27 +1933,6 @@ public function check_comment_author_email( $value, $request, $param ) { return $email; } - /** - * Check if post type supports block comments. - * - * @param string $post_type Post type name. - * @return bool True if post type supports block comments, false otherwise. - */ - private function check_post_type_supports_notes( $post_type ) { - $supports = get_all_post_type_supports( $post_type ); - if ( ! isset( $supports['editor'] ) ) { - return false; - } - if ( ! is_array( $supports['editor'] ) ) { - return false; - } - foreach ( $supports['editor'] as $item ) { - if ( is_array( $item ) && isset( $item['notes'] ) && true === $item['notes'] ) { - return true; - } - } - return true; - } /** * If empty comments are not allowed, checks if the provided comment content is not empty. From 727688456ca2188c2f17b3a7c2a4a287971a56e1 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 17:28:30 +0900 Subject: [PATCH 09/36] Remove unnecessary check --- src/wp-includes/comment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index b16acd0ff3cdd..b5ffeef0e5d10 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -727,7 +727,7 @@ function wp_allow_comment( $commentdata, $wp_error = false ) { $dupe_id = apply_filters( 'duplicate_comment_id', $dupe_id, $commentdata ); // Allow duplicate notes for resolution purposes. - if ( $dupe_id && isset( $commentdata['comment_type'] ) && 'note' === $commentdata['comment_type'] ) { + if ( isset( $commentdata['comment_type'] ) && 'note' === $commentdata['comment_type'] ) { $dupe_id = false; } From f74effdb95cd9c8faf90b4ce905f7500fe736acf Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 17:37:51 +0900 Subject: [PATCH 10/36] Change how sub-features are checked --- src/wp-includes/post.php | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 4db61b42f77e3..233979ccfe946 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -39,6 +39,7 @@ function create_initial_post_types() { 'delete_with_user' => true, 'supports' => array( 'title', + 'editor' => array( 'notes' => true ), 'author', 'thumbnail', 'excerpt', @@ -47,7 +48,6 @@ function create_initial_post_types() { 'comments', 'revisions', 'post-formats', - array( 'editor' => array( 'notes' => true ) ), ), 'show_in_rest' => true, 'rest_base' => 'posts', @@ -75,13 +75,13 @@ function create_initial_post_types() { 'delete_with_user' => true, 'supports' => array( 'title', + 'editor' => array( 'notes' => true ), 'author', 'thumbnail', 'page-attributes', 'custom-fields', 'comments', 'revisions', - array( 'editor' => array( 'notes' => true ) ), ), 'show_in_rest' => true, 'rest_base' => 'pages', @@ -2354,21 +2354,18 @@ function post_type_supports( $post_type, $feature ) { $parts = explode( '/', $feature, 2 ); $feature = $parts[0]; $sub_feature = $parts[1]; - $supports = get_all_post_type_supports( $post_type ); - if ( ! isset( $supports[ $parent_feature ] ) ) { + if ( ! isset( $_wp_post_type_features[ $post_type ][ $feature ] ) ) { return false; } - if ( ! is_array( $supports[ $parent_feature ] ) ) { - return false; - } - foreach ( $supports[ $parent_feature ] as $item ) { - if ( is_array( $item ) && isset( $item[ $sub_feature ] ) && true === $item[ $sub_feature ] ) { - return true; - } + + $feature_value = $_wp_post_type_features[ $post_type ][ $feature ]; + + if ( is_array( $feature_value ) && isset( $feature_value[0] ) && is_array( $feature_value[0] ) ) { + return ! empty( $feature_value[0][ $sub_feature ] ); } - return true; + return false; } return ( isset( $_wp_post_type_features[ $post_type ][ $feature ] ) ); From 13defce0ef6ba6ed6e24d0c39441fe6ba62728c8 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 18:04:23 +0900 Subject: [PATCH 11/36] Fix WP_Test_REST_Comments_Controller Unit test --- tests/phpunit/tests/rest-api/rest-comments-controller.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index 0bfe4e778d870..5dff8908a03da 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -1680,7 +1680,7 @@ public function test_create_comment_with_invalid_type() { $request->set_body( wp_json_encode( $params ) ); $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_invalid_comment_type', $response, 400 ); + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); } public function test_create_comment_invalid_email() { @@ -2697,7 +2697,7 @@ public function test_update_comment_invalid_type() { $request->set_body( wp_json_encode( $params ) ); $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_comment_invalid_type', $response, 404 ); + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); } public function test_update_comment_with_raw_property() { @@ -3286,9 +3286,9 @@ public function test_get_item_schema() { $this->assertSame( 0, $properties['parent']['default'] ); $this->assertSame( 0, $properties['post']['default'] ); + $this->assertSame( array( 'comment', 'note' ), $properties['type']['enum'] ); $this->assertTrue( $properties['link']['readonly'] ); - $this->assertTrue( $properties['type']['readonly'] ); } public function test_get_item_schema_show_avatar() { From 3cf787a6362b061ed0b25df171693d7e58bcf985 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 18:17:51 +0900 Subject: [PATCH 12/36] Revert changes to the post_type_supports function --- src/wp-includes/post.php | 19 -------------- .../class-wp-rest-comments-controller.php | 26 +++++++++++++++++-- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 233979ccfe946..2b8eb16c4a99f 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2350,27 +2350,8 @@ function get_all_post_type_supports( $post_type ) { function post_type_supports( $post_type, $feature ) { global $_wp_post_type_features; - if ( str_contains( $feature, '/' ) ) { - $parts = explode( '/', $feature, 2 ); - $feature = $parts[0]; - $sub_feature = $parts[1]; - - if ( ! isset( $_wp_post_type_features[ $post_type ][ $feature ] ) ) { - return false; - } - - $feature_value = $_wp_post_type_features[ $post_type ][ $feature ]; - - if ( is_array( $feature_value ) && isset( $feature_value[0] ) && is_array( $feature_value[0] ) ) { - return ! empty( $feature_value[0][ $sub_feature ] ); - } - - return false; - } - return ( isset( $_wp_post_type_features[ $post_type ][ $feature ] ) ); } - /** * Retrieves a list of post type names that support a specific feature. * 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 522e1d819ed31..5284c58f38089 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 @@ -130,7 +130,7 @@ public function get_items_permissions_check( $request ) { foreach ( (array) $request['post'] as $post_id ) { $post = get_post( $post_id ); - if ( $post && $is_note && ! post_type_supports( $post->post_type, 'editor/notes' ) ) { + if ( $post && $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.' ), @@ -566,7 +566,7 @@ public function create_item_permissions_check( $request ) { ); } - if ( $is_note && ! post_type_supports( $post->post_type, 'editor/notes' ) ) { + 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.' ), @@ -1978,4 +1978,26 @@ protected function check_is_comment_content_allowed( $prepared_comment ) { */ return '' !== $check['comment_content']; } + + /** + * Check if post type supports block comments. + * + * @param string $post_type Post type name. + * @return bool True if post type supports block comments, false otherwise. + */ + private function check_post_type_supports_notes( $post_type ) { + $supports = get_all_post_type_supports( $post_type ); + if ( ! isset( $supports['editor'] ) ) { + return false; + } + if ( ! is_array( $supports['editor'] ) ) { + return false; + } + foreach ( $supports['editor'] as $item ) { + if ( is_array( $item ) && isset( $item['notes'] ) && true === $item['notes'] ) { + return true; + } + } + return true; + } } From b351ddb3f5968d82daca65246ac0d635790cdce0 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 18:51:39 +0900 Subject: [PATCH 13/36] Add unit test for notes --- .../rest-api/rest-comments-controller.php | 412 +++++++++++++++++- 1 file changed, 411 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index 5dff8908a03da..217cf5042a921 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -5,15 +5,17 @@ * @package WordPress * @subpackage REST API * - * @group restapi + * @group restapi1 */ 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; @@ -26,6 +28,7 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase protected static $comment_ids = array(); protected static $total_comments = 30; protected static $per_page = 50; + protected static $num_notes = 10; protected $endpoint; @@ -60,6 +63,11 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { 'role' => 'comment_moderator', ) ); + self::$contributor_id = $factory->user->create( + array( + 'role' => 'contributor', + ) + ); self::$subscriber_id = $factory->user->create( array( 'role' => 'subscriber', @@ -131,6 +139,7 @@ public static function wpTearDownAfterClass() { self::delete_user( self::$admin_id ); self::delete_user( self::$editor_id ); self::delete_user( self::$moderator_id ); + self::delete_user( self::$contributor_id ); self::delete_user( self::$subscriber_id ); self::delete_user( self::$author_id ); @@ -154,6 +163,16 @@ public function set_up() { if ( is_multisite() ) { update_site_option( 'site_admins', array( 'superadmin' ) ); } + + self::$user_ids = array( + 'superadmin' => self::$superadmin_id, + 'administrator' => self::$admin_id, + 'editor' => self::$editor_id, + 'moderator' => self::$moderator_id, + 'contributor' => self::$contributor_id, + 'subscriber' => self::$subscriber_id, + 'author' => self::$author_id, + ); } public function test_register_routes() { @@ -3597,4 +3616,395 @@ public static function data_head_request_with_specified_fields_returns_success_r 'get_items request' => array( '/wp/v2/comments' ), ); } + + /** + * Create a test post with note. + * + * @param int $user_id Post author's user ID. + * @return int Post ID. + */ + protected function create_test_post_with_note( $role ) { + $user_id = self::$user_ids[ $role ]; + $post_id = self::factory()->post->create( + array( + 'post_title' => 'Test Post for Notes', + 'post_content' => 'This is a test post to check note permissions.', + 'post_status' => 'contributor' === $role ? 'draft' : 'publish', + 'post_author' => $user_id, + ) + ); + + for ( $i = 0; $i < self::$num_notes; $i++ ) { + self::factory()->comment->create( + array( + 'comment_post_ID' => $post_id, + 'comment_type' => 'note', + 'comment_approved' => 0 === $i % 2 ? 1 : 0, + ) + ); + } + + return $post_id; + } + + public function test_cannot_read_note_without_post_type_support() { + register_post_type( + 'no-notes', + array( + 'label' => 'No Notes', + 'supports' => array( 'title', 'editor', 'author', 'comments' ), + 'show_in_rest' => true, + 'public' => true, + ) + ); + + create_initial_rest_routes(); + wp_set_current_user( self::$admin_id ); + + $post_id = self::factory()->post->create( array( 'post_type' => 'no-notes' ) ); + $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); + $request->set_param( 'post', $post_id ); + $request->set_param( 'type', 'note' ); + $request->set_param( 'context', 'edit' ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_comment_not_supported_post_type', $response, 403 ); + + _unregister_post_type( 'no-notes' ); + } + + public function test_create_note_require_login() { + wp_set_current_user( 0 ); + + $post_id = self::factory()->post->create(); + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + $request->set_param( 'post', $post_id ); + $request->set_param( 'type', 'note' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_comment_login_required', $response, 401 ); + } + + public function test_cannot_create_note_without_post_type_support() { + register_post_type( + 'no-note', + array( + 'label' => 'No Notes', + 'supports' => array( 'title', 'editor', 'author', 'comments' ), + 'show_in_rest' => true, + 'public' => true, + ) + ); + + wp_set_current_user( self::$admin_id ); + $post_id = self::factory()->post->create( array( 'post_type' => 'no-note' ) ); + $params = array( + 'post' => $post_id, + 'author_name' => 'Ishmael', + 'author_email' => 'herman-melville@earthlink.net', + 'author_url' => 'https://en.wikipedia.org/wiki/Herman_Melville', + 'content' => 'Call me Ishmael.', + 'author' => self::$admin_id, + 'type' => 'note', + ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( wp_json_encode( $params ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_comment_not_supported_post_type', $response, 403 ); + + _unregister_post_type( 'no-note' ); + } + + public function test_create_note_draft_post() { + wp_set_current_user( self::$editor_id ); + $draft_id = self::factory()->post->create( + array( + 'post_status' => 'draft', + ) + ); + $params = array( + 'post' => $draft_id, + 'author_name' => 'Ishmael', + 'author_email' => 'herman-melville@earthlink.net', + 'author_url' => 'https://en.wikipedia.org/wiki/Herman_Melville', + 'content' => 'Call me Ishmael.', + 'author' => self::$editor_id, + 'type' => 'note', + ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( wp_json_encode( $params ) ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $new_comment = get_comment( $data['id'] ); + $this->assertSame( 'Call me Ishmael.', $new_comment->comment_content ); + $this->assertSame( 'note', $new_comment->comment_type ); + } + + public function test_create_note_status() { + wp_set_current_user( self::$author_id ); + $post_id = self::factory()->post->create( array( 'post_author' => self::$author_id ) ); + + $params = array( + 'post' => $post_id, + 'author_name' => 'Ishmael', + 'author_email' => 'herman-melville@earthlink.net', + 'author_url' => 'https://en.wikipedia.org/wiki/Herman_Melville', + 'content' => 'Comic Book Guy', + 'author' => self::$author_id, + 'type' => 'note', + 'status' => 'hold', + ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( wp_json_encode( $params ) ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $new_comment = get_comment( $data['id'] ); + + $this->assertSame( '0', $new_comment->comment_approved ); + $this->assertSame( 'note', $new_comment->comment_type ); + } + + public function test_cannot_create_with_non_valid_comment_type() { + wp_set_current_user( self::$admin_id ); + + $params = array( + 'author_name' => 'Ishmael', + 'author_email' => 'herman-melville@earthlink.net', + 'author_url' => 'https://en.wikipedia.org/wiki/Herman_Melville', + 'content' => 'Comic Book Guy', + 'author' => self::$admin_id, + 'type' => 'review', + ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( wp_json_encode( $params ) ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + } + + public function test_create_assigns_default_type() { + wp_set_current_user( self::$editor_id ); + $post_id = self::factory()->post->create(); + + $params = array( + 'post' => $post_id, + 'author_name' => 'Ishmael', + 'author_email' => 'herman-melville@earthlink.net', + 'author_url' => 'https://en.wikipedia.org/wiki/Herman_Melville', + 'content' => 'Comic Book Guy', + 'author' => self::$editor_id, + ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( wp_json_encode( $params ) ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $new_comment = get_comment( $data['id'] ); + + $this->assertSame( 'comment', $new_comment->comment_type ); + } + + /** + * @dataProvider data_note_status_provider + */ + public function test_create_empty_note_with_resolution_meta( $status ) { + wp_set_current_user( self::$editor_id ); + $post_id = self::factory()->post->create(); + $params = array( + 'post' => $post_id, + 'author_name' => 'Editor', + 'author_email' => 'editor@example.com', + 'author_url' => 'https://example.com', + 'author' => self::$editor_id, + 'type' => 'note', + 'content' => '', + 'meta' => array( + '_wp_note_status' => $status, + ), + ); + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( wp_json_encode( $params ) ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( 201, $response->get_status() ); + } + + public function test_cannot_create_empty_note_without_resolution_meta() { + wp_set_current_user( self::$editor_id ); + $post_id = self::factory()->post->create(); + $params = array( + 'post' => $post_id, + 'author_name' => 'Editor', + 'author_email' => 'editor@example.com', + 'author_url' => 'https://example.com', + 'author' => self::$editor_id, + 'type' => 'note', + 'content' => '', + ); + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( wp_json_encode( $params ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_comment_content_invalid', $response, 400 ); + } + + public function test_cannot_create_empty_note_with_invalid_resolution_meta() { + wp_set_current_user( self::$editor_id ); + $post_id = self::factory()->post->create(); + $params = array( + 'post' => $post_id, + 'author_name' => 'Editor', + 'author_email' => 'editor@example.com', + 'author_url' => 'https://example.com', + 'author' => self::$editor_id, + 'type' => 'note', + 'content' => '', + 'meta' => array( + '_wp_note_status' => 'invalid', + ), + ); + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( wp_json_encode( $params ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_comment_content_invalid', $response, 400 ); + } + + public function test_create_duplicate_note() { + wp_set_current_user( self::$editor_id ); + $post_id = self::factory()->post->create(); + + for ( $i = 0; $i < 2; $i++ ) { + $params = array( + 'post' => $post_id, + 'author_name' => 'Editor', + 'author_email' => 'editor@example.com', + 'author_url' => 'https://example.com', + 'author' => self::$editor_id, + 'type' => 'note', + 'content' => 'Doplicated comment', + ); + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( wp_json_encode( $params ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( 201, $response->get_status() ); + } + } + + /** + * @dataProvider data_note_get_items_permissions_data_provider + */ + public function test_note_get_items_permissions_edit_context( $role, $post_author_role, $can_read ) { + wp_set_current_user( self::$user_ids[ $role ] ); + $post_id = $this->create_test_post_with_note( $post_author_role ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); + $request->set_param( 'post', $post_id ); + $request->set_param( 'type', 'note' ); + $request->set_param( 'status', 'all' ); + $request->set_param( 'per_page', 100 ); + $request->set_param( 'context', 'edit' ); + $response = rest_get_server()->dispatch( $request ); + + if ( $can_read ) { + $comments = $response->get_data(); + $this->assertEquals( self::$num_notes, count( $comments ) ); + } else { + $this->assertErrorResponse( 'rest_forbidden_context', $response, 403 ); + } + + wp_delete_post( $post_id, true ); + } + + public function test_note_get_items_permissions_mixed_post_authors() { + $author_post_id = $this->create_test_post_with_note( 'author' ); + $editor_post_id = $this->create_test_post_with_note( 'editor' ); + + wp_set_current_user( self::$author_id ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); + $request->set_param( 'post', array( $author_post_id, $editor_post_id ) ); + $request->set_param( 'type', 'note' ); + $request->set_param( 'status', 'all' ); + $request->set_param( 'per_page', 100 ); + $request->set_param( 'context', 'edit' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_forbidden_context', $response, 403 ); + + wp_delete_post( $author_post_id, true ); + wp_delete_post( $editor_post_id, true ); + } + + /** + * @dataProvider data_note_get_items_permissions_data_provider + */ + public function test_note_get_item_permissions_edit_context( $role, $post_author_role, $can_read ) { + wp_set_current_user( self::$user_ids[ $role ] ); + + $post_id = self::factory()->post->create( + array( + 'post_title' => 'Test Post for Block Comments', + 'post_content' => 'This is a test post to check block comment permissions.', + 'post_status' => 'contributor' === $post_author_role ? 'draft' : 'publish', + 'post_author' => self::$user_ids[ $post_author_role ], + ) + ); + + $comment_id = self::factory()->comment->create( + array( + 'comment_post_ID' => $post_id, + 'comment_type' => 'note', + // Test with unapproved comment, which is more restrictive. + 'comment_approved' => 0, + 'user_id' => self::$user_ids[ $post_author_role ], + ) + ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/comments/' . $comment_id ); + $request->set_param( 'context', 'edit' ); + $response = rest_get_server()->dispatch( $request ); + + if ( $can_read ) { + $comment = $response->get_data(); + $this->assertEquals( $comment_id, $comment['id'] ); + } else { + $this->assertErrorResponse( 'rest_forbidden_context', $response, 403 ); + } + + wp_delete_post( $post_id, true ); + } + + public function data_note_get_items_permissions_data_provider() { + return array( + 'Administrator can see note on other posts' => array( 'administrator', 'author', true ), + 'Editor can see note on other posts' => array( 'editor', 'contributor', true ), + 'Author cannot see note on other posts' => array( 'author', 'editor', false ), + 'Contributor cannot see note on other posts' => array( 'contributor', 'author', false ), + 'Subscriber cannot see note' => array( 'subscriber', 'author', false ), + 'Author can see note on own post' => array( 'author', 'author', true ), + 'Contributor can see note on own post' => array( 'contributor', 'contributor', true ), + ); + } + + public function data_note_status_provider() { + return array( + 'resolved' => array( 'resolved' ), + 'reopen' => array( 'reopen' ), + ); + } } From ce0f128cf7c145255163ff6f196bc6b05aeb3c14 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 20:18:52 +0900 Subject: [PATCH 14/36] Minor refactoring and fix lint error --- .../rest-api/rest-comments-controller.php | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index 217cf5042a921..3cd3d4b1a79f3 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -5,7 +5,7 @@ * @package WordPress * @subpackage REST API * - * @group restapi1 + * @group restapi */ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase { protected static $superadmin_id; @@ -42,23 +42,23 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { ) ); - self::$superadmin_id = $factory->user->create( + self::$superadmin_id = $factory->user->create( array( 'role' => 'administrator', 'user_login' => 'superadmin', ) ); - self::$admin_id = $factory->user->create( + self::$admin_id = $factory->user->create( array( 'role' => 'administrator', ) ); - self::$editor_id = $factory->user->create( + self::$editor_id = $factory->user->create( array( 'role' => 'editor', ) ); - self::$moderator_id = $factory->user->create( + self::$moderator_id = $factory->user->create( array( 'role' => 'comment_moderator', ) @@ -68,12 +68,12 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { 'role' => 'contributor', ) ); - self::$subscriber_id = $factory->user->create( + self::$subscriber_id = $factory->user->create( array( 'role' => 'subscriber', ) ); - self::$author_id = $factory->user->create( + self::$author_id = $factory->user->create( array( 'role' => 'author', 'display_name' => 'Sea Captain', @@ -121,6 +121,16 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { ) ); + self::$user_ids = array( + 'superadmin' => self::$superadmin_id, + 'administrator' => self::$admin_id, + 'editor' => self::$editor_id, + 'moderator' => self::$moderator_id, + 'contributor' => self::$contributor_id, + 'subscriber' => self::$subscriber_id, + 'author' => self::$author_id, + ); + // Set up comments for pagination tests. for ( $i = 0; $i < self::$total_comments - 1; $i++ ) { self::$comment_ids[] = $factory->comment->create( @@ -163,16 +173,6 @@ public function set_up() { if ( is_multisite() ) { update_site_option( 'site_admins', array( 'superadmin' ) ); } - - self::$user_ids = array( - 'superadmin' => self::$superadmin_id, - 'administrator' => self::$admin_id, - 'editor' => self::$editor_id, - 'moderator' => self::$moderator_id, - 'contributor' => self::$contributor_id, - 'subscriber' => self::$subscriber_id, - 'author' => self::$author_id, - ); } public function test_register_routes() { @@ -3991,13 +3991,13 @@ public function test_note_get_item_permissions_edit_context( $role, $post_author public function data_note_get_items_permissions_data_provider() { return array( - 'Administrator can see note on other posts' => array( 'administrator', 'author', true ), - 'Editor can see note on other posts' => array( 'editor', 'contributor', true ), - 'Author cannot see note on other posts' => array( 'author', 'editor', false ), + 'Administrator can see note on other posts' => array( 'administrator', 'author', true ), + 'Editor can see note on other posts' => array( 'editor', 'contributor', true ), + 'Author cannot see note on other posts' => array( 'author', 'editor', false ), 'Contributor cannot see note on other posts' => array( 'contributor', 'author', false ), - 'Subscriber cannot see note' => array( 'subscriber', 'author', false ), - 'Author can see note on own post' => array( 'author', 'author', true ), - 'Contributor can see note on own post' => array( 'contributor', 'contributor', true ), + 'Subscriber cannot see note' => array( 'subscriber', 'author', false ), + 'Author can see note on own post' => array( 'author', 'author', true ), + 'Contributor can see note on own post' => array( 'contributor', 'contributor', true ), ); } From df8cb5dce043e961635a138c6c38aa2734186935 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 20:33:45 +0900 Subject: [PATCH 15/36] Regenerate API fixtures --- tests/qunit/fixtures/wp-api-generated.js | 271 ++++++++++++++++++++--- 1 file changed, 236 insertions(+), 35 deletions(-) diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index bc9ea0a2dc424..878535a9353a0 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -21,13 +21,7 @@ mockedApiResponse.Schema = { "wp-site-health/v1", "wp-block-editor/v1" ], - "authentication": { - "application-passwords": { - "endpoints": { - "authorization": "http://example.org/wp-admin/authorize-application.php" - } - } - }, + "authentication": [], "routes": { "/": { "namespace": "", @@ -854,7 +848,14 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "footnotes": { + "type": "string", + "title": "", + "description": "", + "default": "" + } + }, "required": false }, "sticky": { @@ -1138,7 +1139,14 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "footnotes": { + "type": "string", + "title": "", + "description": "", + "default": "" + } + }, "required": false }, "sticky": { @@ -1577,7 +1585,14 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "footnotes": { + "type": "string", + "title": "", + "description": "", + "default": "" + } + }, "required": false }, "sticky": { @@ -2053,7 +2068,14 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "footnotes": { + "type": "string", + "title": "", + "description": "", + "default": "" + } + }, "required": false }, "template": { @@ -2309,7 +2331,14 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "footnotes": { + "type": "string", + "title": "", + "description": "", + "default": "" + } + }, "required": false }, "template": { @@ -2715,7 +2744,14 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "footnotes": { + "type": "string", + "title": "", + "description": "", + "default": "" + } + }, "required": false }, "template": { @@ -4878,7 +4914,24 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "wp_pattern_sync_status": { + "type": "string", + "title": "", + "description": "", + "default": "", + "enum": [ + "partial", + "unsynced" + ] + }, + "footnotes": { + "type": "string", + "title": "", + "description": "", + "default": "" + } + }, "required": false }, "template": { @@ -5087,7 +5140,24 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "wp_pattern_sync_status": { + "type": "string", + "title": "", + "description": "", + "default": "", + "enum": [ + "partial", + "unsynced" + ] + }, + "footnotes": { + "type": "string", + "title": "", + "description": "", + "default": "" + } + }, "required": false }, "template": { @@ -5451,7 +5521,24 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "wp_pattern_sync_status": { + "type": "string", + "title": "", + "description": "", + "default": "", + "enum": [ + "partial", + "unsynced" + ] + }, + "footnotes": { + "type": "string", + "title": "", + "description": "", + "default": "" + } + }, "required": false }, "template": { @@ -9834,7 +9921,26 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "persisted_preferences": { + "type": "object", + "title": "", + "description": "", + "default": [], + "context": [ + "edit" + ], + "properties": { + "_modified": { + "description": "The date and time the preferences were updated.", + "type": "string", + "format": "date-time", + "readonly": false + } + }, + "additionalProperties": true + } + }, "required": false } } @@ -9972,7 +10078,26 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "persisted_preferences": { + "type": "object", + "title": "", + "description": "", + "default": [], + "context": [ + "edit" + ], + "properties": { + "_modified": { + "description": "The date and time the preferences were updated.", + "type": "string", + "format": "date-time", + "readonly": false + } + }, + "additionalProperties": true + } + }, "required": false } } @@ -10117,7 +10242,26 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "persisted_preferences": { + "type": "object", + "title": "", + "description": "", + "default": [], + "context": [ + "edit" + ], + "properties": { + "_modified": { + "description": "The date and time the preferences were updated.", + "type": "string", + "format": "date-time", + "readonly": false + } + }, + "additionalProperties": true + } + }, "required": false } } @@ -10568,10 +10712,31 @@ mockedApiResponse.Schema = { "type": "string", "required": false }, + "type": { + "default": "comment", + "description": "Type of the comment.", + "type": "string", + "enum": [ + "comment", + "note" + ], + "required": false + }, "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "_wp_note_status": { + "type": "string", + "title": "", + "description": "Note resolution status", + "default": "", + "enum": [ + "resolved", + "reopen" + ] + } + }, "required": false } } @@ -10715,10 +10880,30 @@ mockedApiResponse.Schema = { "type": "string", "required": false }, + "type": { + "description": "Type of the comment.", + "type": "string", + "enum": [ + "comment", + "note" + ], + "required": false + }, "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "_wp_note_status": { + "type": "string", + "title": "", + "description": "Note resolution status", + "default": "", + "enum": [ + "resolved", + "reopen" + ] + } + }, "required": false } } @@ -12574,7 +12759,8 @@ mockedApiResponse.PostsCollection = [ "template": "", "format": "standard", "meta": { - "meta_key": "meta_value" + "meta_key": "meta_value", + "footnotes": "" }, "categories": [ 1 @@ -12692,7 +12878,8 @@ mockedApiResponse.PostModel = { "template": "", "format": "standard", "meta": { - "meta_key": "meta_value" + "meta_key": "meta_value", + "footnotes": "" }, "categories": [ 1 @@ -12732,7 +12919,8 @@ mockedApiResponse.postRevisions = [ "rendered": "" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "_links": { "parent": [ @@ -12764,7 +12952,8 @@ mockedApiResponse.postRevisions = [ "rendered": "

REST API Client Fixture: Post

\n" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "_links": { "parent": [ @@ -12798,7 +12987,8 @@ mockedApiResponse.revision = { "rendered": "

REST API Client Fixture: Post

\n" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" } }; @@ -12825,7 +13015,8 @@ mockedApiResponse.postAutosaves = [ "rendered": "" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "_links": { "parent": [ @@ -12859,7 +13050,8 @@ mockedApiResponse.autosave = { "rendered": "" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" } }; @@ -12896,7 +13088,8 @@ mockedApiResponse.PagesCollection = [ "ping_status": "closed", "template": "", "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "class_list": [ "post-7", @@ -12996,7 +13189,8 @@ mockedApiResponse.PageModel = { "ping_status": "closed", "template": "", "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "class_list": [ "post-7", @@ -13030,7 +13224,8 @@ mockedApiResponse.pageRevisions = [ "rendered": "" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "_links": { "parent": [ @@ -13062,7 +13257,8 @@ mockedApiResponse.pageRevisions = [ "rendered": "

REST API Client Fixture: Page

\n" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "_links": { "parent": [ @@ -13096,7 +13292,8 @@ mockedApiResponse.pageRevision = { "rendered": "

REST API Client Fixture: Page

\n" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" } }; @@ -13123,7 +13320,8 @@ mockedApiResponse.pageAutosaves = [ "rendered": "" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "_links": { "parent": [ @@ -13157,7 +13355,8 @@ mockedApiResponse.pageAutosave = { "rendered": "" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" } }; @@ -14174,6 +14373,7 @@ mockedApiResponse.CommentsCollection = [ "96": "https://secure.gravatar.com/avatar/9ca51ced0b389ffbeba3d269c6d824be664c84fa1b35503282abdd302e1f417c?s=96&d=mm&r=g" }, "meta": { + "_wp_note_status": null, "meta_key": "meta_value" }, "_links": { @@ -14228,6 +14428,7 @@ mockedApiResponse.CommentModel = { "96": "https://secure.gravatar.com/avatar/9ca51ced0b389ffbeba3d269c6d824be664c84fa1b35503282abdd302e1f417c?s=96&d=mm&r=g" }, "meta": { + "_wp_note_status": null, "meta_key": "meta_value" } }; From 4833d1902fec46405be277c48d26e3f453f630a9 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 20:44:09 +0900 Subject: [PATCH 16/36] Try to fix JS API Tests --- tests/qunit/wp-includes/js/wp-api.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/qunit/wp-includes/js/wp-api.js b/tests/qunit/wp-includes/js/wp-api.js index be0249462d398..01d89552cb144 100644 --- a/tests/qunit/wp-includes/js/wp-api.js +++ b/tests/qunit/wp-includes/js/wp-api.js @@ -398,12 +398,16 @@ theModels.fetch().done( function() { - // Get the main endpoint. - var endpoint = theModels.at(0); - var expectedMetas = '{"meta_key":"meta_value"}'; - if ( 'Tags' === modelType ) { - expectedMetas = '{"test_single":"","test_multi":[],"meta_key":"meta_value","test_tag_meta":""}'; - } + // Get the main endpoint. + var endpoint = theModels.at(0); + var expectedMetas = '{"meta_key":"meta_value"}'; + if ( 'Tags' === modelType ) { + expectedMetas = '{"test_single":"","test_multi":[],"meta_key":"meta_value","test_tag_meta":""}'; + } else if ( 'Posts' === modelType ) { + expectedMetas = '{"meta_key":"meta_value","footnotes":""}'; + } else if ( 'Comments' === modelType ) { + expectedMetas = '{"meta_key":"meta_value"},"_wp_note_status":null'; + } // Verify the meta object returned correctly from `getMetas()`. assert.equal( JSON.stringify( endpoint.getMetas() ), expectedMetas, 'Full meta key/values object should be readable.' ); From 35375c80e07234627376f2ec077e50484cb87749 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 20:47:29 +0900 Subject: [PATCH 17/36] "block comments" to "notes" --- .../endpoints/class-wp-rest-comments-controller.php | 6 +++--- 1 file changed, 3 insertions(+), 3 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 5284c58f38089..389d5190dc038 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 @@ -1962,7 +1962,7 @@ protected function check_is_comment_content_allowed( $prepared_comment ) { return true; } - // Allow empty block comments only when resolution metadata is valid. + // Allow empty notes only when resolution metadata is valid. if ( isset( $check['comment_type'] ) && 'note' === $check['comment_type'] && @@ -1980,10 +1980,10 @@ protected function check_is_comment_content_allowed( $prepared_comment ) { } /** - * Check if post type supports block comments. + * Check if post type supports notes. * * @param string $post_type Post type name. - * @return bool True if post type supports block comments, false otherwise. + * @return bool True if post type supports notes, false otherwise. */ private function check_post_type_supports_notes( $post_type ) { $supports = get_all_post_type_supports( $post_type ); From 4ab0abf3767b597919ee52582eb4e0dcbdcda82f Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 20:54:45 +0900 Subject: [PATCH 18/36] Try to fix JS API Tests (Take 2) --- tests/qunit/wp-includes/js/wp-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/qunit/wp-includes/js/wp-api.js b/tests/qunit/wp-includes/js/wp-api.js index 01d89552cb144..9b3e46a3a9c21 100644 --- a/tests/qunit/wp-includes/js/wp-api.js +++ b/tests/qunit/wp-includes/js/wp-api.js @@ -406,7 +406,7 @@ } else if ( 'Posts' === modelType ) { expectedMetas = '{"meta_key":"meta_value","footnotes":""}'; } else if ( 'Comments' === modelType ) { - expectedMetas = '{"meta_key":"meta_value"},"_wp_note_status":null'; + expectedMetas = '{"_wp_note_status":null,"meta_key":"meta_value"}'; } // Verify the meta object returned correctly from `getMetas()`. From b0bcc8c0ef8614c17a9275e93c4b23d2bb68a154 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 21:21:32 +0900 Subject: [PATCH 19/36] Revert changes for JS API Tests --- tests/qunit/wp-includes/js/wp-api.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/qunit/wp-includes/js/wp-api.js b/tests/qunit/wp-includes/js/wp-api.js index 9b3e46a3a9c21..28cfaa8d6eb28 100644 --- a/tests/qunit/wp-includes/js/wp-api.js +++ b/tests/qunit/wp-includes/js/wp-api.js @@ -403,10 +403,6 @@ var expectedMetas = '{"meta_key":"meta_value"}'; if ( 'Tags' === modelType ) { expectedMetas = '{"test_single":"","test_multi":[],"meta_key":"meta_value","test_tag_meta":""}'; - } else if ( 'Posts' === modelType ) { - expectedMetas = '{"meta_key":"meta_value","footnotes":""}'; - } else if ( 'Comments' === modelType ) { - expectedMetas = '{"_wp_note_status":null,"meta_key":"meta_value"}'; } // Verify the meta object returned correctly from `getMetas()`. From 891d9d22a896d717c9689e94be3e2d6fefe1cd3b Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 21:24:43 +0900 Subject: [PATCH 20/36] Add @ticket docblock --- .../rest-api/rest-comments-controller.php | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index 3cd3d4b1a79f3..fbe6d0386e2cc 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -3647,6 +3647,9 @@ protected function create_test_post_with_note( $role ) { return $post_id; } + /** + * @ticket 64096 + */ public function test_cannot_read_note_without_post_type_support() { register_post_type( 'no-notes', @@ -3673,6 +3676,9 @@ public function test_cannot_read_note_without_post_type_support() { _unregister_post_type( 'no-notes' ); } + /** + * @ticket 64096 + */ public function test_create_note_require_login() { wp_set_current_user( 0 ); @@ -3685,6 +3691,9 @@ public function test_create_note_require_login() { $this->assertErrorResponse( 'rest_comment_login_required', $response, 401 ); } + /** + * @ticket 64096 + */ public function test_cannot_create_note_without_post_type_support() { register_post_type( 'no-note', @@ -3717,6 +3726,9 @@ public function test_cannot_create_note_without_post_type_support() { _unregister_post_type( 'no-note' ); } + /** + * @ticket 64096 + */ public function test_create_note_draft_post() { wp_set_current_user( self::$editor_id ); $draft_id = self::factory()->post->create( @@ -3745,6 +3757,9 @@ public function test_create_note_draft_post() { $this->assertSame( 'note', $new_comment->comment_type ); } + /** + * @ticket 64096 + */ public function test_create_note_status() { wp_set_current_user( self::$author_id ); $post_id = self::factory()->post->create( array( 'post_author' => self::$author_id ) ); @@ -3772,6 +3787,9 @@ public function test_create_note_status() { $this->assertSame( 'note', $new_comment->comment_type ); } + /** + * @ticket 64096 + */ public function test_cannot_create_with_non_valid_comment_type() { wp_set_current_user( self::$admin_id ); @@ -3792,6 +3810,9 @@ public function test_cannot_create_with_non_valid_comment_type() { $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); } + /** + * @ticket 64096 + */ public function test_create_assigns_default_type() { wp_set_current_user( self::$editor_id ); $post_id = self::factory()->post->create(); @@ -3818,6 +3839,7 @@ public function test_create_assigns_default_type() { /** * @dataProvider data_note_status_provider + * @ticket 64096 */ public function test_create_empty_note_with_resolution_meta( $status ) { wp_set_current_user( self::$editor_id ); @@ -3842,6 +3864,9 @@ public function test_create_empty_note_with_resolution_meta( $status ) { $this->assertSame( 201, $response->get_status() ); } + /** + * @ticket 64096 + */ public function test_cannot_create_empty_note_without_resolution_meta() { wp_set_current_user( self::$editor_id ); $post_id = self::factory()->post->create(); @@ -3861,6 +3886,9 @@ public function test_cannot_create_empty_note_without_resolution_meta() { $this->assertErrorResponse( 'rest_comment_content_invalid', $response, 400 ); } + /** + * @ticket 64096 + */ public function test_cannot_create_empty_note_with_invalid_resolution_meta() { wp_set_current_user( self::$editor_id ); $post_id = self::factory()->post->create(); @@ -3883,6 +3911,9 @@ public function test_cannot_create_empty_note_with_invalid_resolution_meta() { $this->assertErrorResponse( 'rest_comment_content_invalid', $response, 400 ); } + /** + * @ticket 64096 + */ public function test_create_duplicate_note() { wp_set_current_user( self::$editor_id ); $post_id = self::factory()->post->create(); @@ -3907,6 +3938,7 @@ public function test_create_duplicate_note() { /** * @dataProvider data_note_get_items_permissions_data_provider + * @ticket 64096 */ public function test_note_get_items_permissions_edit_context( $role, $post_author_role, $can_read ) { wp_set_current_user( self::$user_ids[ $role ] ); @@ -3930,6 +3962,9 @@ public function test_note_get_items_permissions_edit_context( $role, $post_autho wp_delete_post( $post_id, true ); } + /** + * @ticket 64096 + */ public function test_note_get_items_permissions_mixed_post_authors() { $author_post_id = $this->create_test_post_with_note( 'author' ); $editor_post_id = $this->create_test_post_with_note( 'editor' ); @@ -3952,6 +3987,7 @@ public function test_note_get_items_permissions_mixed_post_authors() { /** * @dataProvider data_note_get_items_permissions_data_provider + * @ticket 64096 */ public function test_note_get_item_permissions_edit_context( $role, $post_author_role, $can_read ) { wp_set_current_user( self::$user_ids[ $role ] ); From 6caa20b6c490c7e24e6d9d12f84490da2d22bf04 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 21:25:41 +0900 Subject: [PATCH 21/36] Revert unnecessary changes for post_type_support --- src/wp-includes/post.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 2b8eb16c4a99f..19cd927c5c8dc 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2337,9 +2337,6 @@ function get_all_post_type_supports( $post_type ) { * Checks a post type's support for a given feature. * * @since 3.0.0 - * @since 6.9.0 Added support for sub-features. - * To check a sub-feature, pass a slash-delimited feature string - * like 'editor/notes'. * * @global array $_wp_post_type_features * From 1f3b1d8851babae968bf9abd3e66d20b278a0ad4 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 21:35:30 +0900 Subject: [PATCH 22/36] Move "where" clauses into WP_Comments_List_Table --- src/wp-admin/includes/class-wp-comments-list-table.php | 1 + src/wp-includes/class-wp-comment-query.php | 10 ---------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/wp-admin/includes/class-wp-comments-list-table.php b/src/wp-admin/includes/class-wp-comments-list-table.php index c4d323cf892b0..82cc921ee3bc0 100644 --- a/src/wp-admin/includes/class-wp-comments-list-table.php +++ b/src/wp-admin/includes/class-wp-comments-list-table.php @@ -151,6 +151,7 @@ public function prepare_items() { 'order' => $order, 'post_type' => $post_type, 'update_comment_post_cache' => true, + 'type__not_in' => array( 'note' ), ); /** diff --git a/src/wp-includes/class-wp-comment-query.php b/src/wp-includes/class-wp-comment-query.php index 4ae45f72563ec..6c813c7e1a7db 100644 --- a/src/wp-includes/class-wp-comment-query.php +++ b/src/wp-includes/class-wp-comment-query.php @@ -939,16 +939,6 @@ protected function get_comment_ids() { */ $clauses = apply_filters_ref_array( 'comments_clauses', array( compact( $pieces ), &$this ) ); - // Exclude notes from admin comment queries. - if ( is_admin() && isset( $this->query_vars['type'] ) && '' === $this->query_vars['type'] ) { - global $wpdb; - if ( isset( $clauses['where'] ) && ! empty( $clauses['where'] ) ) { - $clauses['where'] .= " AND {$wpdb->comments}.comment_type != 'note'"; - } else { - $clauses['where'] = "{$wpdb->comments}.comment_type != 'note'"; - } - } - $fields = isset( $clauses['fields'] ) ? $clauses['fields'] : ''; $join = isset( $clauses['join'] ) ? $clauses['join'] : ''; $where = isset( $clauses['where'] ) ? $clauses['where'] : ''; From 10e10233d855559761e5df5912bd4970979f3891 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 21:37:35 +0900 Subject: [PATCH 23/36] Revert wp-api.js and wp-api-generated.js to trunk versions --- tests/qunit/fixtures/wp-api-generated.js | 419 +++-------------------- tests/qunit/wp-includes/js/wp-api.js | 12 +- 2 files changed, 52 insertions(+), 379 deletions(-) diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 878535a9353a0..6626758a8a9dc 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -21,7 +21,13 @@ mockedApiResponse.Schema = { "wp-site-health/v1", "wp-block-editor/v1" ], - "authentication": [], + "authentication": { + "application-passwords": { + "endpoints": { + "authorization": "http://example.org/wp-admin/authorize-application.php" + } + } + }, "routes": { "/": { "namespace": "", @@ -848,14 +854,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "footnotes": { - "type": "string", - "title": "", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "sticky": { @@ -1139,14 +1138,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "footnotes": { - "type": "string", - "title": "", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "sticky": { @@ -1585,14 +1577,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "footnotes": { - "type": "string", - "title": "", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "sticky": { @@ -2068,14 +2053,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "footnotes": { - "type": "string", - "title": "", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "template": { @@ -2331,14 +2309,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "footnotes": { - "type": "string", - "title": "", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "template": { @@ -2744,14 +2715,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "footnotes": { - "type": "string", - "title": "", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "template": { @@ -2999,27 +2963,21 @@ mockedApiResponse.Schema = { }, "media_type": { "default": null, - "description": "Limit result set to attachments of a particular media type or media types.", - "type": "array", - "items": { - "type": "string", - "enum": [ - "image", - "video", - "text", - "application", - "audio" - ] - }, + "description": "Limit result set to attachments of a particular media type.", + "type": "string", + "enum": [ + "image", + "video", + "text", + "application", + "audio" + ], "required": false }, "mime_type": { "default": null, - "description": "Limit result set to attachments of a particular MIME type or MIME types.", - "type": "array", - "items": { - "type": "string" - }, + "description": "Limit result set to attachments of a particular MIME type.", + "type": "string", "required": false } } @@ -3469,45 +3427,6 @@ mockedApiResponse.Schema = { "args" ], "oneOf": [ - { - "title": "Flip", - "properties": { - "type": { - "description": "Flip type.", - "type": "string", - "enum": [ - "flip" - ] - }, - "args": { - "description": "Flip arguments.", - "type": "object", - "required": [ - "flip" - ], - "properties": { - "flip": { - "description": "Flip direction.", - "type": "object", - "required": [ - "horizontal", - "vertical" - ], - "properties": { - "horizontal": { - "description": "Whether to flip in the horizontal direction.", - "type": "boolean" - }, - "vertical": { - "description": "Whether to flip in the vertical direction.", - "type": "boolean" - } - } - } - } - } - } - }, { "title": "Rotation", "properties": { @@ -3613,87 +3532,6 @@ mockedApiResponse.Schema = { "minimum": 0, "maximum": 100, "required": false - }, - "caption": { - "description": "The attachment caption.", - "type": "object", - "properties": { - "raw": { - "description": "Caption for the attachment, as it exists in the database.", - "type": "string", - "context": [ - "edit" - ] - }, - "rendered": { - "description": "HTML caption for the attachment, transformed for display.", - "type": "string", - "context": [ - "view", - "edit", - "embed" - ], - "readonly": true - } - }, - "required": false - }, - "description": { - "description": "The attachment description.", - "type": "object", - "properties": { - "raw": { - "description": "Description for the attachment, as it exists in the database.", - "type": "string", - "context": [ - "edit" - ] - }, - "rendered": { - "description": "HTML description for the attachment, transformed for display.", - "type": "string", - "context": [ - "view", - "edit" - ], - "readonly": true - } - }, - "required": false - }, - "title": { - "description": "The title for the post.", - "type": "object", - "properties": { - "raw": { - "description": "Title for the post, as it exists in the database.", - "type": "string", - "context": [ - "edit" - ] - }, - "rendered": { - "description": "HTML title for the post, transformed for display.", - "type": "string", - "context": [ - "view", - "edit", - "embed" - ], - "readonly": true - } - }, - "required": false - }, - "post": { - "description": "The ID for the associated post of the attachment.", - "type": "integer", - "required": false - }, - "alt_text": { - "description": "Alternative text to display when attachment is not displayed.", - "type": "string", - "required": false } } } @@ -4914,24 +4752,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "wp_pattern_sync_status": { - "type": "string", - "title": "", - "description": "", - "default": "", - "enum": [ - "partial", - "unsynced" - ] - }, - "footnotes": { - "type": "string", - "title": "", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "template": { @@ -5140,24 +4961,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "wp_pattern_sync_status": { - "type": "string", - "title": "", - "description": "", - "default": "", - "enum": [ - "partial", - "unsynced" - ] - }, - "footnotes": { - "type": "string", - "title": "", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "template": { @@ -5521,24 +5325,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "wp_pattern_sync_status": { - "type": "string", - "title": "", - "description": "", - "default": "", - "enum": [ - "partial", - "unsynced" - ] - }, - "footnotes": { - "type": "string", - "title": "", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "template": { @@ -9921,26 +9708,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "persisted_preferences": { - "type": "object", - "title": "", - "description": "", - "default": [], - "context": [ - "edit" - ], - "properties": { - "_modified": { - "description": "The date and time the preferences were updated.", - "type": "string", - "format": "date-time", - "readonly": false - } - }, - "additionalProperties": true - } - }, + "properties": [], "required": false } } @@ -10078,26 +9846,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "persisted_preferences": { - "type": "object", - "title": "", - "description": "", - "default": [], - "context": [ - "edit" - ], - "properties": { - "_modified": { - "description": "The date and time the preferences were updated.", - "type": "string", - "format": "date-time", - "readonly": false - } - }, - "additionalProperties": true - } - }, + "properties": [], "required": false } } @@ -10242,26 +9991,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "persisted_preferences": { - "type": "object", - "title": "", - "description": "", - "default": [], - "context": [ - "edit" - ], - "properties": { - "_modified": { - "description": "The date and time the preferences were updated.", - "type": "string", - "format": "date-time", - "readonly": false - } - }, - "additionalProperties": true - } - }, + "properties": [], "required": false } } @@ -10712,31 +10442,10 @@ mockedApiResponse.Schema = { "type": "string", "required": false }, - "type": { - "default": "comment", - "description": "Type of the comment.", - "type": "string", - "enum": [ - "comment", - "note" - ], - "required": false - }, "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "_wp_note_status": { - "type": "string", - "title": "", - "description": "Note resolution status", - "default": "", - "enum": [ - "resolved", - "reopen" - ] - } - }, + "properties": [], "required": false } } @@ -10880,30 +10589,10 @@ mockedApiResponse.Schema = { "type": "string", "required": false }, - "type": { - "description": "Type of the comment.", - "type": "string", - "enum": [ - "comment", - "note" - ], - "required": false - }, "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "_wp_note_status": { - "type": "string", - "title": "", - "description": "Note resolution status", - "default": "", - "enum": [ - "resolved", - "reopen" - ] - } - }, + "properties": [], "required": false } } @@ -12759,8 +12448,7 @@ mockedApiResponse.PostsCollection = [ "template": "", "format": "standard", "meta": { - "meta_key": "meta_value", - "footnotes": "" + "meta_key": "meta_value" }, "categories": [ 1 @@ -12878,8 +12566,7 @@ mockedApiResponse.PostModel = { "template": "", "format": "standard", "meta": { - "meta_key": "meta_value", - "footnotes": "" + "meta_key": "meta_value" }, "categories": [ 1 @@ -12919,8 +12606,7 @@ mockedApiResponse.postRevisions = [ "rendered": "" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "_links": { "parent": [ @@ -12952,8 +12638,7 @@ mockedApiResponse.postRevisions = [ "rendered": "

REST API Client Fixture: Post

\n" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "_links": { "parent": [ @@ -12987,8 +12672,7 @@ mockedApiResponse.revision = { "rendered": "

REST API Client Fixture: Post

\n" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" } }; @@ -13015,8 +12699,7 @@ mockedApiResponse.postAutosaves = [ "rendered": "" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "_links": { "parent": [ @@ -13050,8 +12733,7 @@ mockedApiResponse.autosave = { "rendered": "" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" } }; @@ -13088,8 +12770,7 @@ mockedApiResponse.PagesCollection = [ "ping_status": "closed", "template": "", "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "class_list": [ "post-7", @@ -13189,8 +12870,7 @@ mockedApiResponse.PageModel = { "ping_status": "closed", "template": "", "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "class_list": [ "post-7", @@ -13224,8 +12904,7 @@ mockedApiResponse.pageRevisions = [ "rendered": "" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "_links": { "parent": [ @@ -13257,8 +12936,7 @@ mockedApiResponse.pageRevisions = [ "rendered": "

REST API Client Fixture: Page

\n" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "_links": { "parent": [ @@ -13292,8 +12970,7 @@ mockedApiResponse.pageRevision = { "rendered": "

REST API Client Fixture: Page

\n" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" } }; @@ -13320,8 +12997,7 @@ mockedApiResponse.pageAutosaves = [ "rendered": "" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "_links": { "parent": [ @@ -13355,8 +13031,7 @@ mockedApiResponse.pageAutosave = { "rendered": "" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" } }; @@ -14373,7 +14048,6 @@ mockedApiResponse.CommentsCollection = [ "96": "https://secure.gravatar.com/avatar/9ca51ced0b389ffbeba3d269c6d824be664c84fa1b35503282abdd302e1f417c?s=96&d=mm&r=g" }, "meta": { - "_wp_note_status": null, "meta_key": "meta_value" }, "_links": { @@ -14428,7 +14102,6 @@ mockedApiResponse.CommentModel = { "96": "https://secure.gravatar.com/avatar/9ca51ced0b389ffbeba3d269c6d824be664c84fa1b35503282abdd302e1f417c?s=96&d=mm&r=g" }, "meta": { - "_wp_note_status": null, "meta_key": "meta_value" } }; diff --git a/tests/qunit/wp-includes/js/wp-api.js b/tests/qunit/wp-includes/js/wp-api.js index 28cfaa8d6eb28..be0249462d398 100644 --- a/tests/qunit/wp-includes/js/wp-api.js +++ b/tests/qunit/wp-includes/js/wp-api.js @@ -398,12 +398,12 @@ theModels.fetch().done( function() { - // Get the main endpoint. - var endpoint = theModels.at(0); - var expectedMetas = '{"meta_key":"meta_value"}'; - if ( 'Tags' === modelType ) { - expectedMetas = '{"test_single":"","test_multi":[],"meta_key":"meta_value","test_tag_meta":""}'; - } + // Get the main endpoint. + var endpoint = theModels.at(0); + var expectedMetas = '{"meta_key":"meta_value"}'; + if ( 'Tags' === modelType ) { + expectedMetas = '{"test_single":"","test_multi":[],"meta_key":"meta_value","test_tag_meta":""}'; + } // Verify the meta object returned correctly from `getMetas()`. assert.equal( JSON.stringify( endpoint.getMetas() ), expectedMetas, 'Full meta key/values object should be readable.' ); From ae2ba635e9fc556c3ce7e51664ae7f4b0bd6b3ae Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 22:00:44 +0900 Subject: [PATCH 24/36] Don't change 'type' schema --- .../class-wp-rest-comments-controller.php | 14 +++++++++++--- .../tests/rest-api/rest-comments-controller.php | 10 ++++++---- 2 files changed, 17 insertions(+), 7 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 389d5190dc038..efddc6822de08 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 @@ -626,12 +626,21 @@ 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 ) ) { + return new WP_Error( + 'rest_invalid_comment_type', + __( 'Cannot create a comment with that type.', 'gutenberg' ), + array( 'status' => 400 ) + ); + } + $prepared_comment = $this->prepare_item_for_database( $request ); if ( is_wp_error( $prepared_comment ) ) { return $prepared_comment; } - $prepared_comment['comment_type'] = $request['type']; + $prepared_comment['comment_type'] = empty( $request['type'] ) ? 'comment' : $request['type']; if ( ! isset( $prepared_comment['comment_content'] ) ) { $prepared_comment['comment_content'] = ''; @@ -1556,9 +1565,8 @@ public function get_item_schema() { 'type' => array( 'description' => __( 'Type of the comment.' ), 'type' => 'string', - 'enum' => array( 'comment', 'note' ), - 'default' => 'comment', 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, ), ), ); diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index fbe6d0386e2cc..89712af26ac47 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -1699,7 +1699,7 @@ public function test_create_comment_with_invalid_type() { $request->set_body( wp_json_encode( $params ) ); $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + $this->assertErrorResponse( 'rest_invalid_comment_type', $response, 400 ); } public function test_create_comment_invalid_email() { @@ -2716,7 +2716,7 @@ public function test_update_comment_invalid_type() { $request->set_body( wp_json_encode( $params ) ); $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + $this->assertErrorResponse( 'rest_comment_invalid_type', $response, 404 ); } public function test_update_comment_with_raw_property() { @@ -3305,9 +3305,9 @@ public function test_get_item_schema() { $this->assertSame( 0, $properties['parent']['default'] ); $this->assertSame( 0, $properties['post']['default'] ); - $this->assertSame( array( 'comment', 'note' ), $properties['type']['enum'] ); $this->assertTrue( $properties['link']['readonly'] ); + $this->assertTrue( $properties['type']['readonly'] ); } public function test_get_item_schema_show_avatar() { @@ -3792,8 +3792,10 @@ public function test_create_note_status() { */ public function test_cannot_create_with_non_valid_comment_type() { wp_set_current_user( self::$admin_id ); + $post_id = $this->factory->post->create(); $params = array( + 'post' => $post_id, 'author_name' => 'Ishmael', 'author_email' => 'herman-melville@earthlink.net', 'author_url' => 'https://en.wikipedia.org/wiki/Herman_Melville', @@ -3807,7 +3809,7 @@ public function test_cannot_create_with_non_valid_comment_type() { $request->set_body( wp_json_encode( $params ) ); $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + $this->assertErrorResponse( 'rest_invalid_comment_type', $response, 400 ); } /** From 6ada59c98d2c545c953e000712b83f9a1e5ea27c Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 22:27:25 +0900 Subject: [PATCH 25/36] check_post_type_supports_notes: invert default return value --- .../rest-api/endpoints/class-wp-rest-comments-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 efddc6822de08..28805651ccc27 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 @@ -2006,6 +2006,6 @@ private function check_post_type_supports_notes( $post_type ) { return true; } } - return true; + return false; } } From 571ede1347bb17af856a4af2b4f81a524e132678 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 15 Oct 2025 22:29:20 +0900 Subject: [PATCH 26/36] entirely revert wp-api-generated.js --- tests/qunit/fixtures/wp-api-generated.js | 148 +++++++++++++++++++++-- 1 file changed, 137 insertions(+), 11 deletions(-) diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 6626758a8a9dc..bc9ea0a2dc424 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -2963,21 +2963,27 @@ mockedApiResponse.Schema = { }, "media_type": { "default": null, - "description": "Limit result set to attachments of a particular media type.", - "type": "string", - "enum": [ - "image", - "video", - "text", - "application", - "audio" - ], + "description": "Limit result set to attachments of a particular media type or media types.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "image", + "video", + "text", + "application", + "audio" + ] + }, "required": false }, "mime_type": { "default": null, - "description": "Limit result set to attachments of a particular MIME type.", - "type": "string", + "description": "Limit result set to attachments of a particular MIME type or MIME types.", + "type": "array", + "items": { + "type": "string" + }, "required": false } } @@ -3427,6 +3433,45 @@ mockedApiResponse.Schema = { "args" ], "oneOf": [ + { + "title": "Flip", + "properties": { + "type": { + "description": "Flip type.", + "type": "string", + "enum": [ + "flip" + ] + }, + "args": { + "description": "Flip arguments.", + "type": "object", + "required": [ + "flip" + ], + "properties": { + "flip": { + "description": "Flip direction.", + "type": "object", + "required": [ + "horizontal", + "vertical" + ], + "properties": { + "horizontal": { + "description": "Whether to flip in the horizontal direction.", + "type": "boolean" + }, + "vertical": { + "description": "Whether to flip in the vertical direction.", + "type": "boolean" + } + } + } + } + } + } + }, { "title": "Rotation", "properties": { @@ -3532,6 +3577,87 @@ mockedApiResponse.Schema = { "minimum": 0, "maximum": 100, "required": false + }, + "caption": { + "description": "The attachment caption.", + "type": "object", + "properties": { + "raw": { + "description": "Caption for the attachment, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML caption for the attachment, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "description": { + "description": "The attachment description.", + "type": "object", + "properties": { + "raw": { + "description": "Description for the attachment, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML description for the attachment, transformed for display.", + "type": "string", + "context": [ + "view", + "edit" + ], + "readonly": true + } + }, + "required": false + }, + "title": { + "description": "The title for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Title for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML title for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "post": { + "description": "The ID for the associated post of the attachment.", + "type": "integer", + "required": false + }, + "alt_text": { + "description": "Alternative text to display when attachment is not displayed.", + "type": "string", + "required": false } } } From c0d81dbb6cdd5dd9072e2cf037f33de4e5208d6b Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Wed, 15 Oct 2025 22:33:40 +0900 Subject: [PATCH 27/36] Remove unnecessary textdomain Co-authored-by: George Mamadashvili --- .../rest-api/endpoints/class-wp-rest-comments-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 28805651ccc27..37ec7586fd148 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 @@ -630,7 +630,7 @@ public function create_item( $request ) { if ( ! empty( $request['type'] ) && ! in_array( $request['type'], array( 'comment', 'note' ), true ) ) { return new WP_Error( 'rest_invalid_comment_type', - __( 'Cannot create a comment with that type.', 'gutenberg' ), + __( 'Cannot create a comment with that type.' ), array( 'status' => 400 ) ); } From a208f84e49f8667ce8cac30cc4a94121e6ba13e6 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Thu, 16 Oct 2025 09:49:39 +0900 Subject: [PATCH 28/36] Delete unnecessary empty line Co-authored-by: Timothy Jacobs --- .../rest-api/endpoints/class-wp-rest-comments-controller.php | 1 - 1 file changed, 1 deletion(-) 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 37ec7586fd148..3b5ab2ea28d89 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 @@ -1941,7 +1941,6 @@ public function check_comment_author_email( $value, $request, $param ) { return $email; } - /** * If empty comments are not allowed, checks if the provided comment content is not empty. * From a21c51f21c421b0941db97ca220e560e8a4bcb2c Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Thu, 16 Oct 2025 09:50:45 +0900 Subject: [PATCH 29/36] Simplify note support check Co-authored-by: Timothy Jacobs --- .../rest-api/endpoints/class-wp-rest-comments-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3b5ab2ea28d89..d37e6b9635d18 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 @@ -2001,7 +2001,7 @@ private function check_post_type_supports_notes( $post_type ) { return false; } foreach ( $supports['editor'] as $item ) { - if ( is_array( $item ) && isset( $item['notes'] ) && true === $item['notes'] ) { + if ( ! empty( $item['notes'] ) ) { return true; } } From 992a3bdc4c50111c438c2033efe2ba2bc1504854 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 16 Oct 2025 09:58:22 +0900 Subject: [PATCH 30/36] Removed redundant empty check --- .../rest-api/endpoints/class-wp-rest-comments-controller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 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 d37e6b9635d18..bb06208d2e56c 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 @@ -123,8 +123,8 @@ 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 = ! empty( $request['type'] ) && 'note' === $request['type']; - $is_edit_context = ! empty( $request['context'] ) && 'edit' === $request['context']; + $is_note = 'note' === $request['type']; + $is_edit_context = 'edit' === $request['context']; if ( ! empty( $request['post'] ) ) { foreach ( (array) $request['post'] as $post_id ) { From f0ee4697f0ae75ce6f72f576077ffe2b49e1c8de Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 16 Oct 2025 10:05:31 +0900 Subject: [PATCH 31/36] =?UTF-8?q?Define=20default=20value=20=E2=80=8B?= =?UTF-8?q?=E2=80=8Bfor=20comment=20type=20as=20a=20schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rest-api/endpoints/class-wp-rest-comments-controller.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 bb06208d2e56c..5c0fe3e8c7770 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 @@ -640,7 +640,7 @@ public function create_item( $request ) { return $prepared_comment; } - $prepared_comment['comment_type'] = empty( $request['type'] ) ? 'comment' : $request['type']; + $prepared_comment['comment_type'] = $request['type']; if ( ! isset( $prepared_comment['comment_content'] ) ) { $prepared_comment['comment_content'] = ''; @@ -1567,6 +1567,7 @@ public function get_item_schema() { 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, + 'default' => 'comment', ), ), ); From 48cb687fc291eb33e03dd581f1664104558885d1 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 16 Oct 2025 10:07:44 +0900 Subject: [PATCH 32/36] Updated function name for registering comment meta --- src/wp-includes/comment.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index b5ffeef0e5d10..2e595eddebd92 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -4110,11 +4110,11 @@ function _wp_check_for_scheduled_update_comment_type() { } /** - * Register notes metadata for notes status. + * Register initial comment meta. * * @since 6.9.0 */ -function wp_register_notes_metadata() { +function wp_create_initial_comment_meta() { register_meta( 'comment', '_wp_note_status', @@ -4134,4 +4134,4 @@ function wp_register_notes_metadata() { ) ); } -add_action( 'init', 'wp_register_notes_metadata' ); +add_action( 'init', 'wp_create_initial_comment_meta' ); From 278d27413a3b11287f26eab43c09089b68b1eada Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 16 Oct 2025 10:12:30 +0900 Subject: [PATCH 33/36] Remove redundant check from comment meta registration --- src/wp-includes/comment.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 2e595eddebd92..dfec28e8581a3 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -4128,9 +4128,6 @@ function wp_create_initial_comment_meta() { 'enum' => array( 'resolved', 'reopen' ), ), ), - 'auth_callback' => function ( $allowed, $meta_key, $object_id ) { - return current_user_can( 'edit_comment', $object_id ); - }, ) ); } From a43a2b0c1a2ef7dc2b085f24410af3f8dbe6b000 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 16 Oct 2025 10:15:44 +0900 Subject: [PATCH 34/36] Make filterable --- src/wp-includes/comment.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index dfec28e8581a3..dbd92fbc1c551 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -714,6 +714,11 @@ function wp_allow_comment( $commentdata, $wp_error = false ) { $dupe_id = $wpdb->get_var( $dupe ); + // Allow duplicate notes for resolution purposes. + if ( isset( $commentdata['comment_type'] ) && 'note' === $commentdata['comment_type'] ) { + $dupe_id = false; + } + /** * Filters the ID, if any, of the duplicate comment found when creating a new comment. * @@ -726,11 +731,6 @@ function wp_allow_comment( $commentdata, $wp_error = false ) { */ $dupe_id = apply_filters( 'duplicate_comment_id', $dupe_id, $commentdata ); - // Allow duplicate notes for resolution purposes. - if ( isset( $commentdata['comment_type'] ) && 'note' === $commentdata['comment_type'] ) { - $dupe_id = false; - } - if ( $dupe_id ) { /** * Fires immediately after a duplicate comment is detected. From a6438788e3475cf50b2a5bb607fc01f86c43586e Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 16 Oct 2025 10:27:46 +0900 Subject: [PATCH 35/36] Don't count Note comments in the dashboard UI --- src/wp-includes/comment.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index dbd92fbc1c551..b489146ea272a 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -417,6 +417,7 @@ function get_comment_count( $post_id = 0 ) { 'count' => true, 'update_comment_meta_cache' => false, 'orderby' => 'none', + 'type__not_in' => array( 'note' ), ); if ( $post_id > 0 ) { $args['post_id'] = $post_id; From 4edfc8d91b722f5ca1f0c7315ab48c542b96041e Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Sat, 18 Oct 2025 15:56:52 +0900 Subject: [PATCH 36/36] Update PHPDoc comment Co-authored-by: Adam Silverstein --- src/wp-includes/comment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index b489146ea272a..8ae1c843252d7 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -4111,7 +4111,7 @@ function _wp_check_for_scheduled_update_comment_type() { } /** - * Register initial comment meta. + * Register initial note status meta. * * @since 6.9.0 */