home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / rest-api / endpoints / class-wp-rest-comments-controller.php < prev    next >
Encoding:
PHP Script  |  2017-10-04  |  52.5 KB  |  1,620 lines

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