[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

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


Generated : Tue Sep 22 08:20:31 2026 Cross-referenced by PHPXref