balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-wp-rest-comments-controller.php (57118B)


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