Make WordPress Core

Changeset 55369


Ignore:
Timestamp:
02/21/2023 01:43:33 AM (22 months ago)
Author:
peterwilsoncc
Message:

Comments: Prevent replying to unapproved comments.

Introduces client and server side validation to ensure the replytocom query string parameter can not be exploited to reply to an unapproved comment or display the name of an unapproved commenter.

This only affects commenting via the front end of the site. Comment replies via the dashboard continue their current behaviour of logging the reply and approving the parent comment.

Introduces the $post parameter, defaulting to the current global post, to get_cancel_comment_reply_link() and comment_form_title().

Introduces _get_comment_reply_id() for determining the comment reply ID based on the replytocom query string parameter.

Renames the parameter $post_id to $post in get_comment_id_fields() and comment_id_fields() to accept either a post ID or WP_Post object.

Adds a new WP_Error return state to wp_handle_comment_submission() to prevent replies to unapproved comments. The error code is comment_reply_to_unapproved_comment with the message Sorry, replies to unapproved comments are not allowed..

Props costdev, jrf, hellofromtonya, fasuto, boniu91, milana_cap.
Fixes #53962.

Location:
trunk
Files:
4 edited

Legend:

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

    r55346 r55369  
    19271927 *
    19281928 * @since 2.7.0
    1929  *
    1930  * @param string $text Optional. Text to display for cancel reply link. If empty,
    1931  *                     defaults to 'Click here to cancel reply'. Default empty.
     1929 * @since 6.2.0 Added the `$post` parameter.
     1930 *
     1931 * @param string           $text Optional. Text to display for cancel reply link. If empty,
     1932 *                               defaults to 'Click here to cancel reply'. Default empty.
     1933 * @param int|WP_Post|null $post Optional. The post the comment thread is being
     1934 *                               displayed for. Defaults to the current global post.
    19321935 * @return string
    19331936 */
    1934 function get_cancel_comment_reply_link( $text = '' ) {
     1937function get_cancel_comment_reply_link( $text = '', $post = null ) {
    19351938    if ( empty( $text ) ) {
    19361939        $text = __( 'Click here to cancel reply.' );
    19371940    }
    19381941
    1939     $style = isset( $_GET['replytocom'] ) ? '' : ' style="display:none;"';
    1940     $link  = esc_html( remove_query_arg( array( 'replytocom', 'unapproved', 'moderation-hash' ) ) ) . '#respond';
     1942    $post        = get_post( $post );
     1943    $reply_to_id = $post ? _get_comment_reply_id( $post->ID ) : 0;
     1944    $style       = 0 !== $reply_to_id ? '' : ' style="display:none;"';
     1945    $link        = esc_html( remove_query_arg( array( 'replytocom', 'unapproved', 'moderation-hash' ) ) ) . '#respond';
    19411946
    19421947    $formatted_link = '<a rel="nofollow" id="cancel-comment-reply-link" href="' . $link . '"' . $style . '>' . $text . '</a>';
     
    19701975 *
    19711976 * @since 3.0.0
    1972  *
    1973  * @param int $post_id Optional. Post ID. Defaults to the current post ID.
     1977 * @since 6.2.0 Renamed `$post_id` to `$post` and added WP_Post support.
     1978 *
     1979 * @param int|WP_Post|null $post Optional. The post the comment is being displayed for.
     1980 *                               Defaults to the current global post.
    19741981 * @return string Hidden input HTML for replying to comments.
    19751982 */
    1976 function get_comment_id_fields( $post_id = 0 ) {
    1977     if ( empty( $post_id ) ) {
    1978         $post_id = get_the_ID();
    1979     }
    1980 
    1981     $reply_to_id = isset( $_GET['replytocom'] ) ? (int) $_GET['replytocom'] : 0;
     1983function get_comment_id_fields( $post = null ) {
     1984    $post = get_post( $post );
     1985    if ( ! $post ) {
     1986        return '';
     1987    }
     1988
     1989    $post_id     = $post->ID;
     1990    $reply_to_id = _get_comment_reply_id( $post_id );
    19821991    $result      = "<input type='hidden' name='comment_post_ID' value='$post_id' id='comment_post_ID' />\n";
    19831992    $result     .= "<input type='hidden' name='comment_parent' id='comment_parent' value='$reply_to_id' />\n";
     
    20042013 *
    20052014 * @since 2.7.0
     2015 * @since 6.2.0 Renamed `$post_id` to `$post` and added WP_Post support.
    20062016 *
    20072017 * @see get_comment_id_fields()
    20082018 *
    2009  * @param int $post_id Optional. Post ID. Defaults to the current post ID.
    2010  */
    2011 function comment_id_fields( $post_id = 0 ) {
    2012     echo get_comment_id_fields( $post_id );
     2019 * @param int|WP_Post|null $post Optional. The post the comment is being displayed for.
     2020 *                               Defaults to the current global post.
     2021 */
     2022function comment_id_fields( $post = null ) {
     2023    echo get_comment_id_fields( $post );
    20132024}
    20142025
     
    20222033 *
    20232034 * @since 2.7.0
    2024  *
    2025  * @global WP_Comment $comment Global comment object.
    2026  *
    2027  * @param string|false $no_reply_text  Optional. Text to display when not replying to a comment.
    2028  *                                     Default false.
    2029  * @param string|false $reply_text     Optional. Text to display when replying to a comment.
    2030  *                                     Default false. Accepts "%s" for the author of the comment
    2031  *                                     being replied to.
    2032  * @param bool         $link_to_parent Optional. Boolean to control making the author's name a link
    2033  *                                     to their comment. Default true.
    2034  */
    2035 function comment_form_title( $no_reply_text = false, $reply_text = false, $link_to_parent = true ) {
    2036     global $comment;
    2037 
     2035 * @since 6.2.0 Added the `$post` parameter.
     2036 *
     2037 * @param string|false      $no_reply_text  Optional. Text to display when not replying to a comment.
     2038 *                                          Default false.
     2039 * @param string|false      $reply_text     Optional. Text to display when replying to a comment.
     2040 *                                          Default false. Accepts "%s" for the author of the comment
     2041 *                                          being replied to.
     2042 * @param bool              $link_to_parent Optional. Boolean to control making the author's name a link
     2043 *                                          to their comment. Default true.
     2044 * @param int|WP_Post|null  $post           Optional. The post that the comment form is being displayed for.
     2045 *                                          Defaults to the current global post.
     2046 */
     2047function comment_form_title( $no_reply_text = false, $reply_text = false, $link_to_parent = true, $post = null ) {
    20382048    if ( false === $no_reply_text ) {
    20392049        $no_reply_text = __( 'Leave a Reply' );
     
    20452055    }
    20462056
    2047     $reply_to_id = isset( $_GET['replytocom'] ) ? (int) $_GET['replytocom'] : 0;
    2048 
    2049     if ( 0 == $reply_to_id ) {
     2057    $post = get_post( $post );
     2058    if ( ! $post ) {
    20502059        echo $no_reply_text;
     2060        return;
     2061    }
     2062
     2063    $reply_to_id = _get_comment_reply_id( $post->ID );
     2064
     2065    if ( 0 === $reply_to_id ) {
     2066        echo $no_reply_text;
     2067        return;
     2068    }
     2069
     2070    if ( $link_to_parent ) {
     2071        $author = '<a href="#comment-' . get_comment_ID() . '">' . get_comment_author( $reply_to_id ) . '</a>';
    20512072    } else {
    2052         // Sets the global so that template tags can be used in the comment form.
    2053         $comment = get_comment( $reply_to_id );
    2054 
    2055         if ( $link_to_parent ) {
    2056             $author = '<a href="#comment-' . get_comment_ID() . '">' . get_comment_author( $comment ) . '</a>';
    2057         } else {
    2058             $author = get_comment_author( $comment );
    2059         }
    2060 
    2061         printf( $reply_text, $author );
    2062     }
     2073        $author = get_comment_author( $reply_to_id );
     2074    }
     2075
     2076    printf( $reply_text, $author );
     2077}
     2078
     2079/**
     2080 * Gets the comment's reply to ID from the $_GET['replytocom'].
     2081 *
     2082 * @since 6.2.0
     2083 *
     2084 * @access private
     2085 *
     2086 * @param int|WP_Post $post The post the comment is being displayed for.
     2087 *                          Defaults to the current global post.
     2088 * @return int Comment's reply to ID.
     2089 */
     2090function _get_comment_reply_id( $post = null ) {
     2091    $post = get_post( $post );
     2092
     2093    if ( ! $post || ! isset( $_GET['replytocom'] ) || ! is_numeric( $_GET['replytocom'] ) ) {
     2094        return 0;
     2095    }
     2096
     2097    $reply_to_id = (int) $_GET['replytocom'];
     2098
     2099    /*
     2100     * Validate the comment.
     2101     * Bail out if it does not exist, is not approved, or its
     2102     * `comment_post_ID` does not match the given post ID.
     2103     */
     2104    $comment = get_comment( $reply_to_id );
     2105
     2106    if (
     2107        ! $comment instanceof WP_Comment ||
     2108        0 === (int) $comment->comment_approved ||
     2109        $post->ID !== (int) $comment->comment_post_ID
     2110    ) {
     2111        return 0;
     2112    }
     2113
     2114    return $reply_to_id;
    20632115}
    20642116
     
    25712623        echo $args['title_reply_before'];
    25722624
    2573         comment_form_title( $args['title_reply'], $args['title_reply_to'] );
     2625        comment_form_title( $args['title_reply'], $args['title_reply_to'], true, $post_id );
    25742626
    25752627        if ( get_option( 'thread_comments' ) ) {
  • trunk/src/wp-includes/comment.php

    r55324 r55369  
    34763476    }
    34773477    if ( isset( $comment_data['comment_parent'] ) ) {
    3478         $comment_parent = absint( $comment_data['comment_parent'] );
     3478        $comment_parent        = absint( $comment_data['comment_parent'] );
     3479        $comment_parent_object = get_comment( $comment_parent );
     3480
     3481        if (
     3482            0 !== $comment_parent &&
     3483            (
     3484                ! $comment_parent_object instanceof WP_Comment ||
     3485                0 === (int) $comment_parent_object->comment_approved
     3486            )
     3487        ) {
     3488            /**
     3489             * Fires when a comment reply is attempted to an unapproved comment.
     3490             *
     3491             * @since 6.2.0
     3492             *
     3493             * @param int $comment_post_id Post ID.
     3494             * @param int $comment_parent  Parent comment ID.
     3495             */
     3496            do_action( 'comment_reply_to_unapproved_comment', $comment_post_id, $comment_parent );
     3497
     3498            return new WP_Error( 'comment_reply_to_unapproved_comment', __( 'Sorry, replies to unapproved comments are not allowed.' ), 403 );
     3499        }
    34793500    }
    34803501
     
    35613582
    35623583    } else {
    3563 
    35643584        /**
    35653585         * Fires before a comment is posted.
     
    35703590         */
    35713591        do_action( 'pre_comment_on_post', $comment_post_id );
    3572 
    35733592    }
    35743593
  • trunk/tests/phpunit/tests/comment.php

    r55321 r55369  
    376376
    377377        $this->assertSame( array(), $found );
     378    }
     379
     380    /**
     381     * Tests that get_cancel_comment_reply_link() returns the expected value.
     382     *
     383     * @ticket 53962
     384     *
     385     * @dataProvider data_get_cancel_comment_reply_link
     386     *
     387     * @covers ::get_cancel_comment_reply_link
     388     *
     389     * @param string        $text       Text to display for cancel reply link.
     390     *                                  If empty, defaults to 'Click here to cancel reply'.
     391     * @param string|int    $post       The post the comment thread is being displayed for.
     392     *                                  Accepts 'POST_ID', 'POST', or an integer post ID.
     393     * @param int|bool|null $replytocom A comment ID (int), whether to generate an approved (true) or unapproved (false) comment,
     394     *                                  or null not to create a comment.
     395     * @param string        $expected   The expected reply link.
     396     */
     397    public function test_get_cancel_comment_reply_link( $text, $post, $replytocom, $expected ) {
     398        if ( 'POST_ID' === $post ) {
     399            $post = self::$post_id;
     400        } elseif ( 'POST' === $post ) {
     401            $post = self::factory()->post->get_object_by_id( self::$post_id );
     402        }
     403
     404        if ( null === $replytocom ) {
     405            unset( $_GET['replytocom'] );
     406        } else {
     407            $_GET['replytocom'] = $this->create_comment_with_approval_status( $replytocom );
     408        }
     409
     410        $this->assertSame( $expected, get_cancel_comment_reply_link( $text, $post ) );
     411    }
     412
     413    /**
     414     * Data provider.
     415     *
     416     * @return array[]
     417     */
     418    public function data_get_cancel_comment_reply_link() {
     419        return array(
     420            'text as empty string, a valid post ID and an approved comment'    => array(
     421                'text'       => '',
     422                'post'       => 'POST_ID',
     423                'replytocom' => true,
     424                'expected'   => '<a rel="nofollow" id="cancel-comment-reply-link" href="#respond">Click here to cancel reply.</a>',
     425            ),
     426            'text as a custom string, a valid post ID and an approved comment' => array(
     427                'text'       => 'Leave a reply!',
     428                'post'       => 'POST_ID',
     429                'replytocom' => true,
     430                'expected'   => '<a rel="nofollow" id="cancel-comment-reply-link" href="#respond">Leave a reply!</a>',
     431            ),
     432            'text as empty string, a valid WP_Post object and an approved comment' => array(
     433                'text'       => '',
     434                'post'       => 'POST',
     435                'replytocom' => true,
     436                'expected'   => '<a rel="nofollow" id="cancel-comment-reply-link" href="#respond">Click here to cancel reply.</a>',
     437            ),
     438            'text as a custom string, a valid WP_Post object and an approved comment' => array(
     439                'text'       => 'Leave a reply!',
     440                'post'       => 'POST',
     441                'replytocom' => true,
     442                'expected'   => '<a rel="nofollow" id="cancel-comment-reply-link" href="#respond">Leave a reply!</a>',
     443            ),
     444            'text as empty string, an invalid post and an approved comment'    => array(
     445                'text'       => '',
     446                'post'       => -99999,
     447                'replytocom' => true,
     448                'expected'   => '<a rel="nofollow" id="cancel-comment-reply-link" href="#respond" style="display:none;">Click here to cancel reply.</a>',
     449            ),
     450            'text as a custom string, a valid post, but no replytocom' => array(
     451                'text'       => 'Leave a reply!',
     452                'post'       => 'POST',
     453                'replytocom' => null,
     454                'expected'   => '<a rel="nofollow" id="cancel-comment-reply-link" href="#respond" style="display:none;">Leave a reply!</a>',
     455            ),
     456        );
     457    }
     458
     459    /**
     460     * Tests that comment_form_title() outputs the author of an approved comment.
     461     *
     462     * @ticket 53962
     463     *
     464     * @covers ::comment_form_title
     465     */
     466    public function test_should_output_the_author_of_an_approved_comment() {
     467        // Must be set for `comment_form_title()`.
     468        $_GET['replytocom'] = $this->create_comment_with_approval_status( true );
     469
     470        $comment = get_comment( $_GET['replytocom'] );
     471        comment_form_title( false, false, false, self::$post_id );
     472
     473        $this->assertInstanceOf(
     474            'WP_Comment',
     475            $comment,
     476            'The comment is not an instance of WP_Comment.'
     477        );
     478
     479        $this->assertObjectHasAttribute(
     480            'comment_author',
     481            $comment,
     482            'The comment object does not have a "comment_author" property.'
     483        );
     484
     485        $this->assertIsString(
     486            $comment->comment_author,
     487            'The "comment_author" is not a string.'
     488        );
     489
     490        $this->expectOutputString(
     491            'Leave a Reply to ' . $comment->comment_author,
     492            'The expected string was not output.'
     493        );
     494    }
     495
     496    /**
     497     * Tests that get_comment_id_fields() allows replying to an approved comment.
     498     *
     499     * @ticket 53962
     500     *
     501     * @dataProvider data_should_allow_reply_to_an_approved_comment
     502     *
     503     * @covers ::get_comment_id_fields
     504     *
     505     * @param string $comment_post The post of the comment.
     506     *                             Accepts 'POST', 'NEW_POST', 'POST_ID' and 'NEW_POST_ID'.
     507     */
     508    public function test_should_allow_reply_to_an_approved_comment( $comment_post ) {
     509        // Must be set for `get_comment_id_fields()`.
     510        $_GET['replytocom'] = $this->create_comment_with_approval_status( true );
     511
     512        if ( 'POST_ID' === $comment_post ) {
     513            $comment_post = self::$post_id;
     514        } elseif ( 'POST' === $comment_post ) {
     515            $comment_post = self::factory()->post->get_object_by_id( self::$post_id );
     516        }
     517
     518        $expected  = "<input type='hidden' name='comment_post_ID' value='" . self::$post_id . "' id='comment_post_ID' />\n";
     519        $expected .= "<input type='hidden' name='comment_parent' id='comment_parent' value='" . $_GET['replytocom'] . "' />\n";
     520        $actual    = get_comment_id_fields( $comment_post );
     521
     522        $this->assertSame( $expected, $actual );
     523    }
     524
     525    /**
     526     * Data provider.
     527     *
     528     * @return array[]
     529     */
     530    public function data_should_allow_reply_to_an_approved_comment() {
     531        return array(
     532            'a post ID'        => array( 'comment_post' => 'POST_ID' ),
     533            'a WP_Post object' => array( 'comment_post' => 'POST' ),
     534        );
     535    }
     536
     537    /**
     538     * Tests that get_comment_id_fields() returns an empty string
     539     * when the post cannot be retrieved.
     540     *
     541     * @ticket 53962
     542     *
     543     * @dataProvider data_non_existent_posts
     544     *
     545     * @covers ::get_comment_id_fields
     546     *
     547     * @param bool  $replytocom   Whether to create an approved (true) or unapproved (false) comment.
     548     * @param int   $comment_post The post of the comment.
     549     *
     550     */
     551    public function test_should_return_empty_string( $replytocom, $comment_post ) {
     552        if ( is_bool( $replytocom ) ) {
     553            $replytocom = $this->create_comment_with_approval_status( $replytocom );
     554        }
     555
     556        // Must be set for `get_comment_id_fields()`.
     557        $_GET['replytocom'] = $replytocom;
     558
     559        $actual = get_comment_id_fields( $comment_post );
     560
     561        $this->assertSame( '', $actual );
     562    }
     563
     564    /**
     565     * Tests that comment_form_title() does not output the author.
     566     *
     567     * @ticket 53962
     568     *
     569     * @covers ::comment_form_title
     570     *
     571     * @dataProvider data_parent_comments
     572     * @dataProvider data_non_existent_posts
     573     *
     574     * @param bool   $replytocom   Whether to create an approved (true) or unapproved (false) comment.
     575     * @param string $comment_post The post of the comment.
     576     *                             Accepts 'POST', 'NEW_POST', 'POST_ID' and 'NEW_POST_ID'.
     577     */
     578    public function test_should_not_output_the_author( $replytocom, $comment_post ) {
     579        if ( is_bool( $replytocom ) ) {
     580            $replytocom = $this->create_comment_with_approval_status( $replytocom );
     581        }
     582
     583        // Must be set for `comment_form_title()`.
     584        $_GET['replytocom'] = $replytocom;
     585
     586        if ( 'NEW_POST_ID' === $comment_post ) {
     587            $comment_post = self::factory()->post->create();
     588        } elseif ( 'NEW_POST' === $comment_post ) {
     589            $comment_post = self::factory()->post->create_and_get();
     590        } elseif ( 'POST_ID' === $comment_post ) {
     591            $comment_post = self::$post_id;
     592        } elseif ( 'POST' === $comment_post ) {
     593            $comment_post = self::factory()->post->get_object_by_id( self::$post_id );
     594        }
     595
     596        $comment_post_id = $comment_post instanceof WP_Post ? $comment_post->ID : $comment_post;
     597
     598        get_comment( $_GET['replytocom'] );
     599
     600        comment_form_title( false, false, false, $comment_post_id );
     601
     602        $this->expectOutputString( 'Leave a Reply' );
     603    }
     604
     605    /**
     606     * Data provider.
     607     *
     608     * @return array[]
     609     */
     610    public function data_non_existent_posts() {
     611        return array(
     612            'an unapproved comment and a non-existent post ID' => array(
     613                'replytocom'   => false,
     614                'comment_post' => -99999,
     615            ),
     616            'an approved comment and a non-existent post ID' => array(
     617                'replytocom'   => true,
     618                'comment_post' => -99999,
     619            ),
     620        );
     621    }
     622
     623    /**
     624     * Tests that get_comment_id_fields() does not allow replies when
     625     * the comment does not have a parent post.
     626     *
     627     * @ticket 53962
     628     *
     629     * @covers ::get_comment_id_fields
     630     *
     631     * @dataProvider data_parent_comments
     632     *
     633     * @param mixed  $replytocom   Whether to create an approved (true) or unapproved (false) comment,
     634     *                             or an invalid comment ID.
     635     * @param string $comment_post The post of the comment.
     636     *                             Accepts 'POST', 'NEW_POST', 'POST_ID' and 'NEW_POST_ID'.
     637     */
     638    public function test_should_not_allow_reply( $replytocom, $comment_post ) {
     639        if ( is_bool( $replytocom ) ) {
     640            $replytocom = $this->create_comment_with_approval_status( $replytocom );
     641        }
     642
     643        // Must be set for `get_comment_id_fields()`.
     644        $_GET['replytocom'] = $replytocom;
     645
     646        if ( 'NEW_POST_ID' === $comment_post ) {
     647            $comment_post = self::factory()->post->create();
     648        } elseif ( 'NEW_POST' === $comment_post ) {
     649            $comment_post = self::factory()->post->create_and_get();
     650        } elseif ( 'POST_ID' === $comment_post ) {
     651            $comment_post = self::$post_id;
     652        } elseif ( 'POST' === $comment_post ) {
     653            $comment_post = self::factory()->post->get_object_by_id( self::$post_id );
     654        }
     655
     656        $comment_post_id = $comment_post instanceof WP_Post ? $comment_post->ID : $comment_post;
     657
     658        $expected  = "<input type='hidden' name='comment_post_ID' value='" . $comment_post_id . "' id='comment_post_ID' />\n";
     659        $expected .= "<input type='hidden' name='comment_parent' id='comment_parent' value='0' />\n";
     660        $actual    = get_comment_id_fields( $comment_post );
     661
     662        $this->assertSame( $expected, $actual );
     663    }
     664
     665    /**
     666     * Data provider.
     667     *
     668     * @return array[]
     669     */
     670    public function data_parent_comments() {
     671        return array(
     672            'an unapproved parent comment (ID)'      => array(
     673                'replytocom'   => false,
     674                'comment_post' => 'POST_ID',
     675            ),
     676            'an approved parent comment on another post (ID)' => array(
     677                'replytocom'   => true,
     678                'comment_post' => 'NEW_POST_ID',
     679            ),
     680            'an unapproved parent comment on another post (ID)' => array(
     681                'replytocom'   => false,
     682                'comment_post' => 'NEW_POST_ID',
     683            ),
     684            'a parent comment ID that cannot be cast to an integer' => array(
     685                'replytocom'   => array( 'I cannot be cast to an integer.' ),
     686                'comment_post' => 'POST_ID',
     687            ),
     688            'an unapproved parent comment (WP_Post)' => array(
     689                'replytocom'   => false,
     690                'comment_post' => 'POST',
     691            ),
     692            'an approved parent comment on another post (WP_Post)' => array(
     693                'replytocom'   => true,
     694                'comment_post' => 'NEW_POST',
     695            ),
     696            'an unapproved parent comment on another post (WP_Post)' => array(
     697                'replytocom'   => false,
     698                'comment_post' => 'NEW_POST',
     699            ),
     700            'a parent comment WP_Post that cannot be cast to an integer' => array(
     701                'replytocom'   => array( 'I cannot be cast to an integer.' ),
     702                'comment_post' => 'POST',
     703            ),
     704        );
     705    }
     706
     707    /**
     708     * Helper function to create a comment with an approval status.
     709     *
     710     * @since 6.2.0
     711     *
     712     * @param bool $approved Whether or not the comment is approved.
     713     * @return int The comment ID.
     714     */
     715    public function create_comment_with_approval_status( $approved ) {
     716        return self::factory()->comment->create(
     717            array(
     718                'comment_post_ID'  => self::$post_id,
     719                'comment_approved' => ( $approved ) ? '1' : '0',
     720            )
     721        );
     722    }
     723
     724    /**
     725     * Tests that _get_comment_reply_id() returns the expected value.
     726     *
     727     * @ticket 53962
     728     *
     729     * @dataProvider data_get_comment_reply_id
     730     *
     731     * @covers ::_get_comment_reply_id
     732     *
     733     * @param int|bool|null $replytocom A comment ID (int), whether to generate an approved (true) or unapproved (false) comment,
     734     *                                  or null not to create a comment.
     735     * @param string|int    $post       The post the comment thread is being displayed for.
     736     *                                  Accepts 'POST_ID', 'POST', or an integer post ID.
     737     * @param int           $expected   The expected result.
     738     */
     739    public function test_get_comment_reply_id( $replytocom, $post, $expected ) {
     740        if ( false === $replytocom ) {
     741            unset( $_GET['replytocom'] );
     742        } else {
     743            $_GET['replytocom'] = $this->create_comment_with_approval_status( (bool) $replytocom );
     744        }
     745
     746        if ( 'POST_ID' === $post ) {
     747            $post = self::$post_id;
     748        } elseif ( 'POST' === $post ) {
     749            $post = self::factory()->post->get_object_by_id( self::$post_id );
     750        }
     751
     752        if ( 'replytocom' === $expected ) {
     753            $expected = $_GET['replytocom'];
     754        }
     755
     756        $this->assertSame( $expected, _get_comment_reply_id( $post ) );
     757    }
     758
     759    /**
     760     * Data provider.
     761     *
     762     * @return array[]
     763     */
     764    public function data_get_comment_reply_id() {
     765        return array(
     766            'no comment ID set ($_GET["replytocom"])'     => array(
     767                'replytocom' => false,
     768                'post'       => 0,
     769                'expected'   => 0,
     770            ),
     771            'a non-numeric comment ID'                    => array(
     772                'replytocom' => 'three',
     773                'post'       => 0,
     774                'expected'   => 0,
     775            ),
     776            'a non-existent comment ID'                   => array(
     777                'replytocom' => -999999,
     778                'post'       => 0,
     779                'expected'   => 0,
     780            ),
     781            'an unapproved comment'                       => array(
     782                'replytocom' => false,
     783                'post'       => 0,
     784                'expected'   => 0,
     785            ),
     786            'a post that does not match the parent'       => array(
     787                'replytocom' => false,
     788                'post'       => -999999,
     789                'expected'   => 0,
     790            ),
     791            'an approved comment and the correct post ID' => array(
     792                'replytocom' => true,
     793                'post'       => 'POST_ID',
     794                'expected'   => 'replytocom',
     795            ),
     796            'an approved comment and the correct WP_Post object' => array(
     797                'replytocom' => true,
     798                'post'       => 'POST',
     799                'expected'   => 'replytocom',
     800            ),
     801        );
    378802    }
    379803
  • trunk/tests/phpunit/tests/comment/wpHandleCommentSubmission.php

    r54701 r55369  
    883883        $this->assertEquals( self::$post->ID, $second_comment->comment_post_ID );
    884884    }
     885
     886    /**
     887     * Tests that wp_handle_comment_submission() only allows replying to
     888     * an approved parent comment.
     889     *
     890     * @ticket 53962
     891     *
     892     * @dataProvider data_should_only_allow_replying_to_an_approved_parent_comment
     893     *
     894     * @param int $approved Whether the parent comment is approved.
     895     */
     896    public function test_should_only_allow_replying_to_an_approved_parent_comment( $approved ) {
     897        wp_set_current_user( self::$editor_id );
     898
     899        $comment_parent = self::factory()->comment->create(
     900            array(
     901                'comment_post_ID'  => self::$post->ID,
     902                'comment_approved' => $approved,
     903            )
     904        );
     905
     906        $comment = wp_handle_comment_submission(
     907            array(
     908                'comment_post_ID'      => self::$post->ID,
     909                'comment_author'       => 'A comment author',
     910                'comment_author_email' => 'comment_author@example.org',
     911                'comment'              => 'Howdy, comment!',
     912                'comment_parent'       => $comment_parent,
     913            )
     914        );
     915
     916        if ( $approved ) {
     917            $this->assertInstanceOf(
     918                'WP_Comment',
     919                $comment,
     920                'The comment was not submitted.'
     921            );
     922        } else {
     923            $this->assertWPError( $comment, 'The comment was submitted.' );
     924            $this->assertSame(
     925                'comment_reply_to_unapproved_comment',
     926                $comment->get_error_code(),
     927                'The wrong error code was returned.'
     928            );
     929        }
     930    }
     931
     932    /**
     933     * Data provider.
     934     *
     935     * @return array[]
     936     */
     937    public function data_should_only_allow_replying_to_an_approved_parent_comment() {
     938        return array(
     939            'an approved parent comment'   => array( 'approved' => 1 ),
     940            'an unapproved parent comment' => array( 'approved' => 0 ),
     941        );
     942    }
     943
     944    /**
     945     * Tests that wp_handle_comment_submission() only allows replying to
     946     * an existing parent comment.
     947     *
     948     * @ticket 53962
     949     *
     950     * @dataProvider data_should_only_allow_replying_to_an_existing_parent_comment
     951     *
     952     * @param bool $exists Whether the parent comment exists.
     953     */
     954    public function test_should_only_allow_replying_to_an_existing_parent_comment( $exists ) {
     955        wp_set_current_user( self::$editor_id );
     956
     957        $parent_comment = -99999;
     958
     959        if ( $exists ) {
     960            $parent_comment = self::factory()->comment->create(
     961                array(
     962                    'comment_post_ID'  => self::$post->ID,
     963                    'comment_approved' => 1,
     964                )
     965            );
     966        }
     967
     968        $comment = wp_handle_comment_submission(
     969            array(
     970                'comment_post_ID'      => self::$post->ID,
     971                'comment_author'       => 'A comment author',
     972                'comment_author_email' => 'comment_author@example.org',
     973                'comment'              => 'Howdy, comment!',
     974                'comment_parent'       => $parent_comment,
     975            )
     976        );
     977
     978        if ( $exists ) {
     979            $this->assertInstanceOf(
     980                'WP_Comment',
     981                $comment,
     982                'The comment was not submitted.'
     983            );
     984        } else {
     985            $this->assertWPError( $comment, 'The comment was submitted.' );
     986            $this->assertSame(
     987                'comment_reply_to_unapproved_comment',
     988                $comment->get_error_code(),
     989                'The wrong error code was returned.'
     990            );
     991        }
     992    }
     993
     994    /**
     995     * Data provider.
     996     *
     997     * @return array[]
     998     */
     999    public function data_should_only_allow_replying_to_an_existing_parent_comment() {
     1000        return array(
     1001            'an existing parent comment'    => array( 'exists' => true ),
     1002            'a non-existent parent comment' => array( 'exists' => false ),
     1003        );
     1004    }
    8851005}
Note: See TracChangeset for help on using the changeset viewer.