WP_REST_Comments_Controller

Core controller used to access comments via the REST API.

Defined (1)

The class is defined in the following location(s).

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