Make WordPress Core

Changeset 50109


Ignore:
Timestamp:
01/31/2021 12:48:24 PM (4 years ago)
Author:
johnbillion
Message:

Comments: Introduce a method for commenters to opt-in to receiving an email notification when their moderated comment gets approved.

The opt-in form is shown after the comment is submitted and held for moderation.

Sorry this took five years.

Props jeffr0, swissspidy, mrahmadawais, wonderboymusic, jdgrimes, obenland, Monika, imath, garrett-eclipse, johnbillion

Fixes #33717

Location:
trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-comments-post.php

    r49108 r50109  
    2323nocache_headers();
    2424
    25 $comment = wp_handle_comment_submission( wp_unslash( $_POST ) );
    26 if ( is_wp_error( $comment ) ) {
    27     $data = (int) $comment->get_error_data();
    28     if ( ! empty( $data ) ) {
     25if ( isset( $_POST['wp-comment-approved-notification-optin'], $_POST['comment_ID'], $_POST['moderation-hash'] ) ) {
     26    $comment = get_comment( $_POST['comment_ID'] );
     27
     28    if ( $comment && hash_equals( $_POST['moderation-hash'], wp_hash( $comment->comment_date_gmt ) ) ) {
     29        update_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin', true );
     30    } else {
    2931        wp_die(
    30             '<p>' . $comment->get_error_message() . '</p>',
    31             __( 'Comment Submission Failure' ),
     32            '<p>' . __( 'Invalid comment ID.' ) . '</p>',
     33            __( 'Comment Notification Opt-in Failure' ),
    3234            array(
    33                 'response'  => $data,
     35                'response'  => 404,
    3436                'back_link' => true,
    3537            )
    3638        );
    37     } else {
    38         exit;
    3939    }
     40} else {
     41    $comment = wp_handle_comment_submission( wp_unslash( $_POST ) );
     42    if ( is_wp_error( $comment ) ) {
     43        $data = (int) $comment->get_error_data();
     44        if ( ! empty( $data ) ) {
     45            wp_die(
     46                '<p>' . $comment->get_error_message() . '</p>',
     47                __( 'Comment Submission Failure' ),
     48                array(
     49                    'response'  => $data,
     50                    'back_link' => true,
     51                )
     52            );
     53        } else {
     54            exit;
     55        }
     56    }
     57
     58    $user            = wp_get_current_user();
     59    $cookies_consent = ( isset( $_POST['wp-comment-cookies-consent'] ) );
     60
     61    /**
     62     * Perform other actions when comment cookies are set.
     63     *
     64     * @since 3.4.0
     65     * @since 4.9.6 The `$cookies_consent` parameter was added.
     66     *
     67     * @param WP_Comment $comment         Comment object.
     68     * @param WP_User    $user            Comment author's user object. The user may not exist.
     69     * @param bool       $cookies_consent Comment author's consent to store cookies.
     70     */
     71    do_action( 'set_comment_cookies', $comment, $user, $cookies_consent );
    4072}
    41 
    42 $user            = wp_get_current_user();
    43 $cookies_consent = ( isset( $_POST['wp-comment-cookies-consent'] ) );
    44 
    45 /**
    46  * Perform other actions when comment cookies are set.
    47  *
    48  * @since 3.4.0
    49  * @since 4.9.6 The `$cookies_consent` parameter was added.
    50  *
    51  * @param WP_Comment $comment         Comment object.
    52  * @param WP_User    $user            Comment author's user object. The user may not exist.
    53  * @param bool       $cookies_consent Comment author's consent to store cookies.
    54  */
    55 do_action( 'set_comment_cookies', $comment, $user, $cookies_consent );
    5673
    5774$location = empty( $_POST['redirect_to'] ) ? get_comment_link( $comment ) : $_POST['redirect_to'] . '#comment-' . $comment->comment_ID;
  • trunk/src/wp-includes/class-walker-comment.php

    r49160 r50109  
    4040        'id'     => 'comment_ID',
    4141    );
     42
     43    /**
     44     * Whether the comment approval notification opt-in form or message needs to be
     45     * output automatically.
     46     *
     47     * @since 5.7.0
     48     *
     49     * @var bool
     50     */
     51    protected $needs_comment_approval_notification_output = true;
    4252
    4353    /**
     
    256266     * Filters the comment text.
    257267     *
    258      * Removes links from the pending comment's text if the commenter did not consent
    259      * to the comment cookies.
     268     *   - Removes links from the pending comment's text if the commenter did not consent
     269     *     to the comment cookies
     270     *   - Prepends the approval notification opt-in form or message to pending comments
    260271     *
    261272     * @since 5.4.2
     273     * @since 5.7.0 Comment approval notification opt-in form is now automatically
     274     *              appended if necessary.
    262275     *
    263276     * @param string          $comment_text Text of the current comment.
     
    273286        }
    274287
     288        /*
     289         * Checks if we need to output the comment approval notification opt-in form.
     290         */
     291        if ( $this->needs_comment_approval_notification_output ) {
     292            $comment_text = $this->comment_approval_notification_form( $comment ) . "\n" . $comment_text;
     293        }
     294
     295        $this->needs_comment_approval_notification_output = true;
     296
    275297        return $comment_text;
     298    }
     299
     300    /**
     301     * Outputs the awaiting moderation text.
     302     *
     303     * @since 5.7.0
     304     *
     305     * @param WP_Comment $comment Comment to display.
     306     */
     307    protected function awaiting_moderation_text( $comment ) {
     308        if ( '0' !== $comment->comment_approved ) {
     309            return;
     310        }
     311
     312        $commenter = wp_get_current_commenter();
     313
     314        if ( $commenter['comment_author_email'] ) {
     315            $moderation_note = __( 'Your comment is awaiting moderation.' );
     316        } else {
     317            $moderation_note = __( 'Your comment is awaiting moderation. This is a preview, your comment will be visible after it has been approved.' );
     318        }
     319
     320        printf(
     321            '<em class="comment-awaiting-moderation">%s</em>',
     322            esc_html( $moderation_note )
     323        );
     324    }
     325
     326    /**
     327     * Gets the comment approval notification opt-in form or message for a pending comment.
     328     *
     329     * @since 5.7.0
     330     *
     331     * @param WP_Comment $comment Comment to display.
     332     * @return string HTML output.
     333     */
     334    protected function comment_approval_notification_form( $comment ) {
     335        $comment_approval_output = '';
     336
     337        if ( '0' === $comment->comment_approved && has_action( 'comment_unapproved_to_approved', 'wp_new_comment_notify_comment_author' ) ) {
     338            if ( get_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin', true ) ) {
     339                $comment_approval_output = sprintf(
     340                    '<p><em class="wp-comment-approved-notification-optedin">%s</em></p>',
     341                    esc_html__( 'You will receive an email when your comment is approved.' )
     342                );
     343            } else {
     344                $comment_approval_output = sprintf(
     345                    '<form action="%1$s" method="post">
     346                        <p>
     347                            <input type="checkbox" id="wp-comment-approved-notification-optin" name="wp-comment-approved-notification-optin">
     348                            <label for="wp-comment-approved-notification-optin">
     349                                %2$s
     350                            </label>
     351                        </p>
     352                        <input type="hidden" name="comment_ID" value="%3$s">
     353                        <input type="hidden" name="moderation-hash" value="%4$s">
     354                        <input type="submit" class="button" value="%5$s">
     355                    </form>',
     356                    esc_url( site_url( '/wp-comments-post.php' ) ),
     357                    esc_html__( 'I want to be notified by email when my comment is approved.' ),
     358                    absint( $comment->comment_ID ),
     359                    wp_hash( $comment->comment_date_gmt ),
     360                    esc_html_x( 'Save', 'comment approval notification form' )
     361                );
     362            }
     363        }
     364
     365        // Disable the backcompat output.
     366        $this->needs_comment_approval_notification_output = false;
     367
     368        // Return the approval notification opt-in form.
     369        return $comment_approval_output;
    276370    }
    277371
     
    298392        $commenter          = wp_get_current_commenter();
    299393        $show_pending_links = isset( $commenter['comment_author'] ) && $commenter['comment_author'];
    300 
    301         if ( $commenter['comment_author_email'] ) {
    302             $moderation_note = __( 'Your comment is awaiting moderation.' );
    303         } else {
    304             $moderation_note = __( 'Your comment is awaiting moderation. This is a preview; your comment will be visible after it has been approved.' );
    305         }
    306394        ?>
    307395        <<?php echo $tag; ?> <?php comment_class( $this->has_children ? 'parent' : '', $comment ); ?> id="comment-<?php comment_ID(); ?>">
     
    329417            ?>
    330418        </div>
    331         <?php if ( '0' == $comment->comment_approved ) : ?>
    332         <em class="comment-awaiting-moderation"><?php echo $moderation_note; ?></em>
    333         <br />
    334         <?php endif; ?>
     419
     420        <?php
     421        // Output the comment moderation feedback if needed.
     422        $this->awaiting_moderation_text( $comment );
     423
     424        // Output the comment approval notification opt-in form if needed.
     425        echo $this->comment_approval_notification_form( $comment );
     426        ?>
    335427
    336428        <div class="comment-meta commentmetadata">
     
    402494        $commenter          = wp_get_current_commenter();
    403495        $show_pending_links = ! empty( $commenter['comment_author'] );
    404 
    405         if ( $commenter['comment_author_email'] ) {
    406             $moderation_note = __( 'Your comment is awaiting moderation.' );
    407         } else {
    408             $moderation_note = __( 'Your comment is awaiting moderation. This is a preview; your comment will be visible after it has been approved.' );
    409         }
    410496        ?>
    411497        <<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( $this->has_children ? 'parent' : '', $comment ); ?>>
     
    451537                    </div><!-- .comment-metadata -->
    452538
    453                     <?php if ( '0' == $comment->comment_approved ) : ?>
    454                     <em class="comment-awaiting-moderation"><?php echo $moderation_note; ?></em>
    455                     <?php endif; ?>
     539                    <?php
     540                    // Output the comment moderation feedback if needed.
     541                    $this->awaiting_moderation_text( $comment );
     542
     543                    // Output the comment approval notification opt-in form if needed.
     544                    echo $this->comment_approval_notification_form( $comment );
     545                    ?>
    456546                </footer><!-- .comment-meta -->
    457547
  • trunk/src/wp-includes/comment.php

    r49936 r50109  
    23502350
    23512351/**
     2352 * Notifies the comment author when their comment gets approved.
     2353 *
     2354 * This notification is only sent once when the comment status
     2355 * changes from unapproved to approved.
     2356 *
     2357 * @since 5.7.0
     2358 *
     2359 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
     2360 * @return bool Whether the email was sent.
     2361 */
     2362function wp_new_comment_notify_comment_author( $comment_id ) {
     2363    $comment = get_comment( $comment_id );
     2364
     2365    if ( ! $comment ) {
     2366        return false;
     2367    }
     2368
     2369    $post = get_post( $comment->comment_post_ID );
     2370
     2371    if ( ! $post ) {
     2372        return false;
     2373    }
     2374
     2375    // Make sure the comment author can be notified by email.
     2376    if ( empty( $comment->comment_author_email ) ) {
     2377        return false;
     2378    }
     2379
     2380    if ( ! get_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin', true ) ) {
     2381        return false;
     2382    }
     2383
     2384    /**
     2385     * The blogname option is escaped with esc_html when
     2386     * saved into the database, we need to reverse this for
     2387     * the plain text area of the email.
     2388     */
     2389    $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
     2390
     2391    $subject = sprintf(
     2392        /* translators: 1: blog name, 2: post title */
     2393        __( '[%1$s] Your comment on "%2$s" has been approved' ),
     2394        $blogname,
     2395        $post->post_title
     2396    );
     2397
     2398    if ( ! empty( $comment->comment_author ) ) {
     2399        $notify_message = sprintf(
     2400            /* translators: 1: comment author's name */
     2401            __( 'Howdy %s,' ),
     2402            $comment->comment_author
     2403        ) . "\r\n\r\n";
     2404    } else {
     2405        $notify_message = __( 'Howdy,' ) . "\r\n\r\n";
     2406    }
     2407
     2408    $notify_message .= sprintf(
     2409        /* translators: 1: post title */
     2410        __( 'Your comment on "%s" has been approved.' ),
     2411        $post->post_title
     2412    ) . "\r\n\r\n";
     2413
     2414    $notify_message .= sprintf(
     2415        /* translators: 1: comment permalink */
     2416        __( 'View comment: %s' ),
     2417        get_comment_link( $comment )
     2418    ) . "\r\n";
     2419
     2420    $email = array(
     2421        'to'      => $comment->comment_author_email,
     2422        'subject' => $subject,
     2423        'message' => $notify_message,
     2424        'headers' => '',
     2425    );
     2426
     2427    /**
     2428     * Filters the contents of the email sent to notify a comment author that their comment was approved.
     2429     *
     2430     * Content should be formatted for transmission via wp_mail().
     2431     *
     2432     * @since 5.7.0
     2433     *
     2434     * @param array      $email   {
     2435     *     Used to build wp_mail().
     2436     *
     2437     *     @type string $to      The email address of the comment author.
     2438     *     @type string $subject The subject of the email.
     2439     *     @type string $message The content of the email.
     2440     *     @type string $headers Headers.
     2441     * }
     2442     * @param WP_Comment $comment Comment object.
     2443     */
     2444    $email = apply_filters( 'comment_approval_notification', $email, $comment );
     2445
     2446    $sent = wp_mail(
     2447        $email['to'],
     2448        wp_specialchars_decode( $email['subject'] ),
     2449        $email['message'],
     2450        $email['headers']
     2451    );
     2452
     2453    // Delete the opt-in now the notification has been sent.
     2454    delete_comment_meta( $comment->comment_ID, '_wp_comment_author_notification_optin' );
     2455
     2456    return $sent;
     2457}
     2458
     2459/**
    23522460 * Sets the status of a comment.
    23532461 *
  • trunk/src/wp-includes/default-filters.php

    r50078 r50109  
    469469add_action( 'register_new_user', 'wp_send_new_user_notifications' );
    470470add_action( 'edit_user_created_user', 'wp_send_new_user_notifications', 10, 2 );
     471add_action( 'comment_unapproved_to_approved', 'wp_new_comment_notify_comment_author' );
    471472
    472473// REST API actions.
  • trunk/tests/phpunit/tests/comment.php

    r49603 r50109  
    553553        self::$notify_message = $notify_message;
    554554        return $notify_message;
     555    }
     556
     557    /**
     558     * @ticket 33717
     559     */
     560    public function test_wp_new_comment_notify_comment_author_once_only() {
     561        $c = self::factory()->comment->create(
     562            array(
     563                'comment_post_ID'      => self::$post_id,
     564                'comment_approved'     => '0',
     565                'comment_author_email' => 'foo@bar.mail',
     566            )
     567        );
     568
     569        // The comment author subscribed to receive an email once comment is approved.
     570        update_comment_meta( $c, '_wp_comment_author_notification_optin', true );
     571
     572        // For the purpose of the test we are removing this hook to directly use the function to notify the comment author.
     573        remove_action( 'comment_unapproved_to_approved', 'wp_new_comment_notify_comment_author' );
     574
     575        // Approve the comment.
     576        wp_set_comment_status( $c, 'approve' );
     577
     578        $sent = wp_new_comment_notify_comment_author( $c );
     579        $resent = wp_new_comment_notify_comment_author( $c );
     580
     581        $this->assertTrue( $sent );
     582        $this->assertFalse( $resent );
     583    }
     584
     585    /**
     586     * @ticket 33717
     587     */
     588    public function test_wp_new_comment_notify_comment_author_has_not_opted_in() {
     589        $c = self::factory()->comment->create(
     590            array(
     591                'comment_post_ID'      => self::$post_id,
     592                'comment_approved'     => '0',
     593                'comment_author_email' => 'bat@man.mail',
     594            )
     595        );
     596
     597        // For the purpose of the test we are removing this hook to directly use the function to notify the comment author.
     598        remove_action( 'comment_unapproved_to_approved', 'wp_new_comment_notify_comment_author' );
     599
     600        // Approve the comment.
     601        wp_set_comment_status( $c, 'approve' );
     602
     603        $sent = wp_new_comment_notify_comment_author( $c );
     604
     605        // The comment author hasn't subscribed to receive an email, no email should be sent.
     606        $this->assertFalse( $sent );
    555607    }
    556608
Note: See TracChangeset for help on using the changeset viewer.