| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * REST API: WP_REST_Comments_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 4.7.0 8 */ 9 10 /** 11 * Core controller used to access comments via the REST API. 12 * 13 * @since 4.7.0 14 * 15 * @see WP_REST_Controller 16 */ 17 class WP_REST_Comments_Controller extends WP_REST_Controller { 18 19 /** 20 * Instance of a comment meta fields object. 21 * 22 * @since 4.7.0 23 * @var WP_REST_Comment_Meta_Fields 24 */ 25 protected $meta; 26 27 /** 28 * Constructor. 29 * 30 * @since 4.7.0 31 */ 32 public function __construct() { 33 $this->namespace = 'wp/v2'; 34 $this->rest_base = 'comments'; 35 36 $this->meta = new WP_REST_Comment_Meta_Fields(); 37 } 38 39 /** 40 * Registers the routes for comments. 41 * 42 * @since 4.7.0 43 * 44 * @see register_rest_route() 45 */ 46 public function register_routes() { 47 48 register_rest_route( 49 $this->namespace, 50 '/' . $this->rest_base, 51 array( 52 array( 53 'methods' => WP_REST_Server::READABLE, 54 'callback' => array( $this, 'get_items' ), 55 'permission_callback' => array( $this, 'get_items_permissions_check' ), 56 'args' => $this->get_collection_params(), 57 ), 58 array( 59 'methods' => WP_REST_Server::CREATABLE, 60 'callback' => array( $this, 'create_item' ), 61 'permission_callback' => array( $this, 'create_item_permissions_check' ), 62 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), 63 ), 64 'schema' => array( $this, 'get_public_item_schema' ), 65 ) 66 ); 67 68 register_rest_route( 69 $this->namespace, 70 '/' . $this->rest_base . '/(?P<id>[\d]+)', 71 array( 72 'args' => array( 73 'id' => array( 74 'description' => __( 'Unique identifier for the comment.' ), 75 'type' => 'integer', 76 ), 77 ), 78 array( 79 'methods' => WP_REST_Server::READABLE, 80 'callback' => array( $this, 'get_item' ), 81 'permission_callback' => array( $this, 'get_item_permissions_check' ), 82 'args' => array( 83 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 84 'password' => array( 85 'description' => __( 'The password for the parent post of the comment (if the post is password protected).' ), 86 'type' => 'string', 87 ), 88 ), 89 ), 90 array( 91 'methods' => WP_REST_Server::EDITABLE, 92 'callback' => array( $this, 'update_item' ), 93 'permission_callback' => array( $this, 'update_item_permissions_check' ), 94 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), 95 ), 96 array( 97 'methods' => WP_REST_Server::DELETABLE, 98 'callback' => array( $this, 'delete_item' ), 99 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 100 'args' => array( 101 'force' => array( 102 'type' => 'boolean', 103 'default' => false, 104 'description' => __( 'Whether to bypass Trash and force deletion.' ), 105 ), 106 'password' => array( 107 'description' => __( 'The password for the parent post of the comment (if the post is password protected).' ), 108 'type' => 'string', 109 ), 110 ), 111 ), 112 'schema' => array( $this, 'get_public_item_schema' ), 113 ) 114 ); 115 } 116 117 /** 118 * Checks if a given request has access to read comments. 119 * 120 * @since 4.7.0 121 * 122 * @param WP_REST_Request $request Full details about the request. 123 * @return true|WP_Error True if the request has read access, error object otherwise. 124 */ 125 public function get_items_permissions_check( $request ) { 126 $is_note = 'note' === $request['type']; 127 $is_edit_context = 'edit' === $request['context']; 128 $protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' ); 129 $forbidden_params = array(); 130 131 if ( ! empty( $request['post'] ) ) { 132 foreach ( (array) $request['post'] as $post_id ) { 133 $post = get_post( $post_id ); 134 135 if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post, $request ) ) { 136 return new WP_Error( 137 'rest_cannot_read_post', 138 __( 'Sorry, you are not allowed to read the post for this comment.' ), 139 array( 'status' => rest_authorization_required_code() ) 140 ); 141 } elseif ( 0 === $post_id && ! current_user_can( 'moderate_comments' ) ) { 142 return new WP_Error( 143 'rest_cannot_read', 144 __( 'Sorry, you are not allowed to read comments without a post.' ), 145 array( 'status' => rest_authorization_required_code() ) 146 ); 147 } 148 149 if ( $post && $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) { 150 if ( current_user_can( 'edit_post', $post->ID ) ) { 151 return new WP_Error( 152 'rest_comment_not_supported_post_type', 153 __( 'Sorry, this post type does not support notes.' ), 154 array( 'status' => 403 ) 155 ); 156 } 157 158 foreach ( $protected_params as $param ) { 159 if ( 'status' === $param ) { 160 if ( 'approve' !== $request[ $param ] ) { 161 $forbidden_params[] = $param; 162 } 163 } elseif ( 'type' === $param ) { 164 if ( 'comment' !== $request[ $param ] ) { 165 $forbidden_params[] = $param; 166 } 167 } elseif ( ! empty( $request[ $param ] ) ) { 168 $forbidden_params[] = $param; 169 } 170 } 171 return new WP_Error( 172 'rest_forbidden_param', 173 /* translators: %s: List of forbidden parameters. */ 174 sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ), 175 array( 'status' => rest_authorization_required_code() ) 176 ); 177 } 178 } 179 } 180 181 // Re-map edit context capabilities when requesting `note` for a post. 182 if ( $is_edit_context && $is_note && ! empty( $request['post'] ) ) { 183 foreach ( (array) $request['post'] as $post_id ) { 184 if ( ! current_user_can( 'edit_post', $post_id ) ) { 185 return new WP_Error( 186 'rest_forbidden_context', 187 __( 'Sorry, you are not allowed to edit comments.' ), 188 array( 'status' => rest_authorization_required_code() ) 189 ); 190 } 191 } 192 } elseif ( $is_edit_context && ! current_user_can( 'moderate_comments' ) ) { 193 return new WP_Error( 194 'rest_forbidden_context', 195 __( 'Sorry, you are not allowed to edit comments.' ), 196 array( 'status' => rest_authorization_required_code() ) 197 ); 198 } 199 200 if ( ! current_user_can( 'edit_posts' ) ) { 201 foreach ( $protected_params as $param ) { 202 if ( 'status' === $param ) { 203 if ( 'approve' !== $request[ $param ] ) { 204 $forbidden_params[] = $param; 205 } 206 } elseif ( 'type' === $param ) { 207 if ( 'comment' !== $request[ $param ] ) { 208 $forbidden_params[] = $param; 209 } 210 } elseif ( ! empty( $request[ $param ] ) ) { 211 $forbidden_params[] = $param; 212 } 213 } 214 215 if ( ! empty( $forbidden_params ) ) { 216 return new WP_Error( 217 'rest_forbidden_param', 218 /* translators: %s: List of forbidden parameters. */ 219 sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ), 220 array( 'status' => rest_authorization_required_code() ) 221 ); 222 } 223 } 224 225 return true; 226 } 227 228 /** 229 * Retrieves a list of comment items. 230 * 231 * @since 4.7.0 232 * 233 * @param WP_REST_Request $request Full details about the request. 234 * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. 235 */ 236 public function get_items( $request ) { 237 238 // Retrieve the list of registered collection query parameters. 239 $registered = $this->get_collection_params(); 240 241 /* 242 * This array defines mappings between public API query parameters whose 243 * values are accepted as-passed, and their internal WP_Query parameter 244 * name equivalents (some are the same). Only values which are also 245 * present in $registered will be set. 246 */ 247 $parameter_mappings = array( 248 'author' => 'author__in', 249 'author_email' => 'author_email', 250 'author_exclude' => 'author__not_in', 251 'exclude' => 'comment__not_in', 252 'include' => 'comment__in', 253 'offset' => 'offset', 254 'order' => 'order', 255 'parent' => 'parent__in', 256 'parent_exclude' => 'parent__not_in', 257 'per_page' => 'number', 258 'post' => 'post__in', 259 'search' => 'search', 260 'status' => 'status', 261 'type' => 'type', 262 ); 263 264 $prepared_args = array(); 265 266 /* 267 * For each known parameter which is both registered and present in the request, 268 * set the parameter's value on the query $prepared_args. 269 */ 270 foreach ( $parameter_mappings as $api_param => $wp_param ) { 271 if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { 272 $prepared_args[ $wp_param ] = $request[ $api_param ]; 273 } 274 } 275 276 // Ensure certain parameter values default to empty strings. 277 foreach ( array( 'author_email', 'search' ) as $param ) { 278 if ( ! isset( $prepared_args[ $param ] ) ) { 279 $prepared_args[ $param ] = ''; 280 } 281 } 282 283 if ( isset( $registered['orderby'] ) ) { 284 $prepared_args['orderby'] = $this->normalize_query_param( $request['orderby'] ); 285 } 286 287 $prepared_args['no_found_rows'] = false; 288 289 $prepared_args['update_comment_post_cache'] = true; 290 291 $prepared_args['date_query'] = array(); 292 293 // Set before into date query. Date query must be specified as an array of an array. 294 if ( isset( $registered['before'], $request['before'] ) ) { 295 $prepared_args['date_query'][0]['before'] = $request['before']; 296 } 297 298 // Set after into date query. Date query must be specified as an array of an array. 299 if ( isset( $registered['after'], $request['after'] ) ) { 300 $prepared_args['date_query'][0]['after'] = $request['after']; 301 } 302 303 if ( isset( $registered['page'] ) && empty( $request['offset'] ) ) { 304 $prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 ); 305 } 306 307 $is_head_request = $request->is_method( 'HEAD' ); 308 if ( $is_head_request ) { 309 // Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination. 310 $prepared_args['fields'] = 'ids'; 311 // Disable priming comment meta for HEAD requests to improve performance. 312 $prepared_args['update_comment_meta_cache'] = false; 313 } 314 315 /** 316 * Filters WP_Comment_Query arguments when querying comments via the REST API. 317 * 318 * @since 4.7.0 319 * 320 * @link https://developer.wordpress.org/reference/classes/wp_comment_query/ 321 * 322 * @param array $prepared_args Array of arguments for WP_Comment_Query. 323 * @param WP_REST_Request $request The REST API request. 324 */ 325 $prepared_args = apply_filters( 'rest_comment_query', $prepared_args, $request ); 326 327 $query = new WP_Comment_Query(); 328 $query_result = $query->query( $prepared_args ); 329 330 if ( ! $is_head_request ) { 331 $comments = array(); 332 333 foreach ( $query_result as $comment ) { 334 if ( ! $this->check_read_permission( $comment, $request ) ) { 335 continue; 336 } 337 338 $data = $this->prepare_item_for_response( $comment, $request ); 339 $comments[] = $this->prepare_response_for_collection( $data ); 340 } 341 } 342 343 $total_comments = (int) $query->found_comments; 344 $max_pages = (int) $query->max_num_pages; 345 346 if ( $total_comments < 1 ) { 347 // Out-of-bounds, run the query without pagination/offset to get the total count. 348 unset( $prepared_args['number'], $prepared_args['offset'] ); 349 350 $query = new WP_Comment_Query(); 351 $prepared_args['count'] = true; 352 $prepared_args['orderby'] = 'none'; 353 $prepared_args['update_comment_meta_cache'] = false; 354 355 $total_comments = $query->query( $prepared_args ); 356 $max_pages = (int) ceil( $total_comments / $request['per_page'] ); 357 } 358 359 $response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $comments ); 360 $response->header( 'X-WP-Total', (string) $total_comments ); 361 $response->header( 'X-WP-TotalPages', (string) $max_pages ); 362 363 $base = add_query_arg( urlencode_deep( $request->get_query_params() ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); 364 365 if ( $request['page'] > 1 ) { 366 $prev_page = $request['page'] - 1; 367 368 if ( $prev_page > $max_pages ) { 369 $prev_page = $max_pages; 370 } 371 372 $prev_link = add_query_arg( 'page', $prev_page, $base ); 373 $response->link_header( 'prev', $prev_link ); 374 } 375 376 if ( $max_pages > $request['page'] ) { 377 $next_page = $request['page'] + 1; 378 $next_link = add_query_arg( 'page', $next_page, $base ); 379 380 $response->link_header( 'next', $next_link ); 381 } 382 383 return $response; 384 } 385 386 /** 387 * Get the comment, if the ID is valid. 388 * 389 * @since 4.7.2 390 * 391 * @param int $id Supplied ID. 392 * @return WP_Comment|WP_Error Comment object if ID is valid, WP_Error otherwise. 393 */ 394 protected function get_comment( $id ) { 395 $error = new WP_Error( 396 'rest_comment_invalid_id', 397 __( 'Invalid comment ID.' ), 398 array( 'status' => 404 ) 399 ); 400 401 if ( (int) $id <= 0 ) { 402 return $error; 403 } 404 405 $id = (int) $id; 406 $comment = get_comment( $id ); 407 if ( empty( $comment ) ) { 408 return $error; 409 } 410 411 if ( ! empty( $comment->comment_post_ID ) ) { 412 $post = get_post( (int) $comment->comment_post_ID ); 413 414 if ( empty( $post ) ) { 415 return new WP_Error( 416 'rest_post_invalid_id', 417 __( 'Invalid post ID.' ), 418 array( 'status' => 404 ) 419 ); 420 } 421 } 422 423 return $comment; 424 } 425 426 /** 427 * Checks if a given request has access to read the comment. 428 * 429 * @since 4.7.0 430 * 431 * @param WP_REST_Request $request Full details about the request. 432 * @return true|WP_Error True if the request has read access for the item, error object otherwise. 433 */ 434 public function get_item_permissions_check( $request ) { 435 $comment = $this->get_comment( $request['id'] ); 436 if ( is_wp_error( $comment ) ) { 437 return $comment; 438 } 439 440 // Re-map edit context capabilities when requesting `note` type. 441 $edit_cap = 'note' === $comment->comment_type ? array( 'edit_comment', $comment->comment_ID ) : array( 'moderate_comments' ); 442 if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( ...$edit_cap ) ) { 443 return new WP_Error( 444 'rest_forbidden_context', 445 __( 'Sorry, you are not allowed to edit comments.' ), 446 array( 'status' => rest_authorization_required_code() ) 447 ); 448 } 449 450 $post = get_post( $comment->comment_post_ID ); 451 452 if ( ! $this->check_read_permission( $comment, $request ) ) { 453 return new WP_Error( 454 'rest_cannot_read', 455 __( 'Sorry, you are not allowed to read this comment.' ), 456 array( 'status' => rest_authorization_required_code() ) 457 ); 458 } 459 460 if ( $post && ! $this->check_read_post_permission( $post, $request ) ) { 461 return new WP_Error( 462 'rest_cannot_read_post', 463 __( 'Sorry, you are not allowed to read the post for this comment.' ), 464 array( 'status' => rest_authorization_required_code() ) 465 ); 466 } 467 468 return true; 469 } 470 471 /** 472 * Retrieves a comment. 473 * 474 * @since 4.7.0 475 * 476 * @param WP_REST_Request $request Full details about the request. 477 * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. 478 */ 479 public function get_item( $request ) { 480 $comment = $this->get_comment( $request['id'] ); 481 if ( is_wp_error( $comment ) ) { 482 return $comment; 483 } 484 485 $data = $this->prepare_item_for_response( $comment, $request ); 486 $response = rest_ensure_response( $data ); 487 488 return $response; 489 } 490 491 /** 492 * Checks if a given request has access to create a comment. 493 * 494 * @since 4.7.0 495 * 496 * @param WP_REST_Request $request Full details about the request. 497 * @return true|WP_Error True if the request has access to create items, error object otherwise. 498 */ 499 public function create_item_permissions_check( $request ) { 500 $is_note = ! empty( $request['type'] ) && 'note' === $request['type']; 501 502 if ( ! is_user_logged_in() && $is_note ) { 503 return new WP_Error( 504 'rest_comment_login_required', 505 __( 'Sorry, you must be logged in to comment.' ), 506 array( 'status' => 401 ) 507 ); 508 } 509 510 if ( ! is_user_logged_in() ) { 511 if ( get_option( 'comment_registration' ) ) { 512 return new WP_Error( 513 'rest_comment_login_required', 514 __( 'Sorry, you must be logged in to comment.' ), 515 array( 'status' => 401 ) 516 ); 517 } 518 519 /** 520 * Filters whether comments can be created via the REST API without authentication. 521 * 522 * Enables creating comments for anonymous users. 523 * 524 * @since 4.7.0 525 * 526 * @param bool $allow_anonymous Whether to allow anonymous comments to 527 * be created. Default `false`. 528 * @param WP_REST_Request $request Request used to generate the 529 * response. 530 */ 531 $allow_anonymous = apply_filters( 'rest_allow_anonymous_comments', false, $request ); 532 533 if ( ! $allow_anonymous ) { 534 return new WP_Error( 535 'rest_comment_login_required', 536 __( 'Sorry, you must be logged in to comment.' ), 537 array( 'status' => 401 ) 538 ); 539 } 540 } 541 542 // Limit who can set comment `author`, `author_ip` or `status` to anything other than the default. 543 if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) { 544 return new WP_Error( 545 'rest_comment_invalid_author', 546 /* translators: %s: Request parameter. */ 547 sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author' ), 548 array( 'status' => rest_authorization_required_code() ) 549 ); 550 } 551 552 if ( isset( $request['author_ip'] ) && ! current_user_can( 'moderate_comments' ) ) { 553 if ( empty( $_SERVER['REMOTE_ADDR'] ) || $request['author_ip'] !== $_SERVER['REMOTE_ADDR'] ) { 554 return new WP_Error( 555 'rest_comment_invalid_author_ip', 556 /* translators: %s: Request parameter. */ 557 sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author_ip' ), 558 array( 'status' => rest_authorization_required_code() ) 559 ); 560 } 561 } 562 563 if ( $is_note && ! empty( $request['post'] ) ) { 564 $target_check = $this->check_target_post_permission( (int) $request['post'], $request, true ); 565 566 if ( is_wp_error( $target_check ) ) { 567 return $target_check; 568 } 569 } 570 571 $edit_cap = $is_note ? array( 'edit_post', (int) $request['post'] ) : array( 'moderate_comments' ); 572 if ( isset( $request['status'] ) && ! current_user_can( ...$edit_cap ) ) { 573 return new WP_Error( 574 'rest_comment_invalid_status', 575 /* translators: %s: Request parameter. */ 576 sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'status' ), 577 array( 'status' => rest_authorization_required_code() ) 578 ); 579 } 580 581 if ( empty( $request['post'] ) ) { 582 return new WP_Error( 583 'rest_comment_invalid_post_id', 584 __( 'Sorry, you are not allowed to create this comment without a post.' ), 585 array( 'status' => 403 ) 586 ); 587 } 588 589 /* 590 * A note's target post was fully checked by check_target_post_permission() 591 * above. Everything below applies to other comments only: a note is allowed 592 * on a draft and on a post whose discussion is closed, and the rest would 593 * repeat what that check already did. 594 */ 595 if ( $is_note ) { 596 return true; 597 } 598 599 $post = get_post( (int) $request['post'] ); 600 601 if ( ! $post ) { 602 return new WP_Error( 603 'rest_comment_invalid_post_id', 604 __( 'Sorry, you are not allowed to create this comment without a post.' ), 605 array( 'status' => 403 ) 606 ); 607 } 608 609 if ( 'draft' === $post->post_status ) { 610 return new WP_Error( 611 'rest_comment_draft_post', 612 __( 'Sorry, you are not allowed to create a comment on this post.' ), 613 array( 'status' => 403 ) 614 ); 615 } 616 617 if ( 'trash' === $post->post_status ) { 618 return new WP_Error( 619 'rest_comment_trash_post', 620 __( 'Sorry, you are not allowed to create a comment on this post.' ), 621 array( 'status' => 403 ) 622 ); 623 } 624 625 if ( ! $this->check_read_post_permission( $post, $request ) ) { 626 return new WP_Error( 627 'rest_cannot_read_post', 628 __( 'Sorry, you are not allowed to read the post for this comment.' ), 629 array( 'status' => rest_authorization_required_code() ) 630 ); 631 } 632 633 if ( ! comments_open( $post->ID ) ) { 634 return new WP_Error( 635 'rest_comment_closed', 636 __( 'Sorry, comments are closed for this item.' ), 637 array( 'status' => 403 ) 638 ); 639 } 640 641 return true; 642 } 643 644 /** 645 * Creates a comment. 646 * 647 * @since 4.7.0 648 * 649 * @param WP_REST_Request $request Full details about the request. 650 * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. 651 */ 652 public function create_item( $request ) { 653 if ( ! empty( $request['id'] ) ) { 654 return new WP_Error( 655 'rest_comment_exists', 656 __( 'Cannot create existing comment.' ), 657 array( 'status' => 400 ) 658 ); 659 } 660 661 // Do not allow comments to be created with a non-core type. 662 if ( ! empty( $request['type'] ) && ! in_array( $request['type'], array( 'comment', 'note' ), true ) ) { 663 return new WP_Error( 664 'rest_invalid_comment_type', 665 __( 'Cannot create a comment with that type.' ), 666 array( 'status' => 400 ) 667 ); 668 } 669 670 $prepared_comment = $this->prepare_item_for_database( $request ); 671 if ( is_wp_error( $prepared_comment ) ) { 672 return $prepared_comment; 673 } 674 675 $prepared_comment['comment_type'] = $request['type']; 676 677 if ( ! isset( $prepared_comment['comment_content'] ) ) { 678 $prepared_comment['comment_content'] = ''; 679 } 680 681 // Include note metadata into check_is_comment_content_allowed. 682 if ( isset( $request['meta']['_wp_note_status'] ) ) { 683 $prepared_comment['meta']['_wp_note_status'] = $request['meta']['_wp_note_status']; 684 } 685 686 if ( ! $this->check_is_comment_content_allowed( $prepared_comment ) ) { 687 return new WP_Error( 688 'rest_comment_content_invalid', 689 __( 'Invalid comment content.' ), 690 array( 'status' => 400 ) 691 ); 692 } 693 694 // Setting remaining values before wp_insert_comment so we can use wp_allow_comment(). 695 if ( ! isset( $prepared_comment['comment_date_gmt'] ) ) { 696 $prepared_comment['comment_date_gmt'] = current_time( 'mysql', true ); 697 } 698 699 // Set author data if the user's logged in. 700 $missing_author = empty( $prepared_comment['user_id'] ) 701 && empty( $prepared_comment['comment_author'] ) 702 && empty( $prepared_comment['comment_author_email'] ) 703 && empty( $prepared_comment['comment_author_url'] ); 704 705 if ( is_user_logged_in() && $missing_author ) { 706 $user = wp_get_current_user(); 707 708 $prepared_comment['user_id'] = $user->ID; 709 $prepared_comment['comment_author'] = $user->display_name; 710 $prepared_comment['comment_author_email'] = $user->user_email; 711 $prepared_comment['comment_author_url'] = $user->user_url; 712 } 713 714 // Honor the discussion setting that requires a name and email address of the comment author. 715 if ( get_option( 'require_name_email' ) ) { 716 if ( empty( $prepared_comment['comment_author'] ) || empty( $prepared_comment['comment_author_email'] ) ) { 717 return new WP_Error( 718 'rest_comment_author_data_required', 719 __( 'Creating a comment requires valid author name and email values.' ), 720 array( 'status' => 400 ) 721 ); 722 } 723 } 724 725 if ( ! isset( $prepared_comment['comment_author_email'] ) ) { 726 $prepared_comment['comment_author_email'] = ''; 727 } 728 729 if ( ! isset( $prepared_comment['comment_author_url'] ) ) { 730 $prepared_comment['comment_author_url'] = ''; 731 } 732 733 if ( ! isset( $prepared_comment['comment_agent'] ) ) { 734 $prepared_comment['comment_agent'] = ''; 735 } 736 737 $check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_comment ); 738 739 if ( is_wp_error( $check_comment_lengths ) ) { 740 $error_code = $check_comment_lengths->get_error_code(); 741 return new WP_Error( 742 $error_code, 743 __( 'Comment field exceeds maximum length allowed.' ), 744 array( 'status' => 400 ) 745 ); 746 } 747 748 // Don't check for duplicates or flooding for notes. 749 $prepared_comment['comment_approved'] = 750 'note' === $prepared_comment['comment_type'] ? 751 '1' : 752 wp_allow_comment( $prepared_comment, true ); 753 754 if ( is_wp_error( $prepared_comment['comment_approved'] ) ) { 755 $error_code = $prepared_comment['comment_approved']->get_error_code(); 756 $error_message = $prepared_comment['comment_approved']->get_error_message(); 757 758 if ( 'comment_duplicate' === $error_code ) { 759 return new WP_Error( 760 $error_code, 761 $error_message, 762 array( 'status' => 409 ) 763 ); 764 } 765 766 if ( 'comment_flood' === $error_code ) { 767 return new WP_Error( 768 $error_code, 769 $error_message, 770 array( 'status' => 400 ) 771 ); 772 } 773 774 return $prepared_comment['comment_approved']; 775 } 776 777 /** 778 * Filters a comment before it is inserted via the REST API. 779 * 780 * Allows modification of the comment right before it is inserted via wp_insert_comment(). 781 * Returning a WP_Error value from the filter will short-circuit insertion and allow 782 * skipping further processing. 783 * 784 * @since 4.7.0 785 * @since 4.8.0 `$prepared_comment` can now be a WP_Error to short-circuit insertion. 786 * 787 * @param array|WP_Error $prepared_comment The prepared comment data for wp_insert_comment(). 788 * @param WP_REST_Request $request Request used to insert the comment. 789 */ 790 $prepared_comment = apply_filters( 'rest_pre_insert_comment', $prepared_comment, $request ); 791 if ( is_wp_error( $prepared_comment ) ) { 792 return $prepared_comment; 793 } 794 795 $comment_id = wp_insert_comment( wp_filter_comment( wp_slash( (array) $prepared_comment ) ) ); 796 797 if ( ! $comment_id ) { 798 return new WP_Error( 799 'rest_comment_failed_create', 800 __( 'Creating comment failed.' ), 801 array( 'status' => 500 ) 802 ); 803 } 804 805 if ( isset( $request['status'] ) ) { 806 $this->handle_status_param( $request['status'], $comment_id ); 807 } 808 809 $comment = get_comment( $comment_id ); 810 811 /** 812 * Fires after a comment is created or updated via the REST API. 813 * 814 * @since 4.7.0 815 * 816 * @param WP_Comment $comment Inserted or updated comment object. 817 * @param WP_REST_Request $request Request object. 818 * @param bool $creating True when creating a comment, false 819 * when updating. 820 */ 821 do_action( 'rest_insert_comment', $comment, $request, true ); 822 823 $schema = $this->get_item_schema(); 824 825 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 826 $meta_update = $this->meta->update_value( $request['meta'], $comment_id ); 827 828 if ( is_wp_error( $meta_update ) ) { 829 return $meta_update; 830 } 831 } 832 833 $fields_update = $this->update_additional_fields_for_object( $comment, $request ); 834 835 if ( is_wp_error( $fields_update ) ) { 836 return $fields_update; 837 } 838 839 $context = current_user_can( 'moderate_comments' ) ? 'edit' : 'view'; 840 $request->set_param( 'context', $context ); 841 842 /** 843 * Fires completely after a comment is created or updated via the REST API. 844 * 845 * @since 5.0.0 846 * 847 * @param WP_Comment $comment Inserted or updated comment object. 848 * @param WP_REST_Request $request Request object. 849 * @param bool $creating True when creating a comment, false 850 * when updating. 851 */ 852 do_action( 'rest_after_insert_comment', $comment, $request, true ); 853 854 $response = $this->prepare_item_for_response( $comment, $request ); 855 $response = rest_ensure_response( $response ); 856 857 $response->set_status( 201 ); 858 $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment_id ) ) ); 859 860 return $response; 861 } 862 863 /** 864 * Checks if a given REST request has access to update a comment. 865 * 866 * @since 4.7.0 867 * @since 7.1.1 Target post permissions are checked when a comment's parent post is changed. 868 * 869 * @param WP_REST_Request $request Full details about the request. 870 * @return true|WP_Error True if the request has access to update the item, error object otherwise. 871 */ 872 public function update_item_permissions_check( $request ) { 873 $comment = $this->get_comment( $request['id'] ); 874 if ( is_wp_error( $comment ) ) { 875 return $comment; 876 } 877 878 if ( ! $this->check_edit_permission( $comment ) ) { 879 return new WP_Error( 880 'rest_cannot_edit', 881 __( 'Sorry, you are not allowed to edit this comment.' ), 882 array( 'status' => rest_authorization_required_code() ) 883 ); 884 } 885 886 /* 887 * check_edit_permission() above only establishes that the comment may be 888 * edited where it currently sits, because 'edit_comment' maps to 'edit_post' 889 * on the comment's current parent. When the parent is being changed, the new 890 * parent has to be authorized as well. Without this, a user holding 891 * edit_comment on their own comment or note could reparent it onto any post, 892 * including posts they can neither read nor edit. 893 */ 894 if ( isset( $request['post'] ) && (int) $request['post'] !== (int) $comment->comment_post_ID ) { 895 $target_check = $this->check_target_post_permission( 896 (int) $request['post'], 897 $request, 898 'note' === $comment->comment_type 899 ); 900 901 if ( is_wp_error( $target_check ) ) { 902 return $target_check; 903 } 904 } 905 906 return true; 907 } 908 909 /** 910 * Updates a comment. 911 * 912 * @since 4.7.0 913 * 914 * @param WP_REST_Request $request Full details about the request. 915 * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. 916 */ 917 public function update_item( $request ) { 918 $comment = $this->get_comment( $request['id'] ); 919 if ( is_wp_error( $comment ) ) { 920 return $comment; 921 } 922 923 $id = $comment->comment_ID; 924 925 if ( isset( $request['type'] ) && get_comment_type( $id ) !== $request['type'] ) { 926 return new WP_Error( 927 'rest_comment_invalid_type', 928 __( 'Sorry, you are not allowed to change the comment type.' ), 929 array( 'status' => 404 ) 930 ); 931 } 932 933 $prepared_args = $this->prepare_item_for_database( $request ); 934 935 if ( is_wp_error( $prepared_args ) ) { 936 return $prepared_args; 937 } 938 939 if ( ! empty( $prepared_args['comment_post_ID'] ) ) { 940 $post = get_post( $prepared_args['comment_post_ID'] ); 941 942 if ( empty( $post ) ) { 943 return new WP_Error( 944 'rest_comment_invalid_post_id', 945 __( 'Invalid post ID.' ), 946 array( 'status' => 403 ) 947 ); 948 } 949 } 950 951 if ( empty( $prepared_args ) && isset( $request['status'] ) ) { 952 // Only the comment status is being changed. 953 $change = $this->handle_status_param( $request['status'], $id ); 954 955 if ( ! $change ) { 956 return new WP_Error( 957 'rest_comment_failed_edit', 958 __( 'Updating comment status failed.' ), 959 array( 'status' => 500 ) 960 ); 961 } 962 } elseif ( ! empty( $prepared_args ) ) { 963 if ( is_wp_error( $prepared_args ) ) { 964 return $prepared_args; 965 } 966 if ( ! $this->check_is_comment_content_allowed( $prepared_args ) ) { 967 return new WP_Error( 968 'rest_comment_content_invalid', 969 __( 'Invalid comment content.' ), 970 array( 'status' => 400 ) 971 ); 972 } 973 974 $prepared_args['comment_ID'] = $id; 975 976 $check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_args ); 977 978 if ( is_wp_error( $check_comment_lengths ) ) { 979 $error_code = $check_comment_lengths->get_error_code(); 980 return new WP_Error( 981 $error_code, 982 __( 'Comment field exceeds maximum length allowed.' ), 983 array( 'status' => 400 ) 984 ); 985 } 986 987 $updated = wp_update_comment( wp_slash( (array) $prepared_args ), true ); 988 989 if ( is_wp_error( $updated ) ) { 990 return new WP_Error( 991 'rest_comment_failed_edit', 992 __( 'Updating comment failed.' ), 993 array( 'status' => 500 ) 994 ); 995 } 996 997 if ( isset( $request['status'] ) ) { 998 $this->handle_status_param( $request['status'], $id ); 999 } 1000 } 1001 1002 $comment = get_comment( $id ); 1003 1004 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */ 1005 do_action( 'rest_insert_comment', $comment, $request, false ); 1006 1007 $schema = $this->get_item_schema(); 1008 1009 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 1010 $meta_update = $this->meta->update_value( $request['meta'], $id ); 1011 1012 if ( is_wp_error( $meta_update ) ) { 1013 return $meta_update; 1014 } 1015 } 1016 1017 $fields_update = $this->update_additional_fields_for_object( $comment, $request ); 1018 1019 if ( is_wp_error( $fields_update ) ) { 1020 return $fields_update; 1021 } 1022 1023 $request->set_param( 'context', 'edit' ); 1024 1025 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */ 1026 do_action( 'rest_after_insert_comment', $comment, $request, false ); 1027 1028 $response = $this->prepare_item_for_response( $comment, $request ); 1029 1030 return rest_ensure_response( $response ); 1031 } 1032 1033 /** 1034 * Checks if a given request has access to delete a comment. 1035 * 1036 * @since 4.7.0 1037 * 1038 * @param WP_REST_Request $request Full details about the request. 1039 * @return true|WP_Error True if the request has access to delete the item, error object otherwise. 1040 */ 1041 public function delete_item_permissions_check( $request ) { 1042 $comment = $this->get_comment( $request['id'] ); 1043 if ( is_wp_error( $comment ) ) { 1044 return $comment; 1045 } 1046 1047 if ( ! $this->check_edit_permission( $comment ) ) { 1048 return new WP_Error( 1049 'rest_cannot_delete', 1050 __( 'Sorry, you are not allowed to delete this comment.' ), 1051 array( 'status' => rest_authorization_required_code() ) 1052 ); 1053 } 1054 return true; 1055 } 1056 1057 /** 1058 * Deletes a comment. 1059 * 1060 * @since 4.7.0 1061 * 1062 * @param WP_REST_Request $request Full details about the request. 1063 * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. 1064 */ 1065 public function delete_item( $request ) { 1066 $comment = $this->get_comment( $request['id'] ); 1067 if ( is_wp_error( $comment ) ) { 1068 return $comment; 1069 } 1070 1071 $force = isset( $request['force'] ) ? (bool) $request['force'] : false; 1072 1073 /** 1074 * Filters whether a comment can be trashed via the REST API. 1075 * 1076 * Return false to disable trash support for the comment. 1077 * 1078 * @since 4.7.0 1079 * 1080 * @param bool $supports_trash Whether the comment supports trashing. 1081 * @param WP_Comment $comment The comment object being considered for trashing support. 1082 */ 1083 $supports_trash = apply_filters( 'rest_comment_trashable', ( EMPTY_TRASH_DAYS > 0 ), $comment ); 1084 1085 $request->set_param( 'context', 'edit' ); 1086 1087 if ( $force ) { 1088 $previous = $this->prepare_item_for_response( $comment, $request ); 1089 $result = wp_delete_comment( $comment->comment_ID, true ); 1090 $response = new WP_REST_Response(); 1091 $response->set_data( 1092 array( 1093 'deleted' => true, 1094 'previous' => $previous->get_data(), 1095 ) 1096 ); 1097 } else { 1098 // If this type doesn't support trashing, error out. 1099 if ( ! $supports_trash ) { 1100 return new WP_Error( 1101 'rest_trash_not_supported', 1102 /* translators: %s: force=true */ 1103 sprintf( __( "The comment does not support trashing. Set '%s' to delete." ), 'force=true' ), 1104 array( 'status' => 501 ) 1105 ); 1106 } 1107 1108 if ( 'trash' === $comment->comment_approved ) { 1109 return new WP_Error( 1110 'rest_already_trashed', 1111 __( 'The comment has already been trashed.' ), 1112 array( 'status' => 410 ) 1113 ); 1114 } 1115 1116 $result = wp_trash_comment( $comment->comment_ID ); 1117 $comment = get_comment( $comment->comment_ID ); 1118 $response = $this->prepare_item_for_response( $comment, $request ); 1119 } 1120 1121 if ( ! $result ) { 1122 return new WP_Error( 1123 'rest_cannot_delete', 1124 __( 'The comment cannot be deleted.' ), 1125 array( 'status' => 500 ) 1126 ); 1127 } 1128 1129 /** 1130 * Fires after a comment is deleted via the REST API. 1131 * 1132 * @since 4.7.0 1133 * 1134 * @param WP_Comment $comment The deleted comment data. 1135 * @param WP_REST_Response $response The response returned from the API. 1136 * @param WP_REST_Request $request The request sent to the API. 1137 */ 1138 do_action( 'rest_delete_comment', $comment, $response, $request ); 1139 1140 return $response; 1141 } 1142 1143 /** 1144 * Prepares a single comment output for response. 1145 * 1146 * @since 4.7.0 1147 * @since 5.9.0 Renamed `$comment` to `$item` to match parent class for PHP 8 named parameter support. 1148 * 1149 * @param WP_Comment $item Comment object. 1150 * @param WP_REST_Request $request Request object. 1151 * @return WP_REST_Response Response object. 1152 */ 1153 public function prepare_item_for_response( $item, $request ) { 1154 // Restores the more descriptive, specific name for use within this method. 1155 $comment = $item; 1156 1157 // Don't prepare the response body for HEAD requests. 1158 if ( $request->is_method( 'HEAD' ) ) { 1159 /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */ 1160 return apply_filters( 'rest_prepare_comment', new WP_REST_Response( array() ), $comment, $request ); 1161 } 1162 1163 $fields = $this->get_fields_for_response( $request ); 1164 $data = array(); 1165 1166 if ( in_array( 'id', $fields, true ) ) { 1167 $data['id'] = (int) $comment->comment_ID; 1168 } 1169 1170 if ( in_array( 'post', $fields, true ) ) { 1171 $data['post'] = (int) $comment->comment_post_ID; 1172 } 1173 1174 if ( in_array( 'parent', $fields, true ) ) { 1175 $data['parent'] = (int) $comment->comment_parent; 1176 } 1177 1178 if ( in_array( 'author', $fields, true ) ) { 1179 $data['author'] = (int) $comment->user_id; 1180 } 1181 1182 if ( in_array( 'author_name', $fields, true ) ) { 1183 $data['author_name'] = $comment->comment_author; 1184 } 1185 1186 if ( in_array( 'author_email', $fields, true ) ) { 1187 $data['author_email'] = $comment->comment_author_email; 1188 } 1189 1190 if ( in_array( 'author_url', $fields, true ) ) { 1191 $data['author_url'] = $comment->comment_author_url; 1192 } 1193 1194 if ( in_array( 'author_ip', $fields, true ) ) { 1195 $data['author_ip'] = $comment->comment_author_IP; 1196 } 1197 1198 if ( in_array( 'author_user_agent', $fields, true ) ) { 1199 $data['author_user_agent'] = $comment->comment_agent; 1200 } 1201 1202 if ( in_array( 'date', $fields, true ) ) { 1203 $data['date'] = mysql_to_rfc3339( $comment->comment_date ); 1204 } 1205 1206 if ( in_array( 'date_gmt', $fields, true ) ) { 1207 $data['date_gmt'] = mysql_to_rfc3339( $comment->comment_date_gmt ); 1208 } 1209 1210 if ( in_array( 'content', $fields, true ) ) { 1211 $data['content'] = array( 1212 /** This filter is documented in wp-includes/comment-template.php */ 1213 'rendered' => apply_filters( 'comment_text', $comment->comment_content, $comment, array() ), 1214 'raw' => $comment->comment_content, 1215 ); 1216 } 1217 1218 if ( in_array( 'link', $fields, true ) ) { 1219 $data['link'] = get_comment_link( $comment ); 1220 } 1221 1222 if ( in_array( 'status', $fields, true ) ) { 1223 $data['status'] = $this->prepare_status_response( $comment->comment_approved ); 1224 } 1225 1226 if ( in_array( 'type', $fields, true ) ) { 1227 $data['type'] = get_comment_type( $comment->comment_ID ); 1228 } 1229 1230 if ( in_array( 'author_avatar_urls', $fields, true ) ) { 1231 $data['author_avatar_urls'] = rest_get_avatar_urls( $comment ); 1232 } 1233 1234 if ( in_array( 'meta', $fields, true ) ) { 1235 $data['meta'] = $this->meta->get_value( $comment->comment_ID, $request ); 1236 } 1237 1238 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 1239 $data = $this->add_additional_fields_to_object( $data, $request ); 1240 $data = $this->filter_response_by_context( $data, $context ); 1241 1242 // Wrap the data in a response object. 1243 $response = rest_ensure_response( $data ); 1244 1245 if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { 1246 $response->add_links( $this->prepare_links( $comment ) ); 1247 } 1248 1249 /** 1250 * Filters a comment returned from the REST API. 1251 * 1252 * Allows modification of the comment right before it is returned. 1253 * 1254 * @since 4.7.0 1255 * 1256 * @param WP_REST_Response $response The response object. 1257 * @param WP_Comment $comment The original comment object. 1258 * @param WP_REST_Request $request Request used to generate the response. 1259 */ 1260 return apply_filters( 'rest_prepare_comment', $response, $comment, $request ); 1261 } 1262 1263 /** 1264 * Prepares links for the request. 1265 * 1266 * @since 4.7.0 1267 * 1268 * @param WP_Comment $comment Comment object. 1269 * @return array Links for the given comment. 1270 */ 1271 protected function prepare_links( $comment ) { 1272 $links = array( 1273 'self' => array( 1274 'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_ID ) ), 1275 ), 1276 'collection' => array( 1277 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), 1278 ), 1279 ); 1280 1281 if ( 0 !== (int) $comment->user_id ) { 1282 $links['author'] = array( 1283 'href' => rest_url( 'wp/v2/users/' . $comment->user_id ), 1284 'embeddable' => true, 1285 ); 1286 } 1287 1288 if ( 0 !== (int) $comment->comment_post_ID ) { 1289 $post = get_post( $comment->comment_post_ID ); 1290 $post_route = rest_get_route_for_post( $post ); 1291 1292 if ( ! empty( $post->ID ) && $post_route ) { 1293 $links['up'] = array( 1294 'href' => rest_url( $post_route ), 1295 'embeddable' => true, 1296 'post_type' => $post->post_type, 1297 ); 1298 } 1299 } 1300 1301 if ( 0 !== (int) $comment->comment_parent ) { 1302 $links['in-reply-to'] = array( 1303 'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_parent ) ), 1304 'embeddable' => true, 1305 ); 1306 } 1307 1308 // Only grab one comment to verify the comment has children. 1309 $comment_children = $comment->get_children( 1310 array( 1311 'count' => true, 1312 'orderby' => 'none', 1313 'type' => 'all', 1314 ) 1315 ); 1316 1317 if ( ! empty( $comment_children ) ) { 1318 $args = array( 1319 'parent' => $comment->comment_ID, 1320 ); 1321 1322 $rest_url = add_query_arg( $args, rest_url( $this->namespace . '/' . $this->rest_base ) ); 1323 1324 $links['children'] = array( 1325 'href' => $rest_url, 1326 'embeddable' => true, 1327 ); 1328 } 1329 1330 // Embedding children for notes requires `type` and `status` inheritance. 1331 if ( isset( $links['children'] ) && 'note' === $comment->comment_type ) { 1332 $args = array( 1333 'parent' => $comment->comment_ID, 1334 'type' => $comment->comment_type, 1335 'status' => 'all', 1336 ); 1337 1338 $rest_url = add_query_arg( $args, rest_url( $this->namespace . '/' . $this->rest_base ) ); 1339 1340 $links['children'] = array( 1341 'href' => $rest_url, 1342 'embeddable' => true, 1343 ); 1344 } 1345 1346 return $links; 1347 } 1348 1349 /** 1350 * Prepends internal property prefix to query parameters to match our response fields. 1351 * 1352 * @since 4.7.0 1353 * 1354 * @param string $query_param Query parameter. 1355 * @return string The normalized query parameter. 1356 */ 1357 protected function normalize_query_param( $query_param ) { 1358 $prefix = 'comment_'; 1359 1360 switch ( $query_param ) { 1361 case 'id': 1362 $normalized = $prefix . 'ID'; 1363 break; 1364 case 'post': 1365 $normalized = $prefix . 'post_ID'; 1366 break; 1367 case 'parent': 1368 $normalized = $prefix . 'parent'; 1369 break; 1370 case 'include': 1371 $normalized = 'comment__in'; 1372 break; 1373 default: 1374 $normalized = $prefix . $query_param; 1375 break; 1376 } 1377 1378 return $normalized; 1379 } 1380 1381 /** 1382 * Checks comment_approved to set comment status for single comment output. 1383 * 1384 * @since 4.7.0 1385 * 1386 * @param string $comment_approved Comment status. 1387 * @return string Comment status. 1388 */ 1389 protected function prepare_status_response( $comment_approved ) { 1390 1391 switch ( $comment_approved ) { 1392 case 'hold': 1393 case '0': 1394 $status = 'hold'; 1395 break; 1396 1397 case 'approve': 1398 case '1': 1399 $status = 'approved'; 1400 break; 1401 1402 case 'spam': 1403 case 'trash': 1404 default: 1405 $status = $comment_approved; 1406 break; 1407 } 1408 1409 return $status; 1410 } 1411 1412 /** 1413 * Prepares a single comment to be inserted into the database. 1414 * 1415 * @since 4.7.0 1416 * 1417 * @param WP_REST_Request $request Request object. 1418 * @return array|WP_Error Prepared comment, otherwise WP_Error object. 1419 */ 1420 protected function prepare_item_for_database( $request ) { 1421 $prepared_comment = array(); 1422 1423 /* 1424 * Allow the comment_content to be set via the 'content' or 1425 * the 'content.raw' properties of the Request object. 1426 */ 1427 if ( isset( $request['content'] ) && is_string( $request['content'] ) ) { 1428 $prepared_comment['comment_content'] = trim( $request['content'] ); 1429 } elseif ( isset( $request['content']['raw'] ) && is_string( $request['content']['raw'] ) ) { 1430 $prepared_comment['comment_content'] = trim( $request['content']['raw'] ); 1431 } 1432 1433 if ( isset( $request['post'] ) ) { 1434 $prepared_comment['comment_post_ID'] = (int) $request['post']; 1435 } 1436 1437 if ( isset( $request['parent'] ) ) { 1438 $prepared_comment['comment_parent'] = $request['parent']; 1439 } 1440 1441 if ( isset( $request['author'] ) ) { 1442 $user = new WP_User( $request['author'] ); 1443 1444 if ( $user->exists() ) { 1445 $prepared_comment['user_id'] = $user->ID; 1446 $prepared_comment['comment_author'] = $user->display_name; 1447 $prepared_comment['comment_author_email'] = $user->user_email; 1448 $prepared_comment['comment_author_url'] = $user->user_url; 1449 } else { 1450 return new WP_Error( 1451 'rest_comment_author_invalid', 1452 __( 'Invalid comment author ID.' ), 1453 array( 'status' => 400 ) 1454 ); 1455 } 1456 } 1457 1458 if ( isset( $request['author_name'] ) ) { 1459 $prepared_comment['comment_author'] = $request['author_name']; 1460 } 1461 1462 if ( isset( $request['author_email'] ) ) { 1463 $prepared_comment['comment_author_email'] = $request['author_email']; 1464 } 1465 1466 if ( isset( $request['author_url'] ) ) { 1467 $prepared_comment['comment_author_url'] = $request['author_url']; 1468 } 1469 1470 if ( isset( $request['author_ip'] ) && current_user_can( 'moderate_comments' ) ) { 1471 $prepared_comment['comment_author_IP'] = $request['author_ip']; 1472 } elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) && rest_is_ip_address( $_SERVER['REMOTE_ADDR'] ) ) { 1473 $prepared_comment['comment_author_IP'] = $_SERVER['REMOTE_ADDR']; 1474 } else { 1475 $prepared_comment['comment_author_IP'] = '127.0.0.1'; 1476 } 1477 1478 if ( ! empty( $request['author_user_agent'] ) ) { 1479 $prepared_comment['comment_agent'] = $request['author_user_agent']; 1480 } elseif ( $request->get_header( 'user_agent' ) ) { 1481 $prepared_comment['comment_agent'] = $request->get_header( 'user_agent' ); 1482 } 1483 1484 if ( ! empty( $request['date'] ) ) { 1485 $date_data = rest_get_date_with_gmt( $request['date'] ); 1486 1487 if ( ! empty( $date_data ) ) { 1488 list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data; 1489 } 1490 } elseif ( ! empty( $request['date_gmt'] ) ) { 1491 $date_data = rest_get_date_with_gmt( $request['date_gmt'], true ); 1492 1493 if ( ! empty( $date_data ) ) { 1494 list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data; 1495 } 1496 } 1497 1498 /** 1499 * Filters a comment added via the REST API after it is prepared for insertion into the database. 1500 * 1501 * Allows modification of the comment right after it is prepared for the database. 1502 * 1503 * @since 4.7.0 1504 * 1505 * @param array $prepared_comment The prepared comment data for `wp_insert_comment`. 1506 * @param WP_REST_Request $request The current request. 1507 */ 1508 return apply_filters( 'rest_preprocess_comment', $prepared_comment, $request ); 1509 } 1510 1511 /** 1512 * Retrieves the comment's schema, conforming to JSON Schema. 1513 * 1514 * @since 4.7.0 1515 * 1516 * @return array 1517 */ 1518 public function get_item_schema() { 1519 if ( $this->schema ) { 1520 return $this->add_additional_fields_schema( $this->schema ); 1521 } 1522 1523 $schema = array( 1524 '$schema' => 'http://json-schema.org/draft-04/schema#', 1525 'title' => 'comment', 1526 'type' => 'object', 1527 'properties' => array( 1528 'id' => array( 1529 'description' => __( 'Unique identifier for the comment.' ), 1530 'type' => 'integer', 1531 'context' => array( 'view', 'edit', 'embed' ), 1532 'readonly' => true, 1533 ), 1534 'author' => array( 1535 'description' => __( 'The ID of the user object, if author was a user.' ), 1536 'type' => 'integer', 1537 'context' => array( 'view', 'edit', 'embed' ), 1538 ), 1539 'author_email' => array( 1540 'description' => __( 'Email address for the comment author.' ), 1541 'type' => 'string', 1542 'format' => 'email', 1543 'context' => array( 'edit' ), 1544 'arg_options' => array( 1545 'sanitize_callback' => array( $this, 'check_comment_author_email' ), 1546 'validate_callback' => null, // Skip built-in validation of 'email'. 1547 ), 1548 ), 1549 'author_ip' => array( 1550 'description' => __( 'IP address for the comment author.' ), 1551 'type' => 'string', 1552 'format' => 'ip', 1553 'context' => array( 'edit' ), 1554 ), 1555 'author_name' => array( 1556 'description' => __( 'Display name for the comment author.' ), 1557 'type' => 'string', 1558 'context' => array( 'view', 'edit', 'embed' ), 1559 'arg_options' => array( 1560 'sanitize_callback' => 'sanitize_text_field', 1561 ), 1562 ), 1563 'author_url' => array( 1564 'description' => __( 'URL for the comment author.' ), 1565 'type' => 'string', 1566 'format' => 'uri', 1567 'context' => array( 'view', 'edit', 'embed' ), 1568 ), 1569 'author_user_agent' => array( 1570 'description' => __( 'User agent for the comment author.' ), 1571 'type' => 'string', 1572 'context' => array( 'edit' ), 1573 'arg_options' => array( 1574 'sanitize_callback' => 'sanitize_text_field', 1575 ), 1576 ), 1577 'content' => array( 1578 'description' => __( 'The content for the comment.' ), 1579 'type' => 'object', 1580 'context' => array( 'view', 'edit', 'embed' ), 1581 'arg_options' => array( 1582 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). 1583 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). 1584 ), 1585 'properties' => array( 1586 'raw' => array( 1587 'description' => __( 'Content for the comment, as it exists in the database.' ), 1588 'type' => 'string', 1589 'context' => array( 'edit' ), 1590 ), 1591 'rendered' => array( 1592 'description' => __( 'HTML content for the comment, transformed for display.' ), 1593 'type' => 'string', 1594 'context' => array( 'view', 'edit', 'embed' ), 1595 'readonly' => true, 1596 ), 1597 ), 1598 ), 1599 'date' => array( 1600 'description' => __( "The date the comment was published, in the site's timezone." ), 1601 'type' => 'string', 1602 'format' => 'date-time', 1603 'context' => array( 'view', 'edit', 'embed' ), 1604 ), 1605 'date_gmt' => array( 1606 'description' => __( 'The date the comment was published, as GMT.' ), 1607 'type' => 'string', 1608 'format' => 'date-time', 1609 'context' => array( 'view', 'edit' ), 1610 ), 1611 'link' => array( 1612 'description' => __( 'URL to the comment.' ), 1613 'type' => 'string', 1614 'format' => 'uri', 1615 'context' => array( 'view', 'edit', 'embed' ), 1616 'readonly' => true, 1617 ), 1618 'parent' => array( 1619 'description' => __( 'The ID for the parent of the comment.' ), 1620 'type' => 'integer', 1621 'context' => array( 'view', 'edit', 'embed' ), 1622 'default' => 0, 1623 ), 1624 'post' => array( 1625 'description' => __( 'The ID of the associated post object.' ), 1626 'type' => 'integer', 1627 'context' => array( 'view', 'edit' ), 1628 'default' => 0, 1629 ), 1630 'status' => array( 1631 'description' => __( 'State of the comment.' ), 1632 'type' => 'string', 1633 'context' => array( 'view', 'edit' ), 1634 'arg_options' => array( 1635 'sanitize_callback' => 'sanitize_key', 1636 ), 1637 ), 1638 'type' => array( 1639 'description' => __( 'Type of the comment.' ), 1640 'type' => 'string', 1641 'context' => array( 'view', 'edit', 'embed' ), 1642 'readonly' => true, 1643 'default' => 'comment', 1644 ), 1645 ), 1646 ); 1647 1648 if ( get_option( 'show_avatars' ) ) { 1649 $avatar_properties = array(); 1650 1651 $avatar_sizes = rest_get_avatar_sizes(); 1652 1653 foreach ( $avatar_sizes as $size ) { 1654 $avatar_properties[ $size ] = array( 1655 /* translators: %d: Avatar image size in pixels. */ 1656 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ), 1657 'type' => 'string', 1658 'format' => 'uri', 1659 'context' => array( 'embed', 'view', 'edit' ), 1660 ); 1661 } 1662 1663 $schema['properties']['author_avatar_urls'] = array( 1664 'description' => __( 'Avatar URLs for the comment author.' ), 1665 'type' => 'object', 1666 'context' => array( 'view', 'edit', 'embed' ), 1667 'readonly' => true, 1668 'properties' => $avatar_properties, 1669 ); 1670 } 1671 1672 $schema['properties']['meta'] = $this->meta->get_field_schema(); 1673 1674 $this->schema = $schema; 1675 1676 return $this->add_additional_fields_schema( $this->schema ); 1677 } 1678 1679 /** 1680 * Retrieves the query params for collections. 1681 * 1682 * @since 4.7.0 1683 * 1684 * @return array Comments collection parameters. 1685 */ 1686 public function get_collection_params() { 1687 $query_params = parent::get_collection_params(); 1688 1689 $query_params['context']['default'] = 'view'; 1690 1691 $query_params['after'] = array( 1692 'description' => __( 'Limit response to comments published after a given ISO8601 compliant date.' ), 1693 'type' => 'string', 1694 'format' => 'date-time', 1695 ); 1696 1697 $query_params['author'] = array( 1698 'description' => __( 'Limit result set to comments assigned to specific user IDs. Requires authorization.' ), 1699 'type' => 'array', 1700 'items' => array( 1701 'type' => 'integer', 1702 ), 1703 ); 1704 1705 $query_params['author_exclude'] = array( 1706 'description' => __( 'Ensure result set excludes comments assigned to specific user IDs. Requires authorization.' ), 1707 'type' => 'array', 1708 'items' => array( 1709 'type' => 'integer', 1710 ), 1711 ); 1712 1713 $query_params['author_email'] = array( 1714 'default' => null, 1715 'description' => __( 'Limit result set to that from a specific author email. Requires authorization.' ), 1716 'format' => 'email', 1717 'type' => 'string', 1718 ); 1719 1720 $query_params['before'] = array( 1721 'description' => __( 'Limit response to comments published before a given ISO8601 compliant date.' ), 1722 'type' => 'string', 1723 'format' => 'date-time', 1724 ); 1725 1726 $query_params['exclude'] = array( 1727 'description' => __( 'Ensure result set excludes specific IDs.' ), 1728 'type' => 'array', 1729 'items' => array( 1730 'type' => 'integer', 1731 ), 1732 'default' => array(), 1733 ); 1734 1735 $query_params['include'] = array( 1736 'description' => __( 'Limit result set to specific IDs.' ), 1737 'type' => 'array', 1738 'items' => array( 1739 'type' => 'integer', 1740 ), 1741 'default' => array(), 1742 ); 1743 1744 $query_params['offset'] = array( 1745 'description' => __( 'Offset the result set by a specific number of items.' ), 1746 'type' => 'integer', 1747 ); 1748 1749 $query_params['order'] = array( 1750 'description' => __( 'Order sort attribute ascending or descending.' ), 1751 'type' => 'string', 1752 'default' => 'desc', 1753 'enum' => array( 1754 'asc', 1755 'desc', 1756 ), 1757 ); 1758 1759 $query_params['orderby'] = array( 1760 'description' => __( 'Sort collection by comment attribute.' ), 1761 'type' => 'string', 1762 'default' => 'date_gmt', 1763 'enum' => array( 1764 'date', 1765 'date_gmt', 1766 'id', 1767 'include', 1768 'post', 1769 'parent', 1770 'type', 1771 ), 1772 ); 1773 1774 $query_params['parent'] = array( 1775 'default' => array(), 1776 'description' => __( 'Limit result set to comments of specific parent IDs.' ), 1777 'type' => 'array', 1778 'items' => array( 1779 'type' => 'integer', 1780 ), 1781 ); 1782 1783 $query_params['parent_exclude'] = array( 1784 'default' => array(), 1785 'description' => __( 'Ensure result set excludes specific parent IDs.' ), 1786 'type' => 'array', 1787 'items' => array( 1788 'type' => 'integer', 1789 ), 1790 ); 1791 1792 $query_params['post'] = array( 1793 'default' => array(), 1794 'description' => __( 'Limit result set to comments assigned to specific post IDs.' ), 1795 'type' => 'array', 1796 'items' => array( 1797 'type' => 'integer', 1798 ), 1799 ); 1800 1801 $query_params['status'] = array( 1802 'default' => 'approve', 1803 'description' => __( 'Limit result set to comments assigned a specific status. Requires authorization.' ), 1804 'sanitize_callback' => 'sanitize_key', 1805 'type' => 'string', 1806 'validate_callback' => 'rest_validate_request_arg', 1807 ); 1808 1809 $query_params['type'] = array( 1810 'default' => 'comment', 1811 'description' => __( 'Limit result set to comments assigned a specific type. Requires authorization.' ), 1812 'sanitize_callback' => 'sanitize_key', 1813 'type' => 'string', 1814 'validate_callback' => 'rest_validate_request_arg', 1815 ); 1816 1817 $query_params['password'] = array( 1818 'description' => __( 'The password for the post if it is password protected.' ), 1819 'type' => 'string', 1820 ); 1821 1822 /** 1823 * Filters REST API collection parameters for the comments controller. 1824 * 1825 * This filter registers the collection parameter, but does not map the 1826 * collection parameter to an internal WP_Comment_Query parameter. Use the 1827 * `rest_comment_query` filter to set WP_Comment_Query parameters. 1828 * 1829 * @since 4.7.0 1830 * 1831 * @param array $query_params JSON Schema-formatted collection parameters. 1832 */ 1833 return apply_filters( 'rest_comment_collection_params', $query_params ); 1834 } 1835 1836 /** 1837 * Sets the comment_status of a given comment object when creating or updating a comment. 1838 * 1839 * @since 4.7.0 1840 * 1841 * @param string|int $new_status New comment status. 1842 * @param int $comment_id Comment ID. 1843 * @return bool Whether the status was changed. 1844 */ 1845 protected function handle_status_param( $new_status, $comment_id ) { 1846 $old_status = wp_get_comment_status( $comment_id ); 1847 1848 if ( $new_status === $old_status ) { 1849 return false; 1850 } 1851 1852 switch ( $new_status ) { 1853 case 'approved': 1854 case 'approve': 1855 case '1': 1856 $changed = wp_set_comment_status( $comment_id, 'approve' ); 1857 break; 1858 case 'hold': 1859 case '0': 1860 $changed = wp_set_comment_status( $comment_id, 'hold' ); 1861 break; 1862 case 'spam': 1863 $changed = wp_spam_comment( $comment_id ); 1864 break; 1865 case 'unspam': 1866 $changed = wp_unspam_comment( $comment_id ); 1867 break; 1868 case 'trash': 1869 $changed = wp_trash_comment( $comment_id ); 1870 break; 1871 case 'untrash': 1872 $changed = wp_untrash_comment( $comment_id ); 1873 break; 1874 default: 1875 $changed = false; 1876 break; 1877 } 1878 1879 return $changed; 1880 } 1881 1882 /** 1883 * Checks if the post can be read. 1884 * 1885 * Correctly handles posts with the inherit status. 1886 * 1887 * @since 4.7.0 1888 * 1889 * @param WP_Post $post Post object. 1890 * @param WP_REST_Request $request Request data to check. 1891 * @return bool Whether post can be read. 1892 */ 1893 protected function check_read_post_permission( $post, $request ) { 1894 $post_type = get_post_type_object( $post->post_type ); 1895 1896 // Return false if custom post type doesn't exist 1897 if ( ! $post_type ) { 1898 return false; 1899 } 1900 1901 $posts_controller = $post_type->get_rest_controller(); 1902 1903 /* 1904 * Ensure the posts controller is specifically a WP_REST_Posts_Controller instance 1905 * before using methods specific to that controller. 1906 */ 1907 if ( ! $posts_controller instanceof WP_REST_Posts_Controller ) { 1908 $posts_controller = new WP_REST_Posts_Controller( $post->post_type ); 1909 } 1910 1911 $has_password_filter = false; 1912 1913 // Only check password if a specific post was queried for or a single comment 1914 $requested_post = ! empty( $request['post'] ) && ( ! is_array( $request['post'] ) || 1 === count( $request['post'] ) ); 1915 $requested_comment = ! empty( $request['id'] ); 1916 if ( ( $requested_post || $requested_comment ) && $posts_controller->can_access_password_content( $post, $request ) ) { 1917 add_filter( 'post_password_required', '__return_false' ); 1918 1919 $has_password_filter = true; 1920 } 1921 1922 if ( post_password_required( $post ) ) { 1923 $result = current_user_can( 'edit_post', $post->ID ); 1924 } else { 1925 $result = $posts_controller->check_read_permission( $post ); 1926 } 1927 1928 if ( $has_password_filter ) { 1929 remove_filter( 'post_password_required', '__return_false' ); 1930 } 1931 1932 return $result; 1933 } 1934 1935 /** 1936 * Checks if the comment can be read. 1937 * 1938 * @since 4.7.0 1939 * 1940 * @param WP_Comment $comment Comment object. 1941 * @param WP_REST_Request $request Request data to check. 1942 * @return bool Whether the comment can be read. 1943 */ 1944 protected function check_read_permission( $comment, $request ) { 1945 if ( 'note' !== $comment->comment_type && ! empty( $comment->comment_post_ID ) ) { 1946 $post = get_post( $comment->comment_post_ID ); 1947 if ( $post ) { 1948 if ( $this->check_read_post_permission( $post, $request ) && 1 === (int) $comment->comment_approved ) { 1949 return true; 1950 } 1951 } 1952 } 1953 1954 if ( 0 === get_current_user_id() ) { 1955 return false; 1956 } 1957 1958 if ( empty( $comment->comment_post_ID ) && ! current_user_can( 'moderate_comments' ) ) { 1959 return false; 1960 } 1961 1962 if ( ! empty( $comment->user_id ) && get_current_user_id() === (int) $comment->user_id ) { 1963 return true; 1964 } 1965 1966 return current_user_can( 'edit_comment', $comment->comment_ID ); 1967 } 1968 1969 /** 1970 * Checks if a comment can be edited or deleted. 1971 * 1972 * @since 4.7.0 1973 * 1974 * @param WP_Comment $comment Comment object. 1975 * @return bool Whether the comment can be edited or deleted. 1976 */ 1977 protected function check_edit_permission( $comment ) { 1978 if ( 0 === (int) get_current_user_id() ) { 1979 return false; 1980 } 1981 1982 if ( current_user_can( 'moderate_comments' ) ) { 1983 return true; 1984 } 1985 1986 return current_user_can( 'edit_comment', $comment->comment_ID ); 1987 } 1988 1989 /** 1990 * Checks a comment author email for validity. 1991 * 1992 * Accepts either a valid email address or empty string as a valid comment 1993 * author email address. Setting the comment author email to an empty 1994 * string is allowed when a comment is being updated. 1995 * 1996 * @since 4.7.0 1997 * 1998 * @param string $value Author email value submitted. 1999 * @param WP_REST_Request $request Full details about the request. 2000 * @param string $param The parameter name. 2001 * @return string|WP_Error The sanitized email address, if valid, 2002 * otherwise an error. 2003 */ 2004 public function check_comment_author_email( $value, $request, $param ) { 2005 $email = (string) $value; 2006 if ( empty( $email ) ) { 2007 return $email; 2008 } 2009 2010 $check_email = rest_validate_request_arg( $email, $request, $param ); 2011 if ( is_wp_error( $check_email ) ) { 2012 return $check_email; 2013 } 2014 2015 return $email; 2016 } 2017 2018 /** 2019 * If empty comments are not allowed, checks if the provided comment content is not empty. 2020 * 2021 * @since 5.6.0 2022 * 2023 * @param array $prepared_comment The prepared comment data. 2024 * @return bool True if the content is allowed, false otherwise. 2025 */ 2026 protected function check_is_comment_content_allowed( $prepared_comment ) { 2027 if ( ! isset( $prepared_comment['comment_content'] ) ) { 2028 return true; 2029 } 2030 2031 $check = wp_parse_args( 2032 $prepared_comment, 2033 array( 2034 'comment_post_ID' => 0, 2035 'comment_author' => null, 2036 'comment_author_email' => null, 2037 'comment_author_url' => null, 2038 'comment_parent' => 0, 2039 'user_id' => 0, 2040 ) 2041 ); 2042 2043 /** This filter is documented in wp-includes/comment.php */ 2044 $allow_empty = apply_filters( 'allow_empty_comment', false, $check ); 2045 2046 if ( $allow_empty ) { 2047 return true; 2048 } 2049 2050 // Allow empty notes only when resolution metadata is valid. 2051 if ( 2052 isset( $check['comment_type'] ) && 2053 'note' === $check['comment_type'] && 2054 isset( $check['meta']['_wp_note_status'] ) && 2055 in_array( $check['meta']['_wp_note_status'], array( 'resolved', 'reopen' ), true ) 2056 ) { 2057 return true; 2058 } 2059 2060 /* 2061 * Do not allow a comment to be created with missing or empty 2062 * comment_content. See wp_handle_comment_submission(). 2063 */ 2064 return '' !== $check['comment_content']; 2065 } 2066 2067 /** 2068 * Checks that a post can receive a comment or a note from the current user. 2069 * 2070 * Used when creating a note and when changing the parent post of an existing 2071 * comment or note, so that attaching content to a post is authorized the same 2072 * way whichever path it arrives by. 2073 * 2074 * @since 7.1.1 2075 * 2076 * @param int $post_id Target post ID. 2077 * @param WP_REST_Request $request Full details about the request. 2078 * @param bool $is_note Optional. Whether the comment is a note. Default false. 2079 * @return true|WP_Error True if the post can receive the comment, error object otherwise. 2080 */ 2081 protected function check_target_post_permission( int $post_id, WP_REST_Request $request, bool $is_note = false ) { 2082 if ( ! $post_id ) { 2083 return new WP_Error( 2084 'rest_comment_invalid_post_id', 2085 __( 'Sorry, you are not allowed to create this comment without a post.' ), 2086 array( 'status' => 403 ) 2087 ); 2088 } 2089 2090 /* 2091 * Notes are editorial content, so they may only be attached to a post the 2092 * user can edit. Any other comment needs either comment moderation rights 2093 * or edit access to the post, which is what check_edit_permission() grants 2094 * on the post a comment is moving away from. Requiring the same at the 2095 * destination means both ends of a move are authorized alike. 2096 */ 2097 if ( $is_note ) { 2098 $can_target_post = current_user_can( 'edit_post', $post_id ); 2099 } else { 2100 $can_target_post = current_user_can( 'moderate_comments' ) || current_user_can( 'edit_post', $post_id ); 2101 } 2102 2103 if ( ! $can_target_post ) { 2104 if ( $is_note ) { 2105 return new WP_Error( 2106 'rest_cannot_create_note', 2107 __( 'Sorry, you are not allowed to create notes for this post.' ), 2108 array( 'status' => rest_authorization_required_code() ) 2109 ); 2110 } 2111 2112 return new WP_Error( 2113 'rest_cannot_edit', 2114 __( 'Sorry, you are not allowed to edit this comment.' ), 2115 array( 'status' => rest_authorization_required_code() ) 2116 ); 2117 } 2118 2119 $post = get_post( $post_id ); 2120 2121 if ( ! $post ) { 2122 return new WP_Error( 2123 'rest_comment_invalid_post_id', 2124 __( 'Sorry, you are not allowed to create this comment without a post.' ), 2125 array( 'status' => 403 ) 2126 ); 2127 } 2128 2129 /* 2130 * The remaining rules mirror the create-time checks for notes only. They are 2131 * deliberately not applied to other comments, because moderators move comments 2132 * onto posts whose discussion has closed and onto drafts today. Enforcing the 2133 * create-time rules there would break that without blocking anything the 2134 * capability check above already permits. 2135 */ 2136 if ( ! $is_note ) { 2137 return true; 2138 } 2139 2140 if ( ! $this->check_post_type_supports_notes( $post->post_type ) ) { 2141 return new WP_Error( 2142 'rest_comment_not_supported_post_type', 2143 __( 'Sorry, this post type does not support notes.' ), 2144 array( 'status' => 403 ) 2145 ); 2146 } 2147 2148 if ( 'trash' === $post->post_status ) { 2149 return new WP_Error( 2150 'rest_comment_trash_post', 2151 __( 'Sorry, you are not allowed to create a comment on this post.' ), 2152 array( 'status' => 403 ) 2153 ); 2154 } 2155 2156 if ( ! $this->check_read_post_permission( $post, $request ) ) { 2157 return new WP_Error( 2158 'rest_cannot_read_post', 2159 __( 'Sorry, you are not allowed to read the post for this comment.' ), 2160 array( 'status' => rest_authorization_required_code() ) 2161 ); 2162 } 2163 2164 return true; 2165 } 2166 2167 /** 2168 * Check if post type supports notes. 2169 * 2170 * @since 6.9.0 2171 * 2172 * @param string $post_type Post type name. 2173 * @return bool True if post type supports notes, false otherwise. 2174 */ 2175 private function check_post_type_supports_notes( $post_type ) { 2176 $supports = get_all_post_type_supports( $post_type ); 2177 2178 if ( ! isset( $supports['editor'] ) ) { 2179 return false; 2180 } 2181 2182 if ( ! is_array( $supports['editor'] ) ) { 2183 return false; 2184 } 2185 2186 return array_any( $supports['editor'], fn( $item ) => ! empty( $item['notes'] ) ); 2187 } 2188 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Tue Sep 22 08:20:31 2026 | Cross-referenced by PHPXref |