[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

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


Generated : Thu Apr 10 08:20:01 2025 Cross-referenced by PHPXref