WordPress.org

Make WordPress Core

Changeset 38778


Ignore:
Timestamp:
10/11/2016 03:42:28 AM (20 months ago)
Author:
boonebgorges
Message:

Comments: Abstract die() calls from comment submission routine.

Since 4.4, comment submission has been mostly abstracted into a function,
rather than being processed inline in wp-comments-post.php. This change
made it easier to write automated tests against the bulk of the comment
submission process. wp_allow_comment() remained untestable, however:
when a comment failed one of its checks (flooding, duplicates, etc),
die() or wp_die() would be called directly. This shortcoming posed
problems for any application attempting to use WP's comment verification
functions in an abstract way - from PHPUnit to the REST API.

The current changeset introduces a new parameter, $avoid_die, to the
wp_new_comment() stack. When set to true, wp_new_comment() and
wp_allow_comment() will return WP_Error objects when a comment check
fails. When set to false - the default, for backward compatibility -
a failed check will result in a die() or wp_die(), as appropriate.

Prior to this changeset, default comment flood checks took place in the
function check_comment_flood_db(), which was hooked to the
'check_comment_flood' action. This design allowed the default comment
flood routine to be bypassed or replaced using remove_action().
In order to maintain backward compatibility with this usage, while
simultaneously converting the comment flood logic into something that
returns a value rather than calling die() directly,
check_comment_flood_db() has been changed into a wrapper function for
a call to add_filter(); this, in turn, adds the *actual* comment flood
check to a new filter, 'wp_is_comment_flood'. Note that direct calls
to check_comment_flood_db() will no longer do anything in isolation.

