WC_REST_CRUD_Controller

WC_REST_CRUD_Controller class.

Defined (1)

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

/includes/abstracts/abstract-wc-rest-crud-controller.php  
  1. abstract class WC_REST_CRUD_Controller extends WC_REST_Posts_Controller { 
  2.  
  3. /** 
  4. * Endpoint namespace. 
  5. * @var string 
  6. */ 
  7. protected $namespace = 'wc/v2'; 
  8.  
  9. /** 
  10. * If object is hierarchical. 
  11. * @var bool 
  12. */ 
  13. protected $hierarchical = false; 
  14.  
  15. /** 
  16. * Get object. 
  17. * @param int $id Object ID. 
  18. * @return WP_Error|WC_Data 
  19. */ 
  20. protected function get_object( $id ) { 
  21. return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) ); 
  22.  
  23. /** 
  24. * Check if a given request has access to read an item. 
  25. * @param WP_REST_Request $request Full details about the request. 
  26. * @return WP_Error|boolean 
  27. */ 
  28. public function get_item_permissions_check( $request ) { 
  29. $object = $this->get_object( (int) $request['id'] ); 
  30.  
  31. if ( $object && 0 !== $object->get_id() && ! wc_rest_check_post_permissions( $this->post_type, 'read', $object->get_id() ) ) { 
  32. return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); 
  33.  
  34. return true; 
  35.  
  36. /** 
  37. * Check if a given request has access to update an item. 
  38. * @param WP_REST_Request $request Full details about the request. 
  39. * @return WP_Error|boolean 
  40. */ 
  41. public function update_item_permissions_check( $request ) { 
  42. $object = $this->get_object( (int) $request['id'] ); 
  43.  
  44. if ( $object && 0 !== $object->get_id() && ! wc_rest_check_post_permissions( $this->post_type, 'edit', $object->get_id() ) ) { 
  45. return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); 
  46.  
  47. return true; 
  48.  
  49. /** 
  50. * Check if a given request has access to delete an item. 
  51. * @param WP_REST_Request $request Full details about the request. 
  52. * @return bool|WP_Error 
  53. */ 
  54. public function delete_item_permissions_check( $request ) { 
  55. $object = $this->get_object( (int) $request['id'] ); 
  56.  
  57. if ( $object && 0 !== $object->get_id() && ! wc_rest_check_post_permissions( $this->post_type, 'delete', $object->get_id() ) ) { 
  58. return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); 
  59.  
  60. return true; 
  61.  
  62. /** 
  63. * Get object permalink. 
  64. * @param int $id Object ID. 
  65. * @return string 
  66. */ 
  67. protected function get_permalink( $object ) { 
  68. return ''; 
  69.  
  70. /** 
  71. * Prepares the object for the REST response. 
  72. * @since 3.0.0 
  73. * @param WC_Data $object Object data. 
  74. * @param WP_REST_Request $request Request object. 
  75. * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. 
  76. */ 
  77. protected function prepare_object_for_response( $object, $request ) { 
  78. return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) ); 
  79.  
  80. /** 
  81. * Prepares one object for create or update operation. 
  82. * @since 3.0.0 
  83. * @param WP_REST_Request $request Request object. 
  84. * @param bool $creating If is creating a new object. 
  85. * @return WP_Error|WC_Data The prepared item, or WP_Error object on failure. 
  86. */ 
  87. protected function prepare_object_for_database( $request, $creating = false ) { 
  88. return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) ); 
  89.  
  90. /** 
  91. * Get a single item. 
  92. * @param WP_REST_Request $request Full details about the request. 
  93. * @return WP_Error|WP_REST_Response 
  94. */ 
  95. public function get_item( $request ) { 
  96. $object = $this->get_object( (int) $request['id'] ); 
  97.  
  98. if ( ! $object || 0 === $object->get_id() ) { 
  99. return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) ); 
  100.  
  101. $data = $this->prepare_object_for_response( $object, $request ); 
  102. $response = rest_ensure_response( $data ); 
  103.  
  104. if ( $this->public ) { 
  105. $response->link_header( 'alternate', $this->get_permalink( $object ), array( 'type' => 'text/html' ) ); 
  106.  
  107. return $response; 
  108.  
  109. /** 
  110. * Save an object data. 
  111. * @since 3.0.0 
  112. * @param WP_REST_Request $request Full details about the request. 
  113. * @param bool $creating If is creating a new object. 
  114. * @return WC_Data|WP_Error 
  115. */ 
  116. protected function save_object( $request, $creating = false ) { 
  117. try { 
  118. $object = $this->prepare_object_for_database( $request, $creating ); 
  119.  
  120. if ( is_wp_error( $object ) ) { 
  121. return $object; 
  122.  
  123. $object->save(); 
  124.  
  125. return $this->get_object( $object->get_id() ); 
  126. } catch ( WC_Data_Exception $e ) { 
  127. return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() ); 
  128. } catch ( WC_REST_Exception $e ) { 
  129. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  130.  
  131. /** 
  132. * Create a single item. 
  133. * @param WP_REST_Request $request Full details about the request. 
  134. * @return WP_Error|WP_REST_Response 
  135. */ 
  136. public function create_item( $request ) { 
  137. if ( ! empty( $request['id'] ) ) { 
  138. /** translators: %s: post type */ 
  139. return new WP_Error( "woocommerce_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'woocommerce' ), $this->post_type ), array( 'status' => 400 ) ); 
  140.  
  141. $object = $this->save_object( $request, true ); 
  142.  
  143. if ( is_wp_error( $object ) ) { 
  144. return $object; 
  145.  
  146. $this->update_additional_fields_for_object( $object, $request ); 
  147.  
  148. /** 
  149. * Fires after a single object is created or updated via the REST API. 
  150. * @param WC_Data $object Inserted object. 
  151. * @param WP_REST_Request $request Request object. 
  152. * @param boolean $creating True when creating object, false when updating. 
  153. */ 
  154. do_action( "woocommerce_rest_insert_{$this->post_type}_object", $object, $request, true ); 
  155.  
  156. $request->set_param( 'context', 'edit' ); 
  157. $response = $this->prepare_object_for_response( $object, $request ); 
  158. $response = rest_ensure_response( $response ); 
  159. $response->set_status( 201 ); 
  160. $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ) ); 
  161.  
  162. return $response; 
  163.  
  164. /** 
  165. * Update a single post. 
  166. * @param WP_REST_Request $request Full details about the request. 
  167. * @return WP_Error|WP_REST_Response 
  168. */ 
  169. public function update_item( $request ) { 
  170. $object = $this->get_object( (int) $request['id'] ); 
  171.  
  172. if ( ! $object || 0 === $object->get_id() ) { 
  173. return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 400 ) ); 
  174.  
  175. $object = $this->save_object( $request, false ); 
  176.  
  177. if ( is_wp_error( $object ) ) { 
  178. return $object; 
  179.  
  180. $this->update_additional_fields_for_object( $object, $request ); 
  181.  
  182. /** 
  183. * Fires after a single object is created or updated via the REST API. 
  184. * @param WC_Data $object Inserted object. 
  185. * @param WP_REST_Request $request Request object. 
  186. * @param boolean $creating True when creating object, false when updating. 
  187. */ 
  188. do_action( "woocommerce_rest_insert_{$this->post_type}_object", $object, $request, false ); 
  189.  
  190. $request->set_param( 'context', 'edit' ); 
  191. $response = $this->prepare_object_for_response( $object, $request ); 
  192. return rest_ensure_response( $response ); 
  193.  
  194. /** 
  195. * Prepare objects query. 
  196. * @since 3.0.0 
  197. * @param WP_REST_Request $request Full details about the request. 
  198. * @return array 
  199. */ 
  200. protected function prepare_objects_query( $request ) { 
  201. $args = array(); 
  202. $args['offset'] = $request['offset']; 
  203. $args['order'] = $request['order']; 
  204. $args['orderby'] = $request['orderby']; 
  205. $args['paged'] = $request['page']; 
  206. $args['post__in'] = $request['include']; 
  207. $args['post__not_in'] = $request['exclude']; 
  208. $args['posts_per_page'] = $request['per_page']; 
  209. $args['name'] = $request['slug']; 
  210. $args['post_parent__in'] = $request['parent']; 
  211. $args['post_parent__not_in'] = $request['parent_exclude']; 
  212. $args['s'] = $request['search']; 
  213.  
  214. $args['date_query'] = array(); 
  215. // Set before into date query. Date query must be specified as an array of an array. 
  216. if ( isset( $request['before'] ) ) { 
  217. $args['date_query'][0]['before'] = $request['before']; 
  218.  
  219. // Set after into date query. Date query must be specified as an array of an array. 
  220. if ( isset( $request['after'] ) ) { 
  221. $args['date_query'][0]['after'] = $request['after']; 
  222.  
  223. // Force the post_type argument, since it's not a user input variable. 
  224. $args['post_type'] = $this->post_type; 
  225.  
  226. /** 
  227. * Filter the query arguments for a request. 
  228. * Enables adding extra arguments or setting defaults for a post 
  229. * collection request. 
  230. * @param array $args Key value array of query var to query value. 
  231. * @param WP_REST_Request $request The request used. 
  232. */ 
  233. $args = apply_filters( "woocommerce_rest_{$this->post_type}_object_query", $args, $request ); 
  234.  
  235. return $this->prepare_items_query( $args, $request ); 
  236.  
  237. /** 
  238. * Get objects. 
  239. * @since 3.0.0 
  240. * @param array $query_args Query args. 
  241. * @return array 
  242. */ 
  243. protected function get_objects( $query_args ) { 
  244. $query = new WP_Query(); 
  245. $result = $query->query( $query_args ); 
  246.  
  247. $total_posts = $query->found_posts; 
  248. if ( $total_posts < 1 ) { 
  249. // Out-of-bounds, run the query again without LIMIT for total count. 
  250. unset( $query_args['paged'] ); 
  251. $count_query = new WP_Query(); 
  252. $count_query->query( $query_args ); 
  253. $total_posts = $count_query->found_posts; 
  254.  
  255. return array( 
  256. 'objects' => array_map( array( $this, 'get_object' ), $result ),  
  257. 'total' => (int) $total_posts,  
  258. 'pages' => (int) ceil( $total_posts / (int) $query->query_vars['posts_per_page'] ),  
  259. ); 
  260.  
  261. /** 
  262. * Get a collection of posts. 
  263. * @param WP_REST_Request $request Full details about the request. 
  264. * @return WP_Error|WP_REST_Response 
  265. */ 
  266. public function get_items( $request ) { 
  267. $query_args = $this->prepare_objects_query( $request ); 
  268. $query_results = $this->get_objects( $query_args ); 
  269.  
  270. $objects = array(); 
  271. foreach ( $query_results['objects'] as $object ) { 
  272. if ( ! wc_rest_check_post_permissions( $this->post_type, 'read', $object->get_id() ) ) { 
  273. continue; 
  274.  
  275. $data = $this->prepare_object_for_response( $object, $request ); 
  276. $objects[] = $this->prepare_response_for_collection( $data ); 
  277.  
  278. $page = (int) $query_args['paged']; 
  279. $max_pages = $query_results['pages']; 
  280.  
  281. $response = rest_ensure_response( $objects ); 
  282. $response->header( 'X-WP-Total', $query_results['total'] ); 
  283. $response->header( 'X-WP-TotalPages', (int) $max_pages ); 
  284.  
  285. $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) ); 
  286.  
  287. if ( $page > 1 ) { 
  288. $prev_page = $page - 1; 
  289. if ( $prev_page > $max_pages ) { 
  290. $prev_page = $max_pages; 
  291. $prev_link = add_query_arg( 'page', $prev_page, $base ); 
  292. $response->link_header( 'prev', $prev_link ); 
  293. if ( $max_pages > $page ) { 
  294. $next_page = $page + 1; 
  295. $next_link = add_query_arg( 'page', $next_page, $base ); 
  296. $response->link_header( 'next', $next_link ); 
  297.  
  298. return $response; 
  299.  
  300. /** 
  301. * Delete a single item. 
  302. * @param WP_REST_Request $request Full details about the request. 
  303. * @return WP_REST_Response|WP_Error 
  304. */ 
  305. public function delete_item( $request ) { 
  306. $id = (int) $request['id']; 
  307. $force = (bool) $request['force']; 
  308. $object = $this->get_object( (int) $request['id'] ); 
  309. $result = false; 
  310.  
  311. if ( ! $object || 0 === $object->get_id() ) { 
  312. return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) ); 
  313.  
  314. $supports_trash = EMPTY_TRASH_DAYS > 0 && is_callable( array( $object, 'get_status' ) ); 
  315.  
  316. /** 
  317. * Filter whether an object is trashable. 
  318. * Return false to disable trash support for the object. 
  319. * @param boolean $supports_trash Whether the object type support trashing. 
  320. * @param WC_Data $object The object being considered for trashing support. 
  321. */ 
  322. $supports_trash = apply_filters( "woocommerce_rest_{$this->post_type}_object_trashable", $supports_trash, $object ); 
  323.  
  324. if ( ! wc_rest_check_post_permissions( $this->post_type, 'delete', $object->get_id() ) ) { 
  325. /** translators: %s: post type */ 
  326. return new WP_Error( "woocommerce_rest_user_cannot_delete_{$this->post_type}", sprintf( __( 'Sorry, you are not allowed to delete %s.', 'woocommerce' ), $this->post_type ), array( 'status' => rest_authorization_required_code() ) ); 
  327.  
  328. $request->set_param( 'context', 'edit' ); 
  329. $response = $this->prepare_object_for_response( $object, $request ); 
  330.  
  331. // If we're forcing, then delete permanently. 
  332. if ( $force ) { 
  333. $object->delete( true ); 
  334. $result = 0 === $object->get_id(); 
  335. } else { 
  336. // If we don't support trashing for this type, error out. 
  337. if ( ! $supports_trash ) { 
  338. /** translators: %s: post type */ 
  339. return new WP_Error( 'woocommerce_rest_trash_not_supported', sprintf( __( 'The %s does not support trashing.', 'woocommerce' ), $this->post_type ), array( 'status' => 501 ) ); 
  340.  
  341. // Otherwise, only trash if we haven't already. 
  342. if ( is_callable( array( $object, 'get_status' ) ) ) { 
  343. if ( 'trash' === $object->get_status() ) { 
  344. /** translators: %s: post type */ 
  345. return new WP_Error( 'woocommerce_rest_already_trashed', sprintf( __( 'The %s has already been deleted.', 'woocommerce' ), $this->post_type ), array( 'status' => 410 ) ); 
  346.  
  347. $object->delete(); 
  348. $result = 'trash' === $object->get_status(); 
  349.  
  350. if ( ! $result ) { 
  351. /** translators: %s: post type */ 
  352. return new WP_Error( 'woocommerce_rest_cannot_delete', sprintf( __( 'The %s cannot be deleted.', 'woocommerce' ), $this->post_type ), array( 'status' => 500 ) ); 
  353.  
  354. /** 
  355. * Fires after a single object is deleted or trashed via the REST API. 
  356. * @param WC_Data $object The deleted or trashed object. 
  357. * @param WP_REST_Response $response The response data. 
  358. * @param WP_REST_Request $request The request sent to the API. 
  359. */ 
  360. do_action( "woocommerce_rest_delete_{$this->post_type}_object", $object, $response, $request ); 
  361.  
  362. return $response; 
  363.  
  364. /** 
  365. * Prepare links for the request. 
  366. * @param WC_Data $object Object data. 
  367. * @param WP_REST_Request $request Request object. 
  368. * @return array Links for the given post. 
  369. */ 
  370. protected function prepare_links( $object, $request ) { 
  371. $links = array( 
  372. 'self' => array( 
  373. 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ),  
  374. ),  
  375. 'collection' => array( 
  376. 'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),  
  377. ),  
  378. ); 
  379.  
  380. return $links; 
  381.  
  382. /** 
  383. * Get the query params for collections of attachments. 
  384. * @return array 
  385. */ 
  386. public function get_collection_params() { 
  387. $params = array(); 
  388. $params['context'] = $this->get_context_param(); 
  389. $params['context']['default'] = 'view'; 
  390.  
  391. $params['page'] = array( 
  392. 'description' => __( 'Current page of the collection.', 'woocommerce' ),  
  393. 'type' => 'integer',  
  394. 'default' => 1,  
  395. 'sanitize_callback' => 'absint',  
  396. 'validate_callback' => 'rest_validate_request_arg',  
  397. 'minimum' => 1,  
  398. ); 
  399. $params['per_page'] = array( 
  400. 'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ),  
  401. 'type' => 'integer',  
  402. 'default' => 10,  
  403. 'minimum' => 1,  
  404. 'maximum' => 100,  
  405. 'sanitize_callback' => 'absint',  
  406. 'validate_callback' => 'rest_validate_request_arg',  
  407. ); 
  408. $params['search'] = array( 
  409. 'description' => __( 'Limit results to those matching a string.', 'woocommerce' ),  
  410. 'type' => 'string',  
  411. 'sanitize_callback' => 'sanitize_text_field',  
  412. 'validate_callback' => 'rest_validate_request_arg',  
  413. ); 
  414. $params['after'] = array( 
  415. 'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ),  
  416. 'type' => 'string',  
  417. 'format' => 'date-time',  
  418. 'validate_callback' => 'rest_validate_request_arg',  
  419. ); 
  420. $params['before'] = array( 
  421. 'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce' ),  
  422. 'type' => 'string',  
  423. 'format' => 'date-time',  
  424. 'validate_callback' => 'rest_validate_request_arg',  
  425. ); 
  426. $params['exclude'] = array( 
  427. 'description' => __( 'Ensure result set excludes specific IDs.', 'woocommerce' ),  
  428. 'type' => 'array',  
  429. 'items' => array( 
  430. 'type' => 'integer',  
  431. ),  
  432. 'default' => array(),  
  433. 'sanitize_callback' => 'wp_parse_id_list',  
  434. ); 
  435. $params['include'] = array( 
  436. 'description' => __( 'Limit result set to specific ids.', 'woocommerce' ),  
  437. 'type' => 'array',  
  438. 'items' => array( 
  439. 'type' => 'integer',  
  440. ),  
  441. 'default' => array(),  
  442. 'sanitize_callback' => 'wp_parse_id_list',  
  443. ); 
  444. $params['offset'] = array( 
  445. 'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ),  
  446. 'type' => 'integer',  
  447. 'sanitize_callback' => 'absint',  
  448. 'validate_callback' => 'rest_validate_request_arg',  
  449. ); 
  450. $params['order'] = array( 
  451. 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),  
  452. 'type' => 'string',  
  453. 'default' => 'desc',  
  454. 'enum' => array( 'asc', 'desc' ),  
  455. 'validate_callback' => 'rest_validate_request_arg',  
  456. ); 
  457. $params['orderby'] = array( 
  458. 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ),  
  459. 'type' => 'string',  
  460. 'default' => 'date',  
  461. 'enum' => array( 
  462. 'date',  
  463. 'id',  
  464. 'include',  
  465. 'title',  
  466. 'slug',  
  467. ),  
  468. 'validate_callback' => 'rest_validate_request_arg',  
  469. ); 
  470.  
  471. if ( $this->hierarchical ) { 
  472. $params['parent'] = array( 
  473. 'description' => __( 'Limit result set to those of particular parent IDs.', 'woocommerce' ),  
  474. 'type' => 'array',  
  475. 'items' => array( 
  476. 'type' => 'integer',  
  477. ),  
  478. 'sanitize_callback' => 'wp_parse_id_list',  
  479. 'default' => array(),  
  480. ); 
  481. $params['parent_exclude'] = array( 
  482. 'description' => __( 'Limit result set to all items except those of a particular parent ID.', 'woocommerce' ),  
  483. 'type' => 'array',  
  484. 'items' => array( 
  485. 'type' => 'integer',  
  486. ),  
  487. 'sanitize_callback' => 'wp_parse_id_list',  
  488. 'default' => array(),  
  489. ); 
  490.  
  491. return $params;