[ 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 the objects of the controller.
  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 object.' ),
  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 arguments, before passing to WP_Comment_Query, 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 current 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 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          /*
 591           * Do not allow a comment to be created with missing or empty
 592           * comment_content. See wp_handle_comment_submission().
 593           */
 594          if ( empty( $prepared_comment['comment_content'] ) ) {
 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.
 959           *
 960           * Return false to disable Trash support for the post.
 961           *
 962           * @since 4.7.0
 963           *
 964           * @param bool    $supports_trash Whether the post type support trashing.
 965           * @param WP_Post $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       *
1032       * @param WP_Comment      $comment Comment object.
1033       * @param WP_REST_Request $request Request object.
1034       * @return WP_REST_Response Response object.
1035       */
1036  	public function prepare_item_for_response( $comment, $request ) {
1037  
1038          $fields = $this->get_fields_for_response( $request );
1039          $data   = array();
1040  
1041          if ( in_array( 'id', $fields, true ) ) {
1042              $data['id'] = (int) $comment->comment_ID;
1043          }
1044  
1045          if ( in_array( 'post', $fields, true ) ) {
1046              $data['post'] = (int) $comment->comment_post_ID;
1047          }
1048  
1049          if ( in_array( 'parent', $fields, true ) ) {
1050              $data['parent'] = (int) $comment->comment_parent;
1051          }
1052  
1053          if ( in_array( 'author', $fields, true ) ) {
1054              $data['author'] = (int) $comment->user_id;
1055          }
1056  
1057          if ( in_array( 'author_name', $fields, true ) ) {
1058              $data['author_name'] = $comment->comment_author;
1059          }
1060  
1061          if ( in_array( 'author_email', $fields, true ) ) {
1062              $data['author_email'] = $comment->comment_author_email;
1063          }
1064  
1065          if ( in_array( 'author_url', $fields, true ) ) {
1066              $data['author_url'] = $comment->comment_author_url;
1067          }
1068  
1069          if ( in_array( 'author_ip', $fields, true ) ) {
1070              $data['author_ip'] = $comment->comment_author_IP;
1071          }
1072  
1073          if ( in_array( 'author_user_agent', $fields, true ) ) {
1074              $data['author_user_agent'] = $comment->comment_agent;
1075          }
1076  
1077          if ( in_array( 'date', $fields, true ) ) {
1078              $data['date'] = mysql_to_rfc3339( $comment->comment_date );
1079          }
1080  
1081          if ( in_array( 'date_gmt', $fields, true ) ) {
1082              $data['date_gmt'] = mysql_to_rfc3339( $comment->comment_date_gmt );
1083          }
1084  
1085          if ( in_array( 'content', $fields, true ) ) {
1086              $data['content'] = array(
1087                  /** This filter is documented in wp-includes/comment-template.php */
1088                  'rendered' => apply_filters( 'comment_text', $comment->comment_content, $comment ),
1089                  'raw'      => $comment->comment_content,
1090              );
1091          }
1092  
1093          if ( in_array( 'link', $fields, true ) ) {
1094              $data['link'] = get_comment_link( $comment );
1095          }
1096  
1097          if ( in_array( 'status', $fields, true ) ) {
1098              $data['status'] = $this->prepare_status_response( $comment->comment_approved );
1099          }
1100  
1101          if ( in_array( 'type', $fields, true ) ) {
1102              $data['type'] = get_comment_type( $comment->comment_ID );
1103          }
1104  
1105          if ( in_array( 'author_avatar_urls', $fields, true ) ) {
1106              $data['author_avatar_urls'] = rest_get_avatar_urls( $comment );
1107          }
1108  
1109          if ( in_array( 'meta', $fields, true ) ) {
1110              $data['meta'] = $this->meta->get_value( $comment->comment_ID, $request );
1111          }
1112  
1113          $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
1114          $data    = $this->add_additional_fields_to_object( $data, $request );
1115          $data    = $this->filter_response_by_context( $data, $context );
1116  
1117          // Wrap the data in a response object.
1118          $response = rest_ensure_response( $data );
1119  
1120          $response->add_links( $this->prepare_links( $comment ) );
1121  
1122          /**
1123           * Filters a comment returned from the API.
1124           *
1125           * Allows modification of the comment right before it is returned.
1126           *
1127           * @since 4.7.0
1128           *
1129           * @param WP_REST_Response  $response The response object.
1130           * @param WP_Comment        $comment  The original comment object.
1131           * @param WP_REST_Request   $request  Request used to generate the response.
1132           */
1133          return apply_filters( 'rest_prepare_comment', $response, $comment, $request );
1134      }
1135  
1136      /**
1137       * Prepares links for the request.
1138       *
1139       * @since 4.7.0
1140       *
1141       * @param WP_Comment $comment Comment object.
1142       * @return array Links for the given comment.
1143       */
1144  	protected function prepare_links( $comment ) {
1145          $links = array(
1146              'self'       => array(
1147                  'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_ID ) ),
1148              ),
1149              'collection' => array(
1150                  'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
1151              ),
1152          );
1153  
1154          if ( 0 !== (int) $comment->user_id ) {
1155              $links['author'] = array(
1156                  'href'       => rest_url( 'wp/v2/users/' . $comment->user_id ),
1157                  'embeddable' => true,
1158              );
1159          }
1160  
1161          if ( 0 !== (int) $comment->comment_post_ID ) {
1162              $post = get_post( $comment->comment_post_ID );
1163  
1164              if ( ! empty( $post->ID ) ) {
1165                  $obj  = get_post_type_object( $post->post_type );
1166                  $base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
1167  
1168                  $links['up'] = array(
1169                      'href'       => rest_url( 'wp/v2/' . $base . '/' . $comment->comment_post_ID ),
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              );
1201          }
1202  
1203          return $links;
1204      }
1205  
1206      /**
1207       * Prepends internal property prefix to query parameters to match our response fields.
1208       *
1209       * @since 4.7.0
1210       *
1211       * @param string $query_param Query parameter.
1212       * @return string The normalized query parameter.
1213       */
1214  	protected function normalize_query_param( $query_param ) {
1215          $prefix = 'comment_';
1216  
1217          switch ( $query_param ) {
1218              case 'id':
1219                  $normalized = $prefix . 'ID';
1220                  break;
1221              case 'post':
1222                  $normalized = $prefix . 'post_ID';
1223                  break;
1224              case 'parent':
1225                  $normalized = $prefix . 'parent';
1226                  break;
1227              case 'include':
1228                  $normalized = 'comment__in';
1229                  break;
1230              default:
1231                  $normalized = $prefix . $query_param;
1232                  break;
1233          }
1234  
1235          return $normalized;
1236      }
1237  
1238      /**
1239       * Checks comment_approved to set comment status for single comment output.
1240       *
1241       * @since 4.7.0
1242       *
1243       * @param string|int $comment_approved comment status.
1244       * @return string Comment status.
1245       */
1246  	protected function prepare_status_response( $comment_approved ) {
1247  
1248          switch ( $comment_approved ) {
1249              case 'hold':
1250              case '0':
1251                  $status = 'hold';
1252                  break;
1253  
1254              case 'approve':
1255              case '1':
1256                  $status = 'approved';
1257                  break;
1258  
1259              case 'spam':
1260              case 'trash':
1261              default:
1262                  $status = $comment_approved;
1263                  break;
1264          }
1265  
1266          return $status;
1267      }
1268  
1269      /**
1270       * Prepares a single comment to be inserted into the database.
1271       *
1272       * @since 4.7.0
1273       *
1274       * @param WP_REST_Request $request Request object.
1275       * @return array|WP_Error Prepared comment, otherwise WP_Error object.
1276       */
1277  	protected function prepare_item_for_database( $request ) {
1278          $prepared_comment = array();
1279  
1280          /*
1281           * Allow the comment_content to be set via the 'content' or
1282           * the 'content.raw' properties of the Request object.
1283           */
1284          if ( isset( $request['content'] ) && is_string( $request['content'] ) ) {
1285              $prepared_comment['comment_content'] = $request['content'];
1286          } elseif ( isset( $request['content']['raw'] ) && is_string( $request['content']['raw'] ) ) {
1287              $prepared_comment['comment_content'] = $request['content']['raw'];
1288          }
1289  
1290          if ( isset( $request['post'] ) ) {
1291              $prepared_comment['comment_post_ID'] = (int) $request['post'];
1292          }
1293  
1294          if ( isset( $request['parent'] ) ) {
1295              $prepared_comment['comment_parent'] = $request['parent'];
1296          }
1297  
1298          if ( isset( $request['author'] ) ) {
1299              $user = new WP_User( $request['author'] );
1300  
1301              if ( $user->exists() ) {
1302                  $prepared_comment['user_id']              = $user->ID;
1303                  $prepared_comment['comment_author']       = $user->display_name;
1304                  $prepared_comment['comment_author_email'] = $user->user_email;
1305                  $prepared_comment['comment_author_url']   = $user->user_url;
1306              } else {
1307                  return new WP_Error(
1308                      'rest_comment_author_invalid',
1309                      __( 'Invalid comment author ID.' ),
1310                      array( 'status' => 400 )
1311                  );
1312              }
1313          }
1314  
1315          if ( isset( $request['author_name'] ) ) {
1316              $prepared_comment['comment_author'] = $request['author_name'];
1317          }
1318  
1319          if ( isset( $request['author_email'] ) ) {
1320              $prepared_comment['comment_author_email'] = $request['author_email'];
1321          }
1322  
1323          if ( isset( $request['author_url'] ) ) {
1324              $prepared_comment['comment_author_url'] = $request['author_url'];
1325          }
1326  
1327          if ( isset( $request['author_ip'] ) && current_user_can( 'moderate_comments' ) ) {
1328              $prepared_comment['comment_author_IP'] = $request['author_ip'];
1329          } elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) && rest_is_ip_address( $_SERVER['REMOTE_ADDR'] ) ) {
1330              $prepared_comment['comment_author_IP'] = $_SERVER['REMOTE_ADDR'];
1331          } else {
1332              $prepared_comment['comment_author_IP'] = '127.0.0.1';
1333          }
1334  
1335          if ( ! empty( $request['author_user_agent'] ) ) {
1336              $prepared_comment['comment_agent'] = $request['author_user_agent'];
1337          } elseif ( $request->get_header( 'user_agent' ) ) {
1338              $prepared_comment['comment_agent'] = $request->get_header( 'user_agent' );
1339          }
1340  
1341          if ( ! empty( $request['date'] ) ) {
1342              $date_data = rest_get_date_with_gmt( $request['date'] );
1343  
1344              if ( ! empty( $date_data ) ) {
1345                  list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data;
1346              }
1347          } elseif ( ! empty( $request['date_gmt'] ) ) {
1348              $date_data = rest_get_date_with_gmt( $request['date_gmt'], true );
1349  
1350              if ( ! empty( $date_data ) ) {
1351                  list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data;
1352              }
1353          }
1354  
1355          /**
1356           * Filters a comment after it is prepared for the database.
1357           *
1358           * Allows modification of the comment right after it is prepared for the database.
1359           *
1360           * @since 4.7.0
1361           *
1362           * @param array           $prepared_comment The prepared comment data for `wp_insert_comment`.
1363           * @param WP_REST_Request $request          The current request.
1364           */
1365          return apply_filters( 'rest_preprocess_comment', $prepared_comment, $request );
1366      }
1367  
1368      /**
1369       * Retrieves the comment's schema, conforming to JSON Schema.
1370       *
1371       * @since 4.7.0
1372       *
1373       * @return array
1374       */
1375  	public function get_item_schema() {
1376          if ( $this->schema ) {
1377              return $this->add_additional_fields_schema( $this->schema );
1378          }
1379  
1380          $schema = array(
1381              '$schema'    => 'http://json-schema.org/draft-04/schema#',
1382              'title'      => 'comment',
1383              'type'       => 'object',
1384              'properties' => array(
1385                  'id'                => array(
1386                      'description' => __( 'Unique identifier for the object.' ),
1387                      'type'        => 'integer',
1388                      'context'     => array( 'view', 'edit', 'embed' ),
1389                      'readonly'    => true,
1390                  ),
1391                  'author'            => array(
1392                      'description' => __( 'The ID of the user object, if author was a user.' ),
1393                      'type'        => 'integer',
1394                      'context'     => array( 'view', 'edit', 'embed' ),
1395                  ),
1396                  'author_email'      => array(
1397                      'description' => __( 'Email address for the object author.' ),
1398                      'type'        => 'string',
1399                      'format'      => 'email',
1400                      'context'     => array( 'edit' ),
1401                      'arg_options' => array(
1402                          'sanitize_callback' => array( $this, 'check_comment_author_email' ),
1403                          'validate_callback' => null, // Skip built-in validation of 'email'.
1404                      ),
1405                  ),
1406                  'author_ip'         => array(
1407                      'description' => __( 'IP address for the object author.' ),
1408                      'type'        => 'string',
1409                      'format'      => 'ip',
1410                      'context'     => array( 'edit' ),
1411                  ),
1412                  'author_name'       => array(
1413                      'description' => __( 'Display name for the object author.' ),
1414                      'type'        => 'string',
1415                      'context'     => array( 'view', 'edit', 'embed' ),
1416                      'arg_options' => array(
1417                          'sanitize_callback' => 'sanitize_text_field',
1418                      ),
1419                  ),
1420                  'author_url'        => array(
1421                      'description' => __( 'URL for the object author.' ),
1422                      'type'        => 'string',
1423                      'format'      => 'uri',
1424                      'context'     => array( 'view', 'edit', 'embed' ),
1425                  ),
1426                  'author_user_agent' => array(
1427                      'description' => __( 'User agent for the object author.' ),
1428                      'type'        => 'string',
1429                      'context'     => array( 'edit' ),
1430                      'arg_options' => array(
1431                          'sanitize_callback' => 'sanitize_text_field',
1432                      ),
1433                  ),
1434                  'content'           => array(
1435                      'description' => __( 'The content for the object.' ),
1436                      'type'        => 'object',
1437                      'context'     => array( 'view', 'edit', 'embed' ),
1438                      'arg_options' => array(
1439                          'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
1440                          'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
1441                      ),
1442                      'properties'  => array(
1443                          'raw'      => array(
1444                              'description' => __( 'Content for the object, as it exists in the database.' ),
1445                              'type'        => 'string',
1446                              'context'     => array( 'edit' ),
1447                          ),
1448                          'rendered' => array(
1449                              'description' => __( 'HTML content for the object, transformed for display.' ),
1450                              'type'        => 'string',
1451                              'context'     => array( 'view', 'edit', 'embed' ),
1452                              'readonly'    => true,
1453                          ),
1454                      ),
1455                  ),
1456                  'date'              => array(
1457                      'description' => __( "The date the object was published, in the site's timezone." ),
1458                      'type'        => 'string',
1459                      'format'      => 'date-time',
1460                      'context'     => array( 'view', 'edit', 'embed' ),
1461                  ),
1462                  'date_gmt'          => array(
1463                      'description' => __( 'The date the object was published, as GMT.' ),
1464                      'type'        => 'string',
1465                      'format'      => 'date-time',
1466                      'context'     => array( 'view', 'edit' ),
1467                  ),
1468                  'link'              => array(
1469                      'description' => __( 'URL to the object.' ),
1470                      'type'        => 'string',
1471                      'format'      => 'uri',
1472                      'context'     => array( 'view', 'edit', 'embed' ),
1473                      'readonly'    => true,
1474                  ),
1475                  'parent'            => array(
1476                      'description' => __( 'The ID for the parent of the object.' ),
1477                      'type'        => 'integer',
1478                      'context'     => array( 'view', 'edit', 'embed' ),
1479                      'default'     => 0,
1480                  ),
1481                  'post'              => array(
1482                      'description' => __( 'The ID of the associated post object.' ),
1483                      'type'        => 'integer',
1484                      'context'     => array( 'view', 'edit' ),
1485                      'default'     => 0,
1486                  ),
1487                  'status'            => array(
1488                      'description' => __( 'State of the object.' ),
1489                      'type'        => 'string',
1490                      'context'     => array( 'view', 'edit' ),
1491                      'arg_options' => array(
1492                          'sanitize_callback' => 'sanitize_key',
1493                      ),
1494                  ),
1495                  'type'              => array(
1496                      'description' => __( 'Type of Comment for the object.' ),
1497                      'type'        => 'string',
1498                      'context'     => array( 'view', 'edit', 'embed' ),
1499                      'readonly'    => true,
1500                  ),
1501              ),
1502          );
1503  
1504          if ( get_option( 'show_avatars' ) ) {
1505              $avatar_properties = array();
1506  
1507              $avatar_sizes = rest_get_avatar_sizes();
1508  
1509              foreach ( $avatar_sizes as $size ) {
1510                  $avatar_properties[ $size ] = array(
1511                      /* translators: %d: Avatar image size in pixels. */
1512                      'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ),
1513                      'type'        => 'string',
1514                      'format'      => 'uri',
1515                      'context'     => array( 'embed', 'view', 'edit' ),
1516                  );
1517              }
1518  
1519              $schema['properties']['author_avatar_urls'] = array(
1520                  'description' => __( 'Avatar URLs for the object author.' ),
1521                  'type'        => 'object',
1522                  'context'     => array( 'view', 'edit', 'embed' ),
1523                  'readonly'    => true,
1524                  'properties'  => $avatar_properties,
1525              );
1526          }
1527  
1528          $schema['properties']['meta'] = $this->meta->get_field_schema();
1529  
1530          $this->schema = $schema;
1531  
1532          return $this->add_additional_fields_schema( $this->schema );
1533      }
1534  
1535      /**
1536       * Retrieves the query params for collections.
1537       *
1538       * @since 4.7.0
1539       *
1540       * @return array Comments collection parameters.
1541       */
1542  	public function get_collection_params() {
1543          $query_params = parent::get_collection_params();
1544  
1545          $query_params['context']['default'] = 'view';
1546  
1547          $query_params['after'] = array(
1548              'description' => __( 'Limit response to comments published after a given ISO8601 compliant date.' ),
1549              'type'        => 'string',
1550              'format'      => 'date-time',
1551          );
1552  
1553          $query_params['author'] = array(
1554              'description' => __( 'Limit result set to comments assigned to specific user IDs. Requires authorization.' ),
1555              'type'        => 'array',
1556              'items'       => array(
1557                  'type' => 'integer',
1558              ),
1559          );
1560  
1561          $query_params['author_exclude'] = array(
1562              'description' => __( 'Ensure result set excludes comments assigned to specific user IDs. Requires authorization.' ),
1563              'type'        => 'array',
1564              'items'       => array(
1565                  'type' => 'integer',
1566              ),
1567          );
1568  
1569          $query_params['author_email'] = array(
1570              'default'     => null,
1571              'description' => __( 'Limit result set to that from a specific author email. Requires authorization.' ),
1572              'format'      => 'email',
1573              'type'        => 'string',
1574          );
1575  
1576          $query_params['before'] = array(
1577              'description' => __( 'Limit response to comments published before a given ISO8601 compliant date.' ),
1578              'type'        => 'string',
1579              'format'      => 'date-time',
1580          );
1581  
1582          $query_params['exclude'] = array(
1583              'description' => __( 'Ensure result set excludes specific IDs.' ),
1584              'type'        => 'array',
1585              'items'       => array(
1586                  'type' => 'integer',
1587              ),
1588              'default'     => array(),
1589          );
1590  
1591          $query_params['include'] = array(
1592              'description' => __( 'Limit result set to specific IDs.' ),
1593              'type'        => 'array',
1594              'items'       => array(
1595                  'type' => 'integer',
1596              ),
1597              'default'     => array(),
1598          );
1599  
1600          $query_params['offset'] = array(
1601              'description' => __( 'Offset the result set by a specific number of items.' ),
1602              'type'        => 'integer',
1603          );
1604  
1605          $query_params['order'] = array(
1606              'description' => __( 'Order sort attribute ascending or descending.' ),
1607              'type'        => 'string',
1608              'default'     => 'desc',
1609              'enum'        => array(
1610                  'asc',
1611                  'desc',
1612              ),
1613          );
1614  
1615          $query_params['orderby'] = array(
1616              'description' => __( 'Sort collection by object attribute.' ),
1617              'type'        => 'string',
1618              'default'     => 'date_gmt',
1619              'enum'        => array(
1620                  'date',
1621                  'date_gmt',
1622                  'id',
1623                  'include',
1624                  'post',
1625                  'parent',
1626                  'type',
1627              ),
1628          );
1629  
1630          $query_params['parent'] = array(
1631              'default'     => array(),
1632              'description' => __( 'Limit result set to comments of specific parent IDs.' ),
1633              'type'        => 'array',
1634              'items'       => array(
1635                  'type' => 'integer',
1636              ),
1637          );
1638  
1639          $query_params['parent_exclude'] = array(
1640              'default'     => array(),
1641              'description' => __( 'Ensure result set excludes specific parent IDs.' ),
1642              'type'        => 'array',
1643              'items'       => array(
1644                  'type' => 'integer',
1645              ),
1646          );
1647  
1648          $query_params['post'] = array(
1649              'default'     => array(),
1650              'description' => __( 'Limit result set to comments assigned to specific post IDs.' ),
1651              'type'        => 'array',
1652              'items'       => array(
1653                  'type' => 'integer',
1654              ),
1655          );
1656  
1657          $query_params['status'] = array(
1658              'default'           => 'approve',
1659              'description'       => __( 'Limit result set to comments assigned a specific status. Requires authorization.' ),
1660              'sanitize_callback' => 'sanitize_key',
1661              'type'              => 'string',
1662              'validate_callback' => 'rest_validate_request_arg',
1663          );
1664  
1665          $query_params['type'] = array(
1666              'default'           => 'comment',
1667              'description'       => __( 'Limit result set to comments assigned a specific type. Requires authorization.' ),
1668              'sanitize_callback' => 'sanitize_key',
1669              'type'              => 'string',
1670              'validate_callback' => 'rest_validate_request_arg',
1671          );
1672  
1673          $query_params['password'] = array(
1674              'description' => __( 'The password for the post if it is password protected.' ),
1675              'type'        => 'string',
1676          );
1677  
1678          /**
1679           * Filters collection parameters for the comments controller.
1680           *
1681           * This filter registers the collection parameter, but does not map the
1682           * collection parameter to an internal WP_Comment_Query parameter. Use the
1683           * `rest_comment_query` filter to set WP_Comment_Query parameters.
1684           *
1685           * @since 4.7.0
1686           *
1687           * @param array $query_params JSON Schema-formatted collection parameters.
1688           */
1689          return apply_filters( 'rest_comment_collection_params', $query_params );
1690      }
1691  
1692      /**
1693       * Sets the comment_status of a given comment object when creating or updating a comment.
1694       *
1695       * @since 4.7.0
1696       *
1697       * @param string|int $new_status New comment status.
1698       * @param int        $comment_id Comment ID.
1699       * @return bool Whether the status was changed.
1700       */
1701  	protected function handle_status_param( $new_status, $comment_id ) {
1702          $old_status = wp_get_comment_status( $comment_id );
1703  
1704          if ( $new_status === $old_status ) {
1705              return false;
1706          }
1707  
1708          switch ( $new_status ) {
1709              case 'approved':
1710              case 'approve':
1711              case '1':
1712                  $changed = wp_set_comment_status( $comment_id, 'approve' );
1713                  break;
1714              case 'hold':
1715              case '0':
1716                  $changed = wp_set_comment_status( $comment_id, 'hold' );
1717                  break;
1718              case 'spam':
1719                  $changed = wp_spam_comment( $comment_id );
1720                  break;
1721              case 'unspam':
1722                  $changed = wp_unspam_comment( $comment_id );
1723                  break;
1724              case 'trash':
1725                  $changed = wp_trash_comment( $comment_id );
1726                  break;
1727              case 'untrash':
1728                  $changed = wp_untrash_comment( $comment_id );
1729                  break;
1730              default:
1731                  $changed = false;
1732                  break;
1733          }
1734  
1735          return $changed;
1736      }
1737  
1738      /**
1739       * Checks if the post can be read.
1740       *
1741       * Correctly handles posts with the inherit status.
1742       *
1743       * @since 4.7.0
1744       *
1745       * @param WP_Post         $post    Post object.
1746       * @param WP_REST_Request $request Request data to check.
1747       * @return bool Whether post can be read.
1748       */
1749  	protected function check_read_post_permission( $post, $request ) {
1750          $post_type = get_post_type_object( $post->post_type );
1751  
1752          // Return false if custom post type doesn't exist
1753          if ( ! $post_type ) {
1754              return false;
1755          }
1756  
1757          $posts_controller = $post_type->get_rest_controller();
1758  
1759          // Ensure the posts controller is specifically a WP_REST_Posts_Controller instance
1760          // before using methods specific to that controller.
1761          if ( ! $posts_controller instanceof WP_REST_Posts_Controller ) {
1762              $posts_controller = new WP_REST_Posts_Controller( $post->post_type );
1763          }
1764  
1765          $has_password_filter = false;
1766  
1767          // Only check password if a specific post was queried for or a single comment
1768          $requested_post    = ! empty( $request['post'] ) && ( ! is_array( $request['post'] ) || 1 === count( $request['post'] ) );
1769          $requested_comment = ! empty( $request['id'] );
1770          if ( ( $requested_post || $requested_comment ) && $posts_controller->can_access_password_content( $post, $request ) ) {
1771              add_filter( 'post_password_required', '__return_false' );
1772  
1773              $has_password_filter = true;
1774          }
1775  
1776          if ( post_password_required( $post ) ) {
1777              $result = current_user_can( 'edit_post', $post->ID );
1778          } else {
1779              $result = $posts_controller->check_read_permission( $post );
1780          }
1781  
1782          if ( $has_password_filter ) {
1783              remove_filter( 'post_password_required', '__return_false' );
1784          }
1785  
1786          return $result;
1787      }
1788  
1789      /**
1790       * Checks if the comment can be read.
1791       *
1792       * @since 4.7.0
1793       *
1794       * @param WP_Comment      $comment Comment object.
1795       * @param WP_REST_Request $request Request data to check.
1796       * @return bool Whether the comment can be read.
1797       */
1798  	protected function check_read_permission( $comment, $request ) {
1799          if ( ! empty( $comment->comment_post_ID ) ) {
1800              $post = get_post( $comment->comment_post_ID );
1801              if ( $post ) {
1802                  if ( $this->check_read_post_permission( $post, $request ) && 1 === (int) $comment->comment_approved ) {
1803                      return true;
1804                  }
1805              }
1806          }
1807  
1808          if ( 0 === get_current_user_id() ) {
1809              return false;
1810          }
1811  
1812          if ( empty( $comment->comment_post_ID ) && ! current_user_can( 'moderate_comments' ) ) {
1813              return false;
1814          }
1815  
1816          if ( ! empty( $comment->user_id ) && get_current_user_id() === (int) $comment->user_id ) {
1817              return true;
1818          }
1819  
1820          return current_user_can( 'edit_comment', $comment->comment_ID );
1821      }
1822  
1823      /**
1824       * Checks if a comment can be edited or deleted.
1825       *
1826       * @since 4.7.0
1827       *
1828       * @param WP_Comment $comment Comment object.
1829       * @return bool Whether the comment can be edited or deleted.
1830       */
1831  	protected function check_edit_permission( $comment ) {
1832          if ( 0 === (int) get_current_user_id() ) {
1833              return false;
1834          }
1835  
1836          if ( current_user_can( 'moderate_comments' ) ) {
1837              return true;
1838          }
1839  
1840          return current_user_can( 'edit_comment', $comment->comment_ID );
1841      }
1842  
1843      /**
1844       * Checks a comment author email for validity.
1845       *
1846       * Accepts either a valid email address or empty string as a valid comment
1847       * author email address. Setting the comment author email to an empty
1848       * string is allowed when a comment is being updated.
1849       *
1850       * @since 4.7.0
1851       *
1852       * @param string          $value   Author email value submitted.
1853       * @param WP_REST_Request $request Full details about the request.
1854       * @param string          $param   The parameter name.
1855       * @return string|WP_Error The sanitized email address, if valid,
1856       *                         otherwise an error.
1857       */
1858  	public function check_comment_author_email( $value, $request, $param ) {
1859          $email = (string) $value;
1860          if ( empty( $email ) ) {
1861              return $email;
1862          }
1863  
1864          $check_email = rest_validate_request_arg( $email, $request, $param );
1865          if ( is_wp_error( $check_email ) ) {
1866              return $check_email;
1867          }
1868  
1869          return $email;
1870      }
1871  }


Generated: Thu Oct 22 01:00:02 2020 Cross-referenced by PHPXref 0.7.1