woocommerce/includes/api/class-wc-api-resource.php

328 lines
10 KiB
PHP
Raw Normal View History

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
* @package WooCommerce/API
* @since 2.1
*/
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2013-11-09 21:20:23 +00:00
class WC_API_Resource {
2013-11-04 01:06:36 +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
* @param WC_API_Server $server
2013-11-09 21:20:23 +00:00
* @return WC_API_Resource
2013-11-04 01:06:36 +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
add_filter( 'woocommerce_api_endpoints', array( $this, 'register_routes' ) );
2013-11-04 01:06:36 +00:00
// remove fields from responses when requests specify certain fields
2013-11-04 06:36:16 +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
foreach ( array( 'order', 'coupon', 'customer', 'product', 'report' ) as $resource ) {
add_filter( "woocommerce_api_{$resource}_response", array( $this, 'maybe_add_meta' ), 15, 2 );
add_filter( "woocommerce_api_{$resource}_response", array( $this, 'filter_response_fields' ), 20, 3 );
}
2013-11-04 01:06: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 ) {
if ( 'shop_order' === $type || 'shop_coupon' === $type )
$resource_name = str_replace( 'shop_', '', $type );
else
$resource_name = $type;
$id = absint( $id );
// validate ID
if ( empty( $id ) )
return new WP_Error( "woocommerce_api_invalid_{$resource_name}_id", sprintf( __( 'Invalid %s ID', 'woocommerce' ), $type ), array( 'status' => 404 ) );
// only custom post types have per-post type/permission checks
if ( 'customer' !== $type ) {
$post = get_post( $id, ARRAY_A );
// validate post type
if ( $type !== $post['post_type'] )
return new WP_Error( "woocommerce_api_invalid_{$resource_name}", sprintf( __( 'Invalid %s', 'woocommerce' ), $resource_name ), array( 'status' => 404 ) );
// validate permissions
switch ( $context ) {
case 'read':
if ( ! $this->is_readable( $post ) )
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 ) );
break;
case 'edit':
if ( ! $this->is_editable( $post ) )
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 ) );
break;
case 'delete':
if ( ! $this->is_deletable( $post ) )
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 ) );
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
*/
protected function merge_query_args( $base_args, $request_args ) {
2013-11-04 01:06:36 +00:00
$args = array();
// TODO: convert all dates from provided timezone into UTC
// TODO: return all dates in provided timezone, else UTC
// 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
$args['date_query'] = array();
2013-11-04 01:06:36 +00:00
// resources created after specified date
2013-11-04 01:06:36 +00:00
if ( ! empty( $request_args['created_at_min'] ) )
$args['date_query'][] = array( 'column' => 'post_date_gmt', 'after' => $request_args['created_at_min'], 'inclusive' => true );
2013-11-04 01:06:36 +00:00
// resources created before specified date
2013-11-04 01:06:36 +00:00
if ( ! empty( $request_args['created_at_max'] ) )
$args['date_query'][] = array( 'column' => 'post_date_gmt', 'before' => $request_args['created_at_max'], 'inclusive' => true );
// resources updated after specified date
if ( ! empty( $request_args['updated_at_min'] ) )
$args['date_query'][] = array( 'column' => 'post_modified_gmt', 'after' => $request_args['updated_at_min'], 'inclusive' => true );
// resources updated before specified date
if ( ! empty( $request_args['updated_at_max'] ) )
$args['date_query'][] = array( 'column' => 'post_modified_gmt', 'before' => $request_args['updated_at_max'], 'inclusive' => true );
2013-11-04 01:06:36 +00:00
}
// search
if ( ! empty( $request_args['q'] ) )
$args['s'] = $request_args['q'];
// resources per response
if ( ! empty( $request_args['limit'] ) )
$args['posts_per_page'] = $request_args['limit'];
// resource offset
if ( ! empty( $request_args['offset'] ) )
$args['offset'] = $request_args['offset'];
// resource page
if ( empty( $request_args['page'] ) )
$args['paged'] = 1;
else
$args['paged'] = absint( $request_args['page'] );
2013-11-04 01:06:36 +00:00
return array_merge( $base_args, $args );
}
/**
* Add meta to resources when requested by the client. Meta is added as a top-level
* `<resource_name>_meta` attribute (e.g. `order_meta`) as a list of key/value pairs.
*
* @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 ) {
if ( isset( $this->server->params['GET']['filter']['meta'] ) && 'true' === $this->server->params['GET']['filter']['meta'] ) {
// TODO: implement
}
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
*
2013-11-04 06:36:16 +00:00
* @TODO this should also work with sub-fields, like billing_address.country
*
2013-11-04 01:06:36 +00:00
* @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
* @return mixed
*/
public function filter_response_fields( $data, $resource, $fields ) {
2013-11-04 01:06:36 +00:00
if ( empty( $fields ) )
return $data;
2013-11-04 06:36:16 +00:00
$fields = explode( ',', $fields );
2013-11-04 01:06:36 +00:00
foreach ( $data as $data_field => $data_value ) {
if ( ! in_array( $data_field, $fields ) )
unset( $data[ $data_field ] );
}
return $data;
}
/**
* Delete a given resource
*
* @since 2.1
* @param int $id the resource ID
* @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
*/
protected function delete( $id, $type, $force = false ) {
2013-11-04 01:06:36 +00:00
if ( 'shop_order' === $type || 'shop_coupon' === $type )
$resource_name = str_replace( 'shop_', '', $type );
else
$resource_name = $type;
2013-11-04 01:06:36 +00:00
if ( 'customer' === $type ) {
$result = wp_delete_user( $id );
if ( $result )
return array( 'message' => __( 'Permanently deleted customer', 'woocommerce' ) );
else
return new WP_Error( 'woocommerce_api_cannot_delete_customer', __( 'The customer cannot be deleted', 'woocommerce' ), array( 'status' => 500 ) );
} else {
// delete order/coupon/product
$result = ( $force ) ? wp_delete_post( $id, true ) : wp_trash_post( $id );
2013-11-04 01:06:36 +00:00
if ( ! $result )
return new WP_Error( "woocommerce_api_cannot_delete_{$resource_name}", sprintf( __( 'This %s cannot be deleted', 'woocommerce' ), $resource_name ), array( 'status' => 500 ) );
2013-11-04 01:06:36 +00:00
if ( $force ) {
return array( 'message' => sprintf( __( 'Permanently deleted %s', 'woocommerce' ), $resource_name ) );
2013-11-04 01:06:36 +00:00
} else {
$this->server->send_status( '202' );
return array( 'message' => sprintf( __( 'Deleted %s', 'woocommerce' ), $resource_name ) );
2013-11-04 01:06: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 ) {
if ( ! is_a( $post, 'WP_Post' ) )
$post = get_post( $post, ARRAY_A );
if ( is_null( $post ) )
return false;
$post_type = get_post_type_object( $post['post_type'] );
if ( 'read' === $context )
return current_user_can( $post_type->cap->read_post, $post['ID'] );
elseif ( 'edit' === $context )
return current_user_can( $post_type->cap->edit_post, $post['ID'] );
elseif ( 'delete' === $context )
return current_user_can( $post_type->cap->delete_post, $post['ID'] );
else
return false;
}
2013-11-04 01:06:36 +00:00
}