WC_API_Resource

The WooCommerce WC API Resource class.

Defined (3)

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

/includes/api/legacy/v1/class-wc-api-resource.php  
  1. class WC_API_Resource { 
  2.  
  3. /** @var WC_API_Server the API server */ 
  4. protected $server; 
  5.  
  6. /** @var string sub-classes override this to set a resource-specific base route */ 
  7. protected $base; 
  8.  
  9. /** 
  10. * Setup class 
  11. * @since 2.1 
  12. * @param WC_API_Server $server 
  13. * @return WC_API_Resource 
  14. */ 
  15. public function __construct( WC_API_Server $server ) { 
  16.  
  17. $this->server = $server; 
  18.  
  19. // automatically register routes for sub-classes 
  20. add_filter( 'woocommerce_api_endpoints', array( $this, 'register_routes' ) ); 
  21.  
  22. // remove fields from responses when requests specify certain fields 
  23. // note these are hooked at a later priority so data added via filters (e.g. customer data to the order response) 
  24. // still has the fields filtered properly 
  25. foreach ( array( 'order', 'coupon', 'customer', 'product', 'report' ) as $resource ) { 
  26.  
  27. add_filter( "woocommerce_api_{$resource}_response", array( $this, 'maybe_add_meta' ), 15, 2 ); 
  28. add_filter( "woocommerce_api_{$resource}_response", array( $this, 'filter_response_fields' ), 20, 3 ); 
  29.  
  30. /** 
  31. * Validate the request by checking: 
  32. * 1) the ID is a valid integer 
  33. * 2) the ID returns a valid post object and matches the provided post type 
  34. * 3) the current user has the proper permissions to read/edit/delete the post 
  35. * @since 2.1 
  36. * @param string|int $id the post ID 
  37. * @param string $type the post type, either `shop_order`, `shop_coupon`, or `product` 
  38. * @param string $context the context of the request, either `read`, `edit` or `delete` 
  39. * @return int|WP_Error valid post ID or WP_Error if any of the checks fails 
  40. */ 
  41. protected function validate_request( $id, $type, $context ) { 
  42.  
  43. if ( 'shop_order' === $type || 'shop_coupon' === $type ) 
  44. $resource_name = str_replace( 'shop_', '', $type ); 
  45. else 
  46. $resource_name = $type; 
  47.  
  48. $id = absint( $id ); 
  49.  
  50. // validate ID 
  51. if ( empty( $id ) ) 
  52. return new WP_Error( "woocommerce_api_invalid_{$resource_name}_id", sprintf( __( 'Invalid %s ID', 'woocommerce' ), $type ), array( 'status' => 404 ) ); 
  53.  
  54. // only custom post types have per-post type/permission checks 
  55. if ( 'customer' !== $type ) { 
  56.  
  57. $post = get_post( $id ); 
  58.  
  59. // for checking permissions, product variations are the same as the product post type 
  60. $post_type = ( 'product_variation' === $post->post_type ) ? 'product' : $post->post_type; 
  61.  
  62. // validate post type 
  63. if ( $type !== $post_type ) 
  64. return new WP_Error( "woocommerce_api_invalid_{$resource_name}", sprintf( __( 'Invalid %s', 'woocommerce' ), $resource_name ), array( 'status' => 404 ) ); 
  65.  
  66. // validate permissions 
  67. switch ( $context ) { 
  68.  
  69. case 'read': 
  70. if ( ! $this->is_readable( $post ) ) 
  71. return new WP_Error( "woocommerce_api_user_cannot_read_{$resource_name}", sprintf( __( 'You do not have permission to read this %s', 'woocommerce' ), $resource_name ), array( 'status' => 401 ) ); 
  72. break; 
  73.  
  74. case 'edit': 
  75. if ( ! $this->is_editable( $post ) ) 
  76. return new WP_Error( "woocommerce_api_user_cannot_edit_{$resource_name}", sprintf( __( 'You do not have permission to edit this %s', 'woocommerce' ), $resource_name ), array( 'status' => 401 ) ); 
  77. break; 
  78.  
  79. case 'delete': 
  80. if ( ! $this->is_deletable( $post ) ) 
  81. return new WP_Error( "woocommerce_api_user_cannot_delete_{$resource_name}", sprintf( __( 'You do not have permission to delete this %s', 'woocommerce' ), $resource_name ), array( 'status' => 401 ) ); 
  82. break; 
  83.  
  84. return $id; 
  85.  
  86. /** 
  87. * Add common request arguments to argument list before WP_Query is run 
  88. * @since 2.1 
  89. * @param array $base_args required arguments for the query (e.g. `post_type`, etc) 
  90. * @param array $request_args arguments provided in the request 
  91. * @return array 
  92. */ 
  93. protected function merge_query_args( $base_args, $request_args ) { 
  94.  
  95. $args = array(); 
  96.  
  97. // date 
  98. if ( ! empty( $request_args['created_at_min'] ) || ! empty( $request_args['created_at_max'] ) || ! empty( $request_args['updated_at_min'] ) || ! empty( $request_args['updated_at_max'] ) ) { 
  99.  
  100. $args['date_query'] = array(); 
  101.  
  102. // resources created after specified date 
  103. if ( ! empty( $request_args['created_at_min'] ) ) 
  104. $args['date_query'][] = array( 'column' => 'post_date_gmt', 'after' => $this->server->parse_datetime( $request_args['created_at_min'] ), 'inclusive' => true ); 
  105.  
  106. // resources created before specified date 
  107. if ( ! empty( $request_args['created_at_max'] ) ) 
  108. $args['date_query'][] = array( 'column' => 'post_date_gmt', 'before' => $this->server->parse_datetime( $request_args['created_at_max'] ), 'inclusive' => true ); 
  109.  
  110. // resources updated after specified date 
  111. if ( ! empty( $request_args['updated_at_min'] ) ) 
  112. $args['date_query'][] = array( 'column' => 'post_modified_gmt', 'after' => $this->server->parse_datetime( $request_args['updated_at_min'] ), 'inclusive' => true ); 
  113.  
  114. // resources updated before specified date 
  115. if ( ! empty( $request_args['updated_at_max'] ) ) 
  116. $args['date_query'][] = array( 'column' => 'post_modified_gmt', 'before' => $this->server->parse_datetime( $request_args['updated_at_max'] ), 'inclusive' => true ); 
  117.  
  118. // search 
  119. if ( ! empty( $request_args['q'] ) ) 
  120. $args['s'] = $request_args['q']; 
  121.  
  122. // resources per response 
  123. if ( ! empty( $request_args['limit'] ) ) 
  124. $args['posts_per_page'] = $request_args['limit']; 
  125.  
  126. // resource offset 
  127. if ( ! empty( $request_args['offset'] ) ) 
  128. $args['offset'] = $request_args['offset']; 
  129.  
  130. // resource page 
  131. $args['paged'] = ( isset( $request_args['page'] ) ) ? absint( $request_args['page'] ) : 1; 
  132.  
  133. return array_merge( $base_args, $args ); 
  134.  
  135. /** 
  136. * Add meta to resources when requested by the client. Meta is added as a top-level 
  137. * `<resource_name>_meta` attribute (e.g. `order_meta`) as a list of key/value pairs 
  138. * @since 2.1 
  139. * @param array $data the resource data 
  140. * @param object $resource the resource object (e.g WC_Order) 
  141. * @return mixed 
  142. */ 
  143. public function maybe_add_meta( $data, $resource ) { 
  144.  
  145. if ( isset( $this->server->params['GET']['filter']['meta'] ) && 'true' === $this->server->params['GET']['filter']['meta'] && is_object( $resource ) ) { 
  146.  
  147. // don't attempt to add meta more than once 
  148. if ( preg_grep( '/[a-z]+_meta/', array_keys( $data ) ) ) 
  149. return $data; 
  150.  
  151. // define the top-level property name for the meta 
  152. switch ( get_class( $resource ) ) { 
  153.  
  154. case 'WC_Order': 
  155. $meta_name = 'order_meta'; 
  156. break; 
  157.  
  158. case 'WC_Coupon': 
  159. $meta_name = 'coupon_meta'; 
  160. break; 
  161.  
  162. case 'WP_User': 
  163. $meta_name = 'customer_meta'; 
  164. break; 
  165.  
  166. default: 
  167. $meta_name = 'product_meta'; 
  168. break; 
  169.  
  170. if ( is_a( $resource, 'WP_User' ) ) { 
  171.  
  172. // customer meta 
  173. $meta = (array) get_user_meta( $resource->ID ); 
  174.  
  175. } elseif ( is_a( $resource, 'WC_Product_Variation' ) ) { 
  176.  
  177. // product variation meta 
  178. $meta = (array) get_post_meta( $resource->get_variation_id() ); 
  179.  
  180. } else { 
  181.  
  182. // coupon/order/product meta 
  183. $meta = (array) get_post_meta( $resource->id ); 
  184.  
  185. foreach ( $meta as $meta_key => $meta_value ) { 
  186.  
  187. // don't add hidden meta by default 
  188. if ( ! is_protected_meta( $meta_key ) ) { 
  189. $data[ $meta_name ][ $meta_key ] = maybe_unserialize( $meta_value[0] ); 
  190.  
  191. return $data; 
  192.  
  193. /** 
  194. * Restrict the fields included in the response if the request specified certain only certain fields should be returned 
  195. * @since 2.1 
  196. * @param array $data the response data 
  197. * @param object $resource the object that provided the response data, e.g. WC_Coupon or WC_Order 
  198. * @param array|string the requested list of fields to include in the response 
  199. * @return array response data 
  200. */ 
  201. public function filter_response_fields( $data, $resource, $fields ) { 
  202.  
  203. if ( ! is_array( $data ) || empty( $fields ) ) 
  204. return $data; 
  205.  
  206. $fields = explode( ', ', $fields ); 
  207. $sub_fields = array(); 
  208.  
  209. // get sub fields 
  210. foreach ( $fields as $field ) { 
  211.  
  212. if ( false !== strpos( $field, '.' ) ) { 
  213.  
  214. list( $name, $value ) = explode( '.', $field ); 
  215.  
  216. $sub_fields[ $name ] = $value; 
  217.  
  218. // iterate through top-level fields 
  219. foreach ( $data as $data_field => $data_value ) { 
  220.  
  221. // if a field has sub-fields and the top-level field has sub-fields to filter 
  222. if ( is_array( $data_value ) && in_array( $data_field, array_keys( $sub_fields ) ) ) { 
  223.  
  224. // iterate through each sub-field 
  225. foreach ( $data_value as $sub_field => $sub_field_value ) { 
  226.  
  227. // remove non-matching sub-fields 
  228. if ( ! in_array( $sub_field, $sub_fields ) ) { 
  229. unset( $data[ $data_field ][ $sub_field ] ); 
  230. } else { 
  231.  
  232. // remove non-matching top-level fields 
  233. if ( ! in_array( $data_field, $fields ) ) { 
  234. unset( $data[ $data_field ] ); 
  235.  
  236. return $data; 
  237.  
  238. /** 
  239. * Delete a given resource 
  240. * @since 2.1 
  241. * @param int $id the resource ID 
  242. * @param string $type the resource post type, or `customer` 
  243. * @param bool $force true to permanently delete resource, false to move to trash (not supported for `customer`) 
  244. * @return array|WP_Error 
  245. */ 
  246. protected function delete( $id, $type, $force = false ) { 
  247.  
  248. if ( 'shop_order' === $type || 'shop_coupon' === $type ) 
  249. $resource_name = str_replace( 'shop_', '', $type ); 
  250. else 
  251. $resource_name = $type; 
  252.  
  253. if ( 'customer' === $type ) { 
  254.  
  255. $result = wp_delete_user( $id ); 
  256.  
  257. if ( $result ) 
  258. return array( 'message' => __( 'Permanently deleted customer', 'woocommerce' ) ); 
  259. else 
  260. return new WP_Error( 'woocommerce_api_cannot_delete_customer', __( 'The customer cannot be deleted', 'woocommerce' ), array( 'status' => 500 ) ); 
  261.  
  262. } else { 
  263.  
  264. // delete order/coupon/product 
  265. $result = ( $force ) ? wp_delete_post( $id, true ) : wp_trash_post( $id ); 
  266.  
  267. if ( ! $result ) 
  268. return new WP_Error( "woocommerce_api_cannot_delete_{$resource_name}", sprintf( __( 'This %s cannot be deleted', 'woocommerce' ), $resource_name ), array( 'status' => 500 ) ); 
  269.  
  270. if ( $force ) { 
  271. return array( 'message' => sprintf( __( 'Permanently deleted %s', 'woocommerce' ), $resource_name ) ); 
  272.  
  273. } else { 
  274.  
  275. $this->server->send_status( '202' ); 
  276.  
  277. return array( 'message' => sprintf( __( 'Deleted %s', 'woocommerce' ), $resource_name ) ); 
  278.  
  279.  
  280. /** 
  281. * Checks if the given post is readable by the current user 
  282. * @since 2.1 
  283. * @see WC_API_Resource::check_permission() 
  284. * @param WP_Post|int $post 
  285. * @return bool 
  286. */ 
  287. protected function is_readable( $post ) { 
  288.  
  289. return $this->check_permission( $post, 'read' ); 
  290.  
  291. /** 
  292. * Checks if the given post is editable by the current user 
  293. * @since 2.1 
  294. * @see WC_API_Resource::check_permission() 
  295. * @param WP_Post|int $post 
  296. * @return bool 
  297. */ 
  298. protected function is_editable( $post ) { 
  299.  
  300. return $this->check_permission( $post, 'edit' ); 
  301.  
  302.  
  303. /** 
  304. * Checks if the given post is deletable by the current user 
  305. * @since 2.1 
  306. * @see WC_API_Resource::check_permission() 
  307. * @param WP_Post|int $post 
  308. * @return bool 
  309. */ 
  310. protected function is_deletable( $post ) { 
  311.  
  312. return $this->check_permission( $post, 'delete' ); 
  313.  
  314. /** 
  315. * Checks the permissions for the current user given a post and context 
  316. * @since 2.1 
  317. * @param WP_Post|int $post 
  318. * @param string $context the type of permission to check, either `read`, `write`, or `delete` 
  319. * @return bool true if the current user has the permissions to perform the context on the post 
  320. */ 
  321. private function check_permission( $post, $context ) { 
  322.  
  323. if ( ! is_a( $post, 'WP_Post' ) ) 
  324. $post = get_post( $post ); 
  325.  
  326. if ( is_null( $post ) ) 
  327. return false; 
  328.  
  329. $post_type = get_post_type_object( $post->post_type ); 
  330.  
  331. if ( 'read' === $context ) 
  332. return current_user_can( $post_type->cap->read_private_posts, $post->ID ); 
  333.  
  334. elseif ( 'edit' === $context ) 
  335. return current_user_can( $post_type->cap->edit_post, $post->ID ); 
  336.  
  337. elseif ( 'delete' === $context ) 
  338. return current_user_can( $post_type->cap->delete_post, $post->ID ); 
  339.  
  340. else 
  341. return false; 
/includes/api/legacy/v2/class-wc-api-resource.php  
  1. class WC_API_Resource { 
  2.  
  3. /** @var WC_API_Server the API server */ 
  4. protected $server; 
  5.  
  6. /** @var string sub-classes override this to set a resource-specific base route */ 
  7. protected $base; 
  8.  
  9. /** 
  10. * Setup class 
  11. * @since 2.1 
  12. * @param WC_API_Server $server 
  13. * @return WC_API_Resource 
  14. */ 
  15. public function __construct( WC_API_Server $server ) { 
  16.  
  17. $this->server = $server; 
  18.  
  19. // automatically register routes for sub-classes 
  20. add_filter( 'woocommerce_api_endpoints', array( $this, 'register_routes' ) ); 
  21.  
  22. // maybe add meta to top-level resource responses 
  23. foreach ( array( 'order', 'coupon', 'customer', 'product', 'report' ) as $resource ) { 
  24. add_filter( "woocommerce_api_{$resource}_response", array( $this, 'maybe_add_meta' ), 15, 2 ); 
  25.  
  26. $response_names = array( 
  27. 'order',  
  28. 'coupon',  
  29. 'customer',  
  30. 'product',  
  31. 'report',  
  32. 'customer_orders',  
  33. 'customer_downloads',  
  34. 'order_note',  
  35. 'order_refund',  
  36. 'product_reviews',  
  37. 'product_category',  
  38. ); 
  39.  
  40. foreach ( $response_names as $name ) { 
  41.  
  42. /** 
  43. * Remove fields from responses when requests specify certain fields 
  44. * note these are hooked at a later priority so data added via 
  45. * filters (e.g. customer data to the order response) still has the 
  46. * fields filtered properly 
  47. */ 
  48. add_filter( "woocommerce_api_{$name}_response", array( $this, 'filter_response_fields' ), 20, 3 ); 
  49.  
  50. /** 
  51. * Validate the request by checking: 
  52. * 1) the ID is a valid integer 
  53. * 2) the ID returns a valid post object and matches the provided post type 
  54. * 3) the current user has the proper permissions to read/edit/delete the post 
  55. * @since 2.1 
  56. * @param string|int $id the post ID 
  57. * @param string $type the post type, either `shop_order`, `shop_coupon`, or `product` 
  58. * @param string $context the context of the request, either `read`, `edit` or `delete` 
  59. * @return int|WP_Error valid post ID or WP_Error if any of the checks fails 
  60. */ 
  61. protected function validate_request( $id, $type, $context ) { 
  62.  
  63. if ( 'shop_order' === $type || 'shop_coupon' === $type || 'shop_webhook' === $type ) { 
  64. $resource_name = str_replace( 'shop_', '', $type ); 
  65. } else { 
  66. $resource_name = $type; 
  67.  
  68. $id = absint( $id ); 
  69.  
  70. // Validate ID 
  71. if ( empty( $id ) ) { 
  72. return new WP_Error( "woocommerce_api_invalid_{$resource_name}_id", sprintf( __( 'Invalid %s ID', 'woocommerce' ), $type ), array( 'status' => 404 ) ); 
  73.  
  74. // Only custom post types have per-post type/permission checks 
  75. if ( 'customer' !== $type ) { 
  76.  
  77. $post = get_post( $id ); 
  78.  
  79. if ( null === $post ) { 
  80. return new WP_Error( "woocommerce_api_no_{$resource_name}_found", sprintf( __( 'No %1$s found with the ID equal to %2$s', 'woocommerce' ), $resource_name, $id ), array( 'status' => 404 ) ); 
  81.  
  82. // For checking permissions, product variations are the same as the product post type 
  83. $post_type = ( 'product_variation' === $post->post_type ) ? 'product' : $post->post_type; 
  84.  
  85. // Validate post type 
  86. if ( $type !== $post_type ) { 
  87. return new WP_Error( "woocommerce_api_invalid_{$resource_name}", sprintf( __( 'Invalid %s', 'woocommerce' ), $resource_name ), array( 'status' => 404 ) ); 
  88.  
  89. // Validate permissions 
  90. switch ( $context ) { 
  91.  
  92. case 'read': 
  93. if ( ! $this->is_readable( $post ) ) 
  94. return new WP_Error( "woocommerce_api_user_cannot_read_{$resource_name}", sprintf( __( 'You do not have permission to read this %s', 'woocommerce' ), $resource_name ), array( 'status' => 401 ) ); 
  95. break; 
  96.  
  97. case 'edit': 
  98. if ( ! $this->is_editable( $post ) ) 
  99. return new WP_Error( "woocommerce_api_user_cannot_edit_{$resource_name}", sprintf( __( 'You do not have permission to edit this %s', 'woocommerce' ), $resource_name ), array( 'status' => 401 ) ); 
  100. break; 
  101.  
  102. case 'delete': 
  103. if ( ! $this->is_deletable( $post ) ) 
  104. return new WP_Error( "woocommerce_api_user_cannot_delete_{$resource_name}", sprintf( __( 'You do not have permission to delete this %s', 'woocommerce' ), $resource_name ), array( 'status' => 401 ) ); 
  105. break; 
  106.  
  107. return $id; 
  108.  
  109. /** 
  110. * Add common request arguments to argument list before WP_Query is run 
  111. * @since 2.1 
  112. * @param array $base_args required arguments for the query (e.g. `post_type`, etc) 
  113. * @param array $request_args arguments provided in the request 
  114. * @return array 
  115. */ 
  116. protected function merge_query_args( $base_args, $request_args ) { 
  117.  
  118. $args = array(); 
  119.  
  120. // date 
  121. if ( ! empty( $request_args['created_at_min'] ) || ! empty( $request_args['created_at_max'] ) || ! empty( $request_args['updated_at_min'] ) || ! empty( $request_args['updated_at_max'] ) ) { 
  122.  
  123. $args['date_query'] = array(); 
  124.  
  125. // resources created after specified date 
  126. if ( ! empty( $request_args['created_at_min'] ) ) { 
  127. $args['date_query'][] = array( 'column' => 'post_date_gmt', 'after' => $this->server->parse_datetime( $request_args['created_at_min'] ), 'inclusive' => true ); 
  128.  
  129. // resources created before specified date 
  130. if ( ! empty( $request_args['created_at_max'] ) ) { 
  131. $args['date_query'][] = array( 'column' => 'post_date_gmt', 'before' => $this->server->parse_datetime( $request_args['created_at_max'] ), 'inclusive' => true ); 
  132.  
  133. // resources updated after specified date 
  134. if ( ! empty( $request_args['updated_at_min'] ) ) { 
  135. $args['date_query'][] = array( 'column' => 'post_modified_gmt', 'after' => $this->server->parse_datetime( $request_args['updated_at_min'] ), 'inclusive' => true ); 
  136.  
  137. // resources updated before specified date 
  138. if ( ! empty( $request_args['updated_at_max'] ) ) { 
  139. $args['date_query'][] = array( 'column' => 'post_modified_gmt', 'before' => $this->server->parse_datetime( $request_args['updated_at_max'] ), 'inclusive' => true ); 
  140.  
  141. // search 
  142. if ( ! empty( $request_args['q'] ) ) { 
  143. $args['s'] = $request_args['q']; 
  144.  
  145. // resources per response 
  146. if ( ! empty( $request_args['limit'] ) ) { 
  147. $args['posts_per_page'] = $request_args['limit']; 
  148.  
  149. // resource offset 
  150. if ( ! empty( $request_args['offset'] ) ) { 
  151. $args['offset'] = $request_args['offset']; 
  152.  
  153. // order (ASC or DESC, ASC by default) 
  154. if ( ! empty( $request_args['order'] ) ) { 
  155. $args['order'] = $request_args['order']; 
  156.  
  157. // orderby 
  158. if ( ! empty( $request_args['orderby'] ) ) { 
  159. $args['orderby'] = $request_args['orderby']; 
  160.  
  161. // allow sorting by meta value 
  162. if ( ! empty( $request_args['orderby_meta_key'] ) ) { 
  163. $args['meta_key'] = $request_args['orderby_meta_key']; 
  164.  
  165. // allow post status change 
  166. if ( ! empty( $request_args['post_status'] ) ) { 
  167. $args['post_status'] = $request_args['post_status']; 
  168. unset( $request_args['post_status'] ); 
  169.  
  170. // filter by a list of post id 
  171. if ( ! empty( $request_args['in'] ) ) { 
  172. $args['post__in'] = explode( ', ', $request_args['in'] ); 
  173. unset( $request_args['in'] ); 
  174.  
  175. // filter by a list of post id 
  176. if ( ! empty( $request_args['in'] ) ) { 
  177. $args['post__in'] = explode( ', ', $request_args['in'] ); 
  178. unset( $request_args['in'] ); 
  179.  
  180. // resource page 
  181. $args['paged'] = ( isset( $request_args['page'] ) ) ? absint( $request_args['page'] ) : 1; 
  182.  
  183. $args = apply_filters( 'woocommerce_api_query_args', $args, $request_args ); 
  184.  
  185. return array_merge( $base_args, $args ); 
  186.  
  187. /** 
  188. * Add meta to resources when requested by the client. Meta is added as a top-level 
  189. * `<resource_name>_meta` attribute (e.g. `order_meta`) as a list of key/value pairs 
  190. * @since 2.1 
  191. * @param array $data the resource data 
  192. * @param object $resource the resource object (e.g WC_Order) 
  193. * @return mixed 
  194. */ 
  195. public function maybe_add_meta( $data, $resource ) { 
  196.  
  197. if ( isset( $this->server->params['GET']['filter']['meta'] ) && 'true' === $this->server->params['GET']['filter']['meta'] && is_object( $resource ) ) { 
  198.  
  199. // don't attempt to add meta more than once 
  200. if ( preg_grep( '/[a-z]+_meta/', array_keys( $data ) ) ) 
  201. return $data; 
  202.  
  203. // define the top-level property name for the meta 
  204. switch ( get_class( $resource ) ) { 
  205.  
  206. case 'WC_Order': 
  207. $meta_name = 'order_meta'; 
  208. break; 
  209.  
  210. case 'WC_Coupon': 
  211. $meta_name = 'coupon_meta'; 
  212. break; 
  213.  
  214. case 'WP_User': 
  215. $meta_name = 'customer_meta'; 
  216. break; 
  217.  
  218. default: 
  219. $meta_name = 'product_meta'; 
  220. break; 
  221.  
  222. if ( is_a( $resource, 'WP_User' ) ) { 
  223.  
  224. // customer meta 
  225. $meta = (array) get_user_meta( $resource->ID ); 
  226.  
  227. } elseif ( is_a( $resource, 'WC_Product_Variation' ) ) { 
  228.  
  229. // product variation meta 
  230. $meta = (array) get_post_meta( $resource->get_variation_id() ); 
  231.  
  232. } else { 
  233.  
  234. // coupon/order/product meta 
  235. $meta = (array) get_post_meta( $resource->id ); 
  236.  
  237. foreach ( $meta as $meta_key => $meta_value ) { 
  238.  
  239. // don't add hidden meta by default 
  240. if ( ! is_protected_meta( $meta_key ) ) { 
  241. $data[ $meta_name ][ $meta_key ] = maybe_unserialize( $meta_value[0] ); 
  242.  
  243. return $data; 
  244.  
  245. /** 
  246. * Restrict the fields included in the response if the request specified certain only certain fields should be returned 
  247. * @since 2.1 
  248. * @param array $data the response data 
  249. * @param object $resource the object that provided the response data, e.g. WC_Coupon or WC_Order 
  250. * @param array|string the requested list of fields to include in the response 
  251. * @return array response data 
  252. */ 
  253. public function filter_response_fields( $data, $resource, $fields ) { 
  254.  
  255. if ( ! is_array( $data ) || empty( $fields ) ) { 
  256. return $data; 
  257.  
  258. $fields = explode( ', ', $fields ); 
  259. $sub_fields = array(); 
  260.  
  261. // get sub fields 
  262. foreach ( $fields as $field ) { 
  263.  
  264. if ( false !== strpos( $field, '.' ) ) { 
  265.  
  266. list( $name, $value ) = explode( '.', $field ); 
  267.  
  268. $sub_fields[ $name ] = $value; 
  269.  
  270. // iterate through top-level fields 
  271. foreach ( $data as $data_field => $data_value ) { 
  272.  
  273. // if a field has sub-fields and the top-level field has sub-fields to filter 
  274. if ( is_array( $data_value ) && in_array( $data_field, array_keys( $sub_fields ) ) ) { 
  275.  
  276. // iterate through each sub-field 
  277. foreach ( $data_value as $sub_field => $sub_field_value ) { 
  278.  
  279. // remove non-matching sub-fields 
  280. if ( ! in_array( $sub_field, $sub_fields ) ) { 
  281. unset( $data[ $data_field ][ $sub_field ] ); 
  282. } else { 
  283.  
  284. // remove non-matching top-level fields 
  285. if ( ! in_array( $data_field, $fields ) ) { 
  286. unset( $data[ $data_field ] ); 
  287.  
  288. return $data; 
  289.  
  290. /** 
  291. * Delete a given resource 
  292. * @since 2.1 
  293. * @param int $id the resource ID 
  294. * @param string $type the resource post type, or `customer` 
  295. * @param bool $force true to permanently delete resource, false to move to trash (not supported for `customer`) 
  296. * @return array|WP_Error 
  297. */ 
  298. protected function delete( $id, $type, $force = false ) { 
  299.  
  300. if ( 'shop_order' === $type || 'shop_coupon' === $type ) { 
  301. $resource_name = str_replace( 'shop_', '', $type ); 
  302. } else { 
  303. $resource_name = $type; 
  304.  
  305. if ( 'customer' === $type ) { 
  306.  
  307. $result = wp_delete_user( $id ); 
  308.  
  309. if ( $result ) { 
  310. return array( 'message' => __( 'Permanently deleted customer', 'woocommerce' ) ); 
  311. } else { 
  312. return new WP_Error( 'woocommerce_api_cannot_delete_customer', __( 'The customer cannot be deleted', 'woocommerce' ), array( 'status' => 500 ) ); 
  313. } else { 
  314.  
  315. // delete order/coupon/webhook 
  316. $result = ( $force ) ? wp_delete_post( $id, true ) : wp_trash_post( $id ); 
  317.  
  318. if ( ! $result ) { 
  319. return new WP_Error( "woocommerce_api_cannot_delete_{$resource_name}", sprintf( __( 'This %s cannot be deleted', 'woocommerce' ), $resource_name ), array( 'status' => 500 ) ); 
  320.  
  321. if ( $force ) { 
  322. return array( 'message' => sprintf( __( 'Permanently deleted %s', 'woocommerce' ), $resource_name ) ); 
  323. } else { 
  324. $this->server->send_status( '202' ); 
  325.  
  326. return array( 'message' => sprintf( __( 'Deleted %s', 'woocommerce' ), $resource_name ) ); 
  327.  
  328.  
  329. /** 
  330. * Checks if the given post is readable by the current user 
  331. * @since 2.1 
  332. * @see WC_API_Resource::check_permission() 
  333. * @param WP_Post|int $post 
  334. * @return bool 
  335. */ 
  336. protected function is_readable( $post ) { 
  337.  
  338. return $this->check_permission( $post, 'read' ); 
  339.  
  340. /** 
  341. * Checks if the given post is editable by the current user 
  342. * @since 2.1 
  343. * @see WC_API_Resource::check_permission() 
  344. * @param WP_Post|int $post 
  345. * @return bool 
  346. */ 
  347. protected function is_editable( $post ) { 
  348.  
  349. return $this->check_permission( $post, 'edit' ); 
  350.  
  351.  
  352. /** 
  353. * Checks if the given post is deletable by the current user 
  354. * @since 2.1 
  355. * @see WC_API_Resource::check_permission() 
  356. * @param WP_Post|int $post 
  357. * @return bool 
  358. */ 
  359. protected function is_deletable( $post ) { 
  360.  
  361. return $this->check_permission( $post, 'delete' ); 
  362.  
  363. /** 
  364. * Checks the permissions for the current user given a post and context 
  365. * @since 2.1 
  366. * @param WP_Post|int $post 
  367. * @param string $context the type of permission to check, either `read`, `write`, or `delete` 
  368. * @return bool true if the current user has the permissions to perform the context on the post 
  369. */ 
  370. private function check_permission( $post, $context ) { 
  371.  
  372. if ( ! is_a( $post, 'WP_Post' ) ) { 
  373. $post = get_post( $post ); 
  374.  
  375. if ( is_null( $post ) ) { 
  376. return false; 
  377.  
  378. $post_type = get_post_type_object( $post->post_type ); 
  379.  
  380. if ( 'read' === $context ) { 
  381. return ( 'revision' !== $post->post_type && current_user_can( $post_type->cap->read_private_posts, $post->ID ) ); 
  382. } elseif ( 'edit' === $context ) { 
  383. return current_user_can( $post_type->cap->edit_post, $post->ID ); 
  384. } elseif ( 'delete' === $context ) { 
  385. return current_user_can( $post_type->cap->delete_post, $post->ID ); 
  386. } else { 
  387. return false; 
/includes/api/legacy/v3/class-wc-api-resource.php  
  1. class WC_API_Resource { 
  2.  
  3. /** @var WC_API_Server the API server */ 
  4. protected $server; 
  5.  
  6. /** @var string sub-classes override this to set a resource-specific base route */ 
  7. protected $base; 
  8.  
  9. /** 
  10. * Setup class 
  11. * @since 2.1 
  12. * @param WC_API_Server $server 
  13. * @return WC_API_Resource 
  14. */ 
  15. public function __construct( WC_API_Server $server ) { 
  16.  
  17. $this->server = $server; 
  18.  
  19. // automatically register routes for sub-classes 
  20. add_filter( 'woocommerce_api_endpoints', array( $this, 'register_routes' ) ); 
  21.  
  22. // maybe add meta to top-level resource responses 
  23. foreach ( array( 'order', 'coupon', 'customer', 'product', 'report' ) as $resource ) { 
  24. add_filter( "woocommerce_api_{$resource}_response", array( $this, 'maybe_add_meta' ), 15, 2 ); 
  25.  
  26. $response_names = array( 
  27. 'order',  
  28. 'coupon',  
  29. 'customer',  
  30. 'product',  
  31. 'report',  
  32. 'customer_orders',  
  33. 'customer_downloads',  
  34. 'order_note',  
  35. 'order_refund',  
  36. 'product_reviews',  
  37. 'product_category',  
  38. 'tax',  
  39. 'tax_class',  
  40. ); 
  41.  
  42. foreach ( $response_names as $name ) { 
  43.  
  44. /** 
  45. * Remove fields from responses when requests specify certain fields 
  46. * note these are hooked at a later priority so data added via 
  47. * filters (e.g. customer data to the order response) still has the 
  48. * fields filtered properly 
  49. */ 
  50. add_filter( "woocommerce_api_{$name}_response", array( $this, 'filter_response_fields' ), 20, 3 ); 
  51.  
  52. /** 
  53. * Validate the request by checking: 
  54. * 1) the ID is a valid integer 
  55. * 2) the ID returns a valid post object and matches the provided post type 
  56. * 3) the current user has the proper permissions to read/edit/delete the post 
  57. * @since 2.1 
  58. * @param string|int $id the post ID 
  59. * @param string $type the post type, either `shop_order`, `shop_coupon`, or `product` 
  60. * @param string $context the context of the request, either `read`, `edit` or `delete` 
  61. * @return int|WP_Error valid post ID or WP_Error if any of the checks fails 
  62. */ 
  63. protected function validate_request( $id, $type, $context ) { 
  64.  
  65. if ( 'shop_order' === $type || 'shop_coupon' === $type || 'shop_webhook' === $type ) { 
  66. $resource_name = str_replace( 'shop_', '', $type ); 
  67. } else { 
  68. $resource_name = $type; 
  69.  
  70. $id = absint( $id ); 
  71.  
  72. // Validate ID 
  73. if ( empty( $id ) ) { 
  74. return new WP_Error( "woocommerce_api_invalid_{$resource_name}_id", sprintf( __( 'Invalid %s ID', 'woocommerce' ), $type ), array( 'status' => 404 ) ); 
  75.  
  76. // Only custom post types have per-post type/permission checks 
  77. if ( 'customer' !== $type ) { 
  78.  
  79. $post = get_post( $id ); 
  80.  
  81. if ( null === $post ) { 
  82. return new WP_Error( "woocommerce_api_no_{$resource_name}_found", sprintf( __( 'No %1$s found with the ID equal to %2$s', 'woocommerce' ), $resource_name, $id ), array( 'status' => 404 ) ); 
  83.  
  84. // For checking permissions, product variations are the same as the product post type 
  85. $post_type = ( 'product_variation' === $post->post_type ) ? 'product' : $post->post_type; 
  86.  
  87. // Validate post type 
  88. if ( $type !== $post_type ) { 
  89. return new WP_Error( "woocommerce_api_invalid_{$resource_name}", sprintf( __( 'Invalid %s', 'woocommerce' ), $resource_name ), array( 'status' => 404 ) ); 
  90.  
  91. // Validate permissions 
  92. switch ( $context ) { 
  93.  
  94. case 'read': 
  95. if ( ! $this->is_readable( $post ) ) 
  96. return new WP_Error( "woocommerce_api_user_cannot_read_{$resource_name}", sprintf( __( 'You do not have permission to read this %s', 'woocommerce' ), $resource_name ), array( 'status' => 401 ) ); 
  97. break; 
  98.  
  99. case 'edit': 
  100. if ( ! $this->is_editable( $post ) ) 
  101. return new WP_Error( "woocommerce_api_user_cannot_edit_{$resource_name}", sprintf( __( 'You do not have permission to edit this %s', 'woocommerce' ), $resource_name ), array( 'status' => 401 ) ); 
  102. break; 
  103.  
  104. case 'delete': 
  105. if ( ! $this->is_deletable( $post ) ) 
  106. return new WP_Error( "woocommerce_api_user_cannot_delete_{$resource_name}", sprintf( __( 'You do not have permission to delete this %s', 'woocommerce' ), $resource_name ), array( 'status' => 401 ) ); 
  107. break; 
  108.  
  109. return $id; 
  110.  
  111. /** 
  112. * Add common request arguments to argument list before WP_Query is run 
  113. * @since 2.1 
  114. * @param array $base_args required arguments for the query (e.g. `post_type`, etc) 
  115. * @param array $request_args arguments provided in the request 
  116. * @return array 
  117. */ 
  118. protected function merge_query_args( $base_args, $request_args ) { 
  119.  
  120. $args = array(); 
  121.  
  122. // date 
  123. if ( ! empty( $request_args['created_at_min'] ) || ! empty( $request_args['created_at_max'] ) || ! empty( $request_args['updated_at_min'] ) || ! empty( $request_args['updated_at_max'] ) ) { 
  124.  
  125. $args['date_query'] = array(); 
  126.  
  127. // resources created after specified date 
  128. if ( ! empty( $request_args['created_at_min'] ) ) { 
  129. $args['date_query'][] = array( 'column' => 'post_date_gmt', 'after' => $this->server->parse_datetime( $request_args['created_at_min'] ), 'inclusive' => true ); 
  130.  
  131. // resources created before specified date 
  132. if ( ! empty( $request_args['created_at_max'] ) ) { 
  133. $args['date_query'][] = array( 'column' => 'post_date_gmt', 'before' => $this->server->parse_datetime( $request_args['created_at_max'] ), 'inclusive' => true ); 
  134.  
  135. // resources updated after specified date 
  136. if ( ! empty( $request_args['updated_at_min'] ) ) { 
  137. $args['date_query'][] = array( 'column' => 'post_modified_gmt', 'after' => $this->server->parse_datetime( $request_args['updated_at_min'] ), 'inclusive' => true ); 
  138.  
  139. // resources updated before specified date 
  140. if ( ! empty( $request_args['updated_at_max'] ) ) { 
  141. $args['date_query'][] = array( 'column' => 'post_modified_gmt', 'before' => $this->server->parse_datetime( $request_args['updated_at_max'] ), 'inclusive' => true ); 
  142.  
  143. // search 
  144. if ( ! empty( $request_args['q'] ) ) { 
  145. $args['s'] = $request_args['q']; 
  146.  
  147. // resources per response 
  148. if ( ! empty( $request_args['limit'] ) ) { 
  149. $args['posts_per_page'] = $request_args['limit']; 
  150.  
  151. // resource offset 
  152. if ( ! empty( $request_args['offset'] ) ) { 
  153. $args['offset'] = $request_args['offset']; 
  154.  
  155. // order (ASC or DESC, ASC by default) 
  156. if ( ! empty( $request_args['order'] ) ) { 
  157. $args['order'] = $request_args['order']; 
  158.  
  159. // orderby 
  160. if ( ! empty( $request_args['orderby'] ) ) { 
  161. $args['orderby'] = $request_args['orderby']; 
  162.  
  163. // allow sorting by meta value 
  164. if ( ! empty( $request_args['orderby_meta_key'] ) ) { 
  165. $args['meta_key'] = $request_args['orderby_meta_key']; 
  166.  
  167. // allow post status change 
  168. if ( ! empty( $request_args['post_status'] ) ) { 
  169. $args['post_status'] = $request_args['post_status']; 
  170. unset( $request_args['post_status'] ); 
  171.  
  172. // filter by a list of post id 
  173. if ( ! empty( $request_args['in'] ) ) { 
  174. $args['post__in'] = explode( ', ', $request_args['in'] ); 
  175. unset( $request_args['in'] ); 
  176.  
  177. // exclude by a list of post id 
  178. if ( ! empty( $request_args['not_in'] ) ) { 
  179. $args['post__not_in'] = explode( ', ', $request_args['not_in'] ); 
  180. unset( $request_args['not_in'] ); 
  181.  
  182. // resource page 
  183. $args['paged'] = ( isset( $request_args['page'] ) ) ? absint( $request_args['page'] ) : 1; 
  184.  
  185. $args = apply_filters( 'woocommerce_api_query_args', $args, $request_args ); 
  186.  
  187. return array_merge( $base_args, $args ); 
  188.  
  189. /** 
  190. * Add meta to resources when requested by the client. Meta is added as a top-level 
  191. * `<resource_name>_meta` attribute (e.g. `order_meta`) as a list of key/value pairs 
  192. * @since 2.1 
  193. * @param array $data the resource data 
  194. * @param object $resource the resource object (e.g WC_Order) 
  195. * @return mixed 
  196. */ 
  197. public function maybe_add_meta( $data, $resource ) { 
  198.  
  199. if ( isset( $this->server->params['GET']['filter']['meta'] ) && 'true' === $this->server->params['GET']['filter']['meta'] && is_object( $resource ) ) { 
  200.  
  201. // don't attempt to add meta more than once 
  202. if ( preg_grep( '/[a-z]+_meta/', array_keys( $data ) ) ) 
  203. return $data; 
  204.  
  205. // define the top-level property name for the meta 
  206. switch ( get_class( $resource ) ) { 
  207.  
  208. case 'WC_Order': 
  209. $meta_name = 'order_meta'; 
  210. break; 
  211.  
  212. case 'WC_Coupon': 
  213. $meta_name = 'coupon_meta'; 
  214. break; 
  215.  
  216. case 'WP_User': 
  217. $meta_name = 'customer_meta'; 
  218. break; 
  219.  
  220. default: 
  221. $meta_name = 'product_meta'; 
  222. break; 
  223.  
  224. if ( is_a( $resource, 'WP_User' ) ) { 
  225.  
  226. // customer meta 
  227. $meta = (array) get_user_meta( $resource->ID ); 
  228.  
  229. } elseif ( is_a( $resource, 'WC_Product_Variation' ) ) { 
  230.  
  231. // product variation meta 
  232. $meta = (array) get_post_meta( $resource->get_variation_id() ); 
  233.  
  234. } else { 
  235.  
  236. // coupon/order/product meta 
  237. $meta = (array) get_post_meta( $resource->id ); 
  238.  
  239. foreach ( $meta as $meta_key => $meta_value ) { 
  240.  
  241. // don't add hidden meta by default 
  242. if ( ! is_protected_meta( $meta_key ) ) { 
  243. $data[ $meta_name ][ $meta_key ] = maybe_unserialize( $meta_value[0] ); 
  244.  
  245. return $data; 
  246.  
  247. /** 
  248. * Restrict the fields included in the response if the request specified certain only certain fields should be returned 
  249. * @since 2.1 
  250. * @param array $data the response data 
  251. * @param object $resource the object that provided the response data, e.g. WC_Coupon or WC_Order 
  252. * @param array|string the requested list of fields to include in the response 
  253. * @return array response data 
  254. */ 
  255. public function filter_response_fields( $data, $resource, $fields ) { 
  256.  
  257. if ( ! is_array( $data ) || empty( $fields ) ) { 
  258. return $data; 
  259.  
  260. $fields = explode( ', ', $fields ); 
  261. $sub_fields = array(); 
  262.  
  263. // get sub fields 
  264. foreach ( $fields as $field ) { 
  265.  
  266. if ( false !== strpos( $field, '.' ) ) { 
  267.  
  268. list( $name, $value ) = explode( '.', $field ); 
  269.  
  270. $sub_fields[ $name ] = $value; 
  271.  
  272. // iterate through top-level fields 
  273. foreach ( $data as $data_field => $data_value ) { 
  274.  
  275. // if a field has sub-fields and the top-level field has sub-fields to filter 
  276. if ( is_array( $data_value ) && in_array( $data_field, array_keys( $sub_fields ) ) ) { 
  277.  
  278. // iterate through each sub-field 
  279. foreach ( $data_value as $sub_field => $sub_field_value ) { 
  280.  
  281. // remove non-matching sub-fields 
  282. if ( ! in_array( $sub_field, $sub_fields ) ) { 
  283. unset( $data[ $data_field ][ $sub_field ] ); 
  284. } else { 
  285.  
  286. // remove non-matching top-level fields 
  287. if ( ! in_array( $data_field, $fields ) ) { 
  288. unset( $data[ $data_field ] ); 
  289.  
  290. return $data; 
  291.  
  292. /** 
  293. * Delete a given resource 
  294. * @since 2.1 
  295. * @param int $id the resource ID 
  296. * @param string $type the resource post type, or `customer` 
  297. * @param bool $force true to permanently delete resource, false to move to trash (not supported for `customer`) 
  298. * @return array|WP_Error 
  299. */ 
  300. protected function delete( $id, $type, $force = false ) { 
  301.  
  302. if ( 'shop_order' === $type || 'shop_coupon' === $type ) { 
  303. $resource_name = str_replace( 'shop_', '', $type ); 
  304. } else { 
  305. $resource_name = $type; 
  306.  
  307. if ( 'customer' === $type ) { 
  308.  
  309. $result = wp_delete_user( $id ); 
  310.  
  311. if ( $result ) 
  312. return array( 'message' => __( 'Permanently deleted customer', 'woocommerce' ) ); 
  313. else 
  314. return new WP_Error( 'woocommerce_api_cannot_delete_customer', __( 'The customer cannot be deleted', 'woocommerce' ), array( 'status' => 500 ) ); 
  315.  
  316. } else { 
  317.  
  318. // delete order/coupon/product/webhook 
  319. $result = ( $force ) ? wp_delete_post( $id, true ) : wp_trash_post( $id ); 
  320.  
  321. if ( ! $result ) 
  322. return new WP_Error( "woocommerce_api_cannot_delete_{$resource_name}", sprintf( __( 'This %s cannot be deleted', 'woocommerce' ), $resource_name ), array( 'status' => 500 ) ); 
  323.  
  324. if ( $force ) { 
  325. return array( 'message' => sprintf( __( 'Permanently deleted %s', 'woocommerce' ), $resource_name ) ); 
  326.  
  327. } else { 
  328.  
  329. $this->server->send_status( '202' ); 
  330.  
  331. return array( 'message' => sprintf( __( 'Deleted %s', 'woocommerce' ), $resource_name ) ); 
  332.  
  333.  
  334. /** 
  335. * Checks if the given post is readable by the current user 
  336. * @since 2.1 
  337. * @see WC_API_Resource::check_permission() 
  338. * @param WP_Post|int $post 
  339. * @return bool 
  340. */ 
  341. protected function is_readable( $post ) { 
  342.  
  343. return $this->check_permission( $post, 'read' ); 
  344.  
  345. /** 
  346. * Checks if the given post is editable by the current user 
  347. * @since 2.1 
  348. * @see WC_API_Resource::check_permission() 
  349. * @param WP_Post|int $post 
  350. * @return bool 
  351. */ 
  352. protected function is_editable( $post ) { 
  353.  
  354. return $this->check_permission( $post, 'edit' ); 
  355.  
  356.  
  357. /** 
  358. * Checks if the given post is deletable by the current user 
  359. * @since 2.1 
  360. * @see WC_API_Resource::check_permission() 
  361. * @param WP_Post|int $post 
  362. * @return bool 
  363. */ 
  364. protected function is_deletable( $post ) { 
  365.  
  366. return $this->check_permission( $post, 'delete' ); 
  367.  
  368. /** 
  369. * Checks the permissions for the current user given a post and context 
  370. * @since 2.1 
  371. * @param WP_Post|int $post 
  372. * @param string $context the type of permission to check, either `read`, `write`, or `delete` 
  373. * @return bool true if the current user has the permissions to perform the context on the post 
  374. */ 
  375. private function check_permission( $post, $context ) { 
  376. $permission = false; 
  377.  
  378. if ( ! is_a( $post, 'WP_Post' ) ) { 
  379. $post = get_post( $post ); 
  380.  
  381. if ( is_null( $post ) ) { 
  382. return $permission; 
  383.  
  384. $post_type = get_post_type_object( $post->post_type ); 
  385.  
  386. if ( 'read' === $context ) { 
  387. $permission = 'revision' !== $post->post_type && current_user_can( $post_type->cap->read_private_posts, $post->ID ); 
  388. } elseif ( 'edit' === $context ) { 
  389. $permission = current_user_can( $post_type->cap->edit_post, $post->ID ); 
  390. } elseif ( 'delete' === $context ) { 
  391. $permission = current_user_can( $post_type->cap->delete_post, $post->ID ); 
  392.  
  393. return apply_filters( 'woocommerce_api_check_permission', $permission, $context, $post, $post_type );