[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/rest-api/endpoints/ -> class-wp-rest-comments-controller.php (source)

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


Generated : Thu Oct 30 08:20:06 2025 Cross-referenced by PHPXref