File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/Version2.tar
class-wc-rest-coupons-v2-controller.php 0000644 00000046043 15155060366 0014154 0 ustar 00 <?php
/**
* REST API Coupons controller
*
* Handles requests to the /coupons endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
use Automattic\WooCommerce\Utilities\StringUtil;
defined( 'ABSPATH' ) || exit;
/**
* REST API Coupons controller class.
*
* @package 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'],
'status' => $data['status'],
'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 );
$coupon_code = $request['code'] ?? null;
if ( ! StringUtil::is_null_or_whitespace( $coupon_code ) ) {
$id = wc_get_coupon_id_by_code( $coupon_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 && StringUtil::is_null_or_whitespace( $request['code'] ?? null ) ) {
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' ),
),
'status' => array(
'description' => __( 'The status of the coupon. Should always be draft, published, or pending review', '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' => 'mixed',
'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;
}
}
class-wc-rest-customer-downloads-v2-controller.php 0000644 00000012675 15155060366 0016323 0 ustar 00 <?php
/**
* REST API Customer Downloads controller
*
* Handles requests to the /customers/<customer_id>/downloads endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Customers controller class.
*
* @package 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 );
}
}
class-wc-rest-customers-v2-controller.php 0000644 00000032426 15155060366 0014512 0 ustar 00 <?php
/**
* REST API Customers controller
*
* Handles requests to the /customers endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Customers controller class.
*
* @package 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 ) {
$formatted_data = $this->get_formatted_item_data_core( $object );
$formatted_data['orders_count'] = $object->get_order_count();
$formatted_data['total_spent'] = $object->get_total_spent();
return $formatted_data;
}
/**
* Get formatted item data, not including orders count nor total spent.
* This method is needed because v3 API doesn't return those two fields.
*
* @internal This method could disappear or have its name or signature changed in future releases.
*
* @param WC_Data $object WC_Data instance.
* @return array
*/
protected function get_formatted_item_data_core( $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 && is_subclass_of( $data[ $key ], 'DateTime' ) ? 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 );
}
$formatted_data = 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(),
);
if ( wc_current_user_has_role( 'administrator' ) ) {
$formatted_data['meta_data'] = $data['meta_data'];
}
return $formatted_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 ) );
//phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
/**
* 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 );
//phpcs:enable WooCommerce.Commenting.CommentHooks.MissingSinceComment
}
/**
* 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' => 'mixed',
'context' => array( 'view', 'edit' ),
),
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}
class-wc-rest-network-orders-v2-controller.php 0000644 00000012222 15155060366 0015443 0 ustar 00 <?php
/**
* REST API Network Orders controller
*
* Handles requests to the /orders/network endpoint
*
* @package WooCommerce\RestApi
* @since 3.4.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Network Orders controller class.
*
* @package 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;
}
}
class-wc-rest-order-notes-v2-controller.php 0000644 00000012764 15155060366 0014732 0 ustar 00 <?php
/**
* REST API Order Notes controller
*
* Handles requests to the /orders/<order_id>/notes endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Order Notes controller class.
*
* @package 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;
}
}
class-wc-rest-order-refunds-v2-controller.php 0000644 00000045405 15155060366 0015246 0 ustar 00 <?php
/**
* REST API Order Refunds controller
*
* Handles requests to the /orders/<order_id>/refunds endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Order Refunds controller class.
*
* @package 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', 'shipping_lines', 'tax_lines', 'fee_lines' );
// 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'],
'shipping_lines' => $data['shipping_lines'],
'tax_lines' => $data['tax_lines'],
'fee_lines' => $data['fee_lines'],
);
}
/**
* 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 refund 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', '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' => 'mixed',
'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,
),
'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,
),
'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' => 'mixed',
'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;
}
}
class-wc-rest-orders-v2-controller.php 0000644 00000212262 15155060366 0013762 0 ustar 00 <?php
/**
* REST API Orders controller
*
* Handles requests to the /orders endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Utilities\ArrayUtil;
use Automattic\WooCommerce\Utilities\OrderUtil;
use Automattic\WooCommerce\Utilities\StringUtil;
// phpcs:disable Squiz.Classes.ClassFileName.NoMatch, Squiz.Classes.ValidClassName.NotCamelCaps -- Legacy class name, can't change without breaking backward compat.
/**
* REST API Orders controller class.
*
* @package WooCommerce\RestApi
* @extends WC_REST_CRUD_Controller
*/
class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
// phpcs:enable
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'orders';
/**
* Post type.
*
* @var string
*/
protected $post_type = 'shop_order';
/**
* If object is hierarchical.
*
* @var bool
*/
protected $hierarchical = true;
/**
* Stores the request.
*
* @var array
*/
protected $request = array();
/**
* Register the routes for orders.
*/
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' => __( '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. Return false if object is not of required type.
*
* @since 3.0.0
* @param int $id Object ID.
* @return WC_Data|bool
*/
protected function get_object( $id ) {
$order = wc_get_order( $id );
// In case id is a refund's id (or it's not an order at all), don't expose it via /orders/ path.
if ( ! $order || 'shop_order_refund' === $order->get_type() ) {
return false;
}
return $order;
}
/**
* 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' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return parent::get_item_permissions_check( $request );
}
/**
* 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, 'read' ) ) {
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 parent::update_item_permissions_check( $request );
}
/**
* 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, 'read' ) ) {
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 parent::delete_item_permissions_check( $request );
}
/**
* Expands an order item to get its data.
*
* @param WC_Order_item $item Order item data.
* @return array
*/
protected function get_order_item_data( $item ) {
$data = $item->get_data();
$format_decimal = array( 'subtotal', 'subtotal_tax', 'total', 'total_tax', 'tax_total', 'shipping_tax_total' );
// Format decimal values.
foreach ( $format_decimal as $key ) {
if ( isset( $data[ $key ] ) ) {
$data[ $key ] = wc_format_decimal( $data[ $key ], $this->request['dp'] );
}
}
// Add SKU, PRICE, and IMAGE to products.
if ( is_callable( array( $item, 'get_product' ) ) ) {
$data['sku'] = $item->get_product() ? $item->get_product()->get_sku() : null;
$data['price'] = $item->get_quantity() ? $item->get_total() / $item->get_quantity() : 0;
$image_id = $item->get_product() ? $item->get_product()->get_image_id() : 0;
$data['image'] = array(
'id' => $image_id,
'src' => $image_id ? wp_get_attachment_image_url( $image_id, 'full' ) : '',
);
}
// Add parent_name if the product is a variation.
if ( is_callable( array( $item, 'get_product' ) ) ) {
$product = $item->get_product();
if ( is_callable( array( $product, 'get_parent_data' ) ) ) {
$data['parent_name'] = $product->get_title();
} else {
$data['parent_name'] = null;
}
}
// Format taxes.
if ( ! empty( $data['taxes']['total'] ) ) {
$taxes = array();
foreach ( $data['taxes']['total'] as $tax_rate_id => $tax ) {
$taxes[] = array(
'id' => $tax_rate_id,
'total' => $tax,
'subtotal' => isset( $data['taxes']['subtotal'][ $tax_rate_id ] ) ? $data['taxes']['subtotal'][ $tax_rate_id ] : '',
);
}
$data['taxes'] = $taxes;
} elseif ( isset( $data['taxes'] ) ) {
$data['taxes'] = array();
}
// Remove names for coupons, taxes and shipping.
if ( isset( $data['code'] ) || isset( $data['rate_code'] ) || isset( $data['method_title'] ) ) {
unset( $data['name'] );
}
// Remove props we don't want to expose.
unset( $data['order_id'] );
unset( $data['type'] );
// Expand meta_data to include user-friendly values.
$formatted_meta_data = $item->get_all_formatted_meta_data( null );
// Filter out product variations.
if ( isset( $product ) && 'true' === $this->request['order_item_display_meta'] ) {
$order_item_name = $data['name'];
$data['meta_data'] = array_filter(
$data['meta_data'],
function( $meta ) use ( $product, $order_item_name ) {
$display_value = wp_kses_post( rawurldecode( (string) $meta->value ) );
// Skip items with values already in the product details area of the product name.
if ( $product && $product->is_type( 'variation' ) && wc_is_attribute_in_product_name( $display_value, $order_item_name ) ) {
return false;
}
return true;
}
);
}
$data['meta_data'] = array_map(
array( $this, 'merge_meta_item_with_formatted_meta_display_attributes' ),
$data['meta_data'],
array_fill( 0, count( $data['meta_data'] ), $formatted_meta_data )
);
return $data;
}
/**
* Merge the `$formatted_meta_data` `display_key` and `display_value` attribute values into the corresponding
* {@link WC_Meta_Data}. Returns the merged array.
*
* @param WC_Meta_Data $meta_item An object from {@link WC_Order_Item::get_meta_data()}.
* @param array $formatted_meta_data An object result from {@link WC_Order_Item::get_all_formatted_meta_data}.
* The keys are the IDs of {@link WC_Meta_Data}.
*
* @return array
*/
private function merge_meta_item_with_formatted_meta_display_attributes( $meta_item, $formatted_meta_data ) {
$result = array(
'id' => $meta_item->id,
'key' => $meta_item->key,
'value' => $meta_item->value,
'display_key' => $meta_item->key, // Default to original key, in case a formatted key is not available.
'display_value' => $meta_item->value, // Default to original value, in case a formatted value is not available.
);
if ( array_key_exists( $meta_item->id, $formatted_meta_data ) ) {
$formatted_meta_item = $formatted_meta_data[ $meta_item->id ];
$result['display_key'] = wc_clean( $formatted_meta_item->display_key );
$result['display_value'] = wc_clean( $formatted_meta_item->display_value );
}
return $result;
}
/**
* With HPOS, few internal meta keys such as _billing_address_index, _shipping_address_index are not considered internal anymore (since most internal keys were flattened into dedicated columns).
*
* This function helps in filtering out any remaining internal meta keys with HPOS is enabled.
*
* @param array $meta_data Order meta data.
*
* @return array Filtered order meta data.
*/
private function filter_internal_meta_keys( $meta_data ) {
if ( ! OrderUtil::custom_orders_table_usage_is_enabled() ) {
return $meta_data;
}
$cpt_hidden_keys = ( new \WC_Order_Data_Store_CPT() )->get_internal_meta_keys();
$meta_data = array_filter(
$meta_data,
function ( $meta ) use ( $cpt_hidden_keys ) {
return ! in_array( $meta->key, $cpt_hidden_keys, true );
}
);
return array_values( $meta_data );
}
/**
* Get formatted item data.
*
* @since 3.0.0
* @param WC_Order $order WC_Data instance.
*
* @return array
*/
protected function get_formatted_item_data( $order ) {
$extra_fields = array( 'meta_data', 'line_items', 'tax_lines', 'shipping_lines', 'fee_lines', 'coupon_lines', 'refunds', 'payment_url', 'is_editable', 'needs_payment', 'needs_processing' );
$format_decimal = array( 'discount_total', 'discount_tax', 'shipping_total', 'shipping_tax', 'shipping_total', 'shipping_tax', 'cart_tax', 'total', 'total_tax' );
$format_date = array( 'date_created', 'date_modified', 'date_completed', 'date_paid' );
// These fields are dependent on other fields.
$dependent_fields = array(
'date_created_gmt' => 'date_created',
'date_modified_gmt' => 'date_modified',
'date_completed_gmt' => 'date_completed',
'date_paid_gmt' => 'date_paid',
);
$format_line_items = array( 'line_items', 'tax_lines', 'shipping_lines', 'fee_lines', 'coupon_lines' );
// Only fetch fields that we need.
$fields = $this->get_fields_for_response( $this->request );
foreach ( $dependent_fields as $field_key => $dependency ) {
if ( in_array( $field_key, $fields, true ) && ! in_array( $dependency, $fields, true ) ) {
$fields[] = $dependency;
}
}
$extra_fields = array_intersect( $extra_fields, $fields );
$format_decimal = array_intersect( $format_decimal, $fields );
$format_date = array_intersect( $format_date, $fields );
$format_line_items = array_intersect( $format_line_items, $fields );
$data = $order->get_base_data();
// Add extra data as necessary.
foreach ( $extra_fields as $field ) {
switch ( $field ) {
case 'meta_data':
$meta_data = $order->get_meta_data();
$data['meta_data'] = $this->get_meta_data_for_response( $this->request, $meta_data );
$data['meta_data'] = $this->filter_internal_meta_keys( $data['meta_data'] );
break;
case 'line_items':
$data['line_items'] = $order->get_items( 'line_item' );
break;
case 'tax_lines':
$data['tax_lines'] = $order->get_items( 'tax' );
break;
case 'shipping_lines':
$data['shipping_lines'] = $order->get_items( 'shipping' );
break;
case 'fee_lines':
$data['fee_lines'] = $order->get_items( 'fee' );
break;
case 'coupon_lines':
$data['coupon_lines'] = $order->get_items( 'coupon' );
break;
case 'refunds':
$data['refunds'] = array();
foreach ( $order->get_refunds() as $refund ) {
$data['refunds'][] = array(
'id' => $refund->get_id(),
'reason' => $refund->get_reason() ? $refund->get_reason() : '',
'total' => '-' . wc_format_decimal( $refund->get_amount(), $this->request['dp'] ),
);
}
break;
case 'payment_url':
$data['payment_url'] = $order->get_checkout_payment_url();
break;
case 'is_editable':
$data['is_editable'] = $order->is_editable();
break;
case 'needs_payment':
$data['needs_payment'] = $order->needs_payment();
break;
case 'needs_processing':
$data['needs_processing'] = $order->needs_processing();
break;
}
}
// 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 the order status.
$data['status'] = 'wc-' === substr( $data['status'], 0, 3 ) ? substr( $data['status'], 3 ) : $data['status'];
// Format line items.
foreach ( $format_line_items as $key ) {
$data[ $key ] = array_values( array_map( array( $this, 'get_order_item_data' ), $data[ $key ] ) );
}
$allowed_fields = array(
'id',
'parent_id',
'number',
'order_key',
'created_via',
'version',
'status',
'currency',
'date_created',
'date_created_gmt',
'date_modified',
'date_modified_gmt',
'discount_total',
'discount_tax',
'shipping_total',
'shipping_tax',
'cart_tax',
'total',
'total_tax',
'prices_include_tax',
'customer_id',
'customer_ip_address',
'customer_user_agent',
'customer_note',
'billing',
'shipping',
'payment_method',
'payment_method_title',
'transaction_id',
'date_paid',
'date_paid_gmt',
'date_completed',
'date_completed_gmt',
'cart_hash',
'meta_data',
'line_items',
'tax_lines',
'shipping_lines',
'fee_lines',
'coupon_lines',
'refunds',
'payment_url',
'is_editable',
'needs_payment',
'needs_processing',
);
$data = array_intersect_key( $data, array_flip( $allowed_fields ) );
return $data;
}
/**
* 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_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'] );
$request['context'] = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->get_formatted_item_data( $object );
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $request['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.
*
* @since 4.5.0
*/
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 ) {
$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 ) ),
),
);
if ( 0 !== (int) $object->get_customer_id() ) {
$links['customer'] = array(
'href' => rest_url( sprintf( '/%s/customers/%d', $this->namespace, $object->get_customer_id() ) ),
);
}
if ( 0 !== (int) $object->get_parent_id() ) {
$links['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 ) {
global $wpdb;
$args = parent::prepare_objects_query( $request );
// Set post_status.
if ( in_array( $request['status'], $this->get_order_statuses(), true ) ) {
$args['post_status'] = 'wc-' . $request['status'];
} elseif ( 'any' === $request['status'] ) {
$args['post_status'] = 'any';
} else {
$args['post_status'] = $request['status'];
}
if ( isset( $request['customer'] ) ) {
if ( ! empty( $args['meta_query'] ) ) {
$args['meta_query'] = array(); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
}
$args['meta_query'][] = array(
'key' => '_customer_user',
'value' => $request['customer'],
'type' => 'NUMERIC',
);
}
// Search by product.
if ( ! empty( $request['product'] ) ) {
$order_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT order_id
FROM {$wpdb->prefix}woocommerce_order_items
WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_key = '_product_id' AND meta_value = %d )
AND order_item_type = 'line_item'",
$request['product']
)
);
// Force WP_Query return empty if don't found any order.
$order_ids = ! empty( $order_ids ) ? $order_ids : array( 0 );
$args['post__in'] = $order_ids;
}
// Search.
if ( ! OrderUtil::custom_orders_table_usage_is_enabled() && ! empty( $args['s'] ) ) {
$order_ids = wc_order_search( $args['s'] );
if ( ! empty( $order_ids ) ) {
unset( $args['s'] );
$args['post__in'] = array_merge( $order_ids, array( 0 ) );
}
}
/**
* Filter the query arguments for a request.
*
* Enables adding extra arguments or setting defaults for an order collection request.
*
* @param array $args Key value array of query var to query value.
* @param WP_REST_Request $request The request used.
*
* @since 4.5.0.
*/
$args = apply_filters( 'woocommerce_rest_orders_prepare_object_query', $args, $request );
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 order 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;
$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 'status':
// 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':
case 'coupon_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.
*
* @since 4.5.0.
*/
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->save();
$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 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() ) );
}
}
/**
* Update address.
*
* @param WC_Order $order Order data.
* @param array $posted Posted data.
* @param string $type Type of address; 'billing' or 'shipping'.
*/
protected function update_address( $order, $posted, $type = 'billing' ) {
foreach ( $posted as $key => $value ) {
if ( is_callable( array( $order, "set_{$type}_{$key}" ) ) ) {
$order->{"set_{$type}_{$key}"}( $value );
}
}
}
/**
* Gets the product ID from the SKU or posted ID.
*
* @throws WC_REST_Exception When SKU or ID is not valid.
* @param array $posted Request data.
* @param string $action 'create' to add line item or 'update' to update it.
* @return int
*/
protected function get_product_id( $posted, $action = 'create' ) {
if ( ! empty( $posted['sku'] ) ) {
$product_id = (int) wc_get_product_id_by_sku( $posted['sku'] );
} elseif ( ! empty( $posted['product_id'] ) && empty( $posted['variation_id'] ) ) {
$product_id = (int) $posted['product_id'];
} elseif ( ! empty( $posted['variation_id'] ) ) {
$product_id = (int) $posted['variation_id'];
} elseif ( 'update' === $action ) {
$product_id = 0;
} else {
throw new WC_REST_Exception( 'woocommerce_rest_required_product_reference', __( 'Product ID or SKU is required.', 'woocommerce' ), 400 );
}
return $product_id;
}
/**
* Maybe set an item prop if the value was posted.
*
* @param WC_Order_Item $item Order item.
* @param string $prop Order property.
* @param array $posted Request data.
*/
protected function maybe_set_item_prop( $item, $prop, $posted ) {
if ( isset( $posted[ $prop ] ) ) {
$item->{"set_$prop"}( $posted[ $prop ] );
}
}
/**
* Maybe set item props if the values were posted.
*
* @param WC_Order_Item $item Order item data.
* @param string[] $props Properties.
* @param array $posted Request data.
*/
protected function maybe_set_item_props( $item, $props, $posted ) {
foreach ( $props as $prop ) {
$this->maybe_set_item_prop( $item, $prop, $posted );
}
}
/**
* Maybe set item meta if posted.
*
* @param WC_Order_Item $item Order item data.
* @param array $posted Request data.
*/
protected function maybe_set_item_meta_data( $item, $posted ) {
if ( ! empty( $posted['meta_data'] ) && is_array( $posted['meta_data'] ) ) {
foreach ( $posted['meta_data'] as $meta ) {
if ( isset( $meta['key'] ) ) {
$value = isset( $meta['value'] ) ? $meta['value'] : null;
$item->update_meta_data( $meta['key'], $value, isset( $meta['id'] ) ? $meta['id'] : '' );
}
}
}
}
/**
* Create or update a line item.
*
* @param array $posted Line item data.
* @param string $action 'create' to add line item or 'update' to update it.
* @param object $item Passed when updating an item. Null during creation.
* @return WC_Order_Item_Product
* @throws WC_REST_Exception Invalid data, server error.
*/
protected function prepare_line_items( $posted, $action = 'create', $item = null ) {
$item = is_null( $item ) ? new WC_Order_Item_Product( ! empty( $posted['id'] ) ? $posted['id'] : '' ) : $item;
$product = wc_get_product( $this->get_product_id( $posted, $action ) );
if ( $product && $product !== $item->get_product() ) {
$item->set_product( $product );
if ( 'create' === $action ) {
$quantity = isset( $posted['quantity'] ) ? $posted['quantity'] : 1;
$total = wc_get_price_excluding_tax( $product, array( 'qty' => $quantity ) );
$item->set_total( $total );
$item->set_subtotal( $total );
}
}
$this->maybe_set_item_props( $item, array( 'name', 'quantity', 'total', 'subtotal', 'tax_class' ), $posted );
$this->maybe_set_item_meta_data( $item, $posted );
return $item;
}
/**
* Create or update an order shipping method.
*
* @param array $posted $shipping Item data.
* @param string $action 'create' to add shipping or 'update' to update it.
* @param object $item Passed when updating an item. Null during creation.
* @return WC_Order_Item_Shipping
* @throws WC_REST_Exception Invalid data, server error.
*/
protected function prepare_shipping_lines( $posted, $action = 'create', $item = null ) {
$item = is_null( $item ) ? new WC_Order_Item_Shipping( ! empty( $posted['id'] ) ? $posted['id'] : '' ) : $item;
if ( 'create' === $action ) {
if ( empty( $posted['method_id'] ) ) {
throw new WC_REST_Exception( 'woocommerce_rest_invalid_shipping_item', __( 'Shipping method ID is required.', 'woocommerce' ), 400 );
}
}
$this->maybe_set_item_props( $item, array( 'method_id', 'method_title', 'total', 'instance_id' ), $posted );
$this->maybe_set_item_meta_data( $item, $posted );
return $item;
}
/**
* Create or update an order fee.
*
* @param array $posted Item data.
* @param string $action 'create' to add fee or 'update' to update it.
* @param object $item Passed when updating an item. Null during creation.
* @return WC_Order_Item_Fee
* @throws WC_REST_Exception Invalid data, server error.
*/
protected function prepare_fee_lines( $posted, $action = 'create', $item = null ) {
$item = is_null( $item ) ? new WC_Order_Item_Fee( ! empty( $posted['id'] ) ? $posted['id'] : '' ) : $item;
if ( 'create' === $action ) {
if ( empty( $posted['name'] ) ) {
throw new WC_REST_Exception( 'woocommerce_rest_invalid_fee_item', __( 'Fee name is required.', 'woocommerce' ), 400 );
}
}
$this->maybe_set_item_props( $item, array( 'name', 'tax_class', 'tax_status', 'total' ), $posted );
$this->maybe_set_item_meta_data( $item, $posted );
return $item;
}
/**
* Create or update an order coupon.
*
* @param array $posted Item data.
* @param string $action 'create' to add coupon or 'update' to update it.
* @param object $item Passed when updating an item. Null during creation.
* @return WC_Order_Item_Coupon
* @throws WC_REST_Exception Invalid data, server error.
*/
protected function prepare_coupon_lines( $posted, $action = 'create', $item = null ) {
$item = is_null( $item ) ? new WC_Order_Item_Coupon( ! empty( $posted['id'] ) ? $posted['id'] : '' ) : $item;
if ( 'create' === $action ) {
$coupon_code = ArrayUtil::get_value_or_default( $posted, 'code' );
if ( StringUtil::is_null_or_whitespace( $coupon_code ) ) {
throw new WC_REST_Exception( 'woocommerce_rest_invalid_coupon_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 );
}
}
$this->maybe_set_item_props( $item, array( 'code', 'discount' ), $posted );
$this->maybe_set_item_meta_data( $item, $posted );
return $item;
}
/**
* Wrapper method to create/update order items.
* When updating, the item ID provided is checked to ensure it is associated
* with the order.
*
* @param WC_Order $order order object.
* @param string $item_type The item type.
* @param array $posted item provided in the request body.
* @throws WC_REST_Exception If item ID is not associated with order.
*/
protected function set_item( $order, $item_type, $posted ) {
global $wpdb;
if ( ! empty( $posted['id'] ) ) {
$action = 'update';
} else {
$action = 'create';
}
$method = 'prepare_' . $item_type;
$item = null;
// Verify provided line item ID is associated with order.
if ( 'update' === $action ) {
$item = $order->get_item( absint( $posted['id'] ), false );
if ( ! $item ) {
throw new WC_REST_Exception( 'woocommerce_rest_invalid_item_id', __( 'Order item ID provided is not associated with order.', 'woocommerce' ), 400 );
}
}
// Prepare item data.
$item = $this->$method( $posted, $action, $item );
/**
* Allow extensions be notified before the item before is saved.
*
* @param WC_Order_Item $item The item object.
* @param array $posted The item data.
*
* @since 4.5.0.
*/
do_action( 'woocommerce_rest_set_order_item', $item, $posted );
// If creating the order, add the item to it.
if ( 'create' === $action ) {
$order->add_item( $item );
} else {
$item->save();
}
}
/**
* Helper method to check if the resource ID associated with the provided item is null.
* Items can be deleted by setting the resource ID to null.
*
* @param array $item Item provided in the request body.
* @return bool True if the item resource ID is null, false otherwise.
*/
protected function item_is_null( $item ) {
$keys = array( 'product_id', 'method_id', 'method_title', 'name', 'code' );
foreach ( $keys as $key ) {
if ( array_key_exists( $key, $item ) && is_null( $item[ $key ] ) ) {
return true;
}
}
return false;
}
/**
* Get order statuses without prefixes.
*
* @return array
*/
protected function get_order_statuses() {
$order_statuses = array( 'auto-draft' );
foreach ( array_keys( wc_get_order_statuses() ) as $status ) {
$order_statuses[] = str_replace( 'wc-', '', $status );
}
return $order_statuses;
}
/**
* 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,
),
'parent_id' => array(
'description' => __( 'Parent order ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'number' => array(
'description' => __( 'Order number.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'order_key' => array(
'description' => __( 'Order key.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'created_via' => array(
'description' => __( 'Shows where the order was created.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'version' => array(
'description' => __( 'Version of WooCommerce which last updated the order.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'status' => array(
'description' => __( 'Order status.', 'woocommerce' ),
'type' => 'string',
'default' => 'pending',
'enum' => $this->get_order_statuses(),
'context' => array( 'view', 'edit' ),
),
'currency' => array(
'description' => __( 'Currency the order was created with, in ISO format.', 'woocommerce' ),
'type' => 'string',
'default' => get_woocommerce_currency(),
'enum' => array_keys( get_woocommerce_currencies() ),
'context' => array( 'view', 'edit' ),
),
'date_created' => array(
'description' => __( "The date the order 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 was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the order 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 order was last modified, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'discount_total' => array(
'description' => __( 'Total discount amount for the order.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'discount_tax' => array(
'description' => __( 'Total discount tax amount for the order.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'shipping_total' => array(
'description' => __( 'Total shipping amount for the order.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'shipping_tax' => array(
'description' => __( 'Total shipping tax amount for the order.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'cart_tax' => array(
'description' => __( 'Sum of line item taxes only.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'total' => array(
'description' => __( 'Grand total.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'total_tax' => array(
'description' => __( 'Sum of all taxes.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'prices_include_tax' => array(
'description' => __( 'True the prices included tax during checkout.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'customer_id' => array(
'description' => __( 'User ID who owns the order. 0 for guests.', 'woocommerce' ),
'type' => 'integer',
'default' => 0,
'context' => array( 'view', 'edit' ),
),
'customer_ip_address' => array(
'description' => __( "Customer's IP address.", 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'customer_user_agent' => array(
'description' => __( 'User agent of the customer.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'customer_note' => array(
'description' => __( 'Note left by customer during checkout.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'billing' => array(
'description' => __( 'Billing address.', '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' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'email' => array(
'description' => __( 'Email address.', 'woocommerce' ),
'type' => array( 'string', 'null' ),
'format' => 'email',
'context' => array( 'view', 'edit' ),
),
'phone' => array(
'description' => __( 'Phone number.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
'shipping' => array(
'description' => __( 'Shipping address.', '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' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
'payment_method' => array(
'description' => __( 'Payment method ID.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'payment_method_title' => array(
'description' => __( 'Payment method title.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'transaction_id' => array(
'description' => __( 'Unique transaction ID.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'date_paid' => array(
'description' => __( "The date the order was paid, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_paid_gmt' => array(
'description' => __( 'The date the order was paid, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_completed' => array(
'description' => __( "The date the order was completed, in the site's timezone.", 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_completed_gmt' => array(
'description' => __( 'The date the order was completed, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'cart_hash' => array(
'description' => __( 'MD5 hash of cart items to ensure orders are not modified.', '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' => 'mixed',
'context' => array( 'view', 'edit' ),
),
),
),
),
'line_items' => array(
'description' => __( 'Line items data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'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' ),
),
'parent_name' => array(
'description' => __( 'Parent product name if the product is a variation.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'product_id' => array(
'description' => __( 'Product ID.', 'woocommerce' ),
'type' => 'mixed',
'context' => array( 'view', 'edit' ),
),
'variation_id' => array(
'description' => __( 'Variation ID, if applicable.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'quantity' => array(
'description' => __( 'Quantity ordered.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'tax_class' => array(
'description' => __( 'Tax class of product.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'subtotal' => array(
'description' => __( 'Line subtotal (before discounts).', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'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' ),
),
'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' ),
),
'total' => array(
'description' => __( 'Tax total.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'subtotal' => array(
'description' => __( 'Tax subtotal.', 'woocommerce' ),
'type' => 'string',
'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' => 'mixed',
'context' => array( 'view', 'edit' ),
),
'display_key' => array(
'description' => __( 'Meta key for UI display.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'display_value' => array(
'description' => __( 'Meta value for UI display.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
),
'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,
),
'image' => array(
'description' => __( 'Properties of the main product image.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'properties' => array(
'id' => array(
'description' => __( 'Image ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'src' => array(
'description' => __( 'Image URL.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit' ),
),
),
),
),
),
),
'tax_lines' => array(
'description' => __( 'Tax lines 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,
),
'rate_code' => array(
'description' => __( 'Tax rate code.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'rate_id' => array(
'description' => __( 'Tax rate ID.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'label' => array(
'description' => __( 'Tax rate label.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'compound' => array(
'description' => __( 'Show if is a compound tax rate.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'tax_total' => array(
'description' => __( 'Tax total (not including shipping taxes).', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'shipping_tax_total' => array(
'description' => __( 'Shipping tax total.', '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' => 'mixed',
'context' => array( 'view', 'edit' ),
),
),
),
),
),
),
),
'shipping_lines' => array(
'description' => __( 'Shipping lines data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Item ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'method_title' => array(
'description' => __( 'Shipping method name.', 'woocommerce' ),
'type' => 'mixed',
'context' => array( 'view', 'edit' ),
),
'method_id' => array(
'description' => __( 'Shipping method ID.', 'woocommerce' ),
'type' => 'mixed',
'context' => array( 'view', 'edit' ),
),
'instance_id' => array(
'description' => __( 'Shipping instance ID.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'total' => array(
'description' => __( 'Line total (after discounts).', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'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,
),
),
),
),
'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' => 'mixed',
'context' => array( 'view', 'edit' ),
),
),
),
),
),
),
),
'fee_lines' => array(
'description' => __( 'Fee lines data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Item ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Fee name.', 'woocommerce' ),
'type' => 'mixed',
'context' => array( 'view', 'edit' ),
),
'tax_class' => array(
'description' => __( 'Tax class of fee.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'tax_status' => array(
'description' => __( 'Tax status of fee.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'enum' => array( 'taxable', 'none' ),
),
'total' => array(
'description' => __( 'Line total (after discounts).', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'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' ),
'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' => 'mixed',
'context' => array( 'view', 'edit' ),
),
),
),
),
),
),
),
'coupon_lines' => array(
'description' => __( 'Coupons line data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Item ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'code' => array(
'description' => __( 'Coupon code.', 'woocommerce' ),
'type' => 'mixed',
'context' => array( 'view', 'edit' ),
),
'discount' => array(
'description' => __( 'Discount total.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'discount_tax' => array(
'description' => __( 'Discount total tax.', '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' => 'mixed',
'context' => array( 'view', 'edit' ),
),
),
),
),
),
),
),
'refunds' => array(
'description' => __( 'List of refunds.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Refund ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'reason' => array(
'description' => __( 'Refund reason.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'total' => array(
'description' => __( 'Refund total.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
),
'payment_url' => array(
'description' => __( 'Order payment URL.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'set_paid' => array(
'description' => __( 'Define if the order is paid. It will set the status to processing and reduce stock items.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'edit' ),
),
'is_editable' => array(
'description' => __( 'Whether an order can be edited.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'needs_payment' => array(
'description' => __( 'Whether an order needs payment, based on status and order total.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'needs_processing' => array(
'description' => __( 'Whether an order needs processing before it can be completed.', '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() {
$params = parent::get_collection_params();
$params['status'] = array(
'default' => 'any',
'description' => __( 'Limit result set to orders assigned a specific status.', 'woocommerce' ),
'type' => 'string',
'enum' => array_merge( array( 'any', 'trash' ), $this->get_order_statuses() ),
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
);
$params['customer'] = array(
'description' => __( 'Limit result set to orders assigned a specific customer.', 'woocommerce' ),
'type' => 'integer',
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
);
$params['product'] = array(
'description' => __( 'Limit result set to orders assigned a specific product.', 'woocommerce' ),
'type' => 'integer',
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
);
$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',
);
$params['order_item_display_meta'] = array(
'default' => false,
'description' => __( 'Only show meta which is meant to be displayed for an order.', 'woocommerce' ),
'type' => 'boolean',
'sanitize_callback' => 'rest_sanitize_boolean',
'validate_callback' => 'rest_validate_request_arg',
);
$params['include_meta'] = array(
'default' => array(),
'description' => __( 'Limit meta_data to specific keys.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'string',
),
'sanitize_callback' => 'wp_parse_list',
);
$params['exclude_meta'] = array(
'default' => array(),
'description' => __( 'Ensure meta_data excludes specific keys.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'string',
),
'sanitize_callback' => 'wp_parse_list',
);
return $params;
}
/**
* Get objects.
*
* @param array $query_args Query args.
* @return array
*/
protected function get_objects( $query_args ) {
// Do not use WC_Order_Query for the CPT datastore.
if ( ! OrderUtil::custom_orders_table_usage_is_enabled() ) {
return parent::get_objects( $query_args );
}
$query = new \WC_Order_Query(
array_merge(
$query_args,
array(
'paginate' => true,
)
)
);
$results = $query->get_orders();
return array(
'objects' => $results->orders,
'total' => $results->total,
'pages' => $results->max_num_pages,
);
}
}
class-wc-rest-payment-gateways-v2-controller.php 0000644 00000036731 15155060366 0015770 0 ustar 00 <?php
/**
* REST API WC Payment gateways controller
*
* Handles requests to the /payment_gateways endpoint.
*
* @package WooCommerce\RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* Paymenga gateways controller class.
*
* @package 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' ) ),
);
}
}
class-wc-rest-product-attribute-terms-v2-controller.php 0000644 00000001104 15155060366 0017264 0 ustar 00 <?php
/**
* REST API Product Attribute Terms controller
*
* Handles requests to the products/attributes/<attribute_id>/terms endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Attribute Terms controller class.
*
* @package 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';
}
class-wc-rest-product-attributes-v2-controller.php 0000644 00000001026 15155060366 0016322 0 ustar 00 <?php
/**
* REST API Product Attributes controller
*
* Handles requests to the products/attributes endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Attributes controller class.
*
* @package 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';
}
class-wc-rest-product-categories-v2-controller.php 0000644 00000016347 15155060366 0016275 0 ustar 00 <?php
/**
* REST API Product Categories controller
*
* Handles requests to the products/categories endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Categories controller class.
*
* @package 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 );
}
}
class-wc-rest-product-reviews-v2-controller.php 0000644 00000014614 15155060366 0015627 0 ustar 00 <?php
/**
* REST API Product Reviews Controller
*
* Handles requests to /products/<product_id>/reviews.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Reviews Controller Class.
*
* @package 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_product_reviews_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;
}
/**
* 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 );
}
}
class-wc-rest-product-shipping-classes-v2-controller.php 0000644 00000001072 15155060366 0017411 0 ustar 00 <?php
/**
* REST API Product Shipping Classes controller
*
* Handles requests to the products/shipping_classes endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Shipping Classes controller class.
*
* @package 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';
}
class-wc-rest-product-tags-v2-controller.php 0000644 00000000762 15155060366 0015100 0 ustar 00 <?php
/**
* REST API Product Tags controller
*
* Handles requests to the products/tags endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Tags controller class.
*
* @package 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';
}
class-wc-rest-product-variations-v2-controller.php 0000644 00000105557 15155060366 0016331 0 ustar 00 <?php
/**
* REST API variations controller
*
* Handles requests to the /products/<product_id>/variations endpoints.
*
* @package WooCommerce\RestApi
* @since 3.0.0
*/
use Automattic\WooCommerce\Utilities\I18nUtil;
defined( 'ABSPATH' ) || exit;
/**
* REST API variations controller class.
*
* @package 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_label = I18nUtil::get_weight_unit_label( get_option( 'woocommerce_weight_unit', 'kg' ) );
$dimension_unit_label = I18nUtil::get_dimensions_unit_label( get_option( 'woocommerce_dimension_unit', 'cm' ) );
$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' => 'mixed',
'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_label ),
'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_label ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'width' => array(
/* translators: %s: dimension unit */
'description' => sprintf( __( 'Variation width (%s).', 'woocommerce' ), $dimension_unit_label ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'height' => array(
/* translators: %s: dimension unit */
'description' => sprintf( __( 'Variation height (%s).', 'woocommerce' ), $dimension_unit_label ),
'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' => 'mixed',
'context' => array( 'view', 'edit' ),
),
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}
class-wc-rest-products-v2-controller.php 0000644 00000234654 15155060366 0014340 0 ustar 00 <?php
/**
* REST API Products controller
*
* Handles requests to the /products endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
use Automattic\WooCommerce\Utilities\I18nUtil;
defined( 'ABSPATH' ) || exit;
/**
* REST API Products controller class.
*
* @package WooCommerce\RestApi
* @extends WC_REST_CRUD_Controller
*/
class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'products';
/**
* Post type.
*
* @var string
*/
protected $post_type = 'product';
/**
* If object is hierarchical.
*
* @var bool
*/
protected $hierarchical = true;
/**
* Initialize product actions.
*/
public function __construct() {
add_action( "woocommerce_rest_insert_{$this->post_type}_object", array( $this, 'clear_transients' ) );
}
/**
* Register the routes for products.
*/
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,
'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),
'type' => 'boolean',
),
),
),
'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.
*
* @param int $id Object ID.
*
* @since 3.0.0
* @return WC_Data
*/
protected function get_object( $id ) {
return wc_get_product( $id );
}
/**
* Prepare a single product output for response.
*
* @param WC_Data $object Object data.
* @param WP_REST_Request $request Request object.
*
* @since 3.0.0
* @return WP_REST_Response
*/
public function prepare_object_for_response( $object, $request ) {
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$this->request = $request;
$data = $this->get_product_data( $object, $context, $request );
// Add variations to variable products.
if ( $object->is_type( 'variable' ) && $object->has_child() ) {
$data['variations'] = $object->get_children();
}
// Add grouped products data.
if ( $object->is_type( 'grouped' ) && $object->has_child() ) {
$data['grouped_products'] = $object->get_children();
}
$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.
*
* @param WP_REST_Request $request Full details about the request.
*
* @since 3.0.0
* @return array
*/
protected function prepare_objects_query( $request ) {
$args = parent::prepare_objects_query( $request );
// Set post_status.
$args['post_status'] = $request['status'];
// Taxonomy query to filter products by type, category,
// tag, shipping class, and attribute.
$tax_query = array();
// Map between taxonomy name and arg's key.
$taxonomies = array(
'product_cat' => 'category',
'product_tag' => 'tag',
'product_shipping_class' => 'shipping_class',
);
// Set tax_query for each passed arg.
foreach ( $taxonomies as $taxonomy => $key ) {
if ( ! empty( $request[ $key ] ) ) {
$tax_query[] = array(
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => $request[ $key ],
);
}
}
// Filter product type by slug.
if ( ! empty( $request['type'] ) ) {
$tax_query[] = array(
'taxonomy' => 'product_type',
'field' => 'slug',
'terms' => $request['type'],
);
}
// Filter by attribute and term.
if ( ! empty( $request['attribute'] ) && ! empty( $request['attribute_term'] ) ) {
if ( in_array( $request['attribute'], wc_get_attribute_taxonomy_names(), true ) ) {
$tax_query[] = array(
'taxonomy' => $request['attribute'],
'field' => 'term_id',
'terms' => $request['attribute_term'],
);
}
}
if ( ! empty( $tax_query ) ) {
$args['tax_query'] = $tax_query; // WPCS: slow query ok.
}
// Filter featured.
if ( is_bool( $request['featured'] ) ) {
$args['tax_query'][] = array(
'taxonomy' => 'product_visibility',
'field' => 'name',
'terms' => 'featured',
'operator' => true === $request['featured'] ? 'IN' : 'NOT IN',
);
}
// 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 in stock or out of stock.
if ( is_bool( $request['in_stock'] ) ) {
$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
$args,
array(
'key' => '_stock_status',
'value' => true === $request['in_stock'] ? 'instock' : 'outofstock',
)
);
}
// 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;
}
return $args;
}
/**
* Get the downloads for a product or product variation.
*
* @param WC_Product|WC_Product_Variation $product Product instance.
*
* @return array
*/
protected function get_downloads( $product ) {
$downloads = array();
if ( $product->is_downloadable() ) {
foreach ( $product->get_downloads() as $file_id => $file ) {
$downloads[] = array(
'id' => $file_id, // MD5 hash.
'name' => $file['name'],
'file' => $file['file'],
);
}
}
return $downloads;
}
/**
* Get taxonomy terms.
*
* @param WC_Product $product Product instance.
* @param string $taxonomy Taxonomy slug.
*
* @return array
*/
protected function get_taxonomy_terms( $product, $taxonomy = 'cat' ) {
$terms = array();
foreach ( wc_get_object_terms( $product->get_id(), 'product_' . $taxonomy ) as $term ) {
$terms[] = array(
'id' => $term->term_id,
'name' => $term->name,
'slug' => $term->slug,
);
}
return $terms;
}
/**
* Get the images for a product or product variation.
*
* @param WC_Product|WC_Product_Variation $product Product instance.
*
* @return array
*/
protected function get_images( $product ) {
$images = array();
$attachment_ids = array();
// Add featured image.
if ( $product->get_image_id() ) {
$attachment_ids[] = $product->get_image_id();
}
// Add gallery images.
$attachment_ids = array_merge( $attachment_ids, $product->get_gallery_image_ids() );
// Build image data.
foreach ( $attachment_ids as $position => $attachment_id ) {
$attachment_post = get_post( $attachment_id );
if ( is_null( $attachment_post ) ) {
continue;
}
$attachment = wp_get_attachment_image_src( $attachment_id, 'full' );
if ( ! is_array( $attachment ) ) {
continue;
}
$images[] = 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 ),
'position' => (int) $position,
);
}
// Set a placeholder image if the product has no images set.
if ( empty( $images ) ) {
$images[] = array(
'id' => 0,
'date_created' => wc_rest_prepare_date_response( current_time( 'mysql' ), false ), // Default to now.
'date_created_gmt' => wc_rest_prepare_date_response( time() ), // Default to now.
'date_modified' => wc_rest_prepare_date_response( current_time( 'mysql' ), false ),
'date_modified_gmt' => wc_rest_prepare_date_response( time() ),
'src' => wc_placeholder_img_src(),
'name' => __( 'Placeholder', 'woocommerce' ),
'alt' => __( 'Placeholder', 'woocommerce' ),
'position' => 0,
);
}
return $images;
}
/**
* Get attribute taxonomy label.
*
* @param string $name Taxonomy name.
*
* @deprecated 3.0.0
* @return string
*/
protected function get_attribute_taxonomy_label( $name ) {
$tax = get_taxonomy( $name );
$labels = get_taxonomy_labels( $tax );
return $labels->singular_name;
}
/**
* Get product attribute taxonomy name.
*
* @param string $slug Taxonomy name.
* @param WC_Product $product Product data.
*
* @since 3.0.0
* @return string
*/
protected function get_attribute_taxonomy_name( $slug, $product ) {
// Format slug so it matches attributes of the product.
$slug = wc_attribute_taxonomy_slug( $slug );
$attributes = $product->get_attributes();
$attribute = false;
// pa_ attributes.
if ( isset( $attributes[ wc_attribute_taxonomy_name( $slug ) ] ) ) {
$attribute = $attributes[ wc_attribute_taxonomy_name( $slug ) ];
} elseif ( isset( $attributes[ $slug ] ) ) {
$attribute = $attributes[ $slug ];
}
if ( ! $attribute ) {
return $slug;
}
// Taxonomy attribute name.
if ( $attribute->is_taxonomy() ) {
$taxonomy = $attribute->get_taxonomy_object();
return $taxonomy->attribute_label;
}
// Custom product attribute name.
return $attribute->get_name();
}
/**
* Get default attributes.
*
* @param WC_Product $product Product instance.
*
* @return array
*/
protected function get_default_attributes( $product ) {
$default = array();
if ( $product->is_type( 'variable' ) ) {
foreach ( array_filter( (array) $product->get_default_attributes(), 'strlen' ) as $key => $value ) {
if ( 0 === strpos( $key, 'pa_' ) ) {
$default[] = array(
'id' => wc_attribute_taxonomy_id_by_name( $key ),
'name' => $this->get_attribute_taxonomy_name( $key, $product ),
'option' => $value,
);
} else {
$default[] = array(
'id' => 0,
'name' => $this->get_attribute_taxonomy_name( $key, $product ),
'option' => $value,
);
}
}
}
return $default;
}
/**
* Get attribute options.
*
* @param int $product_id Product ID.
* @param array $attribute Attribute data.
*
* @return array
*/
protected function get_attribute_options( $product_id, $attribute ) {
if ( isset( $attribute['is_taxonomy'] ) && $attribute['is_taxonomy'] ) {
return wc_get_product_terms(
$product_id,
$attribute['name'],
array(
'fields' => 'names',
)
);
} elseif ( isset( $attribute['value'] ) ) {
return array_map( 'trim', explode( '|', $attribute['value'] ) );
}
return array();
}
/**
* Get the attributes for a product or product variation.
*
* @param WC_Product|WC_Product_Variation $product Product instance.
*
* @return array
*/
protected function get_attributes( $product ) {
$attributes = array();
if ( $product->is_type( 'variation' ) ) {
$_product = wc_get_product( $product->get_parent_id() );
foreach ( $product->get_variation_attributes() as $attribute_name => $attribute ) {
$name = str_replace( 'attribute_', '', $attribute_name );
if ( empty( $attribute ) && '0' !== $attribute ) {
continue;
}
// Taxonomy-based attributes are prefixed with `pa_`, otherwise simply `attribute_`.
if ( 0 === strpos( $attribute_name, 'attribute_pa_' ) ) {
$option_term = get_term_by( 'slug', $attribute, $name );
$attributes[] = array(
'id' => wc_attribute_taxonomy_id_by_name( $name ),
'name' => $this->get_attribute_taxonomy_name( $name, $_product ),
'option' => $option_term && ! is_wp_error( $option_term ) ? $option_term->name : $attribute,
);
} else {
$attributes[] = array(
'id' => 0,
'name' => $this->get_attribute_taxonomy_name( $name, $_product ),
'option' => $attribute,
);
}
}
} else {
foreach ( $product->get_attributes() as $attribute ) {
$attributes[] = array(
'id' => $attribute['is_taxonomy'] ? wc_attribute_taxonomy_id_by_name( $attribute['name'] ) : 0,
'name' => $this->get_attribute_taxonomy_name( $attribute['name'], $product ),
'position' => (int) $attribute['position'],
'visible' => (bool) $attribute['is_visible'],
'variation' => (bool) $attribute['is_variation'],
'options' => $this->get_attribute_options( $product->get_id(), $attribute ),
);
}
}
return $attributes;
}
/**
* Fetch price HTML.
*
* @param WC_Product $product Product object.
* @param string $context Context of request, can be `view` or `edit`.
*
* @return string
*/
protected function api_get_price_html( $product, $context ) {
return $product->get_price_html();
}
/**
* Fetch related IDs.
*
* @param WC_Product $product Product object.
* @param string $context Context of request, can be `view` or `edit`.
*
* @return array
*/
protected function api_get_related_ids( $product, $context ) {
return array_map( 'absint', array_values( wc_get_related_products( $product->get_id() ) ) );
}
/**
* Fetch meta data.
*
* @param WC_Product $product Product object.
* @param string $context Context of request, can be `view` or `edit`.
*
* @return array
*/
protected function api_get_meta_data( $product, $context ) {
$meta_data = $product->get_meta_data();
if ( ! isset( $this->request ) || ! $this->request instanceof WP_REST_Request ) {
return $meta_data;
}
return $this->get_meta_data_for_response( $this->request, $meta_data );
}
/**
* Get product data.
*
* @param WC_Product $product Product instance.
* @param string $context Request context. Options: 'view' and 'edit'.
*
* @return array
*/
protected function get_product_data( $product, $context = 'view' ) {
/*
* @param WP_REST_Request $request Current request object. For backward compatibility, we pass this argument silently.
*
* TODO: Refactor to fix this behavior when DI gets included to make it obvious and clean.
*/
$request = func_num_args() >= 3 ? func_get_arg( 2 ) : new WP_REST_Request( '', '', array( 'context' => $context ) );
$fields = $this->get_fields_for_response( $request );
$base_data = array();
foreach ( $fields as $field ) {
switch ( $field ) {
case 'id':
$base_data['id'] = $product->get_id();
break;
case 'name':
$base_data['name'] = $product->get_name( $context );
break;
case 'slug':
$base_data['slug'] = $product->get_slug( $context );
break;
case 'permalink':
$base_data['permalink'] = $product->get_permalink();
break;
case 'date_created':
$base_data['date_created'] = wc_rest_prepare_date_response( $product->get_date_created( $context ), false );
break;
case 'date_created_gmt':
$base_data['date_created_gmt'] = wc_rest_prepare_date_response( $product->get_date_created( $context ) );
break;
case 'date_modified':
$base_data['date_modified'] = wc_rest_prepare_date_response( $product->get_date_modified( $context ), false );
break;
case 'date_modified_gmt':
$base_data['date_modified_gmt'] = wc_rest_prepare_date_response( $product->get_date_modified( $context ) );
break;
case 'type':
$base_data['type'] = $product->get_type();
break;
case 'status':
$base_data['status'] = $product->get_status( $context );
break;
case 'featured':
$base_data['featured'] = $product->is_featured();
break;
case 'catalog_visibility':
$base_data['catalog_visibility'] = $product->get_catalog_visibility( $context );
break;
case 'description':
$base_data['description'] = 'view' === $context ? wpautop( do_shortcode( $product->get_description() ) ) : $product->get_description( $context );
break;
case 'short_description':
$base_data['short_description'] = 'view' === $context ? apply_filters( 'woocommerce_short_description', $product->get_short_description() ) : $product->get_short_description( $context );
break;
case 'sku':
$base_data['sku'] = $product->get_sku( $context );
break;
case 'price':
$base_data['price'] = $product->get_price( $context );
break;
case 'regular_price':
$base_data['regular_price'] = $product->get_regular_price( $context );
break;
case 'sale_price':
$base_data['sale_price'] = $product->get_sale_price( $context ) ? $product->get_sale_price( $context ) : '';
break;
case 'date_on_sale_from':
$base_data['date_on_sale_from'] = wc_rest_prepare_date_response( $product->get_date_on_sale_from( $context ), false );
break;
case 'date_on_sale_from_gmt':
$base_data['date_on_sale_from_gmt'] = wc_rest_prepare_date_response( $product->get_date_on_sale_from( $context ) );
break;
case 'date_on_sale_to':
$base_data['date_on_sale_to'] = wc_rest_prepare_date_response( $product->get_date_on_sale_to( $context ), false );
break;
case 'date_on_sale_to_gmt':
$base_data['date_on_sale_to_gmt'] = wc_rest_prepare_date_response( $product->get_date_on_sale_to( $context ) );
break;
case 'on_sale':
$base_data['on_sale'] = $product->is_on_sale( $context );
break;
case 'purchasable':
$base_data['purchasable'] = $product->is_purchasable();
break;
case 'total_sales':
$base_data['total_sales'] = $product->get_total_sales( $context );
break;
case 'virtual':
$base_data['virtual'] = $product->is_virtual();
break;
case 'downloadable':
$base_data['downloadable'] = $product->is_downloadable();
break;
case 'downloads':
$base_data['downloads'] = $this->get_downloads( $product );
break;
case 'download_limit':
$base_data['download_limit'] = $product->get_download_limit( $context );
break;
case 'download_expiry':
$base_data['download_expiry'] = $product->get_download_expiry( $context );
break;
case 'external_url':
$base_data['external_url'] = $product->is_type( 'external' ) ? $product->get_product_url( $context ) : '';
break;
case 'button_text':
$base_data['button_text'] = $product->is_type( 'external' ) ? $product->get_button_text( $context ) : '';
break;
case 'tax_status':
$base_data['tax_status'] = $product->get_tax_status( $context );
break;
case 'tax_class':
$base_data['tax_class'] = $product->get_tax_class( $context );
break;
case 'manage_stock':
$base_data['manage_stock'] = $product->managing_stock();
break;
case 'stock_quantity':
$base_data['stock_quantity'] = $product->get_stock_quantity( $context );
break;
case 'in_stock':
$base_data['in_stock'] = $product->is_in_stock();
break;
case 'backorders':
$base_data['backorders'] = $product->get_backorders( $context );
break;
case 'backorders_allowed':
$base_data['backorders_allowed'] = $product->backorders_allowed();
break;
case 'backordered':
$base_data['backordered'] = $product->is_on_backorder();
break;
case 'low_stock_amount':
$base_data['low_stock_amount'] = '' === $product->get_low_stock_amount() ? null : $product->get_low_stock_amount();
break;
case 'sold_individually':
$base_data['sold_individually'] = $product->is_sold_individually();
break;
case 'weight':
$base_data['weight'] = $product->get_weight( $context );
break;
case 'dimensions':
$base_data['dimensions'] = array(
'length' => $product->get_length( $context ),
'width' => $product->get_width( $context ),
'height' => $product->get_height( $context ),
);
break;
case 'shipping_required':
$base_data['shipping_required'] = $product->needs_shipping();
break;
case 'shipping_taxable':
$base_data['shipping_taxable'] = $product->is_shipping_taxable();
break;
case 'shipping_class':
$base_data['shipping_class'] = $product->get_shipping_class();
break;
case 'shipping_class_id':
$base_data['shipping_class_id'] = $product->get_shipping_class_id( $context );
break;
case 'reviews_allowed':
$base_data['reviews_allowed'] = $product->get_reviews_allowed( $context );
break;
case 'average_rating':
$base_data['average_rating'] = 'view' === $context ? wc_format_decimal( $product->get_average_rating(), 2 ) : $product->get_average_rating( $context );
break;
case 'rating_count':
$base_data['rating_count'] = $product->get_rating_count();
break;
case 'upsell_ids':
$base_data['upsell_ids'] = array_map( 'absint', $product->get_upsell_ids( $context ) );
break;
case 'cross_sell_ids':
$base_data['cross_sell_ids'] = array_map( 'absint', $product->get_cross_sell_ids( $context ) );
break;
case 'parent_id':
$base_data['parent_id'] = $product->get_parent_id( $context );
break;
case 'purchase_note':
$base_data['purchase_note'] = 'view' === $context ? wpautop( do_shortcode( wp_kses_post( $product->get_purchase_note() ) ) ) : $product->get_purchase_note( $context );
break;
case 'categories':
$base_data['categories'] = $this->get_taxonomy_terms( $product );
break;
case 'tags':
$base_data['tags'] = $this->get_taxonomy_terms( $product, 'tag' );
break;
case 'images':
$base_data['images'] = $this->get_images( $product );
break;
case 'attributes':
$base_data['attributes'] = $this->get_attributes( $product );
break;
case 'default_attributes':
$base_data['default_attributes'] = $this->get_default_attributes( $product );
break;
case 'variations':
$base_data['variations'] = array();
break;
case 'grouped_products':
$base_data['grouped_products'] = array();
break;
case 'menu_order':
$base_data['menu_order'] = $product->get_menu_order( $context );
break;
}
}
$data = array_merge(
$base_data,
$this->fetch_fields_using_getters( $product, $context, $fields )
);
return $data;
}
/**
* 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() ) ), // @codingStandardsIgnoreLine.
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ), // @codingStandardsIgnoreLine.
),
);
if ( $object->get_parent_id() ) {
$links['up'] = array(
'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $object->get_parent_id() ) ), // @codingStandardsIgnoreLine.
);
}
return $links;
}
/**
* Prepare a single product 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;
// Type is the most important part here because we need to be using the correct class and methods.
if ( isset( $request['type'] ) ) {
$classname = WC_Product_Factory::get_classname_from_product_type( $request['type'] );
if ( ! class_exists( $classname ) ) {
$classname = 'WC_Product_Simple';
}
$product = new $classname( $id );
} elseif ( isset( $request['id'] ) ) {
$product = wc_get_product( $id );
} else {
$product = new WC_Product_Simple();
}
if ( 'variation' === $product->get_type() ) {
return new WP_Error(
"woocommerce_rest_invalid_{$this->post_type}_id",
__( 'To manipulate product variations you should use the /products/<product_id>/variations/<id> endpoint.', 'woocommerce' ),
array(
'status' => 404,
)
);
}
// Post title.
if ( isset( $request['name'] ) ) {
$product->set_name( wp_filter_post_kses( $request['name'] ) );
}
// Post content.
if ( isset( $request['description'] ) ) {
$product->set_description( wp_filter_post_kses( $request['description'] ) );
}
// Post excerpt.
if ( isset( $request['short_description'] ) ) {
$product->set_short_description( wp_filter_post_kses( $request['short_description'] ) );
}
// Post status.
if ( isset( $request['status'] ) ) {
$product->set_status( get_post_status_object( $request['status'] ) ? $request['status'] : 'draft' );
}
// Post slug.
if ( isset( $request['slug'] ) ) {
$product->set_slug( $request['slug'] );
}
// Menu order.
if ( isset( $request['menu_order'] ) ) {
$product->set_menu_order( $request['menu_order'] );
}
// Comment status.
if ( isset( $request['reviews_allowed'] ) ) {
$product->set_reviews_allowed( $request['reviews_allowed'] );
}
// Virtual.
if ( isset( $request['virtual'] ) ) {
$product->set_virtual( $request['virtual'] );
}
// Tax status.
if ( isset( $request['tax_status'] ) ) {
$product->set_tax_status( $request['tax_status'] );
}
// Tax Class.
if ( isset( $request['tax_class'] ) ) {
$product->set_tax_class( $request['tax_class'] );
}
// Catalog Visibility.
if ( isset( $request['catalog_visibility'] ) ) {
$product->set_catalog_visibility( $request['catalog_visibility'] );
}
// Purchase Note.
if ( isset( $request['purchase_note'] ) ) {
$product->set_purchase_note( wp_kses_post( wp_unslash( $request['purchase_note'] ) ) );
}
// Featured Product.
if ( isset( $request['featured'] ) ) {
$product->set_featured( $request['featured'] );
}
// Shipping data.
$product = $this->save_product_shipping_data( $product, $request );
// SKU.
if ( isset( $request['sku'] ) ) {
$product->set_sku( wc_clean( $request['sku'] ) );
}
// Attributes.
if ( isset( $request['attributes'] ) ) {
$attributes = array();
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 = wc_clean( $attribute['name'] );
}
if ( ! $attribute_id && ! $attribute_name ) {
continue;
}
if ( $attribute_id ) {
if ( isset( $attribute['options'] ) ) {
$options = $attribute['options'];
if ( ! is_array( $attribute['options'] ) ) {
// Text based attributes - Posted values are term names.
$options = explode( WC_DELIMITER, $options );
}
$values = array_map( 'wc_sanitize_term_text_based', $options );
$values = array_filter( $values, 'strlen' );
} else {
$values = array();
}
if ( ! empty( $values ) ) {
// Add attribute to array, but don't set values.
$attribute_object = new WC_Product_Attribute();
$attribute_object->set_id( $attribute_id );
$attribute_object->set_name( $attribute_name );
$attribute_object->set_options( $values );
$attribute_object->set_position( isset( $attribute['position'] ) ? (string) absint( $attribute['position'] ) : '0' );
$attribute_object->set_visible( ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0 );
$attribute_object->set_variation( ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0 );
$attributes[] = $attribute_object;
}
} elseif ( isset( $attribute['options'] ) ) {
// Custom attribute - Add attribute to array and set the values.
if ( is_array( $attribute['options'] ) ) {
$values = $attribute['options'];
} else {
$values = explode( WC_DELIMITER, $attribute['options'] );
}
$attribute_object = new WC_Product_Attribute();
$attribute_object->set_name( $attribute_name );
$attribute_object->set_options( $values );
$attribute_object->set_position( isset( $attribute['position'] ) ? (string) absint( $attribute['position'] ) : '0' );
$attribute_object->set_visible( ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0 );
$attribute_object->set_variation( ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0 );
$attributes[] = $attribute_object;
}
}
$product->set_attributes( $attributes );
}
// Sales and prices.
if ( in_array( $product->get_type(), array( 'variable', 'grouped' ), true ) ) {
$product->set_regular_price( '' );
$product->set_sale_price( '' );
$product->set_date_on_sale_to( '' );
$product->set_date_on_sale_from( '' );
$product->set_price( '' );
} else {
// Regular Price.
if ( isset( $request['regular_price'] ) ) {
$product->set_regular_price( $request['regular_price'] );
}
// Sale Price.
if ( isset( $request['sale_price'] ) ) {
$product->set_sale_price( $request['sale_price'] );
}
if ( isset( $request['date_on_sale_from'] ) ) {
$product->set_date_on_sale_from( $request['date_on_sale_from'] );
}
if ( isset( $request['date_on_sale_from_gmt'] ) ) {
$product->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'] ) ) {
$product->set_date_on_sale_to( $request['date_on_sale_to'] );
}
if ( isset( $request['date_on_sale_to_gmt'] ) ) {
$product->set_date_on_sale_to( $request['date_on_sale_to_gmt'] ? strtotime( $request['date_on_sale_to_gmt'] ) : null );
}
}
// Product parent ID.
if ( isset( $request['parent_id'] ) ) {
$product->set_parent_id( $request['parent_id'] );
}
// Sold individually.
if ( isset( $request['sold_individually'] ) ) {
$product->set_sold_individually( $request['sold_individually'] );
}
// Stock status.
if ( isset( $request['in_stock'] ) ) {
$stock_status = true === $request['in_stock'] ? 'instock' : 'outofstock';
} else {
$stock_status = $product->get_stock_status();
}
// Stock data.
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
// Manage stock.
if ( isset( $request['manage_stock'] ) ) {
$product->set_manage_stock( $request['manage_stock'] );
}
// Backorders.
if ( isset( $request['backorders'] ) ) {
$product->set_backorders( $request['backorders'] );
}
if ( $product->is_type( 'grouped' ) ) {
$product->set_manage_stock( 'no' );
$product->set_backorders( 'no' );
$product->set_stock_quantity( '' );
$product->set_stock_status( $stock_status );
} elseif ( $product->is_type( 'external' ) ) {
$product->set_manage_stock( 'no' );
$product->set_backorders( 'no' );
$product->set_stock_quantity( '' );
$product->set_stock_status( 'instock' );
} elseif ( $product->get_manage_stock() ) {
// Stock status is always determined by children so sync later.
if ( ! $product->is_type( 'variable' ) ) {
$product->set_stock_status( $stock_status );
}
// Stock quantity.
if ( isset( $request['stock_quantity'] ) ) {
$product->set_stock_quantity( wc_stock_amount( $request['stock_quantity'] ) );
} elseif ( isset( $request['inventory_delta'] ) ) {
$stock_quantity = wc_stock_amount( $product->get_stock_quantity() );
$stock_quantity += wc_stock_amount( $request['inventory_delta'] );
$product->set_stock_quantity( wc_stock_amount( $stock_quantity ) );
}
} else {
// Don't manage stock.
$product->set_manage_stock( 'no' );
$product->set_stock_quantity( '' );
$product->set_stock_status( $stock_status );
}
} elseif ( ! $product->is_type( 'variable' ) ) {
$product->set_stock_status( $stock_status );
}
// Upsells.
if ( isset( $request['upsell_ids'] ) ) {
$upsells = array();
$ids = $request['upsell_ids'];
if ( ! empty( $ids ) ) {
foreach ( $ids as $id ) {
if ( $id && $id > 0 ) {
$upsells[] = $id;
}
}
}
$product->set_upsell_ids( $upsells );
}
// Cross sells.
if ( isset( $request['cross_sell_ids'] ) ) {
$crosssells = array();
$ids = $request['cross_sell_ids'];
if ( ! empty( $ids ) ) {
foreach ( $ids as $id ) {
if ( $id && $id > 0 ) {
$crosssells[] = $id;
}
}
}
$product->set_cross_sell_ids( $crosssells );
}
// Product categories.
if ( isset( $request['categories'] ) && is_array( $request['categories'] ) ) {
$product = $this->save_taxonomy_terms( $product, $request['categories'] );
}
// Product tags.
if ( isset( $request['tags'] ) && is_array( $request['tags'] ) ) {
$product = $this->save_taxonomy_terms( $product, $request['tags'], 'tag' );
}
// Downloadable.
if ( isset( $request['downloadable'] ) ) {
$product->set_downloadable( $request['downloadable'] );
}
// Downloadable options.
if ( $product->get_downloadable() ) {
// Downloadable files.
if ( isset( $request['downloads'] ) && is_array( $request['downloads'] ) ) {
$product = $this->save_downloadable_files( $product, $request['downloads'] );
}
// Download limit.
if ( isset( $request['download_limit'] ) ) {
$product->set_download_limit( $request['download_limit'] );
}
// Download expiry.
if ( isset( $request['download_expiry'] ) ) {
$product->set_download_expiry( $request['download_expiry'] );
}
}
// Product url and button text for external products.
if ( $product->is_type( 'external' ) ) {
if ( isset( $request['external_url'] ) ) {
$product->set_product_url( $request['external_url'] );
}
if ( isset( $request['button_text'] ) ) {
$product->set_button_text( $request['button_text'] );
}
}
// Save default attributes for variable products.
if ( $product->is_type( 'variable' ) ) {
$product = $this->save_default_attributes( $product, $request );
}
// Set children for a grouped product.
if ( $product->is_type( 'grouped' ) && isset( $request['grouped_products'] ) ) {
$product->set_children( $request['grouped_products'] );
}
// Check for featured/gallery images, upload it and set it.
if ( isset( $request['images'] ) ) {
$product = $this->set_product_images( $product, $request['images'] );
}
// Allow set meta_data.
if ( is_array( $request['meta_data'] ) ) {
foreach ( $request['meta_data'] as $meta ) {
$product->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 $product 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", $product, $request, $creating );
}
/**
* Set product images.
*
* @param WC_Product $product Product instance.
* @param array $images Images data.
*
* @throws WC_REST_Exception REST API exceptions.
* @return WC_Product
*/
protected function set_product_images( $product, $images ) {
$images = is_array( $images ) ? array_filter( $images ) : array();
if ( ! empty( $images ) ) {
$gallery_positions = array();
foreach ( $images as $index => $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, $product->get_id(), $images ) ) {
throw new WC_REST_Exception( 'woocommerce_product_image_upload_error', $upload->get_error_message(), 400 );
} else {
continue;
}
}
$attachment_id = wc_rest_set_uploaded_image_as_attachment( $upload, $product->get_id() );
}
if ( ! wp_attachment_is_image( $attachment_id ) ) {
/* translators: %s: attachment id */
throw new WC_REST_Exception( 'woocommerce_product_invalid_image_id', sprintf( __( '#%s is an invalid image ID.', 'woocommerce' ), $attachment_id ), 400 );
}
$gallery_positions[ $attachment_id ] = absint( isset( $image['position'] ) ? $image['position'] : $index );
// 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'],
)
);
}
// Set the image source if present, for future reference.
if ( ! empty( $image['src'] ) ) {
update_post_meta( $attachment_id, '_wc_attachment_source', esc_url_raw( $image['src'] ) );
}
}
// Sort images and get IDs in correct order.
asort( $gallery_positions );
// Get gallery in correct order.
$gallery = array_keys( $gallery_positions );
// Featured image is in position 0.
$image_id = array_shift( $gallery );
// Set images.
$product->set_image_id( $image_id );
$product->set_gallery_image_ids( $gallery );
} else {
$product->set_image_id( '' );
$product->set_gallery_image_ids( array() );
}
return $product;
}
/**
* Save product shipping data.
*
* @param WC_Product $product Product instance.
* @param array $data Shipping data.
*
* @return WC_Product
*/
protected function save_product_shipping_data( $product, $data ) {
// Virtual.
if ( isset( $data['virtual'] ) && true === $data['virtual'] ) {
$product->set_weight( '' );
$product->set_height( '' );
$product->set_length( '' );
$product->set_width( '' );
} else {
if ( isset( $data['weight'] ) ) {
$product->set_weight( $data['weight'] );
}
// Height.
if ( isset( $data['dimensions']['height'] ) ) {
$product->set_height( $data['dimensions']['height'] );
}
// Width.
if ( isset( $data['dimensions']['width'] ) ) {
$product->set_width( $data['dimensions']['width'] );
}
// Length.
if ( isset( $data['dimensions']['length'] ) ) {
$product->set_length( $data['dimensions']['length'] );
}
}
// Shipping class.
if ( isset( $data['shipping_class'] ) ) {
$data_store = $product->get_data_store();
$shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $data['shipping_class'] ) );
$product->set_shipping_class_id( $shipping_class_id );
}
return $product;
}
/**
* Save downloadable files.
*
* @param WC_Product $product Product instance.
* @param array $downloads Downloads data.
* @param int $deprecated Deprecated since 3.0.
*
* @return WC_Product
*/
protected function save_downloadable_files( $product, $downloads, $deprecated = 0 ) {
if ( $deprecated ) {
wc_deprecated_argument( 'variation_id', '3.0', 'save_downloadable_files() not requires a variation_id anymore.' );
}
$files = array();
foreach ( $downloads as $key => $file ) {
if ( empty( $file['file'] ) ) {
continue;
}
$download = new WC_Product_Download();
$download->set_id( ! empty( $file['id'] ) ? $file['id'] : wp_generate_uuid4() );
$download->set_name( $file['name'] ? $file['name'] : wc_get_filename_from_url( $file['file'] ) );
$download->set_file( apply_filters( 'woocommerce_file_download_path', $file['file'], $product, $key ) );
$files[] = $download;
}
$product->set_downloads( $files );
return $product;
}
/**
* Save taxonomy terms.
*
* @param WC_Product $product Product instance.
* @param array $terms Terms data.
* @param string $taxonomy Taxonomy name.
*
* @return WC_Product
*/
protected function save_taxonomy_terms( $product, $terms, $taxonomy = 'cat' ) {
$term_ids = wp_list_pluck( $terms, 'id' );
if ( 'cat' === $taxonomy ) {
$product->set_category_ids( $term_ids );
} elseif ( 'tag' === $taxonomy ) {
$product->set_tag_ids( $term_ids );
}
return $product;
}
/**
* Save default attributes.
*
* @param WC_Product $product Product instance.
* @param WP_REST_Request $request Request data.
*
* @since 3.0.0
* @return WC_Product
*/
protected function save_default_attributes( $product, $request ) {
if ( isset( $request['default_attributes'] ) && is_array( $request['default_attributes'] ) ) {
$attributes = $product->get_attributes();
$default_attributes = array();
foreach ( $request['default_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( $attributes[ $attribute_name ] ) ) {
$_attribute = $attributes[ $attribute_name ];
if ( $_attribute['is_variation'] ) {
$value = isset( $attribute['option'] ) ? wc_clean( stripslashes( $attribute['option'] ) ) : '';
if ( ! empty( $_attribute['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', $value, $attribute_name );
if ( $term && ! is_wp_error( $term ) ) {
$value = $term->slug;
} else {
$value = sanitize_title( $value );
}
}
if ( $value ) {
$default_attributes[ $attribute_name ] = $value;
}
}
}
}
$product->set_default_attributes( $default_attributes );
}
return $product;
}
/**
* Clear caches here so in sync with any new variations/children.
*
* @param WC_Data $object Object data.
*/
public function clear_transients( $object ) {
wc_delete_product_transients( $object->get_id() );
wp_cache_delete( 'product-' . $object->get_id(), 'products' );
}
/**
* 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'];
$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,
)
);
}
if ( 'variation' === $object->get_type() ) {
return new WP_Error(
"woocommerce_rest_invalid_{$this->post_type}_id",
__( 'To manipulate product variations you should use the /products/<product_id>/variations/<id> endpoint.', '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(
"woocommerce_rest_user_cannot_delete_{$this->post_type}",
/* translators: %s: 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 ) {
if ( $object->is_type( 'variable' ) ) {
foreach ( $object->get_children() as $child_id ) {
$child = wc_get_product( $child_id );
if ( ! empty( $child ) ) {
$child->delete( true );
}
}
} else {
// For other product types, if the product has children, remove the relationship.
foreach ( $object->get_children() as $child_id ) {
$child = wc_get_product( $child_id );
if ( ! empty( $child ) ) {
$child->set_parent_id( 0 );
$child->save();
}
}
}
$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(
'woocommerce_rest_trash_not_supported',
/* translators: %s: post type */
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(
'woocommerce_rest_already_trashed',
/* translators: %s: post type */
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(
'woocommerce_rest_cannot_delete',
/* translators: %s: post type */
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;
}
/**
* Get the Product's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$weight_unit_label = I18nUtil::get_weight_unit_label( get_option( 'woocommerce_weight_unit', 'kg' ) );
$dimension_unit_label = I18nUtil::get_dimensions_unit_label( get_option( 'woocommerce_dimension_unit', 'cm' ) );
$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,
),
'name' => array(
'description' => __( 'Product name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'slug' => array(
'description' => __( 'Product slug.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'permalink' => array(
'description' => __( 'Product URL.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_created' => array(
'description' => __( "The date the product 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 product was created, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'date_modified' => array(
'description' => __( "The date the product 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 product was last modified, as GMT.', 'woocommerce' ),
'type' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'type' => array(
'description' => __( 'Product type.', 'woocommerce' ),
'type' => 'string',
'default' => 'simple',
'enum' => array_keys( wc_get_product_types() ),
'context' => array( 'view', 'edit' ),
),
'status' => array(
'description' => __( 'Product status (post status).', 'woocommerce' ),
'type' => 'string',
'default' => 'publish',
'enum' => array_merge( array_keys( get_post_statuses() ), array( 'future' ) ),
'context' => array( 'view', 'edit' ),
),
'featured' => array(
'description' => __( 'Featured product.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'catalog_visibility' => array(
'description' => __( 'Catalog visibility.', 'woocommerce' ),
'type' => 'string',
'default' => 'visible',
'enum' => array( 'visible', 'catalog', 'search', 'hidden' ),
'context' => array( 'view', 'edit' ),
),
'description' => array(
'description' => __( 'Product description.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'short_description' => array(
'description' => __( 'Product short description.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'sku' => array(
'description' => __( 'Unique identifier.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'price' => array(
'description' => __( 'Current product price.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'regular_price' => array(
'description' => __( 'Product regular price.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'sale_price' => array(
'description' => __( 'Product 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' ),
),
'price_html' => array(
'description' => __( 'Price formatted in HTML.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'on_sale' => array(
'description' => __( 'Shows if the product is on sale.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'purchasable' => array(
'description' => __( 'Shows if the product can be bought.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'total_sales' => array(
'description' => __( 'Amount of sales.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'virtual' => array(
'description' => __( 'If the product is virtual.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'downloadable' => array(
'description' => __( 'If the product 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' ),
),
'external_url' => array(
'description' => __( 'Product external URL. Only for external products.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit' ),
),
'button_text' => array(
'description' => __( 'Product external button text. Only for external products.', 'woocommerce' ),
'type' => 'string',
'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 product level.', 'woocommerce' ),
'type' => 'boolean',
'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 product 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 product is on backordered.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'sold_individually' => array(
'description' => __( 'Allow one item to be bought in a single order.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'weight' => array(
/* translators: %s: weight unit */
'description' => sprintf( __( 'Product weight (%s).', 'woocommerce' ), $weight_unit_label ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'dimensions' => array(
'description' => __( 'Product dimensions.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(
'length' => array(
/* translators: %s: dimension unit */
'description' => sprintf( __( 'Product length (%s).', 'woocommerce' ), $dimension_unit_label ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'width' => array(
/* translators: %s: dimension unit */
'description' => sprintf( __( 'Product width (%s).', 'woocommerce' ), $dimension_unit_label ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'height' => array(
/* translators: %s: dimension unit */
'description' => sprintf( __( 'Product height (%s).', 'woocommerce' ), $dimension_unit_label ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
),
'shipping_required' => array(
'description' => __( 'Shows if the product need to be shipped.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'shipping_taxable' => array(
'description' => __( 'Shows whether or not the product shipping is taxable.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'shipping_class' => array(
'description' => __( 'Shipping class slug.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'shipping_class_id' => array(
'description' => __( 'Shipping class ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'reviews_allowed' => array(
'description' => __( 'Allow reviews.', 'woocommerce' ),
'type' => 'boolean',
'default' => true,
'context' => array( 'view', 'edit' ),
),
'average_rating' => array(
'description' => __( 'Reviews average rating.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'rating_count' => array(
'description' => __( 'Amount of reviews that the product have.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'related_ids' => array(
'description' => __( 'List of related products IDs.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'upsell_ids' => array(
'description' => __( 'List of up-sell products IDs.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'context' => array( 'view', 'edit' ),
),
'cross_sell_ids' => array(
'description' => __( 'List of cross-sell products IDs.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'context' => array( 'view', 'edit' ),
),
'parent_id' => array(
'description' => __( 'Product parent ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'purchase_note' => array(
'description' => __( 'Optional note to send the customer after purchase.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'categories' => array(
'description' => __( 'List of categories.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Category ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'name' => array(
'description' => __( 'Category name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'slug' => array(
'description' => __( 'Category slug.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
),
'tags' => array(
'description' => __( 'List of tags.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Tag ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'name' => array(
'description' => __( 'Tag name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'slug' => array(
'description' => __( 'Tag slug.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
),
'images' => array(
'description' => __( 'List of images.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'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' ),
),
'position' => array(
'description' => __( 'Attribute position.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
),
'visible' => array(
'description' => __( "Define if the attribute is visible on the \"Additional information\" tab in the product's page.", 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'variation' => array(
'description' => __( 'Define if the attribute can be used as variation.', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'context' => array( 'view', 'edit' ),
),
'options' => array(
'description' => __( 'List of available term names of the attribute.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'string',
),
),
),
),
),
'default_attributes' => array(
'description' => __( 'Defaults variation 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' ),
),
),
),
),
'variations' => array(
'description' => __( 'List of variations IDs.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'integer',
),
'readonly' => true,
),
'grouped_products' => array(
'description' => __( 'List of grouped products ID.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'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' => 'mixed',
'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['orderby']['enum'] = array_merge( $params['orderby']['enum'], array( 'menu_order' ) );
$params['slug'] = array(
'description' => __( 'Limit result set to products with a specific slug.', 'woocommerce' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
$params['status'] = array(
'default' => 'any',
'description' => __( 'Limit result set to products assigned a specific status.', 'woocommerce' ),
'type' => 'string',
'enum' => array_merge( array( 'any', 'future', 'trash' ), array_keys( get_post_statuses() ) ),
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
);
$params['type'] = array(
'description' => __( 'Limit result set to products assigned a specific type.', 'woocommerce' ),
'type' => 'string',
'enum' => array_keys( wc_get_product_types() ),
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
);
$params['sku'] = array(
'description' => __( 'Limit result set to products with specific SKU(s). Use commas to separate.', 'woocommerce' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
);
$params['featured'] = array(
'description' => __( 'Limit result set to featured products.', 'woocommerce' ),
'type' => 'boolean',
'sanitize_callback' => 'wc_string_to_bool',
'validate_callback' => 'rest_validate_request_arg',
);
$params['category'] = array(
'description' => __( 'Limit result set to products assigned a specific category ID.', 'woocommerce' ),
'type' => 'string',
'sanitize_callback' => 'wp_parse_id_list',
'validate_callback' => 'rest_validate_request_arg',
);
$params['tag'] = array(
'description' => __( 'Limit result set to products assigned a specific tag ID.', 'woocommerce' ),
'type' => 'string',
'sanitize_callback' => 'wp_parse_id_list',
'validate_callback' => 'rest_validate_request_arg',
);
$params['shipping_class'] = array(
'description' => __( 'Limit result set to products assigned a specific shipping class ID.', 'woocommerce' ),
'type' => 'string',
'sanitize_callback' => 'wp_parse_id_list',
'validate_callback' => 'rest_validate_request_arg',
);
$params['attribute'] = array(
'description' => __( 'Limit result set to products with a specific attribute. Use the taxonomy name/attribute slug.', 'woocommerce' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
);
$params['attribute_term'] = array(
'description' => __( 'Limit result set to products with a specific attribute term ID (required an assigned attribute).', 'woocommerce' ),
'type' => 'string',
'sanitize_callback' => 'wp_parse_id_list',
'validate_callback' => 'rest_validate_request_arg',
);
if ( wc_tax_enabled() ) {
$params['tax_class'] = array(
'description' => __( 'Limit result set to products with a specific tax class.', 'woocommerce' ),
'type' => 'string',
'enum' => array_merge( array( 'standard' ), WC_Tax::get_tax_class_slugs() ),
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
);
}
$params['in_stock'] = array(
'description' => __( 'Limit result set to products in stock or out of stock.', 'woocommerce' ),
'type' => 'boolean',
'sanitize_callback' => 'wc_string_to_bool',
'validate_callback' => 'rest_validate_request_arg',
);
$params['on_sale'] = array(
'description' => __( 'Limit result set to products on sale.', 'woocommerce' ),
'type' => 'boolean',
'sanitize_callback' => 'wc_string_to_bool',
'validate_callback' => 'rest_validate_request_arg',
);
$params['min_price'] = array(
'description' => __( 'Limit result set to products based on a minimum price.', 'woocommerce' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
);
$params['max_price'] = array(
'description' => __( 'Limit result set to products based on a maximum price.', 'woocommerce' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
);
$params['include_meta'] = array(
'default' => array(),
'description' => __( 'Limit meta_data to specific keys.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'string',
),
'sanitize_callback' => 'wp_parse_list',
);
$params['exclude_meta'] = array(
'default' => array(),
'description' => __( 'Ensure meta_data excludes specific keys.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'string',
),
'sanitize_callback' => 'wp_parse_list',
);
return $params;
}
}
class-wc-rest-report-sales-v2-controller.php 0000644 00000000755 15155060366 0015106 0 ustar 00 <?php
/**
* REST API Reports controller
*
* Handles requests to the reports/sales endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Report Sales controller class.
*
* @package 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';
}
class-wc-rest-report-top-sellers-v2-controller.php 0000644 00000001013 15155060366 0016234 0 ustar 00 <?php
/**
* REST API Reports controller
*
* Handles requests to the reports/top_sellers endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Report Top Sellers controller class.
*
* @package 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';
}
class-wc-rest-reports-v2-controller.php 0000644 00000000723 15155060366 0014157 0 ustar 00 <?php
/**
* REST API Reports controller
*
* Handles requests to the reports endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Reports controller class.
*
* @package 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';
}
class-wc-rest-setting-options-v2-controller.php 0000644 00000042603 15155060366 0015632 0 ustar 00 <?php
/**
* REST API Setting Options controller
*
* Handles requests to the /settings/$group/$setting endpoints.
*
* @package WooCommerce\RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Setting Options controller class.
*
* @package 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 ) );
}
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
$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, true );
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], null ) ?? array();
$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',
),
true
);
}
/**
* 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.
),
true
);
}
/**
* 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' => 'mixed',
'context' => array( 'view', 'edit' ),
),
'default' => array(
'description' => __( 'Default value for the setting.', 'woocommerce' ),
'type' => 'mixed',
'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 );
}
}
class-wc-rest-settings-v2-controller.php 0000644 00000014204 15155060366 0014320 0 ustar 00 <?php
/**
* REST API Settings controller
*
* Handles requests to the /settings endpoints.
*
* @package WooCommerce\RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Settings controller class.
*
* @package 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 );
}
}
class-wc-rest-shipping-methods-v2-controller.php 0000644 00000015432 15155060366 0015746 0 ustar 00 <?php
/**
* REST API WC Shipping Methods controller
*
* Handles requests to the /shipping_methods endpoint.
*
* @package WooCommerce\RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* Shipping methods controller class.
*
* @package 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' ) ),
);
}
}
class-wc-rest-shipping-zone-locations-v2-controller.php 0000644 00000012247 15155060366 0017250 0 ustar 00 <?php
/**
* REST API Shipping Zone Locations controller
*
* Handles requests to the /shipping/zones/<id>/locations endpoint.
*
* @package WooCommerce\RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Shipping Zone Locations class.
*
* @package 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 );
}
}
class-wc-rest-shipping-zone-methods-v2-controller.php 0000644 00000042503 15155060366 0016716 0 ustar 00 <?php
/**
* REST API Shipping Zone Methods controller
*
* Handles requests to the /shipping/zones/<id>/methods endpoint.
*
* @package WooCommerce\RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Shipping Zone Methods class.
*
* @package 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 );
}
}
class-wc-rest-shipping-zones-v2-controller.php 0000644 00000021046 15155060366 0015437 0 ustar 00 <?php
/**
* REST API Shipping Zones controller
*
* Handles requests to the /shipping/zones endpoint.
*
* @package WooCommerce\RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Shipping Zones class.
*
* @package 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 );
}
}
class-wc-rest-system-status-tools-v2-controller.php 0000644 00000054417 15155060366 0016475 0 ustar 00 <?php
/**
* REST API WC System Status Tools Controller
*
* Handles requests to the /system_status/tools/* endpoints.
*
* @package WooCommerce\RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* System status tools controller.
*
* @package 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 related records in wc_download_log (aka ON DELETE CASCADE).
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->prefix}wc_download_log
WHERE permission_id IN (
SELECT permission_id FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
WHERE ( downloads_remaining != '' AND downloads_remaining = 0 ) OR ( access_expires IS NOT NULL AND access_expires < %s )
)",
current_time( 'Y-m-d' )
)
);
// 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 )",
current_time( 'Y-m-d' )
)
)
);
/* 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':
wc_recount_all_terms();
$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'];
try {
$return = call_user_func( $callback );
} catch ( Exception $exception ) {
$return = $exception;
}
if ( is_a( $return, Exception::class ) ) {
$callback_string = $this->get_printable_callback_name( $callback, $tool );
$ran = false;
/* translators: %1$s: callback string, %2$s: error message */
$message = sprintf( __( 'There was an error calling %1$s: %2$s', 'woocommerce' ), $callback_string, $return->getMessage() );
$logger = wc_get_logger();
$logger->error(
sprintf(
'Error running debug tool %s: %s',
$tool,
$return->getMessage()
),
array(
'source' => 'run-debug-tool',
'tool' => $tool,
'callback' => $callback,
'error' => $return,
)
);
} elseif ( is_string( $return ) ) {
$message = $return;
} elseif ( false === $return ) {
$callback_string = $this->get_printable_callback_name( $callback, $tool );
$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,
);
}
/**
* Get a printable name for a callback.
*
* @param mixed $callback The callback to get a name for.
* @param string $default The default name, to be returned when the callback is an inline function.
* @return string A printable name for the callback.
*/
private function get_printable_callback_name( $callback, $default ) {
if ( is_array( $callback ) ) {
return get_class( $callback[0] ) . '::' . $callback[1];
}
if ( is_string( $callback ) ) {
return $callback;
}
return $default;
}
}
class-wc-rest-system-status-v2-controller.php 0000644 00000142440 15155060366 0015331 0 ustar 00 <?php
/**
* REST API WC System Status controller
*
* Handles requests to the /system_status endpoint.
*
* @package WooCommerce\RestApi
* @since 3.0.0
*/
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Internal\WCCom\ConnectionHelper;
use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Register as Download_Directories;
use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer as Order_DataSynchronizer;
use Automattic\WooCommerce\Utilities\OrderUtil;
/**
* System status controller class.
*
* @package WooCommerce\RestApi
* @extends WC_REST_Controller
*/
class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v2';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'system_status';
/**
* Register cache cleaner
*
* Handles all the cache cleaning for this endpoint. We need to register
* these functions before the routes are registered, so this function gets
* called from Server.php
*/
public static function register_cache_clean() {
// Clear the theme cache if we switch themes or our theme is upgraded.
add_action( 'switch_theme', array( __CLASS__, 'clean_theme_cache' ) );
add_action( 'activate_plugin', array( __CLASS__, 'clean_plugin_cache' ) );
add_action( 'deactivate_plugin', array( __CLASS__, 'clean_plugin_cache' ) );
add_action(
'upgrader_process_complete',
function( $upgrader, $extra ) {
if ( ! $extra || ! $extra['type'] ) {
return;
}
// Clear the cache if woocommerce is updated.
if ( 'plugin' === $extra['type'] ) {
\WC_REST_System_Status_V2_Controller::clean_theme_cache();
\WC_REST_System_Status_V2_Controller::clean_plugin_cache();
return;
}
if ( 'theme' === $extra['type'] ) {
\WC_REST_System_Status_V2_Controller::clean_theme_cache();
return;
}
},
10,
2
);
}
/**
* Register the route for /system_status
*/
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 view system status.
*
* @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;
}
/**
* Get a system status info, by section.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$fields = $this->get_fields_for_response( $request );
$mappings = $this->get_item_mappings_per_fields( $fields );
$response = $this->prepare_item_for_response( $mappings, $request );
return rest_ensure_response( $response );
}
/**
* Get the system status 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',
'type' => 'object',
'properties' => array(
'environment' => array(
'description' => __( 'Environment.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view' ),
'readonly' => true,
'properties' => array(
'home_url' => array(
'description' => __( 'Home URL.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view' ),
'readonly' => true,
),
'site_url' => array(
'description' => __( 'Site URL.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view' ),
'readonly' => true,
),
'version' => array(
'description' => __( 'WooCommerce version.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'log_directory' => array(
'description' => __( 'Log directory.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'log_directory_writable' => array(
'description' => __( 'Is log directory writable?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'wp_version' => array(
'description' => __( 'WordPress version.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'wp_multisite' => array(
'description' => __( 'Is WordPress multisite?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'wp_memory_limit' => array(
'description' => __( 'WordPress memory limit.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'wp_debug_mode' => array(
'description' => __( 'Is WordPress debug mode active?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'wp_cron' => array(
'description' => __( 'Are WordPress cron jobs enabled?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'language' => array(
'description' => __( 'WordPress language.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'server_info' => array(
'description' => __( 'Server info.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'php_version' => array(
'description' => __( 'PHP version.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'php_post_max_size' => array(
'description' => __( 'PHP post max size.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'php_max_execution_time' => array(
'description' => __( 'PHP max execution time.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'php_max_input_vars' => array(
'description' => __( 'PHP max input vars.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'curl_version' => array(
'description' => __( 'cURL version.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'suhosin_installed' => array(
'description' => __( 'Is SUHOSIN installed?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'max_upload_size' => array(
'description' => __( 'Max upload size.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'mysql_version' => array(
'description' => __( 'MySQL version.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'mysql_version_string' => array(
'description' => __( 'MySQL version string.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'default_timezone' => array(
'description' => __( 'Default timezone.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'fsockopen_or_curl_enabled' => array(
'description' => __( 'Is fsockopen/cURL enabled?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'soapclient_enabled' => array(
'description' => __( 'Is SoapClient class enabled?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'domdocument_enabled' => array(
'description' => __( 'Is DomDocument class enabled?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'gzip_enabled' => array(
'description' => __( 'Is GZip enabled?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'mbstring_enabled' => array(
'description' => __( 'Is mbstring enabled?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'remote_post_successful' => array(
'description' => __( 'Remote POST successful?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'remote_post_response' => array(
'description' => __( 'Remote POST response.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'remote_get_successful' => array(
'description' => __( 'Remote GET successful?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'remote_get_response' => array(
'description' => __( 'Remote GET response.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
),
'database' => array(
'description' => __( 'Database.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view' ),
'readonly' => true,
'properties' => array(
'wc_database_version' => array(
'description' => __( 'WC database version.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'database_prefix' => array(
'description' => __( 'Database prefix.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'maxmind_geoip_database' => array(
'description' => __( 'MaxMind GeoIP database.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'database_tables' => array(
'description' => __( 'Database tables.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
),
),
'active_plugins' => array(
'description' => __( 'Active plugins.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
'inactive_plugins' => array(
'description' => __( 'Inactive plugins.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
'dropins_mu_plugins' => array(
'description' => __( 'Dropins & MU plugins.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
'theme' => array(
'description' => __( 'Theme.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view' ),
'readonly' => true,
'properties' => array(
'name' => array(
'description' => __( 'Theme name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'version' => array(
'description' => __( 'Theme version.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'version_latest' => array(
'description' => __( 'Latest version of theme.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'author_url' => array(
'description' => __( 'Theme author URL.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view' ),
'readonly' => true,
),
'is_child_theme' => array(
'description' => __( 'Is this theme a child theme?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'has_woocommerce_support' => array(
'description' => __( 'Does the theme declare WooCommerce support?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'has_woocommerce_file' => array(
'description' => __( 'Does the theme have a woocommerce.php file?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'has_outdated_templates' => array(
'description' => __( 'Does this theme have outdated templates?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'overrides' => array(
'description' => __( 'Template overrides.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
'parent_name' => array(
'description' => __( 'Parent theme name.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'parent_version' => array(
'description' => __( 'Parent theme version.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'parent_author_url' => array(
'description' => __( 'Parent theme author URL.', 'woocommerce' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view' ),
'readonly' => true,
),
),
),
'settings' => array(
'description' => __( 'Settings.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view' ),
'readonly' => true,
'properties' => array(
'api_enabled' => array(
'description' => __( 'REST API enabled?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'force_ssl' => array(
'description' => __( 'SSL forced?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'currency' => array(
'description' => __( 'Currency.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'currency_symbol' => array(
'description' => __( 'Currency symbol.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'currency_position' => array(
'description' => __( 'Currency position.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'thousand_separator' => array(
'description' => __( 'Thousand separator.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'decimal_separator' => array(
'description' => __( 'Decimal separator.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'number_of_decimals' => array(
'description' => __( 'Number of decimals.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'geolocation_enabled' => array(
'description' => __( 'Geolocation enabled?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'taxonomies' => array(
'description' => __( 'Taxonomy terms for product/order statuses.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
'product_visibility_terms' => array(
'description' => __( 'Terms in the product visibility taxonomy.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
'wccom_connected' => array(
'description' => __( 'Is store connected to WooCommerce.com?', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'enforce_approved_download_dirs' => array(
'description' => __( 'Enforce approved download directories?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'HPOS_feature_screen_enabled' => array(
'description' => __( 'Is HPOS feature screen enabled?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'HPOS_enabled' => array(
'description' => __( 'Is HPOS enabled?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'order_datastore' => array(
'description' => __( 'Order datastore.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'HPOS_sync_enabled' => array(
'description' => __( 'Is HPOS sync enabled?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
),
),
'security' => array(
'description' => __( 'Security.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view' ),
'readonly' => true,
'properties' => array(
'secure_connection' => array(
'description' => __( 'Is the connection to your store secure?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'hide_errors' => array(
'description' => __( 'Hide errors from visitors?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
),
),
'pages' => array(
'description' => __( 'WooCommerce pages.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
'post_type_counts' => array(
'description' => __( 'Total post count.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'string',
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Return an array of sections and the data associated with each.
*
* @deprecated 3.9.0
* @return array
*/
public function get_item_mappings() {
return array(
'environment' => $this->get_environment_info(),
'database' => $this->get_database_info(),
'active_plugins' => $this->get_active_plugins(),
'inactive_plugins' => $this->get_inactive_plugins(),
'dropins_mu_plugins' => $this->get_dropins_mu_plugins(),
'theme' => $this->get_theme_info(),
'settings' => $this->get_settings(),
'security' => $this->get_security_info(),
'pages' => $this->get_pages(),
'post_type_counts' => $this->get_post_type_counts(),
);
}
/**
* Return an array of sections and the data associated with each.
*
* @since 3.9.0
* @param array $fields List of fields to be included on the response.
* @return array
*/
public function get_item_mappings_per_fields( $fields ) {
$items = array();
foreach ( $fields as $field ) {
// If we're looking for a sub-property, like environment.version we need
// to extract the first-level property here so we know which function to run.
list( $prop ) = explode( '.', $field, 2 );
switch ( $prop ) {
case 'environment':
$items['environment'] = $this->get_environment_info_per_fields( $fields );
break;
case 'database':
$items['database'] = $this->get_database_info();
break;
case 'active_plugins':
$items['active_plugins'] = $this->get_active_plugins();
break;
case 'inactive_plugins':
$items['inactive_plugins'] = $this->get_inactive_plugins();
break;
case 'dropins_mu_plugins':
$items['dropins_mu_plugins'] = $this->get_dropins_mu_plugins();
break;
case 'theme':
$items['theme'] = $this->get_theme_info();
break;
case 'settings':
$items['settings'] = $this->get_settings();
break;
case 'security':
$items['security'] = $this->get_security_info();
break;
case 'pages':
$items['pages'] = $this->get_pages();
break;
case 'post_type_counts':
$items['post_type_counts'] = $this->get_post_type_counts();
break;
}
}
return $items;
}
/**
* Get array of environment information. Includes thing like software
* versions, and various server settings.
*
* @deprecated 3.9.0
* @return array
*/
public function get_environment_info() {
return $this->get_environment_info_per_fields( array( 'environment' ) );
}
/**
* Check if field item exists.
*
* @since 3.9.0
* @param string $section Fields section.
* @param array $items List of items to check for.
* @param array $fields List of fields to be included on the response.
* @return bool
*/
private function check_if_field_item_exists( $section, $items, $fields ) {
if ( ! in_array( $section, $fields, true ) ) {
return false;
}
$exclude = array();
foreach ( $fields as $field ) {
$values = explode( '.', $field );
if ( $section !== $values[0] || empty( $values[1] ) ) {
continue;
}
$exclude[] = $values[1];
}
return 0 <= count( array_intersect( $items, $exclude ) );
}
/**
* Get array of environment information. Includes thing like software
* versions, and various server settings.
*
* @param array $fields List of fields to be included on the response.
* @return array
*/
public function get_environment_info_per_fields( $fields ) {
global $wpdb;
$enable_remote_post = $this->check_if_field_item_exists( 'environment', array( 'remote_post_successful', 'remote_post_response' ), $fields );
$enable_remote_get = $this->check_if_field_item_exists( 'environment', array( 'remote_get_successful', 'remote_get_response' ), $fields );
// Figure out cURL version, if installed.
$curl_version = '';
if ( function_exists( 'curl_version' ) ) {
$curl_version = curl_version();
$curl_version = $curl_version['version'] . ', ' . $curl_version['ssl_version'];
} elseif ( extension_loaded( 'curl' ) ) {
$curl_version = __( 'cURL installed but unable to retrieve version.', 'woocommerce' );
}
// WP memory limit.
$wp_memory_limit = wc_let_to_num( WP_MEMORY_LIMIT );
if ( function_exists( 'memory_get_usage' ) ) {
$wp_memory_limit = max( $wp_memory_limit, wc_let_to_num( @ini_get( 'memory_limit' ) ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
}
// Test POST requests.
$post_response_successful = null;
$post_response_code = null;
if ( $enable_remote_post ) {
$post_response_code = get_transient( 'woocommerce_test_remote_post' );
if ( false === $post_response_code || is_wp_error( $post_response_code ) ) {
$response = wp_safe_remote_post(
'https://www.paypal.com/cgi-bin/webscr',
array(
'timeout' => 10,
'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
'httpversion' => '1.1',
'body' => array(
'cmd' => '_notify-validate',
),
)
);
if ( ! is_wp_error( $response ) ) {
$post_response_code = $response['response']['code'];
}
set_transient( 'woocommerce_test_remote_post', $post_response_code, HOUR_IN_SECONDS );
}
$post_response_successful = ! is_wp_error( $post_response_code ) && $post_response_code >= 200 && $post_response_code < 300;
}
// Test GET requests.
$get_response_successful = null;
$get_response_code = null;
if ( $enable_remote_get ) {
$get_response_code = get_transient( 'woocommerce_test_remote_get' );
if ( false === $get_response_code || is_wp_error( $get_response_code ) ) {
$response = wp_safe_remote_get(
'https://woocommerce.com/wc-api/product-key-api?request=ping&network=' . ( is_multisite() ? '1' : '0' ),
array(
'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
)
);
if ( ! is_wp_error( $response ) ) {
$get_response_code = $response['response']['code'];
}
set_transient( 'woocommerce_test_remote_get', $get_response_code, HOUR_IN_SECONDS );
}
$get_response_successful = ! is_wp_error( $get_response_code ) && $get_response_code >= 200 && $get_response_code < 300;
}
$database_version = wc_get_server_database_version();
// Return all environment info. Described by JSON Schema.
return array(
'home_url' => get_option( 'home' ),
'site_url' => get_option( 'siteurl' ),
'version' => WC()->version,
'log_directory' => WC_LOG_DIR,
'log_directory_writable' => (bool) @fopen( WC_LOG_DIR . 'test-log.log', 'a' ), // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_read_fopen
'wp_version' => get_bloginfo( 'version' ),
'wp_multisite' => is_multisite(),
'wp_memory_limit' => $wp_memory_limit,
'wp_debug_mode' => ( defined( 'WP_DEBUG' ) && WP_DEBUG ),
'wp_cron' => ! ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ),
'language' => get_locale(),
'external_object_cache' => wp_using_ext_object_cache(),
'server_info' => isset( $_SERVER['SERVER_SOFTWARE'] ) ? wc_clean( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '',
'php_version' => phpversion(),
'php_post_max_size' => wc_let_to_num( ini_get( 'post_max_size' ) ),
'php_max_execution_time' => (int) ini_get( 'max_execution_time' ),
'php_max_input_vars' => (int) ini_get( 'max_input_vars' ),
'curl_version' => $curl_version,
'suhosin_installed' => extension_loaded( 'suhosin' ),
'max_upload_size' => wp_max_upload_size(),
'mysql_version' => $database_version['number'],
'mysql_version_string' => $database_version['string'],
'default_timezone' => date_default_timezone_get(),
'fsockopen_or_curl_enabled' => ( function_exists( 'fsockopen' ) || function_exists( 'curl_init' ) ),
'soapclient_enabled' => class_exists( 'SoapClient' ),
'domdocument_enabled' => class_exists( 'DOMDocument' ),
'gzip_enabled' => is_callable( 'gzopen' ),
'mbstring_enabled' => extension_loaded( 'mbstring' ),
'remote_post_successful' => $post_response_successful,
'remote_post_response' => is_wp_error( $post_response_code ) ? $post_response_code->get_error_message() : $post_response_code,
'remote_get_successful' => $get_response_successful,
'remote_get_response' => is_wp_error( $get_response_code ) ? $get_response_code->get_error_message() : $get_response_code,
);
}
/**
* Add prefix to table.
*
* @param string $table Table name.
* @return stromg
*/
protected function add_db_table_prefix( $table ) {
global $wpdb;
return $wpdb->prefix . $table;
}
/**
* Get array of database information. Version, prefix, and table existence.
*
* @return array
*/
public function get_database_info() {
global $wpdb;
$tables = array();
$database_size = array();
// It is not possible to get the database name from some classes that replace wpdb (e.g., HyperDB)
// and that is why this if condition is needed.
if ( defined( 'DB_NAME' ) ) {
$database_table_information = $wpdb->get_results(
$wpdb->prepare(
"SELECT
table_name AS 'name',
engine AS 'engine',
round( ( data_length / 1024 / 1024 ), 2 ) 'data',
round( ( index_length / 1024 / 1024 ), 2 ) 'index'
FROM information_schema.TABLES
WHERE table_schema = %s
ORDER BY name ASC;",
DB_NAME
)
);
// WC Core tables to check existence of.
$core_tables = apply_filters(
'woocommerce_database_tables',
array(
'woocommerce_sessions',
'woocommerce_api_keys',
'woocommerce_attribute_taxonomies',
'woocommerce_downloadable_product_permissions',
'woocommerce_order_items',
'woocommerce_order_itemmeta',
'woocommerce_tax_rates',
'woocommerce_tax_rate_locations',
'woocommerce_shipping_zones',
'woocommerce_shipping_zone_locations',
'woocommerce_shipping_zone_methods',
'woocommerce_payment_tokens',
'woocommerce_payment_tokenmeta',
'woocommerce_log',
)
);
/**
* Adding the prefix to the tables array, for backwards compatibility.
*
* If we changed the tables above to include the prefix, then any filters against that table could break.
*/
$core_tables = array_map( array( $this, 'add_db_table_prefix' ), $core_tables );
/**
* Organize WooCommerce and non-WooCommerce tables separately for display purposes later.
*
* To ensure we include all WC tables, even if they do not exist, pre-populate the WC array with all the tables.
*/
$tables = array(
'woocommerce' => array_fill_keys( $core_tables, false ),
'other' => array(),
);
$database_size = array(
'data' => 0,
'index' => 0,
);
$site_tables_prefix = $wpdb->get_blog_prefix( get_current_blog_id() );
$global_tables = $wpdb->tables( 'global', true );
foreach ( $database_table_information as $table ) {
// Only include tables matching the prefix of the current site, this is to prevent displaying all tables on a MS install not relating to the current.
if ( is_multisite() && 0 !== strpos( $table->name, $site_tables_prefix ) && ! in_array( $table->name, $global_tables, true ) ) {
continue;
}
$table_type = in_array( $table->name, $core_tables, true ) ? 'woocommerce' : 'other';
$tables[ $table_type ][ $table->name ] = array(
'data' => $table->data,
'index' => $table->index,
'engine' => $table->engine,
);
$database_size['data'] += $table->data;
$database_size['index'] += $table->index;
}
}
// Return all database info. Described by JSON Schema.
return array(
'wc_database_version' => get_option( 'woocommerce_db_version' ),
'database_prefix' => $wpdb->prefix,
'maxmind_geoip_database' => '',
'database_tables' => $tables,
'database_size' => $database_size,
);
}
/**
* Get array of counts of objects. Orders, products, etc.
*
* @return array
*/
public function get_post_type_counts() {
global $wpdb;
$post_type_counts = $wpdb->get_results( "SELECT post_type AS 'type', count(1) AS 'count' FROM {$wpdb->posts} GROUP BY post_type;" );
return is_array( $post_type_counts ) ? $post_type_counts : array();
}
/**
* Get a list of plugins active on the site.
*
* @return array
*/
public function get_active_plugins() {
$active_plugins_data = get_transient( 'wc_system_status_active_plugins' );
if ( false === $active_plugins_data ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
if ( ! function_exists( 'get_plugin_data' ) ) {
return array();
}
$active_plugins = (array) get_option( 'active_plugins', array() );
if ( is_multisite() ) {
$network_activated_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
$active_plugins = array_merge( $active_plugins, $network_activated_plugins );
}
$active_plugins_data = array();
foreach ( $active_plugins as $plugin ) {
$data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
$active_plugins_data[] = $this->format_plugin_data( $plugin, $data );
}
set_transient( 'wc_system_status_active_plugins', $active_plugins_data, HOUR_IN_SECONDS );
}
return $active_plugins_data;
}
/**
* Get a list of inplugins active on the site.
*
* @return array
*/
public function get_inactive_plugins() {
$plugins_data = get_transient( 'wc_system_status_inactive_plugins' );
if ( false === $plugins_data ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
if ( ! function_exists( 'get_plugins' ) ) {
return array();
}
$plugins = get_plugins();
$active_plugins = (array) get_option( 'active_plugins', array() );
if ( is_multisite() ) {
$network_activated_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
$active_plugins = array_merge( $active_plugins, $network_activated_plugins );
}
$plugins_data = array();
foreach ( $plugins as $plugin => $data ) {
if ( in_array( $plugin, $active_plugins, true ) ) {
continue;
}
$plugins_data[] = $this->format_plugin_data( $plugin, $data );
}
set_transient( 'wc_system_status_inactive_plugins', $plugins_data, HOUR_IN_SECONDS );
}
return $plugins_data;
}
/**
* Format plugin data, including data on updates, into a standard format.
*
* @since 3.6.0
* @param string $plugin Plugin directory/file.
* @param array $data Plugin data from WP.
* @return array Formatted data.
*/
protected function format_plugin_data( $plugin, $data ) {
require_once ABSPATH . 'wp-admin/includes/update.php';
if ( ! function_exists( 'get_plugin_updates' ) ) {
return array();
}
// Use WP API to lookup latest updates for plugins. WC_Helper injects updates for premium plugins.
if ( empty( $this->available_updates ) ) {
$this->available_updates = get_plugin_updates();
}
$version_latest = $data['Version'];
// Find latest version.
if ( isset( $this->available_updates[ $plugin ]->update->new_version ) ) {
$version_latest = $this->available_updates[ $plugin ]->update->new_version;
}
return array(
'plugin' => $plugin,
'name' => $data['Name'],
'version' => $data['Version'],
'version_latest' => $version_latest,
'url' => $data['PluginURI'],
'author_name' => $data['AuthorName'],
'author_url' => esc_url_raw( $data['AuthorURI'] ),
'network_activated' => $data['Network'],
);
}
/**
* Get a list of Dropins and MU plugins.
*
* @since 3.6.0
* @return array
*/
public function get_dropins_mu_plugins() {
$plugins = get_transient( 'wc_system_status_dropins_mu_plugins' );
if ( false === $plugins ) {
$dropins = get_dropins();
$plugins = array(
'dropins' => array(),
'mu_plugins' => array(),
);
foreach ( $dropins as $key => $dropin ) {
$plugins['dropins'][] = array(
'plugin' => $key,
'name' => $dropin['Name'],
);
}
$mu_plugins = get_mu_plugins();
foreach ( $mu_plugins as $plugin => $mu_plugin ) {
$plugins['mu_plugins'][] = array(
'plugin' => $plugin,
'name' => $mu_plugin['Name'],
'version' => $mu_plugin['Version'],
'url' => $mu_plugin['PluginURI'],
'author_name' => $mu_plugin['AuthorName'],
'author_url' => esc_url_raw( $mu_plugin['AuthorURI'] ),
);
}
set_transient( 'wc_system_status_dropins_mu_plugins', $plugins, HOUR_IN_SECONDS );
}
return $plugins;
}
/**
* Get info on the current active theme, info on parent theme (if presnet)
* and a list of template overrides.
*
* @return array
*/
public function get_theme_info() {
$theme_info = get_transient( 'wc_system_status_theme_info' );
if ( false === $theme_info ) {
$active_theme = wp_get_theme();
// Get parent theme info if this theme is a child theme, otherwise
// pass empty info in the response.
if ( is_child_theme() ) {
$parent_theme = wp_get_theme( $active_theme->template );
$parent_theme_info = array(
'parent_name' => $parent_theme->name,
'parent_version' => $parent_theme->version,
'parent_version_latest' => WC_Admin_Status::get_latest_theme_version( $parent_theme ),
'parent_author_url' => $parent_theme->{'Author URI'},
);
} else {
$parent_theme_info = array(
'parent_name' => '',
'parent_version' => '',
'parent_version_latest' => '',
'parent_author_url' => '',
);
}
/**
* Scan the theme directory for all WC templates to see if our theme
* overrides any of them.
*/
$override_files = array();
$outdated_templates = false;
$scan_files = WC_Admin_Status::scan_template_files( WC()->plugin_path() . '/templates/' );
// Include *-product_<cat|tag> templates for backwards compatibility.
$scan_files[] = 'content-product_cat.php';
$scan_files[] = 'taxonomy-product_cat.php';
$scan_files[] = 'taxonomy-product_tag.php';
foreach ( $scan_files as $file ) {
$located = apply_filters( 'wc_get_template', $file, $file, array(), WC()->template_path(), WC()->plugin_path() . '/templates/' );
if ( file_exists( $located ) ) {
$theme_file = $located;
} elseif ( file_exists( get_stylesheet_directory() . '/' . $file ) ) {
$theme_file = get_stylesheet_directory() . '/' . $file;
} elseif ( file_exists( get_stylesheet_directory() . '/' . WC()->template_path() . $file ) ) {
$theme_file = get_stylesheet_directory() . '/' . WC()->template_path() . $file;
} elseif ( file_exists( get_template_directory() . '/' . $file ) ) {
$theme_file = get_template_directory() . '/' . $file;
} elseif ( file_exists( get_template_directory() . '/' . WC()->template_path() . $file ) ) {
$theme_file = get_template_directory() . '/' . WC()->template_path() . $file;
} else {
$theme_file = false;
}
if ( ! empty( $theme_file ) ) {
$core_file = $file;
// Update *-product_<cat|tag> template name before searching in core.
if ( false !== strpos( $core_file, '-product_cat' ) || false !== strpos( $core_file, '-product_tag' ) ) {
$core_file = str_replace( '_', '-', $core_file );
}
$core_version = WC_Admin_Status::get_file_version( WC()->plugin_path() . '/templates/' . $core_file );
$theme_version = WC_Admin_Status::get_file_version( $theme_file );
if ( $core_version && ( empty( $theme_version ) || version_compare( $theme_version, $core_version, '<' ) ) ) {
if ( ! $outdated_templates ) {
$outdated_templates = true;
}
}
$override_files[] = array(
'file' => str_replace( WP_CONTENT_DIR . '/themes/', '', $theme_file ),
'version' => $theme_version,
'core_version' => $core_version,
);
}
}
$active_theme_info = array(
'name' => $active_theme->name,
'version' => $active_theme->version,
'version_latest' => WC_Admin_Status::get_latest_theme_version( $active_theme ),
'author_url' => esc_url_raw( $active_theme->{'Author URI'} ),
'is_child_theme' => is_child_theme(),
'has_woocommerce_support' => current_theme_supports( 'woocommerce' ),
'has_woocommerce_file' => ( file_exists( get_stylesheet_directory() . '/woocommerce.php' ) || file_exists( get_template_directory() . '/woocommerce.php' ) ),
'has_outdated_templates' => $outdated_templates,
'overrides' => $override_files,
);
$theme_info = array_merge( $active_theme_info, $parent_theme_info );
set_transient( 'wc_system_status_theme_info', $theme_info, HOUR_IN_SECONDS );
}
return $theme_info;
}
/**
* Clear the system status theme cache
*/
public static function clean_theme_cache() {
delete_transient( 'wc_system_status_theme_info' );
}
/**
* Clear the system status plugin caches
*/
public static function clean_plugin_cache() {
delete_transient( 'wc_system_status_active_plugins' );
delete_transient( 'wc_system_status_inactive_plugins' );
delete_transient( 'wc_system_status_dropins_mu_plugins' );
}
/**
* Get some setting values for the site that are useful for debugging
* purposes. For full settings access, use the settings api.
*
* @return array
*/
public function get_settings() {
// Get a list of terms used for product/order taxonomies.
$term_response = array();
$terms = get_terms( 'product_type', array( 'hide_empty' => 0 ) );
foreach ( $terms as $term ) {
$term_response[ $term->slug ] = strtolower( $term->name );
}
// Get a list of terms used for product visibility.
$product_visibility_terms = array();
$terms = get_terms( 'product_visibility', array( 'hide_empty' => 0 ) );
foreach ( $terms as $term ) {
$product_visibility_terms[ $term->slug ] = strtolower( $term->name );
}
// Return array of useful settings for debugging.
return array(
'api_enabled' => 'yes' === get_option( 'woocommerce_api_enabled' ),
'force_ssl' => 'yes' === get_option( 'woocommerce_force_ssl_checkout' ),
'currency' => get_woocommerce_currency(),
'currency_symbol' => get_woocommerce_currency_symbol(),
'currency_position' => get_option( 'woocommerce_currency_pos' ),
'thousand_separator' => wc_get_price_thousand_separator(),
'decimal_separator' => wc_get_price_decimal_separator(),
'number_of_decimals' => wc_get_price_decimals(),
'geolocation_enabled' => in_array(
get_option( 'woocommerce_default_customer_address' ),
array(
'geolocation_ajax',
'geolocation',
),
true
),
'taxonomies' => $term_response,
'product_visibility_terms' => $product_visibility_terms,
'woocommerce_com_connected' => ConnectionHelper::is_connected() ? 'yes' : 'no',
'enforce_approved_download_dirs' => wc_get_container()->get( Download_Directories::class )->get_mode() === Download_Directories::MODE_ENABLED,
'order_datastore' => WC_Data_Store::load( 'order' )->get_current_class_name(),
'HPOS_feature_screen_enabled' => wc_get_container()->get( Automattic\WooCommerce\Internal\Features\FeaturesController::class )->feature_is_enabled( 'custom_order_tables' ),
'HPOS_enabled' => OrderUtil::custom_orders_table_usage_is_enabled(),
'HPOS_sync_enabled' => wc_get_container()->get( Order_DataSynchronizer::class )->data_sync_is_enabled(),
);
}
/**
* Returns security tips.
*
* @return array
*/
public function get_security_info() {
$check_page = wc_get_page_permalink( 'shop' );
return array(
'secure_connection' => 'https' === substr( $check_page, 0, 5 ),
'hide_errors' => ! ( defined( 'WP_DEBUG' ) && defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG && WP_DEBUG_DISPLAY ) || 0 === intval( ini_get( 'display_errors' ) ),
);
}
/**
* Returns a mini-report on WC pages and if they are configured correctly:
* Present, visible, and including the correct shortcode or block.
*
* @return array
*/
public function get_pages() {
// WC pages to check against.
$check_pages = array(
_x( 'Shop base', 'Page setting', 'woocommerce' ) => array(
'option' => 'woocommerce_shop_page_id',
'shortcode' => '',
'block' => '',
),
_x( 'Cart', 'Page setting', 'woocommerce' ) => array(
'option' => 'woocommerce_cart_page_id',
'shortcode' => '[' . apply_filters( 'woocommerce_cart_shortcode_tag', 'woocommerce_cart' ) . ']',
'block' => 'woocommerce/cart',
),
_x( 'Checkout', 'Page setting', 'woocommerce' ) => array(
'option' => 'woocommerce_checkout_page_id',
'shortcode' => '[' . apply_filters( 'woocommerce_checkout_shortcode_tag', 'woocommerce_checkout' ) . ']',
'block' => 'woocommerce/checkout',
),
_x( 'My account', 'Page setting', 'woocommerce' ) => array(
'option' => 'woocommerce_myaccount_page_id',
'shortcode' => '[' . apply_filters( 'woocommerce_my_account_shortcode_tag', 'woocommerce_my_account' ) . ']',
'block' => '',
),
_x( 'Terms and conditions', 'Page setting', 'woocommerce' ) => array(
'option' => 'woocommerce_terms_page_id',
'shortcode' => '',
'block' => '',
),
);
$pages_output = array();
foreach ( $check_pages as $page_name => $values ) {
$page_id = get_option( $values['option'] );
$page_set = false;
$page_exists = false;
$page_visible = false;
$shortcode_present = false;
$shortcode_required = false;
$block_present = false;
$block_required = false;
// Page checks.
if ( $page_id ) {
$page_set = true;
}
if ( get_post( $page_id ) ) {
$page_exists = true;
}
if ( 'publish' === get_post_status( $page_id ) ) {
$page_visible = true;
}
// Shortcode checks.
if ( $values['shortcode'] && get_post( $page_id ) ) {
$shortcode_required = true;
$page = get_post( $page_id );
if ( strstr( $page->post_content, $values['shortcode'] ) ) {
$shortcode_present = true;
}
}
// Block checks.
if ( $values['block'] && get_post( $page_id ) ) {
$block_required = true;
$block_present = WC_Blocks_Utils::has_block_in_page( $page_id, $values['block'] );
}
// Wrap up our findings into an output array.
$pages_output[] = array(
'page_name' => $page_name,
'page_id' => $page_id,
'page_set' => $page_set,
'page_exists' => $page_exists,
'page_visible' => $page_visible,
'shortcode' => $values['shortcode'],
'block' => $values['block'],
'shortcode_required' => $shortcode_required,
'shortcode_present' => $shortcode_present,
'block_present' => $block_present,
'block_required' => $block_required,
);
}
return $pages_output;
}
/**
* Get any query params needed.
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
/**
* Prepare the system status response
*
* @param array $system_status System status data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
public function prepare_item_for_response( $system_status, $request ) {
$data = $this->add_additional_fields_to_object( $system_status, $request );
$data = $this->filter_response_by_context( $data, 'view' );
$response = rest_ensure_response( $data );
/**
* Filter the system status returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param mixed $system_status System status
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'woocommerce_rest_prepare_system_status', $response, $system_status, $request );
}
}
class-wc-rest-tax-classes-v2-controller.php 0000644 00000005656 15155060366 0014722 0 ustar 00 <?php
/**
* REST API Tax Classes controller
*
* Handles requests to the /taxes/classes endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Tax Classes controller class.
*
* @package 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';
/**
* 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::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
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' ),
)
);
}
/**
* Get one tax class.
*
* @param WP_REST_Request $request Request object.
* @return array
*/
public function get_item( $request ) {
if ( 'standard' === $request['slug'] ) {
$tax_class = array(
'slug' => 'standard',
'name' => __( 'Standard rate', 'woocommerce' ),
);
} else {
$tax_class = WC_Tax::get_tax_class_by( 'slug', sanitize_title( $request['slug'] ) );
}
$data = array();
if ( $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 );
}
}
class-wc-rest-taxes-v2-controller.php 0000644 00000000710 15155060366 0013601 0 ustar 00 <?php
/**
* REST API Taxes controller
*
* Handles requests to the /taxes endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Taxes controller class.
*
* @package 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';
}
class-wc-rest-webhook-deliveries-v2-controller.php 0000644 00000011552 15155060366 0016252 0 ustar 00 <?php
/**
* REST API Webhooks controller
*
* Handles requests to the /webhooks/<webhook_id>/deliveries endpoint.
*
* @package 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 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 );
}
}
class-wc-rest-webhooks-v2-controller.php 0000644 00000014216 15155060366 0014304 0 ustar 00 <?php
/**
* REST API Webhooks controller
*
* Handles requests to the /webhooks endpoint.
*
* @package WooCommerce\RestApi
* @since 2.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Webhooks controller class.
*
* @package 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 );
}
}