Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
fe19243
Editor: Add emoji reactions as a comment type for Notes.
adamsilverstein Feb 18, 2026
82a98f7
Merge branch 'trunk' into backport-note-reactions-meta-70
adamsilverstein Feb 18, 2026
b3b78f8
Tests: Regenerate wp-api-generated.js fixtures after trunk merge.
adamsilverstein Apr 24, 2026
059480d
Merge branch 'trunk' into backport-note-reactions-meta-70
adamsilverstein Apr 29, 2026
ec10e92
Comments: Centralize internal comment types via wp_get_internal_comme…
adamsilverstein Apr 29, 2026
7a83b58
Apply suggestions from code review
adamsilverstein Apr 29, 2026
f3d39d0
Comments: Add wp_get_note_reaction_emojis() helper.
adamsilverstein May 11, 2026
8fec4c9
REST API: Expand reaction support in comments controller.
adamsilverstein May 11, 2026
b6923e7
REST API: Test expanded reaction support in comments controller.
adamsilverstein May 11, 2026
7c53824
Coding Standards: Fix alignment and PHPCS ignores in reaction support.
adamsilverstein May 11, 2026
f3b5af2
Editor: Address review feedback on note reactions backport.
adamsilverstein May 15, 2026
dd9b750
REST API: Harden reaction validation and canonicalize stored slugs.
adamsilverstein Jul 14, 2026
417c5bf
REST API: Converge concurrent duplicate reactions to a single row.
adamsilverstein Jul 14, 2026
df707e4
Comments: Delete and trash a note's reactions along with the note.
adamsilverstein Aug 26, 2026
4f1c947
REST API: Restrict reaction writes to create and delete.
adamsilverstein Aug 26, 2026
a892699
Comments: Target reactions at 7.2.0.
adamsilverstein Aug 26, 2026
c2bc4f7
REST API: Pin a reaction to the user who adds it.
adamsilverstein Aug 26, 2026
aae9545
Comments: Delete a note's trashed reactions along with it.
adamsilverstein Aug 26, 2026
e8efed9
REST API: Accept hex codepoint reaction slugs again.
adamsilverstein Aug 27, 2026
fd3e2d4
Merge remote-tracking branch 'origin/trunk' into backport-note-reacti…
adamsilverstein Aug 27, 2026
34a802e
Merge remote-tracking branch 'origin/trunk' into backport-note-reacti…
adamsilverstein Sep 29, 2026
6f0a928
Feeds: Keep reactions out of comment feeds.
adamsilverstein Sep 29, 2026
28c8dae
Comments: Restore only the reactions trashed along with a note.
adamsilverstein Sep 29, 2026
877868b
REST API: Point a note's children link back at its reply notes.
adamsilverstein Sep 29, 2026
82256de
REST API: Accept the object form of content when adding a reaction.
adamsilverstein Sep 29, 2026
86f307b
REST API: Only create reactions in the approved status.
adamsilverstein Sep 29, 2026
fd48524
REST API: Simplify the note reaction_summary entry shape.
adamsilverstein Oct 6, 2026
7aedba7
Apply suggestion from @westonruter
adamsilverstein Oct 7, 2026
c15e3ff
Comments: Make the internal comment types helper private.
adamsilverstein Oct 7, 2026
e164e32
Apply suggestion from @westonruter
adamsilverstein Oct 7, 2026
56fee63
Merge remote-tracking branch 'origin/trunk' into backport-note-reacti…
adamsilverstein Oct 7, 2026
611a0ef
REST API: Accept only the curated emoji hex keys for note reactions.
adamsilverstein Oct 7, 2026
6ef4ee7
REST API: Restrict reaction removal and reactions on closed notes.
adamsilverstein Oct 7, 2026
4a38052
Comments: Stop restoring a note's reactions when the note is untrashed.
adamsilverstein Oct 7, 2026
2be8091
REST API: Check a reaction's post before its parent note's status.
adamsilverstein Oct 7, 2026
92b0795
Tests: Cover reaction read access and paged reaction summaries.
adamsilverstein Oct 7, 2026
6ba5c7b
Merge branch 'trunk' into backport-note-reactions-meta-70
adamsilverstein Oct 8, 2026
cd5e385
Editor: Add a filter for the emoji a note can be reacted with.
adamsilverstein Oct 8, 2026
aa46398
Tests: Cover the note reaction emoji editor setting in the block edit…
adamsilverstein Oct 8, 2026
ba90b58
Tests: Point the note reaction emoji tests at their own ticket.
adamsilverstein Oct 9, 2026
ff65798
Comments: Inline the note and reaction types instead of a helper.
adamsilverstein Oct 9, 2026
b08b3a3
Tests: Restore the blog description values in the REST API fixture.
adamsilverstein Oct 9, 2026
b7c5604
Merge remote-tracking branch 'fork/backport-note-reactions-meta-70' i…
adamsilverstein Oct 9, 2026
d4eeff0
Tests: Use the reactions ticket number in the note reaction tests.
adamsilverstein Oct 9, 2026
5148dd1
Merge remote-tracking branch 'fork/backport-note-reactions-meta-70' i…
adamsilverstein Oct 9, 2026
32a74f3
Merge branch 'trunk' into backport-note-reaction-emoji-filter
adamsilverstein Nov 8, 2026
4d20c22
Merge remote-tracking branch 'origin/trunk' into backport-note-reacti…
adamsilverstein Nov 11, 2026
616c4a9
Comments: Document the note reaction emoji shape and narrow the key t…
adamsilverstein Nov 11, 2026
6405e13
Comments: Target the note reaction emoji filter at 7.3.0.
adamsilverstein Nov 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/wp-includes/block-editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,9 @@ function get_block_editor_settings( array $custom_settings, $block_editor_contex

$editor_settings['canUpdateBlockBindings'] = current_user_can( 'edit_block_binding', $block_editor_context );

// Lets the Notes reaction menu offer the same emoji the REST API accepts.
$editor_settings['noteReactionEmojis'] = wp_get_note_reaction_emojis();

/**
* Filters the settings to pass to the block editor for all editor type.
*
Expand Down
152 changes: 152 additions & 0 deletions src/wp-includes/comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,158 @@ function get_comment_statuses() {
return $status;
}

/**
* Normalizes an emoji hex key to the form note reactions are stored under.
*
* Lowercases the code points, pads each to four digits and drops U+FE0F, so
* `2764-FE0F`, `2764` and `2764-fe0f` all become `2764`.
*
* @since 7.3.0
*
* @param mixed $hex_key The key to normalize.
* @return string The normalized key, or an empty string when it is not a
* sequence of valid Unicode code points.
*
* @phpstan-return lowercase-string
*/
function wp_normalize_note_reaction_key( $hex_key ): string {
if ( ! is_string( $hex_key ) || ! preg_match( '/^[0-9a-f]{1,6}(?:-[0-9a-f]{1,6})*$/i', $hex_key ) ) {
return '';
}

$code_points = array();
foreach ( explode( '-', strtolower( $hex_key ) ) as $code_point ) {
$value = (int) hexdec( $code_point );
// Surrogates and values past U+10FFFF are not code points.
if ( $value > 0x10FFFF || ( $value >= 0xD800 && $value <= 0xDFFF ) ) {
return '';
}
if ( 0xFE0F === $value ) {
continue;
}
$code_points[] = str_pad( ltrim( $code_point, '0' ), 4, '0', STR_PAD_LEFT );
}

return implode( '-', $code_points );
}

/**
* Retrieves the emoji a note can be reacted with.
*
* @since 7.3.0
*
* @return array[] {
* The emoji, in display order.
*
* @type array ...$0 {
* @type string $hexKey The emoji's hex key, e.g. `2764`.
* @type string $label The emoji's name, lowercase, e.g. `heart`.
* }
* }
*
* @phpstan-return list<array{ hexKey: lowercase-string&non-empty-string, label: string }>
*/
function wp_get_note_reaction_emojis(): array {
// Labels are lowercase since they also appear mid-sentence, e.g. in the reaction pill's tooltip.
$default_emojis = array(
array(
'hexKey' => '1f44d',
'label' => _x( 'thumbs up', 'emoji reaction' ),
),
array(
'hexKey' => '2795',
'label' => _x( 'plus', 'emoji reaction' ),
),
array(
'hexKey' => '2764',
'label' => _x( 'heart', 'emoji reaction' ),
),
array(
'hexKey' => '1f389',
'label' => _x( 'celebration', 'emoji reaction' ),
),
array(
'hexKey' => '1f604',
'label' => _x( 'smile', 'emoji reaction' ),
),
array(
'hexKey' => '1f440',
'label' => _x( 'eyes', 'emoji reaction' ),
),
array(
'hexKey' => '1f680',
'label' => _x( 'rocket', 'emoji reaction' ),
),
);

/**
* Filters the emoji a note can be reacted with.
*
* Add entries to offer more emoji, or remove them to offer fewer. An
* empty list turns off adding reactions; existing reactions still show
* and can be removed by their authors.
*
* Each emoji is identified by its hex key: its code points in hex,
* joined by `-` (e.g. `1f984` for 🦄, `1f468-200d-1f4bb` for 👨‍💻).
* U+FE0F is dropped and case does not matter. The editor renders each
* emoji as text, so pick emoji the fonts on your users' systems can
* display: newer emoji and flags do not render everywhere.
*
* @since 7.3.0
*
* @param array[] $emojis {
* The emoji to offer, in display order.
*
* @type array ...$0 {
* @type string $hexKey The emoji's hex key, e.g. `1f984`.
* @type string $label The emoji's name, lowercase, e.g. `unicorn`.
* }
* }
*/
$emojis = apply_filters( 'wp_note_reaction_emojis', $default_emojis );

if ( ! is_array( $emojis ) ) {
return $default_emojis;
}

// Drop malformed entries and duplicate keys, keeping the first label.
$sanitized = array();
foreach ( $emojis as $emoji ) {
if ( ! is_array( $emoji ) || ! isset( $emoji['hexKey'], $emoji['label'] ) || ! is_string( $emoji['label'] ) ) {
continue;
}

$hex_key = wp_normalize_note_reaction_key( $emoji['hexKey'] );
$label = sanitize_text_field( $emoji['label'] );
if ( '' === $hex_key || '' === $label || isset( $sanitized[ $hex_key ] ) ) {
continue;
}

$sanitized[ $hex_key ] = array(
'hexKey' => $hex_key,
'label' => $label,
);
}

return array_values( $sanitized );
}

/**
* Retrieves the hex keys of the emoji a note reaction accepts.
*
* Each key is the emoji's lowercase code points, padded to four digits and
* joined by `-`. A reaction stores its key in `comment_content`.
*
* @since 7.3.0
*
* @return string[] The keys of the emoji from wp_get_note_reaction_emojis().
*
* @phpstan-return list<lowercase-string&non-empty-string>
*/
function wp_get_note_reaction_keys(): array {
return array_column( wp_get_note_reaction_emojis(), 'hexKey' );
}

/**
* Gets the default comment status for a post type.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,6 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
*/
protected $reaction_summaries = null;

/**
* Retrieves the hex keys of the emoji a note reaction accepts.
*
* Each key is the emoji's lowercase code points, padded to four digits,
* matching the client's `emojiToHexKey()`. A reaction stores its key in
* `comment_content`.
*
* @since 7.2.0
*
* @return string[] Hex keys for thumbs up, plus, heart, celebration, smile, eyes and rocket.
*
* @phpstan-return non-empty-list<lowercase-string&non-falsy-string>
*/
private static function get_note_reaction_keys(): array {
return array( '1f44d', '2795', '2764', '1f389', '1f604', '1f440', '1f680' );
}

/**
* Constructor.
*
Expand Down Expand Up @@ -796,11 +779,11 @@ public function create_item( $request ) {
}

/*
* Validate the reaction content: the hex key of one of the curated
* reaction emoji, as listed by self::get_note_reaction_keys() (e.g.
* `2764` for the heart). Raw emoji bytes are rejected because the
* comments table is not guaranteed to be utf8mb4 across all WordPress
* installs; clients are expected to normalize before submitting.
* Validate the reaction content: the hex key of one of the reaction
* emoji, as listed by wp_get_note_reaction_keys() (e.g. `2764` for
* the heart). Raw emoji bytes are rejected because the comments
* table is not guaranteed to be utf8mb4 across all WordPress installs;
* clients are expected to normalize before submitting.
*
* Read the content the same two ways prepare_item_for_database()
* does, so `content` and `content.raw` are both accepted.
Expand All @@ -814,7 +797,7 @@ public function create_item( $request ) {

$emoji_key = trim( wp_strip_all_tags( $raw_content ) );

if ( ! in_array( $emoji_key, self::get_note_reaction_keys(), true ) ) {
if ( ! in_array( $emoji_key, wp_get_note_reaction_keys(), true ) ) {
return new WP_Error(
'rest_comment_invalid_reaction',
__( 'Invalid reaction emoji.' ),
Expand Down
15 changes: 15 additions & 0 deletions tests/phpunit/tests/blocks/editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,21 @@ public function test_get_legacy_widget_block_editor_settings() {
);
}

/**
* The note reaction menu offers the same emoji the REST API accepts.
*
* @ticket 66276
*
* @covers ::get_block_editor_settings
*/
public function test_get_block_editor_settings_includes_note_reaction_emojis(): void {
add_filter( 'wp_note_reaction_emojis', '__return_empty_array' );

$settings = get_block_editor_settings( array(), new WP_Block_Editor_Context() );

$this->assertSame( array(), $settings['noteReactionEmojis'] );
}

/**
* @ticket 52920
*/
Expand Down
155 changes: 155 additions & 0 deletions tests/phpunit/tests/comment/wpGetNoteReactionEmojis.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<?php

/**
* Tests for the filterable list of note reaction emoji.
*
* @group comment
* @group notes
*
* @covers ::wp_get_note_reaction_emojis
* @covers ::wp_get_note_reaction_keys
* @covers ::wp_normalize_note_reaction_key
*/
class Tests_Comment_WpGetNoteReactionEmojis extends WP_UnitTestCase {

/**
* @ticket 66276
*/
public function test_defaults_to_the_seven_quick_reactions(): void {
$this->assertSame(
array( '1f44d', '2795', '2764', '1f389', '1f604', '1f440', '1f680' ),
wp_get_note_reaction_keys()
);
}

/**
* @ticket 66276
*/
public function test_filter_can_add_and_remove_emoji(): void {
add_filter(
'wp_note_reaction_emojis',
static function ( array $emojis ): array {
$emojis[] = array(
'hexKey' => '1f984',
'label' => 'unicorn',
);
return array_slice( $emojis, 1 );
}
);

$this->assertSame(
array( '2795', '2764', '1f389', '1f604', '1f440', '1f680', '1f984' ),
wp_get_note_reaction_keys()
);
}

/**
* @ticket 66276
*/
public function test_filter_can_remove_every_emoji(): void {
add_filter( 'wp_note_reaction_emojis', '__return_empty_array' );

$this->assertSame( array(), wp_get_note_reaction_emojis() );
}

/**
* @ticket 66276
*/
public function test_falls_back_to_the_defaults_when_the_filter_returns_no_list(): void {
add_filter( 'wp_note_reaction_emojis', '__return_null' );

$this->assertCount( 7, wp_get_note_reaction_emojis() );
}

/**
* @ticket 66276
*/
public function test_drops_malformed_and_duplicate_entries(): void {
add_filter(
'wp_note_reaction_emojis',
static function () {
return array(
// Normalized: lowercase, padded, U+FE0F dropped.
array(
'hexKey' => '2764-FE0F',
'label' => 'heart',
),
// The same key again.
array(
'hexKey' => '2764',
'label' => 'red heart',
),
array(
'hexKey' => '1F468-200D-1F4BB',
'label' => '<b>technologist</b>',
),
array(
'hexKey' => 'not-hex',
'label' => 'broken',
),
array(
'hexKey' => 'd83d',
'label' => 'surrogate',
),
array(
'hexKey' => '110000',
'label' => 'past U+10FFFF',
),
array(
'hexKey' => '1f984',
'label' => '',
),
array( 'hexKey' => '1f680' ),
'1f389',
);
}
);

$this->assertSame(
array(
array(
'hexKey' => '2764',
'label' => 'heart',
),
array(
'hexKey' => '1f468-200d-1f4bb',
'label' => 'technologist',
),
),
wp_get_note_reaction_emojis()
);
}

/**
* @ticket 66276
*
* @dataProvider data_normalize_note_reaction_key
*
* @param mixed $hex_key The key to normalize.
* @param string $expected The normalized key.
*/
public function test_normalize_note_reaction_key( $hex_key, $expected ): void {
$this->assertSame( $expected, wp_normalize_note_reaction_key( $hex_key ) );
}

/**
* Data provider.
*
* @return array<string, array{mixed, string}>
*/
public static function data_normalize_note_reaction_key(): array {
return array(
'already normalized' => array( '1f984', '1f984' ),
'uppercase' => array( '1F984', '1f984' ),
'short code point' => array( '23', '0023' ),
'variation selector' => array( '2764-fe0f', '2764' ),
'ZWJ sequence' => array( '1f468-200d-1f4bb', '1f468-200d-1f4bb' ),
'surrogate' => array( 'd83d', '' ),
'past U+10FFFF' => array( '110000', '' ),
'not hex' => array( 'heart', '' ),
'trailing separator' => array( '2764-', '' ),
'not a string' => array( 0x2764, '' ),
'empty' => array( '', '' ),
);
}
}
Loading
Loading