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