/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php

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