From 0cf568ccb69ea003de6d12fdf3aff9caf952f54d Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 13 Jul 2026 22:01:07 -0700 Subject: [PATCH 1/2] Notes: Arm the mention kses allowance on REST note creation WP_REST_Comments_Controller::create_item() only copies the type param into the prepared data after 'rest_preprocess_comment' has run, and it inserts through wp_filter_comment() directly rather than wp_new_comment(), so neither existing arm hook fired on the REST create path and mention markup was stripped for note authors without unfiltered_html. Resolve the comment type from the request's type param when the prepared data and request id leave it unknown. --- lib/compat/wordpress-7.1/block-comments.php | 17 ++++-- phpunit/tests/notes-mention-kses-test.php | 57 +++++++++++++++++++++ 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/lib/compat/wordpress-7.1/block-comments.php b/lib/compat/wordpress-7.1/block-comments.php index 1b3aa31ad3ba52..5d49aa48e5a074 100644 --- a/lib/compat/wordpress-7.1/block-comments.php +++ b/lib/compat/wordpress-7.1/block-comments.php @@ -198,9 +198,9 @@ function gutenberg_notes_scope_mention_kses( $commentdata ) { * Arms the mention markup allowance for REST note writes. * * Runs in WP_REST_Comments_Controller::prepare_item_for_database() for both - * creates and updates, before the comment is sanitized. Updates do not carry - * the comment type in the prepared data, so it is resolved from the comment - * being updated. + * creates and updates, before the comment is sanitized. Neither carries the + * comment type in the prepared data, so it is resolved from the comment being + * updated or, on creation, from the request's `type` param. * * @param array $prepared_comment Prepared comment data. * @param WP_REST_Request $request The REST request. @@ -213,6 +213,17 @@ function gutenberg_notes_scope_mention_kses_rest( $prepared_comment, $request ) $comment_type = get_comment_type( (int) $request['id'] ); } + /* + * On creation the controller only copies the `type` param into the + * prepared data after this filter has run, and it inserts through + * wp_filter_comment() directly - never through wp_new_comment() and its + * 'preprocess_comment' filter - so this is the only chance to arm and the + * type must be resolved from the request. + */ + if ( '' === $comment_type && isset( $request['type'] ) ) { + $comment_type = $request['type']; + } + if ( 'note' === $comment_type ) { gutenberg_notes_arm_mention_kses(); } diff --git a/phpunit/tests/notes-mention-kses-test.php b/phpunit/tests/notes-mention-kses-test.php index d4b4aaf0a1c0c6..9e21fec9af1dc4 100644 --- a/phpunit/tests/notes-mention-kses-test.php +++ b/phpunit/tests/notes-mention-kses-test.php @@ -94,6 +94,34 @@ public function test_rest_prepare_arms_for_note_update() { gutenberg_notes_disarm_mention_kses(); } + public function test_rest_prepare_arms_for_note_creation() { + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + $request->set_param( 'type', 'note' ); + + // On creation the prepared data does not carry the comment type either: + // the controller only copies the `type` param in after preparing. + gutenberg_notes_scope_mention_kses_rest( array(), $request ); + + $this->assertNotFalse( + has_filter( 'wp_kses_allowed_html', 'gutenberg_notes_allow_mention_attributes' ), + 'Creating a note should arm the mention allowance.' + ); + + gutenberg_notes_disarm_mention_kses(); + } + + public function test_rest_prepare_does_not_arm_for_regular_comment_creation() { + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + $request->set_param( 'type', 'comment' ); + + gutenberg_notes_scope_mention_kses_rest( array(), $request ); + + $this->assertFalse( + has_filter( 'wp_kses_allowed_html', 'gutenberg_notes_allow_mention_attributes' ), + 'Creating a regular comment should not arm the mention allowance.' + ); + } + public function test_rest_prepare_does_not_arm_for_regular_comment_update() { $post_id = self::factory()->post->create(); $comment_id = self::factory()->comment->create( array( 'comment_post_ID' => $post_id ) ); @@ -134,6 +162,35 @@ public function test_mention_markup_survives_note_insert_end_to_end() { $this->assertSame( self::MENTION_FILTERED, get_comment( $comment_id )->comment_content ); } + public function test_mention_markup_survives_rest_note_creation_end_to_end() { + $author_id = self::factory()->user->create( array( 'role' => 'author' ) ); + $post_id = self::factory()->post->create( array( 'post_author' => $author_id ) ); + + // Authors lack `unfiltered_html`, so comment kses filters their content. + wp_set_current_user( $author_id ); + $this->assertFalse( current_user_can( 'unfiltered_html' ) ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); + $request->set_param( 'post', $post_id ); + $request->set_param( 'type', 'note' ); + $request->set_param( 'content', self::MENTION_CONTENT ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( 201, $response->get_status() ); + + /* + * REST creation reaches kses through wp_filter_comment() directly, not + * through wp_new_comment() and its 'preprocess_comment' filter, so the + * REST-specific arming must cover it or mentions written by users + * without `unfiltered_html` are silently stripped on save. + */ + $data = $response->get_data(); + $this->assertSame( + self::MENTION_FILTERED, + get_comment( (int) $data['id'] )->comment_content + ); + } + /** * Runs commentdata of the given type through the insert-path sanitization. * From bd27d25025429e61f760d8622e32397cb97fab3b Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 13 Jul 2026 22:03:27 -0700 Subject: [PATCH 2/2] Add PR to the notes mentions Core backport changelog entry --- backport-changelog/7.1/12503.md | 1 + 1 file changed, 1 insertion(+) diff --git a/backport-changelog/7.1/12503.md b/backport-changelog/7.1/12503.md index 4c119e8996c703..713a0387fa2a2c 100644 --- a/backport-changelog/7.1/12503.md +++ b/backport-changelog/7.1/12503.md @@ -1,3 +1,4 @@ https://github.com/WordPress/wordpress-develop/pull/12503 * https://github.com/WordPress/gutenberg/pull/79604 +* https://github.com/WordPress/gutenberg/pull/80221