2017-02-16 22:27:07 +00:00
< ? php
/**
2017-03-28 17:58:51 +00:00
* Abstract Rest CRUD Controller Class
2017-02-16 22:27:07 +00:00
*
* @ author Automattic
* @ category API
* @ package WooCommerce / Abstracts
2017-03-15 16:36:53 +00:00
* @ version 3.0 . 0
2017-02-16 22:27:07 +00:00
*/
if ( ! defined ( 'ABSPATH' ) ) {
exit ;
}
/**
* WC_REST_CRUD_Controller class .
*
* @ extends WC_REST_Posts_Controller
*/
abstract class WC_REST_CRUD_Controller extends WC_REST_Posts_Controller {
/**
* Endpoint namespace .
*
* @ var string
*/
protected $namespace = 'wc/v2' ;
/**
* If object is hierarchical .
*
* @ var bool
*/
protected $hierarchical = false ;
2017-02-17 02:09:46 +00:00
/**
* Get object .
*
* @ param int $id Object ID .
2017-08-23 13:38:00 +00:00
* @ return object WC_Data object or WP_Error object .
2017-02-17 02:09:46 +00:00
*/
protected function get_object ( $id ) {
return new WP_Error ( 'invalid-method' , sprintf ( __ ( " Method '%s' not implemented. Must be overridden in subclass. " , 'woocommerce' ), __METHOD__ ), array ( 'status' => 405 ) );
}
2017-02-16 22:27:07 +00:00
/**
* Check if a given request has access to read an item .
*
* @ param WP_REST_Request $request Full details about the request .
* @ return WP_Error | boolean
*/
public function get_item_permissions_check ( $request ) {
2017-02-17 02:09:46 +00:00
$object = $this -> get_object ( ( int ) $request [ 'id' ] );
if ( $object && 0 !== $object -> get_id () && ! wc_rest_check_post_permissions ( $this -> post_type , 'read' , $object -> get_id () ) ) {
2017-02-16 22:27:07 +00:00
return new WP_Error ( 'woocommerce_rest_cannot_view' , __ ( 'Sorry, you cannot view this resource.' , 'woocommerce' ), array ( 'status' => rest_authorization_required_code () ) );
}
return true ;
}
/**
* Check if a given request has access to update an item .
*
* @ param WP_REST_Request $request Full details about the request .
* @ return WP_Error | boolean
*/
public function update_item_permissions_check ( $request ) {
2017-02-17 02:09:46 +00:00
$object = $this -> get_object ( ( int ) $request [ 'id' ] );
if ( $object && 0 !== $object -> get_id () && ! wc_rest_check_post_permissions ( $this -> post_type , 'edit' , $object -> get_id () ) ) {
2017-02-16 22:27:07 +00:00
return new WP_Error ( 'woocommerce_rest_cannot_edit' , __ ( 'Sorry, you are not allowed to edit this resource.' , 'woocommerce' ), array ( 'status' => rest_authorization_required_code () ) );
}
return true ;
}
/**
* Check if a given request has access to delete an item .
*
* @ param WP_REST_Request $request Full details about the request .
* @ return bool | WP_Error
*/
public function delete_item_permissions_check ( $request ) {
2017-02-17 02:09:46 +00:00
$object = $this -> get_object ( ( int ) $request [ 'id' ] );
if ( $object && 0 !== $object -> get_id () && ! wc_rest_check_post_permissions ( $this -> post_type , 'delete' , $object -> get_id () ) ) {
2017-02-16 22:27:07 +00:00
return new WP_Error ( 'woocommerce_rest_cannot_delete' , __ ( 'Sorry, you are not allowed to delete this resource.' , 'woocommerce' ), array ( 'status' => rest_authorization_required_code () ) );
}
return true ;
}
/**
* Get object permalink .
*
2017-05-15 11:50:52 +00:00
* @ param object $object
2017-02-16 22:27:07 +00:00
* @ return string
*/
protected function get_permalink ( $object ) {
return '' ;
}
/**
* Prepares the object for the REST response .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2017-02-16 22:27:07 +00:00
* @ param WC_Data $object Object data .
* @ param WP_REST_Request $request Request object .
* @ return WP_Error | WP_REST_Response Response object on success , or WP_Error object on failure .
*/
protected function prepare_object_for_response ( $object , $request ) {
return new WP_Error ( 'invalid-method' , sprintf ( __ ( " Method '%s' not implemented. Must be overridden in subclass. " , 'woocommerce' ), __METHOD__ ), array ( 'status' => 405 ) );
}
/**
* Prepares one object for create or update operation .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2017-02-16 22:27:07 +00:00
* @ param WP_REST_Request $request Request object .
* @ param bool $creating If is creating a new object .
* @ return WP_Error | WC_Data The prepared item , or WP_Error object on failure .
*/
protected function prepare_object_for_database ( $request , $creating = false ) {
return new WP_Error ( 'invalid-method' , sprintf ( __ ( " Method '%s' not implemented. Must be overridden in subclass. " , 'woocommerce' ), __METHOD__ ), array ( 'status' => 405 ) );
}
/**
* Get a single item .
*
* @ param WP_REST_Request $request Full details about the request .
* @ return WP_Error | WP_REST_Response
*/
public function get_item ( $request ) {
$object = $this -> get_object ( ( int ) $request [ 'id' ] );
2017-02-17 02:09:46 +00:00
if ( ! $object || 0 === $object -> get_id () ) {
2017-02-16 22:27:07 +00:00
return new WP_Error ( " woocommerce_rest_ { $this -> post_type } _invalid_id " , __ ( 'Invalid ID.' , 'woocommerce' ), array ( 'status' => 404 ) );
}
$data = $this -> prepare_object_for_response ( $object , $request );
$response = rest_ensure_response ( $data );
if ( $this -> public ) {
$response -> link_header ( 'alternate' , $this -> get_permalink ( $object ), array ( 'type' => 'text/html' ) );
}
return $response ;
}
/**
* Save an object data .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2017-02-16 22:27:07 +00:00
* @ param WP_REST_Request $request Full details about the request .
* @ param bool $creating If is creating a new object .
* @ return WC_Data | WP_Error
*/
protected function save_object ( $request , $creating = false ) {
try {
$object = $this -> prepare_object_for_database ( $request , $creating );
if ( is_wp_error ( $object ) ) {
return $object ;
}
$object -> save ();
2017-02-17 03:46:42 +00:00
return $this -> get_object ( $object -> get_id () );
2017-02-16 22:27:07 +00:00
} catch ( WC_Data_Exception $e ) {
return new WP_Error ( $e -> getErrorCode (), $e -> getMessage (), $e -> getErrorData () );
} catch ( WC_REST_Exception $e ) {
return new WP_Error ( $e -> getErrorCode (), $e -> getMessage (), array ( 'status' => $e -> getCode () ) );
}
}
/**
* Create a single item .
*
* @ param WP_REST_Request $request Full details about the request .
* @ return WP_Error | WP_REST_Response
*/
public function create_item ( $request ) {
if ( ! empty ( $request [ 'id' ] ) ) {
/* translators: %s: post type */
return new WP_Error ( " woocommerce_rest_ { $this -> post_type } _exists " , sprintf ( __ ( 'Cannot create existing %s.' , 'woocommerce' ), $this -> post_type ), array ( 'status' => 400 ) );
}
$object = $this -> save_object ( $request , true );
if ( is_wp_error ( $object ) ) {
return $object ;
}
$this -> update_additional_fields_for_object ( $object , $request );
/**
* Fires after a single object is created or updated via the REST API .
*
* @ param WC_Data $object Inserted object .
* @ param WP_REST_Request $request Request object .
* @ param boolean $creating True when creating object , false when updating .
*/
do_action ( " woocommerce_rest_insert_ { $this -> post_type } _object " , $object , $request , true );
$request -> set_param ( 'context' , 'edit' );
$response = $this -> prepare_object_for_response ( $object , $request );
$response = rest_ensure_response ( $response );
$response -> set_status ( 201 );
$response -> header ( 'Location' , rest_url ( sprintf ( '/%s/%s/%d' , $this -> namespace , $this -> rest_base , $object -> get_id () ) ) );
return $response ;
}
/**
* Update a single post .
*
* @ param WP_REST_Request $request Full details about the request .
* @ return WP_Error | WP_REST_Response
*/
public function update_item ( $request ) {
$object = $this -> get_object ( ( int ) $request [ 'id' ] );
2017-02-17 02:09:46 +00:00
if ( ! $object || 0 === $object -> get_id () ) {
2017-02-17 03:17:52 +00:00
return new WP_Error ( " woocommerce_rest_ { $this -> post_type } _invalid_id " , __ ( 'Invalid ID.' , 'woocommerce' ), array ( 'status' => 400 ) );
2017-02-16 22:27:07 +00:00
}
$object = $this -> save_object ( $request , false );
if ( is_wp_error ( $object ) ) {
return $object ;
}
$this -> update_additional_fields_for_object ( $object , $request );
/**
* Fires after a single object is created or updated via the REST API .
*
* @ param WC_Data $object Inserted object .
* @ param WP_REST_Request $request Request object .
* @ param boolean $creating True when creating object , false when updating .
*/
do_action ( " woocommerce_rest_insert_ { $this -> post_type } _object " , $object , $request , false );
$request -> set_param ( 'context' , 'edit' );
$response = $this -> prepare_object_for_response ( $object , $request );
return rest_ensure_response ( $response );
}
/**
* Prepare objects query .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2017-02-16 22:27:07 +00:00
* @ param WP_REST_Request $request Full details about the request .
* @ return array
*/
protected function prepare_objects_query ( $request ) {
$args = array ();
$args [ 'offset' ] = $request [ 'offset' ];
$args [ 'order' ] = $request [ 'order' ];
$args [ 'orderby' ] = $request [ 'orderby' ];
$args [ 'paged' ] = $request [ 'page' ];
$args [ 'post__in' ] = $request [ 'include' ];
$args [ 'post__not_in' ] = $request [ 'exclude' ];
$args [ 'posts_per_page' ] = $request [ 'per_page' ];
$args [ 'name' ] = $request [ 'slug' ];
$args [ 'post_parent__in' ] = $request [ 'parent' ];
$args [ 'post_parent__not_in' ] = $request [ 'parent_exclude' ];
$args [ 's' ] = $request [ 'search' ];
2017-09-25 12:31:25 +00:00
if ( 'date' === $args [ 'orderby' ] ) {
$args [ 'orderby' ] = 'date ID' ;
}
2017-02-16 22:27:07 +00:00
$args [ 'date_query' ] = array ();
// Set before into date query. Date query must be specified as an array of an array.
if ( isset ( $request [ 'before' ] ) ) {
$args [ 'date_query' ][ 0 ][ 'before' ] = $request [ 'before' ];
}
// Set after into date query. Date query must be specified as an array of an array.
if ( isset ( $request [ 'after' ] ) ) {
$args [ 'date_query' ][ 0 ][ 'after' ] = $request [ 'after' ];
}
// Force the post_type argument, since it's not a user input variable.
$args [ 'post_type' ] = $this -> post_type ;
/**
* Filter the query arguments for a request .
*
* Enables adding extra arguments or setting defaults for a post
* collection request .
*
* @ param array $args Key value array of query var to query value .
* @ param WP_REST_Request $request The request used .
*/
$args = apply_filters ( " woocommerce_rest_ { $this -> post_type } _object_query " , $args , $request );
return $this -> prepare_items_query ( $args , $request );
}
/**
* Get objects .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2017-02-16 22:27:07 +00:00
* @ param array $query_args Query args .
* @ return array
*/
protected function get_objects ( $query_args ) {
$query = new WP_Query ();
$result = $query -> query ( $query_args );
$total_posts = $query -> found_posts ;
if ( $total_posts < 1 ) {
// Out-of-bounds, run the query again without LIMIT for total count.
unset ( $query_args [ 'paged' ] );
$count_query = new WP_Query ();
$count_query -> query ( $query_args );
$total_posts = $count_query -> found_posts ;
}
return array (
'objects' => array_map ( array ( $this , 'get_object' ), $result ),
'total' => ( int ) $total_posts ,
'pages' => ( int ) ceil ( $total_posts / ( int ) $query -> query_vars [ 'posts_per_page' ] ),
);
}
/**
* Get a collection of posts .
*
* @ param WP_REST_Request $request Full details about the request .
* @ return WP_Error | WP_REST_Response
*/
public function get_items ( $request ) {
$query_args = $this -> prepare_objects_query ( $request );
$query_results = $this -> get_objects ( $query_args );
$objects = array ();
foreach ( $query_results [ 'objects' ] as $object ) {
if ( ! wc_rest_check_post_permissions ( $this -> post_type , 'read' , $object -> get_id () ) ) {
continue ;
}
$data = $this -> prepare_object_for_response ( $object , $request );
$objects [] = $this -> prepare_response_for_collection ( $data );
}
$page = ( int ) $query_args [ 'paged' ];
$max_pages = $query_results [ 'pages' ];
$response = rest_ensure_response ( $objects );
$response -> header ( 'X-WP-Total' , $query_results [ 'total' ] );
$response -> header ( 'X-WP-TotalPages' , ( int ) $max_pages );
$base = add_query_arg ( $request -> get_query_params (), rest_url ( sprintf ( '/%s/%s' , $this -> namespace , $this -> rest_base ) ) );
if ( $page > 1 ) {
$prev_page = $page - 1 ;
if ( $prev_page > $max_pages ) {
$prev_page = $max_pages ;
}
$prev_link = add_query_arg ( 'page' , $prev_page , $base );
$response -> link_header ( 'prev' , $prev_link );
}
if ( $max_pages > $page ) {
$next_page = $page + 1 ;
$next_link = add_query_arg ( 'page' , $next_page , $base );
$response -> link_header ( 'next' , $next_link );
}
return $response ;
}
/**
* Delete a single item .
*
* @ param WP_REST_Request $request Full details about the request .
* @ return WP_REST_Response | WP_Error
*/
public function delete_item ( $request ) {
$force = ( bool ) $request [ 'force' ];
$object = $this -> get_object ( ( int ) $request [ 'id' ] );
2017-02-17 00:25:07 +00:00
$result = false ;
2017-02-16 22:27:07 +00:00
2017-02-17 02:09:46 +00:00
if ( ! $object || 0 === $object -> get_id () ) {
2017-02-16 22:27:07 +00:00
return new WP_Error ( " woocommerce_rest_ { $this -> post_type } _invalid_id " , __ ( 'Invalid ID.' , 'woocommerce' ), array ( 'status' => 404 ) );
}
$supports_trash = EMPTY_TRASH_DAYS > 0 && is_callable ( array ( $object , 'get_status' ) );
/**
* Filter whether an object is trashable .
*
* Return false to disable trash support for the object .
*
* @ param boolean $supports_trash Whether the object type support trashing .
* @ param WC_Data $object The object being considered for trashing support .
*/
$supports_trash = apply_filters ( " woocommerce_rest_ { $this -> post_type } _object_trashable " , $supports_trash , $object );
if ( ! wc_rest_check_post_permissions ( $this -> post_type , 'delete' , $object -> get_id () ) ) {
/* translators: %s: post type */
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 () ) );
}
$request -> set_param ( 'context' , 'edit' );
$response = $this -> prepare_object_for_response ( $object , $request );
// If we're forcing, then delete permanently.
if ( $force ) {
2017-02-17 00:25:07 +00:00
$object -> delete ( true );
$result = 0 === $object -> get_id ();
2017-02-16 22:27:07 +00:00
} else {
// If we don't support trashing for this type, error out.
if ( ! $supports_trash ) {
/* translators: %s: post type */
return new WP_Error ( 'woocommerce_rest_trash_not_supported' , sprintf ( __ ( 'The %s does not support trashing.' , 'woocommerce' ), $this -> post_type ), array ( 'status' => 501 ) );
}
// Otherwise, only trash if we haven't already.
2017-02-17 00:25:07 +00:00
if ( is_callable ( array ( $object , 'get_status' ) ) ) {
if ( 'trash' === $object -> get_status () ) {
/* translators: %s: post type */
return new WP_Error ( 'woocommerce_rest_already_trashed' , sprintf ( __ ( 'The %s has already been deleted.' , 'woocommerce' ), $this -> post_type ), array ( 'status' => 410 ) );
}
$object -> delete ();
$result = 'trash' === $object -> get_status ();
2017-02-16 22:27:07 +00:00
}
}
if ( ! $result ) {
/* translators: %s: post type */
return new WP_Error ( 'woocommerce_rest_cannot_delete' , sprintf ( __ ( 'The %s cannot be deleted.' , 'woocommerce' ), $this -> post_type ), array ( 'status' => 500 ) );
}
/**
* Fires after a single object is deleted or trashed via the REST API .
*
* @ param WC_Data $object The deleted or trashed object .
* @ param WP_REST_Response $response The response data .
* @ param WP_REST_Request $request The request sent to the API .
*/
do_action ( " woocommerce_rest_delete_ { $this -> post_type } _object " , $object , $response , $request );
return $response ;
}
/**
* Prepare links for the request .
*
* @ param WC_Data $object Object data .
* @ param WP_REST_Request $request Request object .
* @ return array Links for the given post .
*/
protected function prepare_links ( $object , $request ) {
$links = array (
'self' => array (
'href' => rest_url ( sprintf ( '/%s/%s/%d' , $this -> namespace , $this -> rest_base , $object -> get_id () ) ),
),
'collection' => array (
'href' => rest_url ( sprintf ( '/%s/%s' , $this -> namespace , $this -> rest_base ) ),
),
);
return $links ;
}
/**
* Get the query params for collections of attachments .
*
* @ return array
*/
public function get_collection_params () {
2017-04-04 17:53:05 +00:00
$params = array ();
$params [ 'context' ] = $this -> get_context_param ();
2017-02-16 22:27:07 +00:00
$params [ 'context' ][ 'default' ] = 'view' ;
$params [ 'page' ] = array (
'description' => __ ( 'Current page of the collection.' , 'woocommerce' ),
'type' => 'integer' ,
'default' => 1 ,
'sanitize_callback' => 'absint' ,
'validate_callback' => 'rest_validate_request_arg' ,
'minimum' => 1 ,
);
$params [ 'per_page' ] = array (
'description' => __ ( 'Maximum number of items to be returned in result set.' , 'woocommerce' ),
'type' => 'integer' ,
'default' => 10 ,
'minimum' => 1 ,
'maximum' => 100 ,
'sanitize_callback' => 'absint' ,
'validate_callback' => 'rest_validate_request_arg' ,
);
$params [ 'search' ] = array (
'description' => __ ( 'Limit results to those matching a string.' , 'woocommerce' ),
'type' => 'string' ,
'sanitize_callback' => 'sanitize_text_field' ,
'validate_callback' => 'rest_validate_request_arg' ,
);
$params [ 'after' ] = array (
'description' => __ ( 'Limit response to resources published after a given ISO8601 compliant date.' , 'woocommerce' ),
'type' => 'string' ,
'format' => 'date-time' ,
'validate_callback' => 'rest_validate_request_arg' ,
);
$params [ 'before' ] = array (
'description' => __ ( 'Limit response to resources published before a given ISO8601 compliant date.' , 'woocommerce' ),
'type' => 'string' ,
'format' => 'date-time' ,
'validate_callback' => 'rest_validate_request_arg' ,
);
$params [ 'exclude' ] = array (
'description' => __ ( 'Ensure result set excludes specific IDs.' , 'woocommerce' ),
'type' => 'array' ,
'items' => array (
'type' => 'integer' ,
),
'default' => array (),
'sanitize_callback' => 'wp_parse_id_list' ,
);
$params [ 'include' ] = array (
'description' => __ ( 'Limit result set to specific ids.' , 'woocommerce' ),
'type' => 'array' ,
'items' => array (
'type' => 'integer' ,
),
'default' => array (),
'sanitize_callback' => 'wp_parse_id_list' ,
);
$params [ 'offset' ] = array (
'description' => __ ( 'Offset the result set by a specific number of items.' , 'woocommerce' ),
'type' => 'integer' ,
'sanitize_callback' => 'absint' ,
'validate_callback' => 'rest_validate_request_arg' ,
);
$params [ 'order' ] = array (
'description' => __ ( 'Order sort attribute ascending or descending.' , 'woocommerce' ),
'type' => 'string' ,
'default' => 'desc' ,
'enum' => array ( 'asc' , 'desc' ),
'validate_callback' => 'rest_validate_request_arg' ,
);
$params [ 'orderby' ] = array (
'description' => __ ( 'Sort collection by object attribute.' , 'woocommerce' ),
'type' => 'string' ,
'default' => 'date' ,
'enum' => array (
'date' ,
'id' ,
'include' ,
'title' ,
'slug' ,
),
'validate_callback' => 'rest_validate_request_arg' ,
);
if ( $this -> hierarchical ) {
$params [ 'parent' ] = array (
'description' => __ ( 'Limit result set to those of particular parent IDs.' , 'woocommerce' ),
'type' => 'array' ,
'items' => array (
'type' => 'integer' ,
),
'sanitize_callback' => 'wp_parse_id_list' ,
'default' => array (),
);
$params [ 'parent_exclude' ] = array (
'description' => __ ( 'Limit result set to all items except those of a particular parent ID.' , 'woocommerce' ),
'type' => 'array' ,
'items' => array (
'type' => 'integer' ,
),
'sanitize_callback' => 'wp_parse_id_list' ,
'default' => array (),
);
}
return $params ;
}
}