Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 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
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
d4eeff0
Tests: Use the reactions ticket number in the note reaction tests.
adamsilverstein Oct 9, 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
4 changes: 2 additions & 2 deletions src/wp-admin/includes/class-wp-comments-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public function prepare_items() {

$comment_type = '';

if ( ! empty( $_REQUEST['comment_type'] ) && 'note' !== $_REQUEST['comment_type'] ) {
if ( ! empty( $_REQUEST['comment_type'] ) && ! in_array( $_REQUEST['comment_type'], array( 'note', 'reaction' ), true ) ) {
$comment_type = $_REQUEST['comment_type'];
}

Expand Down Expand Up @@ -157,7 +157,7 @@ public function prepare_items() {
'number' => $number,
'post_id' => $post_id,
'type' => $comment_type,
'type__not_in' => array( 'note' ),
'type__not_in' => array( 'note', 'reaction' ),
'orderby' => $orderby,
'order' => $order,
'post_type' => $post_type,
Expand Down
3 changes: 2 additions & 1 deletion src/wp-admin/includes/comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ function get_comment_to_edit( $id ) {
*
* @since 2.3.0
* @since 6.9.0 Exclude the 'note' comment type from the count.
* @since 7.2.0 Exclude the 'reaction' comment type from the count.
*
* @global wpdb $wpdb WordPress database abstraction object.
*
Expand All @@ -242,7 +243,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' AND comment_type != 'note' 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 NOT IN ( 'note', 'reaction' ) GROUP BY comment_post_ID", ARRAY_A );

if ( $single ) {
if ( empty( $pending ) ) {
Expand Down
18 changes: 11 additions & 7 deletions src/wp-includes/class-wp-comment-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ public function get_comments() {
*
* @since 4.4.0
* @since 6.9.0 Excludes the 'note' comment type, unless 'all' or the 'note' types are requested.
* @since 7.2.0 Excludes the 'reaction' comment type, unless 'all' or the 'reaction' types are requested.
*
* @global wpdb $wpdb WordPress database abstraction object.
*
Expand Down Expand Up @@ -784,13 +785,16 @@ protected function get_comment_ids() {
'NOT IN' => (array) $this->query_vars['type__not_in'],
);

// Exclude the 'note' comment type, unless 'all' types or the 'note' type explicitly are requested.
if (
! in_array( 'all', $raw_types['IN'], true ) &&
! in_array( 'note', $raw_types['IN'], true ) &&
! in_array( 'note', $raw_types['NOT IN'], true )
) {
$raw_types['NOT IN'][] = 'note';
// Exclude the 'note' and 'reaction' comment types, unless 'all' types or that type explicitly are requested.
if ( ! in_array( 'all', $raw_types['IN'], true ) ) {
foreach ( array( 'note', 'reaction' ) as $excluded_type ) {
if (
! in_array( $excluded_type, $raw_types['IN'], true ) &&
! in_array( $excluded_type, $raw_types['NOT IN'], true )
) {
$raw_types['NOT IN'][] = $excluded_type;
}
}
}

$comment_types = array();
Expand Down
6 changes: 3 additions & 3 deletions src/wp-includes/class-wp-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -2803,11 +2803,11 @@ public function get_posts() {
if ( $this->is_comment_feed && ! $this->is_singular ) {
if ( $this->is_archive || $this->is_search ) {
$cjoin = "JOIN {$wpdb->posts} ON ( {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID ) $join ";
$cwhere = "WHERE comment_approved = '1' AND {$wpdb->comments}.comment_type != 'note' $where";
$cwhere = "WHERE comment_approved = '1' AND {$wpdb->comments}.comment_type NOT IN ( 'note', 'reaction' ) $where";
$cgroupby = "{$wpdb->comments}.comment_id";
} else { // Other non-singular, e.g. front.
$cjoin = "JOIN {$wpdb->posts} ON ( {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID )";
$cwhere = "WHERE ( post_status = 'publish' OR ( post_status = 'inherit' AND post_type = 'attachment' ) ) AND comment_approved = '1' AND {$wpdb->comments}.comment_type != 'note'";
$cwhere = "WHERE ( post_status = 'publish' OR ( post_status = 'inherit' AND post_type = 'attachment' ) ) AND comment_approved = '1' AND {$wpdb->comments}.comment_type NOT IN ( 'note', 'reaction' )";
$cgroupby = '';
}

Expand Down Expand Up @@ -3524,7 +3524,7 @@ public function get_posts() {
$cjoin = apply_filters_ref_array( 'comment_feed_join', array( '', &$this ) );

/** This filter is documented in wp-includes/class-wp-query.php */
$cwhere = apply_filters_ref_array( 'comment_feed_where', array( "WHERE comment_post_ID = '{$this->posts[0]->ID}' AND comment_approved = '1' AND {$wpdb->comments}.comment_type != 'note'", &$this ) );
$cwhere = apply_filters_ref_array( 'comment_feed_where', array( "WHERE comment_post_ID = '{$this->posts[0]->ID}' AND comment_approved = '1' AND {$wpdb->comments}.comment_type NOT IN ( 'note', 'reaction' )", &$this ) );

/** This filter is documented in wp-includes/class-wp-query.php */
$cgroupby = apply_filters_ref_array( 'comment_feed_groupby', array( '', &$this ) );
Expand Down
69 changes: 65 additions & 4 deletions src/wp-includes/comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,40 @@ function get_comment_statuses() {
return $status;
}

/**
* Retrieves the IDs of a note's reaction comments.
*
* Reactions hang off a note as child comments, so they have to be trashed
* and deleted along with it.
*
* @since 7.2.0
*
* @param int|WP_Comment $comment_id Note comment ID or WP_Comment object.
* @param string $status Optional. Comment status to match, as accepted by
* WP_Comment_Query. Note that 'all' covers only approved
* and pending comments, so trashed reactions need an
* explicit status. Default 'any'.
* @return int[] Reaction comment IDs, oldest first. Empty if the comment is not a note.
*/
function wp_get_note_reaction_ids( $comment_id, $status = 'any' ): array {
$comment = get_comment( $comment_id );

if ( ! $comment || 'note' !== $comment->comment_type ) {
return array();
}

return get_comments(
array(
'parent' => $comment->comment_ID,
'type' => 'reaction',
'status' => $status,
'fields' => 'ids',
'orderby' => 'comment_ID',
'order' => 'ASC',
)
);
}

/**
* Gets the default comment status for a post type.
*
Expand Down Expand Up @@ -401,6 +435,7 @@ function get_default_comment_status( $post_type = 'post', $comment_type = 'comme
* @since 1.5.0
* @since 4.7.0 Replaced caching the modified date in a local static variable
* with the Object Cache API.
* @since 7.2.0 The 'note' and 'reaction' comment types are excluded from the query.
*
* @global wpdb $wpdb WordPress database abstraction object.
*
Expand All @@ -420,15 +455,15 @@ function get_lastcommentmodified( $timezone = 'server' ) {

switch ( $timezone ) {
case 'gmt':
$comment_modified_date = $wpdb->get_var( "SELECT comment_date_gmt FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1" );
$comment_modified_date = $wpdb->get_var( "SELECT comment_date_gmt FROM $wpdb->comments WHERE comment_approved = '1' AND comment_type NOT IN ( 'note', 'reaction' ) ORDER BY comment_date_gmt DESC LIMIT 1" );
break;
case 'blog':
$comment_modified_date = $wpdb->get_var( "SELECT comment_date FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1" );
$comment_modified_date = $wpdb->get_var( "SELECT comment_date FROM $wpdb->comments WHERE comment_approved = '1' AND comment_type NOT IN ( 'note', 'reaction' ) ORDER BY comment_date_gmt DESC LIMIT 1" );
break;
case 'server':
$add_seconds_server = gmdate( 'Z' );

$comment_modified_date = $wpdb->get_var( $wpdb->prepare( "SELECT DATE_ADD(comment_date_gmt, INTERVAL %s SECOND) FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1", $add_seconds_server ) );
$comment_modified_date = $wpdb->get_var( $wpdb->prepare( "SELECT DATE_ADD(comment_date_gmt, INTERVAL %s SECOND) FROM $wpdb->comments WHERE comment_approved = '1' AND comment_type NOT IN ( 'note', 'reaction' ) ORDER BY comment_date_gmt DESC LIMIT 1", $add_seconds_server ) );
break;
}

Expand Down Expand Up @@ -1571,6 +1606,8 @@ function wp_count_comments( $post_id = 0 ) {
* post ID available.
*
* @since 2.0.0
* @since 7.2.0 A note's reactions are deleted along with it, rather than
* being reparented.
*
* @global wpdb $wpdb WordPress database abstraction object.
*
Expand Down Expand Up @@ -1601,6 +1638,16 @@ function wp_delete_comment( $comment_id, $force_delete = false ) {
*/
do_action( 'delete_comment', $comment->comment_ID, $comment );

/*
* Delete a note's reactions rather than letting them be reparented below.
* A reaction only means anything attached to its note, and an orphaned one
* would keep the reactor's identity on a note that no longer exists. This
* covers every status: a reaction the user removed is trashed, not deleted.
*/
foreach ( wp_get_note_reaction_ids( $comment ) as $reaction_id ) {
wp_delete_comment( $reaction_id, true );
}

// Move children up a level.
$children = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_parent = %d", $comment->comment_ID ) );
if ( ! empty( $children ) ) {
Expand Down Expand Up @@ -1651,6 +1698,7 @@ function wp_delete_comment( $comment_id, $force_delete = false ) {
*
* @since 2.9.0
* @since 6.9.0 Any child notes are deleted when deleting a note.
* @since 7.2.0 A note's reactions are trashed along with it.
*
* @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
* @return bool True on success, false on failure.
Expand Down Expand Up @@ -1717,6 +1765,19 @@ function wp_trash_comment( $comment_id ) {
*/
do_action( 'trashed_comment', $comment->comment_ID, $comment );

/*
* Trash a note's reactions with it, at any depth. The child-note
* cascade below trashes each reply in turn, which brings the replies'
* own reactions along through this same branch.
*
* Restoring the note does not bring its reactions back:
* wp_untrash_comment() restores no children of any type, so restoring
* children is left to a cascade that covers every child type together.
*/
foreach ( wp_get_note_reaction_ids( $comment, 'approve' ) as $reaction_id ) {
wp_trash_comment( $reaction_id );
}

// For top level 'note' type comments, also trash children.
if ( 'note' === $comment->comment_type && 0 === (int) $comment->comment_parent ) {
$children = $comment->get_children(
Expand Down Expand Up @@ -3159,7 +3220,7 @@ function wp_update_comment_count_now( $post_id ) {
$new = apply_filters( 'pre_wp_update_comment_count_now', null, $old, $post_id );

if ( is_null( $new ) ) {
$new = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1' AND comment_type != 'note'", $post_id ) );
$new = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1' AND comment_type NOT IN ( 'note', 'reaction' )", $post_id ) );
} else {
$new = (int) $new;
}
Expand Down
6 changes: 4 additions & 2 deletions src/wp-includes/link-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -4378,10 +4378,12 @@ function is_avatar_comment_type( $comment_type ) {
* @since 3.0.0
*
* @since 6.9.0 The 'note' comment type was added.
* @since 7.2.0 The 'reaction' comment type was added.
*
* @param array $types An array of content types. Default contains 'comment' and 'note'.
* @param array $types An array of content types. Default contains 'comment', 'note',
* and 'reaction'.
*/
$allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment', 'note' ) );
$allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment', 'note', 'reaction' ) );

return in_array( $comment_type, (array) $allowed_comment_types, true );
}
Expand Down
Loading
Loading