PageRenderTime 22ms CodeModel.GetById 6ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/woocommerce/includes/api/v1/class-wc-api-resource.php

https://gitlab.com/Suhailgit/Project
PHP | 402 lines | 174 code | 97 blank | 131 comment | 48 complexity | cb2519068167bb87eefff4dccf70c0fc MD5 | raw file
  1. <?php
  2. /**
  3. * WooCommerce API Resource class
  4. *
  5. * Provides shared functionality for resource-specific API classes
  6. *
  7. * @author WooThemes
  8. * @category API
  9. * @package WooCommerce/API
  10. * @since 2.1
  11. * @version 2.1
  12. */
  13. if ( ! defined( 'ABSPATH' ) ) {
  14. exit; // Exit if accessed directly
  15. }
  16. class WC_API_Resource {
  17. /** @var WC_API_Server the API server */
  18. protected $server;
  19. /** @var string sub-classes override this to set a resource-specific base route */
  20. protected $base;
  21. /**
  22. * Setup class
  23. *
  24. * @since 2.1
  25. * @param WC_API_Server $server
  26. * @return WC_API_Resource
  27. */
  28. public function __construct( WC_API_Server $server ) {
  29. $this->server = $server;
  30. // automatically register routes for sub-classes
  31. add_filter( 'woocommerce_api_endpoints', array( $this, 'register_routes' ) );
  32. // remove fields from responses when requests specify certain fields
  33. // note these are hooked at a later priority so data added via filters (e.g. customer data to the order response)
  34. // still has the fields filtered properly
  35. foreach ( array( 'order', 'coupon', 'customer', 'product', 'report' ) as $resource ) {
  36. add_filter( "woocommerce_api_{$resource}_response", array( $this, 'maybe_add_meta' ), 15, 2 );
  37. add_filter( "woocommerce_api_{$resource}_response", array( $this, 'filter_response_fields' ), 20, 3 );
  38. }
  39. }
  40. /**
  41. * Validate the request by checking:
  42. *
  43. * 1) the ID is a valid integer
  44. * 2) the ID returns a valid post object and matches the provided post type
  45. * 3) the current user has the proper permissions to read/edit/delete the post
  46. *
  47. * @since 2.1
  48. * @param string|int $id the post ID
  49. * @param string $type the post type, either `shop_order`, `shop_coupon`, or `product`
  50. * @param string $context the context of the request, either `read`, `edit` or `delete`
  51. * @return int|WP_Error valid post ID or WP_Error if any of the checks fails
  52. */
  53. protected function validate_request( $id, $type, $context ) {
  54. if ( 'shop_order' === $type || 'shop_coupon' === $type )
  55. $resource_name = str_replace( 'shop_', '', $type );
  56. else
  57. $resource_name = $type;
  58. $id = absint( $id );
  59. // validate ID
  60. if ( empty( $id ) )
  61. return new WP_Error( "woocommerce_api_invalid_{$resource_name}_id", sprintf( __( 'Invalid %s ID', 'woocommerce' ), $type ), array( 'status' => 404 ) );
  62. // only custom post types have per-post type/permission checks
  63. if ( 'customer' !== $type ) {
  64. $post = get_post( $id );
  65. // for checking permissions, product variations are the same as the product post type
  66. $post_type = ( 'product_variation' === $post->post_type ) ? 'product' : $post->post_type;
  67. // validate post type
  68. if ( $type !== $post_type )
  69. return new WP_Error( "woocommerce_api_invalid_{$resource_name}", sprintf( __( 'Invalid %s', 'woocommerce' ), $resource_name ), array( 'status' => 404 ) );
  70. // validate permissions
  71. switch ( $context ) {
  72. case 'read':
  73. if ( ! $this->is_readable( $post ) )
  74. 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 ) );
  75. break;
  76. case 'edit':
  77. if ( ! $this->is_editable( $post ) )
  78. 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 ) );
  79. break;
  80. case 'delete':
  81. if ( ! $this->is_deletable( $post ) )
  82. 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 ) );
  83. break;
  84. }
  85. }
  86. return $id;
  87. }
  88. /**
  89. * Add common request arguments to argument list before WP_Query is run
  90. *
  91. * @since 2.1
  92. * @param array $base_args required arguments for the query (e.g. `post_type`, etc)
  93. * @param array $request_args arguments provided in the request
  94. * @return array
  95. */
  96. protected function merge_query_args( $base_args, $request_args ) {
  97. $args = array();
  98. // date
  99. 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'] ) ) {
  100. $args['date_query'] = array();
  101. // resources created after specified date
  102. if ( ! empty( $request_args['created_at_min'] ) )
  103. $args['date_query'][] = array( 'column' => 'post_date_gmt', 'after' => $this->server->parse_datetime( $request_args['created_at_min'] ), 'inclusive' => true );
  104. // resources created before specified date
  105. if ( ! empty( $request_args['created_at_max'] ) )
  106. $args['date_query'][] = array( 'column' => 'post_date_gmt', 'before' => $this->server->parse_datetime( $request_args['created_at_max'] ), 'inclusive' => true );
  107. // resources updated after specified date
  108. if ( ! empty( $request_args['updated_at_min'] ) )
  109. $args['date_query'][] = array( 'column' => 'post_modified_gmt', 'after' => $this->server->parse_datetime( $request_args['updated_at_min'] ), 'inclusive' => true );
  110. // resources updated before specified date
  111. if ( ! empty( $request_args['updated_at_max'] ) )
  112. $args['date_query'][] = array( 'column' => 'post_modified_gmt', 'before' => $this->server->parse_datetime( $request_args['updated_at_max'] ), 'inclusive' => true );
  113. }
  114. // search
  115. if ( ! empty( $request_args['q'] ) )
  116. $args['s'] = $request_args['q'];
  117. // resources per response
  118. if ( ! empty( $request_args['limit'] ) )
  119. $args['posts_per_page'] = $request_args['limit'];
  120. // resource offset
  121. if ( ! empty( $request_args['offset'] ) )
  122. $args['offset'] = $request_args['offset'];
  123. // resource page
  124. $args['paged'] = ( isset( $request_args['page'] ) ) ? absint( $request_args['page'] ) : 1;
  125. return array_merge( $base_args, $args );
  126. }
  127. /**
  128. * Add meta to resources when requested by the client. Meta is added as a top-level
  129. * `<resource_name>_meta` attribute (e.g. `order_meta`) as a list of key/value pairs
  130. *
  131. * @since 2.1
  132. * @param array $data the resource data
  133. * @param object $resource the resource object (e.g WC_Order)
  134. * @return mixed
  135. */
  136. public function maybe_add_meta( $data, $resource ) {
  137. if ( isset( $this->server->params['GET']['filter']['meta'] ) && 'true' === $this->server->params['GET']['filter']['meta'] && is_object( $resource ) ) {
  138. // don't attempt to add meta more than once
  139. if ( preg_grep( '/[a-z]+_meta/', array_keys( $data ) ) )
  140. return $data;
  141. // define the top-level property name for the meta
  142. switch ( get_class( $resource ) ) {
  143. case 'WC_Order':
  144. $meta_name = 'order_meta';
  145. break;
  146. case 'WC_Coupon':
  147. $meta_name = 'coupon_meta';
  148. break;
  149. case 'WP_User':
  150. $meta_name = 'customer_meta';
  151. break;
  152. default:
  153. $meta_name = 'product_meta';
  154. break;
  155. }
  156. if ( is_a( $resource, 'WP_User' ) ) {
  157. // customer meta
  158. $meta = (array) get_user_meta( $resource->ID );
  159. } elseif ( is_a( $resource, 'WC_Product_Variation' ) ) {
  160. // product variation meta
  161. $meta = (array) get_post_meta( $resource->get_variation_id() );
  162. } else {
  163. // coupon/order/product meta
  164. $meta = (array) get_post_meta( $resource->id );
  165. }
  166. foreach( $meta as $meta_key => $meta_value ) {
  167. // don't add hidden meta by default
  168. if ( ! is_protected_meta( $meta_key ) ) {
  169. $data[ $meta_name ][ $meta_key ] = maybe_unserialize( $meta_value[0] );
  170. }
  171. }
  172. }
  173. return $data;
  174. }
  175. /**
  176. * Restrict the fields included in the response if the request specified certain only certain fields should be returned
  177. *
  178. * @since 2.1
  179. * @param array $data the response data
  180. * @param object $resource the object that provided the response data, e.g. WC_Coupon or WC_Order
  181. * @param array|string the requested list of fields to include in the response
  182. * @return array response data
  183. */
  184. public function filter_response_fields( $data, $resource, $fields ) {
  185. if ( ! is_array( $data ) || empty( $fields ) )
  186. return $data;
  187. $fields = explode( ',', $fields );
  188. $sub_fields = array();
  189. // get sub fields
  190. foreach ( $fields as $field ) {
  191. if ( false !== strpos( $field, '.' ) ) {
  192. list( $name, $value ) = explode( '.', $field );
  193. $sub_fields[ $name ] = $value;
  194. }
  195. }
  196. // iterate through top-level fields
  197. foreach ( $data as $data_field => $data_value ) {
  198. // if a field has sub-fields and the top-level field has sub-fields to filter
  199. if ( is_array( $data_value ) && in_array( $data_field, array_keys( $sub_fields ) ) ) {
  200. // iterate through each sub-field
  201. foreach ( $data_value as $sub_field => $sub_field_value ) {
  202. // remove non-matching sub-fields
  203. if ( ! in_array( $sub_field, $sub_fields ) ) {
  204. unset( $data[ $data_field ][ $sub_field ] );
  205. }
  206. }
  207. } else {
  208. // remove non-matching top-level fields
  209. if ( ! in_array( $data_field, $fields ) ) {
  210. unset( $data[ $data_field ] );
  211. }
  212. }
  213. }
  214. return $data;
  215. }
  216. /**
  217. * Delete a given resource
  218. *
  219. * @since 2.1
  220. * @param int $id the resource ID
  221. * @param string $type the resource post type, or `customer`
  222. * @param bool $force true to permanently delete resource, false to move to trash (not supported for `customer`)
  223. * @return array|WP_Error
  224. */
  225. protected function delete( $id, $type, $force = false ) {
  226. if ( 'shop_order' === $type || 'shop_coupon' === $type )
  227. $resource_name = str_replace( 'shop_', '', $type );
  228. else
  229. $resource_name = $type;
  230. if ( 'customer' === $type ) {
  231. $result = wp_delete_user( $id );
  232. if ( $result )
  233. return array( 'message' => __( 'Permanently deleted customer', 'woocommerce' ) );
  234. else
  235. return new WP_Error( 'woocommerce_api_cannot_delete_customer', __( 'The customer cannot be deleted', 'woocommerce' ), array( 'status' => 500 ) );
  236. } else {
  237. // delete order/coupon/product
  238. $result = ( $force ) ? wp_delete_post( $id, true ) : wp_trash_post( $id );
  239. if ( ! $result )
  240. return new WP_Error( "woocommerce_api_cannot_delete_{$resource_name}", sprintf( __( 'This %s cannot be deleted', 'woocommerce' ), $resource_name ), array( 'status' => 500 ) );
  241. if ( $force ) {
  242. return array( 'message' => sprintf( __( 'Permanently deleted %s', 'woocommerce' ), $resource_name ) );
  243. } else {
  244. $this->server->send_status( '202' );
  245. return array( 'message' => sprintf( __( 'Deleted %s', 'woocommerce' ), $resource_name ) );
  246. }
  247. }
  248. }
  249. /**
  250. * Checks if the given post is readable by the current user
  251. *
  252. * @since 2.1
  253. * @see WC_API_Resource::check_permission()
  254. * @param WP_Post|int $post
  255. * @return bool
  256. */
  257. protected function is_readable( $post ) {
  258. return $this->check_permission( $post, 'read' );
  259. }
  260. /**
  261. * Checks if the given post is editable by the current user
  262. *
  263. * @since 2.1
  264. * @see WC_API_Resource::check_permission()
  265. * @param WP_Post|int $post
  266. * @return bool
  267. */
  268. protected function is_editable( $post ) {
  269. return $this->check_permission( $post, 'edit' );
  270. }
  271. /**
  272. * Checks if the given post is deletable by the current user
  273. *
  274. * @since 2.1
  275. * @see WC_API_Resource::check_permission()
  276. * @param WP_Post|int $post
  277. * @return bool
  278. */
  279. protected function is_deletable( $post ) {
  280. return $this->check_permission( $post, 'delete' );
  281. }
  282. /**
  283. * Checks the permissions for the current user given a post and context
  284. *
  285. * @since 2.1
  286. * @param WP_Post|int $post
  287. * @param string $context the type of permission to check, either `read`, `write`, or `delete`
  288. * @return bool true if the current user has the permissions to perform the context on the post
  289. */
  290. private function check_permission( $post, $context ) {
  291. if ( ! is_a( $post, 'WP_Post' ) )
  292. $post = get_post( $post );
  293. if ( is_null( $post ) )
  294. return false;
  295. $post_type = get_post_type_object( $post->post_type );
  296. if ( 'read' === $context )
  297. return current_user_can( $post_type->cap->read_private_posts, $post->ID );
  298. elseif ( 'edit' === $context )
  299. return current_user_can( $post_type->cap->edit_post, $post->ID );
  300. elseif ( 'delete' === $context )
  301. return current_user_can( $post_type->cap->delete_post, $post->ID );
  302. else
  303. return false;
  304. }
  305. }