: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* REST API: WP_REST_Comments_Controller class
* Core controller used to access comments via the REST API.
* @see WP_REST_Controller
class WP_REST_Comments_Controller extends WP_REST_Controller {
* Instance of a comment meta fields object.
* @var WP_REST_Comment_Meta_Fields
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'comments';
$this->meta = new WP_REST_Comment_Meta_Fields();
* Registers the routes for comments.
* @see register_rest_route()
public function register_routes() {
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
'schema' => array( $this, 'get_public_item_schema' ),
'/' . $this->rest_base . '/(?P<id>[\d]+)',
'description' => __( 'Unique identifier for the comment.' ),
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
'description' => __( 'The password for the parent post of the comment (if the post is password protected).' ),
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'description' => __( 'Whether to bypass Trash and force deletion.' ),
'description' => __( 'The password for the parent post of the comment (if the post is password protected).' ),
'schema' => array( $this, 'get_public_item_schema' ),
* Checks if a given request has access to read comments.
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, error object otherwise.
public function get_items_permissions_check( $request ) {
if ( ! empty( $request['post'] ) ) {
foreach ( (array) $request['post'] as $post_id ) {
$post = get_post( $post_id );
if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post, $request ) ) {
__( 'Sorry, you are not allowed to read the post for this comment.' ),
array( 'status' => rest_authorization_required_code() )
} elseif ( 0 === $post_id && ! current_user_can( 'moderate_comments' ) ) {
__( 'Sorry, you are not allowed to read comments without a post.' ),
array( 'status' => rest_authorization_required_code() )
if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) {
'rest_forbidden_context',
__( 'Sorry, you are not allowed to edit comments.' ),
array( 'status' => rest_authorization_required_code() )
if ( ! current_user_can( 'edit_posts' ) ) {
$protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' );
$forbidden_params = array();
foreach ( $protected_params as $param ) {
if ( 'status' === $param ) {
if ( 'approve' !== $request[ $param ] ) {
$forbidden_params[] = $param;
} elseif ( 'type' === $param ) {
if ( 'comment' !== $request[ $param ] ) {
$forbidden_params[] = $param;
} elseif ( ! empty( $request[ $param ] ) ) {
$forbidden_params[] = $param;
if ( ! empty( $forbidden_params ) ) {
/* translators: %s: List of forbidden parameters. */
sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ),
array( 'status' => rest_authorization_required_code() )
* Retrieves a list of comment items.
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or error object on failure.
public function get_items( $request ) {
// Retrieve the list of registered collection query parameters.
$registered = $this->get_collection_params();
* This array defines mappings between public API query parameters whose
* values are accepted as-passed, and their internal WP_Query parameter
* name equivalents (some are the same). Only values which are also
* present in $registered will be set.
$parameter_mappings = array(
'author' => 'author__in',
'author_email' => 'author_email',
'author_exclude' => 'author__not_in',
'exclude' => 'comment__not_in',
'include' => 'comment__in',
'parent' => 'parent__in',
'parent_exclude' => 'parent__not_in',
$prepared_args = array();
* For each known parameter which is both registered and present in the request,
* set the parameter's value on the query $prepared_args.
foreach ( $parameter_mappings as $api_param => $wp_param ) {
if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
$prepared_args[ $wp_param ] = $request[ $api_param ];
// Ensure certain parameter values default to empty strings.
foreach ( array( 'author_email', 'search' ) as $param ) {
if ( ! isset( $prepared_args[ $param ] ) ) {
$prepared_args[ $param ] = '';
if ( isset( $registered['orderby'] ) ) {
$prepared_args['orderby'] = $this->normalize_query_param( $request['orderby'] );
$prepared_args['no_found_rows'] = false;
$prepared_args['update_comment_post_cache'] = true;
$prepared_args['date_query'] = array();
// Set before into date query. Date query must be specified as an array of an array.
if ( isset( $registered['before'], $request['before'] ) ) {
$prepared_args['date_query'][0]['before'] = $request['before'];
// Set after into date query. Date query must be specified as an array of an array.
if ( isset( $registered['after'], $request['after'] ) ) {
$prepared_args['date_query'][0]['after'] = $request['after'];
if ( isset( $registered['page'] ) && empty( $request['offset'] ) ) {
$prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 );
* Filters WP_Comment_Query arguments when querying comments via the REST API.
* @link https://developer.wordpress.org/reference/classes/wp_comment_query/
* @param array $prepared_args Array of arguments for WP_Comment_Query.
* @param WP_REST_Request $request The REST API request.
$prepared_args = apply_filters( 'rest_comment_query', $prepared_args, $request );
$query = new WP_Comment_Query();
$query_result = $query->query( $prepared_args );
foreach ( $query_result as $comment ) {
if ( ! $this->check_read_permission( $comment, $request ) ) {
$data = $this->prepare_item_for_response( $comment, $request );
$comments[] = $this->prepare_response_for_collection( $data );
$total_comments = (int) $query->found_comments;
$max_pages = (int) $query->max_num_pages;
if ( $total_comments < 1 ) {
// Out-of-bounds, run the query again without LIMIT for total count.
unset( $prepared_args['number'], $prepared_args['offset'] );
$query = new WP_Comment_Query();
$prepared_args['count'] = true;
$prepared_args['orderby'] = 'none';
$total_comments = $query->query( $prepared_args );
$max_pages = (int) ceil( $total_comments / $request['per_page'] );
$response = rest_ensure_response( $comments );
$response->header( 'X-WP-Total', $total_comments );
$response->header( 'X-WP-TotalPages', $max_pages );
$base = add_query_arg( urlencode_deep( $request->get_query_params() ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
if ( $request['page'] > 1 ) {
$prev_page = $request['page'] - 1;
if ( $prev_page > $max_pages ) {
$prev_link = add_query_arg( 'page', $prev_page, $base );
$response->link_header( 'prev', $prev_link );
if ( $max_pages > $request['page'] ) {
$next_page = $request['page'] + 1;
$next_link = add_query_arg( 'page', $next_page, $base );
$response->link_header( 'next', $next_link );
* Get the comment, if the ID is valid.
* @param int $id Supplied ID.
* @return WP_Comment|WP_Error Comment object if ID is valid, WP_Error otherwise.
protected function get_comment( $id ) {
'rest_comment_invalid_id',
__( 'Invalid comment ID.' ),
$comment = get_comment( $id );
if ( empty( $comment ) ) {
if ( ! empty( $comment->comment_post_ID ) ) {
$post = get_post( (int) $comment->comment_post_ID );
__( 'Invalid post ID.' ),
* Checks if a given request has access to read the comment.
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access for the item, error object otherwise.
public function get_item_permissions_check( $request ) {
$comment = $this->get_comment( $request['id'] );
if ( is_wp_error( $comment ) ) {
if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) {
'rest_forbidden_context',
__( 'Sorry, you are not allowed to edit comments.' ),
array( 'status' => rest_authorization_required_code() )
$post = get_post( $comment->comment_post_ID );
if ( ! $this->check_read_permission( $comment, $request ) ) {
__( 'Sorry, you are not allowed to read this comment.' ),
array( 'status' => rest_authorization_required_code() )
if ( $post && ! $this->check_read_post_permission( $post, $request ) ) {
__( 'Sorry, you are not allowed to read the post for this comment.' ),
array( 'status' => rest_authorization_required_code() )
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or error object on failure.
public function get_item( $request ) {
$comment = $this->get_comment( $request['id'] );
if ( is_wp_error( $comment ) ) {
$data = $this->prepare_item_for_response( $comment, $request );
$response = rest_ensure_response( $data );
* Checks if a given request has access to create a comment.
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to create items, error object otherwise.
public function create_item_permissions_check( $request ) {
if ( ! is_user_logged_in() ) {
if ( get_option( 'comment_registration' ) ) {
'rest_comment_login_required',
__( 'Sorry, you must be logged in to comment.' ),
* Filters whether comments can be created via the REST API without authentication.
* Enables creating comments for anonymous users.
* @param bool $allow_anonymous Whether to allow anonymous comments to
* be created. Default `false`.
* @param WP_REST_Request $request Request used to generate the
$allow_anonymous = apply_filters( 'rest_allow_anonymous_comments', false, $request );
if ( ! $allow_anonymous ) {
'rest_comment_login_required',
__( 'Sorry, you must be logged in to comment.' ),
// Limit who can set comment `author`, `author_ip` or `status` to anything other than the default.
if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) {
'rest_comment_invalid_author',
/* translators: %s: Request parameter. */
sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author' ),
array( 'status' => rest_authorization_required_code() )
if ( isset( $request['author_ip'] ) && ! current_user_can( 'moderate_comments' ) ) {
if ( empty( $_SERVER['REMOTE_ADDR'] ) || $request['author_ip'] !== $_SERVER['REMOTE_ADDR'] ) {
'rest_comment_invalid_author_ip',
/* translators: %s: Request parameter. */
sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author_ip' ),
array( 'status' => rest_authorization_required_code() )
if ( isset( $request['status'] ) && ! current_user_can( 'moderate_comments' ) ) {
'rest_comment_invalid_status',