/includes/api/v1/class-wc-rest-product-reviews-controller.php

  1. <?php 
  2. /** 
  3. * REST API Product Reviews Controller 
  4. * 
  5. * Handles requests to /products/<product_id>/reviews. 
  6. * 
  7. * @author WooThemes 
  8. * @category API 
  9. * @package WooCommerce/API 
  10. * @since 3.0.0 
  11. */ 
  12.  
  13. if ( ! defined( 'ABSPATH' ) ) { 
  14. exit; 
  15.  
  16. /** 
  17. * REST API Product Reviews Controller Class. 
  18. * 
  19. * @package WooCommerce/API 
  20. * @extends WC_REST_Controller 
  21. */ 
  22. class WC_REST_Product_Reviews_V1_Controller extends WC_REST_Controller { 
  23.  
  24. /** 
  25. * Endpoint namespace. 
  26. * 
  27. * @var string 
  28. */ 
  29. protected $namespace = 'wc/v1'; 
  30.  
  31. /** 
  32. * Route base. 
  33. * 
  34. * @var string 
  35. */ 
  36. protected $rest_base = 'products/(?P<product_id>[\d]+)/reviews'; 
  37.  
  38. /** 
  39. * Register the routes for product reviews. 
  40. */ 
  41. public function register_routes() { 
  42. register_rest_route( $this->namespace, '/' . $this->rest_base, array( 
  43. 'args' => array( 
  44. 'product_id' => array( 
  45. 'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),  
  46. 'type' => 'integer',  
  47. ),  
  48. 'id' => array( 
  49. 'description' => __( 'Unique identifier for the variation.', 'woocommerce' ),  
  50. 'type' => 'integer',  
  51. ),  
  52. ),  
  53. array( 
  54. 'methods' => WP_REST_Server::READABLE,  
  55. 'callback' => array( $this, 'get_items' ),  
  56. 'permission_callback' => array( $this, 'get_items_permissions_check' ),  
  57. 'args' => $this->get_collection_params(),  
  58. ),  
  59. array( 
  60. 'methods' => WP_REST_Server::CREATABLE,  
  61. 'callback' => array( $this, 'create_item' ),  
  62. 'permission_callback' => array( $this, 'create_item_permissions_check' ),  
  63. 'args' => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array( 
  64. 'review' => array( 
  65. 'required' => true,  
  66. 'type' => 'string',  
  67. 'description' => __( 'Review content.', 'woocommerce' ),  
  68. ),  
  69. 'name' => array( 
  70. 'required' => true,  
  71. 'type' => 'string',  
  72. 'description' => __( 'Name of the reviewer.', 'woocommerce' ),  
  73. ),  
  74. 'email' => array( 
  75. 'required' => true,  
  76. 'type' => 'string',  
  77. 'description' => __( 'Email of the reviewer.', 'woocommerce' ),  
  78. ),  
  79. ) ),  
  80. ),  
  81. 'schema' => array( $this, 'get_public_item_schema' ),  
  82. ) ); 
  83.  
  84. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 
  85. 'args' => array( 
  86. 'product_id' => array( 
  87. 'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),  
  88. 'type' => 'integer',  
  89. ),  
  90. 'id' => array( 
  91. 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),  
  92. 'type' => 'integer',  
  93. ),  
  94. ),  
  95. array( 
  96. 'methods' => WP_REST_Server::READABLE,  
  97. 'callback' => array( $this, 'get_item' ),  
  98. 'permission_callback' => array( $this, 'get_item_permissions_check' ),  
  99. 'args' => array( 
  100. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),  
  101. ),  
  102. ),  
  103. array( 
  104. 'methods' => WP_REST_Server::EDITABLE,  
  105. 'callback' => array( $this, 'update_item' ),  
  106. 'permission_callback' => array( $this, 'update_item_permissions_check' ),  
  107. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),  
  108. ),  
  109. array( 
  110. 'methods' => WP_REST_Server::DELETABLE,  
  111. 'callback' => array( $this, 'delete_item' ),  
  112. 'permission_callback' => array( $this, 'delete_item_permissions_check' ),  
  113. 'args' => array( 
  114. 'force' => array( 
  115. 'default' => false,  
  116. 'type' => 'boolean',  
  117. 'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),  
  118. ),  
  119. ),  
  120. ),  
  121. 'schema' => array( $this, 'get_public_item_schema' ),  
  122. ) ); 
  123.  
  124. /** 
  125. * Check whether a given request has permission to read webhook deliveries. 
  126. * 
  127. * @param WP_REST_Request $request Full details about the request. 
  128. * @return WP_Error|boolean 
  129. */ 
  130. public function get_items_permissions_check( $request ) { 
  131. if ( ! wc_rest_check_post_permissions( 'product', 'read' ) ) { 
  132. return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); 
  133.  
  134. return true; 
  135.  
  136. /** 
  137. * Check if a given request has access to read a product review. 
  138. * 
  139. * @param WP_REST_Request $request Full details about the request. 
  140. * @return WP_Error|boolean 
  141. */ 
  142. public function get_item_permissions_check( $request ) { 
  143. $post = get_post( (int) $request['product_id'] ); 
  144.  
  145. if ( $post && ! wc_rest_check_post_permissions( 'product', 'read', $post->ID ) ) { 
  146. return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); 
  147.  
  148. return true; 
  149.  
  150. /** 
  151. * Check if a given request has access to create a new product review. 
  152. * 
  153. * @param WP_REST_Request $request Full details about the request. 
  154. * @return WP_Error|boolean 
  155. */ 
  156. public function create_item_permissions_check( $request ) { 
  157. $post = get_post( (int) $request['product_id'] ); 
  158. if ( $post && ! wc_rest_check_post_permissions( 'product', 'create', $post->ID ) ) { 
  159. return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); 
  160. return true; 
  161.  
  162. /** 
  163. * Check if a given request has access to update a product review. 
  164. * 
  165. * @param WP_REST_Request $request Full details about the request. 
  166. * @return WP_Error|boolean 
  167. */ 
  168. public function update_item_permissions_check( $request ) { 
  169. $post = get_post( (int) $request['product_id'] ); 
  170. if ( $post && ! wc_rest_check_post_permissions( 'product', 'edit', $post->ID ) ) { 
  171. return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you cannot edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); 
  172. return true; 
  173.  
  174. /** 
  175. * Check if a given request has access to delete a product review. 
  176. * 
  177. * @param WP_REST_Request $request Full details about the request. 
  178. * @return WP_Error|boolean 
  179. */ 
  180. public function delete_item_permissions_check( $request ) { 
  181. $post = get_post( (int) $request['product_id'] ); 
  182. if ( $post && ! wc_rest_check_post_permissions( 'product', 'delete', $post->ID ) ) { 
  183. return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you cannot delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); 
  184. return true; 
  185.  
  186. /** 
  187. * Get all reviews from a product. 
  188. * 
  189. * @param WP_REST_Request $request 
  190. * @return array 
  191. */ 
  192. public function get_items( $request ) { 
  193. $product_id = (int) $request['product_id']; 
  194.  
  195. if ( 'product' !== get_post_type( $product_id ) ) { 
  196. return new WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) ); 
  197.  
  198. $reviews = get_approved_comments( $product_id ); 
  199. $data = array(); 
  200. foreach ( $reviews as $review_data ) { 
  201. $review = $this->prepare_item_for_response( $review_data, $request ); 
  202. $review = $this->prepare_response_for_collection( $review ); 
  203. $data[] = $review; 
  204.  
  205. return rest_ensure_response( $data ); 
  206.  
  207. /** 
  208. * Get a single product review. 
  209. * 
  210. * @param WP_REST_Request $request Full details about the request. 
  211. * @return WP_Error|WP_REST_Response 
  212. */ 
  213. public function get_item( $request ) { 
  214. $id = (int) $request['id']; 
  215. $product_id = (int) $request['product_id']; 
  216.  
  217. if ( 'product' !== get_post_type( $product_id ) ) { 
  218. return new WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) ); 
  219.  
  220. $review = get_comment( $id ); 
  221.  
  222. if ( empty( $id ) || empty( $review ) || intval( $review->comment_post_ID ) !== $product_id ) { 
  223. return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource ID.', 'woocommerce' ), array( 'status' => 404 ) ); 
  224.  
  225. $delivery = $this->prepare_item_for_response( $review, $request ); 
  226. $response = rest_ensure_response( $delivery ); 
  227.  
  228. return $response; 
  229.  
  230.  
  231. /** 
  232. * Create a product review. 
  233. * 
  234. * @param WP_REST_Request $request Full details about the request. 
  235. * @return WP_Error|WP_REST_Response 
  236. */ 
  237. public function create_item( $request ) { 
  238. $product_id = (int) $request['product_id']; 
  239.  
  240. if ( 'product' !== get_post_type( $product_id ) ) { 
  241. return new WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) ); 
  242.  
  243. $prepared_review = $this->prepare_item_for_database( $request ); 
  244.  
  245. /** 
  246. * Filter a product review (comment) before it is inserted via the REST API. 
  247. * 
  248. * Allows modification of the comment right before it is inserted via `wp_insert_comment`. 
  249. * 
  250. * @param array $prepared_review The prepared comment data for `wp_insert_comment`. 
  251. * @param WP_REST_Request $request Request used to insert the comment. 
  252. */ 
  253. $prepared_review = apply_filters( 'rest_pre_insert_product_review', $prepared_review, $request ); 
  254.  
  255. $product_review_id = wp_insert_comment( $prepared_review ); 
  256. if ( ! $product_review_id ) { 
  257. return new WP_Error( 'rest_product_review_failed_create', __( 'Creating product review failed.', 'woocommerce' ), array( 'status' => 500 ) ); 
  258.  
  259. update_comment_meta( $product_review_id, 'rating', ( ! empty( $request['rating'] ) ? $request['rating'] : '0' ) ); 
  260.  
  261. $product_review = get_comment( $product_review_id ); 
  262. $this->update_additional_fields_for_object( $product_review, $request ); 
  263.  
  264. /** 
  265. * Fires after a single item is created or updated via the REST API. 
  266. * 
  267. * @param WP_Comment $product_review Inserted object. 
  268. * @param WP_REST_Request $request Request object. 
  269. * @param boolean $creating True when creating item, false when updating. 
  270. */ 
  271. do_action( "woocommerce_rest_insert_product_review", $product_review, $request, true ); 
  272.  
  273. $request->set_param( 'context', 'edit' ); 
  274. $response = $this->prepare_item_for_response( $product_review, $request ); 
  275. $response = rest_ensure_response( $response ); 
  276. $response->set_status( 201 ); 
  277. $base = str_replace( '(?P<product_id>[\d]+)', $product_id, $this->rest_base ); 
  278. $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $product_review_id ) ) ); 
  279.  
  280. return $response; 
  281.  
  282. /** 
  283. * Update a single product review. 
  284. * 
  285. * @param WP_REST_Request $request Full details about the request. 
  286. * @return WP_Error|WP_REST_Response 
  287. */ 
  288. public function update_item( $request ) { 
  289. $product_review_id = (int) $request['id']; 
  290. $product_id = (int) $request['product_id']; 
  291.  
  292. if ( 'product' !== get_post_type( $product_id ) ) { 
  293. return new WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) ); 
  294.  
  295. $review = get_comment( $product_review_id ); 
  296.  
  297. if ( empty( $product_review_id ) || empty( $review ) || intval( $review->comment_post_ID ) !== $product_id ) { 
  298. return new WP_Error( 'woocommerce_rest_product_review_invalid_id', __( 'Invalid resource ID.', 'woocommerce' ), array( 'status' => 404 ) ); 
  299.  
  300. $prepared_review = $this->prepare_item_for_database( $request ); 
  301.  
  302. $updated = wp_update_comment( $prepared_review ); 
  303. if ( 0 === $updated ) { 
  304. return new WP_Error( 'rest_product_review_failed_edit', __( 'Updating product review failed.', 'woocommerce' ), array( 'status' => 500 ) ); 
  305.  
  306. if ( ! empty( $request['rating'] ) ) { 
  307. update_comment_meta( $product_review_id, 'rating', $request['rating'] ); 
  308.  
  309. $product_review = get_comment( $product_review_id ); 
  310. $this->update_additional_fields_for_object( $product_review, $request ); 
  311.  
  312. /** 
  313. * Fires after a single item is created or updated via the REST API. 
  314. * 
  315. * @param WP_Comment $comment Inserted object. 
  316. * @param WP_REST_Request $request Request object. 
  317. * @param boolean $creating True when creating item, false when updating. 
  318. */ 
  319. do_action( "woocommerce_rest_insert_product_review", $product_review, $request, true ); 
  320.  
  321. $request->set_param( 'context', 'edit' ); 
  322. $response = $this->prepare_item_for_response( $product_review, $request ); 
  323.  
  324. return rest_ensure_response( $response ); 
  325.  
  326. /** 
  327. * Delete a product review. 
  328. * 
  329. * @param WP_REST_Request $request Full details about the request 
  330. * @return WP_Error|boolean 
  331. */ 
  332. public function delete_item( $request ) { 
  333. $product_review_id = absint( is_array( $request['id'] ) ? $request['id']['id'] : $request['id'] ); 
  334. $force = isset( $request['force'] ) ? (bool) $request['force'] : false; 
  335.  
  336. $product_review = get_comment( $product_review_id ); 
  337. if ( empty( $product_review_id ) || empty( $product_review->comment_ID ) || empty( $product_review->comment_post_ID ) ) { 
  338. return new WP_Error( 'woocommerce_rest_product_review_invalid_id', __( 'Invalid product review ID.', 'woocommerce' ), array( 'status' => 404 ) ); 
  339.  
  340. /** 
  341. * Filter whether a product review is trashable. 
  342. * 
  343. * Return false to disable trash support for the product review. 
  344. * 
  345. * @param boolean $supports_trash Whether the object supports trashing. 
  346. * @param WP_Post $product_review The object being considered for trashing support. 
  347. */ 
  348. $supports_trash = apply_filters( 'rest_product_review_trashable', ( EMPTY_TRASH_DAYS > 0 ), $product_review ); 
  349.  
  350. $request->set_param( 'context', 'edit' ); 
  351. $response = $this->prepare_item_for_response( $product_review, $request ); 
  352.  
  353. if ( $force ) { 
  354. $result = wp_delete_comment( $product_review_id, true ); 
  355. } else { 
  356. if ( ! $supports_trash ) { 
  357. return new WP_Error( 'rest_trash_not_supported', __( 'The product review does not support trashing.', 'woocommerce' ), array( 'status' => 501 ) ); 
  358.  
  359. if ( 'trash' === $product_review->comment_approved ) { 
  360. return new WP_Error( 'rest_already_trashed', __( 'The comment has already been trashed.', 'woocommerce' ), array( 'status' => 410 ) ); 
  361.  
  362. $result = wp_trash_comment( $product_review->comment_ID ); 
  363.  
  364. if ( ! $result ) { 
  365. return new WP_Error( 'rest_cannot_delete', __( 'The product review cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) ); 
  366.  
  367. /** 
  368. * Fires after a product review is deleted via the REST API. 
  369. * 
  370. * @param object $product_review The deleted item. 
  371. * @param WP_REST_Response $response The response data. 
  372. * @param WP_REST_Request $request The request sent to the API. 
  373. */ 
  374. do_action( 'rest_delete_product_review', $product_review, $response, $request ); 
  375.  
  376. return $response; 
  377.  
  378. /** 
  379. * Prepare a single product review output for response. 
  380. * 
  381. * @param WP_Comment $review Product review object. 
  382. * @param WP_REST_Request $request Request object. 
  383. * @return WP_REST_Response $response Response data. 
  384. */ 
  385. public function prepare_item_for_response( $review, $request ) { 
  386. $data = array( 
  387. 'id' => (int) $review->comment_ID,  
  388. 'date_created' => wc_rest_prepare_date_response( $review->comment_date_gmt ),  
  389. 'review' => $review->comment_content,  
  390. 'rating' => (int) get_comment_meta( $review->comment_ID, 'rating', true ),  
  391. 'name' => $review->comment_author,  
  392. 'email' => $review->comment_author_email,  
  393. 'verified' => wc_review_is_from_verified_owner( $review->comment_ID ),  
  394. ); 
  395.  
  396. $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 
  397. $data = $this->add_additional_fields_to_object( $data, $request ); 
  398. $data = $this->filter_response_by_context( $data, $context ); 
  399.  
  400. // Wrap the data in a response object. 
  401. $response = rest_ensure_response( $data ); 
  402.  
  403. $response->add_links( $this->prepare_links( $review, $request ) ); 
  404.  
  405. /** 
  406. * Filter product reviews object returned from the REST API. 
  407. * 
  408. * @param WP_REST_Response $response The response object. 
  409. * @param WP_Comment $review Product review object used to create response. 
  410. * @param WP_REST_Request $request Request object. 
  411. */ 
  412. return apply_filters( 'woocommerce_rest_prepare_product_review', $response, $review, $request ); 
  413.  
  414. /** 
  415. * Prepare a single product review to be inserted into the database. 
  416. * 
  417. * @param WP_REST_Request $request Request object. 
  418. * @return array|WP_Error $prepared_review 
  419. */ 
  420. protected function prepare_item_for_database( $request ) { 
  421. $prepared_review = array( 'comment_approved' => 1, 'comment_type' => 'review' ); 
  422.  
  423. if ( isset( $request['id'] ) ) { 
  424. $prepared_review['comment_ID'] = (int) $request['id']; 
  425.  
  426. if ( isset( $request['review'] ) ) { 
  427. $prepared_review['comment_content'] = $request['review']; 
  428.  
  429. if ( isset( $request['product_id'] ) ) { 
  430. $prepared_review['comment_post_ID'] = (int) $request['product_id']; 
  431.  
  432. if ( isset( $request['name'] ) ) { 
  433. $prepared_review['comment_author'] = $request['name']; 
  434.  
  435. if ( isset( $request['email'] ) ) { 
  436. $prepared_review['comment_author_email'] = $request['email']; 
  437.  
  438. return apply_filters( 'rest_preprocess_product_review', $prepared_review, $request ); 
  439.  
  440. /** 
  441. * Prepare links for the request. 
  442. * 
  443. * @param WP_Comment $review Product review object. 
  444. * @param WP_REST_Request $request Request object. 
  445. * @return array Links for the given product review. 
  446. */ 
  447. protected function prepare_links( $review, $request ) { 
  448. $product_id = (int) $request['product_id']; 
  449. $base = str_replace( '(?P<product_id>[\d]+)', $product_id, $this->rest_base ); 
  450. $links = array( 
  451. 'self' => array( 
  452. 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $review->comment_ID ) ),  
  453. ),  
  454. 'collection' => array( 
  455. 'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ),  
  456. ),  
  457. 'up' => array( 
  458. 'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product_id ) ),  
  459. ),  
  460. ); 
  461.  
  462. return $links; 
  463.  
  464. /** 
  465. * Get the Product Review's schema, conforming to JSON Schema. 
  466. * 
  467. * @return array 
  468. */ 
  469. public function get_item_schema() { 
  470. $schema = array( 
  471. '$schema' => 'http://json-schema.org/draft-04/schema#',  
  472. 'title' => 'product_review',  
  473. 'type' => 'object',  
  474. 'properties' => array( 
  475. 'id' => array( 
  476. 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),  
  477. 'type' => 'integer',  
  478. 'context' => array( 'view', 'edit' ),  
  479. 'readonly' => true,  
  480. ),  
  481. 'review' => array( 
  482. 'description' => __( 'The content of the review.', 'woocommerce' ),  
  483. 'type' => 'string',  
  484. 'context' => array( 'view', 'edit' ),  
  485. ),  
  486. 'date_created' => array( 
  487. 'description' => __( "The date the review was created, in the site's timezone.", 'woocommerce' ),  
  488. 'type' => 'date-time',  
  489. 'context' => array( 'view', 'edit' ),  
  490. ),  
  491. 'rating' => array( 
  492. 'description' => __( 'Review rating (0 to 5).', 'woocommerce' ),  
  493. 'type' => 'integer',  
  494. 'context' => array( 'view', 'edit' ),  
  495. ),  
  496. 'name' => array( 
  497. 'description' => __( 'Reviewer name.', 'woocommerce' ),  
  498. 'type' => 'string',  
  499. 'context' => array( 'view', 'edit' ),  
  500. ),  
  501. 'email' => array( 
  502. 'description' => __( 'Reviewer email.', 'woocommerce' ),  
  503. 'type' => 'string',  
  504. 'context' => array( 'view', 'edit' ),  
  505. ),  
  506. 'verified' => array( 
  507. 'description' => __( 'Shows if the reviewer bought the product or not.', 'woocommerce' ),  
  508. 'type' => 'boolean',  
  509. 'context' => array( 'view', 'edit' ),  
  510. 'readonly' => true,  
  511. ),  
  512. ),  
  513. ); 
  514.  
  515. return $this->add_additional_fields_schema( $schema ); 
  516.  
  517. /** 
  518. * Get the query params for collections. 
  519. * 
  520. * @return array 
  521. */ 
  522. public function get_collection_params() { 
  523. return array( 
  524. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),  
  525. ); 
.