merge package config with factories changes

This commit is contained in:
Ron Rennick 2020-08-07 10:33:53 -03:00
commit db76c8ac2d
196 changed files with 60812 additions and 1015 deletions

View File

@ -8,16 +8,15 @@
"minimum-stability": "dev",
"require": {
"php": ">=7.0",
"automattic/jetpack-autoloader": "^2.0.2",
"automattic/jetpack-constants": "^1.1",
"automattic/jetpack-autoloader": "2.0.2",
"automattic/jetpack-constants": "1.4.0",
"composer/installers": "1.7.0",
"league/container": "^3.3",
"league/container": "3.3.1",
"maxmind-db/reader": "1.6.0",
"pelago/emogrifier": "^3.1",
"pelago/emogrifier": "3.1.0",
"woocommerce/action-scheduler": "3.1.6",
"woocommerce/woocommerce-admin": "1.4.0-beta.3",
"woocommerce/woocommerce-blocks": "3.1.0",
"woocommerce/woocommerce-rest-api": "1.0.11"
"woocommerce/woocommerce-blocks": "3.1.0"
},
"require-dev": {
"phpunit/phpunit": "7.5.20",
@ -40,6 +39,9 @@
"includes/legacy",
"includes/libraries"
],
"classmap": [
"includes/rest-api"
],
"psr-4": {
"Automattic\\WooCommerce\\": "src/"
}
@ -48,7 +50,10 @@
"psr-4": {
"Automattic\\WooCommerce\\Tests\\": "tests/php/src/",
"Automattic\\WooCommerce\\Testing\\Tools\\": "tests/Tools/"
}
},
"classmap": [
"tests/legacy/unit-tests/rest-api/Helpers"
]
},
"scripts": {
"post-install-cmd": [

109
composer.lock generated
View File

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d7df64252352cb3445d827cf204f24b4",
"content-hash": "ae4abaa8d39e860cc6c379cb5f6a0c2f",
"packages": [
{
"name": "automattic/jetpack-autoloader",
"version": "v2.1.0",
"version": "v2.0.2",
"source": {
"type": "git",
"url": "https://github.com/Automattic/jetpack-autoloader.git",
"reference": "802517b3ff3010de89141d9f7c4d56aec1d21527"
"reference": "4502da4b2443fc1b61389cacc94c34876aca2b3d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/802517b3ff3010de89141d9f7c4d56aec1d21527",
"reference": "802517b3ff3010de89141d9f7c4d56aec1d21527",
"url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/4502da4b2443fc1b61389cacc94c34876aca2b3d",
"reference": "4502da4b2443fc1b61389cacc94c34876aca2b3d",
"shasum": ""
},
"require": {
@ -40,7 +40,7 @@
"GPL-2.0-or-later"
],
"description": "Creates a custom autoloader for a plugin or theme.",
"time": "2020-07-27T20:37:00+00:00"
"time": "2020-07-09T13:18:38+00:00"
},
{
"name": "automattic/jetpack-constants",
@ -259,6 +259,12 @@
"provider",
"service"
],
"funding": [
{
"url": "https://github.com/philipobenito",
"type": "github"
}
],
"time": "2020-05-18T08:20:23+00:00"
},
{
@ -495,6 +501,20 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-03-16T08:31:04+00:00"
},
{
@ -625,46 +645,6 @@
"woocommerce"
],
"time": "2020-07-29T15:45:19+00:00"
},
{
"name": "woocommerce/woocommerce-rest-api",
"version": "1.0.11",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-rest-api.git",
"reference": "304bb95cb4b95f182f09d56153d5ac254d5fe60a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-rest-api/zipball/304bb95cb4b95f182f09d56153d5ac254d5fe60a",
"reference": "304bb95cb4b95f182f09d56153d5ac254d5fe60a",
"shasum": ""
},
"require": {
"automattic/jetpack-autoloader": "^2.0.0"
},
"require-dev": {
"phpunit/phpunit": "6.5.14",
"woocommerce/woocommerce-sniffs": "0.0.9"
},
"type": "wordpress-plugin",
"autoload": {
"classmap": [
"src/Controllers/Version1",
"src/Controllers/Version2",
"src/Controllers/Version3"
],
"psr-4": {
"Automattic\\WooCommerce\\RestApi\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-3.0-or-later"
],
"description": "The WooCommerce core REST API.",
"homepage": "https://github.com/woocommerce/woocommerce-rest-api",
"time": "2020-07-24T13:38:16+00:00"
}
],
"packages-dev": [
@ -788,6 +768,20 @@
"constructor",
"instantiate"
],
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
"type": "tidelift"
}
],
"time": "2020-05-29T17:27:14+00:00"
},
{
@ -1050,6 +1044,12 @@
"object",
"object graph"
],
"funding": [
{
"url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
"type": "tidelift"
}
],
"time": "2020-06-29T13:22:24+00:00"
},
{
@ -2574,6 +2574,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-02-14T07:34:21+00:00"
},
{
@ -3041,5 +3055,6 @@
"platform-dev": [],
"platform-overrides": {
"php": "7.1"
}
},
"plugin-api-version": "1.1.0"
}

View File

@ -30,7 +30,7 @@ class WC_API extends WC_Legacy_API {
}
/**
* Get the version of the REST API package being ran.
* Get the version of the REST API package being ran. Since API package was merged into core, this now follows WC version.
*
* @since 3.7.0
* @return string|null
@ -39,6 +39,14 @@ class WC_API extends WC_Legacy_API {
if ( ! $this->is_rest_api_loaded() ) {
return null;
}
if ( method_exists( \Automattic\WooCommerce\RestApi\Server::class, 'get_path' ) ) {
$path = \Automattic\WooCommerce\RestApi\Server::get_path();
if ( 0 === strpos( $path, __DIR__ ) ) {
// We are loading API from included version.
return WC()->version;
}
}
// We are loading API from external plugin.
return \Automattic\WooCommerce\RestApi\Package::get_version();
}
@ -52,6 +60,11 @@ class WC_API extends WC_Legacy_API {
if ( ! $this->is_rest_api_loaded() ) {
return null;
}
if ( method_exists( \Automattic\WooCommerce\RestApi\Server::class, 'get_path' ) ) {
// We are loading API from included version.
return \Automattic\WooCommerce\RestApi\Server::get_path();
}
// We are loading API from external plugin.
return \Automattic\WooCommerce\RestApi\Package::get_path();
}
@ -62,7 +75,7 @@ class WC_API extends WC_Legacy_API {
* @return boolean
*/
protected function is_rest_api_loaded() {
return class_exists( '\Automattic\WooCommerce\RestApi\Package', false );
return class_exists( '\Automattic\WooCommerce\RestApi\Server', false );
}
/**

View File

@ -1691,7 +1691,7 @@ class WC_Cart extends WC_Legacy_Cart {
*/
public function remove_coupon( $coupon_code ) {
$coupon_code = wc_format_coupon_code( $coupon_code );
$position = array_search( $coupon_code, $this->get_applied_coupons(), true );
$position = array_search( $coupon_code, array_map( 'wc_format_coupon_code', $this->get_applied_coupons() ), true );
if ( false !== $position ) {
unset( $this->applied_coupons[ $position ] );

View File

@ -153,6 +153,10 @@ class WC_Install {
'wc_update_440_insert_attribute_terms_for_variable_products',
'wc_update_440_db_version',
),
'4.5.0' => array(
'wc_update_450_sanitize_coupons_code',
'wc_update_450_db_version',
),
);
/**

View File

@ -255,6 +255,9 @@ class WC_Post_Data {
}
} elseif ( 'product' === $data['post_type'] && 'auto-draft' === $data['post_status'] ) {
$data['post_title'] = 'AUTO-DRAFT';
} elseif ( 'shop_coupon' === $data['post_type'] ) {
// Coupons should never allow unfiltered HTML.
$data['post_title'] = wp_filter_kses( $data['post_title'] );
}
return $data;

View File

@ -198,6 +198,7 @@ final class WooCommerce {
add_action( 'init', array( 'WC_Shortcodes', 'init' ) );
add_action( 'init', array( 'WC_Emails', 'init_transactional_emails' ) );
add_action( 'init', array( $this, 'add_image_sizes' ) );
add_action( 'init', array( $this, 'load_rest_api' ) );
add_action( 'switch_blog', array( $this, 'wpdb_table_fix' ), 0 );
add_action( 'activated_plugin', array( $this, 'activated_plugin' ) );
add_action( 'deactivated_plugin', array( $this, 'deactivated_plugin' ) );
@ -298,6 +299,13 @@ final class WooCommerce {
return apply_filters( 'woocommerce_is_rest_api_request', $is_rest_api_request );
}
/**
* Load REST API.
*/
public function load_rest_api() {
\Automattic\WooCommerce\RestApi\Server::instance()->init();
}
/**
* What type of request is this?
*

View File

@ -721,7 +721,7 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
return $wpdb->get_col(
$wpdb->prepare(
"SELECT ID FROM $wpdb->posts WHERE post_title = %s AND post_type = 'shop_coupon' AND post_status = 'publish' ORDER BY post_date DESC",
$code
wc_sanitize_coupon_code( $code )
)
);
}

View File

@ -3,7 +3,7 @@
* WC Order Item Shipping Data Store
*
* @version 3.0.0
* @package data-stores
* @package WooCommerce\DataStores
*/
if ( ! defined( 'ABSPATH' ) ) {

View File

@ -0,0 +1,580 @@
<?php
/**
* REST API Coupons controller
*
* Handles requests to the /coupons endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Coupons controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Posts_Controller
*/
class WC_REST_Coupons_V1_Controller extends WC_REST_Posts_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'coupons';
/**
* Post type.
*
* @var string
*/
protected $post_type = 'shop_coupon';
/**
* Coupons actions.
*/
public function __construct() {
add_filter( "woocommerce_rest_{$this->post_type}_query", array( $this, 'query_args' ), 10, 2 );
}
/**
* Register the routes for coupons.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
'code' => array(
'description' => __( 'Coupon code.', 'woocommerce' ),
'required' => true,
'type' => 'string',
),
) ),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
'type' => 'boolean',
'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'batch_items' ),
'permission_callback' => array( $this, 'batch_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_batch_schema' ),
) );
}
/**
* Query args.
*
* @param array $args Query args
* @param WP_REST_Request $request Request data.
* @return array
*/
public function query_args( $args, $request ) {
if ( ! empty( $request['code'] ) ) {
$id = wc_get_coupon_id_by_code( $request['code'] );
$args['post__in'] = array( $id );
}
return $args;
}
/**
* Prepare a single coupon output for response.
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $data
*/
public function prepare_item_for_response( $post, $request ) {
$coupon = new WC_Coupon( (int) $post->ID );
$_data = $coupon->get_data();
$format_decimal = array( 'amount', 'minimum_amount', 'maximum_amount' );
$format_date = array( 'date_created', 'date_modified' );
$format_date_utc = array( 'date_expires' );
$format_null = array( 'usage_limit', 'usage_limit_per_user' );
// Format decimal values.
foreach ( $format_decimal as $key ) {
$_data[ $key ] = wc_format_decimal( $_data[ $key ], 2 );
}
// Format date values.
foreach ( $format_date as $key ) {
$_data[ $key ] = $_data[ $key ] ? wc_rest_prepare_date_response( $_data[ $key ], false ) : null;
}
foreach ( $format_date_utc as $key ) {
$_data[ $key ] = $_data[ $key ] ? wc_rest_prepare_date_response( $_data[ $key ] ) : null;
}
// Format null values.
foreach ( $format_null as $key ) {
$_data[ $key ] = $_data[ $key ] ? $_data[ $key ] : null;
}
$data = array(
'id' => $_data['id'],
'code' => $_data['code'],
'date_created' => $_data['date_created'],
'date_modified' => $_data['date_modified'],
'discount_type' => $_data['discount_type'],
'description' => $_data['description'],
'amount' => $_data['amount'],
'expiry_date' => $_data['date_expires'],
'usage_count' => $_data['usage_count'],
'individual_use' => $_data['individual_use'],
'product_ids' => $_data['product_ids'],
'exclude_product_ids' => $_data['excluded_product_ids'],
'usage_limit' => $_data['usage_limit'],
'usage_limit_per_user' => $_data['usage_limit_per_user'],
'limit_usage_to_x_items' => $_data['limit_usage_to_x_items'],
'free_shipping' => $_data['free_shipping'],
'product_categories' => $_data['product_categories'],
'excluded_product_categories' => $_data['excluded_product_categories'],
'exclude_sale_items' => $_data['exclude_sale_items'],
'minimum_amount' => $_data['minimum_amount'],
'maximum_amount' => $_data['maximum_amount'],
'email_restrictions' => $_data['email_restrictions'],
'used_by' => $_data['used_by'],
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $post, $request ) );
/**
* Filter the data for a response.
*
* The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
* prepared for the response.
*
* @param WP_REST_Response $response The response object.
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "woocommerce_rest_prepare_{$this->post_type}", $response, $post, $request );
}
/**
* Only return writable props from schema.
* @param array $schema
* @return bool
*/
protected function filter_writable_props( $schema ) {
return empty( $schema['readonly'] );
}
/**
* Prepare a single coupon for create or update.
*
* @param WP_REST_Request $request Request object.
* @return WP_Error|stdClass $data Post object.
*/
protected function prepare_item_for_database( $request ) {
$id = isset( $request['id'] ) ? absint( $request['id'] ) : 0;
$coupon = new WC_Coupon( $id );
$schema = $this->get_item_schema();
$data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) );
// Update to schema to make compatible with CRUD schema.
if ( $request['exclude_product_ids'] ) {
$request['excluded_product_ids'] = $request['exclude_product_ids'];
}
if ( $request['expiry_date'] ) {
$request['date_expires'] = $request['expiry_date'];
}
// Validate required POST fields.
if ( 'POST' === $request->get_method() && 0 === $coupon->get_id() ) {
if ( empty( $request['code'] ) ) {
return new WP_Error( 'woocommerce_rest_empty_coupon_code', sprintf( __( 'The coupon code cannot be empty.', 'woocommerce' ), 'code' ), array( 'status' => 400 ) );
}
}
// Handle all writable props.
foreach ( $data_keys as $key ) {
$value = $request[ $key ];
if ( ! is_null( $value ) ) {
switch ( $key ) {
case 'code' :
$coupon_code = wc_format_coupon_code( $value );
$id = $coupon->get_id() ? $coupon->get_id() : 0;
$id_from_code = wc_get_coupon_id_by_code( $coupon_code, $id );
if ( $id_from_code ) {
return new WP_Error( 'woocommerce_rest_coupon_code_already_exists', __( 'The coupon code already exists', 'woocommerce' ), array( 'status' => 400 ) );
}
$coupon->set_code( $coupon_code );
break;
case 'description' :
$coupon->set_description( wp_filter_post_kses( $value ) );
break;
case 'expiry_date' :
$coupon->set_date_expires( $value );
break;
default :
if ( is_callable( array( $coupon, "set_{$key}" ) ) ) {
$coupon->{"set_{$key}"}( $value );
}
break;
}
}
}
/**
* Filter the query_vars used in `get_items` for the constructed query.
*
* The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
* prepared for insertion.
*
* @param WC_Coupon $coupon The coupon object.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $coupon, $request );
}
/**
* 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 ) );
}
$coupon_id = $this->save_coupon( $request );
if ( is_wp_error( $coupon_id ) ) {
return $coupon_id;
}
$post = get_post( $coupon_id );
$this->update_additional_fields_for_object( $post, $request );
$this->add_post_meta_fields( $post, $request );
/**
* Fires after a single item is created or updated via the REST API.
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating item, false when updating.
*/
do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, true );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $post, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post->ID ) ) );
return $response;
}
/**
* Update a single coupon.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
try {
$post_id = (int) $request['id'];
if ( empty( $post_id ) || get_post_type( $post_id ) !== $this->post_type ) {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'ID is invalid.', 'woocommerce' ), array( 'status' => 400 ) );
}
$coupon_id = $this->save_coupon( $request );
if ( is_wp_error( $coupon_id ) ) {
return $coupon_id;
}
$post = get_post( $coupon_id );
$this->update_additional_fields_for_object( $post, $request );
/**
* Fires after a single item is created or updated via the REST API.
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating item, false when updating.
*/
do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, false );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $post, $request );
return rest_ensure_response( $response );
} catch ( Exception $e ) {
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
}
}
/**
* Saves a coupon to the database.
*
* @since 3.0.0
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|int
*/
protected function save_coupon( $request ) {
try {
$coupon = $this->prepare_item_for_database( $request );
if ( is_wp_error( $coupon ) ) {
return $coupon;
}
$coupon->save();
return $coupon->get_id();
} 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() ) );
}
}
/**
* Get the Coupon's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => $this->post_type,
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the object.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'code' => array(
'description' => __( 'Coupon code.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'date_created' => array(
'description' => __( "The date the coupon was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the coupon was last modified, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'Coupon description.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'discount_type' => array(
'description' => __( 'Determines the type of discount that will be applied.', 'woocommerce' ),
'type' => 'string',
'default' => 'fixed_cart',
'enum' => array_keys( wc_get_coupon_types() ),
'context' => array( 'view', 'edit' ),
),
'amount' => array(
'description' => __( 'The amount of discount. Should always be numeric, even if setting a percentage.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'expiry_date' => array(
'description' => __( 'UTC DateTime when the coupon expires.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'usage_count' => array(
'description' => __( 'Number of times the coupon has been used already.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'individual_use' => array(
'description' => __( 'If true, the coupon can only be used individually. Other applied coupons will be removed from the cart.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'product_ids' => array(
'description' => __( "List of product IDs the coupon can be used on.", 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'context' => array( 'view', 'edit' ),
),
'exclude_product_ids' => array(
'description' => __( "List of product IDs the coupon cannot be used on.", 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'context' => array( 'view', 'edit' ),
),
'usage_limit' => array(
'description' => __( 'How many times the coupon can be used in total.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'usage_limit_per_user' => array(
'description' => __( 'How many times the coupon can be used per customer.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'limit_usage_to_x_items' => array(
'description' => __( 'Max number of items in the cart the coupon can be applied to.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'free_shipping' => array(
'description' => __( 'If true and if the free shipping method requires a coupon, this coupon will enable free shipping.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'product_categories' => array(
'description' => __( "List of category IDs the coupon applies to.", 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'context' => array( 'view', 'edit' ),
),
'excluded_product_categories' => array(
'description' => __( "List of category IDs the coupon does not apply to.", 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'context' => array( 'view', 'edit' ),
),
'exclude_sale_items' => array(
'description' => __( 'If true, this coupon will not be applied to items that have sale prices.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'minimum_amount' => array(
'description' => __( 'Minimum order amount that needs to be in the cart before coupon applies.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'maximum_amount' => array(
'description' => __( 'Maximum order amount allowed when using the coupon.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'email_restrictions' => array(
'description' => __( 'List of email addresses that can use this coupon.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'string',
),
'context' => array( 'view', 'edit' ),
),
'used_by' => array(
'description' => __( 'List of user IDs (or guest email addresses) that have used the coupon.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections of attachments.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
$params['code'] = array(
'description' => __( 'Limit result set to resources with a specific code.', 'woocommerce' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
}

View File

@ -0,0 +1,252 @@
<?php
/**
* REST API Customer Downloads controller
*
* Handles requests to the /customers/<customer_id>/downloads endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Customers controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Customer_Downloads_V1_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'customers/(?P<customer_id>[\d]+)/downloads';
/**
* Register the routes for customers.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
'args' => array(
'customer_id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
}
/**
* Check whether a given request has permission to read customers.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
$customer = get_user_by( 'id', (int) $request['customer_id'] );
if ( ! $customer ) {
return new WP_Error( 'woocommerce_rest_customer_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
}
if ( ! wc_rest_check_user_permissions( 'read', $customer->get_id() ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get all customer downloads.
*
* @param WP_REST_Request $request
* @return array
*/
public function get_items( $request ) {
$downloads = wc_get_customer_available_downloads( (int) $request['customer_id'] );
$data = array();
foreach ( $downloads as $download_data ) {
$download = $this->prepare_item_for_response( (object) $download_data, $request );
$download = $this->prepare_response_for_collection( $download );
$data[] = $download;
}
return rest_ensure_response( $data );
}
/**
* Prepare a single download output for response.
*
* @param stdObject $download Download object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $download, $request ) {
$data = (array) $download;
$data['access_expires'] = $data['access_expires'] ? wc_rest_prepare_date_response( $data['access_expires'] ) : 'never';
$data['downloads_remaining'] = '' === $data['downloads_remaining'] ? 'unlimited' : $data['downloads_remaining'];
// Remove "product_name" since it's new in 3.0.
unset( $data['product_name'] );
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $download, $request ) );
/**
* Filter customer download data returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param stdObject $download Download object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_customer_download', $response, $download, $request );
}
/**
* Prepare links for the request.
*
* @param stdClass $download Download object.
* @param WP_REST_Request $request Request object.
* @return array Links for the given customer download.
*/
protected function prepare_links( $download, $request ) {
$base = str_replace( '(?P<customer_id>[\d]+)', $request['customer_id'], $this->rest_base );
$links = array(
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ),
),
'product' => array(
'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $download->product_id ) ),
),
'order' => array(
'href' => rest_url( sprintf( '/%s/orders/%d', $this->namespace, $download->order_id ) ),
),
);
return $links;
}
/**
* Get the Customer Download's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'customer_download',
'type' => 'object',
'properties' => array(
'download_url' => array(
'description' => __( 'Download file URL.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'download_id' => array(
'description' => __( 'Download ID (MD5).', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'product_id' => array(
'description' => __( 'Downloadable product ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'download_name' => array(
'description' => __( 'Downloadable file name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'order_id' => array(
'description' => __( 'Order ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'order_key' => array(
'description' => __( 'Order key.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'downloads_remaining' => array(
'description' => __( 'Number of downloads remaining.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'access_expires' => array(
'description' => __( "The date when download access expires, in the site's timezone.", 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'file' => array(
'description' => __( 'File details.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view' ),
'readonly' => true,
'properties' => array(
'name' => array(
'description' => __( 'File name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'file' => array(
'description' => __( 'File URL.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
}

View File

@ -0,0 +1,924 @@
<?php
/**
* REST API Customers controller
*
* Handles requests to the /customers endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Customers controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Customers_V1_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'customers';
/**
* Register the routes for customers.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
'email' => array(
'required' => true,
'type' => 'string',
'description' => __( 'New user email address.', 'woocommerce' ),
),
'username' => array(
'required' => 'no' === get_option( 'woocommerce_registration_generate_username', 'yes' ),
'description' => __( 'New user username.', 'woocommerce' ),
'type' => 'string',
),
'password' => array(
'required' => 'no' === get_option( 'woocommerce_registration_generate_password', 'no' ),
'description' => __( 'New user password.', 'woocommerce' ),
'type' => 'string',
),
) ),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
'type' => 'boolean',
'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
),
'reassign' => array(
'default' => 0,
'type' => 'integer',
'description' => __( 'ID to reassign posts to.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'batch_items' ),
'permission_callback' => array( $this, 'batch_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_batch_schema' ),
) );
}
/**
* Check whether a given request has permission to read customers.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_user_permissions( 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access create customers.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function create_item_permissions_check( $request ) {
if ( ! wc_rest_check_user_permissions( 'create' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to read a customer.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
$id = (int) $request['id'];
if ( ! wc_rest_check_user_permissions( 'read', $id ) ) {
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 update a customer.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function update_item_permissions_check( $request ) {
$id = (int) $request['id'];
if ( ! wc_rest_check_user_permissions( 'edit', $id ) ) {
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 delete a customer.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function delete_item_permissions_check( $request ) {
$id = (int) $request['id'];
if ( ! wc_rest_check_user_permissions( 'delete', $id ) ) {
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;
}
/**
* Check if a given request has access batch create, update and delete items.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function batch_items_permissions_check( $request ) {
if ( ! wc_rest_check_user_permissions( 'batch' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get all customers.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$prepared_args = array();
$prepared_args['exclude'] = $request['exclude'];
$prepared_args['include'] = $request['include'];
$prepared_args['order'] = $request['order'];
$prepared_args['number'] = $request['per_page'];
if ( ! empty( $request['offset'] ) ) {
$prepared_args['offset'] = $request['offset'];
} else {
$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
}
$orderby_possibles = array(
'id' => 'ID',
'include' => 'include',
'name' => 'display_name',
'registered_date' => 'registered',
);
$prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ];
$prepared_args['search'] = $request['search'];
if ( '' !== $prepared_args['search'] ) {
$prepared_args['search'] = '*' . $prepared_args['search'] . '*';
}
// Filter by email.
if ( ! empty( $request['email'] ) ) {
$prepared_args['search'] = $request['email'];
$prepared_args['search_columns'] = array( 'user_email' );
}
// Filter by role.
if ( 'all' !== $request['role'] ) {
$prepared_args['role'] = $request['role'];
}
/**
* Filter arguments, before passing to WP_User_Query, when querying users via the REST API.
*
* @see https://developer.wordpress.org/reference/classes/wp_user_query/
*
* @param array $prepared_args Array of arguments for WP_User_Query.
* @param WP_REST_Request $request The current request.
*/
$prepared_args = apply_filters( 'woocommerce_rest_customer_query', $prepared_args, $request );
$query = new WP_User_Query( $prepared_args );
$users = array();
foreach ( $query->results as $user ) {
$data = $this->prepare_item_for_response( $user, $request );
$users[] = $this->prepare_response_for_collection( $data );
}
$response = rest_ensure_response( $users );
// Store pagination values for headers then unset for count query.
$per_page = (int) $prepared_args['number'];
$page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
$prepared_args['fields'] = 'ID';
$total_users = $query->get_total();
if ( $total_users < 1 ) {
// Out-of-bounds, run the query again without LIMIT for total count.
unset( $prepared_args['number'] );
unset( $prepared_args['offset'] );
$count_query = new WP_User_Query( $prepared_args );
$total_users = $count_query->get_total();
}
$response->header( 'X-WP-Total', (int) $total_users );
$max_pages = ceil( $total_users / $per_page );
$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;
}
/**
* Create a single customer.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function create_item( $request ) {
try {
if ( ! empty( $request['id'] ) ) {
throw new WC_REST_Exception( 'woocommerce_rest_customer_exists', __( 'Cannot create existing resource.', 'woocommerce' ), 400 );
}
// Sets the username.
$request['username'] = ! empty( $request['username'] ) ? $request['username'] : '';
// Sets the password.
$request['password'] = ! empty( $request['password'] ) ? $request['password'] : '';
// Create customer.
$customer = new WC_Customer;
$customer->set_username( $request['username'] );
$customer->set_password( $request['password'] );
$customer->set_email( $request['email'] );
$this->update_customer_meta_fields( $customer, $request );
$customer->save();
if ( ! $customer->get_id() ) {
throw new WC_REST_Exception( 'woocommerce_rest_cannot_create', __( 'This resource cannot be created.', 'woocommerce' ), 400 );
}
$user_data = get_userdata( $customer->get_id() );
$this->update_additional_fields_for_object( $user_data, $request );
/**
* Fires after a customer is created or updated via the REST API.
*
* @param WP_User $user_data Data used to create the customer.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating customer, false when updating customer.
*/
do_action( 'woocommerce_rest_insert_customer', $user_data, $request, true );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $user_data, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $customer->get_id() ) ) );
return $response;
} catch ( Exception $e ) {
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
}
}
/**
* Get a single customer.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$id = (int) $request['id'];
$user_data = get_userdata( $id );
if ( empty( $id ) || empty( $user_data->ID ) ) {
return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$customer = $this->prepare_item_for_response( $user_data, $request );
$response = rest_ensure_response( $customer );
return $response;
}
/**
* Update a single user.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
try {
$id = (int) $request['id'];
$customer = new WC_Customer( $id );
if ( ! $customer->get_id() ) {
throw new WC_REST_Exception( 'woocommerce_rest_invalid_id', __( 'Invalid resource ID.', 'woocommerce' ), 400 );
}
if ( ! empty( $request['email'] ) && email_exists( $request['email'] ) && $request['email'] !== $customer->get_email() ) {
throw new WC_REST_Exception( 'woocommerce_rest_customer_invalid_email', __( 'Email address is invalid.', 'woocommerce' ), 400 );
}
if ( ! empty( $request['username'] ) && $request['username'] !== $customer->get_username() ) {
throw new WC_REST_Exception( 'woocommerce_rest_customer_invalid_argument', __( "Username isn't editable.", 'woocommerce' ), 400 );
}
// Customer email.
if ( isset( $request['email'] ) ) {
$customer->set_email( sanitize_email( $request['email'] ) );
}
// Customer password.
if ( isset( $request['password'] ) ) {
$customer->set_password( $request['password'] );
}
$this->update_customer_meta_fields( $customer, $request );
$customer->save();
$user_data = get_userdata( $customer->get_id() );
$this->update_additional_fields_for_object( $user_data, $request );
if ( ! is_user_member_of_blog( $user_data->ID ) ) {
$user_data->add_role( 'customer' );
}
/**
* Fires after a customer is created or updated via the REST API.
*
* @param WP_User $customer Data used to create the customer.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating customer, false when updating customer.
*/
do_action( 'woocommerce_rest_insert_customer', $user_data, $request, false );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $user_data, $request );
$response = rest_ensure_response( $response );
return $response;
} catch ( Exception $e ) {
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
}
}
/**
* Delete a single customer.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function delete_item( $request ) {
$id = (int) $request['id'];
$reassign = isset( $request['reassign'] ) ? absint( $request['reassign'] ) : null;
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
// We don't support trashing for this type, error out.
if ( ! $force ) {
return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Customers do not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
}
$user_data = get_userdata( $id );
if ( ! $user_data ) {
return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource id.', 'woocommerce' ), array( 'status' => 400 ) );
}
if ( ! empty( $reassign ) ) {
if ( $reassign === $id || ! get_userdata( $reassign ) ) {
return new WP_Error( 'woocommerce_rest_customer_invalid_reassign', __( 'Invalid resource id for reassignment.', 'woocommerce' ), array( 'status' => 400 ) );
}
}
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $user_data, $request );
/** Include admin customer functions to get access to wp_delete_user() */
require_once ABSPATH . 'wp-admin/includes/user.php';
$customer = new WC_Customer( $id );
if ( ! is_null( $reassign ) ) {
$result = $customer->delete_and_reassign( $reassign );
} else {
$result = $customer->delete();
}
if ( ! $result ) {
return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
}
/**
* Fires after a customer is deleted via the REST API.
*
* @param WP_User $user_data User data.
* @param WP_REST_Response $response The response returned from the API.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( 'woocommerce_rest_delete_customer', $user_data, $response, $request );
return $response;
}
/**
* Prepare a single customer output for response.
*
* @param WP_User $user_data User object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $user_data, $request ) {
$customer = new WC_Customer( $user_data->ID );
$_data = $customer->get_data();
$last_order = wc_get_customer_last_order( $customer->get_id() );
$format_date = array( 'date_created', 'date_modified' );
// Format date values.
foreach ( $format_date as $key ) {
$_data[ $key ] = $_data[ $key ] ? wc_rest_prepare_date_response( $_data[ $key ] ) : null; // v1 API used UTC.
}
$data = array(
'id' => $_data['id'],
'date_created' => $_data['date_created'],
'date_modified' => $_data['date_modified'],
'email' => $_data['email'],
'first_name' => $_data['first_name'],
'last_name' => $_data['last_name'],
'username' => $_data['username'],
'last_order' => array(
'id' => is_object( $last_order ) ? $last_order->get_id() : null,
'date' => is_object( $last_order ) ? wc_rest_prepare_date_response( $last_order->get_date_created() ) : null, // v1 API used UTC.
),
'orders_count' => $customer->get_order_count(),
'total_spent' => $customer->get_total_spent(),
'avatar_url' => $customer->get_avatar_url(),
'billing' => $_data['billing'],
'shipping' => $_data['shipping'],
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $user_data ) );
/**
* Filter customer data returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param WP_User $user_data User object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_customer', $response, $user_data, $request );
}
/**
* Update customer meta fields.
*
* @param WC_Customer $customer
* @param WP_REST_Request $request
*/
protected function update_customer_meta_fields( $customer, $request ) {
$schema = $this->get_item_schema();
// Customer first name.
if ( isset( $request['first_name'] ) ) {
$customer->set_first_name( wc_clean( $request['first_name'] ) );
}
// Customer last name.
if ( isset( $request['last_name'] ) ) {
$customer->set_last_name( wc_clean( $request['last_name'] ) );
}
// Customer billing address.
if ( isset( $request['billing'] ) ) {
foreach ( array_keys( $schema['properties']['billing']['properties'] ) as $field ) {
if ( isset( $request['billing'][ $field ] ) && is_callable( array( $customer, "set_billing_{$field}" ) ) ) {
$customer->{"set_billing_{$field}"}( $request['billing'][ $field ] );
}
}
}
// Customer shipping address.
if ( isset( $request['shipping'] ) ) {
foreach ( array_keys( $schema['properties']['shipping']['properties'] ) as $field ) {
if ( isset( $request['shipping'][ $field ] ) && is_callable( array( $customer, "set_shipping_{$field}" ) ) ) {
$customer->{"set_shipping_{$field}"}( $request['shipping'][ $field ] );
}
}
}
}
/**
* Prepare links for the request.
*
* @param WP_User $customer Customer object.
* @return array Links for the given customer.
*/
protected function prepare_links( $customer ) {
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $customer->ID ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
),
);
return $links;
}
/**
* Get the Customer's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'customer',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created' => array(
'description' => __( 'The date the customer was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( 'The date the customer was last modified, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'email' => array(
'description' => __( 'The email address for the customer.', 'woocommerce' ),
'type' => 'string',
'format' => 'email',
'context' => array( 'view', 'edit' ),
),
'first_name' => array(
'description' => __( 'Customer first name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'last_name' => array(
'description' => __( 'Customer last name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'username' => array(
'description' => __( 'Customer login name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_user',
),
),
'password' => array(
'description' => __( 'Customer password.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'edit' ),
),
'last_order' => array(
'description' => __( 'Last order data.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'properties' => array(
'id' => array(
'description' => __( 'Last order ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date' => array(
'description' => __( 'The date of the customer last order, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
'orders_count' => array(
'description' => __( 'Quantity of orders made by the customer.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'total_spent' => array(
'description' => __( 'Total amount spent.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'avatar_url' => array(
'description' => __( 'Avatar URL.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'billing' => array(
'description' => __( 'List of billing address data.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'first_name' => array(
'description' => __( 'First name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'last_name' => array(
'description' => __( 'Last name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'company' => array(
'description' => __( 'Company name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'address_1' => array(
'description' => __( 'Address line 1.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'address_2' => array(
'description' => __( 'Address line 2.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'city' => array(
'description' => __( 'City name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'state' => array(
'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'postcode' => array(
'description' => __( 'Postal code.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'country' => array(
'description' => __( 'ISO code of the country.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'email' => array(
'description' => __( 'Email address.', 'woocommerce' ),
'type' => 'string',
'format' => 'email',
'context' => array( 'view', 'edit' ),
),
'phone' => array(
'description' => __( 'Phone number.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
'shipping' => array(
'description' => __( 'List of shipping address data.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'first_name' => array(
'description' => __( 'First name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'last_name' => array(
'description' => __( 'Last name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'company' => array(
'description' => __( 'Company name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'address_1' => array(
'description' => __( 'Address line 1.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'address_2' => array(
'description' => __( 'Address line 2.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'city' => array(
'description' => __( 'City name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'state' => array(
'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'postcode' => array(
'description' => __( 'Postal code.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'country' => array(
'description' => __( 'ISO code of the country.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get role names.
*
* @return array
*/
protected function get_role_names() {
global $wp_roles;
return array_keys( $wp_roles->role_names );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
$params['context']['default'] = 'view';
$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(
'default' => 'asc',
'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
'enum' => array( 'asc', 'desc' ),
'sanitize_callback' => 'sanitize_key',
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
$params['orderby'] = array(
'default' => 'name',
'description' => __( 'Sort collection by object attribute.', 'woocommerce' ),
'enum' => array(
'id',
'include',
'name',
'registered_date',
),
'sanitize_callback' => 'sanitize_key',
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
$params['email'] = array(
'description' => __( 'Limit result set to resources with a specific email.', 'woocommerce' ),
'type' => 'string',
'format' => 'email',
'validate_callback' => 'rest_validate_request_arg',
);
$params['role'] = array(
'description' => __( 'Limit result set to resources with a specific role.', 'woocommerce' ),
'type' => 'string',
'default' => 'customer',
'enum' => array_merge( array( 'all' ), $this->get_role_names() ),
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
}

View File

@ -0,0 +1,439 @@
<?php
/**
* REST API Order Notes controller
*
* Handles requests to the /orders/<order_id>/notes endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Order Notes controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Order_Notes_V1_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'orders/(?P<order_id>[\d]+)/notes';
/**
* Post type.
*
* @var string
*/
protected $post_type = 'shop_order';
/**
* Register the routes for order notes.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
'args' => array(
'order_id' => array(
'description' => __( 'The order ID.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
'note' => array(
'type' => 'string',
'description' => __( 'Order note content.', 'woocommerce' ),
'required' => true,
),
) ),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
),
'order_id' => array(
'description' => __( 'The order ID.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
'type' => 'boolean',
'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
}
/**
* Check whether a given request has permission to read order notes.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_post_permissions( $this->post_type, 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access create order notes.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function create_item_permissions_check( $request ) {
if ( ! wc_rest_check_post_permissions( $this->post_type, 'create' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to read a order note.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
$order = wc_get_order( (int) $request['order_id'] );
if ( $order && ! wc_rest_check_post_permissions( $this->post_type, 'read', $order->get_id() ) ) {
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 delete a order note.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function delete_item_permissions_check( $request ) {
$order = wc_get_order( (int) $request['order_id'] );
if ( $order && ! wc_rest_check_post_permissions( $this->post_type, 'delete', $order->get_id() ) ) {
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 order notes from an order.
*
* @param WP_REST_Request $request
*
* @return array|WP_Error
*/
public function get_items( $request ) {
$order = wc_get_order( (int) $request['order_id'] );
if ( ! $order || $this->post_type !== $order->get_type() ) {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid order ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$args = array(
'post_id' => $order->get_id(),
'approve' => 'approve',
'type' => 'order_note',
);
remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
$notes = get_comments( $args );
add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
$data = array();
foreach ( $notes as $note ) {
$order_note = $this->prepare_item_for_response( $note, $request );
$order_note = $this->prepare_response_for_collection( $order_note );
$data[] = $order_note;
}
return rest_ensure_response( $data );
}
/**
* Create a single order note.
*
* @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 ) );
}
$order = wc_get_order( (int) $request['order_id'] );
if ( ! $order || $this->post_type !== $order->get_type() ) {
return new WP_Error( 'woocommerce_rest_order_invalid_id', __( 'Invalid order ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
// Create the note.
$note_id = $order->add_order_note( $request['note'], $request['customer_note'] );
if ( ! $note_id ) {
return new WP_Error( 'woocommerce_api_cannot_create_order_note', __( 'Cannot create order note, please try again.', 'woocommerce' ), array( 'status' => 500 ) );
}
$note = get_comment( $note_id );
$this->update_additional_fields_for_object( $note, $request );
/**
* Fires after a order note is created or updated via the REST API.
*
* @param WP_Comment $note New order note object.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating item, false when updating.
*/
do_action( 'woocommerce_rest_insert_order_note', $note, $request, true );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $note, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, str_replace( '(?P<order_id>[\d]+)', $order->get_id(), $this->rest_base ), $note_id ) ) );
return $response;
}
/**
* Get a single order note.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$id = (int) $request['id'];
$order = wc_get_order( (int) $request['order_id'] );
if ( ! $order || $this->post_type !== $order->get_type() ) {
return new WP_Error( 'woocommerce_rest_order_invalid_id', __( 'Invalid order ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$note = get_comment( $id );
if ( empty( $id ) || empty( $note ) || intval( $note->comment_post_ID ) !== intval( $order->get_id() ) ) {
return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$order_note = $this->prepare_item_for_response( $note, $request );
$response = rest_ensure_response( $order_note );
return $response;
}
/**
* Delete a single order note.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function delete_item( $request ) {
$id = (int) $request['id'];
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
// We don't support trashing for this type, error out.
if ( ! $force ) {
return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Webhooks do not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
}
$order = wc_get_order( (int) $request['order_id'] );
if ( ! $order || $this->post_type !== $order->get_type() ) {
return new WP_Error( 'woocommerce_rest_order_invalid_id', __( 'Invalid order ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$note = get_comment( $id );
if ( empty( $id ) || empty( $note ) || intval( $note->comment_post_ID ) !== intval( $order->get_id() ) ) {
return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $note, $request );
$result = wc_delete_order_note( $note->comment_ID );
if ( ! $result ) {
return new WP_Error( 'woocommerce_rest_cannot_delete', sprintf( __( 'The %s cannot be deleted.', 'woocommerce' ), 'order_note' ), array( 'status' => 500 ) );
}
/**
* Fires after a order note is deleted or trashed via the REST API.
*
* @param WP_Comment $note The deleted or trashed order note.
* @param WP_REST_Response $response The response data.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( 'woocommerce_rest_delete_order_note', $note, $response, $request );
return $response;
}
/**
* Prepare a single order note output for response.
*
* @param WP_Comment $note Order note object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $note, $request ) {
$data = array(
'id' => (int) $note->comment_ID,
'date_created' => wc_rest_prepare_date_response( $note->comment_date_gmt ),
'note' => $note->comment_content,
'customer_note' => (bool) get_comment_meta( $note->comment_ID, 'is_customer_note', true ),
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $note ) );
/**
* Filter order note object returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param WP_Comment $note Order note object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_order_note', $response, $note, $request );
}
/**
* Prepare links for the request.
*
* @param WP_Comment $note Delivery order_note object.
* @return array Links for the given order note.
*/
protected function prepare_links( $note ) {
$order_id = (int) $note->comment_post_ID;
$base = str_replace( '(?P<order_id>[\d]+)', $order_id, $this->rest_base );
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $note->comment_ID ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ),
),
'up' => array(
'href' => rest_url( sprintf( '/%s/orders/%d', $this->namespace, $order_id ) ),
),
);
return $links;
}
/**
* Get the Order Notes schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'order_note',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created' => array(
'description' => __( "The date the order note was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'note' => array(
'description' => __( 'Order note.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'customer_note' => array(
'description' => __( 'Shows/define if the note is only for reference or for the customer (the user will be notified).', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
}

View File

@ -0,0 +1,530 @@
<?php
/**
* REST API Order Refunds controller
*
* Handles requests to the /orders/<order_id>/refunds endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Order Refunds controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Orders_V1_Controller
*/
class WC_REST_Order_Refunds_V1_Controller extends WC_REST_Orders_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'orders/(?P<order_id>[\d]+)/refunds';
/**
* Post type.
*
* @var string
*/
protected $post_type = 'shop_order_refund';
/**
* Order refunds actions.
*/
public function __construct() {
add_filter( "woocommerce_rest_{$this->post_type}_trashable", '__return_false' );
add_filter( "woocommerce_rest_{$this->post_type}_query", array( $this, 'query_args' ), 10, 2 );
}
/**
* Register the routes for order refunds.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
'args' => array(
'order_id' => array(
'description' => __( 'The order ID.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
'args' => array(
'order_id' => array(
'description' => __( 'The order ID.', 'woocommerce' ),
'type' => 'integer',
),
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => true,
'type' => 'boolean',
'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
}
/**
* Prepare a single order refund output for response.
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
*
* @return WP_Error|WP_REST_Response
*/
public function prepare_item_for_response( $post, $request ) {
$order = wc_get_order( (int) $request['order_id'] );
if ( ! $order ) {
return new WP_Error( 'woocommerce_rest_invalid_order_id', __( 'Invalid order ID.', 'woocommerce' ), 404 );
}
$refund = wc_get_order( $post );
if ( ! $refund || $refund->get_parent_id() !== $order->get_id() ) {
return new WP_Error( 'woocommerce_rest_invalid_order_refund_id', __( 'Invalid order refund ID.', 'woocommerce' ), 404 );
}
$dp = is_null( $request['dp'] ) ? wc_get_price_decimals() : absint( $request['dp'] );
$data = array(
'id' => $refund->get_id(),
'date_created' => wc_rest_prepare_date_response( $refund->get_date_created() ),
'amount' => wc_format_decimal( $refund->get_amount(), $dp ),
'reason' => $refund->get_reason(),
'line_items' => array(),
);
// Add line items.
foreach ( $refund->get_items() as $item_id => $item ) {
$product = $refund->get_product_from_item( $item );
$product_id = 0;
$variation_id = 0;
$product_sku = null;
// Check if the product exists.
if ( is_object( $product ) ) {
$product_id = $item->get_product_id();
$variation_id = $item->get_variation_id();
$product_sku = $product->get_sku();
}
$item_meta = array();
$hideprefix = 'true' === $request['all_item_meta'] ? null : '_';
foreach ( $item->get_formatted_meta_data( $hideprefix, true ) as $meta_key => $formatted_meta ) {
$item_meta[] = array(
'key' => $formatted_meta->key,
'label' => $formatted_meta->display_key,
'value' => wc_clean( $formatted_meta->display_value ),
);
}
$line_item = array(
'id' => $item_id,
'name' => $item['name'],
'sku' => $product_sku,
'product_id' => (int) $product_id,
'variation_id' => (int) $variation_id,
'quantity' => wc_stock_amount( $item['qty'] ),
'tax_class' => ! empty( $item['tax_class'] ) ? $item['tax_class'] : '',
'price' => wc_format_decimal( $refund->get_item_total( $item, false, false ), $dp ),
'subtotal' => wc_format_decimal( $refund->get_line_subtotal( $item, false, false ), $dp ),
'subtotal_tax' => wc_format_decimal( $item['line_subtotal_tax'], $dp ),
'total' => wc_format_decimal( $refund->get_line_total( $item, false, false ), $dp ),
'total_tax' => wc_format_decimal( $item['line_tax'], $dp ),
'taxes' => array(),
'meta' => $item_meta,
);
$item_line_taxes = maybe_unserialize( $item['line_tax_data'] );
if ( isset( $item_line_taxes['total'] ) ) {
$line_tax = array();
foreach ( $item_line_taxes['total'] as $tax_rate_id => $tax ) {
$line_tax[ $tax_rate_id ] = array(
'id' => $tax_rate_id,
'total' => $tax,
'subtotal' => '',
);
}
foreach ( $item_line_taxes['subtotal'] as $tax_rate_id => $tax ) {
$line_tax[ $tax_rate_id ]['subtotal'] = $tax;
}
$line_item['taxes'] = array_values( $line_tax );
}
$data['line_items'][] = $line_item;
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $refund, $request ) );
/**
* Filter the data for a response.
*
* The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
* prepared for the response.
*
* @param WP_REST_Response $response The response object.
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "woocommerce_rest_prepare_{$this->post_type}", $response, $post, $request );
}
/**
* Prepare links for the request.
*
* @param WC_Order_Refund $refund Comment object.
* @param WP_REST_Request $request Request object.
* @return array Links for the given order refund.
*/
protected function prepare_links( $refund, $request ) {
$order_id = $refund->get_parent_id();
$base = str_replace( '(?P<order_id>[\d]+)', $order_id, $this->rest_base );
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $refund->get_id() ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ),
),
'up' => array(
'href' => rest_url( sprintf( '/%s/orders/%d', $this->namespace, $order_id ) ),
),
);
return $links;
}
/**
* Query args.
*
* @param array $args Request args.
* @param WP_REST_Request $request Request object.
* @return array
*/
public function query_args( $args, $request ) {
$args['post_status'] = array_keys( wc_get_order_statuses() );
$args['post_parent__in'] = array( absint( $request['order_id'] ) );
return $args;
}
/**
* 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 ) );
}
$order_data = get_post( (int) $request['order_id'] );
if ( empty( $order_data ) ) {
return new WP_Error( 'woocommerce_rest_invalid_order', __( 'Order is invalid', 'woocommerce' ), 400 );
}
if ( 0 > $request['amount'] ) {
return new WP_Error( 'woocommerce_rest_invalid_order_refund', __( 'Refund amount must be greater than zero.', 'woocommerce' ), 400 );
}
// Create the refund.
$refund = wc_create_refund( array(
'order_id' => $order_data->ID,
'amount' => $request['amount'],
'reason' => empty( $request['reason'] ) ? null : $request['reason'],
'refund_payment' => is_bool( $request['api_refund'] ) ? $request['api_refund'] : true,
'restock_items' => true,
) );
if ( is_wp_error( $refund ) ) {
return new WP_Error( 'woocommerce_rest_cannot_create_order_refund', $refund->get_error_message(), 500 );
}
if ( ! $refund ) {
return new WP_Error( 'woocommerce_rest_cannot_create_order_refund', __( 'Cannot create order refund, please try again.', 'woocommerce' ), 500 );
}
$post = get_post( $refund->get_id() );
$this->update_additional_fields_for_object( $post, $request );
/**
* Fires after a single item is created or updated via the REST API.
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating item, false when updating.
*/
do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, true );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $post, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post->ID ) ) );
return $response;
}
/**
* Get the Order's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => $this->post_type,
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created' => array(
'description' => __( "The date the order refund was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'amount' => array(
'description' => __( 'Refund amount.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'reason' => array(
'description' => __( 'Reason for refund.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'line_items' => array(
'description' => __( 'Line items data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Item ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Product name.', 'woocommerce' ),
'type' => 'mixed',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'sku' => array(
'description' => __( 'Product SKU.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'product_id' => array(
'description' => __( 'Product ID.', 'woocommerce' ),
'type' => 'mixed',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'variation_id' => array(
'description' => __( 'Variation ID, if applicable.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'quantity' => array(
'description' => __( 'Quantity ordered.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'tax_class' => array(
'description' => __( 'Tax class of product.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'price' => array(
'description' => __( 'Product price.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'subtotal' => array(
'description' => __( 'Line subtotal (before discounts).', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'subtotal_tax' => array(
'description' => __( 'Line subtotal tax (before discounts).', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'total' => array(
'description' => __( 'Line total (after discounts).', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'total_tax' => array(
'description' => __( 'Line total tax (after discounts).', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'taxes' => array(
'description' => __( 'Line taxes.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Tax rate ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'total' => array(
'description' => __( 'Tax total.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'subtotal' => array(
'description' => __( 'Tax subtotal.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
),
'meta' => array(
'description' => __( 'Line item meta data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'items' => array(
'type' => 'object',
'properties' => array(
'key' => array(
'description' => __( 'Meta key.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'label' => array(
'description' => __( 'Meta label.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ),
'type' => 'mixed',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
),
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
$params['dp'] = array(
'default' => wc_get_price_decimals(),
'description' => __( 'Number of decimal points to use in each resource.', 'woocommerce' ),
'type' => 'integer',
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,241 @@
<?php
/**
* REST API Product Attribute Terms controller
*
* Handles requests to the products/attributes/<attribute_id>/terms endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Product Attribute Terms controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Terms_Controller
*/
class WC_REST_Product_Attribute_Terms_V1_Controller extends WC_REST_Terms_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'products/attributes/(?P<attribute_id>[\d]+)/terms';
/**
* Register the routes for terms.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base,
array(
'args' => array(
'attribute_id' => array(
'description' => __( 'Unique identifier for the attribute of the terms.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
'name' => array(
'type' => 'string',
'description' => __( 'Name for the resource.', 'woocommerce' ),
'required' => true,
),
) ),
),
'schema' => array( $this, 'get_public_item_schema' ),
));
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
),
'attribute_id' => array(
'description' => __( 'Unique identifier for the attribute of the terms.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
'type' => 'boolean',
'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array(
'args' => array(
'attribute_id' => array(
'description' => __( 'Unique identifier for the attribute of the terms.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'batch_items' ),
'permission_callback' => array( $this, 'batch_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_batch_schema' ),
) );
}
/**
* Prepare a single product attribute term output for response.
*
* @param WP_Term $item Term object.
* @param WP_REST_Request $request
* @return WP_REST_Response $response
*/
public function prepare_item_for_response( $item, $request ) {
// Get term order.
$menu_order = get_term_meta( $item->term_id, 'order_' . $this->taxonomy, true );
$data = array(
'id' => (int) $item->term_id,
'name' => $item->name,
'slug' => $item->slug,
'description' => $item->description,
'menu_order' => (int) $menu_order,
'count' => (int) $item->count,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item, $request ) );
/**
* Filter a term item returned from the API.
*
* Allows modification of the term data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $item The original term object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( "woocommerce_rest_prepare_{$this->taxonomy}", $response, $item, $request );
}
/**
* Update term meta fields.
*
* @param WP_Term $term
* @param WP_REST_Request $request
* @return bool|WP_Error
*/
protected function update_term_meta_fields( $term, $request ) {
$id = (int) $term->term_id;
update_term_meta( $id, 'order_' . $this->taxonomy, $request['menu_order'] );
return true;
}
/**
* Get the Attribute Term's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'product_attribute_term',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Term name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource unique to its type.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_title',
),
),
'description' => array(
'description' => __( 'HTML description of the resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'wp_filter_post_kses',
),
),
'menu_order' => array(
'description' => __( 'Menu order, used to custom sort the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'count' => array(
'description' => __( 'Number of published products for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,592 @@
<?php
/**
* REST API Product Attributes controller
*
* Handles requests to the products/attributes endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Product Attributes controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'products/attributes';
/**
* Attribute name.
*
* @var string
*/
protected $attribute = '';
/**
* Register the routes for product attributes.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
'name' => array(
'description' => __( 'Name for the resource.', 'woocommerce' ),
'type' => 'string',
'required' => true,
),
) ),
),
'schema' => array( $this, 'get_public_item_schema' ),
));
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => true,
'type' => 'boolean',
'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'batch_items' ),
'permission_callback' => array( $this, 'batch_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_batch_schema' ),
) );
}
/**
* Check if a given request has access to read the attributes.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'attributes', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to create a attribute.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function create_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'attributes', 'create' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you cannot create new resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to read a attribute.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
if ( ! $this->get_taxonomy( $request ) ) {
return new WP_Error( 'woocommerce_rest_taxonomy_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
}
if ( ! wc_rest_check_manager_permissions( 'attributes', 'read' ) ) {
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 a attribute.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function update_item_permissions_check( $request ) {
if ( ! $this->get_taxonomy( $request ) ) {
return new WP_Error( 'woocommerce_rest_taxonomy_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
}
if ( ! wc_rest_check_manager_permissions( 'attributes', 'edit' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_update', __( 'Sorry, you cannot update resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to delete a attribute.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function delete_item_permissions_check( $request ) {
if ( ! $this->get_taxonomy( $request ) ) {
return new WP_Error( 'woocommerce_rest_taxonomy_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
}
if ( ! wc_rest_check_manager_permissions( 'attributes', 'delete' ) ) {
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;
}
/**
* Check if a given request has access batch create, update and delete items.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function batch_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'attributes', 'batch' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get all attributes.
*
* @param WP_REST_Request $request
* @return array
*/
public function get_items( $request ) {
$attributes = wc_get_attribute_taxonomies();
$data = array();
foreach ( $attributes as $attribute_obj ) {
$attribute = $this->prepare_item_for_response( $attribute_obj, $request );
$attribute = $this->prepare_response_for_collection( $attribute );
$data[] = $attribute;
}
$response = rest_ensure_response( $data );
// This API call always returns all product attributes due to retrieval from the object cache.
$response->header( 'X-WP-Total', count( $data ) );
$response->header( 'X-WP-TotalPages', 1 );
return $response;
}
/**
* Create a single attribute.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Request|WP_Error
*/
public function create_item( $request ) {
global $wpdb;
$id = wc_create_attribute( array(
'name' => $request['name'],
'slug' => wc_sanitize_taxonomy_name( stripslashes( $request['slug'] ) ),
'type' => ! empty( $request['type'] ) ? $request['type'] : 'select',
'order_by' => ! empty( $request['order_by'] ) ? $request['order_by'] : 'menu_order',
'has_archives' => true === $request['has_archives'],
) );
// Checks for errors.
if ( is_wp_error( $id ) ) {
return new WP_Error( 'woocommerce_rest_cannot_create', $id->get_error_message(), array( 'status' => 400 ) );
}
$attribute = $this->get_attribute( $id );
if ( is_wp_error( $attribute ) ) {
return $attribute;
}
$this->update_additional_fields_for_object( $attribute, $request );
/**
* Fires after a single product attribute is created or updated via the REST API.
*
* @param stdObject $attribute Inserted attribute object.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating attribute, false when updating.
*/
do_action( 'woocommerce_rest_insert_product_attribute', $attribute, $request, true );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $attribute, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( '/' . $this->namespace . '/' . $this->rest_base . '/' . $attribute->attribute_id ) );
return $response;
}
/**
* Get a single attribute.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Request|WP_Error
*/
public function get_item( $request ) {
$attribute = $this->get_attribute( (int) $request['id'] );
if ( is_wp_error( $attribute ) ) {
return $attribute;
}
$response = $this->prepare_item_for_response( $attribute, $request );
return rest_ensure_response( $response );
}
/**
* Update a single term from a taxonomy.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Request|WP_Error
*/
public function update_item( $request ) {
global $wpdb;
$id = (int) $request['id'];
$edited = wc_update_attribute( $id, array(
'name' => $request['name'],
'slug' => wc_sanitize_taxonomy_name( stripslashes( $request['slug'] ) ),
'type' => $request['type'],
'order_by' => $request['order_by'],
'has_archives' => $request['has_archives'],
) );
// Checks for errors.
if ( is_wp_error( $edited ) ) {
return new WP_Error( 'woocommerce_rest_cannot_edit', $edited->get_error_message(), array( 'status' => 400 ) );
}
$attribute = $this->get_attribute( $id );
if ( is_wp_error( $attribute ) ) {
return $attribute;
}
$this->update_additional_fields_for_object( $attribute, $request );
/**
* Fires after a single product attribute is created or updated via the REST API.
*
* @param stdObject $attribute Inserted attribute object.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating attribute, false when updating.
*/
do_action( 'woocommerce_rest_insert_product_attribute', $attribute, $request, false );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $attribute, $request );
return rest_ensure_response( $response );
}
/**
* Delete a single attribute.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function delete_item( $request ) {
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
// We don't support trashing for this type, error out.
if ( ! $force ) {
return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Resource does not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
}
$attribute = $this->get_attribute( (int) $request['id'] );
if ( is_wp_error( $attribute ) ) {
return $attribute;
}
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $attribute, $request );
$deleted = wc_delete_attribute( $attribute->attribute_id );
if ( false === $deleted ) {
return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
}
/**
* Fires after a single attribute is deleted via the REST API.
*
* @param stdObject $attribute The deleted attribute.
* @param WP_REST_Response $response The response data.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( 'woocommerce_rest_delete_product_attribute', $attribute, $response, $request );
return $response;
}
/**
* Prepare a single product attribute output for response.
*
* @param obj $item Term object.
* @param WP_REST_Request $request
* @return WP_REST_Response $response
*/
public function prepare_item_for_response( $item, $request ) {
$data = array(
'id' => (int) $item->attribute_id,
'name' => $item->attribute_label,
'slug' => wc_attribute_taxonomy_name( $item->attribute_name ),
'type' => $item->attribute_type,
'order_by' => $item->attribute_orderby,
'has_archives' => (bool) $item->attribute_public,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item ) );
/**
* Filter a attribute item returned from the API.
*
* Allows modification of the product attribute data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $item The original attribute object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_product_attribute', $response, $item, $request );
}
/**
* Prepare links for the request.
*
* @param object $attribute Attribute object.
* @return array Links for the given attribute.
*/
protected function prepare_links( $attribute ) {
$base = '/' . $this->namespace . '/' . $this->rest_base;
$links = array(
'self' => array(
'href' => rest_url( trailingslashit( $base ) . $attribute->attribute_id ),
),
'collection' => array(
'href' => rest_url( $base ),
),
);
return $links;
}
/**
* Get the Attribute's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'product_attribute',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Attribute name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource unique to its type.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_title',
),
),
'type' => array(
'description' => __( 'Type of attribute.', 'woocommerce' ),
'type' => 'string',
'default' => 'select',
'enum' => array_keys( wc_get_attribute_types() ),
'context' => array( 'view', 'edit' ),
),
'order_by' => array(
'description' => __( 'Default sort order.', 'woocommerce' ),
'type' => 'string',
'default' => 'menu_order',
'enum' => array( 'menu_order', 'name', 'name_num', 'id' ),
'context' => array( 'view', 'edit' ),
),
'has_archives' => array(
'description' => __( 'Enable/Disable attribute archives.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections
*
* @return array
*/
public function get_collection_params() {
$params = array();
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
return $params;
}
/**
* Get attribute name.
*
* @param WP_REST_Request $request Full details about the request.
* @return string
*/
protected function get_taxonomy( $request ) {
if ( '' !== $this->attribute ) {
return $this->attribute;
}
if ( $request['id'] ) {
$name = wc_attribute_taxonomy_name_by_id( (int) $request['id'] );
$this->attribute = $name;
}
return $this->attribute;
}
/**
* Get attribute data.
*
* @param int $id Attribute ID.
* @return stdClass|WP_Error
*/
protected function get_attribute( $id ) {
global $wpdb;
$attribute = $wpdb->get_row( $wpdb->prepare( "
SELECT *
FROM {$wpdb->prefix}woocommerce_attribute_taxonomies
WHERE attribute_id = %d
", $id ) );
if ( is_wp_error( $attribute ) || is_null( $attribute ) ) {
return new WP_Error( 'woocommerce_rest_attribute_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
}
return $attribute;
}
/**
* Validate attribute slug.
*
* @deprecated 3.2.0
* @param string $slug
* @param bool $new_data
* @return bool|WP_Error
*/
protected function validate_attribute_slug( $slug, $new_data = true ) {
if ( strlen( $slug ) >= 28 ) {
return new WP_Error( 'woocommerce_rest_invalid_product_attribute_slug_too_long', sprintf( __( 'Slug "%s" is too long (28 characters max). Shorten it, please.', 'woocommerce' ), $slug ), array( 'status' => 400 ) );
} elseif ( wc_check_if_attribute_name_is_reserved( $slug ) ) {
return new WP_Error( 'woocommerce_rest_invalid_product_attribute_slug_reserved_name', sprintf( __( 'Slug "%s" is not allowed because it is a reserved term. Change it, please.', 'woocommerce' ), $slug ), array( 'status' => 400 ) );
} elseif ( $new_data && taxonomy_exists( wc_attribute_taxonomy_name( $slug ) ) ) {
return new WP_Error( 'woocommerce_rest_invalid_product_attribute_slug_already_exists', sprintf( __( 'Slug "%s" is already in use. Change it, please.', 'woocommerce' ), $slug ), array( 'status' => 400 ) );
}
return true;
}
/**
* Schedule to flush rewrite rules.
*
* @deprecated 3.2.0
* @since 3.0.0
*/
protected function flush_rewrite_rules() {
wp_schedule_single_event( time(), 'woocommerce_flush_rewrite_rules' );
}
}

View File

@ -0,0 +1,271 @@
<?php
/**
* REST API Product Categories controller
*
* Handles requests to the products/categories endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Product Categories controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Terms_Controller
*/
class WC_REST_Product_Categories_V1_Controller extends WC_REST_Terms_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'products/categories';
/**
* Taxonomy.
*
* @var string
*/
protected $taxonomy = 'product_cat';
/**
* Prepare a single product category output for response.
*
* @param WP_Term $item Term object.
* @param WP_REST_Request $request Request instance.
* @return WP_REST_Response
*/
public function prepare_item_for_response( $item, $request ) {
// Get category display type.
$display_type = get_term_meta( $item->term_id, 'display_type', true );
// Get category order.
$menu_order = get_term_meta( $item->term_id, 'order', true );
$data = array(
'id' => (int) $item->term_id,
'name' => $item->name,
'slug' => $item->slug,
'parent' => (int) $item->parent,
'description' => $item->description,
'display' => $display_type ? $display_type : 'default',
'image' => null,
'menu_order' => (int) $menu_order,
'count' => (int) $item->count,
);
// Get category image.
$image_id = get_term_meta( $item->term_id, 'thumbnail_id', true );
if ( $image_id ) {
$attachment = get_post( $image_id );
$data['image'] = array(
'id' => (int) $image_id,
'date_created' => wc_rest_prepare_date_response( $attachment->post_date_gmt ),
'date_modified' => wc_rest_prepare_date_response( $attachment->post_modified_gmt ),
'src' => wp_get_attachment_url( $image_id ),
'title' => get_the_title( $attachment ),
'alt' => get_post_meta( $image_id, '_wp_attachment_image_alt', true ),
);
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item, $request ) );
/**
* Filter a term item returned from the API.
*
* Allows modification of the term data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $item The original term object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( "woocommerce_rest_prepare_{$this->taxonomy}", $response, $item, $request );
}
/**
* Update term meta fields.
*
* @param WP_Term $term Term object.
* @param WP_REST_Request $request Request instance.
* @return bool|WP_Error
*/
protected function update_term_meta_fields( $term, $request ) {
$id = (int) $term->term_id;
if ( isset( $request['display'] ) ) {
update_term_meta( $id, 'display_type', 'default' === $request['display'] ? '' : $request['display'] );
}
if ( isset( $request['menu_order'] ) ) {
update_term_meta( $id, 'order', $request['menu_order'] );
}
if ( isset( $request['image'] ) ) {
if ( empty( $request['image']['id'] ) && ! empty( $request['image']['src'] ) ) {
$upload = wc_rest_upload_image_from_url( esc_url_raw( $request['image']['src'] ) );
if ( is_wp_error( $upload ) ) {
return $upload;
}
$image_id = wc_rest_set_uploaded_image_as_attachment( $upload );
} else {
$image_id = isset( $request['image']['id'] ) ? absint( $request['image']['id'] ) : 0;
}
// Check if image_id is a valid image attachment before updating the term meta.
if ( $image_id && wp_attachment_is_image( $image_id ) ) {
update_term_meta( $id, 'thumbnail_id', $image_id );
// Set the image alt.
if ( ! empty( $request['image']['alt'] ) ) {
update_post_meta( $image_id, '_wp_attachment_image_alt', wc_clean( $request['image']['alt'] ) );
}
// Set the image title.
if ( ! empty( $request['image']['title'] ) ) {
wp_update_post( array(
'ID' => $image_id,
'post_title' => wc_clean( $request['image']['title'] ),
) );
}
} else {
delete_term_meta( $id, 'thumbnail_id' );
}
}
return true;
}
/**
* Get the Category schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => $this->taxonomy,
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Category name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource unique to its type.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_title',
),
),
'parent' => array(
'description' => __( 'The ID for the parent of the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'description' => array(
'description' => __( 'HTML description of the resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'wp_filter_post_kses',
),
),
'display' => array(
'description' => __( 'Category archive display type.', 'woocommerce' ),
'type' => 'string',
'default' => 'default',
'enum' => array( 'default', 'products', 'subcategories', 'both' ),
'context' => array( 'view', 'edit' ),
),
'image' => array(
'description' => __( 'Image data.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'id' => array(
'description' => __( 'Image ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'date_created' => array(
'description' => __( "The date the image was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the image was last modified, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'src' => array(
'description' => __( 'Image URL.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit' ),
),
'title' => array(
'description' => __( 'Image name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'alt' => array(
'description' => __( 'Image alternative text.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
'menu_order' => array(
'description' => __( 'Menu order, used to custom sort the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'count' => array(
'description' => __( 'Number of published products for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,578 @@
<?php
/**
* REST API Product Reviews Controller
*
* Handles requests to /products/<product_id>/reviews.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Product Reviews Controller Class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Product_Reviews_V1_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'products/(?P<product_id>[\d]+)/reviews';
/**
* Register the routes for product reviews.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
'args' => array(
'product_id' => array(
'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),
'type' => 'integer',
),
'id' => array(
'description' => __( 'Unique identifier for the variation.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
'review' => array(
'required' => true,
'type' => 'string',
'description' => __( 'Review content.', 'woocommerce' ),
),
'name' => array(
'required' => true,
'type' => 'string',
'description' => __( 'Name of the reviewer.', 'woocommerce' ),
),
'email' => array(
'required' => true,
'type' => 'string',
'description' => __( 'Email of the reviewer.', 'woocommerce' ),
),
) ),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
'args' => array(
'product_id' => array(
'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),
'type' => 'integer',
),
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
'type' => 'boolean',
'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
}
/**
* Check whether a given request has permission to read webhook deliveries.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_post_permissions( 'product', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to read a product review.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
$post = get_post( (int) $request['product_id'] );
if ( $post && ! wc_rest_check_post_permissions( 'product', 'read', $post->ID ) ) {
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 create a new product review.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function create_item_permissions_check( $request ) {
$post = get_post( (int) $request['product_id'] );
if ( $post && ! wc_rest_check_post_permissions( 'product', 'create', $post->ID ) ) {
return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to update a product review.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function update_item_permissions_check( $request ) {
$post = get_post( (int) $request['product_id'] );
if ( $post && ! wc_rest_check_post_permissions( 'product', 'edit', $post->ID ) ) {
return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you cannot edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to delete a product review.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function delete_item_permissions_check( $request ) {
$post = get_post( (int) $request['product_id'] );
if ( $post && ! wc_rest_check_post_permissions( 'product', 'delete', $post->ID ) ) {
return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you cannot delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get all reviews from a product.
*
* @param WP_REST_Request $request
*
* @return array|WP_Error
*/
public function get_items( $request ) {
$product_id = (int) $request['product_id'];
if ( 'product' !== get_post_type( $product_id ) ) {
return new WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$reviews = get_approved_comments( $product_id );
$data = array();
foreach ( $reviews as $review_data ) {
$review = $this->prepare_item_for_response( $review_data, $request );
$review = $this->prepare_response_for_collection( $review );
$data[] = $review;
}
return rest_ensure_response( $data );
}
/**
* Get a single product review.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$id = (int) $request['id'];
$product_id = (int) $request['product_id'];
if ( 'product' !== get_post_type( $product_id ) ) {
return new WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$review = get_comment( $id );
if ( empty( $id ) || empty( $review ) || intval( $review->comment_post_ID ) !== $product_id ) {
return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$delivery = $this->prepare_item_for_response( $review, $request );
$response = rest_ensure_response( $delivery );
return $response;
}
/**
* Create a product review.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function create_item( $request ) {
$product_id = (int) $request['product_id'];
if ( 'product' !== get_post_type( $product_id ) ) {
return new WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$prepared_review = $this->prepare_item_for_database( $request );
/**
* Filter a product review (comment) before it is inserted via the REST API.
*
* Allows modification of the comment right before it is inserted via `wp_insert_comment`.
*
* @param array $prepared_review The prepared comment data for `wp_insert_comment`.
* @param WP_REST_Request $request Request used to insert the comment.
*/
$prepared_review = apply_filters( 'rest_pre_insert_product_review', $prepared_review, $request );
$product_review_id = wp_insert_comment( $prepared_review );
if ( ! $product_review_id ) {
return new WP_Error( 'rest_product_review_failed_create', __( 'Creating product review failed.', 'woocommerce' ), array( 'status' => 500 ) );
}
update_comment_meta( $product_review_id, 'rating', ( ! empty( $request['rating'] ) ? $request['rating'] : '0' ) );
$product_review = get_comment( $product_review_id );
$this->update_additional_fields_for_object( $product_review, $request );
/**
* Fires after a single item is created or updated via the REST API.
*
* @param WP_Comment $product_review Inserted object.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating item, false when updating.
*/
do_action( "woocommerce_rest_insert_product_review", $product_review, $request, true );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $product_review, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$base = str_replace( '(?P<product_id>[\d]+)', $product_id, $this->rest_base );
$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $product_review_id ) ) );
return $response;
}
/**
* Update a single product review.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
$product_review_id = (int) $request['id'];
$product_id = (int) $request['product_id'];
if ( 'product' !== get_post_type( $product_id ) ) {
return new WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$review = get_comment( $product_review_id );
if ( empty( $product_review_id ) || empty( $review ) || intval( $review->comment_post_ID ) !== $product_id ) {
return new WP_Error( 'woocommerce_rest_product_review_invalid_id', __( 'Invalid resource ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$prepared_review = $this->prepare_item_for_database( $request );
$updated = wp_update_comment( $prepared_review );
if ( 0 === $updated ) {
return new WP_Error( 'rest_product_review_failed_edit', __( 'Updating product review failed.', 'woocommerce' ), array( 'status' => 500 ) );
}
if ( ! empty( $request['rating'] ) ) {
update_comment_meta( $product_review_id, 'rating', $request['rating'] );
}
$product_review = get_comment( $product_review_id );
$this->update_additional_fields_for_object( $product_review, $request );
/**
* Fires after a single item is created or updated via the REST API.
*
* @param WP_Comment $comment Inserted object.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating item, false when updating.
*/
do_action( "woocommerce_rest_insert_product_review", $product_review, $request, true );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $product_review, $request );
return rest_ensure_response( $response );
}
/**
* Delete a product review.
*
* @param WP_REST_Request $request Full details about the request
*
* @return bool|WP_Error|WP_REST_Response
*/
public function delete_item( $request ) {
$product_review_id = absint( is_array( $request['id'] ) ? $request['id']['id'] : $request['id'] );
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
$product_review = get_comment( $product_review_id );
if ( empty( $product_review_id ) || empty( $product_review->comment_ID ) || empty( $product_review->comment_post_ID ) ) {
return new WP_Error( 'woocommerce_rest_product_review_invalid_id', __( 'Invalid product review ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
/**
* Filter whether a product review is trashable.
*
* Return false to disable trash support for the product review.
*
* @param boolean $supports_trash Whether the object supports trashing.
* @param WP_Post $product_review The object being considered for trashing support.
*/
$supports_trash = apply_filters( 'rest_product_review_trashable', ( EMPTY_TRASH_DAYS > 0 ), $product_review );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $product_review, $request );
if ( $force ) {
$result = wp_delete_comment( $product_review_id, true );
} else {
if ( ! $supports_trash ) {
return new WP_Error( 'rest_trash_not_supported', __( 'The product review does not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
}
if ( 'trash' === $product_review->comment_approved ) {
return new WP_Error( 'rest_already_trashed', __( 'The comment has already been trashed.', 'woocommerce' ), array( 'status' => 410 ) );
}
$result = wp_trash_comment( $product_review->comment_ID );
}
if ( ! $result ) {
return new WP_Error( 'rest_cannot_delete', __( 'The product review cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
}
/**
* Fires after a product review is deleted via the REST API.
*
* @param object $product_review The deleted item.
* @param WP_REST_Response $response The response data.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( 'rest_delete_product_review', $product_review, $response, $request );
return $response;
}
/**
* Prepare a single product review output for response.
*
* @param WP_Comment $review Product review object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $review, $request ) {
$data = array(
'id' => (int) $review->comment_ID,
'date_created' => wc_rest_prepare_date_response( $review->comment_date_gmt ),
'review' => $review->comment_content,
'rating' => (int) get_comment_meta( $review->comment_ID, 'rating', true ),
'name' => $review->comment_author,
'email' => $review->comment_author_email,
'verified' => wc_review_is_from_verified_owner( $review->comment_ID ),
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $review, $request ) );
/**
* Filter product reviews object returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param WP_Comment $review Product review object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_product_review', $response, $review, $request );
}
/**
* Prepare a single product review to be inserted into the database.
*
* @param WP_REST_Request $request Request object.
* @return array|WP_Error $prepared_review
*/
protected function prepare_item_for_database( $request ) {
$prepared_review = array( 'comment_approved' => 1, 'comment_type' => 'review' );
if ( isset( $request['id'] ) ) {
$prepared_review['comment_ID'] = (int) $request['id'];
}
if ( isset( $request['review'] ) ) {
$prepared_review['comment_content'] = $request['review'];
}
if ( isset( $request['product_id'] ) ) {
$prepared_review['comment_post_ID'] = (int) $request['product_id'];
}
if ( isset( $request['name'] ) ) {
$prepared_review['comment_author'] = $request['name'];
}
if ( isset( $request['email'] ) ) {
$prepared_review['comment_author_email'] = $request['email'];
}
if ( isset( $request['date_created'] ) ) {
$prepared_review['comment_date'] = $request['date_created'];
}
if ( isset( $request['date_created_gmt'] ) ) {
$prepared_review['comment_date_gmt'] = $request['date_created_gmt'];
}
return apply_filters( 'rest_preprocess_product_review', $prepared_review, $request );
}
/**
* Prepare links for the request.
*
* @param WP_Comment $review Product review object.
* @param WP_REST_Request $request Request object.
* @return array Links for the given product review.
*/
protected function prepare_links( $review, $request ) {
$product_id = (int) $request['product_id'];
$base = str_replace( '(?P<product_id>[\d]+)', $product_id, $this->rest_base );
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $review->comment_ID ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ),
),
'up' => array(
'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product_id ) ),
),
);
return $links;
}
/**
* Get the Product Review's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'product_review',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'review' => array(
'description' => __( 'The content of the review.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'date_created' => array(
'description' => __( "The date the review was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'rating' => array(
'description' => __( 'Review rating (0 to 5).', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'name' => array(
'description' => __( 'Reviewer name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'email' => array(
'description' => __( 'Reviewer email.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'verified' => array(
'description' => __( 'Shows if the reviewer bought the product or not.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
}

View File

@ -0,0 +1,134 @@
<?php
/**
* REST API Product Shipping Classes controller
*
* Handles requests to the products/shipping_classes endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Product Shipping Classes controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Terms_Controller
*/
class WC_REST_Product_Shipping_Classes_V1_Controller extends WC_REST_Terms_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'products/shipping_classes';
/**
* Taxonomy.
*
* @var string
*/
protected $taxonomy = 'product_shipping_class';
/**
* Prepare a single product shipping class output for response.
*
* @param obj $item Term object.
* @param WP_REST_Request $request
* @return WP_REST_Response $response
*/
public function prepare_item_for_response( $item, $request ) {
$data = array(
'id' => (int) $item->term_id,
'name' => $item->name,
'slug' => $item->slug,
'description' => $item->description,
'count' => (int) $item->count,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item, $request ) );
/**
* Filter a term item returned from the API.
*
* Allows modification of the term data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $item The original term object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( "woocommerce_rest_prepare_{$this->taxonomy}", $response, $item, $request );
}
/**
* Get the Shipping Class schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => $this->taxonomy,
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Shipping class name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource unique to its type.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_title',
),
),
'description' => array(
'description' => __( 'HTML description of the resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'wp_filter_post_kses',
),
),
'count' => array(
'description' => __( 'Number of published products for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,134 @@
<?php
/**
* REST API Product Tags controller
*
* Handles requests to the products/tags endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Product Tags controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Terms_Controller
*/
class WC_REST_Product_Tags_V1_Controller extends WC_REST_Terms_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'products/tags';
/**
* Taxonomy.
*
* @var string
*/
protected $taxonomy = 'product_tag';
/**
* Prepare a single product tag output for response.
*
* @param obj $item Term object.
* @param WP_REST_Request $request
* @return WP_REST_Response $response
*/
public function prepare_item_for_response( $item, $request ) {
$data = array(
'id' => (int) $item->term_id,
'name' => $item->name,
'slug' => $item->slug,
'description' => $item->description,
'count' => (int) $item->count,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item, $request ) );
/**
* Filter a term item returned from the API.
*
* Allows modification of the term data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $item The original term object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( "woocommerce_rest_prepare_{$this->taxonomy}", $response, $item, $request );
}
/**
* Get the Tag's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => $this->taxonomy,
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Tag name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource unique to its type.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_title',
),
),
'description' => array(
'description' => __( 'HTML description of the resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'wp_filter_post_kses',
),
),
'count' => array(
'description' => __( 'Number of published products for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,397 @@
<?php
/**
* REST API Reports controller
*
* Handles requests to the reports/sales endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Report Sales controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Report_Sales_V1_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'reports/sales';
/**
* Report instance.
*
* @var WC_Admin_Report
*/
protected $report;
/**
* Register the routes for sales reports.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
}
/**
* Check whether a given request has permission to read report.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'reports', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get sales reports.
*
* @param WP_REST_Request $request
* @return array|WP_Error
*/
public function get_items( $request ) {
$data = array();
$item = $this->prepare_item_for_response( null, $request );
$data[] = $this->prepare_response_for_collection( $item );
return rest_ensure_response( $data );
}
/**
* Prepare a report sales object for serialization.
*
* @param null $_
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $_, $request ) {
// Set date filtering.
$filter = array(
'period' => $request['period'],
'date_min' => $request['date_min'],
'date_max' => $request['date_max'],
);
$this->setup_report( $filter );
// New customers.
$users_query = new WP_User_Query(
array(
'fields' => array( 'user_registered' ),
'role' => 'customer',
)
);
$customers = $users_query->get_results();
foreach ( $customers as $key => $customer ) {
if ( strtotime( $customer->user_registered ) < $this->report->start_date || strtotime( $customer->user_registered ) > $this->report->end_date ) {
unset( $customers[ $key ] );
}
}
$total_customers = count( $customers );
$report_data = $this->report->get_report_data();
$period_totals = array();
// Setup period totals by ensuring each period in the interval has data.
for ( $i = 0; $i <= $this->report->chart_interval; $i++ ) {
switch ( $this->report->chart_groupby ) {
case 'day' :
$time = date( 'Y-m-d', strtotime( "+{$i} DAY", $this->report->start_date ) );
break;
default :
$time = date( 'Y-m', strtotime( "+{$i} MONTH", $this->report->start_date ) );
break;
}
// Set the customer signups for each period.
$customer_count = 0;
foreach ( $customers as $customer ) {
if ( date( ( 'day' == $this->report->chart_groupby ) ? 'Y-m-d' : 'Y-m', strtotime( $customer->user_registered ) ) == $time ) {
$customer_count++;
}
}
$period_totals[ $time ] = array(
'sales' => wc_format_decimal( 0.00, 2 ),
'orders' => 0,
'items' => 0,
'tax' => wc_format_decimal( 0.00, 2 ),
'shipping' => wc_format_decimal( 0.00, 2 ),
'discount' => wc_format_decimal( 0.00, 2 ),
'customers' => $customer_count,
);
}
// add total sales, total order count, total tax and total shipping for each period
foreach ( $report_data->orders as $order ) {
$time = ( 'day' === $this->report->chart_groupby ) ? date( 'Y-m-d', strtotime( $order->post_date ) ) : date( 'Y-m', strtotime( $order->post_date ) );
if ( ! isset( $period_totals[ $time ] ) ) {
continue;
}
$period_totals[ $time ]['sales'] = wc_format_decimal( $order->total_sales, 2 );
$period_totals[ $time ]['tax'] = wc_format_decimal( $order->total_tax + $order->total_shipping_tax, 2 );
$period_totals[ $time ]['shipping'] = wc_format_decimal( $order->total_shipping, 2 );
}
foreach ( $report_data->order_counts as $order ) {
$time = ( 'day' === $this->report->chart_groupby ) ? date( 'Y-m-d', strtotime( $order->post_date ) ) : date( 'Y-m', strtotime( $order->post_date ) );
if ( ! isset( $period_totals[ $time ] ) ) {
continue;
}
$period_totals[ $time ]['orders'] = (int) $order->count;
}
// Add total order items for each period.
foreach ( $report_data->order_items as $order_item ) {
$time = ( 'day' === $this->report->chart_groupby ) ? date( 'Y-m-d', strtotime( $order_item->post_date ) ) : date( 'Y-m', strtotime( $order_item->post_date ) );
if ( ! isset( $period_totals[ $time ] ) ) {
continue;
}
$period_totals[ $time ]['items'] = (int) $order_item->order_item_count;
}
// Add total discount for each period.
foreach ( $report_data->coupons as $discount ) {
$time = ( 'day' === $this->report->chart_groupby ) ? date( 'Y-m-d', strtotime( $discount->post_date ) ) : date( 'Y-m', strtotime( $discount->post_date ) );
if ( ! isset( $period_totals[ $time ] ) ) {
continue;
}
$period_totals[ $time ]['discount'] = wc_format_decimal( $discount->discount_amount, 2 );
}
$sales_data = array(
'total_sales' => $report_data->total_sales,
'net_sales' => $report_data->net_sales,
'average_sales' => $report_data->average_sales,
'total_orders' => $report_data->total_orders,
'total_items' => $report_data->total_items,
'total_tax' => wc_format_decimal( $report_data->total_tax + $report_data->total_shipping_tax, 2 ),
'total_shipping' => $report_data->total_shipping,
'total_refunds' => $report_data->total_refunds,
'total_discount' => $report_data->total_coupons,
'totals_grouped_by' => $this->report->chart_groupby,
'totals' => $period_totals,
'total_customers' => $total_customers,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $sales_data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( array(
'about' => array(
'href' => rest_url( sprintf( '%s/reports', $this->namespace ) ),
),
) );
/**
* Filter a report sales returned from the API.
*
* Allows modification of the report sales data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param stdClass $data The original report object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_report_sales', $response, (object) $sales_data, $request );
}
/**
* Setup the report object and parse any date filtering.
*
* @param array $filter date filtering
*/
protected function setup_report( $filter ) {
include_once( WC()->plugin_path() . '/includes/admin/reports/class-wc-admin-report.php' );
include_once( WC()->plugin_path() . '/includes/admin/reports/class-wc-report-sales-by-date.php' );
$this->report = new WC_Report_Sales_By_Date();
if ( empty( $filter['period'] ) ) {
// Custom date range.
$filter['period'] = 'custom';
if ( ! empty( $filter['date_min'] ) || ! empty( $filter['date_max'] ) ) {
// Overwrite _GET to make use of WC_Admin_Report::calculate_current_range() for custom date ranges.
$_GET['start_date'] = $filter['date_min'];
$_GET['end_date'] = isset( $filter['date_max'] ) ? $filter['date_max'] : null;
} else {
// Default custom range to today.
$_GET['start_date'] = $_GET['end_date'] = date( 'Y-m-d', current_time( 'timestamp' ) );
}
} else {
$filter['period'] = empty( $filter['period'] ) ? 'week' : $filter['period'];
// Change "week" period to "7day".
if ( 'week' === $filter['period'] ) {
$filter['period'] = '7day';
}
}
$this->report->calculate_current_range( $filter['period'] );
}
/**
* Get the Report's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'sales_report',
'type' => 'object',
'properties' => array(
'total_sales' => array(
'description' => __( 'Gross sales in the period.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'net_sales' => array(
'description' => __( 'Net sales in the period.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'average_sales' => array(
'description' => __( 'Average net daily sales.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'total_orders' => array(
'description' => __( 'Total of orders placed.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'total_items' => array(
'description' => __( 'Total of items purchased.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'total_tax' => array(
'description' => __( 'Total charged for taxes.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'total_shipping' => array(
'description' => __( 'Total charged for shipping.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'total_refunds' => array(
'description' => __( 'Total of refunded orders.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'total_discount' => array(
'description' => __( 'Total of coupons used.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'totals_grouped_by' => array(
'description' => __( 'Group type.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'totals' => array(
'description' => __( 'Totals.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'array',
),
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
'period' => array(
'description' => __( 'Report period.', 'woocommerce' ),
'type' => 'string',
'enum' => array( 'week', 'month', 'last_month', 'year' ),
'validate_callback' => 'rest_validate_request_arg',
'sanitize_callback' => 'sanitize_text_field',
),
'date_min' => array(
/* translators: %s: date format */
'description' => sprintf( __( 'Return sales for a specific start date, the date need to be in the %s format.', 'woocommerce' ), 'YYYY-MM-DD' ),
'type' => 'string',
'format' => 'date',
'validate_callback' => 'wc_rest_validate_reports_request_arg',
'sanitize_callback' => 'sanitize_text_field',
),
'date_max' => array(
/* translators: %s: date format */
'description' => sprintf( __( 'Return sales for a specific end date, the date need to be in the %s format.', 'woocommerce' ), 'YYYY-MM-DD' ),
'type' => 'string',
'format' => 'date',
'validate_callback' => 'wc_rest_validate_reports_request_arg',
'sanitize_callback' => 'sanitize_text_field',
),
);
}
}

View File

@ -0,0 +1,174 @@
<?php
/**
* REST API Reports controller
*
* Handles requests to the reports/top_sellers endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Report Top Sellers controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Report_Sales_V1_Controller
*/
class WC_REST_Report_Top_Sellers_V1_Controller extends WC_REST_Report_Sales_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'reports/top_sellers';
/**
* Get sales reports.
*
* @param WP_REST_Request $request
* @return array|WP_Error
*/
public function get_items( $request ) {
// Set date filtering.
$filter = array(
'period' => $request['period'],
'date_min' => $request['date_min'],
'date_max' => $request['date_max'],
);
$this->setup_report( $filter );
$report_data = $this->report->get_order_report_data( array(
'data' => array(
'_product_id' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => '',
'name' => 'product_id',
),
'_qty' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => 'SUM',
'name' => 'order_item_qty',
),
),
'order_by' => 'order_item_qty DESC',
'group_by' => 'product_id',
'limit' => isset( $filter['limit'] ) ? absint( $filter['limit'] ) : 12,
'query_type' => 'get_results',
'filter_range' => true,
) );
$top_sellers = array();
foreach ( $report_data as $item ) {
$product = wc_get_product( $item->product_id );
if ( $product ) {
$top_sellers[] = array(
'name' => $product->get_name(),
'product_id' => (int) $item->product_id,
'quantity' => wc_stock_amount( $item->order_item_qty ),
);
}
}
$data = array();
foreach ( $top_sellers as $top_seller ) {
$item = $this->prepare_item_for_response( (object) $top_seller, $request );
$data[] = $this->prepare_response_for_collection( $item );
}
return rest_ensure_response( $data );
}
/**
* Prepare a report sales object for serialization.
*
* @param stdClass $top_seller
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $top_seller, $request ) {
$data = array(
'name' => $top_seller->name,
'product_id' => $top_seller->product_id,
'quantity' => $top_seller->quantity,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( array(
'about' => array(
'href' => rest_url( sprintf( '%s/reports', $this->namespace ) ),
),
'product' => array(
'href' => rest_url( sprintf( '/%s/products/%s', $this->namespace, $top_seller->product_id ) ),
),
) );
/**
* Filter a report top sellers returned from the API.
*
* Allows modification of the report top sellers data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param stdClass $top_seller The original report object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_report_top_sellers', $response, $top_seller, $request );
}
/**
* Get the Report's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'top_sellers_report',
'type' => 'object',
'properties' => array(
'name' => array(
'description' => __( 'Product name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'product_id' => array(
'description' => __( 'Product ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'quantity' => array(
'description' => __( 'Total number of purchases.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,184 @@
<?php
/**
* REST API Reports controller
*
* Handles requests to the reports endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Reports controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Reports_V1_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'reports';
/**
* Register the routes for reports.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
}
/**
* Check whether a given request has permission to read reports.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'reports', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get reports list.
*
* @since 3.5.0
* @return array
*/
protected function get_reports() {
return array(
array(
'slug' => 'sales',
'description' => __( 'List of sales reports.', 'woocommerce' ),
),
array(
'slug' => 'top_sellers',
'description' => __( 'List of top sellers products.', 'woocommerce' ),
),
);
}
/**
* Get all reports.
*
* @param WP_REST_Request $request
* @return array|WP_Error
*/
public function get_items( $request ) {
$data = array();
$reports = $this->get_reports();
foreach ( $reports as $report ) {
$item = $this->prepare_item_for_response( (object) $report, $request );
$data[] = $this->prepare_response_for_collection( $item );
}
return rest_ensure_response( $data );
}
/**
* Prepare a report object for serialization.
*
* @param stdClass $report Report data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $report, $request ) {
$data = array(
'slug' => $report->slug,
'description' => $report->description,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $report->slug ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
),
) );
/**
* Filter a report returned from the API.
*
* Allows modification of the report data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $report The original report object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_report', $response, $report, $request );
}
/**
* Get the Report's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'report',
'type' => 'object',
'properties' => array(
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'A human-readable description of the resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
}

View File

@ -0,0 +1,321 @@
<?php
/**
* REST API Tax Classes controller
*
* Handles requests to the /taxes/classes endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Tax Classes controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Tax_Classes_V1_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'taxes/classes';
/**
* Register the routes for tax classes.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<slug>\w[\w\s\-]*)', array(
'args' => array(
'slug' => array(
'description' => __( 'Unique slug for the resource.', 'woocommerce' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
'type' => 'boolean',
'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
}
/**
* Check whether a given request has permission to read tax classes.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access create tax classes.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function create_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'create' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access delete a tax.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function delete_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'delete' ) ) {
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 all tax classes.
*
* @param WP_REST_Request $request
* @return array
*/
public function get_items( $request ) {
$tax_classes = array();
// Add standard class.
$tax_classes[] = array(
'slug' => 'standard',
'name' => __( 'Standard rate', 'woocommerce' ),
);
$classes = WC_Tax::get_tax_classes();
foreach ( $classes as $class ) {
$tax_classes[] = array(
'slug' => sanitize_title( $class ),
'name' => $class,
);
}
$data = array();
foreach ( $tax_classes as $tax_class ) {
$class = $this->prepare_item_for_response( $tax_class, $request );
$class = $this->prepare_response_for_collection( $class );
$data[] = $class;
}
return rest_ensure_response( $data );
}
/**
* Create a single tax class.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function create_item( $request ) {
$tax_class = WC_Tax::create_tax_class( $request['name'] );
if ( is_wp_error( $tax_class ) ) {
return new WP_Error( 'woocommerce_rest_' . $tax_class->get_error_code(), $tax_class->get_error_message(), array( 'status' => 400 ) );
}
$this->update_additional_fields_for_object( $tax_class, $request );
/**
* Fires after a tax class is created or updated via the REST API.
*
* @param stdClass $tax_class Data used to create the tax class.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating tax class, false when updating tax class.
*/
do_action( 'woocommerce_rest_insert_tax_class', (object) $tax_class, $request, true );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $tax_class, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $tax_class['slug'] ) ) );
return $response;
}
/**
* Delete a single tax class.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function delete_item( $request ) {
global $wpdb;
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
// We don't support trashing for this type, error out.
if ( ! $force ) {
return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Taxes do not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
}
$tax_class = WC_Tax::get_tax_class_by( 'slug', sanitize_title( $request['slug'] ) );
$deleted = WC_Tax::delete_tax_class_by( 'slug', sanitize_title( $request['slug'] ) );
if ( ! $deleted ) {
return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource id.', 'woocommerce' ), array( 'status' => 400 ) );
}
if ( is_wp_error( $deleted ) ) {
return new WP_Error( 'woocommerce_rest_' . $deleted->get_error_code(), $deleted->get_error_message(), array( 'status' => 400 ) );
}
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $tax_class, $request );
/**
* Fires after a tax class is deleted via the REST API.
*
* @param stdClass $tax_class The tax data.
* @param WP_REST_Response $response The response returned from the API.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( 'woocommerce_rest_delete_tax', (object) $tax_class, $response, $request );
return $response;
}
/**
* Prepare a single tax class output for response.
*
* @param array $tax_class Tax class data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $tax_class, $request ) {
$data = $tax_class;
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links() );
/**
* Filter tax object returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param stdClass $tax_class Tax object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_tax', $response, (object) $tax_class, $request );
}
/**
* Prepare links for the request.
*
* @return array Links for the given tax class.
*/
protected function prepare_links() {
$links = array(
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
),
);
return $links;
}
/**
* Get the Tax Classes schema, conforming to JSON Schema
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'tax_class',
'type' => 'object',
'properties' => array(
'slug' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Tax class name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'required' => true,
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
}

View File

@ -0,0 +1,709 @@
<?php
/**
* REST API Taxes controller
*
* Handles requests to the /taxes endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Taxes controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Taxes_V1_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'taxes';
/**
* Register the routes for taxes.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
'type' => 'boolean',
'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'batch_items' ),
'permission_callback' => array( $this, 'batch_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_batch_schema' ),
) );
}
/**
* Check whether a given request has permission to read taxes.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access create taxes.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function create_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'create' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to read a tax.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
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 update a tax.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function update_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
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 delete a tax.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function delete_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'delete' ) ) {
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;
}
/**
* Check if a given request has access batch create, update and delete items.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function batch_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'batch' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get all taxes.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
global $wpdb;
$prepared_args = array();
$prepared_args['order'] = $request['order'];
$prepared_args['number'] = $request['per_page'];
if ( ! empty( $request['offset'] ) ) {
$prepared_args['offset'] = $request['offset'];
} else {
$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
}
$orderby_possibles = array(
'id' => 'tax_rate_id',
'order' => 'tax_rate_order',
);
$prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ];
$prepared_args['class'] = $request['class'];
/**
* Filter arguments, before passing to $wpdb->get_results(), when querying taxes via the REST API.
*
* @param array $prepared_args Array of arguments for $wpdb->get_results().
* @param WP_REST_Request $request The current request.
*/
$prepared_args = apply_filters( 'woocommerce_rest_tax_query', $prepared_args, $request );
$query = "
SELECT *
FROM {$wpdb->prefix}woocommerce_tax_rates
WHERE 1 = 1
";
// Filter by tax class.
if ( ! empty( $prepared_args['class'] ) ) {
$class = 'standard' !== $prepared_args['class'] ? sanitize_title( $prepared_args['class'] ) : '';
$query .= " AND tax_rate_class = '$class'";
}
// Order tax rates.
$order_by = sprintf( ' ORDER BY %s', sanitize_key( $prepared_args['orderby'] ) );
// Pagination.
$pagination = sprintf( ' LIMIT %d, %d', $prepared_args['offset'], $prepared_args['number'] );
// Query taxes.
$results = $wpdb->get_results( $query . $order_by . $pagination );
$taxes = array();
foreach ( $results as $tax ) {
$data = $this->prepare_item_for_response( $tax, $request );
$taxes[] = $this->prepare_response_for_collection( $data );
}
$response = rest_ensure_response( $taxes );
// Store pagination values for headers then unset for count query.
$per_page = (int) $prepared_args['number'];
$page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
// Query only for ids.
$wpdb->get_results( str_replace( 'SELECT *', 'SELECT tax_rate_id', $query ) );
// Calculate totals.
$total_taxes = (int) $wpdb->num_rows;
$response->header( 'X-WP-Total', (int) $total_taxes );
$max_pages = ceil( $total_taxes / $per_page );
$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;
}
/**
* Take tax data from the request and return the updated or newly created rate.
*
* @param WP_REST_Request $request Full details about the request.
* @param stdClass|null $current Existing tax object.
* @return object
*/
protected function create_or_update_tax( $request, $current = null ) {
$id = absint( isset( $request['id'] ) ? $request['id'] : 0 );
$data = array();
$fields = array(
'tax_rate_country',
'tax_rate_state',
'tax_rate',
'tax_rate_name',
'tax_rate_priority',
'tax_rate_compound',
'tax_rate_shipping',
'tax_rate_order',
'tax_rate_class',
);
foreach ( $fields as $field ) {
// Keys via API differ from the stored names returned by _get_tax_rate.
$key = 'tax_rate' === $field ? 'rate' : str_replace( 'tax_rate_', '', $field );
// Remove data that was not posted.
if ( ! isset( $request[ $key ] ) ) {
continue;
}
// Test new data against current data.
if ( $current && $current->$field === $request[ $key ] ) {
continue;
}
// Add to data array.
switch ( $key ) {
case 'tax_rate_priority' :
case 'tax_rate_compound' :
case 'tax_rate_shipping' :
case 'tax_rate_order' :
$data[ $field ] = absint( $request[ $key ] );
break;
case 'tax_rate_class' :
$data[ $field ] = 'standard' !== $request['tax_rate_class'] ? $request['tax_rate_class'] : '';
break;
default :
$data[ $field ] = wc_clean( $request[ $key ] );
break;
}
}
if ( $id ) {
WC_Tax::_update_tax_rate( $id, $data );
} else {
$id = WC_Tax::_insert_tax_rate( $data );
}
// Add locales.
if ( ! empty( $request['postcode'] ) ) {
WC_Tax::_update_tax_rate_postcodes( $id, wc_clean( $request['postcode'] ) );
}
if ( ! empty( $request['city'] ) ) {
WC_Tax::_update_tax_rate_cities( $id, wc_clean( $request['city'] ) );
}
return WC_Tax::_get_tax_rate( $id, OBJECT );
}
/**
* Create a single tax.
*
* @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'] ) ) {
return new WP_Error( 'woocommerce_rest_tax_exists', __( 'Cannot create existing resource.', 'woocommerce' ), array( 'status' => 400 ) );
}
$tax = $this->create_or_update_tax( $request );
$this->update_additional_fields_for_object( $tax, $request );
/**
* Fires after a tax is created or updated via the REST API.
*
* @param stdClass $tax Data used to create the tax.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating tax, false when updating tax.
*/
do_action( 'woocommerce_rest_insert_tax', $tax, $request, true );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $tax, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $tax->tax_rate_id ) ) );
return $response;
}
/**
* Get a single tax.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$id = (int) $request['id'];
$tax_obj = WC_Tax::_get_tax_rate( $id, OBJECT );
if ( empty( $id ) || empty( $tax_obj ) ) {
return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$tax = $this->prepare_item_for_response( $tax_obj, $request );
$response = rest_ensure_response( $tax );
return $response;
}
/**
* Update a single tax.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
$id = (int) $request['id'];
$tax_obj = WC_Tax::_get_tax_rate( $id, OBJECT );
if ( empty( $id ) || empty( $tax_obj ) ) {
return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$tax = $this->create_or_update_tax( $request, $tax_obj );
$this->update_additional_fields_for_object( $tax, $request );
/**
* Fires after a tax is created or updated via the REST API.
*
* @param stdClass $tax Data used to create the tax.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating tax, false when updating tax.
*/
do_action( 'woocommerce_rest_insert_tax', $tax, $request, false );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $tax, $request );
$response = rest_ensure_response( $response );
return $response;
}
/**
* Delete a single tax.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function delete_item( $request ) {
global $wpdb;
$id = (int) $request['id'];
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
// We don't support trashing for this type, error out.
if ( ! $force ) {
return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Taxes do not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
}
$tax = WC_Tax::_get_tax_rate( $id, OBJECT );
if ( empty( $id ) || empty( $tax ) ) {
return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource ID.', 'woocommerce' ), array( 'status' => 400 ) );
}
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $tax, $request );
WC_Tax::_delete_tax_rate( $id );
if ( 0 === $wpdb->rows_affected ) {
return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
}
/**
* Fires after a tax is deleted via the REST API.
*
* @param stdClass $tax The tax data.
* @param WP_REST_Response $response The response returned from the API.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( 'woocommerce_rest_delete_tax', $tax, $response, $request );
return $response;
}
/**
* Prepare a single tax output for response.
*
* @param stdClass $tax Tax object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $tax, $request ) {
global $wpdb;
$id = (int) $tax->tax_rate_id;
$data = array(
'id' => $id,
'country' => $tax->tax_rate_country,
'state' => $tax->tax_rate_state,
'postcode' => '',
'city' => '',
'rate' => $tax->tax_rate,
'name' => $tax->tax_rate_name,
'priority' => (int) $tax->tax_rate_priority,
'compound' => (bool) $tax->tax_rate_compound,
'shipping' => (bool) $tax->tax_rate_shipping,
'order' => (int) $tax->tax_rate_order,
'class' => $tax->tax_rate_class ? $tax->tax_rate_class : 'standard',
);
// Get locales from a tax rate.
$locales = $wpdb->get_results( $wpdb->prepare( "
SELECT location_code, location_type
FROM {$wpdb->prefix}woocommerce_tax_rate_locations
WHERE tax_rate_id = %d
", $id ) );
if ( ! is_wp_error( $tax ) && ! is_null( $tax ) ) {
foreach ( $locales as $locale ) {
$data[ $locale->location_type ] = $locale->location_code;
}
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $tax ) );
/**
* Filter tax object returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param stdClass $tax Tax object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_tax', $response, $tax, $request );
}
/**
* Prepare links for the request.
*
* @param stdClass $tax Tax object.
* @return array Links for the given tax.
*/
protected function prepare_links( $tax ) {
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $tax->tax_rate_id ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
),
);
return $links;
}
/**
* Get the Taxes schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'tax',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'country' => array(
'description' => __( 'Country ISO 3166 code.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'state' => array(
'description' => __( 'State code.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'postcode' => array(
'description' => __( 'Postcode / ZIP.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'city' => array(
'description' => __( 'City name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'rate' => array(
'description' => __( 'Tax rate.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'name' => array(
'description' => __( 'Tax rate name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'priority' => array(
'description' => __( 'Tax priority.', 'woocommerce' ),
'type' => 'integer',
'default' => 1,
'context' => array( 'view', 'edit' ),
),
'compound' => array(
'description' => __( 'Whether or not this is a compound rate.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'shipping' => array(
'description' => __( 'Whether or not this tax rate also gets applied to shipping.', 'woocommerce' ),
'type' => 'boolean',
'default' => true,
'context' => array( 'view', 'edit' ),
),
'order' => array(
'description' => __( 'Indicates the order that will appear in queries.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'class' => array(
'description' => __( 'Tax class.', 'woocommerce' ),
'type' => 'string',
'default' => 'standard',
'enum' => array_merge( array( 'standard' ), WC_Tax::get_tax_class_slugs() ),
'context' => array( 'view', 'edit' ),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$params = array();
$params['context'] = $this->get_context_param();
$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['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(
'default' => 'asc',
'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
'enum' => array( 'asc', 'desc' ),
'sanitize_callback' => 'sanitize_key',
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
$params['orderby'] = array(
'default' => 'order',
'description' => __( 'Sort collection by object attribute.', 'woocommerce' ),
'enum' => array(
'id',
'order',
),
'sanitize_callback' => 'sanitize_key',
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
$params['class'] = array(
'description' => __( 'Sort by tax class.', 'woocommerce' ),
'enum' => array_merge( array( 'standard' ), WC_Tax::get_tax_class_slugs() ),
'sanitize_callback' => 'sanitize_title',
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
}

View File

@ -0,0 +1,314 @@
<?php
/**
* REST API Webhooks controller
*
* Handles requests to the /webhooks/<webhook_id>/deliveries endpoint.
*
* @author WooThemes
* @category API
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Webhook Deliveries controller class.
*
* @deprecated 3.3.0 Webhooks deliveries logs now uses logging system.
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Webhook_Deliveries_V1_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'webhooks/(?P<webhook_id>[\d]+)/deliveries';
/**
* Register the routes for webhook deliveries.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
'args' => array(
'webhook_id' => array(
'description' => __( 'Unique identifier for the webhook.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
'args' => array(
'webhook_id' => array(
'description' => __( 'Unique identifier for the webhook.', 'woocommerce' ),
'type' => 'integer',
),
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
}
/**
* Check whether a given request has permission to read taxes.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'webhooks', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to read a tax.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'webhooks', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get all webhook deliveries.
*
* @param WP_REST_Request $request
*
* @return array|WP_Error
*/
public function get_items( $request ) {
$webhook = wc_get_webhook( (int) $request['webhook_id'] );
if ( empty( $webhook ) || is_null( $webhook ) ) {
return new WP_Error( 'woocommerce_rest_webhook_invalid_id', __( 'Invalid webhook ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$logs = array();
$data = array();
foreach ( $logs as $log ) {
$delivery = $this->prepare_item_for_response( (object) $log, $request );
$delivery = $this->prepare_response_for_collection( $delivery );
$data[] = $delivery;
}
return rest_ensure_response( $data );
}
/**
* Get a single webhook delivery.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$id = (int) $request['id'];
$webhook = wc_get_webhook( (int) $request['webhook_id'] );
if ( empty( $webhook ) || is_null( $webhook ) ) {
return new WP_Error( 'woocommerce_rest_webhook_invalid_id', __( 'Invalid webhook ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$log = array();
if ( empty( $id ) || empty( $log ) ) {
return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$delivery = $this->prepare_item_for_response( (object) $log, $request );
$response = rest_ensure_response( $delivery );
return $response;
}
/**
* Prepare a single webhook delivery output for response.
*
* @param stdClass $log Delivery log object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $log, $request ) {
$data = (array) $log;
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $log ) );
/**
* Filter webhook delivery object returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param stdClass $log Delivery log object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_webhook_delivery', $response, $log, $request );
}
/**
* Prepare links for the request.
*
* @param stdClass $log Delivery log object.
* @return array Links for the given webhook delivery.
*/
protected function prepare_links( $log ) {
$webhook_id = (int) $log->request_headers['X-WC-Webhook-ID'];
$base = str_replace( '(?P<webhook_id>[\d]+)', $webhook_id, $this->rest_base );
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $log->id ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ),
),
'up' => array(
'href' => rest_url( sprintf( '/%s/webhooks/%d', $this->namespace, $webhook_id ) ),
),
);
return $links;
}
/**
* Get the Webhook's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'webhook_delivery',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'duration' => array(
'description' => __( 'The delivery duration, in seconds.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'summary' => array(
'description' => __( 'A friendly summary of the response including the HTTP response code, message, and body.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'request_url' => array(
'description' => __( 'The URL where the webhook was delivered.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view' ),
'readonly' => true,
),
'request_headers' => array(
'description' => __( 'Request headers.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
'request_body' => array(
'description' => __( 'Request body.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'response_code' => array(
'description' => __( 'The HTTP response code from the receiving server.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'response_message' => array(
'description' => __( 'The HTTP response message from the receiving server.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'response_headers' => array(
'description' => __( 'Array of the response headers from the receiving server.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
'response_body' => array(
'description' => __( 'The response body from the receiving server.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'date_created' => array(
'description' => __( "The date the webhook delivery was logged, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
}

View File

@ -0,0 +1,763 @@
<?php
/**
* REST API Webhooks controller
*
* Handles requests to the /webhooks endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Webhooks controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Webhooks_V1_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'webhooks';
/**
* Post type.
*
* @var string
*/
protected $post_type = 'shop_webhook';
/**
* Register the routes for webhooks.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
'topic' => array(
'required' => true,
'type' => 'string',
'description' => __( 'Webhook topic.', 'woocommerce' ),
),
'delivery_url' => array(
'required' => true,
'type' => 'string',
'description' => __( 'Webhook delivery URL.', 'woocommerce' ),
),
) ),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
'type' => 'boolean',
'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'batch_items' ),
'permission_callback' => array( $this, 'batch_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_batch_schema' ),
) );
}
/**
* Check whether a given request has permission to read webhooks.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'webhooks', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access create webhooks.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function create_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'webhooks', 'create' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to read a webhook.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'webhooks', 'read' ) ) {
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 update a webhook.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function update_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'webhooks', 'edit' ) ) {
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 delete a webhook.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function delete_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'webhooks', 'delete' ) ) {
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;
}
/**
* Check if a given request has access batch create, update and delete items.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error
*/
public function batch_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'webhooks', 'batch' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get the default REST API version.
*
* @since 3.0.0
* @return string
*/
protected function get_default_api_version() {
return 'wp_api_v1';
}
/**
* Get all webhooks.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$args = array();
$args['order'] = $request['order'];
$args['orderby'] = $request['orderby'];
$args['status'] = 'all' === $request['status'] ? '' : $request['status'];
$args['include'] = implode( ',', $request['include'] );
$args['exclude'] = implode( ',', $request['exclude'] );
$args['limit'] = $request['per_page'];
$args['search'] = $request['search'];
$args['before'] = $request['before'];
$args['after'] = $request['after'];
if ( empty( $request['offset'] ) ) {
$args['offset'] = 1 < $request['page'] ? ( $request['page'] - 1 ) * $args['limit'] : 0;
}
/**
* Filter arguments, before passing to WC_Webhook_Data_Store->search_webhooks, when querying webhooks via the REST API.
*
* @param array $args Array of arguments for $wpdb->get_results().
* @param WP_REST_Request $request The current request.
*/
$prepared_args = apply_filters( 'woocommerce_rest_webhook_query', $args, $request );
unset( $prepared_args['page'] );
$prepared_args['paginate'] = true;
// Get the webhooks.
$webhooks = array();
$data_store = WC_Data_Store::load( 'webhook' );
$results = $data_store->search_webhooks( $prepared_args );
$webhook_ids = $results->webhooks;
foreach ( $webhook_ids as $webhook_id ) {
$data = $this->prepare_item_for_response( $webhook_id, $request );
$webhooks[] = $this->prepare_response_for_collection( $data );
}
$response = rest_ensure_response( $webhooks );
$per_page = (int) $prepared_args['limit'];
$page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
$total_webhooks = $results->total;
$max_pages = $results->max_num_pages;
$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
$response->header( 'X-WP-Total', $total_webhooks );
$response->header( 'X-WP-TotalPages', $max_pages );
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;
}
/**
* 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 ) {
$id = (int) $request['id'];
if ( empty( $id ) ) {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$data = $this->prepare_item_for_response( $id, $request );
$response = rest_ensure_response( $data );
return $response;
}
/**
* Create a single webhook.
*
* @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 ) );
}
// Validate topic.
if ( empty( $request['topic'] ) || ! wc_is_webhook_valid_topic( strtolower( $request['topic'] ) ) ) {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_topic", __( 'Webhook topic is required and must be valid.', 'woocommerce' ), array( 'status' => 400 ) );
}
// Validate delivery URL.
if ( empty( $request['delivery_url'] ) || ! wc_is_valid_url( $request['delivery_url'] ) ) {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_delivery_url", __( 'Webhook delivery URL must be a valid URL starting with http:// or https://.', 'woocommerce' ), array( 'status' => 400 ) );
}
$post = $this->prepare_item_for_database( $request );
if ( is_wp_error( $post ) ) {
return $post;
}
$webhook = new WC_Webhook();
$webhook->set_name( $post->post_title );
$webhook->set_user_id( $post->post_author );
$webhook->set_status( 'publish' === $post->post_status ? 'active' : 'disabled' );
$webhook->set_topic( $request['topic'] );
$webhook->set_delivery_url( $request['delivery_url'] );
$webhook->set_secret( ! empty( $request['secret'] ) ? $request['secret'] : wp_generate_password( 50, true, true ) );
$webhook->set_api_version( $this->get_default_api_version() );
$webhook->save();
$this->update_additional_fields_for_object( $webhook, $request );
/**
* Fires after a single item is created or updated via the REST API.
*
* @param WC_Webhook $webhook Webhook data.
* @param WP_REST_Request $request Request object.
* @param bool $creating True when creating item, false when updating.
*/
do_action( "woocommerce_rest_insert_webhook_object", $webhook, $request, true );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $webhook->get_id(), $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $webhook->get_id() ) ) );
// Send ping.
$webhook->deliver_ping();
return $response;
}
/**
* Update a single webhook.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
$id = (int) $request['id'];
$webhook = wc_get_webhook( $id );
if ( empty( $webhook ) || is_null( $webhook ) ) {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'ID is invalid.', 'woocommerce' ), array( 'status' => 400 ) );
}
// Update topic.
if ( ! empty( $request['topic'] ) ) {
if ( wc_is_webhook_valid_topic( strtolower( $request['topic'] ) ) ) {
$webhook->set_topic( $request['topic'] );
} else {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_topic", __( 'Webhook topic must be valid.', 'woocommerce' ), array( 'status' => 400 ) );
}
}
// Update delivery URL.
if ( ! empty( $request['delivery_url'] ) ) {
if ( wc_is_valid_url( $request['delivery_url'] ) ) {
$webhook->set_delivery_url( $request['delivery_url'] );
} else {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_delivery_url", __( 'Webhook delivery URL must be a valid URL starting with http:// or https://.', 'woocommerce' ), array( 'status' => 400 ) );
}
}
// Update secret.
if ( ! empty( $request['secret'] ) ) {
$webhook->set_secret( $request['secret'] );
}
// Update status.
if ( ! empty( $request['status'] ) ) {
if ( wc_is_webhook_valid_status( strtolower( $request['status'] ) ) ) {
$webhook->set_status( $request['status'] );
} else {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_status", __( 'Webhook status must be valid.', 'woocommerce' ), array( 'status' => 400 ) );
}
}
$post = $this->prepare_item_for_database( $request );
if ( is_wp_error( $post ) ) {
return $post;
}
if ( isset( $post->post_title ) ) {
$webhook->set_name( $post->post_title );
}
$webhook->save();
$this->update_additional_fields_for_object( $webhook, $request );
/**
* Fires after a single item is created or updated via the REST API.
*
* @param WC_Webhook $webhook Webhook data.
* @param WP_REST_Request $request Request object.
* @param bool $creating True when creating item, false when updating.
*/
do_action( "woocommerce_rest_insert_webhook_object", $webhook, $request, false );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $webhook->get_id(), $request );
return rest_ensure_response( $response );
}
/**
* Delete a single webhook.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function delete_item( $request ) {
$id = (int) $request['id'];
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
// We don't support trashing for this type, error out.
if ( ! $force ) {
return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Webhooks do not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
}
$webhook = wc_get_webhook( $id );
if ( empty( $webhook ) || is_null( $webhook ) ) {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $webhook, $request );
$result = $webhook->delete( true );
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 item is deleted or trashed via the REST API.
*
* @param WC_Webhook $webhook The deleted or trashed item.
* @param WP_REST_Response $response The response data.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( "woocommerce_rest_delete_webhook_object", $webhook, $response, $request );
return $response;
}
/**
* Prepare a single webhook for create or update.
*
* @param WP_REST_Request $request Request object.
* @return WP_Error|stdClass $data Post object.
*/
protected function prepare_item_for_database( $request ) {
$data = new stdClass;
// Post ID.
if ( isset( $request['id'] ) ) {
$data->ID = absint( $request['id'] );
}
// Validate required POST fields.
if ( 'POST' === $request->get_method() && empty( $data->ID ) ) {
$data->post_title = ! empty( $request['name'] ) ? $request['name'] : sprintf( __( 'Webhook created on %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Webhook created on date parsed by strftime', 'woocommerce' ) ) ); // @codingStandardsIgnoreLine
// Post author.
$data->post_author = get_current_user_id();
// Post password.
$data->post_password = 'webhook_' . wp_generate_password();
// Post status.
$data->post_status = 'publish';
} else {
// Allow edit post title.
if ( ! empty( $request['name'] ) ) {
$data->post_title = $request['name'];
}
}
// Comment status.
$data->comment_status = 'closed';
// Ping status.
$data->ping_status = 'closed';
/**
* Filter the query_vars used in `get_items` for the constructed query.
*
* The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
* prepared for insertion.
*
* @param stdClass $data An object representing a single item prepared
* for inserting or updating the database.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $data, $request );
}
/**
* Prepare a single webhook output for response.
*
* @param int $id Webhook ID or object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $id, $request ) {
$webhook = wc_get_webhook( $id );
if ( empty( $webhook ) || is_null( $webhook ) ) {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'ID is invalid.', 'woocommerce' ), array( 'status' => 400 ) );
}
$data = array(
'id' => $webhook->get_id(),
'name' => $webhook->get_name(),
'status' => $webhook->get_status(),
'topic' => $webhook->get_topic(),
'resource' => $webhook->get_resource(),
'event' => $webhook->get_event(),
'hooks' => $webhook->get_hooks(),
'delivery_url' => $webhook->get_delivery_url(),
'date_created' => wc_rest_prepare_date_response( $webhook->get_date_created() ),
'date_modified' => wc_rest_prepare_date_response( $webhook->get_date_modified() ),
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $webhook->get_id() ) );
/**
* Filter webhook object returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param WC_Webhook $webhook Webhook object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "woocommerce_rest_prepare_{$this->post_type}", $response, $webhook, $request );
}
/**
* Prepare links for the request.
*
* @param int $id Webhook ID.
* @return array
*/
protected function prepare_links( $id ) {
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $id ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
),
);
return $links;
}
/**
* Get the Webhook's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'webhook',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'A friendly name for the webhook.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'status' => array(
'description' => __( 'Webhook status.', 'woocommerce' ),
'type' => 'string',
'default' => 'active',
'enum' => array_keys( wc_get_webhook_statuses() ),
'context' => array( 'view', 'edit' ),
),
'topic' => array(
'description' => __( 'Webhook topic.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'resource' => array(
'description' => __( 'Webhook resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'event' => array(
'description' => __( 'Webhook event.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'hooks' => array(
'description' => __( 'WooCommerce action names associated with the webhook.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
'delivery_url' => array(
'description' => __( 'The URL where the webhook payload is delivered.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'secret' => array(
'description' => __( "Secret key used to generate a hash of the delivered webhook and provided in the request headers. This will default to a MD5 hash from the current user's ID|username if not provided.", 'woocommerce' ),
'type' => 'string',
'context' => array( 'edit' ),
),
'date_created' => array(
'description' => __( "The date the webhook was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the webhook was last modified, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections of attachments.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
$params['context']['default'] = 'view';
$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',
'title',
),
'validate_callback' => 'rest_validate_request_arg',
);
$params['status'] = array(
'default' => 'all',
'description' => __( 'Limit result set to webhooks assigned a specific status.', 'woocommerce' ),
'type' => 'string',
'enum' => array( 'all', 'active', 'paused', 'disabled' ),
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
}

View File

@ -0,0 +1,542 @@
<?php
/**
* REST API Coupons controller
*
* Handles requests to the /coupons endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Coupons controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_CRUD_Controller
*/
class WC_REST_Coupons_V2_Controller extends WC_REST_CRUD_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'coupons';
/**
* Post type.
*
* @var string
*/
protected $post_type = 'shop_coupon';
/**
* Register the routes for coupons.
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => array_merge(
$this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
'code' => array(
'description' => __( 'Coupon code.', 'woocommerce' ),
'required' => true,
'type' => 'string',
),
)
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
'type' => 'boolean',
'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/batch', array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'batch_items' ),
'permission_callback' => array( $this, 'batch_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_batch_schema' ),
)
);
}
/**
* Get object.
*
* @since 3.0.0
* @param int $id Object ID.
* @return WC_Data
*/
protected function get_object( $id ) {
return new WC_Coupon( $id );
}
/**
* Get formatted item data.
*
* @since 3.0.0
* @param WC_Data $object WC_Data instance.
* @return array
*/
protected function get_formatted_item_data( $object ) {
$data = $object->get_data();
$format_decimal = array( 'amount', 'minimum_amount', 'maximum_amount' );
$format_date = array( 'date_created', 'date_modified', 'date_expires' );
$format_null = array( 'usage_limit', 'usage_limit_per_user', 'limit_usage_to_x_items' );
// Format decimal values.
foreach ( $format_decimal as $key ) {
$data[ $key ] = wc_format_decimal( $data[ $key ], 2 );
}
// Format date values.
foreach ( $format_date as $key ) {
$datetime = $data[ $key ];
$data[ $key ] = wc_rest_prepare_date_response( $datetime, false );
$data[ $key . '_gmt' ] = wc_rest_prepare_date_response( $datetime );
}
// Format null values.
foreach ( $format_null as $key ) {
$data[ $key ] = $data[ $key ] ? $data[ $key ] : null;
}
return array(
'id' => $object->get_id(),
'code' => $data['code'],
'amount' => $data['amount'],
'date_created' => $data['date_created'],
'date_created_gmt' => $data['date_created_gmt'],
'date_modified' => $data['date_modified'],
'date_modified_gmt' => $data['date_modified_gmt'],
'discount_type' => $data['discount_type'],
'description' => $data['description'],
'date_expires' => $data['date_expires'],
'date_expires_gmt' => $data['date_expires_gmt'],
'usage_count' => $data['usage_count'],
'individual_use' => $data['individual_use'],
'product_ids' => $data['product_ids'],
'excluded_product_ids' => $data['excluded_product_ids'],
'usage_limit' => $data['usage_limit'],
'usage_limit_per_user' => $data['usage_limit_per_user'],
'limit_usage_to_x_items' => $data['limit_usage_to_x_items'],
'free_shipping' => $data['free_shipping'],
'product_categories' => $data['product_categories'],
'excluded_product_categories' => $data['excluded_product_categories'],
'exclude_sale_items' => $data['exclude_sale_items'],
'minimum_amount' => $data['minimum_amount'],
'maximum_amount' => $data['maximum_amount'],
'email_restrictions' => $data['email_restrictions'],
'used_by' => $data['used_by'],
'meta_data' => $data['meta_data'],
);
}
/**
* Prepare a single coupon output for response.
*
* @since 3.0.0
* @param WC_Data $object Object data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
public function prepare_object_for_response( $object, $request ) {
$data = $this->get_formatted_item_data( $object );
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $object, $request ) );
/**
* Filter the data for a response.
*
* The dynamic portion of the hook name, $this->post_type,
* refers to object type being prepared for the response.
*
* @param WP_REST_Response $response The response object.
* @param WC_Data $object Object data.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "woocommerce_rest_prepare_{$this->post_type}_object", $response, $object, $request );
}
/**
* Prepare objects query.
*
* @since 3.0.0
* @param WP_REST_Request $request Full details about the request.
* @return array
*/
protected function prepare_objects_query( $request ) {
$args = parent::prepare_objects_query( $request );
if ( ! empty( $request['code'] ) ) {
$id = wc_get_coupon_id_by_code( $request['code'] );
$args['post__in'] = array( $id );
}
// Get only ids.
$args['fields'] = 'ids';
return $args;
}
/**
* Only return writable props from schema.
*
* @param array $schema Schema.
* @return bool
*/
protected function filter_writable_props( $schema ) {
return empty( $schema['readonly'] );
}
/**
* Prepare a single coupon for create or update.
*
* @param WP_REST_Request $request Request object.
* @param bool $creating If is creating a new object.
* @return WP_Error|WC_Data
*/
protected function prepare_object_for_database( $request, $creating = false ) {
$id = isset( $request['id'] ) ? absint( $request['id'] ) : 0;
$coupon = new WC_Coupon( $id );
$schema = $this->get_item_schema();
$data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) );
// Validate required POST fields.
if ( $creating && empty( $request['code'] ) ) {
return new WP_Error( 'woocommerce_rest_empty_coupon_code', sprintf( __( 'The coupon code cannot be empty.', 'woocommerce' ), 'code' ), array( 'status' => 400 ) );
}
// Handle all writable props.
foreach ( $data_keys as $key ) {
$value = $request[ $key ];
if ( ! is_null( $value ) ) {
switch ( $key ) {
case 'code':
$coupon_code = wc_format_coupon_code( $value );
$id = $coupon->get_id() ? $coupon->get_id() : 0;
$id_from_code = wc_get_coupon_id_by_code( $coupon_code, $id );
if ( $id_from_code ) {
return new WP_Error( 'woocommerce_rest_coupon_code_already_exists', __( 'The coupon code already exists', 'woocommerce' ), array( 'status' => 400 ) );
}
$coupon->set_code( $coupon_code );
break;
case 'meta_data':
if ( is_array( $value ) ) {
foreach ( $value as $meta ) {
$coupon->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
}
}
break;
case 'description':
$coupon->set_description( wp_filter_post_kses( $value ) );
break;
default:
if ( is_callable( array( $coupon, "set_{$key}" ) ) ) {
$coupon->{"set_{$key}"}( $value );
}
break;
}
}
}
/**
* Filters an object before it is inserted via the REST API.
*
* The dynamic portion of the hook name, `$this->post_type`,
* refers to the object type slug.
*
* @param WC_Data $coupon Object object.
* @param WP_REST_Request $request Request object.
* @param bool $creating If is creating a new object.
*/
return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $coupon, $request, $creating );
}
/**
* Get the Coupon's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => $this->post_type,
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the object.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'code' => array(
'description' => __( 'Coupon code.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'amount' => array(
'description' => __( 'The amount of discount. Should always be numeric, even if setting a percentage.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'date_created' => array(
'description' => __( "The date the coupon was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created_gmt' => array(
'description' => __( 'The date the coupon was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the coupon was last modified, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified_gmt' => array(
'description' => __( 'The date the coupon was last modified, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'discount_type' => array(
'description' => __( 'Determines the type of discount that will be applied.', 'woocommerce' ),
'type' => 'string',
'default' => 'fixed_cart',
'enum' => array_keys( wc_get_coupon_types() ),
'context' => array( 'view', 'edit' ),
),
'description' => array(
'description' => __( 'Coupon description.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'date_expires' => array(
'description' => __( "The date the coupon expires, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'date_expires_gmt' => array(
'description' => __( 'The date the coupon expires, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'usage_count' => array(
'description' => __( 'Number of times the coupon has been used already.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'individual_use' => array(
'description' => __( 'If true, the coupon can only be used individually. Other applied coupons will be removed from the cart.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'product_ids' => array(
'description' => __( 'List of product IDs the coupon can be used on.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'context' => array( 'view', 'edit' ),
),
'excluded_product_ids' => array(
'description' => __( 'List of product IDs the coupon cannot be used on.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'context' => array( 'view', 'edit' ),
),
'usage_limit' => array(
'description' => __( 'How many times the coupon can be used in total.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'usage_limit_per_user' => array(
'description' => __( 'How many times the coupon can be used per customer.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'limit_usage_to_x_items' => array(
'description' => __( 'Max number of items in the cart the coupon can be applied to.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'free_shipping' => array(
'description' => __( 'If true and if the free shipping method requires a coupon, this coupon will enable free shipping.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'product_categories' => array(
'description' => __( 'List of category IDs the coupon applies to.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'context' => array( 'view', 'edit' ),
),
'excluded_product_categories' => array(
'description' => __( 'List of category IDs the coupon does not apply to.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'context' => array( 'view', 'edit' ),
),
'exclude_sale_items' => array(
'description' => __( 'If true, this coupon will not be applied to items that have sale prices.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'minimum_amount' => array(
'description' => __( 'Minimum order amount that needs to be in the cart before coupon applies.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'maximum_amount' => array(
'description' => __( 'Maximum order amount allowed when using the coupon.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'email_restrictions' => array(
'description' => __( 'List of email addresses that can use this coupon.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'string',
),
'context' => array( 'view', 'edit' ),
),
'used_by' => array(
'description' => __( 'List of user IDs (or guest email addresses) that have used the coupon.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'meta_data' => array(
'description' => __( 'Meta data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Meta ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'key' => array(
'description' => __( 'Meta key.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ),
'context' => array( 'view', 'edit' ),
),
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections of attachments.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
$params['code'] = array(
'description' => __( 'Limit result set to resources with a specific code.', 'woocommerce' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
}

View File

@ -0,0 +1,165 @@
<?php
/**
* REST API Customer Downloads controller
*
* Handles requests to the /customers/<customer_id>/downloads endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Customers controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Customer_Downloads_V1_Controller
*/
class WC_REST_Customer_Downloads_V2_Controller extends WC_REST_Customer_Downloads_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Prepare a single download output for response.
*
* @param stdClass $download Download object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $download, $request ) {
$data = array(
'download_id' => $download->download_id,
'download_url' => $download->download_url,
'product_id' => $download->product_id,
'product_name' => $download->product_name,
'download_name' => $download->download_name,
'order_id' => $download->order_id,
'order_key' => $download->order_key,
'downloads_remaining' => '' === $download->downloads_remaining ? 'unlimited' : $download->downloads_remaining,
'access_expires' => $download->access_expires ? wc_rest_prepare_date_response( $download->access_expires ) : 'never',
'access_expires_gmt' => $download->access_expires ? wc_rest_prepare_date_response( get_gmt_from_date( $download->access_expires ) ) : 'never',
'file' => $download->file,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $download, $request ) );
/**
* Filter customer download data returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param stdClass $download Download object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_customer_download', $response, $download, $request );
}
/**
* Get the Customer Download's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'customer_download',
'type' => 'object',
'properties' => array(
'download_id' => array(
'description' => __( 'Download ID.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'download_url' => array(
'description' => __( 'Download file URL.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'product_id' => array(
'description' => __( 'Downloadable product ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'product_name' => array(
'description' => __( 'Product name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'download_name' => array(
'description' => __( 'Downloadable file name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'order_id' => array(
'description' => __( 'Order ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'order_key' => array(
'description' => __( 'Order key.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'downloads_remaining' => array(
'description' => __( 'Number of downloads remaining.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'access_expires' => array(
'description' => __( "The date when download access expires, in the site's timezone.", 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'access_expires_gmt' => array(
'description' => __( 'The date when download access expires, as GMT.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'file' => array(
'description' => __( 'File details.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view' ),
'readonly' => true,
'properties' => array(
'name' => array(
'description' => __( 'File name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'file' => array(
'description' => __( 'File URL.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,364 @@
<?php
/**
* REST API Customers controller
*
* Handles requests to the /customers endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Customers controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Customers_V1_Controller
*/
class WC_REST_Customers_V2_Controller extends WC_REST_Customers_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Get formatted item data.
*
* @since 3.0.0
* @param WC_Data $object WC_Data instance.
* @return array
*/
protected function get_formatted_item_data( $object ) {
$data = $object->get_data();
$format_date = array( 'date_created', 'date_modified' );
// Format date values.
foreach ( $format_date as $key ) {
$datetime = 'date_created' === $key ? get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $data[ $key ]->getTimestamp() ) ) : $data[ $key ];
$data[ $key ] = wc_rest_prepare_date_response( $datetime, false );
$data[ $key . '_gmt' ] = wc_rest_prepare_date_response( $datetime );
}
return array(
'id' => $object->get_id(),
'date_created' => $data['date_created'],
'date_created_gmt' => $data['date_created_gmt'],
'date_modified' => $data['date_modified'],
'date_modified_gmt' => $data['date_modified_gmt'],
'email' => $data['email'],
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'role' => $data['role'],
'username' => $data['username'],
'billing' => $data['billing'],
'shipping' => $data['shipping'],
'is_paying_customer' => $data['is_paying_customer'],
'orders_count' => $object->get_order_count(),
'total_spent' => $object->get_total_spent(),
'avatar_url' => $object->get_avatar_url(),
'meta_data' => $data['meta_data'],
);
}
/**
* Prepare a single customer output for response.
*
* @param WP_User $user_data User object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $user_data, $request ) {
$customer = new WC_Customer( $user_data->ID );
$data = $this->get_formatted_item_data( $customer );
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $user_data ) );
/**
* Filter customer data returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param WP_User $user_data User object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_customer', $response, $user_data, $request );
}
/**
* Update customer meta fields.
*
* @param WC_Customer $customer Customer data.
* @param WP_REST_Request $request Request data.
*/
protected function update_customer_meta_fields( $customer, $request ) {
parent::update_customer_meta_fields( $customer, $request );
// Meta data.
if ( isset( $request['meta_data'] ) ) {
if ( is_array( $request['meta_data'] ) ) {
foreach ( $request['meta_data'] as $meta ) {
$customer->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
}
}
}
}
/**
* Get the Customer's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'customer',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created' => array(
'description' => __( "The date the customer was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created_gmt' => array(
'description' => __( 'The date the customer was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the customer was last modified, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified_gmt' => array(
'description' => __( 'The date the customer was last modified, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'email' => array(
'description' => __( 'The email address for the customer.', 'woocommerce' ),
'type' => 'string',
'format' => 'email',
'context' => array( 'view', 'edit' ),
),
'first_name' => array(
'description' => __( 'Customer first name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'last_name' => array(
'description' => __( 'Customer last name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'role' => array(
'description' => __( 'Customer role.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'username' => array(
'description' => __( 'Customer login name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_user',
),
),
'password' => array(
'description' => __( 'Customer password.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'edit' ),
),
'billing' => array(
'description' => __( 'List of billing address data.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'first_name' => array(
'description' => __( 'First name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'last_name' => array(
'description' => __( 'Last name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'company' => array(
'description' => __( 'Company name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'address_1' => array(
'description' => __( 'Address line 1', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'address_2' => array(
'description' => __( 'Address line 2', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'city' => array(
'description' => __( 'City name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'state' => array(
'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'postcode' => array(
'description' => __( 'Postal code.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'country' => array(
'description' => __( 'ISO code of the country.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'email' => array(
'description' => __( 'Email address.', 'woocommerce' ),
'type' => 'string',
'format' => 'email',
'context' => array( 'view', 'edit' ),
),
'phone' => array(
'description' => __( 'Phone number.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
'shipping' => array(
'description' => __( 'List of shipping address data.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'first_name' => array(
'description' => __( 'First name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'last_name' => array(
'description' => __( 'Last name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'company' => array(
'description' => __( 'Company name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'address_1' => array(
'description' => __( 'Address line 1', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'address_2' => array(
'description' => __( 'Address line 2', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'city' => array(
'description' => __( 'City name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'state' => array(
'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'postcode' => array(
'description' => __( 'Postal code.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'country' => array(
'description' => __( 'ISO code of the country.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
'is_paying_customer' => array(
'description' => __( 'Is the customer a paying customer?', 'woocommerce' ),
'type' => 'bool',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'orders_count' => array(
'description' => __( 'Quantity of orders made by the customer.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'total_spent' => array(
'description' => __( 'Total amount spent.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'avatar_url' => array(
'description' => __( 'Avatar URL.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'meta_data' => array(
'description' => __( 'Meta data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Meta ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'key' => array(
'description' => __( 'Meta key.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ),
'context' => array( 'view', 'edit' ),
),
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,174 @@
<?php
/**
* REST API Network Orders controller
*
* Handles requests to the /orders/network endpoint
*
* @package Automattic/WooCommerce/RestApi
* @since 3.4.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Network Orders controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Orders_V2_Controller
*/
class WC_REST_Network_Orders_V2_Controller extends WC_REST_Orders_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Register the routes for network orders.
*/
public function register_routes() {
if ( is_multisite() ) {
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/network',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'network_orders' ),
'permission_callback' => array( $this, 'network_orders_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
}
/**
* Retrieves the item's schema for display / public consumption purposes.
*
* @return array Public item schema data.
*/
public function get_public_item_schema() {
$schema = parent::get_public_item_schema();
$schema['properties']['blog'] = array(
'description' => __( 'Blog id of the record on the multisite.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
);
$schema['properties']['edit_url'] = array(
'description' => __( 'URL to edit the order', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
);
$schema['properties']['customer'][] = array(
'description' => __( 'Name of the customer for the order', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
);
$schema['properties']['status_name'][] = array(
'description' => __( 'Order Status', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
);
$schema['properties']['formatted_total'][] = array(
'description' => __( 'Order total formatted for locale', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
);
return $schema;
}
/**
* Does a permissions check for the proper requested blog
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool $permission
*/
public function network_orders_permissions_check( $request ) {
$blog_id = $request->get_param( 'blog_id' );
$blog_id = ! empty( $blog_id ) ? $blog_id : get_current_blog_id();
switch_to_blog( $blog_id );
$permission = $this->get_items_permissions_check( $request );
restore_current_blog();
return $permission;
}
/**
* Get a collection of orders from the requested blog id
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response
*/
public function network_orders( $request ) {
$blog_id = $request->get_param( 'blog_id' );
$blog_id = ! empty( $blog_id ) ? $blog_id : get_current_blog_id();
$active_plugins = get_blog_option( $blog_id, 'active_plugins', array() );
$network_active_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
$plugins = array_merge( $active_plugins, $network_active_plugins );
$wc_active = false;
foreach ( $plugins as $plugin ) {
if ( substr_compare( $plugin, '/woocommerce.php', strlen( $plugin ) - strlen( '/woocommerce.php' ), strlen( '/woocommerce.php' ) ) === 0 ) {
$wc_active = true;
}
}
// If WooCommerce not active for site, return an empty response.
if ( ! $wc_active ) {
$response = rest_ensure_response( array() );
return $response;
}
switch_to_blog( $blog_id );
add_filter( 'woocommerce_rest_orders_prepare_object_query', array( $this, 'network_orders_filter_args' ) );
$items = $this->get_items( $request );
remove_filter( 'woocommerce_rest_orders_prepare_object_query', array( $this, 'network_orders_filter_args' ) );
foreach ( $items->data as &$current_order ) {
$order = wc_get_order( $current_order['id'] );
$current_order['blog'] = get_blog_details( get_current_blog_id() );
$current_order['edit_url'] = get_admin_url( $blog_id, 'post.php?post=' . absint( $order->get_id() ) . '&action=edit' );
/* translators: 1: first name 2: last name */
$current_order['customer'] = trim( sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $order->get_billing_first_name(), $order->get_billing_last_name() ) );
$current_order['status_name'] = wc_get_order_status_name( $order->get_status() );
$current_order['formatted_total'] = $order->get_formatted_order_total();
}
restore_current_blog();
return $items;
}
/**
* Filters the post statuses to on hold and processing for the network order query.
*
* @param array $args Query args.
*
* @return array
*/
public function network_orders_filter_args( $args ) {
$args['post_status'] = array(
'wc-on-hold',
'wc-processing',
);
return $args;
}
}

View File

@ -0,0 +1,182 @@
<?php
/**
* REST API Order Notes controller
*
* Handles requests to the /orders/<order_id>/notes endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Order Notes controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Order_Notes_V1_Controller
*/
class WC_REST_Order_Notes_V2_Controller extends WC_REST_Order_Notes_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Get order notes from an order.
*
* @param WP_REST_Request $request Request data.
*
* @return array|WP_Error
*/
public function get_items( $request ) {
$order = wc_get_order( (int) $request['order_id'] );
if ( ! $order || $this->post_type !== $order->get_type() ) {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid order ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$args = array(
'post_id' => $order->get_id(),
'approve' => 'approve',
'type' => 'order_note',
);
// Allow filter by order note type.
if ( 'customer' === $request['type'] ) {
$args['meta_query'] = array( // WPCS: slow query ok.
array(
'key' => 'is_customer_note',
'value' => 1,
'compare' => '=',
),
);
} elseif ( 'internal' === $request['type'] ) {
$args['meta_query'] = array( // WPCS: slow query ok.
array(
'key' => 'is_customer_note',
'compare' => 'NOT EXISTS',
),
);
}
remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
$notes = get_comments( $args );
add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
$data = array();
foreach ( $notes as $note ) {
$order_note = $this->prepare_item_for_response( $note, $request );
$order_note = $this->prepare_response_for_collection( $order_note );
$data[] = $order_note;
}
return rest_ensure_response( $data );
}
/**
* Prepare a single order note output for response.
*
* @param WP_Comment $note Order note object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $note, $request ) {
$data = array(
'id' => (int) $note->comment_ID,
'date_created' => wc_rest_prepare_date_response( $note->comment_date ),
'date_created_gmt' => wc_rest_prepare_date_response( $note->comment_date_gmt ),
'note' => $note->comment_content,
'customer_note' => (bool) get_comment_meta( $note->comment_ID, 'is_customer_note', true ),
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $note ) );
/**
* Filter order note object returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param WP_Comment $note Order note object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_order_note', $response, $note, $request );
}
/**
* Get the Order Notes schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'order_note',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created' => array(
'description' => __( "The date the order note was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created_gmt' => array(
'description' => __( 'The date the order note was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'note' => array(
'description' => __( 'Order note content.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'customer_note' => array(
'description' => __( 'If true, the note will be shown to customers and they will be notified. If false, the note will be for admin reference only.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$params = array();
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
$params['type'] = array(
'default' => 'any',
'description' => __( 'Limit result to customers or internal notes.', 'woocommerce' ),
'type' => 'string',
'enum' => array( 'any', 'customer', 'internal' ),
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
}

View File

@ -0,0 +1,584 @@
<?php
/**
* REST API Order Refunds controller
*
* Handles requests to the /orders/<order_id>/refunds endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Order Refunds controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Orders_V2_Controller
*/
class WC_REST_Order_Refunds_V2_Controller extends WC_REST_Orders_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'orders/(?P<order_id>[\d]+)/refunds';
/**
* Post type.
*
* @var string
*/
protected $post_type = 'shop_order_refund';
/**
* Stores the request.
*
* @var array
*/
protected $request = array();
/**
* Order refunds actions.
*/
public function __construct() {
add_filter( "woocommerce_rest_{$this->post_type}_object_trashable", '__return_false' );
}
/**
* Register the routes for order refunds.
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base, array(
'args' => array(
'order_id' => array(
'description' => __( 'The order ID.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
'args' => array(
'order_id' => array(
'description' => __( 'The order ID.', 'woocommerce' ),
'type' => 'integer',
),
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => true,
'type' => 'boolean',
'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Get object.
*
* @since 3.0.0
* @param int $id Object ID.
* @return WC_Data
*/
protected function get_object( $id ) {
return wc_get_order( $id );
}
/**
* Get formatted item data.
*
* @since 3.0.0
* @param WC_Data $object WC_Data instance.
* @return array
*/
protected function get_formatted_item_data( $object ) {
$data = $object->get_data();
$format_decimal = array( 'amount' );
$format_date = array( 'date_created' );
$format_line_items = array( 'line_items' );
// Format decimal values.
foreach ( $format_decimal as $key ) {
$data[ $key ] = wc_format_decimal( $data[ $key ], $this->request['dp'] );
}
// Format date values.
foreach ( $format_date as $key ) {
$datetime = $data[ $key ];
$data[ $key ] = wc_rest_prepare_date_response( $datetime, false );
$data[ $key . '_gmt' ] = wc_rest_prepare_date_response( $datetime );
}
// Format line items.
foreach ( $format_line_items as $key ) {
$data[ $key ] = array_values( array_map( array( $this, 'get_order_item_data' ), $data[ $key ] ) );
}
return array(
'id' => $object->get_id(),
'date_created' => $data['date_created'],
'date_created_gmt' => $data['date_created_gmt'],
'amount' => $data['amount'],
'reason' => $data['reason'],
'refunded_by' => $data['refunded_by'],
'refunded_payment' => $data['refunded_payment'],
'meta_data' => $data['meta_data'],
'line_items' => $data['line_items'],
);
}
/**
* Prepare a single order output for response.
*
* @since 3.0.0
*
* @param WC_Data $object Object data.
* @param WP_REST_Request $request Request object.
*
* @return WP_Error|WP_REST_Response
*/
public function prepare_object_for_response( $object, $request ) {
$this->request = $request;
$this->request['dp'] = is_null( $this->request['dp'] ) ? wc_get_price_decimals() : absint( $this->request['dp'] );
$order = wc_get_order( (int) $request['order_id'] );
if ( ! $order ) {
return new WP_Error( 'woocommerce_rest_invalid_order_id', __( 'Invalid order ID.', 'woocommerce' ), 404 );
}
if ( ! $object || $object->get_parent_id() !== $order->get_id() ) {
return new WP_Error( 'woocommerce_rest_invalid_order_refund_id', __( 'Invalid order refund ID.', 'woocommerce' ), 404 );
}
$data = $this->get_formatted_item_data( $object );
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $object, $request ) );
/**
* Filter the data for a response.
*
* The dynamic portion of the hook name, $this->post_type,
* refers to object type being prepared for the response.
*
* @param WP_REST_Response $response The response object.
* @param WC_Data $object Object data.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "woocommerce_rest_prepare_{$this->post_type}_object", $response, $object, $request );
}
/**
* 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 ) {
$base = str_replace( '(?P<order_id>[\d]+)', $object->get_parent_id(), $this->rest_base );
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $object->get_id() ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ),
),
'up' => array(
'href' => rest_url( sprintf( '/%s/orders/%d', $this->namespace, $object->get_parent_id() ) ),
),
);
return $links;
}
/**
* Prepare objects query.
*
* @since 3.0.0
* @param WP_REST_Request $request Full details about the request.
* @return array
*/
protected function prepare_objects_query( $request ) {
$args = parent::prepare_objects_query( $request );
$args['post_status'] = array_keys( wc_get_order_statuses() );
$args['post_parent__in'] = array( absint( $request['order_id'] ) );
return $args;
}
/**
* Prepares one object for create or update operation.
*
* @since 3.0.0
* @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 ) {
$order = wc_get_order( (int) $request['order_id'] );
if ( ! $order ) {
return new WP_Error( 'woocommerce_rest_invalid_order_id', __( 'Invalid order ID.', 'woocommerce' ), 404 );
}
if ( 0 > $request['amount'] ) {
return new WP_Error( 'woocommerce_rest_invalid_order_refund', __( 'Refund amount must be greater than zero.', 'woocommerce' ), 400 );
}
// Create the refund.
$refund = wc_create_refund(
array(
'order_id' => $order->get_id(),
'amount' => $request['amount'],
'reason' => empty( $request['reason'] ) ? null : $request['reason'],
'refund_payment' => is_bool( $request['api_refund'] ) ? $request['api_refund'] : true,
'restock_items' => true,
)
);
if ( is_wp_error( $refund ) ) {
return new WP_Error( 'woocommerce_rest_cannot_create_order_refund', $refund->get_error_message(), 500 );
}
if ( ! $refund ) {
return new WP_Error( 'woocommerce_rest_cannot_create_order_refund', __( 'Cannot create order refund, please try again.', 'woocommerce' ), 500 );
}
if ( ! empty( $request['meta_data'] ) && is_array( $request['meta_data'] ) ) {
foreach ( $request['meta_data'] as $meta ) {
$refund->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
}
$refund->save_meta_data();
}
/**
* Filters an object before it is inserted via the REST API.
*
* The dynamic portion of the hook name, `$this->post_type`,
* refers to the object type slug.
*
* @param WC_Data $coupon Object object.
* @param WP_REST_Request $request Request object.
* @param bool $creating If is creating a new object.
*/
return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $refund, $request, $creating );
}
/**
* Save an object data.
*
* @since 3.0.0
* @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;
}
return $this->get_object( $object->get_id() );
} 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() ) );
}
}
/**
* Get the Order's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => $this->post_type,
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created' => array(
'description' => __( "The date the order refund was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created_gmt' => array(
'description' => __( 'The date the order refund was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'amount' => array(
'description' => __( 'Refund amount.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'reason' => array(
'description' => __( 'Reason for refund.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'refunded_by' => array(
'description' => __( 'User ID of user who created the refund.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'refunded_payment' => array(
'description' => __( 'If the payment was refunded via the API.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'meta_data' => array(
'description' => __( 'Meta data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Meta ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'key' => array(
'description' => __( 'Meta key.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ),
'context' => array( 'view', 'edit' ),
),
),
),
),
'line_items' => array(
'description' => __( 'Line items data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Item ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Product name.', 'woocommerce' ),
'type' => array( 'string', 'null' ),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'product_id' => array(
'description' => __( 'Product ID.', 'woocommerce' ),
'type' => array( 'integer', 'null' ),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'variation_id' => array(
'description' => __( 'Variation ID, if applicable.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'quantity' => array(
'description' => __( 'Quantity ordered.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'tax_class' => array(
'description' => __( 'Tax class of product.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'subtotal' => array(
'description' => __( 'Line subtotal (before discounts).', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'subtotal_tax' => array(
'description' => __( 'Line subtotal tax (before discounts).', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'total' => array(
'description' => __( 'Line total (after discounts).', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'total_tax' => array(
'description' => __( 'Line total tax (after discounts).', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'taxes' => array(
'description' => __( 'Line taxes.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Tax rate ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'total' => array(
'description' => __( 'Tax total.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'subtotal' => array(
'description' => __( 'Tax subtotal.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
),
'meta_data' => array(
'description' => __( 'Meta data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Meta ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'key' => array(
'description' => __( 'Meta key.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
),
'sku' => array(
'description' => __( 'Product SKU.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'price' => array(
'description' => __( 'Product price.', 'woocommerce' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
),
'api_refund' => array(
'description' => __( 'When true, the payment gateway API is used to generate the refund.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'edit' ),
'default' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
unset( $params['status'], $params['customer'], $params['product'] );
return $params;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,466 @@
<?php
/**
* REST API WC Payment gateways controller
*
* Handles requests to the /payment_gateways endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* Paymenga gateways controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Payment_Gateways_V2_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'payment_gateways';
/**
* Register the route for /payment_gateways and /payment_gateways/<id>
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<id>[\w-]+)', array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Check whether a given request has permission to view payment gateways.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'payment_gateways', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to read a payment gateway.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'payment_gateways', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check whether a given request has permission to edit payment gateways.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function update_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'payment_gateways', 'edit' ) ) {
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;
}
/**
* Get payment gateways.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$payment_gateways = WC()->payment_gateways->payment_gateways();
$response = array();
foreach ( $payment_gateways as $payment_gateway_id => $payment_gateway ) {
$payment_gateway->id = $payment_gateway_id;
$gateway = $this->prepare_item_for_response( $payment_gateway, $request );
$gateway = $this->prepare_response_for_collection( $gateway );
$response[] = $gateway;
}
return rest_ensure_response( $response );
}
/**
* Get a single payment gateway.
*
* @param WP_REST_Request $request Request data.
* @return WP_REST_Response|WP_Error
*/
public function get_item( $request ) {
$gateway = $this->get_gateway( $request );
if ( is_null( $gateway ) ) {
return new WP_Error( 'woocommerce_rest_payment_gateway_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
}
$gateway = $this->prepare_item_for_response( $gateway, $request );
return rest_ensure_response( $gateway );
}
/**
* Update A Single Payment Method.
*
* @param WP_REST_Request $request Request data.
* @return WP_REST_Response|WP_Error
*/
public function update_item( $request ) {
$gateway = $this->get_gateway( $request );
if ( is_null( $gateway ) ) {
return new WP_Error( 'woocommerce_rest_payment_gateway_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
}
// Get settings.
$gateway->init_form_fields();
$settings = $gateway->settings;
// Update settings.
if ( isset( $request['settings'] ) ) {
$errors_found = false;
foreach ( $gateway->form_fields as $key => $field ) {
if ( isset( $request['settings'][ $key ] ) ) {
if ( is_callable( array( $this, 'validate_setting_' . $field['type'] . '_field' ) ) ) {
$value = $this->{'validate_setting_' . $field['type'] . '_field'}( $request['settings'][ $key ], $field );
} else {
$value = $this->validate_setting_text_field( $request['settings'][ $key ], $field );
}
if ( is_wp_error( $value ) ) {
$errors_found = true;
break;
}
$settings[ $key ] = $value;
}
}
if ( $errors_found ) {
return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
}
}
// Update if this method is enabled or not.
if ( isset( $request['enabled'] ) ) {
$settings['enabled'] = wc_bool_to_string( $request['enabled'] );
$gateway->enabled = $settings['enabled'];
}
// Update title.
if ( isset( $request['title'] ) ) {
$settings['title'] = $request['title'];
$gateway->title = $settings['title'];
}
// Update description.
if ( isset( $request['description'] ) ) {
$settings['description'] = $request['description'];
$gateway->description = $settings['description'];
}
// Update options.
$gateway->settings = $settings;
update_option( $gateway->get_option_key(), apply_filters( 'woocommerce_gateway_' . $gateway->id . '_settings_values', $settings, $gateway ) );
// Update order.
if ( isset( $request['order'] ) ) {
$order = (array) get_option( 'woocommerce_gateway_order' );
$order[ $gateway->id ] = $request['order'];
update_option( 'woocommerce_gateway_order', $order );
$gateway->order = absint( $request['order'] );
}
$gateway = $this->prepare_item_for_response( $gateway, $request );
return rest_ensure_response( $gateway );
}
/**
* Get a gateway based on the current request object.
*
* @param WP_REST_Request $request Request data.
* @return WP_REST_Response|null
*/
public function get_gateway( $request ) {
$gateway = null;
$payment_gateways = WC()->payment_gateways->payment_gateways();
foreach ( $payment_gateways as $payment_gateway_id => $payment_gateway ) {
if ( $request['id'] !== $payment_gateway_id ) {
continue;
}
$payment_gateway->id = $payment_gateway_id;
$gateway = $payment_gateway;
}
return $gateway;
}
/**
* Prepare a payment gateway for response.
*
* @param WC_Payment_Gateway $gateway Payment gateway object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $gateway, $request ) {
$order = (array) get_option( 'woocommerce_gateway_order' );
$item = array(
'id' => $gateway->id,
'title' => $gateway->title,
'description' => $gateway->description,
'order' => isset( $order[ $gateway->id ] ) ? $order[ $gateway->id ] : '',
'enabled' => ( 'yes' === $gateway->enabled ),
'method_title' => $gateway->get_method_title(),
'method_description' => $gateway->get_method_description(),
'settings' => $this->get_settings( $gateway ),
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $item, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $gateway, $request ) );
/**
* Filter payment gateway objects returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param WC_Payment_Gateway $gateway Payment gateway object.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_payment_gateway', $response, $gateway, $request );
}
/**
* Return settings associated with this payment gateway.
*
* @param WC_Payment_Gateway $gateway Gateway data.
*
* @return array
*/
public function get_settings( $gateway ) {
$settings = array();
$gateway->init_form_fields();
foreach ( $gateway->form_fields as $id => $field ) {
// Make sure we at least have a title and type.
if ( empty( $field['title'] ) || empty( $field['type'] ) ) {
continue;
}
// Ignore 'title' settings/fields -- they are UI only.
if ( 'title' === $field['type'] ) {
continue;
}
// Ignore 'enabled' and 'description' which get included elsewhere.
if ( in_array( $id, array( 'enabled', 'description' ), true ) ) {
continue;
}
$data = array(
'id' => $id,
'label' => empty( $field['label'] ) ? $field['title'] : $field['label'],
'description' => empty( $field['description'] ) ? '' : $field['description'],
'type' => $field['type'],
'value' => empty( $gateway->settings[ $id ] ) ? '' : $gateway->settings[ $id ],
'default' => empty( $field['default'] ) ? '' : $field['default'],
'tip' => empty( $field['description'] ) ? '' : $field['description'],
'placeholder' => empty( $field['placeholder'] ) ? '' : $field['placeholder'],
);
if ( ! empty( $field['options'] ) ) {
$data['options'] = $field['options'];
}
$settings[ $id ] = $data;
}
return $settings;
}
/**
* Prepare links for the request.
*
* @param WC_Payment_Gateway $gateway Payment gateway object.
* @param WP_REST_Request $request Request object.
* @return array
*/
protected function prepare_links( $gateway, $request ) {
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $gateway->id ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
),
);
return $links;
}
/**
* Get the payment gateway schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'payment_gateway',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Payment gateway ID.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'title' => array(
'description' => __( 'Payment gateway title on checkout.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'description' => array(
'description' => __( 'Payment gateway description on checkout.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'order' => array(
'description' => __( 'Payment gateway sort order.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'absint',
),
),
'enabled' => array(
'description' => __( 'Payment gateway enabled status.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
),
'method_title' => array(
'description' => __( 'Payment gateway method title.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'method_description' => array(
'description' => __( 'Payment gateway method description.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'settings' => array(
'description' => __( 'Payment gateway settings.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'id' => array(
'description' => __( 'A unique identifier for the setting.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'label' => array(
'description' => __( 'A human readable label for the setting used in interfaces.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'A human readable description for the setting used in interfaces.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'type' => array(
'description' => __( 'Type of setting.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'enum' => array( 'text', 'email', 'number', 'color', 'password', 'textarea', 'select', 'multiselect', 'radio', 'image_width', 'checkbox' ),
'readonly' => true,
),
'value' => array(
'description' => __( 'Setting value.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'default' => array(
'description' => __( 'Default value for the setting.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'tip' => array(
'description' => __( 'Additional help text shown to the user about the setting.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'placeholder' => array(
'description' => __( 'Placeholder text to be displayed in text inputs.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get any query params needed.
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Product Attribute Terms controller
*
* Handles requests to the products/attributes/<attribute_id>/terms endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Attribute Terms controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Product_Attribute_Terms_V1_Controller
*/
class WC_REST_Product_Attribute_Terms_V2_Controller extends WC_REST_Product_Attribute_Terms_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Product Attributes controller
*
* Handles requests to the products/attributes endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Attributes controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Product_Attributes_V1_Controller
*/
class WC_REST_Product_Attributes_V2_Controller extends WC_REST_Product_Attributes_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
}

View File

@ -0,0 +1,212 @@
<?php
/**
* REST API Product Categories controller
*
* Handles requests to the products/categories endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Categories controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Product_Categories_V1_Controller
*/
class WC_REST_Product_Categories_V2_Controller extends WC_REST_Product_Categories_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Prepare a single product category output for response.
*
* @param WP_Term $item Term object.
* @param WP_REST_Request $request Request instance.
* @return WP_REST_Response
*/
public function prepare_item_for_response( $item, $request ) {
// Get category display type.
$display_type = get_term_meta( $item->term_id, 'display_type', true );
// Get category order.
$menu_order = get_term_meta( $item->term_id, 'order', true );
$data = array(
'id' => (int) $item->term_id,
'name' => $item->name,
'slug' => $item->slug,
'parent' => (int) $item->parent,
'description' => $item->description,
'display' => $display_type ? $display_type : 'default',
'image' => null,
'menu_order' => (int) $menu_order,
'count' => (int) $item->count,
);
// Get category image.
$image_id = get_term_meta( $item->term_id, 'thumbnail_id', true );
if ( $image_id ) {
$attachment = get_post( $image_id );
$data['image'] = array(
'id' => (int) $image_id,
'date_created' => wc_rest_prepare_date_response( $attachment->post_date ),
'date_created_gmt' => wc_rest_prepare_date_response( $attachment->post_date_gmt ),
'date_modified' => wc_rest_prepare_date_response( $attachment->post_modified ),
'date_modified_gmt' => wc_rest_prepare_date_response( $attachment->post_modified_gmt ),
'src' => wp_get_attachment_url( $image_id ),
'title' => get_the_title( $attachment ),
'alt' => get_post_meta( $image_id, '_wp_attachment_image_alt', true ),
);
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item, $request ) );
/**
* Filter a term item returned from the API.
*
* Allows modification of the term data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $item The original term object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( "woocommerce_rest_prepare_{$this->taxonomy}", $response, $item, $request );
}
/**
* Get the Category schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => $this->taxonomy,
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Category name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource unique to its type.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_title',
),
),
'parent' => array(
'description' => __( 'The ID for the parent of the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'description' => array(
'description' => __( 'HTML description of the resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'wp_filter_post_kses',
),
),
'display' => array(
'description' => __( 'Category archive display type.', 'woocommerce' ),
'type' => 'string',
'default' => 'default',
'enum' => array( 'default', 'products', 'subcategories', 'both' ),
'context' => array( 'view', 'edit' ),
),
'image' => array(
'description' => __( 'Image data.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'id' => array(
'description' => __( 'Image ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'date_created' => array(
'description' => __( "The date the image was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created_gmt' => array(
'description' => __( 'The date the image was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the image was last modified, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified_gmt' => array(
'description' => __( 'The date the image was last modified, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'src' => array(
'description' => __( 'Image URL.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit' ),
),
'title' => array(
'description' => __( 'Image name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'alt' => array(
'description' => __( 'Image alternative text.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
'menu_order' => array(
'description' => __( 'Menu order, used to custom sort the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'count' => array(
'description' => __( 'Number of published products for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,199 @@
<?php
/**
* REST API Product Reviews Controller
*
* Handles requests to /products/<product_id>/reviews.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Reviews Controller Class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Product_Reviews_V1_Controller
*/
class WC_REST_Product_Reviews_V2_Controller extends WC_REST_Product_Reviews_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'products/(?P<product_id>[\d]+)/reviews';
/**
* Register the routes for product reviews.
*/
public function register_routes() {
parent::register_routes();
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/batch', array(
'args' => array(
'product_id' => array(
'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'batch_items' ),
'permission_callback' => array( $this, 'batch_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_batch_schema' ),
)
);
}
/**
* Check if a given request has access to batch manage product reviews.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function batch_items_permissions_check( $request ) {
if ( ! wc_rest_check_post_permissions( 'product', 'batch' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Prepare a single product review output for response.
*
* @param WP_Comment $review Product review object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $review, $request ) {
$data = array(
'id' => (int) $review->comment_ID,
'date_created' => wc_rest_prepare_date_response( $review->comment_date ),
'date_created_gmt' => wc_rest_prepare_date_response( $review->comment_date_gmt ),
'review' => $review->comment_content,
'rating' => (int) get_comment_meta( $review->comment_ID, 'rating', true ),
'name' => $review->comment_author,
'email' => $review->comment_author_email,
'verified' => wc_review_is_from_verified_owner( $review->comment_ID ),
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $review, $request ) );
/**
* Filter product reviews object returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param WP_Comment $review Product review object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_product_review', $response, $review, $request );
}
/**
* Bulk create, update and delete items.
*
* @since 3.0.0
* @param WP_REST_Request $request Full details about the request.
* @return array Of WP_Error or WP_REST_Response.
*/
public function batch_items( $request ) {
$items = array_filter( $request->get_params() );
$params = $request->get_url_params();
$product_id = $params['product_id'];
$body_params = array();
foreach ( array( 'update', 'create', 'delete' ) as $batch_type ) {
if ( ! empty( $items[ $batch_type ] ) ) {
$injected_items = array();
foreach ( $items[ $batch_type ] as $item ) {
$injected_items[] = is_array( $item ) ? array_merge( array( 'product_id' => $product_id ), $item ) : $item;
}
$body_params[ $batch_type ] = $injected_items;
}
}
$request = new WP_REST_Request( $request->get_method() );
$request->set_body_params( $body_params );
return parent::batch_items( $request );
}
/**
* Get the Product Review's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'product_review',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'review' => array(
'description' => __( 'The content of the review.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'date_created' => array(
'description' => __( "The date the review was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'date_created_gmt' => array(
'description' => __( 'The date the review was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'rating' => array(
'description' => __( 'Review rating (0 to 5).', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'name' => array(
'description' => __( 'Reviewer name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'email' => array(
'description' => __( 'Reviewer email.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'verified' => array(
'description' => __( 'Shows if the reviewer bought the product or not.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Product Shipping Classes controller
*
* Handles requests to the products/shipping_classes endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Shipping Classes controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Product_Shipping_Classes_V1_Controller
*/
class WC_REST_Product_Shipping_Classes_V2_Controller extends WC_REST_Product_Shipping_Classes_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Product Tags controller
*
* Handles requests to the products/tags endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Tags controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Product_Tags_V1_Controller
*/
class WC_REST_Product_Tags_V2_Controller extends WC_REST_Product_Tags_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
}

View File

@ -0,0 +1,996 @@
<?php
/**
* REST API variations controller
*
* Handles requests to the /products/<product_id>/variations endpoints.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API variations controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Products_V2_Controller
*/
class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'products/(?P<product_id>[\d]+)/variations';
/**
* Post type.
*
* @var string
*/
protected $post_type = 'product_variation';
/**
* Register the routes for products.
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base, array(
'args' => array(
'product_id' => array(
'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
'args' => array(
'product_id' => array(
'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),
'type' => 'integer',
),
'id' => array(
'description' => __( 'Unique identifier for the variation.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param(
array(
'default' => 'view',
)
),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
'type' => 'boolean',
'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/batch', array(
'args' => array(
'product_id' => array(
'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'batch_items' ),
'permission_callback' => array( $this, 'batch_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_batch_schema' ),
)
);
}
/**
* Get object.
*
* @since 3.0.0
* @param int $id Object ID.
* @return WC_Data
*/
protected function get_object( $id ) {
return wc_get_product( $id );
}
/**
* 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 ) {
$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() ) ) {
return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
// Check if variation belongs to the correct parent product.
if ( $object && 0 !== $object->get_parent_id() && absint( $request['product_id'] ) !== $object->get_parent_id() ) {
return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Parent product does not match current variation.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Prepare a single variation output for response.
*
* @since 3.0.0
* @param WC_Data $object Object data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
public function prepare_object_for_response( $object, $request ) {
$data = array(
'id' => $object->get_id(),
'date_created' => wc_rest_prepare_date_response( $object->get_date_created(), false ),
'date_created_gmt' => wc_rest_prepare_date_response( $object->get_date_created() ),
'date_modified' => wc_rest_prepare_date_response( $object->get_date_modified(), false ),
'date_modified_gmt' => wc_rest_prepare_date_response( $object->get_date_modified() ),
'description' => wc_format_content( $object->get_description() ),
'permalink' => $object->get_permalink(),
'sku' => $object->get_sku(),
'price' => $object->get_price(),
'regular_price' => $object->get_regular_price(),
'sale_price' => $object->get_sale_price(),
'date_on_sale_from' => wc_rest_prepare_date_response( $object->get_date_on_sale_from(), false ),
'date_on_sale_from_gmt' => wc_rest_prepare_date_response( $object->get_date_on_sale_from() ),
'date_on_sale_to' => wc_rest_prepare_date_response( $object->get_date_on_sale_to(), false ),
'date_on_sale_to_gmt' => wc_rest_prepare_date_response( $object->get_date_on_sale_to() ),
'on_sale' => $object->is_on_sale(),
'visible' => $object->is_visible(),
'purchasable' => $object->is_purchasable(),
'virtual' => $object->is_virtual(),
'downloadable' => $object->is_downloadable(),
'downloads' => $this->get_downloads( $object ),
'download_limit' => '' !== $object->get_download_limit() ? (int) $object->get_download_limit() : -1,
'download_expiry' => '' !== $object->get_download_expiry() ? (int) $object->get_download_expiry() : -1,
'tax_status' => $object->get_tax_status(),
'tax_class' => $object->get_tax_class(),
'manage_stock' => $object->managing_stock(),
'stock_quantity' => $object->get_stock_quantity(),
'in_stock' => $object->is_in_stock(),
'backorders' => $object->get_backorders(),
'backorders_allowed' => $object->backorders_allowed(),
'backordered' => $object->is_on_backorder(),
'weight' => $object->get_weight(),
'dimensions' => array(
'length' => $object->get_length(),
'width' => $object->get_width(),
'height' => $object->get_height(),
),
'shipping_class' => $object->get_shipping_class(),
'shipping_class_id' => $object->get_shipping_class_id(),
'image' => current( $this->get_images( $object ) ),
'attributes' => $this->get_attributes( $object ),
'menu_order' => $object->get_menu_order(),
'meta_data' => $object->get_meta_data(),
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $object, $request ) );
/**
* Filter the data for a response.
*
* The dynamic portion of the hook name, $this->post_type,
* refers to object type being prepared for the response.
*
* @param WP_REST_Response $response The response object.
* @param WC_Data $object Object data.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "woocommerce_rest_prepare_{$this->post_type}_object", $response, $object, $request );
}
/**
* Prepare objects query.
*
* @since 3.0.0
* @param WP_REST_Request $request Full details about the request.
* @return array
*/
protected function prepare_objects_query( $request ) {
$args = parent::prepare_objects_query( $request );
$args['post_parent'] = $request['product_id'];
return $args;
}
/**
* Prepare a single variation for create or update.
*
* @param WP_REST_Request $request Request object.
* @param bool $creating If is creating a new object.
* @return WP_Error|WC_Data
*/
protected function prepare_object_for_database( $request, $creating = false ) {
if ( isset( $request['id'] ) ) {
$variation = wc_get_product( absint( $request['id'] ) );
} else {
$variation = new WC_Product_Variation();
}
// Update parent ID just once.
if ( 0 === $variation->get_parent_id() ) {
$variation->set_parent_id( absint( $request['product_id'] ) );
}
// Status.
if ( isset( $request['visible'] ) ) {
$variation->set_status( false === $request['visible'] ? 'private' : 'publish' );
}
// SKU.
if ( isset( $request['sku'] ) ) {
$variation->set_sku( wc_clean( $request['sku'] ) );
}
// Thumbnail.
if ( isset( $request['image'] ) ) {
if ( is_array( $request['image'] ) && ! empty( $request['image'] ) ) {
$image = $request['image'];
if ( is_array( $image ) ) {
$image['position'] = 0;
}
$variation = $this->set_product_images( $variation, array( $image ) );
} else {
$variation->set_image_id( '' );
}
}
// Virtual variation.
if ( isset( $request['virtual'] ) ) {
$variation->set_virtual( $request['virtual'] );
}
// Downloadable variation.
if ( isset( $request['downloadable'] ) ) {
$variation->set_downloadable( $request['downloadable'] );
}
// Downloads.
if ( $variation->get_downloadable() ) {
// Downloadable files.
if ( isset( $request['downloads'] ) && is_array( $request['downloads'] ) ) {
$variation = $this->save_downloadable_files( $variation, $request['downloads'] );
}
// Download limit.
if ( isset( $request['download_limit'] ) ) {
$variation->set_download_limit( $request['download_limit'] );
}
// Download expiry.
if ( isset( $request['download_expiry'] ) ) {
$variation->set_download_expiry( $request['download_expiry'] );
}
}
// Shipping data.
$variation = $this->save_product_shipping_data( $variation, $request );
// Stock handling.
if ( isset( $request['manage_stock'] ) ) {
if ( 'parent' === $request['manage_stock'] ) {
$variation->set_manage_stock( false ); // This just indicates the variation does not manage stock, but the parent does.
} else {
$variation->set_manage_stock( wc_string_to_bool( $request['manage_stock'] ) );
}
}
if ( isset( $request['in_stock'] ) ) {
$variation->set_stock_status( true === $request['in_stock'] ? 'instock' : 'outofstock' );
}
if ( isset( $request['backorders'] ) ) {
$variation->set_backorders( $request['backorders'] );
}
if ( $variation->get_manage_stock() ) {
if ( isset( $request['stock_quantity'] ) ) {
$variation->set_stock_quantity( $request['stock_quantity'] );
} elseif ( isset( $request['inventory_delta'] ) ) {
$stock_quantity = wc_stock_amount( $variation->get_stock_quantity() );
$stock_quantity += wc_stock_amount( $request['inventory_delta'] );
$variation->set_stock_quantity( $stock_quantity );
}
} else {
$variation->set_backorders( 'no' );
$variation->set_stock_quantity( '' );
}
// Regular Price.
if ( isset( $request['regular_price'] ) ) {
$variation->set_regular_price( $request['regular_price'] );
}
// Sale Price.
if ( isset( $request['sale_price'] ) ) {
$variation->set_sale_price( $request['sale_price'] );
}
if ( isset( $request['date_on_sale_from'] ) ) {
$variation->set_date_on_sale_from( $request['date_on_sale_from'] );
}
if ( isset( $request['date_on_sale_from_gmt'] ) ) {
$variation->set_date_on_sale_from( $request['date_on_sale_from_gmt'] ? strtotime( $request['date_on_sale_from_gmt'] ) : null );
}
if ( isset( $request['date_on_sale_to'] ) ) {
$variation->set_date_on_sale_to( $request['date_on_sale_to'] );
}
if ( isset( $request['date_on_sale_to_gmt'] ) ) {
$variation->set_date_on_sale_to( $request['date_on_sale_to_gmt'] ? strtotime( $request['date_on_sale_to_gmt'] ) : null );
}
// Tax class.
if ( isset( $request['tax_class'] ) ) {
$variation->set_tax_class( $request['tax_class'] );
}
// Description.
if ( isset( $request['description'] ) ) {
$variation->set_description( wp_kses_post( $request['description'] ) );
}
// Update taxonomies.
if ( isset( $request['attributes'] ) ) {
$attributes = array();
$parent = wc_get_product( $variation->get_parent_id() );
$parent_attributes = $parent->get_attributes();
foreach ( $request['attributes'] as $attribute ) {
$attribute_id = 0;
$attribute_name = '';
// Check ID for global attributes or name for product attributes.
if ( ! empty( $attribute['id'] ) ) {
$attribute_id = absint( $attribute['id'] );
$raw_attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id );
} elseif ( ! empty( $attribute['name'] ) ) {
$raw_attribute_name = sanitize_title( $attribute['name'] );
}
if ( ! $attribute_id && ! $raw_attribute_name ) {
continue;
}
$attribute_name = sanitize_title( $raw_attribute_name );
if ( ! isset( $parent_attributes[ $attribute_name ] ) || ! $parent_attributes[ $attribute_name ]->get_variation() ) {
continue;
}
$attribute_key = sanitize_title( $parent_attributes[ $attribute_name ]->get_name() );
$attribute_value = isset( $attribute['option'] ) ? wc_clean( stripslashes( $attribute['option'] ) ) : '';
if ( $parent_attributes[ $attribute_name ]->is_taxonomy() ) {
// If dealing with a taxonomy, we need to get the slug from the name posted to the API.
$term = get_term_by( 'name', $attribute_value, $raw_attribute_name ); // @codingStandardsIgnoreLine
if ( $term && ! is_wp_error( $term ) ) {
$attribute_value = $term->slug;
} else {
$attribute_value = sanitize_title( $attribute_value );
}
}
$attributes[ $attribute_key ] = $attribute_value;
}
$variation->set_attributes( $attributes );
}
// Menu order.
if ( $request['menu_order'] ) {
$variation->set_menu_order( $request['menu_order'] );
}
// Meta data.
if ( is_array( $request['meta_data'] ) ) {
foreach ( $request['meta_data'] as $meta ) {
$variation->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
}
}
/**
* Filters an object before it is inserted via the REST API.
*
* The dynamic portion of the hook name, `$this->post_type`,
* refers to the object type slug.
*
* @param WC_Data $variation Object object.
* @param WP_REST_Request $request Request object.
* @param bool $creating If is creating a new object.
*/
return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $variation, $request, $creating );
}
/**
* Clear caches here so in sync with any new variations.
*
* @param WC_Data $object Object data.
*/
public function clear_transients( $object ) {
wc_delete_product_transients( $object->get_parent_id() );
wp_cache_delete( 'product-' . $object->get_parent_id(), 'products' );
}
/**
* Delete a variation.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error|WP_REST_Response
*/
public function delete_item( $request ) {
$force = (bool) $request['force'];
$object = $this->get_object( (int) $request['id'] );
$result = false;
if ( ! $object || 0 === $object->get_id() ) {
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() ) ) {
return new WP_Error(
/* translators: %s: post type */
"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 ) {
$object->delete( true );
$result = 0 === $object->get_id();
} else {
// If we don't support trashing for this type, error out.
if ( ! $supports_trash ) {
return new WP_Error(
/* translators: %s: post type */
'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.
if ( is_callable( array( $object, 'get_status' ) ) ) {
if ( 'trash' === $object->get_status() ) {
return new WP_Error(
/* translators: %s: post type */
'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();
}
}
if ( ! $result ) {
return new WP_Error(
/* translators: %s: post type */
'woocommerce_rest_cannot_delete', sprintf( __( 'The %s cannot be deleted.', 'woocommerce' ), $this->post_type ), array(
'status' => 500,
)
);
}
// Delete parent product transients.
if ( 0 !== $object->get_parent_id() ) {
wc_delete_product_transients( $object->get_parent_id() );
}
/**
* 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;
}
/**
* Bulk create, update and delete items.
*
* @since 3.0.0
* @param WP_REST_Request $request Full details about the request.
* @return array Of WP_Error or WP_REST_Response.
*/
public function batch_items( $request ) {
$items = array_filter( $request->get_params() );
$params = $request->get_url_params();
$query = $request->get_query_params();
$product_id = $params['product_id'];
$body_params = array();
foreach ( array( 'update', 'create', 'delete' ) as $batch_type ) {
if ( ! empty( $items[ $batch_type ] ) ) {
$injected_items = array();
foreach ( $items[ $batch_type ] as $item ) {
$injected_items[] = is_array( $item ) ? array_merge(
array(
'product_id' => $product_id,
), $item
) : $item;
}
$body_params[ $batch_type ] = $injected_items;
}
}
$request = new WP_REST_Request( $request->get_method() );
$request->set_body_params( $body_params );
$request->set_query_params( $query );
return parent::batch_items( $request );
}
/**
* 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 ) {
$product_id = (int) $request['product_id'];
$base = str_replace( '(?P<product_id>[\d]+)', $product_id, $this->rest_base );
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $object->get_id() ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ),
),
'up' => array(
'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product_id ) ),
),
);
return $links;
}
/**
* Get the Variation's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$weight_unit = get_option( 'woocommerce_weight_unit' );
$dimension_unit = get_option( 'woocommerce_dimension_unit' );
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => $this->post_type,
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created' => array(
'description' => __( "The date the variation was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the variation was last modified, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'Variation description.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'permalink' => array(
'description' => __( 'Variation URL.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'sku' => array(
'description' => __( 'Unique identifier.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'price' => array(
'description' => __( 'Current variation price.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'regular_price' => array(
'description' => __( 'Variation regular price.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'sale_price' => array(
'description' => __( 'Variation sale price.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'date_on_sale_from' => array(
'description' => __( "Start date of sale price, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'date_on_sale_from_gmt' => array(
'description' => __( 'Start date of sale price, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'date_on_sale_to' => array(
'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'date_on_sale_to_gmt' => array(
'description' => __( 'End date of sale price, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'on_sale' => array(
'description' => __( 'Shows if the variation is on sale.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'visible' => array(
'description' => __( "Define if the variation is visible on the product's page.", 'woocommerce' ),
'type' => 'boolean',
'default' => true,
'context' => array( 'view', 'edit' ),
),
'purchasable' => array(
'description' => __( 'Shows if the variation can be bought.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'virtual' => array(
'description' => __( 'If the variation is virtual.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'downloadable' => array(
'description' => __( 'If the variation is downloadable.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'downloads' => array(
'description' => __( 'List of downloadable files.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'File ID.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'name' => array(
'description' => __( 'File name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'file' => array(
'description' => __( 'File URL.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
),
'download_limit' => array(
'description' => __( 'Number of times downloadable files can be downloaded after purchase.', 'woocommerce' ),
'type' => 'integer',
'default' => -1,
'context' => array( 'view', 'edit' ),
),
'download_expiry' => array(
'description' => __( 'Number of days until access to downloadable files expires.', 'woocommerce' ),
'type' => 'integer',
'default' => -1,
'context' => array( 'view', 'edit' ),
),
'tax_status' => array(
'description' => __( 'Tax status.', 'woocommerce' ),
'type' => 'string',
'default' => 'taxable',
'enum' => array( 'taxable', 'shipping', 'none' ),
'context' => array( 'view', 'edit' ),
),
'tax_class' => array(
'description' => __( 'Tax class.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'manage_stock' => array(
'description' => __( 'Stock management at variation level.', 'woocommerce' ),
'type' => array( 'boolean', 'null' ),
'default' => false,
'context' => array( 'view', 'edit' ),
),
'stock_quantity' => array(
'description' => __( 'Stock quantity.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'in_stock' => array(
'description' => __( 'Controls whether or not the variation is listed as "in stock" or "out of stock" on the frontend.', 'woocommerce' ),
'type' => 'boolean',
'default' => true,
'context' => array( 'view', 'edit' ),
),
'backorders' => array(
'description' => __( 'If managing stock, this controls if backorders are allowed.', 'woocommerce' ),
'type' => 'string',
'default' => 'no',
'enum' => array( 'no', 'notify', 'yes' ),
'context' => array( 'view', 'edit' ),
),
'backorders_allowed' => array(
'description' => __( 'Shows if backorders are allowed.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'backordered' => array(
'description' => __( 'Shows if the variation is on backordered.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'weight' => array(
/* translators: %s: weight unit */
'description' => sprintf( __( 'Variation weight (%s).', 'woocommerce' ), $weight_unit ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'dimensions' => array(
'description' => __( 'Variation dimensions.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'length' => array(
/* translators: %s: dimension unit */
'description' => sprintf( __( 'Variation length (%s).', 'woocommerce' ), $dimension_unit ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'width' => array(
/* translators: %s: dimension unit */
'description' => sprintf( __( 'Variation width (%s).', 'woocommerce' ), $dimension_unit ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'height' => array(
/* translators: %s: dimension unit */
'description' => sprintf( __( 'Variation height (%s).', 'woocommerce' ), $dimension_unit ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
'shipping_class' => array(
'description' => __( 'Shipping class slug.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'shipping_class_id' => array(
'description' => __( 'Shipping class ID.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'image' => array(
'description' => __( 'Variation image data.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'id' => array(
'description' => __( 'Image ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'date_created' => array(
'description' => __( "The date the image was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created_gmt' => array(
'description' => __( 'The date the image was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the image was last modified, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified_gmt' => array(
'description' => __( 'The date the image was last modified, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'src' => array(
'description' => __( 'Image URL.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit' ),
),
'name' => array(
'description' => __( 'Image name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'alt' => array(
'description' => __( 'Image alternative text.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'position' => array(
'description' => __( 'Image position. 0 means that the image is featured.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
),
),
'attributes' => array(
'description' => __( 'List of attributes.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Attribute ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'name' => array(
'description' => __( 'Attribute name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'option' => array(
'description' => __( 'Selected attribute term name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
),
'menu_order' => array(
'description' => __( 'Menu order, used to custom sort products.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'meta_data' => array(
'description' => __( 'Meta data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Meta ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'key' => array(
'description' => __( 'Meta key.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ),
'context' => array( 'view', 'edit' ),
),
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Reports controller
*
* Handles requests to the reports/sales endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Report Sales controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Report_Sales_V1_Controller
*/
class WC_REST_Report_Sales_V2_Controller extends WC_REST_Report_Sales_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Reports controller
*
* Handles requests to the reports/top_sellers endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Report Top Sellers controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Report_Top_Sellers_V1_Controller
*/
class WC_REST_Report_Top_Sellers_V2_Controller extends WC_REST_Report_Top_Sellers_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Reports controller
*
* Handles requests to the reports endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Reports controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Reports_V1_Controller
*/
class WC_REST_Reports_V2_Controller extends WC_REST_Reports_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
}

View File

@ -0,0 +1,587 @@
<?php
/**
* REST API Setting Options controller
*
* Handles requests to the /settings/$group/$setting endpoints.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Setting Options controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller {
/**
* WP REST API namespace/version.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'settings/(?P<group_id>[\w-]+)';
/**
* Register routes.
*
* @since 3.0.0
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base, array(
'args' => array(
'group' => array(
'description' => __( 'Settings group ID.', 'woocommerce' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/batch', array(
'args' => array(
'group' => array(
'description' => __( 'Settings group ID.', 'woocommerce' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'batch_items' ),
'permission_callback' => array( $this, 'update_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_batch_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<id>[\w-]+)', array(
'args' => array(
'group' => array(
'description' => __( 'Settings group ID.', 'woocommerce' ),
'type' => 'string',
),
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Return a single setting.
*
* @since 3.0.0
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$setting = $this->get_setting( $request['group_id'], $request['id'] );
if ( is_wp_error( $setting ) ) {
return $setting;
}
$response = $this->prepare_item_for_response( $setting, $request );
return rest_ensure_response( $response );
}
/**
* Return all settings in a group.
*
* @since 3.0.0
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$settings = $this->get_group_settings( $request['group_id'] );
if ( is_wp_error( $settings ) ) {
return $settings;
}
$data = array();
foreach ( $settings as $setting_obj ) {
$setting = $this->prepare_item_for_response( $setting_obj, $request );
$setting = $this->prepare_response_for_collection( $setting );
if ( $this->is_setting_type_valid( $setting['type'] ) ) {
$data[] = $setting;
}
}
return rest_ensure_response( $data );
}
/**
* Get all settings in a group.
*
* @since 3.0.0
* @param string $group_id Group ID.
* @return array|WP_Error
*/
public function get_group_settings( $group_id ) {
if ( empty( $group_id ) ) {
return new WP_Error( 'rest_setting_setting_group_invalid', __( 'Invalid setting group.', 'woocommerce' ), array( 'status' => 404 ) );
}
$settings = apply_filters( 'woocommerce_settings-' . $group_id, array() );
if ( empty( $settings ) ) {
return new WP_Error( 'rest_setting_setting_group_invalid', __( 'Invalid setting group.', 'woocommerce' ), array( 'status' => 404 ) );
}
$filtered_settings = array();
foreach ( $settings as $setting ) {
$option_key = $setting['option_key'];
$setting = $this->filter_setting( $setting );
$default = isset( $setting['default'] ) ? $setting['default'] : '';
// Get the option value.
if ( is_array( $option_key ) ) {
$option = get_option( $option_key[0] );
$setting['value'] = isset( $option[ $option_key[1] ] ) ? $option[ $option_key[1] ] : $default;
} else {
$admin_setting_value = WC_Admin_Settings::get_option( $option_key, $default );
$setting['value'] = $admin_setting_value;
}
if ( 'multi_select_countries' === $setting['type'] ) {
$setting['options'] = WC()->countries->get_countries();
$setting['type'] = 'multiselect';
} elseif ( 'single_select_country' === $setting['type'] ) {
$setting['type'] = 'select';
$setting['options'] = $this->get_countries_and_states();
}
$filtered_settings[] = $setting;
}
return $filtered_settings;
}
/**
* Returns a list of countries and states for use in the base location setting.
*
* @since 3.0.7
* @return array Array of states and countries.
*/
private function get_countries_and_states() {
$countries = WC()->countries->get_countries();
if ( ! $countries ) {
return array();
}
$output = array();
foreach ( $countries as $key => $value ) {
$states = WC()->countries->get_states( $key );
if ( $states ) {
foreach ( $states as $state_key => $state_value ) {
$output[ $key . ':' . $state_key ] = $value . ' - ' . $state_value;
}
} else {
$output[ $key ] = $value;
}
}
return $output;
}
/**
* Get setting data.
*
* @since 3.0.0
* @param string $group_id Group ID.
* @param string $setting_id Setting ID.
* @return stdClass|WP_Error
*/
public function get_setting( $group_id, $setting_id ) {
if ( empty( $setting_id ) ) {
return new WP_Error( 'rest_setting_setting_invalid', __( 'Invalid setting.', 'woocommerce' ), array( 'status' => 404 ) );
}
$settings = $this->get_group_settings( $group_id );
if ( is_wp_error( $settings ) ) {
return $settings;
}
$array_key = array_keys( wp_list_pluck( $settings, 'id' ), $setting_id );
if ( empty( $array_key ) ) {
return new WP_Error( 'rest_setting_setting_invalid', __( 'Invalid setting.', 'woocommerce' ), array( 'status' => 404 ) );
}
$setting = $settings[ $array_key[0] ];
if ( ! $this->is_setting_type_valid( $setting['type'] ) ) {
return new WP_Error( 'rest_setting_setting_invalid', __( 'Invalid setting.', 'woocommerce' ), array( 'status' => 404 ) );
}
return $setting;
}
/**
* Bulk create, update and delete items.
*
* @since 3.0.0
* @param WP_REST_Request $request Full details about the request.
* @return array Of WP_Error or WP_REST_Response.
*/
public function batch_items( $request ) {
// Get the request params.
$items = array_filter( $request->get_params() );
/*
* Since our batch settings update is group-specific and matches based on the route,
* we inject the URL parameters (containing group) into the batch items
*/
if ( ! empty( $items['update'] ) ) {
$to_update = array();
foreach ( $items['update'] as $item ) {
$to_update[] = array_merge( $request->get_url_params(), $item );
}
$request = new WP_REST_Request( $request->get_method() );
$request->set_body_params( array( 'update' => $to_update ) );
}
return parent::batch_items( $request );
}
/**
* Update a single setting in a group.
*
* @since 3.0.0
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
$setting = $this->get_setting( $request['group_id'], $request['id'] );
if ( is_wp_error( $setting ) ) {
return $setting;
}
if ( is_callable( array( $this, 'validate_setting_' . $setting['type'] . '_field' ) ) ) {
$value = $this->{'validate_setting_' . $setting['type'] . '_field'}( $request['value'], $setting );
} else {
$value = $this->validate_setting_text_field( $request['value'], $setting );
}
if ( is_wp_error( $value ) ) {
return $value;
}
if ( is_array( $setting['option_key'] ) ) {
$setting['value'] = $value;
$option_key = $setting['option_key'];
$prev = get_option( $option_key[0] );
$prev[ $option_key[1] ] = $request['value'];
update_option( $option_key[0], $prev );
} else {
$update_data = array();
$update_data[ $setting['option_key'] ] = $value;
$setting['value'] = $value;
WC_Admin_Settings::save_fields( array( $setting ), $update_data );
}
$response = $this->prepare_item_for_response( $setting, $request );
return rest_ensure_response( $response );
}
/**
* Prepare a single setting object for response.
*
* @since 3.0.0
* @param object $item Setting object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $item, $request ) {
unset( $item['option_key'] );
$data = $this->filter_setting( $item );
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, empty( $request['context'] ) ? 'view' : $request['context'] );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $data['id'], $request['group_id'] ) );
return $response;
}
/**
* Prepare links for the request.
*
* @since 3.0.0
* @param string $setting_id Setting ID.
* @param string $group_id Group ID.
* @return array Links for the given setting.
*/
protected function prepare_links( $setting_id, $group_id ) {
$base = str_replace( '(?P<group_id>[\w-]+)', $group_id, $this->rest_base );
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $base, $setting_id ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ),
),
);
return $links;
}
/**
* Makes sure the current user has access to READ the settings APIs.
*
* @since 3.0.0
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Makes sure the current user has access to WRITE the settings APIs.
*
* @since 3.0.0
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|boolean
*/
public function update_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you cannot edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Filters out bad values from the settings array/filter so we
* only return known values via the API.
*
* @since 3.0.0
* @param array $setting Settings.
* @return array
*/
public function filter_setting( $setting ) {
$setting = array_intersect_key(
$setting,
array_flip( array_filter( array_keys( $setting ), array( $this, 'allowed_setting_keys' ) ) )
);
if ( empty( $setting['options'] ) ) {
unset( $setting['options'] );
}
if ( 'image_width' === $setting['type'] ) {
$setting = $this->cast_image_width( $setting );
}
return $setting;
}
/**
* For image_width, Crop can return "0" instead of false -- so we want
* to make sure we return these consistently the same we accept them.
*
* @todo remove in 4.0
* @since 3.0.0
* @param array $setting Settings.
* @return array
*/
public function cast_image_width( $setting ) {
foreach ( array( 'default', 'value' ) as $key ) {
if ( isset( $setting[ $key ] ) ) {
$setting[ $key ]['width'] = intval( $setting[ $key ]['width'] );
$setting[ $key ]['height'] = intval( $setting[ $key ]['height'] );
$setting[ $key ]['crop'] = (bool) $setting[ $key ]['crop'];
}
}
return $setting;
}
/**
* Callback for allowed keys for each setting response.
*
* @since 3.0.0
* @param string $key Key to check.
* @return boolean
*/
public function allowed_setting_keys( $key ) {
return in_array(
$key, array(
'id',
'label',
'description',
'default',
'tip',
'placeholder',
'type',
'options',
'value',
'option_key',
)
);
}
/**
* Boolean for if a setting type is a valid supported setting type.
*
* @since 3.0.0
* @param string $type Type.
* @return bool
*/
public function is_setting_type_valid( $type ) {
return in_array(
$type, array(
'text', // Validates with validate_setting_text_field.
'email', // Validates with validate_setting_text_field.
'number', // Validates with validate_setting_text_field.
'color', // Validates with validate_setting_text_field.
'password', // Validates with validate_setting_text_field.
'textarea', // Validates with validate_setting_textarea_field.
'select', // Validates with validate_setting_select_field.
'multiselect', // Validates with validate_setting_multiselect_field.
'radio', // Validates with validate_setting_radio_field (-> validate_setting_select_field).
'checkbox', // Validates with validate_setting_checkbox_field.
'image_width', // Validates with validate_setting_image_width_field.
'thumbnail_cropping', // Validates with validate_setting_text_field.
)
);
}
/**
* Get the settings schema, conforming to JSON Schema.
*
* @since 3.0.0
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'setting',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'A unique identifier for the setting.', 'woocommerce' ),
'type' => 'string',
'arg_options' => array(
'sanitize_callback' => 'sanitize_title',
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'label' => array(
'description' => __( 'A human readable label for the setting used in interfaces.', 'woocommerce' ),
'type' => 'string',
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'A human readable description for the setting used in interfaces.', 'woocommerce' ),
'type' => 'string',
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'value' => array(
'description' => __( 'Setting value.', 'woocommerce' ),
'type' => array( 'string', 'array', 'null' ),
'items' => array(
'type' => array( 'string', 'null' ),
),
'context' => array( 'view', 'edit' ),
),
'default' => array(
'description' => __( 'Default value for the setting.', 'woocommerce' ),
'type' => array( 'string', 'array', 'null' ),
'items' => array(
'type' => array( 'string', 'null' ),
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'tip' => array(
'description' => __( 'Additional help text shown to the user about the setting.', 'woocommerce' ),
'type' => 'string',
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'placeholder' => array(
'description' => __( 'Placeholder text to be displayed in text inputs.', 'woocommerce' ),
'type' => 'string',
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'type' => array(
'description' => __( 'Type of setting.', 'woocommerce' ),
'type' => 'string',
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
'context' => array( 'view', 'edit' ),
'enum' => array( 'text', 'email', 'number', 'color', 'password', 'textarea', 'select', 'multiselect', 'radio', 'image_width', 'checkbox', 'thumbnail_cropping' ),
'readonly' => true,
),
'options' => array(
'description' => __( 'Array of options (key value pairs) for inputs such as select, multiselect, and radio buttons.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,232 @@
<?php
/**
* REST API Settings controller
*
* Handles requests to the /settings endpoints.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Settings controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Settings_V2_Controller extends WC_REST_Controller {
/**
* WP REST API namespace/version.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'settings';
/**
* Register routes.
*
* @since 3.0.0
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Get all settings groups items.
*
* @since 3.0.0
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$groups = apply_filters( 'woocommerce_settings_groups', array() );
if ( empty( $groups ) ) {
return new WP_Error( 'rest_setting_groups_empty', __( 'No setting groups have been registered.', 'woocommerce' ), array( 'status' => 500 ) );
}
$defaults = $this->group_defaults();
$filtered_groups = array();
foreach ( $groups as $group ) {
$sub_groups = array();
foreach ( $groups as $_group ) {
if ( ! empty( $_group['parent_id'] ) && $group['id'] === $_group['parent_id'] ) {
$sub_groups[] = $_group['id'];
}
}
$group['sub_groups'] = $sub_groups;
$group = wp_parse_args( $group, $defaults );
if ( ! is_null( $group['id'] ) && ! is_null( $group['label'] ) ) {
$group_obj = $this->filter_group( $group );
$group_data = $this->prepare_item_for_response( $group_obj, $request );
$group_data = $this->prepare_response_for_collection( $group_data );
$filtered_groups[] = $group_data;
}
}
$response = rest_ensure_response( $filtered_groups );
return $response;
}
/**
* Prepare links for the request.
*
* @param string $group_id Group ID.
* @return array Links for the given group.
*/
protected function prepare_links( $group_id ) {
$base = '/' . $this->namespace . '/' . $this->rest_base;
$links = array(
'options' => array(
'href' => rest_url( trailingslashit( $base ) . $group_id ),
),
);
return $links;
}
/**
* Prepare a report sales object for serialization.
*
* @since 3.0.0
* @param array $item Group object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $item, $request ) {
$context = empty( $request['context'] ) ? 'view' : $request['context'];
$data = $this->add_additional_fields_to_object( $item, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item['id'] ) );
return $response;
}
/**
* Filters out bad values from the groups array/filter so we
* only return known values via the API.
*
* @since 3.0.0
* @param array $group Group.
* @return array
*/
public function filter_group( $group ) {
return array_intersect_key(
$group,
array_flip( array_filter( array_keys( $group ), array( $this, 'allowed_group_keys' ) ) )
);
}
/**
* Callback for allowed keys for each group response.
*
* @since 3.0.0
* @param string $key Key to check.
* @return boolean
*/
public function allowed_group_keys( $key ) {
return in_array( $key, array( 'id', 'label', 'description', 'parent_id', 'sub_groups' ) );
}
/**
* Returns default settings for groups. null means the field is required.
*
* @since 3.0.0
* @return array
*/
protected function group_defaults() {
return array(
'id' => null,
'label' => null,
'description' => '',
'parent_id' => '',
'sub_groups' => array(),
);
}
/**
* Makes sure the current user has access to READ the settings APIs.
*
* @since 3.0.0
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get the groups schema, conforming to JSON Schema.
*
* @since 3.0.0
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'setting_group',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'A unique identifier that can be used to link settings together.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'label' => array(
'description' => __( 'A human readable label for the setting used in interfaces.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'A human readable description for the setting used in interfaces.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'parent_id' => array(
'description' => __( 'ID of parent grouping.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'sub_groups' => array(
'description' => __( 'IDs for settings sub groups.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,231 @@
<?php
/**
* REST API WC Shipping Methods controller
*
* Handles requests to the /shipping_methods endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* Shipping methods controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Shipping_Methods_V2_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'shipping_methods';
/**
* Register the route for /shipping_methods and /shipping_methods/<method>
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<id>[\w-]+)', array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Check whether a given request has permission to view shipping methods.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'shipping_methods', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to read a shipping method.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'shipping_methods', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get shipping methods.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$wc_shipping = WC_Shipping::instance();
$response = array();
foreach ( $wc_shipping->get_shipping_methods() as $id => $shipping_method ) {
$method = $this->prepare_item_for_response( $shipping_method, $request );
$method = $this->prepare_response_for_collection( $method );
$response[] = $method;
}
return rest_ensure_response( $response );
}
/**
* Get a single Shipping Method.
*
* @param WP_REST_Request $request Request data.
* @return WP_REST_Response|WP_Error
*/
public function get_item( $request ) {
$wc_shipping = WC_Shipping::instance();
$methods = $wc_shipping->get_shipping_methods();
if ( empty( $methods[ $request['id'] ] ) ) {
return new WP_Error( 'woocommerce_rest_shipping_method_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
}
$method = $methods[ $request['id'] ];
$response = $this->prepare_item_for_response( $method, $request );
return rest_ensure_response( $response );
}
/**
* Prepare a shipping method for response.
*
* @param WC_Shipping_Method $method Shipping method object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $method, $request ) {
$data = array(
'id' => $method->id,
'title' => $method->method_title,
'description' => $method->method_description,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $method, $request ) );
/**
* Filter shipping methods object returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param WC_Shipping_Method $method Shipping method object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_shipping_method', $response, $method, $request );
}
/**
* Prepare links for the request.
*
* @param WC_Shipping_Method $method Shipping method object.
* @param WP_REST_Request $request Request object.
* @return array
*/
protected function prepare_links( $method, $request ) {
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $method->id ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
),
);
return $links;
}
/**
* Get the shipping method schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'shipping_method',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Method ID.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'title' => array(
'description' => __( 'Shipping method title.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'Shipping method description.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get any query params needed.
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
}

View File

@ -0,0 +1,190 @@
<?php
/**
* REST API Shipping Zone Locations controller
*
* Handles requests to the /shipping/zones/<id>/locations endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Shipping Zone Locations class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Shipping_Zones_Controller_Base
*/
class WC_REST_Shipping_Zone_Locations_V2_Controller extends WC_REST_Shipping_Zones_Controller_Base {
/**
* Register the routes for Shipping Zone Locations.
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/locations', array(
'args' => array(
'id' => array(
'description' => __( 'Unique ID for the resource.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_items' ),
'permission_callback' => array( $this, 'update_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Get all Shipping Zone Locations.
*
* @param WP_REST_Request $request Request data.
* @return WP_REST_Response|WP_Error
*/
public function get_items( $request ) {
$zone = $this->get_zone( (int) $request['id'] );
if ( is_wp_error( $zone ) ) {
return $zone;
}
$locations = $zone->get_zone_locations();
$data = array();
foreach ( $locations as $location_obj ) {
$location = $this->prepare_item_for_response( $location_obj, $request );
$location = $this->prepare_response_for_collection( $location );
$data[] = $location;
}
return rest_ensure_response( $data );
}
/**
* Update all Shipping Zone Locations.
*
* @param WP_REST_Request $request Request data.
* @return WP_REST_Response|WP_Error
*/
public function update_items( $request ) {
$zone = $this->get_zone( (int) $request['id'] );
if ( is_wp_error( $zone ) ) {
return $zone;
}
if ( 0 === $zone->get_id() ) {
return new WP_Error( 'woocommerce_rest_shipping_zone_locations_invalid_zone', __( 'The "locations not covered by your other zones" zone cannot be updated.', 'woocommerce' ), array( 'status' => 403 ) );
}
$raw_locations = $request->get_json_params();
$locations = array();
foreach ( (array) $raw_locations as $raw_location ) {
if ( empty( $raw_location['code'] ) ) {
continue;
}
$type = ! empty( $raw_location['type'] ) ? sanitize_text_field( $raw_location['type'] ) : 'country';
if ( ! in_array( $type, array( 'postcode', 'state', 'country', 'continent' ), true ) ) {
continue;
}
$locations[] = array(
'code' => sanitize_text_field( $raw_location['code'] ),
'type' => sanitize_text_field( $type ),
);
}
$zone->set_locations( $locations );
$zone->save();
return $this->get_items( $request );
}
/**
* Prepare the Shipping Zone Location for the REST response.
*
* @param array $item Shipping Zone Location.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response
*/
public function prepare_item_for_response( $item, $request ) {
$context = empty( $request['context'] ) ? 'view' : $request['context'];
$data = $this->add_additional_fields_to_object( $item, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( (int) $request['id'] ) );
return $response;
}
/**
* Prepare links for the request.
*
* @param int $zone_id Given Shipping Zone ID.
* @return array Links for the given Shipping Zone Location.
*/
protected function prepare_links( $zone_id ) {
$base = '/' . $this->namespace . '/' . $this->rest_base . '/' . $zone_id;
$links = array(
'collection' => array(
'href' => rest_url( $base . '/locations' ),
),
'describes' => array(
'href' => rest_url( $base ),
),
);
return $links;
}
/**
* Get the Shipping Zone Locations schema, conforming to JSON Schema
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'shipping_zone_location',
'type' => 'object',
'properties' => array(
'code' => array(
'description' => __( 'Shipping zone location code.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'type' => array(
'description' => __( 'Shipping zone location type.', 'woocommerce' ),
'type' => 'string',
'default' => 'country',
'enum' => array(
'postcode',
'state',
'country',
'continent',
),
'context' => array( 'view', 'edit' ),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,541 @@
<?php
/**
* REST API Shipping Zone Methods controller
*
* Handles requests to the /shipping/zones/<id>/methods endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Shipping Zone Methods class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Shipping_Zones_Controller_Base
*/
class WC_REST_Shipping_Zone_Methods_V2_Controller extends WC_REST_Shipping_Zones_Controller_Base {
/**
* Register the routes for Shipping Zone Methods.
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<zone_id>[\d]+)/methods', array(
'args' => array(
'zone_id' => array(
'description' => __( 'Unique ID for the zone.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => array_merge(
$this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
'method_id' => array(
'required' => true,
'readonly' => false,
'description' => __( 'Shipping method ID.', 'woocommerce' ),
),
)
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<zone_id>[\d]+)/methods/(?P<instance_id>[\d]+)', array(
'args' => array(
'zone_id' => array(
'description' => __( 'Unique ID for the zone.', 'woocommerce' ),
'type' => 'integer',
),
'instance_id' => array(
'description' => __( 'Unique ID for the instance.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_items_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
'type' => 'boolean',
'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Get a single Shipping Zone Method.
*
* @param WP_REST_Request $request Request data.
* @return WP_REST_Response|WP_Error
*/
public function get_item( $request ) {
$zone = $this->get_zone( $request['zone_id'] );
if ( is_wp_error( $zone ) ) {
return $zone;
}
$instance_id = (int) $request['instance_id'];
$methods = $zone->get_shipping_methods();
$method = false;
foreach ( $methods as $method_obj ) {
if ( $instance_id === $method_obj->instance_id ) {
$method = $method_obj;
break;
}
}
if ( false === $method ) {
return new WP_Error( 'woocommerce_rest_shipping_zone_method_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
}
$data = $this->prepare_item_for_response( $method, $request );
return rest_ensure_response( $data );
}
/**
* Get all Shipping Zone Methods.
*
* @param WP_REST_Request $request Request data.
* @return WP_REST_Response|WP_Error
*/
public function get_items( $request ) {
$zone = $this->get_zone( $request['zone_id'] );
if ( is_wp_error( $zone ) ) {
return $zone;
}
$methods = $zone->get_shipping_methods();
$data = array();
foreach ( $methods as $method_obj ) {
$method = $this->prepare_item_for_response( $method_obj, $request );
$data[] = $method;
}
return rest_ensure_response( $data );
}
/**
* Create a new shipping zone method instance.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Request|WP_Error
*/
public function create_item( $request ) {
$method_id = $request['method_id'];
$zone = $this->get_zone( $request['zone_id'] );
if ( is_wp_error( $zone ) ) {
return $zone;
}
$instance_id = $zone->add_shipping_method( $method_id );
$methods = $zone->get_shipping_methods();
$method = false;
foreach ( $methods as $method_obj ) {
if ( $instance_id === $method_obj->instance_id ) {
$method = $method_obj;
break;
}
}
if ( false === $method ) {
return new WP_Error( 'woocommerce_rest_shipping_zone_not_created', __( 'Resource cannot be created.', 'woocommerce' ), array( 'status' => 500 ) );
}
$method = $this->update_fields( $instance_id, $method, $request );
if ( is_wp_error( $method ) ) {
return $method;
}
$data = $this->prepare_item_for_response( $method, $request );
return rest_ensure_response( $data );
}
/**
* Delete a shipping method instance.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function delete_item( $request ) {
$zone = $this->get_zone( $request['zone_id'] );
if ( is_wp_error( $zone ) ) {
return $zone;
}
$instance_id = (int) $request['instance_id'];
$force = $request['force'];
$methods = $zone->get_shipping_methods();
$method = false;
foreach ( $methods as $method_obj ) {
if ( $instance_id === $method_obj->instance_id ) {
$method = $method_obj;
break;
}
}
if ( false === $method ) {
return new WP_Error( 'woocommerce_rest_shipping_zone_method_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
}
$method = $this->update_fields( $instance_id, $method, $request );
if ( is_wp_error( $method ) ) {
return $method;
}
$request->set_param( 'context', 'view' );
$response = $this->prepare_item_for_response( $method, $request );
// Actually delete.
if ( $force ) {
$zone->delete_shipping_method( $instance_id );
} else {
return new WP_Error( 'rest_trash_not_supported', __( 'Shipping methods do not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
}
/**
* Fires after a product review is deleted via the REST API.
*
* @param object $method
* @param WP_REST_Response $response The response data.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( 'rest_delete_product_review', $method, $response, $request );
return $response;
}
/**
* Update A Single Shipping Zone Method.
*
* @param WP_REST_Request $request Request data.
* @return WP_REST_Response|WP_Error
*/
public function update_item( $request ) {
$zone = $this->get_zone( $request['zone_id'] );
if ( is_wp_error( $zone ) ) {
return $zone;
}
$instance_id = (int) $request['instance_id'];
$methods = $zone->get_shipping_methods();
$method = false;
foreach ( $methods as $method_obj ) {
if ( $instance_id === $method_obj->instance_id ) {
$method = $method_obj;
break;
}
}
if ( false === $method ) {
return new WP_Error( 'woocommerce_rest_shipping_zone_method_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
}
$method = $this->update_fields( $instance_id, $method, $request );
if ( is_wp_error( $method ) ) {
return $method;
}
$data = $this->prepare_item_for_response( $method, $request );
return rest_ensure_response( $data );
}
/**
* Updates settings, order, and enabled status on create.
*
* @param int $instance_id Instance ID.
* @param WC_Shipping_Method $method Shipping method data.
* @param WP_REST_Request $request Request data.
*
* @return WC_Shipping_Method
*/
public function update_fields( $instance_id, $method, $request ) {
global $wpdb;
// Update settings if present.
if ( isset( $request['settings'] ) ) {
$method->init_instance_settings();
$instance_settings = $method->instance_settings;
$errors_found = false;
foreach ( $method->get_instance_form_fields() as $key => $field ) {
if ( isset( $request['settings'][ $key ] ) ) {
if ( is_callable( array( $this, 'validate_setting_' . $field['type'] . '_field' ) ) ) {
$value = $this->{'validate_setting_' . $field['type'] . '_field'}( $request['settings'][ $key ], $field );
} else {
$value = $this->validate_setting_text_field( $request['settings'][ $key ], $field );
}
if ( is_wp_error( $value ) ) {
$errors_found = true;
break;
}
$instance_settings[ $key ] = $value;
}
}
if ( $errors_found ) {
return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
}
update_option( $method->get_instance_option_key(), apply_filters( 'woocommerce_shipping_' . $method->id . '_instance_settings_values', $instance_settings, $method ) );
}
// Update order.
if ( isset( $request['order'] ) ) {
$wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'method_order' => absint( $request['order'] ) ), array( 'instance_id' => absint( $instance_id ) ) );
$method->method_order = absint( $request['order'] );
}
// Update if this method is enabled or not.
if ( isset( $request['enabled'] ) ) {
if ( $wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'is_enabled' => $request['enabled'] ), array( 'instance_id' => absint( $instance_id ) ) ) ) {
do_action( 'woocommerce_shipping_zone_method_status_toggled', $instance_id, $method->id, $request['zone_id'], $request['enabled'] );
$method->enabled = ( true === $request['enabled'] ? 'yes' : 'no' );
}
}
return $method;
}
/**
* Prepare the Shipping Zone Method for the REST response.
*
* @param array $item Shipping Zone Method.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response
*/
public function prepare_item_for_response( $item, $request ) {
$method = array(
'id' => $item->instance_id,
'instance_id' => $item->instance_id,
'title' => $item->instance_settings['title'],
'order' => $item->method_order,
'enabled' => ( 'yes' === $item->enabled ),
'method_id' => $item->id,
'method_title' => $item->method_title,
'method_description' => $item->method_description,
'settings' => $this->get_settings( $item ),
);
$context = empty( $request['context'] ) ? 'view' : $request['context'];
$data = $this->add_additional_fields_to_object( $method, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $request['zone_id'], $item->instance_id ) );
$response = $this->prepare_response_for_collection( $response );
return $response;
}
/**
* Return settings associated with this shipping zone method instance.
*
* @param WC_Shipping_Method $item Shipping method data.
*
* @return array
*/
public function get_settings( $item ) {
$item->init_instance_settings();
$settings = array();
foreach ( $item->get_instance_form_fields() as $id => $field ) {
$data = array(
'id' => $id,
'label' => $field['title'],
'description' => empty( $field['description'] ) ? '' : $field['description'],
'type' => $field['type'],
'value' => $item->instance_settings[ $id ],
'default' => empty( $field['default'] ) ? '' : $field['default'],
'tip' => empty( $field['description'] ) ? '' : $field['description'],
'placeholder' => empty( $field['placeholder'] ) ? '' : $field['placeholder'],
);
if ( ! empty( $field['options'] ) ) {
$data['options'] = $field['options'];
}
$settings[ $id ] = $data;
}
return $settings;
}
/**
* Prepare links for the request.
*
* @param int $zone_id Given Shipping Zone ID.
* @param int $instance_id Given Shipping Zone Method Instance ID.
* @return array Links for the given Shipping Zone Method.
*/
protected function prepare_links( $zone_id, $instance_id ) {
$base = '/' . $this->namespace . '/' . $this->rest_base . '/' . $zone_id;
$links = array(
'self' => array(
'href' => rest_url( $base . '/methods/' . $instance_id ),
),
'collection' => array(
'href' => rest_url( $base . '/methods' ),
),
'describes' => array(
'href' => rest_url( $base ),
),
);
return $links;
}
/**
* Get the Shipping Zone Methods schema, conforming to JSON Schema
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'shipping_zone_method',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Shipping method instance ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'instance_id' => array(
'description' => __( 'Shipping method instance ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'title' => array(
'description' => __( 'Shipping method customer facing title.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'order' => array(
'description' => __( 'Shipping method sort order.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'enabled' => array(
'description' => __( 'Shipping method enabled status.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
),
'method_id' => array(
'description' => __( 'Shipping method ID.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'method_title' => array(
'description' => __( 'Shipping method title.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'method_description' => array(
'description' => __( 'Shipping method description.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'settings' => array(
'description' => __( 'Shipping method settings.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'id' => array(
'description' => __( 'A unique identifier for the setting.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'label' => array(
'description' => __( 'A human readable label for the setting used in interfaces.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'A human readable description for the setting used in interfaces.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'type' => array(
'description' => __( 'Type of setting.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'enum' => array( 'text', 'email', 'number', 'color', 'password', 'textarea', 'select', 'multiselect', 'radio', 'image_width', 'checkbox' ),
'readonly' => true,
),
'value' => array(
'description' => __( 'Setting value.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'default' => array(
'description' => __( 'Default value for the setting.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'tip' => array(
'description' => __( 'Additional help text shown to the user about the setting.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'placeholder' => array(
'description' => __( 'Placeholder text to be displayed in text inputs.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,304 @@
<?php
/**
* REST API Shipping Zones controller
*
* Handles requests to the /shipping/zones endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Shipping Zones class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Shipping_Zones_Controller_Base
*/
class WC_REST_Shipping_Zones_V2_Controller extends WC_REST_Shipping_Zones_Controller_Base {
/**
* Register the routes for Shipping Zones.
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => array_merge(
$this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
'name' => array(
'required' => true,
'type' => 'string',
'description' => __( 'Shipping zone name.', 'woocommerce' ),
),
)
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
'args' => array(
'id' => array(
'description' => __( 'Unique ID for the resource.', 'woocommerce' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_items_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
'type' => 'boolean',
'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Get a single Shipping Zone.
*
* @param WP_REST_Request $request Request data.
* @return WP_REST_Response|WP_Error
*/
public function get_item( $request ) {
$zone = $this->get_zone( $request->get_param( 'id' ) );
if ( is_wp_error( $zone ) ) {
return $zone;
}
$data = $zone->get_data();
$data = $this->prepare_item_for_response( $data, $request );
$data = $this->prepare_response_for_collection( $data );
return rest_ensure_response( $data );
}
/**
* Get all Shipping Zones.
*
* @param WP_REST_Request $request Request data.
* @return WP_REST_Response
*/
public function get_items( $request ) {
$rest_of_the_world = WC_Shipping_Zones::get_zone_by( 'zone_id', 0 );
$zones = WC_Shipping_Zones::get_zones();
array_unshift( $zones, $rest_of_the_world->get_data() );
$data = array();
foreach ( $zones as $zone_obj ) {
$zone = $this->prepare_item_for_response( $zone_obj, $request );
$zone = $this->prepare_response_for_collection( $zone );
$data[] = $zone;
}
return rest_ensure_response( $data );
}
/**
* Create a single Shipping Zone.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Request|WP_Error
*/
public function create_item( $request ) {
$zone = new WC_Shipping_Zone( null );
if ( ! is_null( $request->get_param( 'name' ) ) ) {
$zone->set_zone_name( $request->get_param( 'name' ) );
}
if ( ! is_null( $request->get_param( 'order' ) ) ) {
$zone->set_zone_order( $request->get_param( 'order' ) );
}
$zone->save();
if ( $zone->get_id() !== 0 ) {
$request->set_param( 'id', $zone->get_id() );
$response = $this->get_item( $request );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $zone->get_id() ) ) );
return $response;
} else {
return new WP_Error( 'woocommerce_rest_shipping_zone_not_created', __( "Resource cannot be created. Check to make sure 'order' and 'name' are present.", 'woocommerce' ), array( 'status' => 500 ) );
}
}
/**
* Update a single Shipping Zone.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Request|WP_Error
*/
public function update_item( $request ) {
$zone = $this->get_zone( $request->get_param( 'id' ) );
if ( is_wp_error( $zone ) ) {
return $zone;
}
if ( 0 === $zone->get_id() ) {
return new WP_Error( 'woocommerce_rest_shipping_zone_invalid_zone', __( 'The "locations not covered by your other zones" zone cannot be updated.', 'woocommerce' ), array( 'status' => 403 ) );
}
$zone_changed = false;
if ( ! is_null( $request->get_param( 'name' ) ) ) {
$zone->set_zone_name( $request->get_param( 'name' ) );
$zone_changed = true;
}
if ( ! is_null( $request->get_param( 'order' ) ) ) {
$zone->set_zone_order( $request->get_param( 'order' ) );
$zone_changed = true;
}
if ( $zone_changed ) {
$zone->save();
}
return $this->get_item( $request );
}
/**
* Delete a single Shipping Zone.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Request|WP_Error
*/
public function delete_item( $request ) {
$zone = $this->get_zone( $request->get_param( 'id' ) );
if ( is_wp_error( $zone ) ) {
return $zone;
}
$force = $request['force'];
$response = $this->get_item( $request );
if ( $force ) {
$zone->delete();
} else {
return new WP_Error( 'rest_trash_not_supported', __( 'Shipping zones do not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
}
return $response;
}
/**
* Prepare the Shipping Zone for the REST response.
*
* @param array $item Shipping Zone.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response
*/
public function prepare_item_for_response( $item, $request ) {
$data = array(
'id' => (int) $item['id'],
'name' => $item['zone_name'],
'order' => (int) $item['zone_order'],
);
$context = empty( $request['context'] ) ? 'view' : $request['context'];
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $data['id'] ) );
return $response;
}
/**
* Prepare links for the request.
*
* @param int $zone_id Given Shipping Zone ID.
* @return array Links for the given Shipping Zone.
*/
protected function prepare_links( $zone_id ) {
$base = '/' . $this->namespace . '/' . $this->rest_base;
$links = array(
'self' => array(
'href' => rest_url( trailingslashit( $base ) . $zone_id ),
),
'collection' => array(
'href' => rest_url( $base ),
),
'describedby' => array(
'href' => rest_url( trailingslashit( $base ) . $zone_id . '/locations' ),
),
);
return $links;
}
/**
* Get the Shipping Zones schema, conforming to JSON Schema
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'shipping_zone',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Shipping zone name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'order' => array(
'description' => __( 'Shipping zone order.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,618 @@
<?php
/**
* REST API WC System Status Tools Controller
*
* Handles requests to the /system_status/tools/* endpoints.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* System status tools controller.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'system_status/tools';
/**
* Register the routes for /system_status/tools/*.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\w-]+)',
array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Check whether a given request has permission to view system status tools.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'system_status', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check whether a given request has permission to view a specific system status tool.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'system_status', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check whether a given request has permission to execute a specific system status tool.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function update_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'system_status', 'edit' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_update', __( 'Sorry, you cannot update resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* A list of available tools for use in the system status section.
* 'button' becomes 'action' in the API.
*
* @return array
*/
public function get_tools() {
$tools = array(
'clear_transients' => array(
'name' => __( 'WooCommerce transients', 'woocommerce' ),
'button' => __( 'Clear transients', 'woocommerce' ),
'desc' => __( 'This tool will clear the product/shop transients cache.', 'woocommerce' ),
),
'clear_expired_transients' => array(
'name' => __( 'Expired transients', 'woocommerce' ),
'button' => __( 'Clear transients', 'woocommerce' ),
'desc' => __( 'This tool will clear ALL expired transients from WordPress.', 'woocommerce' ),
),
'delete_orphaned_variations' => array(
'name' => __( 'Orphaned variations', 'woocommerce' ),
'button' => __( 'Delete orphaned variations', 'woocommerce' ),
'desc' => __( 'This tool will delete all variations which have no parent.', 'woocommerce' ),
),
'clear_expired_download_permissions' => array(
'name' => __( 'Used-up download permissions', 'woocommerce' ),
'button' => __( 'Clean up download permissions', 'woocommerce' ),
'desc' => __( 'This tool will delete expired download permissions and permissions with 0 remaining downloads.', 'woocommerce' ),
),
'regenerate_product_lookup_tables' => array(
'name' => __( 'Product lookup tables', 'woocommerce' ),
'button' => __( 'Regenerate', 'woocommerce' ),
'desc' => __( 'This tool will regenerate product lookup table data. This process may take a while.', 'woocommerce' ),
),
'recount_terms' => array(
'name' => __( 'Term counts', 'woocommerce' ),
'button' => __( 'Recount terms', 'woocommerce' ),
'desc' => __( 'This tool will recount product terms - useful when changing your settings in a way which hides products from the catalog.', 'woocommerce' ),
),
'reset_roles' => array(
'name' => __( 'Capabilities', 'woocommerce' ),
'button' => __( 'Reset capabilities', 'woocommerce' ),
'desc' => __( 'This tool will reset the admin, customer and shop_manager roles to default. Use this if your users cannot access all of the WooCommerce admin pages.', 'woocommerce' ),
),
'clear_sessions' => array(
'name' => __( 'Clear customer sessions', 'woocommerce' ),
'button' => __( 'Clear', 'woocommerce' ),
'desc' => sprintf(
'<strong class="red">%1$s</strong> %2$s',
__( 'Note:', 'woocommerce' ),
__( 'This tool will delete all customer session data from the database, including current carts and saved carts in the database.', 'woocommerce' )
),
),
'clear_template_cache' => array(
'name' => __( 'Clear template cache', 'woocommerce' ),
'button' => __( 'Clear', 'woocommerce' ),
'desc' => sprintf(
'<strong class="red">%1$s</strong> %2$s',
__( 'Note:', 'woocommerce' ),
__( 'This tool will empty the template cache.', 'woocommerce' )
),
),
'install_pages' => array(
'name' => __( 'Create default WooCommerce pages', 'woocommerce' ),
'button' => __( 'Create pages', 'woocommerce' ),
'desc' => sprintf(
'<strong class="red">%1$s</strong> %2$s',
__( 'Note:', 'woocommerce' ),
__( 'This tool will install all the missing WooCommerce pages. Pages already defined and set up will not be replaced.', 'woocommerce' )
),
),
'delete_taxes' => array(
'name' => __( 'Delete WooCommerce tax rates', 'woocommerce' ),
'button' => __( 'Delete tax rates', 'woocommerce' ),
'desc' => sprintf(
'<strong class="red">%1$s</strong> %2$s',
__( 'Note:', 'woocommerce' ),
__( 'This option will delete ALL of your tax rates, use with caution. This action cannot be reversed.', 'woocommerce' )
),
),
'regenerate_thumbnails' => array(
'name' => __( 'Regenerate shop thumbnails', 'woocommerce' ),
'button' => __( 'Regenerate', 'woocommerce' ),
'desc' => __( 'This will regenerate all shop thumbnails to match your theme and/or image settings.', 'woocommerce' ),
),
'db_update_routine' => array(
'name' => __( 'Update database', 'woocommerce' ),
'button' => __( 'Update database', 'woocommerce' ),
'desc' => sprintf(
'<strong class="red">%1$s</strong> %2$s',
__( 'Note:', 'woocommerce' ),
__( 'This tool will update your WooCommerce database to the latest version. Please ensure you make sufficient backups before proceeding.', 'woocommerce' )
),
),
);
if ( method_exists( 'WC_Install', 'verify_base_tables' ) ) {
$tools['verify_db_tables'] = array(
'name' => __( 'Verify base database tables', 'woocommerce' ),
'button' => __( 'Verify database', 'woocommerce' ),
'desc' => sprintf(
__( 'Verify if all base database tables are present.', 'woocommerce' )
),
);
}
// Jetpack does the image resizing heavy lifting so you don't have to.
if ( ( class_exists( 'Jetpack' ) && Jetpack::is_module_active( 'photon' ) ) || ! apply_filters( 'woocommerce_background_image_regeneration', true ) ) {
unset( $tools['regenerate_thumbnails'] );
}
if ( ! function_exists( 'wc_clear_template_cache' ) ) {
unset( $tools['clear_template_cache'] );
}
return apply_filters( 'woocommerce_debug_tools', $tools );
}
/**
* Get a list of system status tools.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$tools = array();
foreach ( $this->get_tools() as $id => $tool ) {
$tools[] = $this->prepare_response_for_collection(
$this->prepare_item_for_response(
array(
'id' => $id,
'name' => $tool['name'],
'action' => $tool['button'],
'description' => $tool['desc'],
),
$request
)
);
}
$response = rest_ensure_response( $tools );
return $response;
}
/**
* Return a single tool.
*
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$tools = $this->get_tools();
if ( empty( $tools[ $request['id'] ] ) ) {
return new WP_Error( 'woocommerce_rest_system_status_tool_invalid_id', __( 'Invalid tool ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$tool = $tools[ $request['id'] ];
return rest_ensure_response(
$this->prepare_item_for_response(
array(
'id' => $request['id'],
'name' => $tool['name'],
'action' => $tool['button'],
'description' => $tool['desc'],
),
$request
)
);
}
/**
* Update (execute) a tool.
*
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
$tools = $this->get_tools();
if ( empty( $tools[ $request['id'] ] ) ) {
return new WP_Error( 'woocommerce_rest_system_status_tool_invalid_id', __( 'Invalid tool ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$tool = $tools[ $request['id'] ];
$tool = array(
'id' => $request['id'],
'name' => $tool['name'],
'action' => $tool['button'],
'description' => $tool['desc'],
);
$execute_return = $this->execute_tool( $request['id'] );
$tool = array_merge( $tool, $execute_return );
/**
* Fires after a WooCommerce REST system status tool has been executed.
*
* @param array $tool Details about the tool that has been executed.
* @param WP_REST_Request $request The current WP_REST_Request object.
*/
do_action( 'woocommerce_rest_insert_system_status_tool', $tool, $request );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $tool, $request );
return rest_ensure_response( $response );
}
/**
* Prepare a tool item for serialization.
*
* @param array $item Object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $item, $request ) {
$context = empty( $request['context'] ) ? 'view' : $request['context'];
$data = $this->add_additional_fields_to_object( $item, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item['id'] ) );
return $response;
}
/**
* Get the system status tools schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'system_status_tool',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'A unique identifier for the tool.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_title',
),
),
'name' => array(
'description' => __( 'Tool name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'action' => array(
'description' => __( 'What running the tool will do.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'description' => array(
'description' => __( 'Tool description.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'success' => array(
'description' => __( 'Did the tool run successfully?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'edit' ),
),
'message' => array(
'description' => __( 'Tool return message.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Prepare links for the request.
*
* @param string $id ID.
* @return array
*/
protected function prepare_links( $id ) {
$base = '/' . $this->namespace . '/' . $this->rest_base;
$links = array(
'item' => array(
'href' => rest_url( trailingslashit( $base ) . $id ),
'embeddable' => true,
),
);
return $links;
}
/**
* Get any query params needed.
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
/**
* Actually executes a tool.
*
* @param string $tool Tool.
* @return array
*/
public function execute_tool( $tool ) {
global $wpdb;
$ran = true;
switch ( $tool ) {
case 'clear_transients':
wc_delete_product_transients();
wc_delete_shop_order_transients();
delete_transient( 'wc_count_comments' );
delete_transient( 'as_comment_count' );
$attribute_taxonomies = wc_get_attribute_taxonomies();
if ( $attribute_taxonomies ) {
foreach ( $attribute_taxonomies as $attribute ) {
delete_transient( 'wc_layered_nav_counts_pa_' . $attribute->attribute_name );
}
}
WC_Cache_Helper::get_transient_version( 'shipping', true );
$message = __( 'Product transients cleared', 'woocommerce' );
break;
case 'clear_expired_transients':
/* translators: %d: amount of expired transients */
$message = sprintf( __( '%d transients rows cleared', 'woocommerce' ), wc_delete_expired_transients() );
break;
case 'delete_orphaned_variations':
// Delete orphans.
$result = absint(
$wpdb->query(
"DELETE products
FROM {$wpdb->posts} products
LEFT JOIN {$wpdb->posts} wp ON wp.ID = products.post_parent
WHERE wp.ID IS NULL AND products.post_type = 'product_variation';"
)
);
/* translators: %d: amount of orphaned variations */
$message = sprintf( __( '%d orphaned variations deleted', 'woocommerce' ), $result );
break;
case 'clear_expired_download_permissions':
// Delete expired download permissions and ones with 0 downloads remaining.
$result = absint(
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
WHERE ( downloads_remaining != '' AND downloads_remaining = 0 ) OR ( access_expires IS NOT NULL AND access_expires < %s )",
gmdate( 'Y-m-d', current_time( 'timestamp' ) )
)
)
);
/* translators: %d: amount of permissions */
$message = sprintf( __( '%d permissions deleted', 'woocommerce' ), $result );
break;
case 'regenerate_product_lookup_tables':
if ( ! wc_update_product_lookup_tables_is_running() ) {
wc_update_product_lookup_tables();
}
$message = __( 'Lookup tables are regenerating', 'woocommerce' );
break;
case 'reset_roles':
// Remove then re-add caps and roles.
WC_Install::remove_roles();
WC_Install::create_roles();
$message = __( 'Roles successfully reset', 'woocommerce' );
break;
case 'recount_terms':
$product_cats = get_terms(
'product_cat',
array(
'hide_empty' => false,
'fields' => 'id=>parent',
)
);
_wc_term_recount( $product_cats, get_taxonomy( 'product_cat' ), true, false );
$product_tags = get_terms(
'product_tag',
array(
'hide_empty' => false,
'fields' => 'id=>parent',
)
);
_wc_term_recount( $product_tags, get_taxonomy( 'product_tag' ), true, false );
$message = __( 'Terms successfully recounted', 'woocommerce' );
break;
case 'clear_sessions':
$wpdb->query( "TRUNCATE {$wpdb->prefix}woocommerce_sessions" );
$result = absint( $wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key='_woocommerce_persistent_cart_" . get_current_blog_id() . "';" ) ); // WPCS: unprepared SQL ok.
wp_cache_flush();
/* translators: %d: amount of sessions */
$message = sprintf( __( 'Deleted all active sessions, and %d saved carts.', 'woocommerce' ), absint( $result ) );
break;
case 'install_pages':
WC_Install::create_pages();
$message = __( 'All missing WooCommerce pages successfully installed', 'woocommerce' );
break;
case 'delete_taxes':
$wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}woocommerce_tax_rates;" );
$wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}woocommerce_tax_rate_locations;" );
if ( method_exists( 'WC_Cache_Helper', 'invalidate_cache_group' ) ) {
WC_Cache_Helper::invalidate_cache_group( 'taxes' );
} else {
WC_Cache_Helper::incr_cache_prefix( 'taxes' );
}
$message = __( 'Tax rates successfully deleted', 'woocommerce' );
break;
case 'regenerate_thumbnails':
WC_Regenerate_Images::queue_image_regeneration();
$message = __( 'Thumbnail regeneration has been scheduled to run in the background.', 'woocommerce' );
break;
case 'db_update_routine':
$blog_id = get_current_blog_id();
// Used to fire an action added in WP_Background_Process::_construct() that calls WP_Background_Process::handle_cron_healthcheck().
// This method will make sure the database updates are executed even if cron is disabled. Nothing will happen if the updates are already running.
do_action( 'wp_' . $blog_id . '_wc_updater_cron' );
$message = __( 'Database upgrade routine has been scheduled to run in the background.', 'woocommerce' );
break;
case 'clear_template_cache':
if ( function_exists( 'wc_clear_template_cache' ) ) {
wc_clear_template_cache();
$message = __( 'Template cache cleared.', 'woocommerce' );
} else {
$message = __( 'The active version of WooCommerce does not support template cache clearing.', 'woocommerce' );
$ran = false;
}
break;
case 'verify_db_tables':
if ( ! method_exists( 'WC_Install', 'verify_base_tables' ) ) {
$message = __( 'You need WooCommerce 4.2 or newer to run this tool.', 'woocommerce' );
$ran = false;
break;
}
// Try to manually create table again.
$missing_tables = WC_Install::verify_base_tables( true, true );
if ( 0 === count( $missing_tables ) ) {
$message = __( 'Database verified successfully.', 'woocommerce' );
} else {
$message = __( 'Verifying database... One or more tables are still missing: ', 'woocommerce' );
$message .= implode( ', ', $missing_tables );
$ran = false;
}
break;
default:
$tools = $this->get_tools();
if ( isset( $tools[ $tool ]['callback'] ) ) {
$callback = $tools[ $tool ]['callback'];
$return = call_user_func( $callback );
if ( is_string( $return ) ) {
$message = $return;
} elseif ( false === $return ) {
$callback_string = is_array( $callback ) ? get_class( $callback[0] ) . '::' . $callback[1] : $callback;
$ran = false;
/* translators: %s: callback string */
$message = sprintf( __( 'There was an error calling %s', 'woocommerce' ), $callback_string );
} else {
$message = __( 'Tool ran.', 'woocommerce' );
}
} else {
$ran = false;
$message = __( 'There was an error calling this tool. There is no callback present.', 'woocommerce' );
}
break;
}
return array(
'success' => $ran,
'message' => $message,
);
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Tax Classes controller
*
* Handles requests to the /taxes/classes endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Tax Classes controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Tax_Classes_V1_Controller
*/
class WC_REST_Tax_Classes_V2_Controller extends WC_REST_Tax_Classes_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Taxes controller
*
* Handles requests to the /taxes endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Taxes controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Taxes_V1_Controller
*/
class WC_REST_Taxes_V2_Controller extends WC_REST_Taxes_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
}

View File

@ -0,0 +1,153 @@
<?php
/**
* REST API Webhooks controller
*
* Handles requests to the /webhooks/<webhook_id>/deliveries endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Webhook Deliveries controller class.
*
* @deprecated 3.3.0 Webhooks deliveries logs now uses logging system.
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Webhook_Deliveries_V1_Controller
*/
class WC_REST_Webhook_Deliveries_V2_Controller extends WC_REST_Webhook_Deliveries_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Prepare a single webhook delivery output for response.
*
* @param stdClass $log Delivery log object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
public function prepare_item_for_response( $log, $request ) {
$data = (array) $log;
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $log ) );
/**
* Filter webhook delivery object returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param stdClass $log Delivery log object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_webhook_delivery', $response, $log, $request );
}
/**
* Get the Webhook's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'webhook_delivery',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'duration' => array(
'description' => __( 'The delivery duration, in seconds.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'summary' => array(
'description' => __( 'A friendly summary of the response including the HTTP response code, message, and body.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'request_url' => array(
'description' => __( 'The URL where the webhook was delivered.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view' ),
'readonly' => true,
),
'request_headers' => array(
'description' => __( 'Request headers.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
'request_body' => array(
'description' => __( 'Request body.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'response_code' => array(
'description' => __( 'The HTTP response code from the receiving server.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'response_message' => array(
'description' => __( 'The HTTP response message from the receiving server.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'response_headers' => array(
'description' => __( 'Array of the response headers from the receiving server.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
'response_body' => array(
'description' => __( 'The response body from the receiving server.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'date_created' => array(
'description' => __( "The date the webhook delivery was logged, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created_gmt' => array(
'description' => __( 'The date the webhook delivery was logged, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,182 @@
<?php
/**
* REST API Webhooks controller
*
* Handles requests to the /webhooks endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Webhooks controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Webhooks_V1_Controller
*/
class WC_REST_Webhooks_V2_Controller extends WC_REST_Webhooks_V1_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Prepare a single webhook output for response.
*
* @param int $id Webhook ID.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response
*/
public function prepare_item_for_response( $id, $request ) {
$webhook = wc_get_webhook( $id );
if ( empty( $webhook ) || is_null( $webhook ) ) {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'ID is invalid.', 'woocommerce' ), array( 'status' => 400 ) );
}
$data = array(
'id' => $webhook->get_id(),
'name' => $webhook->get_name(),
'status' => $webhook->get_status(),
'topic' => $webhook->get_topic(),
'resource' => $webhook->get_resource(),
'event' => $webhook->get_event(),
'hooks' => $webhook->get_hooks(),
'delivery_url' => $webhook->get_delivery_url(),
'date_created' => wc_rest_prepare_date_response( $webhook->get_date_created(), false ),
'date_created_gmt' => wc_rest_prepare_date_response( $webhook->get_date_created() ),
'date_modified' => wc_rest_prepare_date_response( $webhook->get_date_modified(), false ),
'date_modified_gmt' => wc_rest_prepare_date_response( $webhook->get_date_modified() ),
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $webhook->get_id(), $request ) );
/**
* Filter webhook object returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param WC_Webhook $webhook Webhook object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "woocommerce_rest_prepare_{$this->post_type}", $response, $webhook, $request );
}
/**
* Get the default REST API version.
*
* @since 3.0.0
* @return string
*/
protected function get_default_api_version() {
return 'wp_api_v2';
}
/**
* Get the Webhook's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'webhook',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'A friendly name for the webhook.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'status' => array(
'description' => __( 'Webhook status.', 'woocommerce' ),
'type' => 'string',
'default' => 'active',
'enum' => array_keys( wc_get_webhook_statuses() ),
'context' => array( 'view', 'edit' ),
),
'topic' => array(
'description' => __( 'Webhook topic.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'resource' => array(
'description' => __( 'Webhook resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'event' => array(
'description' => __( 'Webhook event.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'hooks' => array(
'description' => __( 'WooCommerce action names associated with the webhook.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
'delivery_url' => array(
'description' => __( 'The URL where the webhook payload is delivered.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'secret' => array(
'description' => __( "Secret key used to generate a hash of the delivered webhook and provided in the request headers. This will default to a MD5 hash from the current user's ID|username if not provided.", 'woocommerce' ),
'type' => 'string',
'context' => array( 'edit' ),
),
'date_created' => array(
'description' => __( "The date the webhook was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created_gmt' => array(
'description' => __( 'The date the webhook was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the webhook was last modified, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified_gmt' => array(
'description' => __( 'The date the webhook was last modified, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,532 @@
<?php
/**
* REST Controller
*
* This class extend `WP_REST_Controller` in order to include /batch endpoint
* for almost all endpoints in WooCommerce REST API.
*
* It's required to follow "Controller Classes" guide before extending this class:
* <https://developer.wordpress.org/rest-api/extending-the-rest-api/controller-classes/>
*
* NOTE THAT ONLY CODE RELEVANT FOR MOST ENDPOINTS SHOULD BE INCLUDED INTO THIS CLASS.
* If necessary extend this class and create new abstract classes like `WC_REST_CRUD_Controller` or `WC_REST_Terms_Controller`.
*
* @class WC_REST_Controller
* @package Automattic/WooCommerce/RestApi
* @see https://developer.wordpress.org/rest-api/extending-the-rest-api/controller-classes/
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Abstract Rest Controller Class
*
* @package Automattic/WooCommerce/RestApi
* @extends WP_REST_Controller
* @version 2.6.0
*/
abstract class WC_REST_Controller extends WP_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = '';
/**
* Add the schema from additional fields to an schema array.
*
* The type of object is inferred from the passed schema.
*
* @param array $schema Schema array.
*
* @return array
*/
protected function add_additional_fields_schema( $schema ) {
if ( empty( $schema['title'] ) ) {
return $schema;
}
/**
* Can't use $this->get_object_type otherwise we cause an inf loop.
*/
$object_type = $schema['title'];
$additional_fields = $this->get_additional_fields( $object_type );
foreach ( $additional_fields as $field_name => $field_options ) {
if ( ! $field_options['schema'] ) {
continue;
}
$schema['properties'][ $field_name ] = $field_options['schema'];
}
$schema['properties'] = apply_filters( 'woocommerce_rest_' . $object_type . '_schema', $schema['properties'] );
return $schema;
}
/**
* Compatibility functions for WP 5.5, since custom types are not supported anymore.
* See @link https://core.trac.wordpress.org/changeset/48306
*
* @param string $method Optional. HTTP method of the request.
*
* @return array Endpoint arguments.
*/
public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
$endpoint_args = parent::get_endpoint_args_for_item_schema( $method );
if ( false === strpos( WP_REST_Server::EDITABLE, $method ) ) {
return $endpoint_args;
}
foreach ( $endpoint_args as $field_id => $params ) {
/**
* Custom types are not supported as of WP 5.5, this translates type => 'date-time' to type => 'string' with format date-time.
*/
if ( 'date-time' === $params['type'] ) {
$endpoint_args[ $field_id ]['type'] = 'string';
$endpoint_args[ $field_id ]['format'] = 'date-time';
}
}
return $endpoint_args;
}
/**
* Get normalized rest base.
*
* @return string
*/
protected function get_normalized_rest_base() {
return preg_replace( '/\(.*\)\//i', '', $this->rest_base );
}
/**
* Check batch limit.
*
* @param array $items Request items.
* @return bool|WP_Error
*/
protected function check_batch_limit( $items ) {
$limit = apply_filters( 'woocommerce_rest_batch_items_limit', 100, $this->get_normalized_rest_base() );
$total = 0;
if ( ! empty( $items['create'] ) ) {
$total += count( $items['create'] );
}
if ( ! empty( $items['update'] ) ) {
$total += count( $items['update'] );
}
if ( ! empty( $items['delete'] ) ) {
$total += count( $items['delete'] );
}
if ( $total > $limit ) {
/* translators: %s: items limit */
return new WP_Error( 'woocommerce_rest_request_entity_too_large', sprintf( __( 'Unable to accept more than %s items for this request.', 'woocommerce' ), $limit ), array( 'status' => 413 ) );
}
return true;
}
/**
* Bulk create, update and delete items.
*
* @param WP_REST_Request $request Full details about the request.
* @return array Of WP_Error or WP_REST_Response.
*/
public function batch_items( $request ) {
/**
* REST Server
*
* @var WP_REST_Server $wp_rest_server
*/
global $wp_rest_server;
// Get the request params.
$items = array_filter( $request->get_params() );
$query = $request->get_query_params();
$response = array();
// Check batch limit.
$limit = $this->check_batch_limit( $items );
if ( is_wp_error( $limit ) ) {
return $limit;
}
if ( ! empty( $items['create'] ) ) {
foreach ( $items['create'] as $item ) {
$_item = new WP_REST_Request( 'POST' );
// Default parameters.
$defaults = array();
$schema = $this->get_public_item_schema();
foreach ( $schema['properties'] as $arg => $options ) {
if ( isset( $options['default'] ) ) {
$defaults[ $arg ] = $options['default'];
}
}
$_item->set_default_params( $defaults );
// Set request parameters.
$_item->set_body_params( $item );
// Set query (GET) parameters.
$_item->set_query_params( $query );
$_response = $this->create_item( $_item );
if ( is_wp_error( $_response ) ) {
$response['create'][] = array(
'id' => 0,
'error' => array(
'code' => $_response->get_error_code(),
'message' => $_response->get_error_message(),
'data' => $_response->get_error_data(),
),
);
} else {
$response['create'][] = $wp_rest_server->response_to_data( $_response, '' );
}
}
}
if ( ! empty( $items['update'] ) ) {
foreach ( $items['update'] as $item ) {
$_item = new WP_REST_Request( 'PUT' );
$_item->set_body_params( $item );
$_response = $this->update_item( $_item );
if ( is_wp_error( $_response ) ) {
$response['update'][] = array(
'id' => $item['id'],
'error' => array(
'code' => $_response->get_error_code(),
'message' => $_response->get_error_message(),
'data' => $_response->get_error_data(),
),
);
} else {
$response['update'][] = $wp_rest_server->response_to_data( $_response, '' );
}
}
}
if ( ! empty( $items['delete'] ) ) {
foreach ( $items['delete'] as $id ) {
$id = (int) $id;
if ( 0 === $id ) {
continue;
}
$_item = new WP_REST_Request( 'DELETE' );
$_item->set_query_params(
array(
'id' => $id,
'force' => true,
)
);
$_response = $this->delete_item( $_item );
if ( is_wp_error( $_response ) ) {
$response['delete'][] = array(
'id' => $id,
'error' => array(
'code' => $_response->get_error_code(),
'message' => $_response->get_error_message(),
'data' => $_response->get_error_data(),
),
);
} else {
$response['delete'][] = $wp_rest_server->response_to_data( $_response, '' );
}
}
}
return $response;
}
/**
* Validate a text value for a text based setting.
*
* @since 3.0.0
* @param string $value Value.
* @param array $setting Setting.
* @return string
*/
public function validate_setting_text_field( $value, $setting ) {
$value = is_null( $value ) ? '' : $value;
return wp_kses_post( trim( stripslashes( $value ) ) );
}
/**
* Validate select based settings.
*
* @since 3.0.0
* @param string $value Value.
* @param array $setting Setting.
* @return string|WP_Error
*/
public function validate_setting_select_field( $value, $setting ) {
if ( array_key_exists( $value, $setting['options'] ) ) {
return $value;
} else {
return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
}
}
/**
* Validate multiselect based settings.
*
* @since 3.0.0
* @param array $values Values.
* @param array $setting Setting.
* @return array|WP_Error
*/
public function validate_setting_multiselect_field( $values, $setting ) {
if ( empty( $values ) ) {
return array();
}
if ( ! is_array( $values ) ) {
return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
}
$final_values = array();
foreach ( $values as $value ) {
if ( array_key_exists( $value, $setting['options'] ) ) {
$final_values[] = $value;
}
}
return $final_values;
}
/**
* Validate image_width based settings.
*
* @since 3.0.0
* @param array $values Values.
* @param array $setting Setting.
* @return string|WP_Error
*/
public function validate_setting_image_width_field( $values, $setting ) {
if ( ! is_array( $values ) ) {
return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
}
$current = $setting['value'];
if ( isset( $values['width'] ) ) {
$current['width'] = intval( $values['width'] );
}
if ( isset( $values['height'] ) ) {
$current['height'] = intval( $values['height'] );
}
if ( isset( $values['crop'] ) ) {
$current['crop'] = (bool) $values['crop'];
}
return $current;
}
/**
* Validate radio based settings.
*
* @since 3.0.0
* @param string $value Value.
* @param array $setting Setting.
* @return string|WP_Error
*/
public function validate_setting_radio_field( $value, $setting ) {
return $this->validate_setting_select_field( $value, $setting );
}
/**
* Validate checkbox based settings.
*
* @since 3.0.0
* @param string $value Value.
* @param array $setting Setting.
* @return string|WP_Error
*/
public function validate_setting_checkbox_field( $value, $setting ) {
if ( in_array( $value, array( 'yes', 'no' ) ) ) {
return $value;
} elseif ( empty( $value ) ) {
$value = isset( $setting['default'] ) ? $setting['default'] : 'no';
return $value;
} else {
return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
}
}
/**
* Validate textarea based settings.
*
* @since 3.0.0
* @param string $value Value.
* @param array $setting Setting.
* @return string
*/
public function validate_setting_textarea_field( $value, $setting ) {
$value = is_null( $value ) ? '' : $value;
return wp_kses(
trim( stripslashes( $value ) ),
array_merge(
array(
'iframe' => array(
'src' => true,
'style' => true,
'id' => true,
'class' => true,
),
),
wp_kses_allowed_html( 'post' )
)
);
}
/**
* Add meta query.
*
* @since 3.0.0
* @param array $args Query args.
* @param array $meta_query Meta query.
* @return array
*/
protected function add_meta_query( $args, $meta_query ) {
if ( empty( $args['meta_query'] ) ) {
$args['meta_query'] = array();
}
$args['meta_query'][] = $meta_query;
return $args['meta_query'];
}
/**
* Get the batch schema, conforming to JSON Schema.
*
* @return array
*/
public function get_public_batch_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'batch',
'type' => 'object',
'properties' => array(
'create' => array(
'description' => __( 'List of created resources.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
),
),
'update' => array(
'description' => __( 'List of updated resources.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
),
),
'delete' => array(
'description' => __( 'List of delete resources.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'integer',
),
),
),
);
return $schema;
}
/**
* Gets an array of fields to be included on the response.
*
* Included fields are based on item schema and `_fields=` request argument.
* Updated from WordPress 5.3, included into this class to support old versions.
*
* @since 3.5.0
* @param WP_REST_Request $request Full details about the request.
* @return array Fields to be included in the response.
*/
public function get_fields_for_response( $request ) {
$schema = $this->get_item_schema();
$properties = isset( $schema['properties'] ) ? $schema['properties'] : array();
$additional_fields = $this->get_additional_fields();
foreach ( $additional_fields as $field_name => $field_options ) {
// For back-compat, include any field with an empty schema
// because it won't be present in $this->get_item_schema().
if ( is_null( $field_options['schema'] ) ) {
$properties[ $field_name ] = $field_options;
}
}
// Exclude fields that specify a different context than the request context.
$context = $request['context'];
if ( $context ) {
foreach ( $properties as $name => $options ) {
if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) {
unset( $properties[ $name ] );
}
}
}
$fields = array_keys( $properties );
if ( ! isset( $request['_fields'] ) ) {
return $fields;
}
$requested_fields = wp_parse_list( $request['_fields'] );
if ( 0 === count( $requested_fields ) ) {
return $fields;
}
// Trim off outside whitespace from the comma delimited list.
$requested_fields = array_map( 'trim', $requested_fields );
// Always persist 'id', because it can be needed for add_additional_fields_to_object().
if ( in_array( 'id', $fields, true ) ) {
$requested_fields[] = 'id';
}
// Return the list of all requested fields which appear in the schema.
return array_reduce(
$requested_fields,
function( $response_fields, $field ) use ( $fields ) {
if ( in_array( $field, $fields, true ) ) {
$response_fields[] = $field;
return $response_fields;
}
// Check for nested fields if $field is not a direct match.
$nested_fields = explode( '.', $field );
// A nested field is included so long as its top-level property is
// present in the schema.
if ( in_array( $nested_fields[0], $fields, true ) ) {
$response_fields[] = $field;
}
return $response_fields;
},
array()
);
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Coupons controller
*
* Handles requests to the /coupons endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Coupons controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Coupons_V2_Controller
*/
class WC_REST_Coupons_Controller extends WC_REST_Coupons_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

View File

@ -0,0 +1,630 @@
<?php
/**
* Abstract Rest CRUD Controller Class
*
* @class WC_REST_CRUD_Controller
* @package Automattic/WooCommerce/RestApi
* @version 3.0.0
*/
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;
/**
* Get object.
*
* @param int $id Object ID.
* @return object WC_Data object or WP_Error object.
*/
protected function get_object( $id ) {
// translators: %s: Class method name.
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* 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 ) {
$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() ) ) {
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 ) {
$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() ) ) {
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 ) {
$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() ) ) {
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.
*
* @param object $object Object.
* @return string
*/
protected function get_permalink( $object ) {
return '';
}
/**
* Prepares the object for the REST response.
*
* @since 3.0.0
* @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 ) {
// translators: %s: Class method name.
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.
*
* @since 3.0.0
* @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 ) {
// translators: %s: Class method name.
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'] );
if ( ! $object || 0 === $object->get_id() ) {
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.
*
* @since 3.0.0
* @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();
return $this->get_object( $object->get_id() );
} 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;
}
try {
$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 );
} catch ( WC_Data_Exception $e ) {
$object->delete();
return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
} catch ( WC_REST_Exception $e ) {
$object->delete();
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
}
$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'] );
if ( ! $object || 0 === $object->get_id() ) {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 400 ) );
}
$object = $this->save_object( $request, false );
if ( is_wp_error( $object ) ) {
return $object;
}
try {
$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 );
} 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() ) );
}
$request->set_param( 'context', 'edit' );
$response = $this->prepare_object_for_response( $object, $request );
return rest_ensure_response( $response );
}
/**
* Prepare objects query.
*
* @since 3.0.0
* @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'];
if ( 'date' === $args['orderby'] ) {
$args['orderby'] = 'date ID';
}
$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.
*
* @since 3.0.0
* @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_filter( 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 = $this->rest_base;
$attrib_prefix = '(?P<';
if ( strpos( $base, $attrib_prefix ) !== false ) {
$attrib_names = array();
preg_match( '/\(\?P<[^>]+>.*\)/', $base, $attrib_names, PREG_OFFSET_CAPTURE );
foreach ( $attrib_names as $attrib_name_match ) {
$beginning_offset = strlen( $attrib_prefix );
$attrib_name_end = strpos( $attrib_name_match[0], '>', $attrib_name_match[1] );
$attrib_name = substr( $attrib_name_match[0], $beginning_offset, $attrib_name_end - $beginning_offset );
if ( isset( $request[ $attrib_name ] ) ) {
$base = str_replace( "(?P<$attrib_name>[\d]+)", $request[ $attrib_name ], $base );
}
}
}
$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $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'] );
$result = false;
if ( ! $object || 0 === $object->get_id() ) {
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 ) {
$object->delete( true );
$result = 0 === $object->get_id();
} 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.
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();
}
}
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() {
$params = array();
$params['context'] = $this->get_context_param();
$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',
'modified',
),
'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(),
);
}
/**
* Filter collection parameters for the posts controller.
*
* The dynamic part of the filter `$this->post_type` refers to the post
* type slug for the controller.
*
* This filter registers the collection parameter, but does not map the
* collection parameter to an internal WP_Query parameter. Use the
* `rest_{$this->post_type}_query` filter to set WP_Query parameters.
*
* @param array $query_params JSON Schema-formatted collection parameters.
* @param WP_Post_Type $post_type Post type object.
*/
return apply_filters( "rest_{$this->post_type}_collection_params", $params, $this->post_type );
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Customer Downloads controller
*
* Handles requests to the /customers/<customer_id>/downloads endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Customers controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Customer_Downloads_V2_Controller
*/
class WC_REST_Customer_Downloads_Controller extends WC_REST_Customer_Downloads_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

View File

@ -0,0 +1,307 @@
<?php
/**
* REST API Customers controller
*
* Handles requests to the /customers endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Customers controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Customers_V2_Controller
*/
class WC_REST_Customers_Controller extends WC_REST_Customers_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Get formatted item data.
*
* @param WC_Data $object WC_Data instance.
*
* @since 3.0.0
* @return array
*/
protected function get_formatted_item_data( $object ) {
$data = $object->get_data();
$format_date = array( 'date_created', 'date_modified' );
// Format date values.
foreach ( $format_date as $key ) {
// Date created is stored UTC, date modified is stored WP local time.
$datetime = 'date_created' === $key ? get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $data[ $key ]->getTimestamp() ) ) : $data[ $key ];
$data[ $key ] = wc_rest_prepare_date_response( $datetime, false );
$data[ $key . '_gmt' ] = wc_rest_prepare_date_response( $datetime );
}
return array(
'id' => $object->get_id(),
'date_created' => $data['date_created'],
'date_created_gmt' => $data['date_created_gmt'],
'date_modified' => $data['date_modified'],
'date_modified_gmt' => $data['date_modified_gmt'],
'email' => $data['email'],
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'role' => $data['role'],
'username' => $data['username'],
'billing' => $data['billing'],
'shipping' => $data['shipping'],
'is_paying_customer' => $data['is_paying_customer'],
'avatar_url' => $object->get_avatar_url(),
'meta_data' => $data['meta_data'],
);
}
/**
* Get the Customer's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'customer',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created' => array(
'description' => __( "The date the customer was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created_gmt' => array(
'description' => __( 'The date the customer was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the customer was last modified, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified_gmt' => array(
'description' => __( 'The date the customer was last modified, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'email' => array(
'description' => __( 'The email address for the customer.', 'woocommerce' ),
'type' => 'string',
'format' => 'email',
'context' => array( 'view', 'edit' ),
),
'first_name' => array(
'description' => __( 'Customer first name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'last_name' => array(
'description' => __( 'Customer last name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'role' => array(
'description' => __( 'Customer role.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'username' => array(
'description' => __( 'Customer login name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_user',
),
),
'password' => array(
'description' => __( 'Customer password.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'edit' ),
),
'billing' => array(
'description' => __( 'List of billing address data.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'first_name' => array(
'description' => __( 'First name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'last_name' => array(
'description' => __( 'Last name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'company' => array(
'description' => __( 'Company name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'address_1' => array(
'description' => __( 'Address line 1', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'address_2' => array(
'description' => __( 'Address line 2', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'city' => array(
'description' => __( 'City name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'state' => array(
'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'postcode' => array(
'description' => __( 'Postal code.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'country' => array(
'description' => __( 'ISO code of the country.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'email' => array(
'description' => __( 'Email address.', 'woocommerce' ),
'type' => 'string',
'format' => 'email',
'context' => array( 'view', 'edit' ),
),
'phone' => array(
'description' => __( 'Phone number.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
'shipping' => array(
'description' => __( 'List of shipping address data.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'first_name' => array(
'description' => __( 'First name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'last_name' => array(
'description' => __( 'Last name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'company' => array(
'description' => __( 'Company name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'address_1' => array(
'description' => __( 'Address line 1', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'address_2' => array(
'description' => __( 'Address line 2', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'city' => array(
'description' => __( 'City name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'state' => array(
'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'postcode' => array(
'description' => __( 'Postal code.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'country' => array(
'description' => __( 'ISO code of the country.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
'is_paying_customer' => array(
'description' => __( 'Is the customer a paying customer?', 'woocommerce' ),
'type' => 'bool',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'avatar_url' => array(
'description' => __( 'Avatar URL.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'meta_data' => array(
'description' => __( 'Meta data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Meta ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'key' => array(
'description' => __( 'Meta key.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ),
'context' => array( 'view', 'edit' ),
),
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,357 @@
<?php
/**
* REST API Data continents controller.
*
* Handles requests to the /data/continents endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.5.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Data continents controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Data_Continents_Controller extends WC_REST_Data_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'data/continents';
/**
* Register routes.
*
* @since 3.5.0
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<location>[\w-]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
'continent' => array(
'description' => __( '2 character continent code.', 'woocommerce' ),
'type' => 'string',
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Return the list of countries and states for a given continent.
*
* @since 3.5.0
* @param string $continent_code Continent code.
* @param WP_REST_Request $request Request data.
* @return array|mixed Response data, ready for insertion into collection data.
*/
public function get_continent( $continent_code = false, $request ) {
$continents = WC()->countries->get_continents();
$countries = WC()->countries->get_countries();
$states = WC()->countries->get_states();
$locale_info = include WC()->plugin_path() . '/i18n/locale-info.php';
$data = array();
if ( ! array_key_exists( $continent_code, $continents ) ) {
return false;
}
$continent_list = $continents[ $continent_code ];
$continent = array(
'code' => $continent_code,
'name' => $continent_list['name'],
);
$local_countries = array();
foreach ( $continent_list['countries'] as $country_code ) {
if ( isset( $countries[ $country_code ] ) ) {
$country = array(
'code' => $country_code,
'name' => $countries[ $country_code ],
);
// If we have detailed locale information include that in the response.
if ( array_key_exists( $country_code, $locale_info ) ) {
// Defensive programming against unexpected changes in locale-info.php.
$country_data = wp_parse_args(
$locale_info[ $country_code ], array(
'currency_code' => 'USD',
'currency_pos' => 'left',
'decimal_sep' => '.',
'dimension_unit' => 'in',
'num_decimals' => 2,
'thousand_sep' => ',',
'weight_unit' => 'lbs',
)
);
$country = array_merge( $country, $country_data );
}
$local_states = array();
if ( isset( $states[ $country_code ] ) ) {
foreach ( $states[ $country_code ] as $state_code => $state_name ) {
$local_states[] = array(
'code' => $state_code,
'name' => $state_name,
);
}
}
$country['states'] = $local_states;
// Allow only desired keys (e.g. filter out tax rates).
$allowed = array(
'code',
'currency_code',
'currency_pos',
'decimal_sep',
'dimension_unit',
'name',
'num_decimals',
'states',
'thousand_sep',
'weight_unit',
);
$country = array_intersect_key( $country, array_flip( $allowed ) );
$local_countries[] = $country;
}
}
$continent['countries'] = $local_countries;
return $continent;
}
/**
* Return the list of states for all continents.
*
* @since 3.5.0
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$continents = WC()->countries->get_continents();
$data = array();
foreach ( array_keys( $continents ) as $continent_code ) {
$continent = $this->get_continent( $continent_code, $request );
$response = $this->prepare_item_for_response( $continent, $request );
$data[] = $this->prepare_response_for_collection( $response );
}
return rest_ensure_response( $data );
}
/**
* Return the list of locations for a given continent.
*
* @since 3.5.0
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$data = $this->get_continent( strtoupper( $request['location'] ), $request );
if ( empty( $data ) ) {
return new WP_Error( 'woocommerce_rest_data_invalid_location', __( 'There are no locations matching these parameters.', 'woocommerce' ), array( 'status' => 404 ) );
}
return $this->prepare_item_for_response( $data, $request );
}
/**
* Prepare the data object for response.
*
* @since 3.5.0
* @param object $item Data object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $item, $request ) {
$data = $this->add_additional_fields_to_object( $item, $request );
$data = $this->filter_response_by_context( $data, 'view' );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item ) );
/**
* Filter the location list returned from the API.
*
* Allows modification of the loction data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param array $item The original list of continent(s), countries, and states.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_data_continent', $response, $item, $request );
}
/**
* Prepare links for the request.
*
* @param object $item Data object.
* @return array Links for the given continent.
*/
protected function prepare_links( $item ) {
$continent_code = strtolower( $item['code'] );
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $continent_code ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
),
);
return $links;
}
/**
* Get the location schema, conforming to JSON Schema.
*
* @since 3.5.0
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'data_continents',
'type' => 'object',
'properties' => array(
'code' => array(
'type' => 'string',
'description' => __( '2 character continent code.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'name' => array(
'type' => 'string',
'description' => __( 'Full name of continent.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'countries' => array(
'type' => 'array',
'description' => __( 'List of countries on this continent.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'object',
'context' => array( 'view' ),
'readonly' => true,
'properties' => array(
'code' => array(
'type' => 'string',
'description' => __( 'ISO3166 alpha-2 country code.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'currency_code' => array(
'type' => 'string',
'description' => __( 'Default ISO4127 alpha-3 currency code for the country.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'currency_pos' => array(
'type' => 'string',
'description' => __( 'Currency symbol position for this country.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'decimal_sep' => array(
'type' => 'string',
'description' => __( 'Decimal separator for displayed prices for this country.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'dimension_unit' => array(
'type' => 'string',
'description' => __( 'The unit lengths are defined in for this country.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'name' => array(
'type' => 'string',
'description' => __( 'Full name of country.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'num_decimals' => array(
'type' => 'integer',
'description' => __( 'Number of decimal points shown in displayed prices for this country.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'states' => array(
'type' => 'array',
'description' => __( 'List of states in this country.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'object',
'context' => array( 'view' ),
'readonly' => true,
'properties' => array(
'code' => array(
'type' => 'string',
'description' => __( 'State code.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'name' => array(
'type' => 'string',
'description' => __( 'Full name of state.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
),
),
),
'thousand_sep' => array(
'type' => 'string',
'description' => __( 'Thousands separator for displayed prices in this country.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'weight_unit' => array(
'type' => 'string',
'description' => __( 'The unit weights are defined in for this country.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,184 @@
<?php
/**
* REST API Data controller.
*
* Handles requests to the /data endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.5.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Data controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Data_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'data';
/**
* Register routes.
*
* @since 3.5.0
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Check whether a given request has permission to read site data.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check whether a given request has permission to read site settings.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Return the list of data resources.
*
* @since 3.5.0
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$data = array();
$resources = array(
array(
'slug' => 'continents',
'description' => __( 'List of supported continents, countries, and states.', 'woocommerce' ),
),
array(
'slug' => 'countries',
'description' => __( 'List of supported states in a given country.', 'woocommerce' ),
),
array(
'slug' => 'currencies',
'description' => __( 'List of supported currencies.', 'woocommerce' ),
),
);
foreach ( $resources as $resource ) {
$item = $this->prepare_item_for_response( (object) $resource, $request );
$data[] = $this->prepare_response_for_collection( $item );
}
return rest_ensure_response( $data );
}
/**
* Prepare a data resource object for serialization.
*
* @param stdClass $resource Resource data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $resource, $request ) {
$data = array(
'slug' => $resource->slug,
'description' => $resource->description,
);
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, 'view' );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $resource ) );
return $response;
}
/**
* Prepare links for the request.
*
* @param object $item Data object.
* @return array Links for the given country.
*/
protected function prepare_links( $item ) {
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $item->slug ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
),
);
return $links;
}
/**
* Get the data index schema, conforming to JSON Schema.
*
* @since 3.5.0
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'data_index',
'type' => 'object',
'properties' => array(
'slug' => array(
'description' => __( 'Data resource ID.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'Data resource description.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,240 @@
<?php
/**
* REST API Data countries controller.
*
* Handles requests to the /data/countries endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.5.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Data countries controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Data_Countries_Controller extends WC_REST_Data_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'data/countries';
/**
* Register routes.
*
* @since 3.5.0
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<location>[\w-]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
'location' => array(
'description' => __( 'ISO3166 alpha-2 country code.', 'woocommerce' ),
'type' => 'string',
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Get a list of countries and states.
*
* @param string $country_code Country code.
* @param WP_REST_Request $request Request data.
* @return array|mixed Response data, ready for insertion into collection data.
*/
public function get_country( $country_code = false, $request ) {
$countries = WC()->countries->get_countries();
$states = WC()->countries->get_states();
$data = array();
if ( ! array_key_exists( $country_code, $countries ) ) {
return false;
}
$country = array(
'code' => $country_code,
'name' => $countries[ $country_code ],
);
$local_states = array();
if ( isset( $states[ $country_code ] ) ) {
foreach ( $states[ $country_code ] as $state_code => $state_name ) {
$local_states[] = array(
'code' => $state_code,
'name' => $state_name,
);
}
}
$country['states'] = $local_states;
return $country;
}
/**
* Return the list of states for all countries.
*
* @since 3.5.0
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$countries = WC()->countries->get_countries();
$data = array();
foreach ( array_keys( $countries ) as $country_code ) {
$country = $this->get_country( $country_code, $request );
$response = $this->prepare_item_for_response( $country, $request );
$data[] = $this->prepare_response_for_collection( $response );
}
return rest_ensure_response( $data );
}
/**
* Return the list of states for a given country.
*
* @since 3.5.0
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$data = $this->get_country( strtoupper( $request['location'] ), $request );
if ( empty( $data ) ) {
return new WP_Error( 'woocommerce_rest_data_invalid_location', __( 'There are no locations matching these parameters.', 'woocommerce' ), array( 'status' => 404 ) );
}
return $this->prepare_item_for_response( $data, $request );
}
/**
* Prepare the data object for response.
*
* @since 3.5.0
* @param object $item Data object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $item, $request ) {
$data = $this->add_additional_fields_to_object( $item, $request );
$data = $this->filter_response_by_context( $data, 'view' );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item ) );
/**
* Filter the states list for a country returned from the API.
*
* Allows modification of the loction data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param array $data The original country's states list.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_data_country', $response, $item, $request );
}
/**
* Prepare links for the request.
*
* @param object $item Data object.
* @return array Links for the given country.
*/
protected function prepare_links( $item ) {
$country_code = strtolower( $item['code'] );
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $country_code ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
),
);
return $links;
}
/**
* Get the location schema, conforming to JSON Schema.
*
* @since 3.5.0
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'data_countries',
'type' => 'object',
'properties' => array(
'code' => array(
'type' => 'string',
'description' => __( 'ISO3166 alpha-2 country code.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'name' => array(
'type' => 'string',
'description' => __( 'Full name of country.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'states' => array(
'type' => 'array',
'description' => __( 'List of states in this country.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'object',
'context' => array( 'view' ),
'readonly' => true,
'properties' => array(
'code' => array(
'type' => 'string',
'description' => __( 'State code.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'name' => array(
'type' => 'string',
'description' => __( 'Full name of state.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,221 @@
<?php
/**
* REST API Data currencies controller.
*
* Handles requests to the /data/currencies endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.5.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Data Currencies controller class.
*
* @package Automattic/WooCommerce/RestApi
*/
class WC_REST_Data_Currencies_Controller extends WC_REST_Data_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'data/currencies';
/**
* Register routes.
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/current', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_current_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<currency>[\w-]{3})', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'location' => array(
'description' => __( 'ISO4217 currency code.', 'woocommerce' ),
'type' => 'string',
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Get currency information.
*
* @param string $code Currency code.
* @param WP_REST_Request $request Request data.
* @return array|mixed Response data, ready for insertion into collection data.
*/
public function get_currency( $code = false, $request ) {
$currencies = get_woocommerce_currencies();
$data = array();
if ( ! array_key_exists( $code, $currencies ) ) {
return false;
}
$currency = array(
'code' => $code,
'name' => $currencies[ $code ],
'symbol' => get_woocommerce_currency_symbol( $code ),
);
return $currency;
}
/**
* Return the list of currencies.
*
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$currencies = get_woocommerce_currencies();
foreach ( array_keys( $currencies ) as $code ) {
$currency = $this->get_currency( $code, $request );
$response = $this->prepare_item_for_response( $currency, $request );
$data[] = $this->prepare_response_for_collection( $response );
}
return rest_ensure_response( $data );
}
/**
* Return information for a specific currency.
*
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$data = $this->get_currency( strtoupper( $request['currency'] ), $request );
if ( empty( $data ) ) {
return new WP_Error( 'woocommerce_rest_data_invalid_currency', __( 'There are no currencies matching these parameters.', 'woocommerce' ), array( 'status' => 404 ) );
}
return $this->prepare_item_for_response( $data, $request );
}
/**
* Return information for the current site currency.
*
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_current_item( $request ) {
$currency = get_option( 'woocommerce_currency' );
return $this->prepare_item_for_response( $this->get_currency( $currency, $request ), $request );
}
/**
* Prepare the data object for response.
*
* @param object $item Data object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $item, $request ) {
$data = $this->add_additional_fields_to_object( $item, $request );
$data = $this->filter_response_by_context( $data, 'view' );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item ) );
/**
* Filter currency returned from the API.
*
* @param WP_REST_Response $response The response object.
* @param array $item Currency data.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_data_currency', $response, $item, $request );
}
/**
* Prepare links for the request.
*
* @param object $item Data object.
* @return array Links for the given currency.
*/
protected function prepare_links( $item ) {
$code = strtoupper( $item['code'] );
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $code ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
),
);
return $links;
}
/**
* Get the currency schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'data_currencies',
'type' => 'object',
'properties' => array(
'code' => array(
'type' => 'string',
'description' => __( 'ISO4217 currency code.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'name' => array(
'type' => 'string',
'description' => __( 'Full name of currency.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
'symbol' => array(
'type' => 'string',
'description' => __( 'Currency symbol.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Network Orders controller
*
* Handles requests to the /orders/network endpoint
*
* @package Automattic/WooCommerce/RestApi
* @since 3.4.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Network Orders controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Network_Orders_V2_Controller
*/
class WC_REST_Network_Orders_Controller extends WC_REST_Network_Orders_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

View File

@ -0,0 +1,167 @@
<?php
/**
* REST API Order Notes controller
*
* Handles requests to the /orders/<order_id>/notes endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Order Notes controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Order_Notes_V2_Controller
*/
class WC_REST_Order_Notes_Controller extends WC_REST_Order_Notes_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Prepare a single order note output for response.
*
* @param WP_Comment $note Order note object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $note, $request ) {
$data = array(
'id' => (int) $note->comment_ID,
'author' => __( 'woocommerce', 'woocommerce' ) === $note->comment_author ? 'system' : $note->comment_author,
'date_created' => wc_rest_prepare_date_response( $note->comment_date ),
'date_created_gmt' => wc_rest_prepare_date_response( $note->comment_date_gmt ),
'note' => $note->comment_content,
'customer_note' => (bool) get_comment_meta( $note->comment_ID, 'is_customer_note', true ),
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $note ) );
/**
* Filter order note object returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param WP_Comment $note Order note object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_order_note', $response, $note, $request );
}
/**
* Create a single order note.
*
* @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 ) );
}
$order = wc_get_order( (int) $request['order_id'] );
if ( ! $order || $this->post_type !== $order->get_type() ) {
return new WP_Error( 'woocommerce_rest_order_invalid_id', __( 'Invalid order ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
// Create the note.
$note_id = $order->add_order_note( $request['note'], $request['customer_note'], $request['added_by_user'] );
if ( ! $note_id ) {
return new WP_Error( 'woocommerce_api_cannot_create_order_note', __( 'Cannot create order note, please try again.', 'woocommerce' ), array( 'status' => 500 ) );
}
$note = get_comment( $note_id );
$this->update_additional_fields_for_object( $note, $request );
/**
* Fires after a order note is created or updated via the REST API.
*
* @param WP_Comment $note New order note object.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating item, false when updating.
*/
do_action( 'woocommerce_rest_insert_order_note', $note, $request, true );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $note, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, str_replace( '(?P<order_id>[\d]+)', $order->get_id(), $this->rest_base ), $note_id ) ) );
return $response;
}
/**
* Get the Order Notes schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'order_note',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'author' => array(
'description' => __( 'Order note author.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created' => array(
'description' => __( "The date the order note was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created_gmt' => array(
'description' => __( 'The date the order note was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'note' => array(
'description' => __( 'Order note content.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'customer_note' => array(
'description' => __( 'If true, the note will be shown to customers and they will be notified. If false, the note will be for admin reference only.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'added_by_user' => array(
'description' => __( 'If true, this note will be attributed to the current user. If false, the note will be attributed to the system.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'edit' ),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,86 @@
<?php
/**
* REST API Order Refunds controller
*
* Handles requests to the /orders/<order_id>/refunds endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Order Refunds controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Order_Refunds_V2_Controller
*/
class WC_REST_Order_Refunds_Controller extends WC_REST_Order_Refunds_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Prepares one object for create or update operation.
*
* @since 3.0.0
* @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 ) {
$order = wc_get_order( (int) $request['order_id'] );
if ( ! $order ) {
return new WP_Error( 'woocommerce_rest_invalid_order_id', __( 'Invalid order ID.', 'woocommerce' ), 404 );
}
if ( 0 > $request['amount'] ) {
return new WP_Error( 'woocommerce_rest_invalid_order_refund', __( 'Refund amount must be greater than zero.', 'woocommerce' ), 400 );
}
// Create the refund.
$refund = wc_create_refund(
array(
'order_id' => $order->get_id(),
'amount' => $request['amount'],
'reason' => empty( $request['reason'] ) ? null : $request['reason'],
'line_items' => empty( $request['line_items'] ) ? array() : $request['line_items'],
'refund_payment' => is_bool( $request['api_refund'] ) ? $request['api_refund'] : true,
'restock_items' => true,
)
);
if ( is_wp_error( $refund ) ) {
return new WP_Error( 'woocommerce_rest_cannot_create_order_refund', $refund->get_error_message(), 500 );
}
if ( ! $refund ) {
return new WP_Error( 'woocommerce_rest_cannot_create_order_refund', __( 'Cannot create order refund, please try again.', 'woocommerce' ), 500 );
}
if ( ! empty( $request['meta_data'] ) && is_array( $request['meta_data'] ) ) {
foreach ( $request['meta_data'] as $meta ) {
$refund->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
}
$refund->save_meta_data();
}
/**
* Filters an object before it is inserted via the REST API.
*
* The dynamic portion of the hook name, `$this->post_type`,
* refers to the object type slug.
*
* @param WC_Data $coupon Object object.
* @param WP_REST_Request $request Request object.
* @param bool $creating If is creating a new object.
*/
return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $refund, $request, $creating );
}
}

View File

@ -0,0 +1,271 @@
<?php
/**
* REST API Orders controller
*
* Handles requests to the /orders endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Orders controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Orders_V2_Controller
*/
class WC_REST_Orders_Controller extends WC_REST_Orders_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Calculate coupons.
*
* @throws WC_REST_Exception When fails to set any item.
* @param WP_REST_Request $request Request object.
* @param WC_Order $order Order data.
* @return bool
*/
protected function calculate_coupons( $request, $order ) {
if ( ! isset( $request['coupon_lines'] ) || ! is_array( $request['coupon_lines'] ) ) {
return false;
}
// Remove all coupons first to ensure calculation is correct.
foreach ( $order->get_items( 'coupon' ) as $coupon ) {
$order->remove_coupon( $coupon->get_code() );
}
foreach ( $request['coupon_lines'] as $item ) {
if ( is_array( $item ) ) {
if ( empty( $item['id'] ) ) {
if ( empty( $item['code'] ) ) {
throw new WC_REST_Exception( 'woocommerce_rest_invalid_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 );
}
$results = $order->apply_coupon( wc_clean( $item['code'] ) );
if ( is_wp_error( $results ) ) {
throw new WC_REST_Exception( 'woocommerce_rest_' . $results->get_error_code(), $results->get_error_message(), 400 );
}
}
}
}
return true;
}
/**
* Prepare a single order for create or update.
*
* @throws WC_REST_Exception When fails to set any item.
* @param WP_REST_Request $request Request object.
* @param bool $creating If is creating a new object.
* @return WP_Error|WC_Data
*/
protected function prepare_object_for_database( $request, $creating = false ) {
$id = isset( $request['id'] ) ? absint( $request['id'] ) : 0;
$order = new WC_Order( $id );
$schema = $this->get_item_schema();
$data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) );
// Handle all writable props.
foreach ( $data_keys as $key ) {
$value = $request[ $key ];
if ( ! is_null( $value ) ) {
switch ( $key ) {
case 'coupon_lines':
case 'status':
// Change should be done later so transitions have new data.
break;
case 'billing':
case 'shipping':
$this->update_address( $order, $value, $key );
break;
case 'line_items':
case 'shipping_lines':
case 'fee_lines':
if ( is_array( $value ) ) {
foreach ( $value as $item ) {
if ( is_array( $item ) ) {
if ( $this->item_is_null( $item ) || ( isset( $item['quantity'] ) && 0 === $item['quantity'] ) ) {
$order->remove_item( $item['id'] );
} else {
$this->set_item( $order, $key, $item );
}
}
}
}
break;
case 'meta_data':
if ( is_array( $value ) ) {
foreach ( $value as $meta ) {
$order->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
}
}
break;
default:
if ( is_callable( array( $order, "set_{$key}" ) ) ) {
$order->{"set_{$key}"}( $value );
}
break;
}
}
}
/**
* Filters an object before it is inserted via the REST API.
*
* The dynamic portion of the hook name, `$this->post_type`,
* refers to the object type slug.
*
* @param WC_Data $order Object object.
* @param WP_REST_Request $request Request object.
* @param bool $creating If is creating a new object.
*/
return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $order, $request, $creating );
}
/**
* Save an object data.
*
* @since 3.0.0
* @throws WC_REST_Exception But all errors are validated before returning any data.
* @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;
}
// Make sure gateways are loaded so hooks from gateways fire on save/create.
WC()->payment_gateways();
if ( ! is_null( $request['customer_id'] ) && 0 !== $request['customer_id'] ) {
// Make sure customer exists.
if ( false === get_user_by( 'id', $request['customer_id'] ) ) {
throw new WC_REST_Exception( 'woocommerce_rest_invalid_customer_id', __( 'Customer ID is invalid.', 'woocommerce' ), 400 );
}
// Make sure customer is part of blog.
if ( is_multisite() && ! is_user_member_of_blog( $request['customer_id'] ) ) {
add_user_to_blog( get_current_blog_id(), $request['customer_id'], 'customer' );
}
}
if ( $creating ) {
$object->set_created_via( 'rest-api' );
$object->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
$object->calculate_totals();
} else {
// If items have changed, recalculate order totals.
if ( isset( $request['billing'] ) || isset( $request['shipping'] ) || isset( $request['line_items'] ) || isset( $request['shipping_lines'] ) || isset( $request['fee_lines'] ) || isset( $request['coupon_lines'] ) ) {
$object->calculate_totals( true );
}
}
// Set coupons.
$this->calculate_coupons( $request, $object );
// Set status.
if ( ! empty( $request['status'] ) ) {
$object->set_status( $request['status'] );
}
$object->save();
// Actions for after the order is saved.
if ( true === $request['set_paid'] ) {
if ( $creating || $object->needs_payment() ) {
$object->payment_complete( $request['transaction_id'] );
}
}
return $this->get_object( $object->get_id() );
} 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() ) );
}
}
/**
* Prepare objects query.
*
* @since 3.0.0
* @param WP_REST_Request $request Full details about the request.
* @return array
*/
protected function prepare_objects_query( $request ) {
// This is needed to get around an array to string notice in WC_REST_Orders_V2_Controller::prepare_objects_query.
$statuses = $request['status'];
unset( $request['status'] );
$args = parent::prepare_objects_query( $request );
$args['post_status'] = array();
foreach ( $statuses as $status ) {
if ( in_array( $status, $this->get_order_statuses(), true ) ) {
$args['post_status'][] = 'wc-' . $status;
} elseif ( 'any' === $status ) {
// Set status to "any" and short-circuit out.
$args['post_status'] = 'any';
break;
} else {
$args['post_status'][] = $status;
}
}
// Put the statuses back for further processing (next/prev links, etc).
$request['status'] = $statuses;
return $args;
}
/**
* Get the Order's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = parent::get_item_schema();
$schema['properties']['coupon_lines']['items']['properties']['discount']['readonly'] = true;
return $schema;
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
$params['status'] = array(
'default' => 'any',
'description' => __( 'Limit result set to orders which have specific statuses.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'string',
'enum' => array_merge( array( 'any', 'trash' ), $this->get_order_statuses() ),
),
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
}

View File

@ -0,0 +1,226 @@
<?php
/**
* REST API WC Payment gateways controller
*
* Handles requests to the /payment_gateways endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* Paymenga gateways controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Payment_Gateways_V2_Controller
*/
class WC_REST_Payment_Gateways_Controller extends WC_REST_Payment_Gateways_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Prepare a payment gateway for response.
*
* @param WC_Payment_Gateway $gateway Payment gateway object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $gateway, $request ) {
$order = (array) get_option( 'woocommerce_gateway_order' );
$item = array(
'id' => $gateway->id,
'title' => $gateway->title,
'description' => $gateway->description,
'order' => isset( $order[ $gateway->id ] ) ? $order[ $gateway->id ] : '',
'enabled' => ( 'yes' === $gateway->enabled ),
'method_title' => $gateway->get_method_title(),
'method_description' => $gateway->get_method_description(),
'method_supports' => $gateway->supports,
'settings' => $this->get_settings( $gateway ),
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $item, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $gateway, $request ) );
/**
* Filter payment gateway objects returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param WC_Payment_Gateway $gateway Payment gateway object.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_payment_gateway', $response, $gateway, $request );
}
/**
* Return settings associated with this payment gateway.
*
* @param WC_Payment_Gateway $gateway Gateway instance.
*
* @return array
*/
public function get_settings( $gateway ) {
$settings = array();
$gateway->init_form_fields();
foreach ( $gateway->form_fields as $id => $field ) {
// Make sure we at least have a title and type.
if ( empty( $field['title'] ) || empty( $field['type'] ) ) {
continue;
}
// Ignore 'enabled' and 'description' which get included elsewhere.
if ( in_array( $id, array( 'enabled', 'description' ), true ) ) {
continue;
}
$data = array(
'id' => $id,
'label' => empty( $field['label'] ) ? $field['title'] : $field['label'],
'description' => empty( $field['description'] ) ? '' : $field['description'],
'type' => $field['type'],
'value' => empty( $gateway->settings[ $id ] ) ? '' : $gateway->settings[ $id ],
'default' => empty( $field['default'] ) ? '' : $field['default'],
'tip' => empty( $field['description'] ) ? '' : $field['description'],
'placeholder' => empty( $field['placeholder'] ) ? '' : $field['placeholder'],
);
if ( ! empty( $field['options'] ) ) {
$data['options'] = $field['options'];
}
$settings[ $id ] = $data;
}
return $settings;
}
/**
* Get the payment gateway schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'payment_gateway',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Payment gateway ID.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'title' => array(
'description' => __( 'Payment gateway title on checkout.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'description' => array(
'description' => __( 'Payment gateway description on checkout.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'order' => array(
'description' => __( 'Payment gateway sort order.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'absint',
),
),
'enabled' => array(
'description' => __( 'Payment gateway enabled status.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
),
'method_title' => array(
'description' => __( 'Payment gateway method title.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'method_description' => array(
'description' => __( 'Payment gateway method description.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'method_supports' => array(
'description' => __( 'Supported features for this payment gateway.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
'settings' => array(
'description' => __( 'Payment gateway settings.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'id' => array(
'description' => __( 'A unique identifier for the setting.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'label' => array(
'description' => __( 'A human readable label for the setting used in interfaces.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'A human readable description for the setting used in interfaces.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'type' => array(
'description' => __( 'Type of setting.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'enum' => array( 'text', 'email', 'number', 'color', 'password', 'textarea', 'select', 'multiselect', 'radio', 'image_width', 'checkbox' ),
'readonly' => true,
),
'value' => array(
'description' => __( 'Setting value.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'default' => array(
'description' => __( 'Default value for the setting.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'tip' => array(
'description' => __( 'Additional help text shown to the user about the setting.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'placeholder' => array(
'description' => __( 'Placeholder text to be displayed in text inputs.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,724 @@
<?php
/**
* Abstract Rest Posts Controller Class
*
* @class WC_REST_Posts_Controller
* @package Automattic/WooCommerce/RestApi
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_REST_Posts_Controller
*
* @package Automattic/WooCommerce/RestApi
* @version 2.6.0
*/
abstract class WC_REST_Posts_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = '';
/**
* Post type.
*
* @var string
*/
protected $post_type = '';
/**
* Controls visibility on frontend.
*
* @var string
*/
protected $public = false;
/**
* Check if a given request has access to read items.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_post_permissions( $this->post_type, 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to create an item.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function create_item_permissions_check( $request ) {
if ( ! wc_rest_check_post_permissions( $this->post_type, 'create' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* 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 ) {
$post = get_post( (int) $request['id'] );
if ( $post && ! wc_rest_check_post_permissions( $this->post_type, 'read', $post->ID ) ) {
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 ) {
$post = get_post( (int) $request['id'] );
if ( $post && ! wc_rest_check_post_permissions( $this->post_type, 'edit', $post->ID ) ) {
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 ) {
$post = get_post( (int) $request['id'] );
if ( $post && ! wc_rest_check_post_permissions( $this->post_type, 'delete', $post->ID ) ) {
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;
}
/**
* Check if a given request has access batch create, update and delete items.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return boolean|WP_Error
*/
public function batch_items_permissions_check( $request ) {
if ( ! wc_rest_check_post_permissions( $this->post_type, 'batch' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* 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 ) {
$id = (int) $request['id'];
$post = get_post( $id );
if ( ! empty( $post->post_type ) && 'product_variation' === $post->post_type && 'product' === $this->post_type ) {
return new WP_Error( "woocommerce_rest_invalid_{$this->post_type}_id", __( 'To manipulate product variations you should use the /products/&lt;product_id&gt;/variations/&lt;id&gt; endpoint.', 'woocommerce' ), array( 'status' => 404 ) );
} elseif ( empty( $id ) || empty( $post->ID ) || $post->post_type !== $this->post_type ) {
return new WP_Error( "woocommerce_rest_invalid_{$this->post_type}_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$data = $this->prepare_item_for_response( $post, $request );
$response = rest_ensure_response( $data );
if ( $this->public ) {
$response->link_header( 'alternate', get_permalink( $id ), array( 'type' => 'text/html' ) );
}
return $response;
}
/**
* 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 ) );
}
$post = $this->prepare_item_for_database( $request );
if ( is_wp_error( $post ) ) {
return $post;
}
$post->post_type = $this->post_type;
$post_id = wp_insert_post( $post, true );
if ( is_wp_error( $post_id ) ) {
if ( in_array( $post_id->get_error_code(), array( 'db_insert_error' ) ) ) {
$post_id->add_data( array( 'status' => 500 ) );
} else {
$post_id->add_data( array( 'status' => 400 ) );
}
return $post_id;
}
$post->ID = $post_id;
$post = get_post( $post_id );
$this->update_additional_fields_for_object( $post, $request );
// Add meta fields.
$meta_fields = $this->add_post_meta_fields( $post, $request );
if ( is_wp_error( $meta_fields ) ) {
// Remove post.
$this->delete_post( $post );
return $meta_fields;
}
/**
* Fires after a single item is created or updated via the REST API.
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating item, false when updating.
*/
do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, true );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $post, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post_id ) ) );
return $response;
}
/**
* Add post meta fields.
*
* @param WP_Post $post Post Object.
* @param WP_REST_Request $request WP_REST_Request Object.
* @return bool|WP_Error
*/
protected function add_post_meta_fields( $post, $request ) {
return true;
}
/**
* Delete post.
*
* @param WP_Post $post Post object.
*/
protected function delete_post( $post ) {
wp_delete_post( $post->ID, true );
}
/**
* 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 ) {
$id = (int) $request['id'];
$post = get_post( $id );
if ( ! empty( $post->post_type ) && 'product_variation' === $post->post_type && 'product' === $this->post_type ) {
return new WP_Error( "woocommerce_rest_invalid_{$this->post_type}_id", __( 'To manipulate product variations you should use the /products/&lt;product_id&gt;/variations/&lt;id&gt; endpoint.', 'woocommerce' ), array( 'status' => 404 ) );
} elseif ( empty( $id ) || empty( $post->ID ) || $post->post_type !== $this->post_type ) {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'ID is invalid.', 'woocommerce' ), array( 'status' => 400 ) );
}
$post = $this->prepare_item_for_database( $request );
if ( is_wp_error( $post ) ) {
return $post;
}
// Convert the post object to an array, otherwise wp_update_post will expect non-escaped input.
$post_id = wp_update_post( (array) $post, true );
if ( is_wp_error( $post_id ) ) {
if ( in_array( $post_id->get_error_code(), array( 'db_update_error' ) ) ) {
$post_id->add_data( array( 'status' => 500 ) );
} else {
$post_id->add_data( array( 'status' => 400 ) );
}
return $post_id;
}
$post = get_post( $post_id );
$this->update_additional_fields_for_object( $post, $request );
// Update meta fields.
$meta_fields = $this->update_post_meta_fields( $post, $request );
if ( is_wp_error( $meta_fields ) ) {
return $meta_fields;
}
/**
* Fires after a single item is created or updated via the REST API.
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating item, false when updating.
*/
do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, false );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $post, $request );
return rest_ensure_response( $response );
}
/**
* 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 ) {
$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'];
$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'];
}
if ( 'wc/v1' === $this->namespace ) {
if ( is_array( $request['filter'] ) ) {
$args = array_merge( $args, $request['filter'] );
unset( $args['filter'] );
}
}
// 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}_query", $args, $request );
$query_args = $this->prepare_items_query( $args, $request );
$posts_query = new WP_Query();
$query_result = $posts_query->query( $query_args );
$posts = array();
foreach ( $query_result as $post ) {
if ( ! wc_rest_check_post_permissions( $this->post_type, 'read', $post->ID ) ) {
continue;
}
$data = $this->prepare_item_for_response( $post, $request );
$posts[] = $this->prepare_response_for_collection( $data );
}
$page = (int) $query_args['paged'];
$total_posts = $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;
}
$max_pages = ceil( $total_posts / (int) $query_args['posts_per_page'] );
$response = rest_ensure_response( $posts );
$response->header( 'X-WP-Total', (int) $total_posts );
$response->header( 'X-WP-TotalPages', (int) $max_pages );
$request_params = $request->get_query_params();
if ( ! empty( $request_params['filter'] ) ) {
// Normalize the pagination params.
unset( $request_params['filter']['posts_per_page'] );
unset( $request_params['filter']['paged'] );
}
$base = add_query_arg( $request_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 ) {
$id = (int) $request['id'];
$force = (bool) $request['force'];
$post = get_post( $id );
if ( empty( $id ) || empty( $post->ID ) || $post->post_type !== $this->post_type ) {
return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'ID is invalid.', 'woocommerce' ), array( 'status' => 404 ) );
}
$supports_trash = EMPTY_TRASH_DAYS > 0;
/**
* Filter whether an item is trashable.
*
* Return false to disable trash support for the item.
*
* @param boolean $supports_trash Whether the item type support trashing.
* @param WP_Post $post The Post object being considered for trashing support.
*/
$supports_trash = apply_filters( "woocommerce_rest_{$this->post_type}_trashable", $supports_trash, $post );
if ( ! wc_rest_check_post_permissions( $this->post_type, 'delete', $post->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_item_for_response( $post, $request );
// If we're forcing, then delete permanently.
if ( $force ) {
$result = wp_delete_post( $id, true );
} 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.
if ( 'trash' === $post->post_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 ) );
}
// (Note that internally this falls through to `wp_delete_post` if
// the trash is disabled.)
$result = wp_trash_post( $id );
}
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 item is deleted or trashed via the REST API.
*
* @param object $post The deleted or trashed item.
* @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}", $post, $response, $request );
return $response;
}
/**
* Prepare links for the request.
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
* @return array Links for the given post.
*/
protected function prepare_links( $post, $request ) {
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post->ID ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
),
);
return $links;
}
/**
* Determine the allowed query_vars for a get_items() response and
* prepare for WP_Query.
*
* @param array $prepared_args Prepared arguments.
* @param WP_REST_Request $request Request object.
* @return array $query_args
*/
protected function prepare_items_query( $prepared_args = array(), $request = null ) {
$valid_vars = array_flip( $this->get_allowed_query_vars() );
$query_args = array();
foreach ( $valid_vars as $var => $index ) {
if ( isset( $prepared_args[ $var ] ) ) {
/**
* Filter the query_vars used in `get_items` for the constructed query.
*
* The dynamic portion of the hook name, $var, refers to the query_var key.
*
* @param mixed $prepared_args[ $var ] The query_var value.
*/
$query_args[ $var ] = apply_filters( "woocommerce_rest_query_var-{$var}", $prepared_args[ $var ] );
}
}
$query_args['ignore_sticky_posts'] = true;
if ( 'include' === $query_args['orderby'] ) {
$query_args['orderby'] = 'post__in';
} elseif ( 'id' === $query_args['orderby'] ) {
$query_args['orderby'] = 'ID'; // ID must be capitalized.
} elseif ( 'slug' === $query_args['orderby'] ) {
$query_args['orderby'] = 'name';
}
return $query_args;
}
/**
* Get all the WP Query vars that are allowed for the API request.
*
* @return array
*/
protected function get_allowed_query_vars() {
global $wp;
/**
* Filter the publicly allowed query vars.
*
* Allows adjusting of the default query vars that are made public.
*
* @param array Array of allowed WP_Query query vars.
*/
$valid_vars = apply_filters( 'query_vars', $wp->public_query_vars );
$post_type_obj = get_post_type_object( $this->post_type );
if ( current_user_can( $post_type_obj->cap->edit_posts ) ) {
/**
* Filter the allowed 'private' query vars for authorized users.
*
* If the user has the `edit_posts` capability, we also allow use of
* private query parameters, which are only undesirable on the
* frontend, but are safe for use in query strings.
*
* To disable anyway, use
* `add_filter( 'woocommerce_rest_private_query_vars', '__return_empty_array' );`
*
* @param array $private_query_vars Array of allowed query vars for authorized users.
* }
*/
$private = apply_filters( 'woocommerce_rest_private_query_vars', $wp->private_query_vars );
$valid_vars = array_merge( $valid_vars, $private );
}
// Define our own in addition to WP's normal vars.
$rest_valid = array(
'date_query',
'ignore_sticky_posts',
'offset',
'post__in',
'post__not_in',
'post_parent',
'post_parent__in',
'post_parent__not_in',
'posts_per_page',
'meta_query',
'tax_query',
'meta_key',
'meta_value',
'meta_compare',
'meta_value_num',
);
$valid_vars = array_merge( $valid_vars, $rest_valid );
/**
* Filter allowed query vars for the REST API.
*
* This filter allows you to add or remove query vars from the final allowed
* list for all requests, including unauthenticated ones. To alter the
* vars for editors only.
*
* @param array {
* Array of allowed WP_Query query vars.
*
* @param string $allowed_query_var The query var to allow.
* }
*/
$valid_vars = apply_filters( 'woocommerce_rest_query_vars', $valid_vars );
return $valid_vars;
}
/**
* Get the query params for collections of attachments.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
$params['context']['default'] = 'view';
$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',
'modified',
),
'validate_callback' => 'rest_validate_request_arg',
);
$post_type_obj = get_post_type_object( $this->post_type );
if ( isset( $post_type_obj->hierarchical ) && $post_type_obj->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(),
);
}
if ( 'wc/v1' === $this->namespace ) {
$params['filter'] = array(
'type' => 'object',
'description' => __( 'Use WP Query arguments to modify the response; private query vars require appropriate authorization.', 'woocommerce' ),
);
}
return $params;
}
/**
* Update post meta fields.
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
* @return bool|WP_Error
*/
protected function update_post_meta_fields( $post, $request ) {
return true;
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Product Attribute Terms controller
*
* Handles requests to the products/attributes/<attribute_id>/terms endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Attribute Terms controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Product_Attribute_Terms_V2_Controller
*/
class WC_REST_Product_Attribute_Terms_Controller extends WC_REST_Product_Attribute_Terms_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Product Attributes controller
*
* Handles requests to the products/attributes endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Attributes controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Product_Attributes_V2_Controller
*/
class WC_REST_Product_Attributes_Controller extends WC_REST_Product_Attributes_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

View File

@ -0,0 +1,271 @@
<?php
/**
* REST API Product Categories controller
*
* Handles requests to the products/categories endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Categories controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Product_Categories_V2_Controller
*/
class WC_REST_Product_Categories_Controller extends WC_REST_Product_Categories_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Prepare a single product category output for response.
*
* @param WP_Term $item Term object.
* @param WP_REST_Request $request Request instance.
* @return WP_REST_Response
*/
public function prepare_item_for_response( $item, $request ) {
// Get category display type.
$display_type = get_term_meta( $item->term_id, 'display_type', true );
// Get category order.
$menu_order = get_term_meta( $item->term_id, 'order', true );
$data = array(
'id' => (int) $item->term_id,
'name' => $item->name,
'slug' => $item->slug,
'parent' => (int) $item->parent,
'description' => $item->description,
'display' => $display_type ? $display_type : 'default',
'image' => null,
'menu_order' => (int) $menu_order,
'count' => (int) $item->count,
);
// Get category image.
$image_id = get_term_meta( $item->term_id, 'thumbnail_id', true );
if ( $image_id ) {
$attachment = get_post( $image_id );
$data['image'] = array(
'id' => (int) $image_id,
'date_created' => wc_rest_prepare_date_response( $attachment->post_date ),
'date_created_gmt' => wc_rest_prepare_date_response( $attachment->post_date_gmt ),
'date_modified' => wc_rest_prepare_date_response( $attachment->post_modified ),
'date_modified_gmt' => wc_rest_prepare_date_response( $attachment->post_modified_gmt ),
'src' => wp_get_attachment_url( $image_id ),
'name' => get_the_title( $attachment ),
'alt' => get_post_meta( $image_id, '_wp_attachment_image_alt', true ),
);
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item, $request ) );
/**
* Filter a term item returned from the API.
*
* Allows modification of the term data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $item The original term object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( "woocommerce_rest_prepare_{$this->taxonomy}", $response, $item, $request );
}
/**
* Get the Category schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => $this->taxonomy,
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Category name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource unique to its type.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_title',
),
),
'parent' => array(
'description' => __( 'The ID for the parent of the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'description' => array(
'description' => __( 'HTML description of the resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'wp_filter_post_kses',
),
),
'display' => array(
'description' => __( 'Category archive display type.', 'woocommerce' ),
'type' => 'string',
'default' => 'default',
'enum' => array( 'default', 'products', 'subcategories', 'both' ),
'context' => array( 'view', 'edit' ),
),
'image' => array(
'description' => __( 'Image data.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'id' => array(
'description' => __( 'Image ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'date_created' => array(
'description' => __( "The date the image was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created_gmt' => array(
'description' => __( 'The date the image was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the image was last modified, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified_gmt' => array(
'description' => __( 'The date the image was last modified, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'src' => array(
'description' => __( 'Image URL.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit' ),
),
'name' => array(
'description' => __( 'Image name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'alt' => array(
'description' => __( 'Image alternative text.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
'menu_order' => array(
'description' => __( 'Menu order, used to custom sort the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'count' => array(
'description' => __( 'Number of published products for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Update term meta fields.
*
* @param WP_Term $term Term object.
* @param WP_REST_Request $request Request instance.
* @return bool|WP_Error
*
* @since 3.5.5
*/
protected function update_term_meta_fields( $term, $request ) {
$id = (int) $term->term_id;
if ( isset( $request['display'] ) ) {
update_term_meta( $id, 'display_type', 'default' === $request['display'] ? '' : $request['display'] );
}
if ( isset( $request['menu_order'] ) ) {
update_term_meta( $id, 'order', $request['menu_order'] );
}
if ( isset( $request['image'] ) ) {
if ( empty( $request['image']['id'] ) && ! empty( $request['image']['src'] ) ) {
$upload = wc_rest_upload_image_from_url( esc_url_raw( $request['image']['src'] ) );
if ( is_wp_error( $upload ) ) {
return $upload;
}
$image_id = wc_rest_set_uploaded_image_as_attachment( $upload );
} else {
$image_id = isset( $request['image']['id'] ) ? absint( $request['image']['id'] ) : 0;
}
// Check if image_id is a valid image attachment before updating the term meta.
if ( $image_id && wp_attachment_is_image( $image_id ) ) {
update_term_meta( $id, 'thumbnail_id', $image_id );
// Set the image alt.
if ( ! empty( $request['image']['alt'] ) ) {
update_post_meta( $image_id, '_wp_attachment_image_alt', wc_clean( $request['image']['alt'] ) );
}
// Set the image title.
if ( ! empty( $request['image']['name'] ) ) {
wp_update_post(
array(
'ID' => $image_id,
'post_title' => wc_clean( $request['image']['name'] ),
)
);
}
} else {
delete_term_meta( $id, 'thumbnail_id' );
}
}
return true;
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Product Shipping Classes controller
*
* Handles requests to the products/shipping_classes endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Shipping Classes controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Product_Shipping_Classes_V2_Controller
*/
class WC_REST_Product_Shipping_Classes_Controller extends WC_REST_Product_Shipping_Classes_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Product Tags controller
*
* Handles requests to the products/tags endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Tags controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Product_Tags_V2_Controller
*/
class WC_REST_Product_Tags_Controller extends WC_REST_Product_Tags_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

View File

@ -0,0 +1,860 @@
<?php
/**
* REST API variations controller
*
* Handles requests to the /products/<product_id>/variations endpoints.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API variations controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Product_Variations_V2_Controller
*/
class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Prepare a single variation output for response.
*
* @param WC_Data $object Object data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
public function prepare_object_for_response( $object, $request ) {
$data = array(
'id' => $object->get_id(),
'date_created' => wc_rest_prepare_date_response( $object->get_date_created(), false ),
'date_created_gmt' => wc_rest_prepare_date_response( $object->get_date_created() ),
'date_modified' => wc_rest_prepare_date_response( $object->get_date_modified(), false ),
'date_modified_gmt' => wc_rest_prepare_date_response( $object->get_date_modified() ),
'description' => wc_format_content( $object->get_description() ),
'permalink' => $object->get_permalink(),
'sku' => $object->get_sku(),
'price' => $object->get_price(),
'regular_price' => $object->get_regular_price(),
'sale_price' => $object->get_sale_price(),
'date_on_sale_from' => wc_rest_prepare_date_response( $object->get_date_on_sale_from(), false ),
'date_on_sale_from_gmt' => wc_rest_prepare_date_response( $object->get_date_on_sale_from() ),
'date_on_sale_to' => wc_rest_prepare_date_response( $object->get_date_on_sale_to(), false ),
'date_on_sale_to_gmt' => wc_rest_prepare_date_response( $object->get_date_on_sale_to() ),
'on_sale' => $object->is_on_sale(),
'status' => $object->get_status(),
'purchasable' => $object->is_purchasable(),
'virtual' => $object->is_virtual(),
'downloadable' => $object->is_downloadable(),
'downloads' => $this->get_downloads( $object ),
'download_limit' => '' !== $object->get_download_limit() ? (int) $object->get_download_limit() : -1,
'download_expiry' => '' !== $object->get_download_expiry() ? (int) $object->get_download_expiry() : -1,
'tax_status' => $object->get_tax_status(),
'tax_class' => $object->get_tax_class(),
'manage_stock' => $object->managing_stock(),
'stock_quantity' => $object->get_stock_quantity(),
'stock_status' => $object->get_stock_status(),
'backorders' => $object->get_backorders(),
'backorders_allowed' => $object->backorders_allowed(),
'backordered' => $object->is_on_backorder(),
'weight' => $object->get_weight(),
'dimensions' => array(
'length' => $object->get_length(),
'width' => $object->get_width(),
'height' => $object->get_height(),
),
'shipping_class' => $object->get_shipping_class(),
'shipping_class_id' => $object->get_shipping_class_id(),
'image' => $this->get_image( $object ),
'attributes' => $this->get_attributes( $object ),
'menu_order' => $object->get_menu_order(),
'meta_data' => $object->get_meta_data(),
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $object, $request ) );
/**
* Filter the data for a response.
*
* The dynamic portion of the hook name, $this->post_type,
* refers to object type being prepared for the response.
*
* @param WP_REST_Response $response The response object.
* @param WC_Data $object Object data.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "woocommerce_rest_prepare_{$this->post_type}_object", $response, $object, $request );
}
/**
* Prepare a single variation for create or update.
*
* @param WP_REST_Request $request Request object.
* @param bool $creating If is creating a new object.
* @return WP_Error|WC_Data
*/
protected function prepare_object_for_database( $request, $creating = false ) {
if ( isset( $request['id'] ) ) {
$variation = wc_get_product( absint( $request['id'] ) );
} else {
$variation = new WC_Product_Variation();
}
$variation->set_parent_id( absint( $request['product_id'] ) );
// Status.
if ( isset( $request['status'] ) ) {
$variation->set_status( get_post_status_object( $request['status'] ) ? $request['status'] : 'draft' );
}
// SKU.
if ( isset( $request['sku'] ) ) {
$variation->set_sku( wc_clean( $request['sku'] ) );
}
// Thumbnail.
if ( isset( $request['image'] ) ) {
if ( is_array( $request['image'] ) ) {
$variation = $this->set_variation_image( $variation, $request['image'] );
} else {
$variation->set_image_id( '' );
}
}
// Virtual variation.
if ( isset( $request['virtual'] ) ) {
$variation->set_virtual( $request['virtual'] );
}
// Downloadable variation.
if ( isset( $request['downloadable'] ) ) {
$variation->set_downloadable( $request['downloadable'] );
}
// Downloads.
if ( $variation->get_downloadable() ) {
// Downloadable files.
if ( isset( $request['downloads'] ) && is_array( $request['downloads'] ) ) {
$variation = $this->save_downloadable_files( $variation, $request['downloads'] );
}
// Download limit.
if ( isset( $request['download_limit'] ) ) {
$variation->set_download_limit( $request['download_limit'] );
}
// Download expiry.
if ( isset( $request['download_expiry'] ) ) {
$variation->set_download_expiry( $request['download_expiry'] );
}
}
// Shipping data.
$variation = $this->save_product_shipping_data( $variation, $request );
// Stock handling.
if ( isset( $request['manage_stock'] ) ) {
$variation->set_manage_stock( $request['manage_stock'] );
}
if ( isset( $request['stock_status'] ) ) {
$variation->set_stock_status( $request['stock_status'] );
}
if ( isset( $request['backorders'] ) ) {
$variation->set_backorders( $request['backorders'] );
}
if ( $variation->get_manage_stock() ) {
if ( isset( $request['stock_quantity'] ) ) {
$variation->set_stock_quantity( $request['stock_quantity'] );
} elseif ( isset( $request['inventory_delta'] ) ) {
$stock_quantity = wc_stock_amount( $variation->get_stock_quantity() );
$stock_quantity += wc_stock_amount( $request['inventory_delta'] );
$variation->set_stock_quantity( $stock_quantity );
}
} else {
$variation->set_backorders( 'no' );
$variation->set_stock_quantity( '' );
}
// Regular Price.
if ( isset( $request['regular_price'] ) ) {
$variation->set_regular_price( $request['regular_price'] );
}
// Sale Price.
if ( isset( $request['sale_price'] ) ) {
$variation->set_sale_price( $request['sale_price'] );
}
if ( isset( $request['date_on_sale_from'] ) ) {
$variation->set_date_on_sale_from( $request['date_on_sale_from'] );
}
if ( isset( $request['date_on_sale_from_gmt'] ) ) {
$variation->set_date_on_sale_from( $request['date_on_sale_from_gmt'] ? strtotime( $request['date_on_sale_from_gmt'] ) : null );
}
if ( isset( $request['date_on_sale_to'] ) ) {
$variation->set_date_on_sale_to( $request['date_on_sale_to'] );
}
if ( isset( $request['date_on_sale_to_gmt'] ) ) {
$variation->set_date_on_sale_to( $request['date_on_sale_to_gmt'] ? strtotime( $request['date_on_sale_to_gmt'] ) : null );
}
// Tax class.
if ( isset( $request['tax_class'] ) ) {
$variation->set_tax_class( $request['tax_class'] );
}
// Description.
if ( isset( $request['description'] ) ) {
$variation->set_description( wp_kses_post( $request['description'] ) );
}
// Update taxonomies.
if ( isset( $request['attributes'] ) ) {
$attributes = array();
$parent = wc_get_product( $variation->get_parent_id() );
if ( ! $parent ) {
return new WP_Error(
// Translators: %d parent ID.
"woocommerce_rest_{$this->post_type}_invalid_parent", sprintf( __( 'Cannot set attributes due to invalid parent product.', 'woocommerce' ), $variation->get_parent_id() ), array(
'status' => 404,
)
);
}
$parent_attributes = $parent->get_attributes();
foreach ( $request['attributes'] as $attribute ) {
$attribute_id = 0;
$attribute_name = '';
// Check ID for global attributes or name for product attributes.
if ( ! empty( $attribute['id'] ) ) {
$attribute_id = absint( $attribute['id'] );
$attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id );
} elseif ( ! empty( $attribute['name'] ) ) {
$attribute_name = sanitize_title( $attribute['name'] );
}
if ( ! $attribute_id && ! $attribute_name ) {
continue;
}
if ( ! isset( $parent_attributes[ $attribute_name ] ) || ! $parent_attributes[ $attribute_name ]->get_variation() ) {
continue;
}
$attribute_key = sanitize_title( $parent_attributes[ $attribute_name ]->get_name() );
$attribute_value = isset( $attribute['option'] ) ? wc_clean( stripslashes( $attribute['option'] ) ) : '';
if ( $parent_attributes[ $attribute_name ]->is_taxonomy() ) {
// If dealing with a taxonomy, we need to get the slug from the name posted to the API.
$term = get_term_by( 'name', $attribute_value, $attribute_name );
if ( $term && ! is_wp_error( $term ) ) {
$attribute_value = $term->slug;
} else {
$attribute_value = sanitize_title( $attribute_value );
}
}
$attributes[ $attribute_key ] = $attribute_value;
}
$variation->set_attributes( $attributes );
}
// Menu order.
if ( $request['menu_order'] ) {
$variation->set_menu_order( $request['menu_order'] );
}
// Meta data.
if ( is_array( $request['meta_data'] ) ) {
foreach ( $request['meta_data'] as $meta ) {
$variation->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
}
}
/**
* Filters an object before it is inserted via the REST API.
*
* The dynamic portion of the hook name, `$this->post_type`,
* refers to the object type slug.
*
* @param WC_Data $variation Object object.
* @param WP_REST_Request $request Request object.
* @param bool $creating If is creating a new object.
*/
return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $variation, $request, $creating );
}
/**
* Get the image for a product variation.
*
* @param WC_Product_Variation $variation Variation data.
* @return array
*/
protected function get_image( $variation ) {
if ( ! $variation->get_image_id() ) {
return;
}
$attachment_id = $variation->get_image_id();
$attachment_post = get_post( $attachment_id );
if ( is_null( $attachment_post ) ) {
return;
}
$attachment = wp_get_attachment_image_src( $attachment_id, 'full' );
if ( ! is_array( $attachment ) ) {
return;
}
if ( ! isset( $image ) ) {
return array(
'id' => (int) $attachment_id,
'date_created' => wc_rest_prepare_date_response( $attachment_post->post_date, false ),
'date_created_gmt' => wc_rest_prepare_date_response( strtotime( $attachment_post->post_date_gmt ) ),
'date_modified' => wc_rest_prepare_date_response( $attachment_post->post_modified, false ),
'date_modified_gmt' => wc_rest_prepare_date_response( strtotime( $attachment_post->post_modified_gmt ) ),
'src' => current( $attachment ),
'name' => get_the_title( $attachment_id ),
'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
);
}
}
/**
* Set variation image.
*
* @throws WC_REST_Exception REST API exceptions.
* @param WC_Product_Variation $variation Variation instance.
* @param array $image Image data.
* @return WC_Product_Variation
*/
protected function set_variation_image( $variation, $image ) {
$attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0;
if ( 0 === $attachment_id && isset( $image['src'] ) ) {
$upload = wc_rest_upload_image_from_url( esc_url_raw( $image['src'] ) );
if ( is_wp_error( $upload ) ) {
if ( ! apply_filters( 'woocommerce_rest_suppress_image_upload_error', false, $upload, $variation->get_id(), array( $image ) ) ) {
throw new WC_REST_Exception( 'woocommerce_variation_image_upload_error', $upload->get_error_message(), 400 );
}
}
$attachment_id = wc_rest_set_uploaded_image_as_attachment( $upload, $variation->get_id() );
}
if ( ! wp_attachment_is_image( $attachment_id ) ) {
/* translators: %s: attachment ID */
throw new WC_REST_Exception( 'woocommerce_variation_invalid_image_id', sprintf( __( '#%s is an invalid image ID.', 'woocommerce' ), $attachment_id ), 400 );
}
$variation->set_image_id( $attachment_id );
// Set the image alt if present.
if ( ! empty( $image['alt'] ) ) {
update_post_meta( $attachment_id, '_wp_attachment_image_alt', wc_clean( $image['alt'] ) );
}
// Set the image name if present.
if ( ! empty( $image['name'] ) ) {
wp_update_post(
array(
'ID' => $attachment_id,
'post_title' => $image['name'],
)
);
}
return $variation;
}
/**
* Get the Variation's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$weight_unit = get_option( 'woocommerce_weight_unit' );
$dimension_unit = get_option( 'woocommerce_dimension_unit' );
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => $this->post_type,
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created' => array(
'description' => __( "The date the variation was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the variation was last modified, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'Variation description.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'permalink' => array(
'description' => __( 'Variation URL.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'sku' => array(
'description' => __( 'Unique identifier.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'price' => array(
'description' => __( 'Current variation price.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'regular_price' => array(
'description' => __( 'Variation regular price.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'sale_price' => array(
'description' => __( 'Variation sale price.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'date_on_sale_from' => array(
'description' => __( "Start date of sale price, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'date_on_sale_from_gmt' => array(
'description' => __( 'Start date of sale price, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'date_on_sale_to' => array(
'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'date_on_sale_to_gmt' => array(
'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'on_sale' => array(
'description' => __( 'Shows if the variation is on sale.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'status' => array(
'description' => __( 'Variation status.', 'woocommerce' ),
'type' => 'string',
'default' => 'publish',
'enum' => array_keys( get_post_statuses() ),
'context' => array( 'view', 'edit' ),
),
'purchasable' => array(
'description' => __( 'Shows if the variation can be bought.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'virtual' => array(
'description' => __( 'If the variation is virtual.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'downloadable' => array(
'description' => __( 'If the variation is downloadable.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'downloads' => array(
'description' => __( 'List of downloadable files.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'File ID.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'name' => array(
'description' => __( 'File name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'file' => array(
'description' => __( 'File URL.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
),
'download_limit' => array(
'description' => __( 'Number of times downloadable files can be downloaded after purchase.', 'woocommerce' ),
'type' => 'integer',
'default' => -1,
'context' => array( 'view', 'edit' ),
),
'download_expiry' => array(
'description' => __( 'Number of days until access to downloadable files expires.', 'woocommerce' ),
'type' => 'integer',
'default' => -1,
'context' => array( 'view', 'edit' ),
),
'tax_status' => array(
'description' => __( 'Tax status.', 'woocommerce' ),
'type' => 'string',
'default' => 'taxable',
'enum' => array( 'taxable', 'shipping', 'none' ),
'context' => array( 'view', 'edit' ),
),
'tax_class' => array(
'description' => __( 'Tax class.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'manage_stock' => array(
'description' => __( 'Stock management at variation level.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'stock_quantity' => array(
'description' => __( 'Stock quantity.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'stock_status' => array(
'description' => __( 'Controls the stock status of the product.', 'woocommerce' ),
'type' => 'string',
'default' => 'instock',
'enum' => array_keys( wc_get_product_stock_status_options() ),
'context' => array( 'view', 'edit' ),
),
'backorders' => array(
'description' => __( 'If managing stock, this controls if backorders are allowed.', 'woocommerce' ),
'type' => 'string',
'default' => 'no',
'enum' => array( 'no', 'notify', 'yes' ),
'context' => array( 'view', 'edit' ),
),
'backorders_allowed' => array(
'description' => __( 'Shows if backorders are allowed.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'backordered' => array(
'description' => __( 'Shows if the variation is on backordered.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'weight' => array(
/* translators: %s: weight unit */
'description' => sprintf( __( 'Variation weight (%s).', 'woocommerce' ), $weight_unit ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'dimensions' => array(
'description' => __( 'Variation dimensions.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'length' => array(
/* translators: %s: dimension unit */
'description' => sprintf( __( 'Variation length (%s).', 'woocommerce' ), $dimension_unit ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'width' => array(
/* translators: %s: dimension unit */
'description' => sprintf( __( 'Variation width (%s).', 'woocommerce' ), $dimension_unit ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'height' => array(
/* translators: %s: dimension unit */
'description' => sprintf( __( 'Variation height (%s).', 'woocommerce' ), $dimension_unit ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
'shipping_class' => array(
'description' => __( 'Shipping class slug.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'shipping_class_id' => array(
'description' => __( 'Shipping class ID.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'image' => array(
'description' => __( 'Variation image data.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'id' => array(
'description' => __( 'Image ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'date_created' => array(
'description' => __( "The date the image was created, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created_gmt' => array(
'description' => __( 'The date the image was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the image was last modified, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified_gmt' => array(
'description' => __( 'The date the image was last modified, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'src' => array(
'description' => __( 'Image URL.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit' ),
),
'name' => array(
'description' => __( 'Image name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'alt' => array(
'description' => __( 'Image alternative text.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
'attributes' => array(
'description' => __( 'List of attributes.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Attribute ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'name' => array(
'description' => __( 'Attribute name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'option' => array(
'description' => __( 'Selected attribute term name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
),
'menu_order' => array(
'description' => __( 'Menu order, used to custom sort products.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'meta_data' => array(
'description' => __( 'Meta data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Meta ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'key' => array(
'description' => __( 'Meta key.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ),
'context' => array( 'view', 'edit' ),
),
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Prepare objects query.
*
* @since 3.0.0
* @param WP_REST_Request $request Full details about the request.
* @return array
*/
protected function prepare_objects_query( $request ) {
$args = WC_REST_CRUD_Controller::prepare_objects_query( $request );
// Set post_status.
$args['post_status'] = $request['status'];
// Filter by sku.
if ( ! empty( $request['sku'] ) ) {
$skus = explode( ',', $request['sku'] );
// Include the current string as a SKU too.
if ( 1 < count( $skus ) ) {
$skus[] = $request['sku'];
}
$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
$args,
array(
'key' => '_sku',
'value' => $skus,
'compare' => 'IN',
)
);
}
// Filter by tax class.
if ( ! empty( $request['tax_class'] ) ) {
$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
$args,
array(
'key' => '_tax_class',
'value' => 'standard' !== $request['tax_class'] ? $request['tax_class'] : '',
)
);
}
// Price filter.
if ( ! empty( $request['min_price'] ) || ! empty( $request['max_price'] ) ) {
$args['meta_query'] = $this->add_meta_query( $args, wc_get_min_max_price_meta_query( $request ) ); // WPCS: slow query ok.
}
// Filter product based on stock_status.
if ( ! empty( $request['stock_status'] ) ) {
$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
$args,
array(
'key' => '_stock_status',
'value' => $request['stock_status'],
)
);
}
// Filter by on sale products.
if ( is_bool( $request['on_sale'] ) ) {
$on_sale_key = $request['on_sale'] ? 'post__in' : 'post__not_in';
$on_sale_ids = wc_get_product_ids_on_sale();
// Use 0 when there's no on sale products to avoid return all products.
$on_sale_ids = empty( $on_sale_ids ) ? array( 0 ) : $on_sale_ids;
$args[ $on_sale_key ] += $on_sale_ids;
}
// Force the post_type argument, since it's not a user input variable.
if ( ! empty( $request['sku'] ) ) {
$args['post_type'] = array( 'product', 'product_variation' );
} else {
$args['post_type'] = $this->post_type;
}
$args['post_parent'] = $request['product_id'];
return $args;
}
/**
* Get the query params for collections of attachments.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
unset(
$params['in_stock'],
$params['type'],
$params['featured'],
$params['category'],
$params['tag'],
$params['shipping_class'],
$params['attribute'],
$params['attribute_term']
);
$params['stock_status'] = array(
'description' => __( 'Limit result set to products with specified stock status.', 'woocommerce' ),
'type' => 'string',
'enum' => array_keys( wc_get_product_stock_status_options() ),
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,143 @@
<?php
/**
* REST API Reports Coupons Totals controller
*
* Handles requests to the /reports/coupons/count endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.5.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Reports Coupons Totals controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Reports_Controller
*/
class WC_REST_Report_Coupons_Totals_Controller extends WC_REST_Reports_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'reports/coupons/totals';
/**
* Get reports list.
*
* @since 3.5.0
* @return array
*/
protected function get_reports() {
global $wpdb;
$data = get_transient( 'rest_api_coupons_type_count' );
if ( false !== $data ) {
return $data;
}
$types = wc_get_coupon_types();
$data = array();
foreach ( $types as $slug => $name ) {
$results = $wpdb->get_results(
$wpdb->prepare( "
SELECT count(meta_id) AS total
FROM $wpdb->postmeta
WHERE meta_key = 'discount_type'
AND meta_value = %s
", $slug )
);
$total = isset( $results[0] ) ? (int) $results[0]->total : 0;
$data[] = array(
'slug' => $slug,
'name' => $name,
'total' => $total,
);
}
set_transient( 'rest_api_coupons_type_count', $data, YEAR_IN_SECONDS );
return $data;
}
/**
* Prepare a report object for serialization.
*
* @param stdClass $report Report data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $report, $request ) {
$data = array(
'slug' => $report->slug,
'name' => $report->name,
'total' => $report->total,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
/**
* Filter a report returned from the API.
*
* Allows modification of the report data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $report The original report object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_report_coupons_count', $response, $report, $request );
}
/**
* Get the Report's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'report_coupon_total',
'type' => 'object',
'properties' => array(
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Coupon type name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'total' => array(
'description' => __( 'Amount of coupons.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,154 @@
<?php
/**
* REST API Reports Customers Totals controller
*
* Handles requests to the /reports/customers/count endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.5.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Reports Customers Totals controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Reports_Controller
*/
class WC_REST_Report_Customers_Totals_Controller extends WC_REST_Reports_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'reports/customers/totals';
/**
* Get reports list.
*
* @since 3.5.0
* @return array
*/
protected function get_reports() {
$users_count = count_users();
$total_customers = 0;
foreach ( $users_count['avail_roles'] as $role => $total ) {
if ( in_array( $role, array( 'administrator', 'shop_manager' ), true ) ) {
continue;
}
$total_customers += (int) $total;
}
$customers_query = new WP_User_Query(
array(
'role__not_in' => array( 'administrator', 'shop_manager' ),
'number' => 0,
'fields' => 'ID',
'count_total' => true,
'meta_query' => array( // WPCS: slow query ok.
array(
'key' => 'paying_customer',
'value' => 1,
'compare' => '=',
),
),
)
);
$total_paying = (int) $customers_query->get_total();
$data = array(
array(
'slug' => 'paying',
'name' => __( 'Paying customer', 'woocommerce' ),
'total' => $total_paying,
),
array(
'slug' => 'non_paying',
'name' => __( 'Non-paying customer', 'woocommerce' ),
'total' => $total_customers - $total_paying,
),
);
return $data;
}
/**
* Prepare a report object for serialization.
*
* @param stdClass $report Report data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $report, $request ) {
$data = array(
'slug' => $report->slug,
'name' => $report->name,
'total' => $report->total,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
/**
* Filter a report returned from the API.
*
* Allows modification of the report data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $report The original report object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_report_customers_count', $response, $report, $request );
}
/**
* Get the Report's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'report_customer_total',
'type' => 'object',
'properties' => array(
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Customer type name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'total' => array(
'description' => __( 'Amount of customers.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,127 @@
<?php
/**
* REST API Reports Orders Totals controller
*
* Handles requests to the /reports/orders/count endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.5.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Reports Orders Totals controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Reports_Controller
*/
class WC_REST_Report_Orders_Totals_Controller extends WC_REST_Reports_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'reports/orders/totals';
/**
* Get reports list.
*
* @since 3.5.0
* @return array
*/
protected function get_reports() {
$totals = wp_count_posts( 'shop_order' );
$data = array();
foreach ( wc_get_order_statuses() as $slug => $name ) {
if ( ! isset( $totals->$slug ) ) {
continue;
}
$data[] = array(
'slug' => str_replace( 'wc-', '', $slug ),
'name' => $name,
'total' => (int) $totals->$slug,
);
}
return $data;
}
/**
* Prepare a report object for serialization.
*
* @param stdClass $report Report data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $report, $request ) {
$data = array(
'slug' => $report->slug,
'name' => $report->name,
'total' => $report->total,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
/**
* Filter a report returned from the API.
*
* Allows modification of the report data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $report The original report object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_report_orders_count', $response, $report, $request );
}
/**
* Get the Report's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'report_order_total',
'type' => 'object',
'properties' => array(
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Order status name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'total' => array(
'description' => __( 'Amount of orders.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,133 @@
<?php
/**
* REST API Reports Products Totals controller
*
* Handles requests to the /reports/products/count endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.5.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Reports Products Totals controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Reports_Controller
*/
class WC_REST_Report_Products_Totals_Controller extends WC_REST_Reports_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'reports/products/totals';
/**
* Get reports list.
*
* @since 3.5.0
* @return array
*/
protected function get_reports() {
$types = wc_get_product_types();
$terms = get_terms(
array(
'taxonomy' => 'product_type',
'hide_empty' => false,
)
);
$data = array();
foreach ( $terms as $product_type ) {
if ( ! isset( $types[ $product_type->name ] ) ) {
continue;
}
$data[] = array(
'slug' => $product_type->name,
'name' => $types[ $product_type->name ],
'total' => (int) $product_type->count,
);
}
return $data;
}
/**
* Prepare a report object for serialization.
*
* @param stdClass $report Report data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $report, $request ) {
$data = array(
'slug' => $report->slug,
'name' => $report->name,
'total' => $report->total,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
/**
* Filter a report returned from the API.
*
* Allows modification of the report data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $report The original report object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_report_products_count', $response, $report, $request );
}
/**
* Get the Report's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'report_product_total',
'type' => 'object',
'properties' => array(
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Product type name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'total' => array(
'description' => __( 'Amount of products.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,132 @@
<?php
/**
* REST API Reports Reviews Totals controller
*
* Handles requests to the /reports/reviews/count endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.5.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Reports Reviews Totals controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Reports_Controller
*/
class WC_REST_Report_Reviews_Totals_Controller extends WC_REST_Reports_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'reports/reviews/totals';
/**
* Get reports list.
*
* @since 3.5.0
* @return array
*/
protected function get_reports() {
$data = array();
$query_data = array(
'count' => true,
'post_type' => 'product',
'meta_key' => 'rating', // WPCS: slow query ok.
'meta_value' => '', // WPCS: slow query ok.
);
for ( $i = 1; $i <= 5; $i++ ) {
$query_data['meta_value'] = $i;
$data[] = array(
'slug' => 'rated_' . $i . '_out_of_5',
/* translators: %s: average rating */
'name' => sprintf( __( 'Rated %s out of 5', 'woocommerce' ), $i ),
'total' => (int) get_comments( $query_data ),
);
}
return $data;
}
/**
* Prepare a report object for serialization.
*
* @param stdClass $report Report data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $report, $request ) {
$data = array(
'slug' => $report->slug,
'name' => $report->name,
'total' => $report->total,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
/**
* Filter a report returned from the API.
*
* Allows modification of the report data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $report The original report object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_report_reviews_count', $response, $report, $request );
}
/**
* Get the Report's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'report_review_total',
'type' => 'object',
'properties' => array(
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Review type name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'total' => array(
'description' => __( 'Amount of reviews.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Reports controller
*
* Handles requests to the reports/sales endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Report Sales controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Report_Sales_V2_Controller
*/
class WC_REST_Report_Sales_Controller extends WC_REST_Report_Sales_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Reports controller
*
* Handles requests to the reports/top_sellers endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Report Top Sellers controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Report_Top_Sellers_V2_Controller
*/
class WC_REST_Report_Top_Sellers_Controller extends WC_REST_Report_Top_Sellers_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

View File

@ -0,0 +1,72 @@
<?php
/**
* REST API Reports controller
*
* Handles requests to the reports endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Reports controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Reports_V2_Controller
*/
class WC_REST_Reports_Controller extends WC_REST_Reports_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Get reports list.
*
* @since 3.5.0
* @return array
*/
protected function get_reports() {
$reports = parent::get_reports();
$reports[] = array(
'slug' => 'orders/totals',
'description' => __( 'Orders totals.', 'woocommerce' ),
);
$reports[] = array(
'slug' => 'products/totals',
'description' => __( 'Products totals.', 'woocommerce' ),
);
$reports[] = array(
'slug' => 'customers/totals',
'description' => __( 'Customers totals.', 'woocommerce' ),
);
$reports[] = array(
'slug' => 'coupons/totals',
'description' => __( 'Coupons totals.', 'woocommerce' ),
);
$reports[] = array(
'slug' => 'reviews/totals',
'description' => __( 'Reviews totals.', 'woocommerce' ),
);
$reports[] = array(
'slug' => 'categories/totals',
'description' => __( 'Categories totals.', 'woocommerce' ),
);
$reports[] = array(
'slug' => 'tags/totals',
'description' => __( 'Tags totals.', 'woocommerce' ),
);
$reports[] = array(
'slug' => 'attributes/totals',
'description' => __( 'Attributes totals.', 'woocommerce' ),
);
return $reports;
}
}

View File

@ -0,0 +1,256 @@
<?php
/**
* REST API Setting Options controller
*
* Handles requests to the /settings/$group/$setting endpoints.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Setting Options controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Setting_Options_V2_Controller
*/
class WC_REST_Setting_Options_Controller extends WC_REST_Setting_Options_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Get setting data.
*
* @param string $group_id Group ID.
* @param string $setting_id Setting ID.
* @return stdClass|WP_Error
*/
public function get_setting( $group_id, $setting_id ) {
$setting = parent::get_setting( $group_id, $setting_id );
if ( is_wp_error( $setting ) ) {
return $setting;
}
$setting['group_id'] = $group_id;
return $setting;
}
/**
* Callback for allowed keys for each setting response.
*
* @param string $key Key to check.
* @return boolean
*/
public function allowed_setting_keys( $key ) {
return in_array(
$key, array(
'id',
'group_id',
'label',
'description',
'default',
'tip',
'placeholder',
'type',
'options',
'value',
'option_key',
), true
);
}
/**
* Get all settings in a group.
*
* @param string $group_id Group ID.
* @return array|WP_Error
*/
public function get_group_settings( $group_id ) {
if ( empty( $group_id ) ) {
return new WP_Error( 'rest_setting_setting_group_invalid', __( 'Invalid setting group.', 'woocommerce' ), array( 'status' => 404 ) );
}
$settings = apply_filters( 'woocommerce_settings-' . $group_id, array() ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
if ( empty( $settings ) ) {
return new WP_Error( 'rest_setting_setting_group_invalid', __( 'Invalid setting group.', 'woocommerce' ), array( 'status' => 404 ) );
}
$filtered_settings = array();
foreach ( $settings as $setting ) {
$option_key = $setting['option_key'];
$setting = $this->filter_setting( $setting );
$default = isset( $setting['default'] ) ? $setting['default'] : '';
// Get the option value.
if ( is_array( $option_key ) ) {
$option = get_option( $option_key[0] );
$setting['value'] = isset( $option[ $option_key[1] ] ) ? $option[ $option_key[1] ] : $default;
} else {
$admin_setting_value = WC_Admin_Settings::get_option( $option_key, $default );
$setting['value'] = $admin_setting_value;
}
if ( 'multi_select_countries' === $setting['type'] ) {
$setting['options'] = WC()->countries->get_countries();
$setting['type'] = 'multiselect';
} elseif ( 'single_select_country' === $setting['type'] ) {
$setting['type'] = 'select';
$setting['options'] = $this->get_countries_and_states();
} elseif ( 'single_select_page' === $setting['type'] ) {
$pages = get_pages(
array(
'sort_column' => 'menu_order',
'sort_order' => 'ASC',
'hierarchical' => 0,
)
);
$options = array();
foreach ( $pages as $page ) {
$options[ $page->ID ] = ! empty( $page->post_title ) ? $page->post_title : '#' . $page->ID;
}
$setting['type'] = 'select';
$setting['options'] = $options;
}
$filtered_settings[] = $setting;
}
return $filtered_settings;
}
/**
* Returns a list of countries and states for use in the base location setting.
*
* @since 3.0.7
* @return array Array of states and countries.
*/
private function get_countries_and_states() {
$countries = WC()->countries->get_countries();
if ( ! $countries ) {
return array();
}
$output = array();
foreach ( $countries as $key => $value ) {
$states = WC()->countries->get_states( $key );
if ( $states ) {
foreach ( $states as $state_key => $state_value ) {
$output[ $key . ':' . $state_key ] = $value . ' - ' . $state_value;
}
} else {
$output[ $key ] = $value;
}
}
return $output;
}
/**
* Get the settings schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'setting',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'A unique identifier for the setting.', 'woocommerce' ),
'type' => 'string',
'arg_options' => array(
'sanitize_callback' => 'sanitize_title',
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'group_id' => array(
'description' => __( 'An identifier for the group this setting belongs to.', 'woocommerce' ),
'type' => 'string',
'arg_options' => array(
'sanitize_callback' => 'sanitize_title',
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'label' => array(
'description' => __( 'A human readable label for the setting used in interfaces.', 'woocommerce' ),
'type' => 'string',
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'A human readable description for the setting used in interfaces.', 'woocommerce' ),
'type' => 'string',
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'value' => array(
'description' => __( 'Setting value.', 'woocommerce' ),
'type' => array( 'string', 'array', 'null' ),
'items' => array(
'type' => array( 'string', 'null' ),
),
'context' => array( 'view', 'edit' ),
),
'default' => array(
'description' => __( 'Default value for the setting.', 'woocommerce' ),
'type' => array( 'string', 'array', 'null' ),
'items' => array(
'type' => array( 'string', 'null' ),
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'tip' => array(
'description' => __( 'Additional help text shown to the user about the setting.', 'woocommerce' ),
'type' => 'string',
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'placeholder' => array(
'description' => __( 'Placeholder text to be displayed in text inputs.', 'woocommerce' ),
'type' => 'string',
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'type' => array(
'description' => __( 'Type of setting.', 'woocommerce' ),
'type' => 'string',
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
'context' => array( 'view', 'edit' ),
'enum' => array( 'text', 'email', 'number', 'color', 'password', 'textarea', 'select', 'multiselect', 'radio', 'image_width', 'checkbox' ),
'readonly' => true,
),
'options' => array(
'description' => __( 'Array of options (key value pairs) for inputs such as select, multiselect, and radio buttons.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,112 @@
<?php
/**
* REST API Settings controller
*
* Handles requests to the /settings endpoints.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Settings controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Settings_V2_Controller
*/
class WC_REST_Settings_Controller extends WC_REST_Settings_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Register routes.
*/
public function register_routes() {
parent::register_routes();
register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'batch_items' ),
'permission_callback' => array( $this, 'update_items_permissions_check' ),
),
'schema' => array( $this, 'get_public_batch_schema' ),
) );
}
/**
* Makes sure the current user has access to WRITE the settings APIs.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|bool
*/
public function update_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you cannot edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Update a setting.
*
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
$options_controller = new WC_REST_Setting_Options_Controller();
$response = $options_controller->update_item( $request );
return $response;
}
/**
* Get the groups schema, conforming to JSON Schema.
*
* @since 3.0.0
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'setting_group',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'A unique identifier that can be used to link settings together.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'label' => array(
'description' => __( 'A human readable label for the setting used in interfaces.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'description' => array(
'description' => __( 'A human readable description for the setting used in interfaces.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'parent_id' => array(
'description' => __( 'ID of parent grouping.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'sub_groups' => array(
'description' => __( 'IDs for settings sub groups.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API WC Shipping Methods controller
*
* Handles requests to the /shipping_methods endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* Shipping methods controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Shipping_Methods_V2_Controller
*/
class WC_REST_Shipping_Methods_Controller extends WC_REST_Shipping_Methods_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Shipping Zone Locations controller
*
* Handles requests to the /shipping/zones/<id>/locations endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Shipping Zone Locations class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Shipping_Zone_Locations_V2_Controller
*/
class WC_REST_Shipping_Zone_Locations_Controller extends WC_REST_Shipping_Zone_Locations_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Shipping Zone Methods controller
*
* Handles requests to the /shipping/zones/<id>/methods endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Shipping Zone Methods class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Shipping_Zone_Methods_V2_Controller
*/
class WC_REST_Shipping_Zone_Methods_Controller extends WC_REST_Shipping_Zone_Methods_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

View File

@ -0,0 +1,125 @@
<?php
/**
* REST API Shipping Zones Controller base
*
* Houses common functionality between Shipping Zones and Locations.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Shipping Zones base class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Controller
*/
abstract class WC_REST_Shipping_Zones_Controller_Base extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'shipping/zones';
/**
* Retrieve a Shipping Zone by it's ID.
*
* @param int $zone_id Shipping Zone ID.
* @return WC_Shipping_Zone|WP_Error
*/
protected function get_zone( $zone_id ) {
$zone = WC_Shipping_Zones::get_zone_by( 'zone_id', $zone_id );
if ( false === $zone ) {
return new WP_Error( 'woocommerce_rest_shipping_zone_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
}
return $zone;
}
/**
* Check whether a given request has permission to read Shipping Zones.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_shipping_enabled() ) {
return new WP_Error( 'rest_no_route', __( 'Shipping is disabled.', 'woocommerce' ), array( 'status' => 404 ) );
}
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to create Shipping Zones.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function create_item_permissions_check( $request ) {
if ( ! wc_shipping_enabled() ) {
return new WP_Error( 'rest_no_route', __( 'Shipping is disabled.', 'woocommerce' ), array( 'status' => 404 ) );
}
if ( ! wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check whether a given request has permission to edit Shipping Zones.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function update_items_permissions_check( $request ) {
if ( ! wc_shipping_enabled() ) {
return new WP_Error( 'rest_no_route', __( 'Shipping is disabled.', 'woocommerce' ), array( 'status' => 404 ) );
}
if ( ! wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
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 whether a given request has permission to delete Shipping Zones.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function delete_items_permissions_check( $request ) {
if ( ! wc_shipping_enabled() ) {
return new WP_Error( 'rest_no_route', __( 'Shipping is disabled.', 'woocommerce' ), array( 'status' => 404 ) );
}
if ( ! wc_rest_check_manager_permissions( 'settings', 'delete' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API Shipping Zones controller
*
* Handles requests to the /shipping/zones endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Shipping Zones class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_Shipping_Zones_V2_Controller
*/
class WC_REST_Shipping_Zones_Controller extends WC_REST_Shipping_Zones_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API WC System Status controller
*
* Handles requests to the /system_status endpoint.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* System status controller class.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_System_Status_V2_Controller
*/
class WC_REST_System_Status_Controller extends WC_REST_System_Status_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* REST API WC System Status Tools Controller
*
* Handles requests to the /system_status/tools/* endpoints.
*
* @package Automattic/WooCommerce/RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* System status tools controller.
*
* @package Automattic/WooCommerce/RestApi
* @extends WC_REST_System_Status_Tools_V2_Controller
*/
class WC_REST_System_Status_Tools_Controller extends WC_REST_System_Status_Tools_V2_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
}

Some files were not shown because too many files have changed in this diff Show More