Skip to content

Commit 8cffc95

Browse files
Editor: Add emoji reactions ❤️ 🎉 😄 👀 🚀
Add a `reaction` comment type so users can react to a note with one of five emoji. Each reaction is stored as its own comment under the note, with `comment_content` holding the emoji's hex key rather than raw emoji bytes, since the comments table isn't guaranteed to be utf8mb4. Reactions are created and deleted through the `/wp/v2/comments` endpoint using the note permission checks (`edit_post` on the note's post). They are always approved and authored by the current user, limited to one per emoji per user per note, and cannot be edited. Notes gain a read-only `reaction_summary` field, prefetched in at most two queries per page of notes. Reactions are excluded from the admin comments list, comment counts, `get_lastcommentmodified()`, comment feeds and a note's `children` link, and are trashed and deleted along with their note. Restoring a trashed note does not yet restore its reactions, see WordPress/gutenberg#84202. This brings over the PHP changes from WordPress/gutenberg#76767. Props adamsilverstein, wildworks, westonruter, noruzzaman, mamaduka, swissspidy, joedolson, joen, jeffpaul, andraganescu, mciampini, fcoveram, scruffian, manzoorwanijk, annezazu, ellatrix, jorbin. Fixes #64638. git-svn-id: https://develop.svn.wordpress.org/trunk@64488 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 88912df commit 8cffc95

16 files changed

Lines changed: 2998 additions & 75 deletions

‎src/wp-admin/includes/class-wp-comments-list-table.php‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public function prepare_items() {
107107

108108
$comment_type = '';
109109

110-
if ( ! empty( $_REQUEST['comment_type'] ) && 'note' !== $_REQUEST['comment_type'] ) {
110+
if ( ! empty( $_REQUEST['comment_type'] ) && ! in_array( $_REQUEST['comment_type'], array( 'note', 'reaction' ), true ) ) {
111111
$comment_type = $_REQUEST['comment_type'];
112112
}
113113

@@ -157,7 +157,7 @@ public function prepare_items() {
157157
'number' => $number,
158158
'post_id' => $post_id,
159159
'type' => $comment_type,
160-
'type__not_in' => array( 'note' ),
160+
'type__not_in' => array( 'note', 'reaction' ),
161161
'orderby' => $orderby,
162162
'order' => $order,
163163
'post_type' => $post_type,

‎src/wp-admin/includes/comment.php‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ function get_comment_to_edit( $id ) {
223223
*
224224
* @since 2.3.0
225225
* @since 6.9.0 Exclude the 'note' comment type from the count.
226+
* @since 7.2.0 Exclude the 'reaction' comment type from the count.
226227
*
227228
* @global wpdb $wpdb WordPress database abstraction object.
228229
*
@@ -242,7 +243,7 @@ function get_pending_comments_num( $post_id ) {
242243
$post_id_array = array_map( 'intval', $post_id_array );
243244
$post_id_in = "'" . implode( "', '", $post_id_array ) . "'";
244245

245-
$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 );
246+
$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 );
246247

247248
if ( $single ) {
248249
if ( empty( $pending ) ) {

‎src/wp-includes/class-wp-comment-query.php‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@ public function get_comments() {
549549
*
550550
* @since 4.4.0
551551
* @since 6.9.0 Excludes the 'note' comment type, unless 'all' or the 'note' types are requested.
552+
* @since 7.2.0 Excludes the 'reaction' comment type, unless 'all' or the 'reaction' types are requested.
552553
*
553554
* @global wpdb $wpdb WordPress database abstraction object.
554555
*
@@ -784,13 +785,16 @@ protected function get_comment_ids() {
784785
'NOT IN' => (array) $this->query_vars['type__not_in'],
785786
);
786787

787-
// Exclude the 'note' comment type, unless 'all' types or the 'note' type explicitly are requested.
788-
if (
789-
! in_array( 'all', $raw_types['IN'], true ) &&
790-
! in_array( 'note', $raw_types['IN'], true ) &&
791-
! in_array( 'note', $raw_types['NOT IN'], true )
792-
) {
793-
$raw_types['NOT IN'][] = 'note';
788+
// Exclude the 'note' and 'reaction' comment types, unless 'all' types or that type explicitly are requested.
789+
if ( ! in_array( 'all', $raw_types['IN'], true ) ) {
790+
foreach ( array( 'note', 'reaction' ) as $excluded_type ) {
791+
if (
792+
! in_array( $excluded_type, $raw_types['IN'], true ) &&
793+
! in_array( $excluded_type, $raw_types['NOT IN'], true )
794+
) {
795+
$raw_types['NOT IN'][] = $excluded_type;
796+
}
797+
}
794798
}
795799

796800
$comment_types = array();

‎src/wp-includes/class-wp-query.php‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2818,11 +2818,11 @@ public function get_posts() {
28182818
if ( $this->is_comment_feed && ! $this->is_singular ) {
28192819
if ( $this->is_archive || $this->is_search ) {
28202820
$cjoin = "JOIN {$wpdb->posts} ON ( {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID ) $join ";
2821-
$cwhere = "WHERE comment_approved = '1' AND {$wpdb->comments}.comment_type != 'note' $where";
2821+
$cwhere = "WHERE comment_approved = '1' AND {$wpdb->comments}.comment_type NOT IN ( 'note', 'reaction' ) $where";
28222822
$cgroupby = "{$wpdb->comments}.comment_id";
28232823
} else { // Other non-singular, e.g. front.
28242824
$cjoin = "JOIN {$wpdb->posts} ON ( {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID )";
2825-
$cwhere = "WHERE ( post_status = 'publish' OR ( post_status = 'inherit' AND post_type = 'attachment' ) ) AND comment_approved = '1' AND {$wpdb->comments}.comment_type != 'note'";
2825+
$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' )";
28262826
$cgroupby = '';
28272827
}
28282828

@@ -3539,7 +3539,7 @@ public function get_posts() {
35393539
$cjoin = apply_filters_ref_array( 'comment_feed_join', array( '', &$this ) );
35403540

35413541
/** This filter is documented in wp-includes/class-wp-query.php */
3542-
$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 ) );
3542+
$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 ) );
35433543

35443544
/** This filter is documented in wp-includes/class-wp-query.php */
35453545
$cgroupby = apply_filters_ref_array( 'comment_feed_groupby', array( '', &$this ) );

‎src/wp-includes/comment.php‎

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ function get_default_comment_status( $post_type = 'post', $comment_type = 'comme
401401
* @since 1.5.0
402402
* @since 4.7.0 Replaced caching the modified date in a local static variable
403403
* with the Object Cache API.
404+
* @since 7.2.0 The 'note' and 'reaction' comment types are excluded from the query.
404405
*
405406
* @global wpdb $wpdb WordPress database abstraction object.
406407
*
@@ -420,15 +421,15 @@ function get_lastcommentmodified( $timezone = 'server' ) {
420421

421422
switch ( $timezone ) {
422423
case 'gmt':
423-
$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" );
424+
$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" );
424425
break;
425426
case 'blog':
426-
$comment_modified_date = $wpdb->get_var( "SELECT comment_date FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1" );
427+
$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" );
427428
break;
428429
case 'server':
429430
$add_seconds_server = gmdate( 'Z' );
430431

431-
$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 ) );
432+
$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 ) );
432433
break;
433434
}
434435

@@ -1571,6 +1572,8 @@ function wp_count_comments( $post_id = 0 ) {
15711572
* post ID available.
15721573
*
15731574
* @since 2.0.0
1575+
* @since 7.2.0 A note's reactions are deleted along with it, rather than
1576+
* being reparented.
15741577
*
15751578
* @global wpdb $wpdb WordPress database abstraction object.
15761579
*
@@ -1601,6 +1604,27 @@ function wp_delete_comment( $comment_id, $force_delete = false ) {
16011604
*/
16021605
do_action( 'delete_comment', $comment->comment_ID, $comment );
16031606

1607+
/*
1608+
* Delete a note's reactions rather than letting them be reparented below.
1609+
* A reaction only means anything attached to its note, and an orphaned one
1610+
* would keep the reactor's identity on a note that no longer exists. This
1611+
* covers every status: a reaction the user removed is trashed, not deleted.
1612+
*/
1613+
if ( 'note' === $comment->comment_type ) {
1614+
$reaction_ids = get_comments(
1615+
array(
1616+
'parent' => $comment->comment_ID,
1617+
'type' => 'reaction',
1618+
'status' => 'any',
1619+
'fields' => 'ids',
1620+
)
1621+
);
1622+
1623+
foreach ( $reaction_ids as $reaction_id ) {
1624+
wp_delete_comment( $reaction_id, true );
1625+
}
1626+
}
1627+
16041628
// Move children up a level.
16051629
$children = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_parent = %d", $comment->comment_ID ) );
16061630
if ( ! empty( $children ) ) {
@@ -1651,6 +1675,7 @@ function wp_delete_comment( $comment_id, $force_delete = false ) {
16511675
*
16521676
* @since 2.9.0
16531677
* @since 6.9.0 Any child notes are deleted when deleting a note.
1678+
* @since 7.2.0 A note's reactions are trashed along with it.
16541679
*
16551680
* @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
16561681
* @return bool True on success, false on failure.
@@ -1717,6 +1742,30 @@ function wp_trash_comment( $comment_id ) {
17171742
*/
17181743
do_action( 'trashed_comment', $comment->comment_ID, $comment );
17191744

1745+
/*
1746+
* Trash a note's reactions with it, at any depth. The child-note
1747+
* cascade below trashes each reply in turn, which brings the replies'
1748+
* own reactions along through this same branch.
1749+
*
1750+
* Restoring the note does not bring its reactions back:
1751+
* wp_untrash_comment() restores no children of any type, so restoring
1752+
* children is left to a cascade that covers every child type together.
1753+
*/
1754+
if ( 'note' === $comment->comment_type ) {
1755+
$reaction_ids = get_comments(
1756+
array(
1757+
'parent' => $comment->comment_ID,
1758+
'type' => 'reaction',
1759+
'status' => 'approve',
1760+
'fields' => 'ids',
1761+
)
1762+
);
1763+
1764+
foreach ( $reaction_ids as $reaction_id ) {
1765+
wp_trash_comment( $reaction_id );
1766+
}
1767+
}
1768+
17201769
// For top level 'note' type comments, also trash children.
17211770
if ( 'note' === $comment->comment_type && 0 === (int) $comment->comment_parent ) {
17221771
$children = $comment->get_children(
@@ -3159,7 +3208,7 @@ function wp_update_comment_count_now( $post_id ) {
31593208
$new = apply_filters( 'pre_wp_update_comment_count_now', null, $old, $post_id );
31603209

31613210
if ( is_null( $new ) ) {
3162-
$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 ) );
3211+
$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 ) );
31633212
} else {
31643213
$new = (int) $new;
31653214
}

‎src/wp-includes/link-template.php‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4378,10 +4378,12 @@ function is_avatar_comment_type( $comment_type ) {
43784378
* @since 3.0.0
43794379
*
43804380
* @since 6.9.0 The 'note' comment type was added.
4381+
* @since 7.2.0 The 'reaction' comment type was added.
43814382
*
4382-
* @param array $types An array of content types. Default contains 'comment' and 'note'.
4383+
* @param array $types An array of content types. Default contains 'comment', 'note',
4384+
* and 'reaction'.
43834385
*/
4384-
$allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment', 'note' ) );
4386+
$allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment', 'note', 'reaction' ) );
43854387

43864388
return in_array( $comment_type, (array) $allowed_comment_types, true );
43874389
}

0 commit comments

Comments
 (0)