diff --git a/wp-comments-post.php b/wp-comments-post.php index 06cbd460f4..19cf0a34df 100644 --- a/wp-comments-post.php +++ b/wp-comments-post.php @@ -22,38 +22,55 @@ require __DIR__ . '/wp-load.php'; nocache_headers(); -$comment = wp_handle_comment_submission( wp_unslash( $_POST ) ); -if ( is_wp_error( $comment ) ) { - $data = (int) $comment->get_error_data(); - if ( ! empty( $data ) ) { +if ( isset( $_POST['wp-comment-approved-notification-optin'], $_POST['comment_ID'], $_POST['moderation-hash'] ) ) { + $comment = get_comment( $_POST['comment_ID'] ); + + if ( $comment && hash_equals( $_POST['moderation-hash'], wp_hash( $comment->comment_date_gmt ) ) ) { + update_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin', true ); + } else { wp_die( - '

' . $comment->get_error_message() . '

', - __( 'Comment Submission Failure' ), + '

' . __( 'Invalid comment ID.' ) . '

', + __( 'Comment Notification Opt-in Failure' ), array( - 'response' => $data, + 'response' => 404, 'back_link' => true, ) ); - } else { - exit; } +} else { + $comment = wp_handle_comment_submission( wp_unslash( $_POST ) ); + if ( is_wp_error( $comment ) ) { + $data = (int) $comment->get_error_data(); + if ( ! empty( $data ) ) { + wp_die( + '

' . $comment->get_error_message() . '

', + __( 'Comment Submission Failure' ), + array( + 'response' => $data, + 'back_link' => true, + ) + ); + } else { + exit; + } + } + + $user = wp_get_current_user(); + $cookies_consent = ( isset( $_POST['wp-comment-cookies-consent'] ) ); + + /** + * Perform other actions when comment cookies are set. + * + * @since 3.4.0 + * @since 4.9.6 The `$cookies_consent` parameter was added. + * + * @param WP_Comment $comment Comment object. + * @param WP_User $user Comment author's user object. The user may not exist. + * @param bool $cookies_consent Comment author's consent to store cookies. + */ + do_action( 'set_comment_cookies', $comment, $user, $cookies_consent ); } -$user = wp_get_current_user(); -$cookies_consent = ( isset( $_POST['wp-comment-cookies-consent'] ) ); - -/** - * Perform other actions when comment cookies are set. - * - * @since 3.4.0 - * @since 4.9.6 The `$cookies_consent` parameter was added. - * - * @param WP_Comment $comment Comment object. - * @param WP_User $user Comment author's user object. The user may not exist. - * @param bool $cookies_consent Comment author's consent to store cookies. - */ -do_action( 'set_comment_cookies', $comment, $user, $cookies_consent ); - $location = empty( $_POST['redirect_to'] ) ? get_comment_link( $comment ) : $_POST['redirect_to'] . '#comment-' . $comment->comment_ID; // If user didn't consent to cookies, add specific query arguments to display the awaiting moderation message. diff --git a/wp-includes/class-walker-comment.php b/wp-includes/class-walker-comment.php index 9df2d20b3b..a1bf5d48fe 100644 --- a/wp-includes/class-walker-comment.php +++ b/wp-includes/class-walker-comment.php @@ -40,6 +40,16 @@ class Walker_Comment extends Walker { 'id' => 'comment_ID', ); + /** + * Whether the comment approval notification opt-in form or message needs to be + * output automatically. + * + * @since 5.7.0 + * + * @var bool + */ + protected $needs_comment_approval_notification_output = true; + /** * Starts the list before the elements are added. * @@ -255,10 +265,13 @@ class Walker_Comment extends Walker { /** * Filters the comment text. * - * Removes links from the pending comment's text if the commenter did not consent - * to the comment cookies. + * - Removes links from the pending comment's text if the commenter did not consent + * to the comment cookies + * - Prepends the approval notification opt-in form or message to pending comments * * @since 5.4.2 + * @since 5.7.0 Comment approval notification opt-in form is now automatically + * appended if necessary. * * @param string $comment_text Text of the current comment. * @param WP_Comment|null $comment The comment object. Null if not found. @@ -272,9 +285,90 @@ class Walker_Comment extends Walker { $comment_text = wp_kses( $comment_text, array() ); } + /* + * Checks if we need to output the comment approval notification opt-in form. + */ + if ( $this->needs_comment_approval_notification_output ) { + $comment_text = $this->comment_approval_notification_form( $comment ) . "\n" . $comment_text; + } + + $this->needs_comment_approval_notification_output = true; + return $comment_text; } + /** + * Outputs the awaiting moderation text. + * + * @since 5.7.0 + * + * @param WP_Comment $comment Comment to display. + */ + protected function awaiting_moderation_text( $comment ) { + if ( '0' !== $comment->comment_approved ) { + return; + } + + $commenter = wp_get_current_commenter(); + + if ( $commenter['comment_author_email'] ) { + $moderation_note = __( 'Your comment is awaiting moderation.' ); + } else { + $moderation_note = __( 'Your comment is awaiting moderation. This is a preview, your comment will be visible after it has been approved.' ); + } + + printf( + '%s', + esc_html( $moderation_note ) + ); + } + + /** + * Gets the comment approval notification opt-in form or message for a pending comment. + * + * @since 5.7.0 + * + * @param WP_Comment $comment Comment to display. + * @return string HTML output. + */ + protected function comment_approval_notification_form( $comment ) { + $comment_approval_output = ''; + + if ( '0' === $comment->comment_approved && has_action( 'comment_unapproved_to_approved', 'wp_new_comment_notify_comment_author' ) ) { + if ( get_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin', true ) ) { + $comment_approval_output = sprintf( + '

%s

', + esc_html__( 'You will receive an email when your comment is approved.' ) + ); + } else { + $comment_approval_output = sprintf( + '
+

+ + +

+ + + +
', + esc_url( site_url( '/wp-comments-post.php' ) ), + esc_html__( 'I want to be notified by email when my comment is approved.' ), + absint( $comment->comment_ID ), + wp_hash( $comment->comment_date_gmt ), + esc_html_x( 'Save', 'comment approval notification form' ) + ); + } + } + + // Disable the backcompat output. + $this->needs_comment_approval_notification_output = false; + + // Return the approval notification opt-in form. + return $comment_approval_output; + } + /** * Outputs a single comment. * @@ -297,12 +391,6 @@ class Walker_Comment extends Walker { $commenter = wp_get_current_commenter(); $show_pending_links = isset( $commenter['comment_author'] ) && $commenter['comment_author']; - - if ( $commenter['comment_author_email'] ) { - $moderation_note = __( 'Your comment is awaiting moderation.' ); - } else { - $moderation_note = __( 'Your comment is awaiting moderation. This is a preview; your comment will be visible after it has been approved.' ); - } ?> < has_children ? 'parent' : '', $comment ); ?> id="comment-"> @@ -328,10 +416,14 @@ class Walker_Comment extends Walker { ); ?> - comment_approved ) : ?> - -
- + + awaiting_moderation_text( $comment ); + + // Output the comment approval notification opt-in form if needed. + echo $this->comment_approval_notification_form( $comment ); + ?>
< id="comment-" has_children ? 'parent' : '', $comment ); ?>>
@@ -450,9 +536,13 @@ class Walker_Comment extends Walker { ?>
- comment_approved ) : ?> - - + awaiting_moderation_text( $comment ); + + // Output the comment approval notification opt-in form if needed. + echo $this->comment_approval_notification_form( $comment ); + ?>
diff --git a/wp-includes/comment.php b/wp-includes/comment.php index 116e66a57c..124be79db4 100644 --- a/wp-includes/comment.php +++ b/wp-includes/comment.php @@ -2348,6 +2348,114 @@ function wp_new_comment_notify_postauthor( $comment_ID ) { return wp_notify_postauthor( $comment_ID ); } +/** + * Notifies the comment author when their comment gets approved. + * + * This notification is only sent once when the comment status + * changes from unapproved to approved. + * + * @since 5.7.0 + * + * @param int|WP_Comment $comment_id Comment ID or WP_Comment object. + * @return bool Whether the email was sent. + */ +function wp_new_comment_notify_comment_author( $comment_id ) { + $comment = get_comment( $comment_id ); + + if ( ! $comment ) { + return false; + } + + $post = get_post( $comment->comment_post_ID ); + + if ( ! $post ) { + return false; + } + + // Make sure the comment author can be notified by email. + if ( empty( $comment->comment_author_email ) ) { + return false; + } + + if ( ! get_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin', true ) ) { + return false; + } + + /** + * The blogname option is escaped with esc_html when + * saved into the database, we need to reverse this for + * the plain text area of the email. + */ + $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); + + $subject = sprintf( + /* translators: 1: blog name, 2: post title */ + __( '[%1$s] Your comment on "%2$s" has been approved' ), + $blogname, + $post->post_title + ); + + if ( ! empty( $comment->comment_author ) ) { + $notify_message = sprintf( + /* translators: 1: comment author's name */ + __( 'Howdy %s,' ), + $comment->comment_author + ) . "\r\n\r\n"; + } else { + $notify_message = __( 'Howdy,' ) . "\r\n\r\n"; + } + + $notify_message .= sprintf( + /* translators: 1: post title */ + __( 'Your comment on "%s" has been approved.' ), + $post->post_title + ) . "\r\n\r\n"; + + $notify_message .= sprintf( + /* translators: 1: comment permalink */ + __( 'View comment: %s' ), + get_comment_link( $comment ) + ) . "\r\n"; + + $email = array( + 'to' => $comment->comment_author_email, + 'subject' => $subject, + 'message' => $notify_message, + 'headers' => '', + ); + + /** + * Filters the contents of the email sent to notify a comment author that their comment was approved. + * + * Content should be formatted for transmission via wp_mail(). + * + * @since 5.7.0 + * + * @param array $email { + * Used to build wp_mail(). + * + * @type string $to The email address of the comment author. + * @type string $subject The subject of the email. + * @type string $message The content of the email. + * @type string $headers Headers. + * } + * @param WP_Comment $comment Comment object. + */ + $email = apply_filters( 'comment_approval_notification', $email, $comment ); + + $sent = wp_mail( + $email['to'], + wp_specialchars_decode( $email['subject'] ), + $email['message'], + $email['headers'] + ); + + // Delete the opt-in now the notification has been sent. + delete_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin' ); + + return $sent; +} + /** * Sets the status of a comment. * diff --git a/wp-includes/default-filters.php b/wp-includes/default-filters.php index 1014a37b6d..5efa182c2b 100644 --- a/wp-includes/default-filters.php +++ b/wp-includes/default-filters.php @@ -468,6 +468,7 @@ add_action( 'comment_post', 'wp_new_comment_notify_postauthor' ); add_action( 'after_password_reset', 'wp_password_change_notification' ); add_action( 'register_new_user', 'wp_send_new_user_notifications' ); add_action( 'edit_user_created_user', 'wp_send_new_user_notifications', 10, 2 ); +add_action( 'comment_unapproved_to_approved', 'wp_new_comment_notify_comment_author' ); // REST API actions. add_action( 'init', 'rest_api_init' ); diff --git a/wp-includes/version.php b/wp-includes/version.php index cde437c34a..effb9e79ed 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -13,7 +13,7 @@ * * @global string $wp_version */ -$wp_version = '5.7-alpha-50082'; +$wp_version = '5.7-alpha-50109'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.