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