WordPress.org

Make WordPress Core

Changeset 38778


Ignore:
Timestamp:
10/11/16 03:42:28 (6 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.