Make WordPress Core


Ignore:
Timestamp:
10/20/2025 07:20:17 PM (4 months ago)
Author:
adamsilverstein
Message:

Editor: Introduce the PHP-related code for Notes.

Bring the PHP part of the new Notes feature into core for the 6.9 release. See related Gutenberg Issue: https://github.com/WordPress/gutenberg/issues/71826. These changes do not impact any user facing functionality, they simply prepare core for the JavaScript functionality that will come over in a separate sync.

Overview of changes:

  • Ensure Notes are not included in comment counts
  • Enable the note type (REST API)
  • Adjust capabilities so edit_post cap implies ability to edit notes
  • Enable empty and duplicate notes for resolve/re-open actions
  • Add control over notes with post type supports check
  • Register new note resolution status meta

Props: ristojovanovic, adamsilverstein, jeffpaul, wildworks, mamaduka, swissspidy, timothyblynjacobs, kadamwhite.
Fixes #64096.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/rest-api/rest-comments-controller.php

    r60937 r60987  
    1313    protected static $editor_id;
    1414    protected static $moderator_id;
     15    protected static $contributor_id;
    1516    protected static $subscriber_id;
    1617    protected static $author_id;
     18    protected static $user_ids = array();
    1719
    1820    protected static $post_id;
     
    2729    protected static $total_comments = 30;
    2830    protected static $per_page       = 50;
     31    protected static $num_notes      = 10;
    2932
    3033    protected $endpoint;
     
    4043        );
    4144
    42         self::$superadmin_id = $factory->user->create(
     45        self::$superadmin_id  = $factory->user->create(
    4346            array(
    4447                'role'       => 'administrator',
     
    4649            )
    4750        );
    48         self::$admin_id      = $factory->user->create(
     51        self::$admin_id       = $factory->user->create(
    4952            array(
    5053                'role' => 'administrator',
    5154            )
    5255        );
    53         self::$editor_id     = $factory->user->create(
     56        self::$editor_id      = $factory->user->create(
    5457            array(
    5558                'role' => 'editor',
    5659            )
    5760        );
    58         self::$moderator_id  = $factory->user->create(
     61        self::$moderator_id   = $factory->user->create(
    5962            array(
    6063                'role' => 'comment_moderator',
    6164            )
    6265        );
    63         self::$subscriber_id = $factory->user->create(
     66        self::$contributor_id = $factory->user->create(
     67            array(
     68                'role' => 'contributor',
     69            )
     70        );
     71        self::$subscriber_id  = $factory->user->create(
    6472            array(
    6573                'role' => 'subscriber',
    6674            )
    6775        );
    68         self::$author_id     = $factory->user->create(
     76        self::$author_id      = $factory->user->create(
    6977            array(
    7078                'role'         => 'author',
     
    112120                'user_id'          => self::$subscriber_id,
    113121            )
     122        );
     123
     124        self::$user_ids = array(
     125            'superadmin'    => self::$superadmin_id,
     126            'administrator' => self::$admin_id,
     127            'editor'        => self::$editor_id,
     128            'moderator'     => self::$moderator_id,
     129            'contributor'   => self::$contributor_id,
     130            'subscriber'    => self::$subscriber_id,
     131            'author'        => self::$author_id,
    114132        );
    115133
     
    132150        self::delete_user( self::$editor_id );
    133151        self::delete_user( self::$moderator_id );
     152        self::delete_user( self::$contributor_id );
    134153        self::delete_user( self::$subscriber_id );
    135154        self::delete_user( self::$author_id );
     
    36223641        );
    36233642    }
     3643
     3644    /**
     3645     * Create a test post with note.
     3646     *
     3647     * @param int $user_id Post author's user ID.
     3648     * @return int Post ID.
     3649     */
     3650    protected function create_test_post_with_note( $role ) {
     3651        $user_id = self::$user_ids[ $role ];
     3652        $post_id = self::factory()->post->create(
     3653            array(
     3654                'post_title'   => 'Test Post for Notes',
     3655                'post_content' => 'This is a test post to check note permissions.',
     3656                'post_status'  => 'contributor' === $role ? 'draft' : 'publish',
     3657                'post_author'  => $user_id,
     3658            )
     3659        );
     3660
     3661        for ( $i = 0; $i < self::$num_notes; $i++ ) {
     3662            self::factory()->comment->create(
     3663                array(
     3664                    'comment_post_ID'  => $post_id,
     3665                    'comment_type'     => 'note',
     3666                    'comment_approved' => 0 === $i % 2 ? 1 : 0,
     3667                )
     3668            );
     3669        }
     3670
     3671        return $post_id;
     3672    }
     3673
     3674    /**
     3675     * @ticket 64096
     3676     */
     3677    public function test_cannot_read_note_without_post_type_support() {
     3678        register_post_type(
     3679            'no-notes',
     3680            array(
     3681                'label'        => 'No Notes',
     3682                'supports'     => array( 'title', 'editor', 'author', 'comments' ),
     3683                'show_in_rest' => true,
     3684                'public'       => true,
     3685            )
     3686        );
     3687
     3688        create_initial_rest_routes();
     3689        wp_set_current_user( self::$admin_id );
     3690
     3691        $post_id = self::factory()->post->create( array( 'post_type' => 'no-notes' ) );
     3692        $request = new WP_REST_Request( 'GET', '/wp/v2/comments' );
     3693        $request->set_param( 'post', $post_id );
     3694        $request->set_param( 'type', 'note' );
     3695        $request->set_param( 'context', 'edit' );
     3696
     3697        $response = rest_get_server()->dispatch( $request );
     3698        $this->assertErrorResponse( 'rest_comment_not_supported_post_type', $response, 403 );
     3699
     3700        _unregister_post_type( 'no-notes' );
     3701    }
     3702
     3703    /**
     3704     * @ticket 64096
     3705     */
     3706    public function test_create_note_require_login() {
     3707        wp_set_current_user( 0 );
     3708
     3709        $post_id = self::factory()->post->create();
     3710        $request = new WP_REST_Request( 'POST', '/wp/v2/comments' );
     3711        $request->set_param( 'post', $post_id );
     3712        $request->set_param( 'type', 'note' );
     3713        $response = rest_get_server()->dispatch( $request );
     3714
     3715        $this->assertErrorResponse( 'rest_comment_login_required', $response, 401 );
     3716    }
     3717
     3718    /**
     3719     * @ticket 64096
     3720     */
     3721    public function test_cannot_create_note_without_post_type_support() {
     3722        register_post_type(
     3723            'no-note',
     3724            array(
     3725                'label'        => 'No Notes',
     3726                'supports'     => array( 'title', 'editor', 'author', 'comments' ),
     3727                'show_in_rest' => true,
     3728                'public'       => true,
     3729            )
     3730        );
     3731
     3732        wp_set_current_user( self::$admin_id );
     3733        $post_id = self::factory()->post->create( array( 'post_type' => 'no-note' ) );
     3734        $params  = array(
     3735            'post'         => $post_id,
     3736            'author_name'  => 'Ishmael',
     3737            'author_email' => 'herman-melville@earthlink.net',
     3738            'author_url'   => 'https://en.wikipedia.org/wiki/Herman_Melville',
     3739            'content'      => 'Call me Ishmael.',
     3740            'author'       => self::$admin_id,
     3741            'type'         => 'note',
     3742        );
     3743
     3744        $request = new WP_REST_Request( 'POST', '/wp/v2/comments' );
     3745        $request->add_header( 'Content-Type', 'application/json' );
     3746        $request->set_body( wp_json_encode( $params ) );
     3747        $response = rest_get_server()->dispatch( $request );
     3748        $this->assertErrorResponse( 'rest_comment_not_supported_post_type', $response, 403 );
     3749
     3750        _unregister_post_type( 'no-note' );
     3751    }
     3752
     3753    /**
     3754     * @ticket 64096
     3755     */
     3756    public function test_create_note_draft_post() {
     3757        wp_set_current_user( self::$editor_id );
     3758        $draft_id = self::factory()->post->create(
     3759            array(
     3760                'post_status' => 'draft',
     3761            )
     3762        );
     3763        $params   = array(
     3764            'post'         => $draft_id,
     3765            'author_name'  => 'Ishmael',
     3766            'author_email' => 'herman-melville@earthlink.net',
     3767            'author_url'   => 'https://en.wikipedia.org/wiki/Herman_Melville',
     3768            'content'      => 'Call me Ishmael.',
     3769            'author'       => self::$editor_id,
     3770            'type'         => 'note',
     3771        );
     3772
     3773        $request = new WP_REST_Request( 'POST', '/wp/v2/comments' );
     3774        $request->add_header( 'Content-Type', 'application/json' );
     3775        $request->set_body( wp_json_encode( $params ) );
     3776
     3777        $response    = rest_get_server()->dispatch( $request );
     3778        $data        = $response->get_data();
     3779        $new_comment = get_comment( $data['id'] );
     3780        $this->assertSame( 'Call me Ishmael.', $new_comment->comment_content );
     3781        $this->assertSame( 'note', $new_comment->comment_type );
     3782    }
     3783
     3784    /**
     3785     * @ticket 64096
     3786     */
     3787    public function test_create_note_status() {
     3788        wp_set_current_user( self::$author_id );
     3789        $post_id = self::factory()->post->create( array( 'post_author' => self::$author_id ) );
     3790
     3791        $params = array(
     3792            'post'         => $post_id,
     3793            'author_name'  => 'Ishmael',
     3794            'author_email' => 'herman-melville@earthlink.net',
     3795            'author_url'   => 'https://en.wikipedia.org/wiki/Herman_Melville',
     3796            'content'      => 'Comic Book Guy',
     3797            'author'       => self::$author_id,
     3798            'type'         => 'note',
     3799            'status'       => 'hold',
     3800        );
     3801
     3802        $request = new WP_REST_Request( 'POST', '/wp/v2/comments' );
     3803        $request->add_header( 'Content-Type', 'application/json' );
     3804        $request->set_body( wp_json_encode( $params ) );
     3805
     3806        $response    = rest_get_server()->dispatch( $request );
     3807        $data        = $response->get_data();
     3808        $new_comment = get_comment( $data['id'] );
     3809
     3810        $this->assertSame( '0', $new_comment->comment_approved );
     3811        $this->assertSame( 'note', $new_comment->comment_type );
     3812    }
     3813
     3814    /**
     3815     * @ticket 64096
     3816     */
     3817    public function test_cannot_create_with_non_valid_comment_type() {
     3818        wp_set_current_user( self::$admin_id );
     3819        $post_id = $this->factory->post->create();
     3820
     3821        $params = array(
     3822            'post'         => $post_id,
     3823            'author_name'  => 'Ishmael',
     3824            'author_email' => 'herman-melville@earthlink.net',
     3825            'author_url'   => 'https://en.wikipedia.org/wiki/Herman_Melville',
     3826            'content'      => 'Comic Book Guy',
     3827            'author'       => self::$admin_id,
     3828            'type'         => 'review',
     3829        );
     3830
     3831        $request = new WP_REST_Request( 'POST', '/wp/v2/comments' );
     3832        $request->add_header( 'Content-Type', 'application/json' );
     3833        $request->set_body( wp_json_encode( $params ) );
     3834        $response = rest_get_server()->dispatch( $request );
     3835
     3836        $this->assertErrorResponse( 'rest_invalid_comment_type', $response, 400 );
     3837    }
     3838
     3839    /**
     3840     * @ticket 64096
     3841     */
     3842    public function test_create_assigns_default_type() {
     3843        wp_set_current_user( self::$editor_id );
     3844        $post_id = self::factory()->post->create();
     3845
     3846        $params = array(
     3847            'post'         => $post_id,
     3848            'author_name'  => 'Ishmael',
     3849            'author_email' => 'herman-melville@earthlink.net',
     3850            'author_url'   => 'https://en.wikipedia.org/wiki/Herman_Melville',
     3851            'content'      => 'Comic Book Guy',
     3852            'author'       => self::$editor_id,
     3853        );
     3854
     3855        $request = new WP_REST_Request( 'POST', '/wp/v2/comments' );
     3856        $request->add_header( 'Content-Type', 'application/json' );
     3857        $request->set_body( wp_json_encode( $params ) );
     3858
     3859        $response    = rest_get_server()->dispatch( $request );
     3860        $data        = $response->get_data();
     3861        $new_comment = get_comment( $data['id'] );
     3862
     3863        $this->assertSame( 'comment', $new_comment->comment_type );
     3864    }
     3865
     3866    /**
     3867     * @dataProvider data_note_status_provider
     3868     * @ticket 64096
     3869     */
     3870    public function test_create_empty_note_with_resolution_meta( $status ) {
     3871        wp_set_current_user( self::$editor_id );
     3872        $post_id = self::factory()->post->create();
     3873        $params  = array(
     3874            'post'         => $post_id,
     3875            'author_name'  => 'Editor',
     3876            'author_email' => 'editor@example.com',
     3877            'author_url'   => 'https://example.com',
     3878            'author'       => self::$editor_id,
     3879            'type'         => 'note',
     3880            'content'      => '',
     3881            'meta'         => array(
     3882                '_wp_note_status' => $status,
     3883            ),
     3884        );
     3885        $request = new WP_REST_Request( 'POST', '/wp/v2/comments' );
     3886        $request->add_header( 'Content-Type', 'application/json' );
     3887        $request->set_body( wp_json_encode( $params ) );
     3888
     3889        $response = rest_get_server()->dispatch( $request );
     3890        $this->assertSame( 201, $response->get_status() );
     3891    }
     3892
     3893    /**
     3894     * @ticket 64096
     3895     */
     3896    public function test_cannot_create_empty_note_without_resolution_meta() {
     3897        wp_set_current_user( self::$editor_id );
     3898        $post_id = self::factory()->post->create();
     3899        $params  = array(
     3900            'post'         => $post_id,
     3901            'author_name'  => 'Editor',
     3902            'author_email' => 'editor@example.com',
     3903            'author_url'   => 'https://example.com',
     3904            'author'       => self::$editor_id,
     3905            'type'         => 'note',
     3906            'content'      => '',
     3907        );
     3908        $request = new WP_REST_Request( 'POST', '/wp/v2/comments' );
     3909        $request->add_header( 'Content-Type', 'application/json' );
     3910        $request->set_body( wp_json_encode( $params ) );
     3911        $response = rest_get_server()->dispatch( $request );
     3912        $this->assertErrorResponse( 'rest_comment_content_invalid', $response, 400 );
     3913    }
     3914
     3915    /**
     3916     * @ticket 64096
     3917     */
     3918    public function test_cannot_create_empty_note_with_invalid_resolution_meta() {
     3919        wp_set_current_user( self::$editor_id );
     3920        $post_id = self::factory()->post->create();
     3921        $params  = array(
     3922            'post'         => $post_id,
     3923            'author_name'  => 'Editor',
     3924            'author_email' => 'editor@example.com',
     3925            'author_url'   => 'https://example.com',
     3926            'author'       => self::$editor_id,
     3927            'type'         => 'note',
     3928            'content'      => '',
     3929            'meta'         => array(
     3930                '_wp_note_status' => 'invalid',
     3931            ),
     3932        );
     3933        $request = new WP_REST_Request( 'POST', '/wp/v2/comments' );
     3934        $request->add_header( 'Content-Type', 'application/json' );
     3935        $request->set_body( wp_json_encode( $params ) );
     3936        $response = rest_get_server()->dispatch( $request );
     3937        $this->assertErrorResponse( 'rest_comment_content_invalid', $response, 400 );
     3938    }
     3939
     3940    /**
     3941     * @ticket 64096
     3942     */
     3943    public function test_create_duplicate_note() {
     3944        wp_set_current_user( self::$editor_id );
     3945        $post_id = self::factory()->post->create();
     3946
     3947        for ( $i = 0; $i < 2; $i++ ) {
     3948            $params  = array(
     3949                'post'         => $post_id,
     3950                'author_name'  => 'Editor',
     3951                'author_email' => 'editor@example.com',
     3952                'author_url'   => 'https://example.com',
     3953                'author'       => self::$editor_id,
     3954                'type'         => 'note',
     3955                'content'      => 'Doplicated comment',
     3956            );
     3957            $request = new WP_REST_Request( 'POST', '/wp/v2/comments' );
     3958            $request->add_header( 'Content-Type', 'application/json' );
     3959            $request->set_body( wp_json_encode( $params ) );
     3960            $response = rest_get_server()->dispatch( $request );
     3961            $this->assertSame( 201, $response->get_status() );
     3962        }
     3963    }
     3964
     3965    /**
     3966     * @dataProvider data_note_get_items_permissions_data_provider
     3967     * @ticket 64096
     3968     */
     3969    public function test_note_get_items_permissions_edit_context( $role, $post_author_role, $can_read ) {
     3970        wp_set_current_user( self::$user_ids[ $role ] );
     3971        $post_id = $this->create_test_post_with_note( $post_author_role );
     3972
     3973        $request = new WP_REST_Request( 'GET', '/wp/v2/comments' );
     3974        $request->set_param( 'post', $post_id );
     3975        $request->set_param( 'type', 'note' );
     3976        $request->set_param( 'status', 'all' );
     3977        $request->set_param( 'per_page', 100 );
     3978        $request->set_param( 'context', 'edit' );
     3979        $response = rest_get_server()->dispatch( $request );
     3980
     3981        if ( $can_read ) {
     3982            $comments = $response->get_data();
     3983            $this->assertEquals( self::$num_notes, count( $comments ) );
     3984        } else {
     3985            $this->assertErrorResponse( 'rest_forbidden_context', $response, 403 );
     3986        }
     3987
     3988        wp_delete_post( $post_id, true );
     3989    }
     3990
     3991    /**
     3992     * @ticket 64096
     3993     */
     3994    public function test_note_get_items_permissions_mixed_post_authors() {
     3995        $author_post_id = $this->create_test_post_with_note( 'author' );
     3996        $editor_post_id = $this->create_test_post_with_note( 'editor' );
     3997
     3998        wp_set_current_user( self::$author_id );
     3999
     4000        $request = new WP_REST_Request( 'GET', '/wp/v2/comments' );
     4001        $request->set_param( 'post', array( $author_post_id, $editor_post_id ) );
     4002        $request->set_param( 'type', 'note' );
     4003        $request->set_param( 'status', 'all' );
     4004        $request->set_param( 'per_page', 100 );
     4005        $request->set_param( 'context', 'edit' );
     4006        $response = rest_get_server()->dispatch( $request );
     4007
     4008        $this->assertErrorResponse( 'rest_forbidden_context', $response, 403 );
     4009
     4010        wp_delete_post( $author_post_id, true );
     4011        wp_delete_post( $editor_post_id, true );
     4012    }
     4013
     4014    /**
     4015     * @dataProvider data_note_get_items_permissions_data_provider
     4016     * @ticket 64096
     4017     */
     4018    public function test_note_get_item_permissions_edit_context( $role, $post_author_role, $can_read ) {
     4019        wp_set_current_user( self::$user_ids[ $role ] );
     4020
     4021        $post_id = self::factory()->post->create(
     4022            array(
     4023                'post_title'   => 'Test Post for Block Comments',
     4024                'post_content' => 'This is a test post to check block comment permissions.',
     4025                'post_status'  => 'contributor' === $post_author_role ? 'draft' : 'publish',
     4026                'post_author'  => self::$user_ids[ $post_author_role ],
     4027            )
     4028        );
     4029
     4030        $comment_id = self::factory()->comment->create(
     4031            array(
     4032                'comment_post_ID'  => $post_id,
     4033                'comment_type'     => 'note',
     4034                // Test with unapproved comment, which is more restrictive.
     4035                'comment_approved' => 0,
     4036                'user_id'          => self::$user_ids[ $post_author_role ],
     4037            )
     4038        );
     4039
     4040        $request = new WP_REST_Request( 'GET', '/wp/v2/comments/' . $comment_id );
     4041        $request->set_param( 'context', 'edit' );
     4042        $response = rest_get_server()->dispatch( $request );
     4043
     4044        if ( $can_read ) {
     4045            $comment = $response->get_data();
     4046            $this->assertEquals( $comment_id, $comment['id'] );
     4047        } else {
     4048            $this->assertErrorResponse( 'rest_forbidden_context', $response, 403 );
     4049        }
     4050
     4051        wp_delete_post( $post_id, true );
     4052    }
     4053
     4054    public function data_note_get_items_permissions_data_provider() {
     4055        return array(
     4056            'Administrator can see note on other posts'  => array( 'administrator', 'author', true ),
     4057            'Editor can see note on other posts'         => array( 'editor', 'contributor', true ),
     4058            'Author cannot see note on other posts'      => array( 'author', 'editor', false ),
     4059            'Contributor cannot see note on other posts' => array( 'contributor', 'author', false ),
     4060            'Subscriber cannot see note'                 => array( 'subscriber', 'author', false ),
     4061            'Author can see note on own post'            => array( 'author', 'author', true ),
     4062            'Contributor can see note on own post'       => array( 'contributor', 'contributor', true ),
     4063        );
     4064    }
     4065
     4066    public function data_note_status_provider() {
     4067        return array(
     4068            'resolved' => array( 'resolved' ),
     4069            'reopen'   => array( 'reopen' ),
     4070        );
     4071    }
    36244072}
Note: See TracChangeset for help on using the changeset viewer.