[ Index ]

PHP Cross Reference of WordPress

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  
 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  }


Generated: Sat Apr 27 01:00:02 2024 Cross-referenced by PHPXref 0.7.1