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