2013-11-04 01:06:36 +00:00
< ? php
/**
2013-11-09 21:20:23 +00:00
* WooCommerce API Resource class
2013-11-04 01:06:36 +00:00
*
* Provides shared functionality for resource - specific API classes
*
* @ author WooThemes
* @ category API
2020-09-17 14:56:08 +00:00
* @ package WooCommerce\RestApi
2013-11-04 01:06:36 +00:00
* @ since 2.1
*/
2014-09-20 19:24:20 +00:00
if ( ! defined ( 'ABSPATH' ) ) {
exit ; // Exit if accessed directly
}
2013-11-04 01:06:36 +00:00
2013-11-09 21:20:23 +00:00
class WC_API_Resource {
2013-11-04 01:06:36 +00:00
2013-11-06 06:54:19 +00:00
/** @var WC_API_Server the API server */
2013-11-04 01:06:36 +00:00
protected $server ;
/** @var string sub-classes override this to set a resource-specific base route */
protected $base ;
/**
* Setup class
*
* @ since 2.1
2013-11-06 06:54:19 +00:00
* @ param WC_API_Server $server
2013-11-04 01:06:36 +00:00
*/
2013-11-06 06:54:19 +00:00
public function __construct ( WC_API_Server $server ) {
2013-11-04 01:06:36 +00:00
$this -> server = $server ;
// automatically register routes for sub-classes
2013-11-11 00:29:36 +00:00
add_filter ( 'woocommerce_api_endpoints' , array ( $this , 'register_routes' ) );
2013-11-04 01:06:36 +00:00
2014-08-30 19:02:19 +00:00
// maybe add meta to top-level resource responses
2013-11-11 00:29:36 +00:00
foreach ( array ( 'order' , 'coupon' , 'customer' , 'product' , 'report' ) as $resource ) {
add_filter ( " woocommerce_api_ { $resource } _response " , array ( $this , 'maybe_add_meta' ), 15 , 2 );
2014-08-30 19:02:19 +00:00
}
2016-08-27 03:04:10 +00:00
$response_names = array (
'order' ,
'coupon' ,
'customer' ,
'product' ,
'report' ,
'customer_orders' ,
'customer_downloads' ,
'order_note' ,
'order_refund' ,
'product_reviews' ,
'product_category' ,
'tax' ,
'tax_class' ,
2014-08-30 19:02:19 +00:00
);
foreach ( $response_names as $name ) {
2016-09-02 01:03:52 +00:00
/**
* Remove fields from responses when requests specify certain fields
2014-08-30 19:02:19 +00:00
* note these are hooked at a later priority so data added via
* filters ( e . g . customer data to the order response ) still has the
* fields filtered properly
*/
add_filter ( " woocommerce_api_ { $name } _response " , array ( $this , 'filter_response_fields' ), 20 , 3 );
2013-11-11 00:29:36 +00:00
}
2013-11-04 01:06:36 +00:00
}
2013-11-11 00:29:36 +00:00
/**
* Validate the request by checking :
*
* 1 ) the ID is a valid integer
* 2 ) the ID returns a valid post object and matches the provided post type
* 3 ) the current user has the proper permissions to read / edit / delete the post
*
* @ since 2.1
* @ param string | int $id the post ID
* @ param string $type the post type , either `shop_order` , `shop_coupon` , or `product`
* @ param string $context the context of the request , either `read` , `edit` or `delete`
* @ return int | WP_Error valid post ID or WP_Error if any of the checks fails
*/
protected function validate_request ( $id , $type , $context ) {
2015-01-22 19:22:21 +00:00
if ( 'shop_order' === $type || 'shop_coupon' === $type || 'shop_webhook' === $type ) {
2013-11-11 00:29:36 +00:00
$resource_name = str_replace ( 'shop_' , '' , $type );
2015-01-22 19:22:21 +00:00
} else {
2013-11-11 00:29:36 +00:00
$resource_name = $type ;
2015-01-22 19:22:21 +00:00
}
2013-11-11 00:29:36 +00:00
$id = absint ( $id );
2015-01-22 19:22:21 +00:00
// Validate ID
if ( empty ( $id ) ) {
2013-11-11 00:29:36 +00:00
return new WP_Error ( " woocommerce_api_invalid_ { $resource_name } _id " , sprintf ( __ ( 'Invalid %s ID' , 'woocommerce' ), $type ), array ( 'status' => 404 ) );
2015-01-22 19:22:21 +00:00
}
2013-11-11 00:29:36 +00:00
2015-01-22 19:22:21 +00:00
// Only custom post types have per-post type/permission checks
2013-11-11 00:29:36 +00:00
if ( 'customer' !== $type ) {
2013-11-23 19:32:22 +00:00
$post = get_post ( $id );
2013-11-11 00:29:36 +00:00
2015-01-22 19:22:21 +00:00
if ( null === $post ) {
2016-09-02 00:26:24 +00:00
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 ) );
2015-01-22 19:22:21 +00:00
}
// For checking permissions, product variations are the same as the product post type
2013-11-23 19:32:22 +00:00
$post_type = ( 'product_variation' === $post -> post_type ) ? 'product' : $post -> post_type ;
2013-11-14 17:40:35 +00:00
2015-01-22 19:22:21 +00:00
// Validate post type
if ( $type !== $post_type ) {
2013-11-11 00:29:36 +00:00
return new WP_Error ( " woocommerce_api_invalid_ { $resource_name } " , sprintf ( __ ( 'Invalid %s' , 'woocommerce' ), $resource_name ), array ( 'status' => 404 ) );
2015-01-22 19:22:21 +00:00
}
2013-11-11 00:29:36 +00:00
2015-01-22 19:22:21 +00:00
// Validate permissions
2013-11-11 00:29:36 +00:00
switch ( $context ) {
case 'read' :
2017-03-07 20:24:24 +00:00
if ( ! $this -> is_readable ( $post ) ) {
2013-11-11 00:29:36 +00:00
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 ) );
2017-03-07 20:24:24 +00:00
}
2013-11-11 00:29:36 +00:00
break ;
case 'edit' :
2017-03-07 20:24:24 +00:00
if ( ! $this -> is_editable ( $post ) ) {
2013-11-11 00:29:36 +00:00
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 ) );
2017-03-07 20:24:24 +00:00
}
2013-11-11 00:29:36 +00:00
break ;
case 'delete' :
2017-03-07 20:24:24 +00:00
if ( ! $this -> is_deletable ( $post ) ) {
2013-11-11 00:29:36 +00:00
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 ) );
2017-03-07 20:24:24 +00:00
}
2013-11-11 00:29:36 +00:00
break ;
}
}
return $id ;
}
2013-11-04 01:06:36 +00:00
/**
* Add common request arguments to argument list before WP_Query is run
*
* @ since 2.1
* @ param array $base_args required arguments for the query ( e . g . `post_type` , etc )
* @ param array $request_args arguments provided in the request
* @ return array
*/
2013-11-11 00:29:36 +00:00
protected function merge_query_args ( $base_args , $request_args ) {
2013-11-04 01:06:36 +00:00
$args = array ();
2013-11-11 00:29:36 +00:00
// date
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' ] ) ) {
2013-11-04 01:06:36 +00:00
2013-11-11 00:29:36 +00:00
$args [ 'date_query' ] = array ();
2013-11-04 01:06:36 +00:00
2013-11-11 00:29:36 +00:00
// resources created after specified date
2014-07-28 23:34:41 +00:00
if ( ! empty ( $request_args [ 'created_at_min' ] ) ) {
2013-11-18 21:47:38 +00:00
$args [ 'date_query' ][] = array ( 'column' => 'post_date_gmt' , 'after' => $this -> server -> parse_datetime ( $request_args [ 'created_at_min' ] ), 'inclusive' => true );
2014-07-28 23:34:41 +00:00
}
2013-11-04 01:06:36 +00:00
2013-11-11 00:29:36 +00:00
// resources created before specified date
2014-07-28 23:34:41 +00:00
if ( ! empty ( $request_args [ 'created_at_max' ] ) ) {
2013-11-18 21:47:38 +00:00
$args [ 'date_query' ][] = array ( 'column' => 'post_date_gmt' , 'before' => $this -> server -> parse_datetime ( $request_args [ 'created_at_max' ] ), 'inclusive' => true );
2014-07-28 23:34:41 +00:00
}
2013-11-11 00:29:36 +00:00
// resources updated after specified date
2014-07-28 23:34:41 +00:00
if ( ! empty ( $request_args [ 'updated_at_min' ] ) ) {
2013-11-18 21:47:38 +00:00
$args [ 'date_query' ][] = array ( 'column' => 'post_modified_gmt' , 'after' => $this -> server -> parse_datetime ( $request_args [ 'updated_at_min' ] ), 'inclusive' => true );
2014-07-28 23:34:41 +00:00
}
2013-11-11 00:29:36 +00:00
// resources updated before specified date
2014-07-28 23:34:41 +00:00
if ( ! empty ( $request_args [ 'updated_at_max' ] ) ) {
2013-11-18 21:47:38 +00:00
$args [ 'date_query' ][] = array ( 'column' => 'post_modified_gmt' , 'before' => $this -> server -> parse_datetime ( $request_args [ 'updated_at_max' ] ), 'inclusive' => true );
2014-07-28 23:34:41 +00:00
}
2013-11-04 01:06:36 +00:00
}
// search
2014-07-28 23:34:41 +00:00
if ( ! empty ( $request_args [ 'q' ] ) ) {
2013-11-04 01:06:36 +00:00
$args [ 's' ] = $request_args [ 'q' ];
2014-07-28 23:34:41 +00:00
}
2013-11-04 01:06:36 +00:00
// resources per response
2014-07-28 23:34:41 +00:00
if ( ! empty ( $request_args [ 'limit' ] ) ) {
2013-11-04 01:06:36 +00:00
$args [ 'posts_per_page' ] = $request_args [ 'limit' ];
2014-07-28 23:34:41 +00:00
}
2013-11-04 01:06:36 +00:00
// resource offset
2014-07-28 23:34:41 +00:00
if ( ! empty ( $request_args [ 'offset' ] ) ) {
2013-11-04 01:06:36 +00:00
$args [ 'offset' ] = $request_args [ 'offset' ];
2014-07-28 23:34:41 +00:00
}
2013-11-04 01:06:36 +00:00
2014-07-28 23:40:28 +00:00
// order (ASC or DESC, ASC by default)
2014-03-13 17:08:36 +00:00
if ( ! empty ( $request_args [ 'order' ] ) ) {
$args [ 'order' ] = $request_args [ 'order' ];
2014-07-28 23:40:28 +00:00
}
// orderby
if ( ! empty ( $request_args [ 'orderby' ] ) ) {
$args [ 'orderby' ] = $request_args [ 'orderby' ];
// allow sorting by meta value
2014-07-28 23:49:56 +00:00
if ( ! empty ( $request_args [ 'orderby_meta_key' ] ) ) {
2014-07-28 23:40:28 +00:00
$args [ 'meta_key' ] = $request_args [ 'orderby_meta_key' ];
}
2014-03-13 17:08:36 +00:00
}
2014-05-28 16:28:57 +00:00
// allow post status change
if ( ! empty ( $request_args [ 'post_status' ] ) ) {
$args [ 'post_status' ] = $request_args [ 'post_status' ];
unset ( $request_args [ 'post_status' ] );
}
2015-02-11 11:51:56 +00:00
// filter by a list of post id
if ( ! empty ( $request_args [ 'in' ] ) ) {
2015-02-11 12:13:36 +00:00
$args [ 'post__in' ] = explode ( ',' , $request_args [ 'in' ] );
2015-02-11 11:51:56 +00:00
unset ( $request_args [ 'in' ] );
}
2015-06-23 14:51:40 +00:00
2015-06-23 12:48:19 +00:00
// exclude by a list of post id
if ( ! empty ( $request_args [ 'not_in' ] ) ) {
$args [ 'post__not_in' ] = explode ( ',' , $request_args [ 'not_in' ] );
unset ( $request_args [ 'not_in' ] );
}
2015-02-11 11:51:56 +00:00
2013-11-11 00:29:36 +00:00
// resource page
2013-11-22 08:41:32 +00:00
$args [ 'paged' ] = ( isset ( $request_args [ 'page' ] ) ) ? absint ( $request_args [ 'page' ] ) : 1 ;
2013-11-11 00:29:36 +00:00
2015-01-12 18:46:30 +00:00
$args = apply_filters ( 'woocommerce_api_query_args' , $args , $request_args );
2015-01-12 15:59:27 +00:00
2013-11-04 01:06:36 +00:00
return array_merge ( $base_args , $args );
}
2013-11-11 00:29:36 +00:00
/**
* Add meta to resources when requested by the client . Meta is added as a top - level
2013-11-19 03:58:10 +00:00
* `<resource_name>_meta` attribute ( e . g . `order_meta` ) as a list of key / value pairs
2013-11-11 00:29:36 +00:00
*
* @ since 2.1
* @ param array $data the resource data
* @ param object $resource the resource object ( e . g WC_Order )
* @ return mixed
*/
public function maybe_add_meta ( $data , $resource ) {
2013-11-19 03:58:10 +00:00
if ( isset ( $this -> server -> params [ 'GET' ][ 'filter' ][ 'meta' ] ) && 'true' === $this -> server -> params [ 'GET' ][ 'filter' ][ 'meta' ] && is_object ( $resource ) ) {
// don't attempt to add meta more than once
2017-03-07 20:24:24 +00:00
if ( preg_grep ( '/[a-z]+_meta/' , array_keys ( $data ) ) ) {
2013-11-19 03:58:10 +00:00
return $data ;
2017-03-07 20:24:24 +00:00
}
2013-11-19 03:58:10 +00:00
// define the top-level property name for the meta
switch ( get_class ( $resource ) ) {
case 'WC_Order' :
$meta_name = 'order_meta' ;
break ;
case 'WC_Coupon' :
$meta_name = 'coupon_meta' ;
break ;
case 'WP_User' :
$meta_name = 'customer_meta' ;
break ;
default :
$meta_name = 'product_meta' ;
break ;
}
if ( is_a ( $resource , 'WP_User' ) ) {
// customer meta
$meta = ( array ) get_user_meta ( $resource -> ID );
} else {
// coupon/order/product meta
2017-06-05 09:45:10 +00:00
$meta = ( array ) get_post_meta ( $resource -> get_id () );
2013-11-19 03:58:10 +00:00
}
2016-08-27 04:23:02 +00:00
foreach ( $meta as $meta_key => $meta_value ) {
2013-11-19 03:58:10 +00:00
// don't add hidden meta by default
if ( ! is_protected_meta ( $meta_key ) ) {
$data [ $meta_name ][ $meta_key ] = maybe_unserialize ( $meta_value [ 0 ] );
}
}
2013-11-11 00:29:36 +00:00
}
return $data ;
}
2013-11-04 01:06:36 +00:00
/**
* Restrict the fields included in the response if the request specified certain only certain fields should be returned
*
* @ since 2.1
* @ param array $data the response data
* @ param object $resource the object that provided the response data , e . g . WC_Coupon or WC_Order
* @ param array | string the requested list of fields to include in the response
2013-11-19 02:59:13 +00:00
* @ return array response data
2013-11-04 01:06:36 +00:00
*/
2013-11-11 00:29:36 +00:00
public function filter_response_fields ( $data , $resource , $fields ) {
2013-11-04 01:06:36 +00:00
2014-08-30 19:02:19 +00:00
if ( ! is_array ( $data ) || empty ( $fields ) ) {
2013-11-04 01:06:36 +00:00
return $data ;
2014-08-30 19:02:19 +00:00
}
2013-11-04 01:06:36 +00:00
2013-11-04 06:36:16 +00:00
$fields = explode ( ',' , $fields );
2013-11-19 02:59:13 +00:00
$sub_fields = array ();
// get sub fields
foreach ( $fields as $field ) {
if ( false !== strpos ( $field , '.' ) ) {
2013-11-04 06:36:16 +00:00
2013-11-19 02:59:13 +00:00
list ( $name , $value ) = explode ( '.' , $field );
$sub_fields [ $name ] = $value ;
}
}
// iterate through top-level fields
2013-11-04 01:06:36 +00:00
foreach ( $data as $data_field => $data_value ) {
2013-11-19 02:59:13 +00:00
// if a field has sub-fields and the top-level field has sub-fields to filter
if ( is_array ( $data_value ) && in_array ( $data_field , array_keys ( $sub_fields ) ) ) {
// iterate through each sub-field
foreach ( $data_value as $sub_field => $sub_field_value ) {
// remove non-matching sub-fields
if ( ! in_array ( $sub_field , $sub_fields ) ) {
unset ( $data [ $data_field ][ $sub_field ] );
}
}
} else {
// remove non-matching top-level fields
if ( ! in_array ( $data_field , $fields ) ) {
unset ( $data [ $data_field ] );
}
}
2013-11-04 01:06:36 +00:00
}
return $data ;
}
/**
* Delete a given resource
*
* @ since 2.1
* @ param int $id the resource ID
2013-11-11 00:29:36 +00:00
* @ param string $type the resource post type , or `customer`
2013-11-04 01:06:36 +00:00
* @ param bool $force true to permanently delete resource , false to move to trash ( not supported for `customer` )
* @ return array | WP_Error
*/
2013-11-11 00:29:36 +00:00
protected function delete ( $id , $type , $force = false ) {
2013-11-04 01:06:36 +00:00
2014-08-30 19:02:19 +00:00
if ( 'shop_order' === $type || 'shop_coupon' === $type ) {
2013-11-11 00:29:36 +00:00
$resource_name = str_replace ( 'shop_' , '' , $type );
2014-08-30 19:02:19 +00:00
} else {
2013-11-11 00:29:36 +00:00
$resource_name = $type ;
2014-08-30 19:02:19 +00:00
}
2013-11-04 01:06:36 +00:00
if ( 'customer' === $type ) {
$result = wp_delete_user ( $id );
2017-03-07 20:24:24 +00:00
if ( $result ) {
2013-11-04 01:06:36 +00:00
return array ( 'message' => __ ( 'Permanently deleted customer' , 'woocommerce' ) );
2017-03-07 20:24:24 +00:00
} else {
2013-11-04 01:06:36 +00:00
return new WP_Error ( 'woocommerce_api_cannot_delete_customer' , __ ( 'The customer cannot be deleted' , 'woocommerce' ), array ( 'status' => 500 ) );
2017-03-07 20:24:24 +00:00
}
2013-11-04 01:06:36 +00:00
} else {
2014-07-30 20:22:58 +00:00
// delete order/coupon/product/webhook
2013-11-04 06:34:53 +00:00
$result = ( $force ) ? wp_delete_post ( $id , true ) : wp_trash_post ( $id );
2013-11-04 01:06:36 +00:00
2017-03-07 20:24:24 +00:00
if ( ! $result ) {
2013-11-11 00:29:36 +00:00
return new WP_Error ( " woocommerce_api_cannot_delete_ { $resource_name } " , sprintf ( __ ( 'This %s cannot be deleted' , 'woocommerce' ), $resource_name ), array ( 'status' => 500 ) );
2017-03-07 20:24:24 +00:00
}
2013-11-04 01:06:36 +00:00
if ( $force ) {
2013-11-11 00:29:36 +00:00
return array ( 'message' => sprintf ( __ ( 'Permanently deleted %s' , 'woocommerce' ), $resource_name ) );
2013-11-04 01:06:36 +00:00
} else {
$this -> server -> send_status ( '202' );
2013-11-11 00:29:36 +00:00
return array ( 'message' => sprintf ( __ ( 'Deleted %s' , 'woocommerce' ), $resource_name ) );
2013-11-04 01:06:36 +00:00
}
}
}
2013-11-11 00:29:36 +00:00
/**
* Checks if the given post is readable by the current user
*
* @ since 2.1
* @ see WC_API_Resource :: check_permission ()
* @ param WP_Post | int $post
* @ return bool
*/
protected function is_readable ( $post ) {
return $this -> check_permission ( $post , 'read' );
}
/**
* Checks if the given post is editable by the current user
*
* @ since 2.1
* @ see WC_API_Resource :: check_permission ()
* @ param WP_Post | int $post
* @ return bool
*/
protected function is_editable ( $post ) {
return $this -> check_permission ( $post , 'edit' );
}
/**
* Checks if the given post is deletable by the current user
*
* @ since 2.1
* @ see WC_API_Resource :: check_permission ()
* @ param WP_Post | int $post
* @ return bool
*/
protected function is_deletable ( $post ) {
return $this -> check_permission ( $post , 'delete' );
}
/**
* Checks the permissions for the current user given a post and context
*
* @ since 2.1
* @ param WP_Post | int $post
* @ param string $context the type of permission to check , either `read` , `write` , or `delete`
* @ return bool true if the current user has the permissions to perform the context on the post
*/
private function check_permission ( $post , $context ) {
2016-01-21 21:10:46 +00:00
$permission = false ;
2013-11-11 00:29:36 +00:00
2015-04-10 09:03:45 +00:00
if ( ! is_a ( $post , 'WP_Post' ) ) {
2013-11-23 19:32:22 +00:00
$post = get_post ( $post );
2015-04-10 09:03:45 +00:00
}
2013-11-11 00:29:36 +00:00
2015-04-10 09:03:45 +00:00
if ( is_null ( $post ) ) {
2016-01-21 21:10:46 +00:00
return $permission ;
2015-04-10 09:03:45 +00:00
}
2013-11-11 00:29:36 +00:00
2013-11-23 19:32:22 +00:00
$post_type = get_post_type_object ( $post -> post_type );
2013-11-11 00:29:36 +00:00
2015-04-10 08:32:20 +00:00
if ( 'read' === $context ) {
2016-01-21 21:10:46 +00:00
$permission = 'revision' !== $post -> post_type && current_user_can ( $post_type -> cap -> read_private_posts , $post -> ID );
2015-04-10 08:32:20 +00:00
} elseif ( 'edit' === $context ) {
2016-01-21 21:10:46 +00:00
$permission = current_user_can ( $post_type -> cap -> edit_post , $post -> ID );
2015-04-10 08:32:20 +00:00
} elseif ( 'delete' === $context ) {
2016-01-21 21:10:46 +00:00
$permission = current_user_can ( $post_type -> cap -> delete_post , $post -> ID );
2015-04-10 08:32:20 +00:00
}
2016-01-21 21:10:46 +00:00
return apply_filters ( 'woocommerce_api_check_permission' , $permission , $context , $post , $post_type );
2013-11-11 00:29:36 +00:00
}
2013-11-04 01:06:36 +00:00
}