Props websupporter, rachelbaker.
Fixes #36901.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/comment.php

    r38748 r38778  
    589589 *
    590590 * @since 2.0.0
     591 * @since 4.7.0 The `$avoid_die` parameter was added, allowing the function to
     592 *              return a WP_Error object instead of dying.
    591593 *
    592594 * @global wpdb $wpdb WordPress database abstraction object.
    593595 *
    594  * @param array $commentdata Contains information on the comment
    595  * @return int|string Signifies the approval status (0|1|'spam')
    596  */
    597 function wp_allow_comment( $commentdata ) {
     596 * @param array $commentdata Contains information on the comment.
     597 * @param bool  $avoid_die   When true, a disallowed comment will result in the function
     598 *                           returning a WP_Error object, rather than executing wp_die().
     599 *                           Default false.
     600 * @return int|string|WP_Error Allowed comments return the approval status (0|1|'spam').
     601 *                             If `$avoid_die` is true, disallowed comments return a WP_Error.
     602 */
     603function wp_allow_comment( $commentdata, $avoid_die = false ) {
    598604    global $wpdb;
    599605
     
    640646         */
    641647        do_action( 'comment_duplicate_trigger', $commentdata );
    642         if ( wp_doing_ajax() ) {
    643             die( __('Duplicate comment detected; it looks as though you’ve already said that!') );
     648        if ( true === $avoid_die ) {
     649            return new WP_Error( 'comment_duplicate', __( 'Duplicate comment detected; it looks as though you’ve already said that!' ), $dupe_id );
     650        } else {
     651            if ( wp_doing_ajax() ) {
     652                die( __('Duplicate comment detected; it looks as though you’ve already said that!') );
     653            }
     654
     655            wp_die( __( 'Duplicate comment detected; it looks as though you’ve already said that!' ), 409 );
    644656        }
    645         wp_die( __( 'Duplicate comment detected; it looks as though you’ve already said that!' ), 409 );
    646657    }
    647658
     
    652663     *
    653664     * @since 2.3.0
     665     * @since 4.7.0 The `$avoid_die` parameter was added.
    654666     *
    655667     * @param string $comment_author_IP    Comment author's IP address.
    656668     * @param string $comment_author_email Comment author's email.
    657669     * @param string $comment_date_gmt     GMT date the comment was posted.
     670     * @param bool   $avoid_die            Whether to prevent executing wp_die()
     671     *                                     or die() if a comment flood is occurring.
    658672     */
    659673    do_action(
     
    661675        $commentdata['comment_author_IP'],
    662676        $commentdata['comment_author_email'],
    663         $commentdata['comment_date_gmt']
     677        $commentdata['comment_date_gmt'],
     678        $avoid_die
    664679    );
     680
     681    /**
     682     * Filters whether a comment is part of a comment flood.
     683     *
     684     * The default check is wp_check_comment_flood(). See check_comment_flood_db().
     685     *
     686     * @since 4.7.0
     687     *
     688     * @param bool   $is_flood             Is a comment flooding occurring? Default false.
     689     * @param string $comment_author_IP    Comment author's IP address.
     690     * @param string $comment_author_email Comment author's email.
     691     * @param string $comment_date_gmt     GMT date the comment was posted.
     692     * @param bool   $avoid_die            Whether to prevent executing wp_die()
     693     *                                     or die() if a comment flood is occurring.
     694     */
     695    $is_flood = apply_filters(
     696        'wp_is_comment_flood',
     697        false,
     698        $commentdata['comment_author_IP'],
     699        $commentdata['comment_author_email'],
     700        $commentdata['comment_date_gmt'],
     701        $avoid_die
     702    );
     703
     704    if ( $is_flood ) {
     705        return new WP_Error( 'comment_flood', __( 'You are posting comments too quickly. Slow down.' ) );
     706    }
    665707
    666708    if ( ! empty( $commentdata['user_id'] ) ) {
     
    716758
    717759/**
    718  * Check whether comment flooding is occurring.
     760 * Hooks WP's native database-based comment-flood check.
     761 *
     762 * This wrapper maintains backward compatibility with plugins that expect to
     763 * be able to unhook the legacy check_comment_flood_db() function from
     764 * 'check_comment_flood' using remove_action().
     765 *
     766 * @since 2.3.0
     767 * @since 4.7.0 Converted to be an add_filter() wrapper.
     768 */
     769function check_comment_flood_db() {
     770    add_filter( 'wp_is_comment_flood', 'wp_check_comment_flood', 10, 5 );
     771}
     772
     773/**
     774 * Checks whether comment flooding is occurring.
    719775 *
    720776 * Won't run, if current user can manage options, so to not block
    721777 * administrators.
    722778 *
    723  * @since 2.3.0
     779 * @since 4.7.0
    724780 *
    725781 * @global wpdb $wpdb WordPress database abstraction object.
    726782 *
    727  * @param string $ip Comment IP.
    728  * @param string $email Comment author email address.
    729  * @param string $date MySQL time string.
    730  */
    731 function check_comment_flood_db( $ip, $email, $date ) {
     783 * @param bool   $is_flood  Is a comment flooding occurring?
     784 * @param string $ip        Comment IP.
     785 * @param string $email     Comment author email address.
     786 * @param string $date      MySQL time string.
     787 * @param bool   $avoid_die When true, a disallowed comment will result in the function
     788 *                          returning a WP_Error object, rather than executing wp_die().
     789 *                          Default false.
     790 * @return bool Whether comment flooding is occurring.
     791 */
     792function wp_check_comment_flood( $is_flood, $ip, $email, $date, $avoid_die = false ) {
     793
    732794    global $wpdb;
     795
     796    // Another callback has declared a flood. Trust it.
     797    if ( true === $is_flood ) {
     798        return $is_flood;
     799    }
     800
    733801    // don't throttle admins or moderators
    734802    if ( current_user_can( 'manage_options' ) || current_user_can( 'moderate_comments' ) ) {
    735         return;
     803        return false;
    736804    }
    737805    $hour_ago = gmdate( 'Y-m-d H:i:s', time() - HOUR_IN_SECONDS );
     
    775843             */
    776844            do_action( 'comment_flood_trigger', $time_lastcomment, $time_newcomment );
    777 
    778             if ( wp_doing_ajax() )
    779                 die( __('You are posting comments too quickly. Slow down.') );
    780 
    781             wp_die( __( 'You are posting comments too quickly. Slow down.' ), 429 );
     845            if ( true === $avoid_die ) {
     846                return true;
     847            } else {
     848                if ( wp_doing_ajax() ) {
     849                    die( __('You are posting comments too quickly. Slow down.') );
     850                }
     851
     852                wp_die( __( 'You are posting comments too quickly. Slow down.' ), 429 );
     853            }
    782854        }
    783855    }
     856
     857    return false;
    784858}
    785859
     
    17311805 * @since 1.5.0
    17321806 * @since 4.3.0 'comment_agent' and 'comment_author_IP' can be set via `$commentdata`.
     1807 * @since 4.7.0 The `$avoid_die` parameter was added, allowing the function to
     1808 *              return a WP_Error object instead of dying.
    17331809 *
    17341810 * @see wp_insert_comment()
     
    17541830 *                                        'REMOTE_ADDR' in the `$_SERVER` superglobal sent in the original request.
    17551831 * }
    1756  * @return int|false The ID of the comment on success, false on failure.
    1757  */
    1758 function wp_new_comment( $commentdata ) {
     1832 * @param bool $avoid_die Should errors be returned as WP_Error objects instead of
     1833 *                        executing wp_die()? Default false.
     1834 * @return int|false|WP_Error The ID of the comment on success, false or WP_Error on failure.
     1835 */
     1836function wp_new_comment( $commentdata, $avoid_die = false ) {
    17591837    global $wpdb;
    17601838
     
    18051883    $commentdata = wp_filter_comment($commentdata);
    18061884
    1807     $commentdata['comment_approved'] = wp_allow_comment($commentdata);
     1885    $commentdata['comment_approved'] = wp_allow_comment( $commentdata, $avoid_die );
     1886    if ( is_wp_error( $commentdata['comment_approved'] ) ) {
     1887        return $commentdata['comment_approved'];
     1888    }
    18081889
    18091890    $comment_ID = wp_insert_comment($commentdata);
     
    18191900        $commentdata = wp_filter_comment( $commentdata );
    18201901
    1821         $commentdata['comment_approved'] = wp_allow_comment( $commentdata );
     1902        $commentdata['comment_approved'] = wp_allow_comment( $commentdata, $avoid_die );
     1903        if ( is_wp_error( $commentdata['comment_approved'] ) ) {
     1904            return $commentdata['comment_approved'];
     1905        }
    18221906
    18231907        $comment_ID = wp_insert_comment( $commentdata );
     
    29413025    );
    29423026
    2943     $comment_id = wp_new_comment( wp_slash( $commentdata ) );
     3027    $comment_id = wp_new_comment( wp_slash( $commentdata ), true );
     3028    if ( is_wp_error( $comment_id ) ) {
     3029        return $comment_id;
     3030    }
     3031
    29443032    if ( ! $comment_id ) {
    29453033        return new WP_Error( 'comment_save_error', __( '<strong>ERROR</strong>: The comment could not be saved. Please try again later.' ), 500 );
     
    29473035
    29483036    return get_comment( $comment_id );
    2949 
    2950 }
     3037}
  • trunk/src/wp-includes/default-filters.php

    r38046 r38778  
    198198add_filter( 'pre_kses',                 'wp_pre_kses_less_than'               );
    199199add_filter( 'sanitize_title',           'sanitize_title_with_dashes',   10, 3 );
    200 add_action( 'check_comment_flood',      'check_comment_flood_db',       10, 3 );
     200add_action( 'check_comment_flood',      'check_comment_flood_db',       10, 4 );
    201201add_filter( 'comment_flood_filter',     'wp_throttle_comment_flood',    10, 3 );
    202202add_filter( 'pre_comment_content',      'wp_rel_nofollow',              15    );
  • trunk/tests/phpunit/tests/comment-submission.php

    r38763 r38778  
    715715    }
    716716
     717    /**
     718     * @ticket 36901
     719     */
     720    public function test_submitting_duplicate_comments() {
     721        $post = self::factory()->post->create_and_get( array(
     722            'post_status' => 'publish',
     723        ) );
     724        $data = array(
     725            'comment_post_ID' => $post->ID,
     726            'comment'         => 'Did I say that?',
     727            'author'          => 'Repeat myself',
     728            'email'           => 'mail@example.com',
     729        );
     730        $first_comment = wp_handle_comment_submission( $data );
     731        $second_comment = wp_handle_comment_submission( $data );
     732        $this->assertWPError( $second_comment );
     733        $this->assertSame( 'comment_duplicate', $second_comment->get_error_code() );
     734    }
     735
     736    /**
     737     * @ticket 36901
     738     */
     739    public function test_comments_flood() {
     740        $post = self::factory()->post->create_and_get( array(
     741            'post_status' => 'publish',
     742        ) );
     743        $data = array(
     744            'comment_post_ID' => $post->ID,
     745            'comment'         => 'Did I say that?',
     746            'author'          => 'Repeat myself',
     747            'email'           => 'mail@example.com',
     748        );
     749        $first_comment = wp_handle_comment_submission( $data );
     750
     751        $data['comment'] = 'Wow! I am quick!';
     752        $second_comment = wp_handle_comment_submission( $data );
     753
     754        $this->assertWPError( $second_comment );
     755        $this->assertSame( 'comment_flood', $second_comment->get_error_code() );
     756    }
     757
     758    /**
     759     * @ticket 36901
     760     */
     761    public function test_comments_flood_user_is_admin() {
     762        $user = self::factory()->user->create_and_get( array(
     763            'role' => 'administrator',
     764        ) );
     765        wp_set_current_user( $user->ID );
     766
     767        $post = self::factory()->post->create_and_get( array(
     768            'post_status' => 'publish',
     769        ) );
     770        $data = array(
     771            'comment_post_ID' => $post->ID,
     772            'comment'         => 'Did I say that?',
     773            'author'          => 'Repeat myself',
     774            'email'           => 'mail@example.com',
     775        );
     776        $first_comment = wp_handle_comment_submission( $data );
     777
     778        $data['comment'] = 'Wow! I am quick!';
     779        $second_comment = wp_handle_comment_submission( $data );
     780
     781        $this->assertNotWPError( $second_comment );
     782        $this->assertEquals( $post->ID, $second_comment->comment_post_ID );
     783    }
    717784}
Note: See TracChangeset for help on using the changeset viewer.