File manager - Edit - /home/monara/public_html/test.athavaneng.com/Schemas.tar
Back
ExtendSchema.php 0000644 00000025333 15073235740 0007641 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas; use Automattic\WooCommerce\StoreApi\Schemas\V1\CartItemSchema; use Automattic\WooCommerce\StoreApi\Schemas\V1\CartSchema; use Automattic\WooCommerce\StoreApi\Schemas\V1\CheckoutSchema; use Automattic\WooCommerce\StoreApi\Schemas\V1\ProductSchema; use Automattic\WooCommerce\StoreApi\Formatters; /** * Provides utility functions to extend Store API schemas. * * Note there are also helpers that map to these methods. * * @see woocommerce_store_api_register_endpoint_data() * @see woocommerce_store_api_register_update_callback() * @see woocommerce_store_api_register_payment_requirements() * @see woocommerce_store_api_get_formatter() */ final class ExtendSchema { /** * List of Store API schema that is allowed to be extended by extensions. * * @var string[] */ private $endpoints = [ CartItemSchema::IDENTIFIER, CartSchema::IDENTIFIER, CheckoutSchema::IDENTIFIER, ProductSchema::IDENTIFIER, ]; /** * Holds the formatters class instance. * * @var Formatters */ private $formatters; /** * Data to be extended * * @var array */ private $extend_data = []; /** * Data to be extended * * @var array */ private $callback_methods = []; /** * Array of payment requirements * * @var array */ private $payment_requirements = []; /** * Constructor * * @param Formatters $formatters An instance of the formatters class. */ public function __construct( Formatters $formatters ) { $this->formatters = $formatters; } /** * Register endpoint data under a specified namespace * * @param array $args { * An array of elements that make up a post to update or insert. * * @type string $endpoint Required. The endpoint to extend. * @type string $namespace Required. Plugin namespace. * @type callable $schema_callback Callback executed to add schema data. * @type callable $data_callback Callback executed to add endpoint data. * @type string $schema_type The type of data, object or array. * } * * @throws \Exception On failure to register. */ public function register_endpoint_data( $args ) { $args = wp_parse_args( $args, [ 'endpoint' => '', 'namespace' => '', 'schema_callback' => null, 'data_callback' => null, 'schema_type' => ARRAY_A, ] ); if ( ! is_string( $args['namespace'] ) || empty( $args['namespace'] ) ) { $this->throw_exception( 'You must provide a plugin namespace when extending a Store REST endpoint.' ); } if ( ! in_array( $args['endpoint'], $this->endpoints, true ) ) { $this->throw_exception( sprintf( 'You must provide a valid Store REST endpoint to extend, valid endpoints are: %1$s. You provided %2$s.', implode( ', ', $this->endpoints ), $args['endpoint'] ) ); } if ( ! is_null( $args['schema_callback'] ) && ! is_callable( $args['schema_callback'] ) ) { $this->throw_exception( '$schema_callback must be a callable function.' ); } if ( ! is_null( $args['data_callback'] ) && ! is_callable( $args['data_callback'] ) ) { $this->throw_exception( '$data_callback must be a callable function.' ); } if ( ! in_array( $args['schema_type'], [ ARRAY_N, ARRAY_A ], true ) ) { $this->throw_exception( sprintf( 'Data type must be either ARRAY_N for a numeric array or ARRAY_A for an object like array. You provided %1$s.', $args['schema_type'] ) ); } $this->extend_data[ $args['endpoint'] ][ $args['namespace'] ] = [ 'schema_callback' => $args['schema_callback'], 'data_callback' => $args['data_callback'], 'schema_type' => $args['schema_type'], ]; } /** * Add callback functions that can be executed by the cart/extensions endpoint. * * @param array $args { * An array of elements that make up the callback configuration. * * @type string $namespace Required. Plugin namespace. * @type callable $callback Required. The function/callable to execute. * } * * @throws \Exception On failure to register. */ public function register_update_callback( $args ) { $args = wp_parse_args( $args, [ 'namespace' => '', 'callback' => null, ] ); if ( ! is_string( $args['namespace'] ) || empty( $args['namespace'] ) ) { throw new \Exception( 'You must provide a plugin namespace when extending a Store REST endpoint.' ); } if ( ! is_callable( $args['callback'] ) ) { throw new \Exception( 'There is no valid callback supplied to register_update_callback.' ); } $this->callback_methods[ $args['namespace'] ] = $args; } /** * Registers and validates payment requirements callbacks. * * @param array $args { * Array of registration data. * * @type callable $data_callback Required. Callback executed to add payment requirements data. * } * * @throws \Exception On failure to register. */ public function register_payment_requirements( $args ) { if ( empty( $args['data_callback'] ) || ! is_callable( $args['data_callback'] ) ) { $this->throw_exception( '$data_callback must be a callable function.' ); } $this->payment_requirements[] = $args['data_callback']; } /** * Returns a formatter instance. * * @param string $name Formatter name. * @return FormatterInterface */ public function get_formatter( $name ) { return $this->formatters->$name; } /** * Get callback for a specific endpoint and namespace. * * @param string $namespace The namespace to get callbacks for. * * @return callable The callback registered by the extension. * @throws \Exception When callback is not callable or parameters are incorrect. */ public function get_update_callback( $namespace ) { if ( ! is_string( $namespace ) ) { throw new \Exception( 'You must provide a plugin namespace when extending a Store REST endpoint.' ); } if ( ! array_key_exists( $namespace, $this->callback_methods ) ) { throw new \Exception( sprintf( 'There is no such namespace registered: %1$s.', $namespace ) ); } if ( ! array_key_exists( 'callback', $this->callback_methods[ $namespace ] ) || ! is_callable( $this->callback_methods[ $namespace ]['callback'] ) ) { throw new \Exception( sprintf( 'There is no valid callback registered for: %1$s.', $namespace ) ); } return $this->callback_methods[ $namespace ]['callback']; } /** * Returns the registered endpoint data * * @param string $endpoint A valid identifier. * @param array $passed_args Passed arguments from the Schema class. * @return object Returns an casted object with registered endpoint data. * @throws \Exception If a registered callback throws an error, or silently logs it. */ public function get_endpoint_data( $endpoint, array $passed_args = [] ) { $registered_data = []; if ( isset( $this->extend_data[ $endpoint ] ) ) { foreach ( $this->extend_data[ $endpoint ] as $namespace => $callbacks ) { if ( is_null( $callbacks['data_callback'] ) ) { continue; } try { $data = $callbacks['data_callback']( ...$passed_args ); if ( ! is_array( $data ) ) { $data = []; throw new \Exception( '$data_callback must return an array.' ); } } catch ( \Throwable $e ) { $this->throw_exception( $e ); } $registered_data[ $namespace ] = $data; } } return (object) $registered_data; } /** * Returns the registered endpoint schema * * @param string $endpoint A valid identifier. * @param array $passed_args Passed arguments from the Schema class. * @return object Returns an array with registered schema data. * @throws \Exception If a registered callback throws an error, or silently logs it. */ public function get_endpoint_schema( $endpoint, array $passed_args = [] ) { $registered_schema = []; if ( isset( $this->extend_data[ $endpoint ] ) ) { foreach ( $this->extend_data[ $endpoint ] as $namespace => $callbacks ) { if ( is_null( $callbacks['schema_callback'] ) ) { continue; } try { $schema = $callbacks['schema_callback']( ...$passed_args ); if ( ! is_array( $schema ) ) { $schema = []; throw new \Exception( '$schema_callback must return an array.' ); } } catch ( \Throwable $e ) { $this->throw_exception( $e ); } $registered_schema[ $namespace ] = $this->format_extensions_properties( $namespace, $schema, $callbacks['schema_type'] ); } } return (object) $registered_schema; } /** * Returns the additional payment requirements for the cart which are required to make payments. Values listed here * are compared against each Payment Gateways "supports" flag. * * @param array $requirements list of requirements that should be added to the collected requirements. * @return array Returns a list of payment requirements. * @throws \Exception If a registered callback throws an error, or silently logs it. */ public function get_payment_requirements( array $requirements = [ 'products' ] ) { if ( ! empty( $this->payment_requirements ) ) { foreach ( $this->payment_requirements as $callback ) { try { $data = $callback(); if ( ! is_array( $data ) ) { throw new \Exception( '$data_callback must return an array.' ); } $requirements = array_unique( array_merge( $requirements, $data ) ); } catch ( \Throwable $e ) { $this->throw_exception( $e ); } } } return $requirements; } /** * Throws error and/or silently logs it. * * @param string|\Throwable $exception_or_error Error message or \Exception. * @throws \Exception An error to throw if we have debug enabled and user is admin. */ private function throw_exception( $exception_or_error ) { $exception = is_string( $exception_or_error ) ? new \Exception( $exception_or_error ) : $exception_or_error; wc_caught_exception( $exception ); if ( defined( 'WP_DEBUG' ) && WP_DEBUG && current_user_can( 'manage_woocommerce' ) ) { throw $exception; } } /** * Format schema for an extension. * * @param string $namespace Error message or \Exception. * @param array $schema An error to throw if we have debug enabled and user is admin. * @param string $schema_type How should data be shaped. * @return array Formatted schema. */ private function format_extensions_properties( $namespace, $schema, $schema_type ) { if ( ARRAY_N === $schema_type ) { return [ /* translators: %s: extension namespace */ 'description' => sprintf( __( 'Extension data registered by %s', 'woocommerce' ), $namespace ), 'type' => [ 'array', 'null' ], 'context' => [ 'view', 'edit' ], 'items' => $schema, ]; } return [ /* translators: %s: extension namespace */ 'description' => sprintf( __( 'Extension data registered by %s', 'woocommerce' ), $namespace ), 'type' => [ 'object', 'null' ], 'context' => [ 'view', 'edit' ], 'properties' => $schema, ]; } } V1/CartFeeSchema.php 0000644 00000004171 15073235741 0010207 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; /** * CartFeeSchema class. */ class CartFeeSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'cart_fee'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'cart-fee'; /** * Cart schema properties. * * @return array */ public function get_properties() { return [ 'id' => [ 'description' => __( 'Unique identifier for the fee within the cart.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Fee name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'totals' => [ 'description' => __( 'Fee total amounts provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'total' => [ 'description' => __( 'Total amount for this fee.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_tax' => [ 'description' => __( 'Total tax amount for this fee.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ] ), ], ]; } /** * Convert a WooCommerce cart fee to an object suitable for the response. * * @param array $fee Cart fee data. * @return array */ public function get_item_response( $fee ) { return [ 'key' => $fee->id, 'name' => $this->prepare_html_response( $fee->name ), 'totals' => (object) $this->prepare_currency_response( [ 'total' => $this->prepare_money_response( $fee->total, wc_get_price_decimals() ), 'total_tax' => $this->prepare_money_response( $fee->tax, wc_get_price_decimals(), PHP_ROUND_HALF_DOWN ), ] ), ]; } } V1/CartCouponSchema.php 0000644 00000006304 15073235741 0010753 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\Utilities\CartController; /** * CartCouponSchema class. */ class CartCouponSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'cart_coupon'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'cart-coupon'; /** * Cart schema properties. * * @return array */ public function get_properties() { return [ 'code' => [ 'description' => __( 'The coupon\'s unique code.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'arg_options' => [ 'sanitize_callback' => 'wc_format_coupon_code', 'validate_callback' => [ $this, 'coupon_exists' ], ], ], 'discount_type' => [ 'description' => __( 'The discount type for the coupon (e.g. percentage or fixed amount)', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'arg_options' => [ 'validate_callback' => [ $this, 'coupon_exists' ], ], ], 'totals' => [ 'description' => __( 'Total amounts provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'total_discount' => [ 'description' => __( 'Total discount applied by this coupon.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_discount_tax' => [ 'description' => __( 'Total tax removed due to discount applied by this coupon.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ] ), ], ]; } /** * Check given coupon exists. * * @param string $coupon_code Coupon code. * @return bool */ public function coupon_exists( $coupon_code ) { $coupon = new \WC_Coupon( $coupon_code ); return (bool) $coupon->get_id() || $coupon->get_virtual(); } /** * Generate a response from passed coupon code. * * @param string $coupon_code Coupon code from the cart. * @return array */ public function get_item_response( $coupon_code ) { $controller = new CartController(); $cart = $controller->get_cart_instance(); $total_discounts = $cart->get_coupon_discount_totals(); $total_discount_taxes = $cart->get_coupon_discount_tax_totals(); $coupon = new \WC_Coupon( $coupon_code ); return [ 'code' => $coupon_code, 'discount_type' => $coupon->get_discount_type(), 'totals' => (object) $this->prepare_currency_response( [ 'total_discount' => $this->prepare_money_response( isset( $total_discounts[ $coupon_code ] ) ? $total_discounts[ $coupon_code ] : 0, wc_get_price_decimals() ), 'total_discount_tax' => $this->prepare_money_response( isset( $total_discount_taxes[ $coupon_code ] ) ? $total_discount_taxes[ $coupon_code ] : 0, wc_get_price_decimals(), PHP_ROUND_HALF_DOWN ), ] ), ]; } } V1/ProductSchema.php 0000644 00000073027 15073235741 0010324 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\SchemaController; use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema; use Automattic\WooCommerce\StoreApi\Utilities\QuantityLimits; /** * ProductSchema class. */ class ProductSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'product'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'product'; /** * Image attachment schema instance. * * @var ImageAttachmentSchema */ protected $image_attachment_schema; /** * Constructor. * * @param ExtendSchema $extend Rest Extending instance. * @param SchemaController $controller Schema Controller instance. */ public function __construct( ExtendSchema $extend, SchemaController $controller ) { parent::__construct( $extend, $controller ); $this->image_attachment_schema = $this->controller->get( ImageAttachmentSchema::IDENTIFIER ); } /** * Product schema properties. * * @return array */ public function get_properties() { return [ 'id' => [ 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Product name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'slug' => [ 'description' => __( 'Product slug.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'parent' => [ 'description' => __( 'ID of the parent product, if applicable.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'type' => [ 'description' => __( 'Product type.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'variation' => [ 'description' => __( 'Product variation attributes, if applicable.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'permalink' => [ 'description' => __( 'Product URL.', 'woocommerce' ), 'type' => 'string', 'format' => 'uri', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'short_description' => [ 'description' => __( 'Product short description in HTML format.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'description' => [ 'description' => __( 'Product full description in HTML format.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'on_sale' => [ 'description' => __( 'Is the product on sale?', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sku' => [ 'description' => __( 'Unique identifier.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'prices' => [ 'description' => __( 'Price data provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'price' => [ 'description' => __( 'Current product price.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'regular_price' => [ 'description' => __( 'Regular product price.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sale_price' => [ 'description' => __( 'Sale product price, if applicable.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'price_range' => [ 'description' => __( 'Price range, if applicable.', 'woocommerce' ), 'type' => [ 'object', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ 'min_amount' => [ 'description' => __( 'Price amount.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'max_amount' => [ 'description' => __( 'Price amount.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ] ), ], 'price_html' => array( 'description' => __( 'Price string formatted as HTML.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'average_rating' => [ 'description' => __( 'Reviews average rating.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'review_count' => [ 'description' => __( 'Amount of reviews that the product has.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'images' => [ 'description' => __( 'List of images.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => $this->image_attachment_schema->get_properties(), ], ], 'categories' => [ 'description' => __( 'List of categories, if applicable.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'description' => __( 'Category ID', 'woocommerce' ), 'type' => 'number', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Category name', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'slug' => [ 'description' => __( 'Category slug', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'link' => [ 'description' => __( 'Category link', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'tags' => [ 'description' => __( 'List of tags, if applicable.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'description' => __( 'Tag ID', 'woocommerce' ), 'type' => 'number', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Tag name', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'slug' => [ 'description' => __( 'Tag slug', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'link' => [ 'description' => __( 'Tag link', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'attributes' => [ 'description' => __( 'List of attributes (taxonomy terms) assigned to the product. For variable products, these are mapped to variations (see the `variations` field).', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'description' => __( 'The attribute ID, or 0 if the attribute is not taxonomy based.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'The attribute name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'taxonomy' => [ 'description' => __( 'The attribute taxonomy, or null if the attribute is not taxonomy based.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'has_variations' => [ 'description' => __( 'True if this attribute is used by product variations.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'terms' => [ 'description' => __( 'List of assigned attribute terms.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'description' => __( 'The term ID, or 0 if the attribute is not a global attribute.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'The term name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'slug' => [ 'description' => __( 'The term slug.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'default' => [ 'description' => __( 'If this is a default attribute', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], ], ], ], 'variations' => [ 'description' => __( 'List of variation IDs, if applicable.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'description' => __( 'The attribute ID, or 0 if the attribute is not taxonomy based.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'attributes' => [ 'description' => __( 'List of variation attributes.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'name' => [ 'description' => __( 'The attribute name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'value' => [ 'description' => __( 'The assigned attribute.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], ], ], ], 'has_options' => [ 'description' => __( 'Does the product have additional options before it can be added to the cart?', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'is_purchasable' => [ 'description' => __( 'Is the product purchasable?', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'is_in_stock' => [ 'description' => __( 'Is the product in stock?', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'is_on_backorder' => [ 'description' => __( 'Is the product stock backordered? This will also return false if backorder notifications are turned off.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'low_stock_remaining' => [ 'description' => __( 'Quantity left in stock if stock is low, or null if not applicable.', 'woocommerce' ), 'type' => [ 'integer', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sold_individually' => [ 'description' => __( 'If true, only one item of this product is allowed for purchase in a single order.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'add_to_cart' => [ 'description' => __( 'Add to cart button parameters.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ 'text' => [ 'description' => __( 'Button text.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'description' => [ 'description' => __( 'Button description.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'url' => [ 'description' => __( 'Add to cart URL.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'minimum' => [ 'description' => __( 'The minimum quantity that can be added to the cart.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'maximum' => [ 'description' => __( 'The maximum quantity that can be added to the cart.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'multiple_of' => [ 'description' => __( 'The amount that quantities increment by. Quantity must be an multiple of this value.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'default' => 1, ], ], ], self::EXTENDING_KEY => $this->get_extended_schema( self::IDENTIFIER ), ]; } /** * Convert a WooCommerce product into an object suitable for the response. * * @param \WC_Product $product Product instance. * @return array */ public function get_item_response( $product ) { return [ 'id' => $product->get_id(), 'name' => $this->prepare_html_response( $product->get_title() ), 'slug' => $product->get_slug(), 'parent' => $product->get_parent_id(), 'type' => $product->get_type(), 'variation' => $this->prepare_html_response( $product->is_type( 'variation' ) ? wc_get_formatted_variation( $product, true, true, false ) : '' ), 'permalink' => $product->get_permalink(), 'sku' => $this->prepare_html_response( $product->get_sku() ), 'short_description' => $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_short_description() ) ) ), 'description' => $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_description() ) ) ), 'on_sale' => $product->is_on_sale(), 'prices' => (object) $this->prepare_product_price_response( $product ), 'price_html' => $this->prepare_html_response( $product->get_price_html() ), 'average_rating' => (string) $product->get_average_rating(), 'review_count' => $product->get_review_count(), 'images' => $this->get_images( $product ), 'categories' => $this->get_term_list( $product, 'product_cat' ), 'tags' => $this->get_term_list( $product, 'product_tag' ), 'attributes' => $this->get_attributes( $product ), 'variations' => $this->get_variations( $product ), 'has_options' => $product->has_options(), 'is_purchasable' => $product->is_purchasable(), 'is_in_stock' => $product->is_in_stock(), 'is_on_backorder' => 'onbackorder' === $product->get_stock_status(), 'low_stock_remaining' => $this->get_low_stock_remaining( $product ), 'sold_individually' => $product->is_sold_individually(), 'add_to_cart' => (object) array_merge( [ 'text' => $this->prepare_html_response( $product->add_to_cart_text() ), 'description' => $this->prepare_html_response( $product->add_to_cart_description() ), 'url' => $this->prepare_html_response( $product->add_to_cart_url() ), ], ( new QuantityLimits() )->get_add_to_cart_limits( $product ) ), self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER, $product ), ]; } /** * Get list of product images. * * @param \WC_Product $product Product instance. * @return array */ protected function get_images( \WC_Product $product ) { $attachment_ids = array_merge( [ $product->get_image_id() ], $product->get_gallery_image_ids() ); return array_values( array_filter( array_map( [ $this->image_attachment_schema, 'get_item_response' ], $attachment_ids ) ) ); } /** * Gets remaining stock amount for a product. * * @param \WC_Product $product Product instance. * @return integer|null */ protected function get_remaining_stock( \WC_Product $product ) { if ( is_null( $product->get_stock_quantity() ) ) { return null; } return $product->get_stock_quantity(); } /** * If a product has low stock, return the remaining stock amount for display. * * @param \WC_Product $product Product instance. * @return integer|null */ protected function get_low_stock_remaining( \WC_Product $product ) { $remaining_stock = $this->get_remaining_stock( $product ); $stock_format = get_option( 'woocommerce_stock_format' ); // Don't show the low stock badge if the settings doesn't allow it. if ( 'no_amount' === $stock_format ) { return null; } // Show the low stock badge if the remaining stock is below or equal to the threshold. if ( ! is_null( $remaining_stock ) && $remaining_stock <= wc_get_low_stock_amount( $product ) ) { return max( $remaining_stock, 0 ); } return null; } /** * Returns true if the given attribute is valid. * * @param mixed $attribute Object or variable to check. * @return boolean */ protected function filter_valid_attribute( $attribute ) { return is_a( $attribute, '\WC_Product_Attribute' ); } /** * Returns true if the given attribute is valid and used for variations. * * @param mixed $attribute Object or variable to check. * @return boolean */ protected function filter_variation_attribute( $attribute ) { return $this->filter_valid_attribute( $attribute ) && $attribute->get_variation(); } /** * Get variation IDs and attributes from the DB. * * @param \WC_Product $product Product instance. * @returns array */ protected function get_variations( \WC_Product $product ) { $variation_ids = $product->is_type( 'variable' ) ? $product->get_visible_children() : []; if ( ! count( $variation_ids ) ) { return []; } /** * Gets default variation data which applies to all of this products variations. */ $attributes = array_filter( $product->get_attributes(), [ $this, 'filter_variation_attribute' ] ); $default_variation_meta_data = array_reduce( $attributes, function( $defaults, $attribute ) use ( $product ) { $meta_key = wc_variation_attribute_name( $attribute->get_name() ); $defaults[ $meta_key ] = [ 'name' => wc_attribute_label( $attribute->get_name(), $product ), 'value' => null, ]; return $defaults; }, [] ); $default_variation_meta_keys = array_keys( $default_variation_meta_data ); /** * Gets individual variation data from the database, using cache where possible. */ $cache_group = 'product_variation_meta_data'; $cache_value = wp_cache_get( $product->get_id(), $cache_group ); $last_modified = get_the_modified_date( 'U', $product->get_id() ); if ( false === $cache_value || $last_modified !== $cache_value['last_modified'] ) { global $wpdb; // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared $variation_meta_data = $wpdb->get_results( " SELECT post_id as variation_id, meta_key as attribute_key, meta_value as attribute_value FROM {$wpdb->postmeta} WHERE post_id IN (" . implode( ',', array_map( 'esc_sql', $variation_ids ) ) . ") AND meta_key IN ('" . implode( "','", array_map( 'esc_sql', $default_variation_meta_keys ) ) . "') " ); // phpcs:enable wp_cache_set( $product->get_id(), [ 'last_modified' => $last_modified, 'data' => $variation_meta_data, ], $cache_group ); } else { $variation_meta_data = $cache_value['data']; } /** * Merges and formats default variation data with individual variation data. */ $attributes_by_variation = array_reduce( $variation_meta_data, function( $values, $data ) use ( $default_variation_meta_keys ) { // The query above only includes the keys of $default_variation_meta_data so we know all of the attributes // being processed here apply to this product. However, we need an additional check here because the // cache may have been primed elsewhere and include keys from other products. // @see AbstractProductGrid::prime_product_variations. if ( in_array( $data->attribute_key, $default_variation_meta_keys, true ) ) { $values[ $data->variation_id ][ $data->attribute_key ] = $data->attribute_value; } return $values; }, array_fill_keys( $variation_ids, [] ) ); $variations = []; foreach ( $variation_ids as $variation_id ) { $attribute_data = $default_variation_meta_data; foreach ( $attributes_by_variation[ $variation_id ] as $meta_key => $meta_value ) { if ( '' !== $meta_value ) { $attribute_data[ $meta_key ]['value'] = $meta_value; } } $variations[] = (object) [ 'id' => $variation_id, 'attributes' => array_values( $attribute_data ), ]; } return $variations; } /** * Get list of product attributes and attribute terms. * * @param \WC_Product $product Product instance. * @return array */ protected function get_attributes( \WC_Product $product ) { $attributes = array_filter( $product->get_attributes(), [ $this, 'filter_valid_attribute' ] ); $default_attributes = $product->get_default_attributes(); $return = []; foreach ( $attributes as $attribute_slug => $attribute ) { // Only visible or variation attributes will be exposed by this API. if ( ! $attribute->get_visible() && ! $attribute->get_variation() ) { continue; } $terms = $attribute->is_taxonomy() ? array_map( [ $this, 'prepare_product_attribute_taxonomy_value' ], $attribute->get_terms() ) : array_map( [ $this, 'prepare_product_attribute_value' ], $attribute->get_options() ); // Custom attribute names are sanitized to be the array keys. // So when we do the array_key_exists check below we also need to sanitize the attribute names. $sanitized_attribute_name = sanitize_key( $attribute->get_name() ); if ( array_key_exists( $sanitized_attribute_name, $default_attributes ) ) { foreach ( $terms as $term ) { $term->default = $term->slug === $default_attributes[ $sanitized_attribute_name ]; } } $return[] = (object) [ 'id' => $attribute->get_id(), 'name' => wc_attribute_label( $attribute->get_name(), $product ), 'taxonomy' => $attribute->is_taxonomy() ? $attribute->get_name() : null, 'has_variations' => true === $attribute->get_variation(), 'terms' => $terms, ]; } return $return; } /** * Prepare an attribute term for the response. * * @param \WP_Term $term Term object. * @return object */ protected function prepare_product_attribute_taxonomy_value( \WP_Term $term ) { return $this->prepare_product_attribute_value( $term->name, $term->term_id, $term->slug ); } /** * Prepare an attribute term for the response. * * @param string $name Attribute term name. * @param int $id Attribute term ID. * @param string $slug Attribute term slug. * @return object */ protected function prepare_product_attribute_value( $name, $id = 0, $slug = '' ) { return (object) [ 'id' => (int) $id, 'name' => $name, 'slug' => $slug ? $slug : $name, ]; } /** * Get an array of pricing data. * * @param \WC_Product $product Product instance. * @param string $tax_display_mode If returned prices are incl or excl of tax. * @return array */ protected function prepare_product_price_response( \WC_Product $product, $tax_display_mode = '' ) { $prices = []; $tax_display_mode = $this->get_tax_display_mode( $tax_display_mode ); $price_function = $this->get_price_function_from_tax_display_mode( $tax_display_mode ); // If we have a variable product, get the price from the variations (this will use the min value). if ( $product->is_type( 'variable' ) ) { $regular_price = $product->get_variation_regular_price(); $sale_price = $product->get_variation_sale_price(); } else { $regular_price = $product->get_regular_price(); $sale_price = $product->get_sale_price(); } $prices['price'] = $this->prepare_money_response( $price_function( $product ), wc_get_price_decimals() ); $prices['regular_price'] = $this->prepare_money_response( $price_function( $product, [ 'price' => $regular_price ] ), wc_get_price_decimals() ); $prices['sale_price'] = $this->prepare_money_response( $price_function( $product, [ 'price' => $sale_price ] ), wc_get_price_decimals() ); $prices['price_range'] = $this->get_price_range( $product, $tax_display_mode ); return $this->prepare_currency_response( $prices ); } /** * WooCommerce can return prices including or excluding tax; choose the correct method based on tax display mode. * * @param string $tax_display_mode Provided tax display mode. * @return string Valid tax display mode. */ protected function get_tax_display_mode( $tax_display_mode = '' ) { return in_array( $tax_display_mode, [ 'incl', 'excl' ], true ) ? $tax_display_mode : get_option( 'woocommerce_tax_display_shop' ); } /** * WooCommerce can return prices including or excluding tax; choose the correct method based on tax display mode. * * @param string $tax_display_mode If returned prices are incl or excl of tax. * @return string Function name. */ protected function get_price_function_from_tax_display_mode( $tax_display_mode ) { return 'incl' === $tax_display_mode ? 'wc_get_price_including_tax' : 'wc_get_price_excluding_tax'; } /** * Get price range from certain product types. * * @param \WC_Product $product Product instance. * @param string $tax_display_mode If returned prices are incl or excl of tax. * @return object|null */ protected function get_price_range( \WC_Product $product, $tax_display_mode = '' ) { $tax_display_mode = $this->get_tax_display_mode( $tax_display_mode ); if ( $product->is_type( 'variable' ) ) { $prices = $product->get_variation_prices( true ); if ( ! empty( $prices['price'] ) && ( min( $prices['price'] ) !== max( $prices['price'] ) ) ) { return (object) [ 'min_amount' => $this->prepare_money_response( min( $prices['price'] ), wc_get_price_decimals() ), 'max_amount' => $this->prepare_money_response( max( $prices['price'] ), wc_get_price_decimals() ), ]; } } if ( $product->is_type( 'grouped' ) ) { $children = array_filter( array_map( 'wc_get_product', $product->get_children() ), 'wc_products_array_filter_visible_grouped' ); $price_function = 'incl' === $tax_display_mode ? 'wc_get_price_including_tax' : 'wc_get_price_excluding_tax'; foreach ( $children as $child ) { if ( '' !== $child->get_price() ) { $child_prices[] = $price_function( $child ); } } if ( ! empty( $child_prices ) ) { return (object) [ 'min_amount' => $this->prepare_money_response( min( $child_prices ), wc_get_price_decimals() ), 'max_amount' => $this->prepare_money_response( max( $child_prices ), wc_get_price_decimals() ), ]; } } return null; } /** * Returns a list of terms assigned to the product. * * @param \WC_Product $product Product object. * @param string $taxonomy Taxonomy name. * @return array Array of terms (id, name, slug). */ protected function get_term_list( \WC_Product $product, $taxonomy = '' ) { if ( ! $taxonomy ) { return []; } $terms = get_the_terms( $product->get_id(), $taxonomy ); if ( ! $terms || is_wp_error( $terms ) ) { return []; } $return = []; $default_category = (int) get_option( 'default_product_cat', 0 ); foreach ( $terms as $term ) { $link = get_term_link( $term, $taxonomy ); if ( is_wp_error( $link ) ) { continue; } if ( $term->term_id === $default_category ) { continue; } $return[] = (object) [ 'id' => $term->term_id, 'name' => $term->name, 'slug' => $term->slug, 'link' => $link, ]; } return $return; } } V1/BillingAddressSchema.php 0000644 00000012235 15073235741 0011564 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils; /** * BillingAddressSchema class. * * Provides a generic billing address schema for composition in other schemas. */ class BillingAddressSchema extends AbstractAddressSchema { /** * The schema item name. * * @var string */ protected $title = 'billing_address'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'billing-address'; /** * Term properties. * * @return array */ public function get_properties() { $properties = parent::get_properties(); return array_merge( $properties, [ 'email' => [ 'description' => __( 'Email', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], ] ); } /** * Sanitize and format the given address object. * * @param array $address Value being sanitized. * @param \WP_REST_Request $request The Request. * @param string $param The param being sanitized. * @return array */ public function sanitize_callback( $address, $request, $param ) { $address = parent::sanitize_callback( $address, $request, $param ); $address['email'] = sanitize_text_field( wp_unslash( $address['email'] ) ); return $address; } /** * Validate the given address object. * * @param array $address Value being sanitized. * @param \WP_REST_Request $request The Request. * @param string $param The param being sanitized. * @return true|\WP_Error */ public function validate_callback( $address, $request, $param ) { $errors = parent::validate_callback( $address, $request, $param ); $address = $this->sanitize_callback( $address, $request, $param ); $errors = is_wp_error( $errors ) ? $errors : new \WP_Error(); if ( ! empty( $address['email'] ) && ! is_email( $address['email'] ) ) { $errors->add( 'invalid_email', __( 'The provided email address is not valid', 'woocommerce' ) ); } return $errors->has_errors( $errors ) ? $errors : true; } /** * Convert a term object into an object suitable for the response. * * @param \WC_Order|\WC_Customer $address An object with billing address. * * @throws RouteException When the invalid object types are provided. * @return array */ public function get_item_response( $address ) { $validation_util = new ValidationUtils(); if ( ( $address instanceof \WC_Customer || $address instanceof \WC_Order ) ) { $billing_country = $address->get_billing_country(); $billing_state = $address->get_billing_state(); if ( ! $validation_util->validate_state( $billing_state, $billing_country ) ) { $billing_state = ''; } if ( $address instanceof \WC_Order ) { // get additional fields from order. $additional_address_fields = $this->additional_fields_controller->get_all_fields_from_order( $address ); } elseif ( $address instanceof \WC_Customer ) { // get additional fields from customer. $additional_address_fields = $this->additional_fields_controller->get_all_fields_from_customer( $address ); } $additional_address_fields = array_reduce( array_keys( $additional_address_fields ), function( $carry, $key ) use ( $additional_address_fields ) { if ( 0 === strpos( $key, '/billing/' ) ) { $value = $additional_address_fields[ $key ]; $key = str_replace( '/billing/', '', $key ); $carry[ $key ] = $value; } return $carry; }, [] ); $address_object = \array_merge( [ 'first_name' => $address->get_billing_first_name(), 'last_name' => $address->get_billing_last_name(), 'company' => $address->get_billing_company(), 'address_1' => $address->get_billing_address_1(), 'address_2' => $address->get_billing_address_2(), 'city' => $address->get_billing_city(), 'state' => $billing_state, 'postcode' => $address->get_billing_postcode(), 'country' => $billing_country, 'email' => $address->get_billing_email(), 'phone' => $address->get_billing_phone(), ], $additional_address_fields ); // Add any missing keys from additional_fields_controller to the address response. foreach ( $this->additional_fields_controller->get_address_fields_keys() as $field ) { if ( isset( $address_object[ $field ] ) ) { continue; } $address_object[ $field ] = ''; } foreach ( $address_object as $key => $value ) { if ( isset( $this->get_properties()[ $key ]['type'] ) && 'boolean' === $this->get_properties()[ $key ]['type'] ) { $address_object[ $key ] = (bool) $value; } else { $address_object[ $key ] = $this->prepare_html_response( $value ); } } return $address_object; } throw new RouteException( 'invalid_object_type', sprintf( /* translators: Placeholders are class and method names */ __( '%1$s requires an instance of %2$s or %3$s for the address', 'woocommerce' ), 'BillingAddressSchema::get_item_response', 'WC_Customer', 'WC_Order' ), 500 ); } } V1/AbstractSchema.php 0000644 00000031527 15073235741 0010446 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\SchemaController; use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema; use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields; use Automattic\WooCommerce\Blocks\Package; /** * AbstractSchema class. * * For REST Route Schemas */ abstract class AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'Schema'; /** * Rest extend instance. * * @var ExtendSchema */ protected $extend; /** * Schema Controller instance. * * @var SchemaController */ protected $controller; /** * Extending key that gets added to endpoint. * * @var string */ const EXTENDING_KEY = 'extensions'; /** * Constructor. * * @param ExtendSchema $extend Rest Extending instance. * @param SchemaController $controller Schema Controller instance. */ public function __construct( ExtendSchema $extend, SchemaController $controller ) { $this->extend = $extend; $this->controller = $controller; } /** * Returns the full item schema. * * @return array */ public function get_item_schema() { return array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => $this->title, 'type' => 'object', 'properties' => $this->get_properties(), ); } /** * Returns the full item response. * * @param mixed $item Item to get response for. * @return array|stdClass */ public function get_item_response( $item ) { return []; } /** * Return schema properties. * * @return array */ abstract public function get_properties(); /** * Recursive removal of arg_options. * * @param array $properties Schema properties. */ protected function remove_arg_options( $properties ) { return array_map( function( $property ) { if ( isset( $property['properties'] ) ) { $property['properties'] = $this->remove_arg_options( $property['properties'] ); } elseif ( isset( $property['items']['properties'] ) ) { $property['items']['properties'] = $this->remove_arg_options( $property['items']['properties'] ); } unset( $property['arg_options'] ); return $property; }, (array) $properties ); } /** * Returns the public schema. * * @return array */ public function get_public_item_schema() { $schema = $this->get_item_schema(); if ( isset( $schema['properties'] ) ) { $schema['properties'] = $this->remove_arg_options( $schema['properties'] ); } return $schema; } /** * Returns extended data for a specific endpoint. * * @param string $endpoint The endpoint identifier. * @param array ...$passed_args An array of arguments to be passed to callbacks. * @return object the data that will get added. */ protected function get_extended_data( $endpoint, ...$passed_args ) { return $this->extend->get_endpoint_data( $endpoint, $passed_args ); } /** * Gets an array of schema defaults recursively. * * @param array $properties Schema property data. * @return array Array of defaults, pulled from arg_options */ protected function get_recursive_schema_property_defaults( $properties ) { $defaults = []; foreach ( $properties as $property_key => $property_value ) { if ( isset( $property_value['arg_options']['default'] ) ) { $defaults[ $property_key ] = $property_value['arg_options']['default']; } elseif ( isset( $property_value['properties'] ) ) { $defaults[ $property_key ] = $this->get_recursive_schema_property_defaults( $property_value['properties'] ); } } return $defaults; } /** * Gets a function that validates recursively. * * @param array $properties Schema property data. * @return function Anonymous validation callback. */ protected function get_recursive_validate_callback( $properties ) { /** * Validate a request argument based on details registered to the route. * * @param mixed $values * @param \WP_REST_Request $request * @param string $param * @return true|\WP_Error */ return function ( $values, $request, $param ) use ( $properties ) { foreach ( $properties as $property_key => $property_value ) { $current_value = isset( $values[ $property_key ] ) ? $values[ $property_key ] : null; $property_type = is_array( $property_value['type'] ) ? $property_value['type'] : [ $property_value['type'] ]; if ( empty( $current_value ) && in_array( 'null', $property_type, true ) ) { // If the value is null and the schema allows null, we can skip validation for children. continue; } if ( isset( $property_value['arg_options']['validate_callback'] ) ) { $callback = $property_value['arg_options']['validate_callback']; $result = is_callable( $callback ) ? $callback( $current_value, $request, $param ) : false; } else { $result = rest_validate_value_from_schema( $current_value, $property_value, $param . ' > ' . $property_key ); } if ( ! $result || is_wp_error( $result ) ) { // If schema validation fails, we return here as we don't need to validate any deeper. return $result; } if ( isset( $property_value['properties'] ) ) { $validate_callback = $this->get_recursive_validate_callback( $property_value['properties'] ); $result = $validate_callback( $current_value, $request, $param . ' > ' . $property_key ); if ( ! $result || is_wp_error( $result ) ) { // If schema validation fails, we return here as we don't need to validate any deeper. return $result; } } } return true; }; } /** * Gets a function that sanitizes recursively. * * @param array $properties Schema property data. * @return function Anonymous validation callback. */ protected function get_recursive_sanitize_callback( $properties ) { /** * Validate a request argument based on details registered to the route. * * @param mixed $values * @param \WP_REST_Request $request * @param string $param * @return true|\WP_Error */ return function ( $values, $request, $param ) use ( $properties ) { $sanitized_values = []; foreach ( $properties as $property_key => $property_value ) { $current_value = isset( $values[ $property_key ] ) ? $values[ $property_key ] : null; if ( isset( $property_value['arg_options']['sanitize_callback'] ) ) { $callback = $property_value['arg_options']['sanitize_callback']; $current_value = is_callable( $callback ) ? $callback( $current_value, $request, $param ) : $current_value; } else { $current_value = rest_sanitize_value_from_schema( $current_value, $property_value, $param . ' > ' . $property_key ); } // If sanitization failed, return the WP_Error object straight away. if ( is_wp_error( $current_value ) ) { return $current_value; } if ( isset( $property_value['properties'] ) ) { $sanitize_callback = $this->get_recursive_sanitize_callback( $property_value['properties'] ); $sanitized_values[ $property_key ] = $sanitize_callback( $current_value, $request, $param . ' > ' . $property_key ); } else { $sanitized_values[ $property_key ] = $current_value; } } return $sanitized_values; }; } /** * Returns extended schema for a specific endpoint. * * @param string $endpoint The endpoint identifer. * @param array ...$passed_args An array of arguments to be passed to callbacks. * @return array the data that will get added. */ protected function get_extended_schema( $endpoint, ...$passed_args ) { $extended_schema = $this->extend->get_endpoint_schema( $endpoint, $passed_args ); $defaults = $this->get_recursive_schema_property_defaults( $extended_schema ); return [ 'type' => 'object', 'context' => [ 'view', 'edit' ], 'arg_options' => [ 'default' => $defaults, 'validate_callback' => $this->get_recursive_validate_callback( $extended_schema ), 'sanitize_callback' => $this->get_recursive_sanitize_callback( $extended_schema ), ], 'properties' => $extended_schema, ]; } /** * Apply a schema get_item_response callback to an array of items and return the result. * * @param AbstractSchema $schema Schema class instance. * @param array $items Array of items. * @return array Array of values from the callback function. */ protected function get_item_responses_from_schema( AbstractSchema $schema, $items ) { $items = array_filter( $items ); if ( empty( $items ) ) { return []; } return array_values( array_map( [ $schema, 'get_item_response' ], $items ) ); } /** * Retrieves an array of endpoint arguments from the item schema for the controller. * * @uses rest_get_endpoint_args_for_schema() * @param string $method Optional. HTTP method of the request. * @return array Endpoint arguments. */ public function get_endpoint_args_for_item_schema( $method = \WP_REST_Server::CREATABLE ) { $schema = $this->get_item_schema(); $endpoint_args = rest_get_endpoint_args_for_schema( $schema, $method ); $endpoint_args = $this->remove_arg_options( $endpoint_args ); return $endpoint_args; } /** * Force all schema properties to be readonly. * * @param array $properties Schema. * @return array Updated schema. */ protected function force_schema_readonly( $properties ) { return array_map( function( $property ) { $property['readonly'] = true; if ( isset( $property['items']['properties'] ) ) { $property['items']['properties'] = $this->force_schema_readonly( $property['items']['properties'] ); } return $property; }, (array) $properties ); } /** * Returns consistent currency schema used across endpoints for prices. * * @return array */ protected function get_store_currency_properties() { return [ 'currency_code' => [ 'description' => __( 'Currency code (in ISO format) for returned prices.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'currency_symbol' => [ 'description' => __( 'Currency symbol for the currency which can be used to format returned prices.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'currency_minor_unit' => [ 'description' => __( 'Currency minor unit (number of digits after the decimal separator) for returned prices.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'currency_decimal_separator' => array( 'description' => __( 'Decimal separator for the currency which can be used to format returned prices.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'currency_thousand_separator' => array( 'description' => __( 'Thousand separator for the currency which can be used to format returned prices.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'currency_prefix' => array( 'description' => __( 'Price prefix for the currency which can be used to format returned prices.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'currency_suffix' => array( 'description' => __( 'Price prefix for the currency which can be used to format returned prices.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ]; } /** * Adds currency data to an array of monetary values. * * @param array $values Monetary amounts. * @return array Monetary amounts with currency data appended. */ protected function prepare_currency_response( $values ) { return $this->extend->get_formatter( 'currency' )->format( $values ); } /** * Convert monetary values from WooCommerce to string based integers, using * the smallest unit of a currency. * * @param string|float $amount Monetary amount with decimals. * @param int $decimals Number of decimals the amount is formatted with. * @param int $rounding_mode Defaults to the PHP_ROUND_HALF_UP constant. * @return string The new amount. */ protected function prepare_money_response( $amount, $decimals = 2, $rounding_mode = PHP_ROUND_HALF_UP ) { return $this->extend->get_formatter( 'money' )->format( $amount, [ 'decimals' => $decimals, 'rounding_mode' => $rounding_mode, ] ); } /** * Prepares HTML based content, such as post titles and content, for the API response. * * @param string|array $response Data to format. * @return string|array Formatted data. */ protected function prepare_html_response( $response ) { return $this->extend->get_formatter( 'html' )->format( $response ); } } V1/CartExtensionsSchema.php 0000644 00000004025 15073235741 0011645 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; use Automattic\WooCommerce\StoreApi\SchemaController; use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema; use Automattic\WooCommerce\StoreApi\Utilities\CartController; /** * Class CartExtensionsSchema */ class CartExtensionsSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'cart-extensions'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'cart-extensions'; /** * Cart schema instance. * * @var CartSchema */ public $cart_schema; /** * Constructor. * * @param ExtendSchema $extend Rest Extending instance. * @param SchemaController $controller Schema Controller instance. */ public function __construct( ExtendSchema $extend, SchemaController $controller ) { parent::__construct( $extend, $controller ); $this->cart_schema = $this->controller->get( CartSchema::IDENTIFIER ); } /** * Cart extensions schema properties. * * @return array */ public function get_properties() { return []; } /** * Handle the request and return a valid response for this endpoint. * * @param \WP_REST_Request $request Request containing data for the extension callback. * @throws RouteException When callback is not callable or parameters are incorrect. * * @return array */ public function get_item_response( $request = null ) { try { $callback = $this->extend->get_update_callback( $request['namespace'] ); } catch ( \Exception $e ) { throw new RouteException( 'woocommerce_rest_cart_extensions_error', $e->getMessage(), 400 ); } $controller = new CartController(); if ( is_callable( $callback ) ) { $callback( $request['data'] ); // We recalculate the cart if we had something to run. $controller->calculate_totals(); } $cart = $controller->get_cart_instance(); return rest_ensure_response( $this->cart_schema->get_item_response( $cart ) ); } } V1/CheckoutSchema.php 0000644 00000023102 15073235741 0010436 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\SchemaController; use Automattic\WooCommerce\StoreApi\Payments\PaymentResult; use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema; use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields; use Automattic\WooCommerce\Blocks\Package; /** * CheckoutSchema class. */ class CheckoutSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'checkout'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'checkout'; /** * Billing address schema instance. * * @var BillingAddressSchema */ protected $billing_address_schema; /** * Shipping address schema instance. * * @var ShippingAddressSchema */ protected $shipping_address_schema; /** * Image Attachment schema instance. * * @var ImageAttachmentSchema */ protected $image_attachment_schema; /** * Additional fields controller. * * @var CheckoutFields */ protected $additional_fields_controller; /** * Constructor. * * @param ExtendSchema $extend Rest Extending instance. * @param SchemaController $controller Schema Controller instance. */ public function __construct( ExtendSchema $extend, SchemaController $controller ) { parent::__construct( $extend, $controller ); $this->billing_address_schema = $this->controller->get( BillingAddressSchema::IDENTIFIER ); $this->shipping_address_schema = $this->controller->get( ShippingAddressSchema::IDENTIFIER ); $this->image_attachment_schema = $this->controller->get( ImageAttachmentSchema::IDENTIFIER ); $this->additional_fields_controller = Package::container()->get( CheckoutFields::class ); } /** * Checkout schema properties. * * @return array */ public function get_properties() { return [ 'order_id' => [ 'description' => __( 'The order ID to process during checkout.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'status' => [ 'description' => __( 'Order status. Payment providers will update this value after payment.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'order_key' => [ 'description' => __( 'Order key used to check validity or protect access to certain order data.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'order_number' => [ 'description' => __( 'Order number used for display.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'customer_note' => [ 'description' => __( 'Note added to the order by the customer during checkout.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'customer_id' => [ 'description' => __( 'Customer ID if registered. Will return 0 for guests.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'billing_address' => [ 'description' => __( 'Billing address.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'properties' => $this->billing_address_schema->get_properties(), 'arg_options' => [ 'sanitize_callback' => [ $this->billing_address_schema, 'sanitize_callback' ], 'validate_callback' => [ $this->billing_address_schema, 'validate_callback' ], ], 'required' => true, ], 'shipping_address' => [ 'description' => __( 'Shipping address.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'properties' => $this->shipping_address_schema->get_properties(), 'arg_options' => [ 'sanitize_callback' => [ $this->shipping_address_schema, 'sanitize_callback' ], 'validate_callback' => [ $this->shipping_address_schema, 'validate_callback' ], ], ], 'payment_method' => [ 'description' => __( 'The ID of the payment method being used to process the payment.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], // Validation may be based on cart contents which is not available here; this returns all enabled // gateways. Further validation occurs during the request. 'enum' => array_values( WC()->payment_gateways->get_payment_gateway_ids() ), ], 'create_account' => [ 'description' => __( 'Whether to create a new user account as part of order processing.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], ], 'payment_result' => [ 'description' => __( 'Result of payment processing, or false if not yet processed.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ 'payment_status' => [ 'description' => __( 'Status of the payment returned by the gateway. One of success, pending, failure, error.', 'woocommerce' ), 'readonly' => true, 'type' => 'string', ], 'payment_details' => [ 'description' => __( 'An array of data being returned from the payment gateway.', 'woocommerce' ), 'readonly' => true, 'type' => 'array', 'items' => [ 'type' => 'object', 'properties' => [ 'key' => [ 'type' => 'string', ], 'value' => [ 'type' => 'string', ], ], ], ], 'redirect_url' => [ 'description' => __( 'A URL to redirect the customer after checkout. This could be, for example, a link to the payment processors website.', 'woocommerce' ), 'readonly' => true, 'type' => 'string', ], ], ], 'additional_fields' => [ 'description' => __( 'Additional fields to be persisted on the order.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'properties' => $this->get_additional_fields_schema(), ], self::EXTENDING_KEY => $this->get_extended_schema( self::IDENTIFIER ), ]; } /** * Return the response for checkout. * * @param object $item Results from checkout action. * @return array */ public function get_item_response( $item ) { return $this->get_checkout_response( $item->order, $item->payment_result ); } /** * Get the checkout response based on the current order and any payments. * * @param \WC_Order $order Order object. * @param PaymentResult $payment_result Payment result object. * @return array */ protected function get_checkout_response( \WC_Order $order, PaymentResult $payment_result = null ) { return [ 'order_id' => $order->get_id(), 'status' => $order->get_status(), 'order_key' => $order->get_order_key(), 'order_number' => $order->get_order_number(), 'customer_note' => $order->get_customer_note(), 'customer_id' => $order->get_customer_id(), 'billing_address' => (object) $this->billing_address_schema->get_item_response( $order ), 'shipping_address' => (object) $this->shipping_address_schema->get_item_response( $order ), 'payment_method' => $order->get_payment_method(), 'payment_result' => [ 'payment_status' => $payment_result->status, 'payment_details' => $this->prepare_payment_details_for_response( $payment_result->payment_details ), 'redirect_url' => $payment_result->redirect_url, ], 'additional_fields' => $this->get_additional_fields_response( $order ), self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER ), ]; } /** * This prepares the payment details for the response so it's following the * schema where it's an array of objects. * * @param array $payment_details An array of payment details from the processed payment. * * @return array An array of objects where each object has the key and value * as distinct properties. */ protected function prepare_payment_details_for_response( array $payment_details ) { return array_map( function( $key, $value ) { return (object) [ 'key' => $key, 'value' => $value, ]; }, array_keys( $payment_details ), $payment_details ); } /** * Get the additional fields response. * * @param \WC_Order $order Order object. * @return array */ protected function get_additional_fields_response( \WC_Order $order ) { $fields = $this->additional_fields_controller->get_all_fields_from_order( $order ); $response = []; foreach ( $fields as $key => $value ) { if ( 0 === strpos( $key, '/billing/' ) || 0 === strpos( $key, '/shipping/' ) ) { continue; } $response[ $key ] = $value; } return $response; } /** * Get the schema for additional fields. * * @return array */ protected function get_additional_fields_schema() { $additional_fields_keys = $this->additional_fields_controller->get_additional_fields_keys(); $fields = $this->additional_fields_controller->get_additional_fields(); $additional_fields = array_filter( $fields, function( $key ) use ( $additional_fields_keys ) { return in_array( $key, $additional_fields_keys, true ); }, ARRAY_FILTER_USE_KEY ); $schema = []; foreach ( $additional_fields as $key => $field ) { $schema[ $key ] = [ 'description' => $field['label'], 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ]; } return $schema; } } V1/ProductAttributeSchema.php 0000644 00000004773 15073235741 0012212 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; /** * ProductAttributeSchema class. */ class ProductAttributeSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'product_attribute'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'product-attribute'; /** * Term properties. * * @return array */ public function get_properties() { return [ 'id' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'name' => array( 'description' => __( 'Attribute name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'taxonomy' => array( 'description' => __( 'The attribute taxonomy name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'type' => array( 'description' => __( 'Attribute type.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'order' => array( 'description' => __( 'How terms in this attribute are sorted by default.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'has_archives' => array( 'description' => __( 'If this attribute has term archive pages.', 'woocommerce' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'count' => array( 'description' => __( 'Number of terms in the attribute taxonomy.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ]; } /** * Convert an attribute object into an object suitable for the response. * * @param object $attribute Attribute object. * @return array */ public function get_item_response( $attribute ) { return [ 'id' => (int) $attribute->id, 'name' => $this->prepare_html_response( $attribute->name ), 'taxonomy' => $attribute->slug, 'type' => $attribute->type, 'order' => $attribute->order_by, 'has_archives' => $attribute->has_archives, 'count' => (int) \wp_count_terms( $attribute->slug ), ]; } } V1/ProductCollectionDataSchema.php 0000644 00000010271 15073235741 0013122 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; /** * ProductCollectionDataSchema class. */ class ProductCollectionDataSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'product-collection-data'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'product-collection-data'; /** * Product collection data schema properties. * * @return array */ public function get_properties() { return [ 'price_range' => [ 'description' => __( 'Min and max prices found in collection of products, provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => [ 'object', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'min_price' => [ 'description' => __( 'Min price found in collection of products.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'max_price' => [ 'description' => __( 'Max price found in collection of products.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ] ), ], 'attribute_counts' => [ 'description' => __( 'Returns number of products within attribute terms.', 'woocommerce' ), 'type' => [ 'array', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'term' => [ 'description' => __( 'Term ID', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'count' => [ 'description' => __( 'Number of products.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'rating_counts' => [ 'description' => __( 'Returns number of products with each average rating.', 'woocommerce' ), 'type' => [ 'array', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'rating' => [ 'description' => __( 'Average rating', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'count' => [ 'description' => __( 'Number of products.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'stock_status_counts' => [ 'description' => __( 'Returns number of products with each stock status.', 'woocommerce' ), 'type' => [ 'array', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'status' => [ 'description' => __( 'Status', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'count' => [ 'description' => __( 'Number of products.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], ]; } /** * Format data. * * @param array $data Collection data to format and return. * @return array */ public function get_item_response( $data ) { return [ 'price_range' => ! is_null( $data['min_price'] ) && ! is_null( $data['max_price'] ) ? (object) $this->prepare_currency_response( [ 'min_price' => $this->prepare_money_response( $data['min_price'], wc_get_price_decimals() ), 'max_price' => $this->prepare_money_response( $data['max_price'], wc_get_price_decimals() ), ] ) : null, 'attribute_counts' => $data['attribute_counts'], 'rating_counts' => $data['rating_counts'], 'stock_status_counts' => $data['stock_status_counts'], ]; } } V1/OrderCouponSchema.php 0000644 00000004644 15073235741 0011142 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; /** * OrderCouponSchema class. */ class OrderCouponSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'order_coupon'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'order-coupon'; /** * Cart schema properties. * * @return array */ public function get_properties() { return [ 'code' => [ 'description' => __( 'The coupons unique code.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'discount_type' => [ 'description' => __( 'The discount type for the coupon (e.g. percentage or fixed amount)', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'totals' => [ 'description' => __( 'Total amounts provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'total_discount' => [ 'description' => __( 'Total discount applied by this coupon.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_discount_tax' => [ 'description' => __( 'Total tax removed due to discount applied by this coupon.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ] ), ], ]; } /** * Convert an order coupon to an object suitable for the response. * * @param \WC_Order_Item_Coupon $coupon Order coupon object. * @return array */ public function get_item_response( $coupon ) { $coupon_object = new \WC_Coupon( $coupon->get_code() ); return [ 'code' => $coupon->get_code(), 'discount_type' => $coupon_object ? $coupon_object->get_discount_type() : '', 'totals' => (object) $this->prepare_currency_response( [ 'total_discount' => $this->prepare_money_response( $coupon->get_discount(), wc_get_price_decimals() ), 'total_discount_tax' => $this->prepare_money_response( $coupon->get_discount_tax(), wc_get_price_decimals(), PHP_ROUND_HALF_DOWN ), ] ), ]; } } V1/ImageAttachmentSchema.php 0000644 00000005070 15073235741 0011730 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; /** * ImageAttachmentSchema class. */ class ImageAttachmentSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'image'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'image'; /** * Product schema properties. * * @return array */ public function get_properties() { return [ 'id' => [ 'description' => __( 'Image ID.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], ], 'src' => [ 'description' => __( 'Full size image URL.', 'woocommerce' ), 'type' => 'string', 'format' => 'uri', 'context' => [ 'view', 'edit' ], ], 'thumbnail' => [ 'description' => __( 'Thumbnail URL.', 'woocommerce' ), 'type' => 'string', 'format' => 'uri', 'context' => [ 'view', 'edit' ], ], 'srcset' => [ 'description' => __( 'Thumbnail srcset for responsive images.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'sizes' => [ 'description' => __( 'Thumbnail sizes for responsive images.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'name' => [ 'description' => __( 'Image name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'alt' => [ 'description' => __( 'Image alternative text.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], ]; } /** * Convert a WooCommerce product into an object suitable for the response. * * @param int $attachment_id Image attachment ID. * @return object|null */ public function get_item_response( $attachment_id ) { if ( ! $attachment_id ) { return null; } $attachment = wp_get_attachment_image_src( $attachment_id, 'full' ); if ( ! is_array( $attachment ) ) { return null; } $thumbnail = wp_get_attachment_image_src( $attachment_id, 'woocommerce_thumbnail' ); return (object) [ 'id' => (int) $attachment_id, 'src' => current( $attachment ), 'thumbnail' => current( $thumbnail ), 'srcset' => (string) wp_get_attachment_image_srcset( $attachment_id, 'full' ), 'sizes' => (string) wp_get_attachment_image_sizes( $attachment_id, 'full' ), 'name' => get_the_title( $attachment_id ), 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ), ]; } } V1/OrderSchema.php 0000644 00000031366 15073235741 0007757 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\SchemaController; use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema; use Automattic\WooCommerce\StoreApi\Utilities\OrderController; /** * OrderSchema class. */ class OrderSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'order'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'order'; /** * Item schema instance. * * @var OrderItemSchema */ public $item_schema; /** * Order controller class instance. * * @var OrderController */ protected $order_controller; /** * Coupon schema instance. * * @var OrderCouponSchema */ public $coupon_schema; /** * Product item schema instance representing cross-sell items. * * @var ProductSchema */ public $cross_sells_item_schema; /** * Fee schema instance. * * @var OrderFeeSchema */ public $fee_schema; /** * Shipping rates schema instance. * * @var CartShippingRateSchema */ public $shipping_rate_schema; /** * Shipping address schema instance. * * @var ShippingAddressSchema */ public $shipping_address_schema; /** * Billing address schema instance. * * @var BillingAddressSchema */ public $billing_address_schema; /** * Error schema instance. * * @var ErrorSchema */ public $error_schema; /** * Constructor. * * @param ExtendSchema $extend Rest Extending instance. * @param SchemaController $controller Schema Controller instance. */ public function __construct( ExtendSchema $extend, SchemaController $controller ) { parent::__construct( $extend, $controller ); $this->item_schema = $this->controller->get( OrderItemSchema::IDENTIFIER ); $this->coupon_schema = $this->controller->get( OrderCouponSchema::IDENTIFIER ); $this->fee_schema = $this->controller->get( OrderFeeSchema::IDENTIFIER ); $this->shipping_rate_schema = $this->controller->get( CartShippingRateSchema::IDENTIFIER ); $this->shipping_address_schema = $this->controller->get( ShippingAddressSchema::IDENTIFIER ); $this->billing_address_schema = $this->controller->get( BillingAddressSchema::IDENTIFIER ); $this->error_schema = $this->controller->get( ErrorSchema::IDENTIFIER ); $this->order_controller = new OrderController(); } /** * Order schema properties. * * @return array */ public function get_properties() { return [ 'id' => [ 'description' => __( 'The order ID.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'items' => [ 'description' => __( 'Line items data.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->item_schema->get_properties() ), ], ], 'totals' => [ 'description' => __( 'Order totals.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'subtotal' => [ 'description' => __( 'Subtotal of the order.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_discount' => [ 'description' => __( 'Total discount from applied coupons.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_shipping' => [ 'description' => __( 'Total price of shipping.', 'woocommerce' ), 'type' => [ 'string', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_fees' => [ 'description' => __( 'Total price of any applied fees.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_tax' => [ 'description' => __( 'Total tax applied to the order.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_refund' => [ 'description' => __( 'Total refund applied to the order.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_price' => [ 'description' => __( 'Total price the customer will pay.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_items' => [ 'description' => __( 'Total price of items in the order.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_items_tax' => [ 'description' => __( 'Total tax on items in the order.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_fees_tax' => [ 'description' => __( 'Total tax on fees.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_discount_tax' => [ 'description' => __( 'Total tax removed due to discount from applied coupons.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_shipping_tax' => [ 'description' => __( 'Total tax on shipping. If shipping has not been calculated, a null response will be sent.', 'woocommerce' ), 'type' => [ 'string', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'tax_lines' => [ 'description' => __( 'Lines of taxes applied to items and shipping.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'name' => [ 'description' => __( 'The name of the tax.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'price' => [ 'description' => __( 'The amount of tax charged.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'rate' => [ 'description' => __( 'The rate at which tax is applied.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], ] ), ], 'coupons' => [ 'description' => __( 'List of applied cart coupons.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->coupon_schema->get_properties() ), ], ], 'shipping_address' => [ 'description' => __( 'Current set shipping address for the customer.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => $this->force_schema_readonly( $this->shipping_address_schema->get_properties() ), ], 'billing_address' => [ 'description' => __( 'Current set billing address for the customer.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => $this->force_schema_readonly( $this->billing_address_schema->get_properties() ), ], 'needs_payment' => [ 'description' => __( 'True if the cart needs payment. False for carts with only free products and no shipping costs.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'needs_shipping' => [ 'description' => __( 'True if the cart needs shipping. False for carts with only digital goods or stores with no shipping methods set-up.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'errors' => [ 'description' => __( 'List of cart item errors, for example, items in the cart which are out of stock.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->error_schema->get_properties() ), ], ], 'payment_requirements' => [ 'description' => __( 'List of required payment gateway features to process the order.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'status' => [ 'description' => __( 'Status of the order.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ]; } /** * Get an order for response. * * @param \WC_Order $order Order instance. * @return array */ public function get_item_response( $order ) { $order_id = $order->get_id(); $errors = []; $failed_order_stock_error = $this->order_controller->get_failed_order_stock_error( $order_id ); if ( $failed_order_stock_error ) { $errors[] = $failed_order_stock_error; } return [ 'id' => $order_id, 'status' => $order->get_status(), 'items' => $this->get_item_responses_from_schema( $this->item_schema, $order->get_items() ), 'coupons' => $this->get_item_responses_from_schema( $this->coupon_schema, $order->get_items( 'coupon' ) ), 'fees' => $this->get_item_responses_from_schema( $this->fee_schema, $order->get_items( 'fee' ) ), 'totals' => (object) $this->prepare_currency_response( $this->get_totals( $order ) ), 'shipping_address' => (object) $this->shipping_address_schema->get_item_response( $order ), 'billing_address' => (object) $this->billing_address_schema->get_item_response( $order ), 'needs_payment' => $order->needs_payment(), 'needs_shipping' => $order->needs_shipping_address(), 'payment_requirements' => $this->extend->get_payment_requirements(), 'errors' => $errors, ]; } /** * Get total data. * * @param \WC_Order $order Order instance. * @return array */ protected function get_totals( $order ) { return [ 'subtotal' => $this->prepare_money_response( $order->get_subtotal() ), 'total_discount' => $this->prepare_money_response( $order->get_total_discount() ), 'total_shipping' => $this->prepare_money_response( $order->get_total_shipping() ), 'total_fees' => $this->prepare_money_response( $order->get_total_fees() ), 'total_tax' => $this->prepare_money_response( $order->get_total_tax() ), 'total_refund' => $this->prepare_money_response( $order->get_total_refunded() ), 'total_price' => $this->prepare_money_response( $order->get_total() ), 'total_items' => $this->prepare_money_response( array_sum( array_map( function( $item ) { return $item->get_total(); }, array_values( $order->get_items( 'line_item' ) ) ) ) ), 'total_items_tax' => $this->prepare_money_response( array_sum( array_map( function( $item ) { return $item->get_tax_total(); }, array_values( $order->get_items( 'tax' ) ) ) ) ), 'total_fees_tax' => $this->prepare_money_response( array_sum( array_map( function( $item ) { return $item->get_total_tax(); }, array_values( $order->get_items( 'fee' ) ) ) ) ), 'total_discount_tax' => $this->prepare_money_response( $order->get_discount_tax() ), 'total_shipping_tax' => $this->prepare_money_response( $order->get_shipping_tax() ), 'tax_lines' => array_map( function( $item ) { return [ 'name' => $item->get_name(), 'price' => $this->prepare_money_response( $item->get_tax_total() ), 'rate' => strval( $item->get_rate_percent() ), ]; }, array_values( $order->get_items( 'tax' ) ) ), ]; } } V1/BatchSchema.php 0000644 00000000653 15073235741 0007720 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; /** * BatchSchema class. */ class BatchSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'batch'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'batch'; /** * Batch schema properties. * * @return array */ public function get_properties() { return []; } } V1/AbstractAddressSchema.php 0000644 00000021455 15073235741 0011753 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils; use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields; use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema; use Automattic\WooCommerce\StoreApi\SchemaController; use Automattic\WooCommerce\Blocks\Package; /** * AddressSchema class. * * Provides a generic address schema for composition in other schemas. */ abstract class AbstractAddressSchema extends AbstractSchema { /** * Additional fields controller. * * @var CheckoutFields */ protected $additional_fields_controller; /** * Constructor. * * @param ExtendSchema $extend ExtendSchema instance. * @param SchemaController $controller Schema Controller instance. */ public function __construct( ExtendSchema $extend, SchemaController $controller ) { parent::__construct( $extend, $controller ); $this->additional_fields_controller = Package::container()->get( CheckoutFields::class ); } /** * Term properties. * * @internal Note that required properties don't require values, just that they are included in the request. * @return array */ public function get_properties() { return array_merge( [ 'first_name' => [ 'description' => __( 'First name', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'last_name' => [ 'description' => __( 'Last name', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'company' => [ 'description' => __( 'Company', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'address_1' => [ 'description' => __( 'Address', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'address_2' => [ 'description' => __( 'Apartment, suite, etc.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'city' => [ 'description' => __( 'City', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'state' => [ 'description' => __( 'State/County code, or name of the state, county, province, or district.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'postcode' => [ 'description' => __( 'Postal code', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'country' => [ 'description' => __( 'Country/Region code in ISO 3166-1 alpha-2 format.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'phone' => [ 'description' => __( 'Phone', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], ], $this->get_additional_address_fields_schema() ); } /** * Sanitize and format the given address object. * * @param array $address Value being sanitized. * @param \WP_REST_Request $request The Request. * @param string $param The param being sanitized. * @return array */ public function sanitize_callback( $address, $request, $param ) { $validation_util = new ValidationUtils(); $address = array_merge( array_fill_keys( array_keys( $this->get_properties() ), '' ), (array) $address ); $address = array_reduce( array_keys( $address ), function( $carry, $key ) use ( $address, $validation_util, $request ) { switch ( $key ) { case 'country': $carry[ $key ] = wc_strtoupper( sanitize_text_field( wp_unslash( $address[ $key ] ) ) ); break; case 'state': $carry[ $key ] = $validation_util->format_state( sanitize_text_field( wp_unslash( $address[ $key ] ) ), $address['country'] ); break; case 'postcode': $carry[ $key ] = $address['postcode'] ? wc_format_postcode( sanitize_text_field( wp_unslash( $address['postcode'] ) ), $address['country'] ) : ''; break; default: $carry[ $key ] = rest_sanitize_request_arg( wp_unslash( $address[ $key ] ), $request, $key ); break; } return $carry; }, [] ); return $address; } /** * Validate the given address object. * * @see rest_validate_value_from_schema * * @param array $address Value being sanitized. * @param \WP_REST_Request $request The Request. * @param string $param The param being sanitized. * @return true|\WP_Error */ public function validate_callback( $address, $request, $param ) { $errors = new \WP_Error(); $address = $this->sanitize_callback( $address, $request, $param ); $validation_util = new ValidationUtils(); if ( ! empty( $address['country'] ) && ! in_array( $address['country'], array_keys( wc()->countries->get_countries() ), true ) ) { $errors->add( 'invalid_country', sprintf( /* translators: %s valid country codes */ __( 'Invalid country code provided. Must be one of: %s', 'woocommerce' ), implode( ', ', array_keys( wc()->countries->get_countries() ) ) ) ); return $errors; } if ( ! empty( $address['state'] ) && ! $validation_util->validate_state( $address['state'], $address['country'] ) ) { $errors->add( 'invalid_state', sprintf( /* translators: %1$s given state, %2$s valid states */ __( 'The provided state (%1$s) is not valid. Must be one of: %2$s', 'woocommerce' ), esc_html( $address['state'] ), implode( ', ', array_keys( $validation_util->get_states_for_country( $address['country'] ) ) ) ) ); } if ( ! empty( $address['postcode'] ) && ! \WC_Validation::is_postcode( $address['postcode'], $address['country'] ) ) { $errors->add( 'invalid_postcode', __( 'The provided postcode / ZIP is not valid', 'woocommerce' ) ); } if ( ! empty( $address['phone'] ) && ! \WC_Validation::is_phone( $address['phone'] ) ) { $errors->add( 'invalid_phone', __( 'The provided phone number is not valid', 'woocommerce' ) ); } foreach ( array_keys( $address ) as $key ) { // Skip email here it will be validated in BillingAddressSchema. if ( 'email' === $key ) { continue; } $properties = $this->get_properties(); // Only run specific validation on properties that are defined in the schema and present in the address. // This is for partial address pushes when only part of a customer address is sent. // Full schema address validation still happens later, so empty, required values are disallowed. if ( empty( $properties[ $key ] ) || empty( $address[ $key ] ) ) { continue; } $result = rest_validate_value_from_schema( $address[ $key ], $properties[ $key ], $key ); if ( is_wp_error( $result ) ) { $errors->add( $result->get_error_code(), $result->get_error_message() ); } } return $errors->has_errors( $errors ) ? $errors : true; } /** * Get additional address fields schema. * * @return array */ protected function get_additional_address_fields_schema() { $additional_fields_keys = $this->additional_fields_controller->get_address_fields_keys(); $fields = $this->additional_fields_controller->get_additional_fields(); $address_fields = array_filter( $fields, function( $key ) use ( $additional_fields_keys ) { return in_array( $key, $additional_fields_keys, true ); }, ARRAY_FILTER_USE_KEY ); $schema = []; foreach ( $address_fields as $key => $field ) { $field_schema = [ 'description' => $field['label'], 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ]; if ( 'select' === $field['type'] ) { $field_schema['enum'] = array_map( function( $option ) { return $option['value']; }, $field['options'] ); } if ( 'checkbox' === $field['type'] ) { $field_schema['type'] = 'boolean'; } $schema[ $key ] = $field_schema; } return $schema; } } V1/ErrorSchema.php 0000644 00000002177 15073235741 0007773 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; /** * ErrorSchema class. */ class ErrorSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'error'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'error'; /** * Product schema properties. * * @return array */ public function get_properties() { return [ 'code' => [ 'description' => __( 'Error code', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'message' => [ 'description' => __( 'Error message', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ]; } /** * Convert a WP_Error into an object suitable for the response. * * @param \WP_Error $error Error object. * @return array */ public function get_item_response( $error ) { return [ 'code' => $this->prepare_html_response( $error->get_error_code() ), 'message' => $this->prepare_html_response( $error->get_error_message() ), ]; } } V1/ProductCategorySchema.php 0000644 00000006773 15073235741 0012026 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\SchemaController; use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema; /** * ProductCategorySchema class. */ class ProductCategorySchema extends TermSchema { /** * The schema item name. * * @var string */ protected $title = 'product-category'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'product-category'; /** * Image attachment schema instance. * * @var ImageAttachmentSchema */ protected $image_attachment_schema; /** * Constructor. * * @param ExtendSchema $extend Rest Extending instance. * @param SchemaController $controller Schema Controller instance. */ public function __construct( ExtendSchema $extend, SchemaController $controller ) { parent::__construct( $extend, $controller ); $this->image_attachment_schema = $this->controller->get( ImageAttachmentSchema::IDENTIFIER ); } /** * Term properties. * * @return array */ public function get_properties() { $schema = parent::get_properties(); $schema['image'] = [ 'description' => __( 'Category image.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit', 'embed' ], 'readonly' => true, 'properties' => $this->image_attachment_schema->get_properties(), ]; $schema['review_count'] = [ 'description' => __( 'Number of reviews for products in this category.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ]; $schema['permalink'] = [ 'description' => __( 'Category URL.', 'woocommerce' ), 'type' => 'string', 'format' => 'uri', 'context' => [ 'view', 'edit', 'embed' ], 'readonly' => true, ]; return $schema; } /** * Convert a term object into an object suitable for the response. * * @param \WP_Term $term Term object. * @return array */ public function get_item_response( $term ) { $response = parent::get_item_response( $term ); $count = get_term_meta( $term->term_id, 'product_count_product_cat', true ); if ( $count ) { $response['count'] = (int) $count; } $response['image'] = $this->image_attachment_schema->get_item_response( get_term_meta( $term->term_id, 'thumbnail_id', true ) ); $response['review_count'] = $this->get_category_review_count( $term ); $response['permalink'] = get_term_link( $term->term_id, 'product_cat' ); return $response; } /** * Get total number of reviews for products in a category. * * @param \WP_Term $term Term object. * @return int */ protected function get_category_review_count( $term ) { global $wpdb; $children = get_term_children( $term->term_id, 'product_cat' ); if ( ! $children || is_wp_error( $children ) ) { $terms_to_count_str = absint( $term->term_id ); } else { $terms_to_count = array_unique( array_map( 'absint', array_merge( array( $term->term_id ), $children ) ) ); $terms_to_count_str = implode( ',', $terms_to_count ); } $products_of_category_sql = " SELECT SUM(comment_count) as review_count FROM {$wpdb->posts} AS posts INNER JOIN {$wpdb->term_relationships} AS term_relationships ON posts.ID = term_relationships.object_id WHERE term_relationships.term_taxonomy_id IN (" . esc_sql( $terms_to_count_str ) . ') '; $review_count = $wpdb->get_var( $products_of_category_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared return (int) $review_count; } } V1/TermSchema.php 0000644 00000004235 15073235741 0007606 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; /** * TermSchema class. */ class TermSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'term'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'term'; /** * Term properties. * * @return array */ public function get_properties() { return [ 'id' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'name' => array( 'description' => __( 'Term name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'slug' => array( 'description' => __( 'String based identifier for the term.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'description' => array( 'description' => __( 'Term description.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'parent' => array( 'description' => __( 'Parent term ID, if applicable.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'count' => array( 'description' => __( 'Number of objects (posts of any type) assigned to the term.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ]; } /** * Convert a term object into an object suitable for the response. * * @param \WP_Term $term Term object. * @return array */ public function get_item_response( $term ) { return [ 'id' => (int) $term->term_id, 'name' => $this->prepare_html_response( $term->name ), 'slug' => $term->slug, 'description' => $this->prepare_html_response( $term->description ), 'parent' => (int) $term->parent, 'count' => (int) $term->count, ]; } } V1/CartSchema.php 0000644 00000040315 15073235741 0007567 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\SchemaController; use Automattic\WooCommerce\StoreApi\Utilities\CartController; use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema; use WC_Tax; /** * CartSchema class. */ class CartSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'cart'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'cart'; /** * Item schema instance. * * @var CartItemSchema */ public $item_schema; /** * Coupon schema instance. * * @var CartCouponSchema */ public $coupon_schema; /** * Product item schema instance representing cross-sell items. * * @var ProductSchema */ public $cross_sells_item_schema; /** * Fee schema instance. * * @var CartFeeSchema */ public $fee_schema; /** * Shipping rates schema instance. * * @var CartShippingRateSchema */ public $shipping_rate_schema; /** * Shipping address schema instance. * * @var ShippingAddressSchema */ public $shipping_address_schema; /** * Billing address schema instance. * * @var BillingAddressSchema */ public $billing_address_schema; /** * Error schema instance. * * @var ErrorSchema */ public $error_schema; /** * Constructor. * * @param ExtendSchema $extend Rest Extending instance. * @param SchemaController $controller Schema Controller instance. */ public function __construct( ExtendSchema $extend, SchemaController $controller ) { parent::__construct( $extend, $controller ); $this->item_schema = $this->controller->get( CartItemSchema::IDENTIFIER ); $this->cross_sells_item_schema = $this->controller->get( ProductSchema::IDENTIFIER ); $this->coupon_schema = $this->controller->get( CartCouponSchema::IDENTIFIER ); $this->fee_schema = $this->controller->get( CartFeeSchema::IDENTIFIER ); $this->shipping_rate_schema = $this->controller->get( CartShippingRateSchema::IDENTIFIER ); $this->shipping_address_schema = $this->controller->get( ShippingAddressSchema::IDENTIFIER ); $this->billing_address_schema = $this->controller->get( BillingAddressSchema::IDENTIFIER ); $this->error_schema = $this->controller->get( ErrorSchema::IDENTIFIER ); } /** * Cart schema properties. * * @return array */ public function get_properties() { return [ 'coupons' => [ 'description' => __( 'List of applied cart coupons.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->coupon_schema->get_properties() ), ], ], 'shipping_rates' => [ 'description' => __( 'List of available shipping rates for the cart.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->shipping_rate_schema->get_properties() ), ], ], 'shipping_address' => [ 'description' => __( 'Current set shipping address for the customer.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => $this->force_schema_readonly( $this->shipping_address_schema->get_properties() ), ], 'billing_address' => [ 'description' => __( 'Current set billing address for the customer.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => $this->force_schema_readonly( $this->billing_address_schema->get_properties() ), ], 'items' => [ 'description' => __( 'List of cart items.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->item_schema->get_properties() ), ], ], 'items_count' => [ 'description' => __( 'Number of items in the cart.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'items_weight' => [ 'description' => __( 'Total weight (in grams) of all products in the cart.', 'woocommerce' ), 'type' => 'number', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'cross_sells' => [ 'description' => __( 'List of cross-sells items related to cart items.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->cross_sells_item_schema->get_properties() ), ], ], 'needs_payment' => [ 'description' => __( 'True if the cart needs payment. False for carts with only free products and no shipping costs.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'needs_shipping' => [ 'description' => __( 'True if the cart needs shipping. False for carts with only digital goods or stores with no shipping methods set-up.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'has_calculated_shipping' => [ 'description' => __( 'True if the cart meets the criteria for showing shipping costs, and rates have been calculated and included in the totals.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'fees' => [ 'description' => __( 'List of cart fees.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->fee_schema->get_properties() ), ], ], 'totals' => [ 'description' => __( 'Cart total amounts provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'total_items' => [ 'description' => __( 'Total price of items in the cart.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_items_tax' => [ 'description' => __( 'Total tax on items in the cart.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_fees' => [ 'description' => __( 'Total price of any applied fees.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_fees_tax' => [ 'description' => __( 'Total tax on fees.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_discount' => [ 'description' => __( 'Total discount from applied coupons.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_discount_tax' => [ 'description' => __( 'Total tax removed due to discount from applied coupons.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_shipping' => [ 'description' => __( 'Total price of shipping. If shipping has not been calculated, a null response will be sent.', 'woocommerce' ), 'type' => [ 'string', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_shipping_tax' => [ 'description' => __( 'Total tax on shipping. If shipping has not been calculated, a null response will be sent.', 'woocommerce' ), 'type' => [ 'string', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_price' => [ 'description' => __( 'Total price the customer will pay.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_tax' => [ 'description' => __( 'Total tax applied to items and shipping.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'tax_lines' => [ 'description' => __( 'Lines of taxes applied to items and shipping.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'name' => [ 'description' => __( 'The name of the tax.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'price' => [ 'description' => __( 'The amount of tax charged.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'rate' => [ 'description' => __( 'The rate at which tax is applied.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], ] ), ], 'errors' => [ 'description' => __( 'List of cart item errors, for example, items in the cart which are out of stock.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->error_schema->get_properties() ), ], ], 'payment_methods' => [ 'description' => __( 'List of available payment method IDs that can be used to process the order.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'payment_requirements' => [ 'description' => __( 'List of required payment gateway features to process the order.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], self::EXTENDING_KEY => $this->get_extended_schema( self::IDENTIFIER ), ]; } /** * Convert a woo cart into an object suitable for the response. * * @param \WC_Cart $cart Cart class instance. * @return array */ public function get_item_response( $cart ) { $controller = new CartController(); // Get cart errors first so if recalculations are performed, it's reflected in the response. $cart_errors = $this->get_cart_errors( $cart ); // The core cart class will not include shipping in the cart totals if `show_shipping()` returns false. This can // happen if an address is required, or through the use of hooks. This tracks if shipping has actually been // calculated so we can avoid returning costs and rates prematurely. $has_calculated_shipping = $cart->show_shipping(); // Get shipping packages to return in the response from the cart. $shipping_packages = $has_calculated_shipping ? $controller->get_shipping_packages() : []; // Get visible cross sells products. $cross_sells = array_filter( array_map( 'wc_get_product', $cart->get_cross_sells() ), 'wc_products_array_filter_visible' ); return [ 'items' => $this->get_item_responses_from_schema( $this->item_schema, $cart->get_cart() ), 'coupons' => $this->get_item_responses_from_schema( $this->coupon_schema, $cart->get_applied_coupons() ), 'fees' => $this->get_item_responses_from_schema( $this->fee_schema, $cart->get_fees() ), 'totals' => (object) $this->prepare_currency_response( $this->get_totals( $cart ) ), 'shipping_address' => (object) $this->shipping_address_schema->get_item_response( wc()->customer ), 'billing_address' => (object) $this->billing_address_schema->get_item_response( wc()->customer ), 'needs_payment' => $cart->needs_payment(), 'needs_shipping' => $cart->needs_shipping(), 'payment_requirements' => $this->extend->get_payment_requirements(), 'has_calculated_shipping' => $has_calculated_shipping, 'shipping_rates' => $this->get_item_responses_from_schema( $this->shipping_rate_schema, $shipping_packages ), 'items_count' => $cart->get_cart_contents_count(), 'items_weight' => wc_get_weight( $cart->get_cart_contents_weight(), 'g' ), 'cross_sells' => $this->get_item_responses_from_schema( $this->cross_sells_item_schema, $cross_sells ), 'errors' => $cart_errors, 'payment_methods' => array_values( wp_list_pluck( WC()->payment_gateways->get_available_payment_gateways(), 'id' ) ), self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER ), ]; } /** * Get total data. * * @param \WC_Cart $cart Cart class instance. * @return array */ protected function get_totals( $cart ) { $has_calculated_shipping = $cart->show_shipping(); $decimals = wc_get_price_decimals(); return [ 'total_items' => $this->prepare_money_response( $cart->get_subtotal(), $decimals ), 'total_items_tax' => $this->prepare_money_response( $cart->get_subtotal_tax(), $decimals ), 'total_fees' => $this->prepare_money_response( $cart->get_fee_total(), $decimals ), 'total_fees_tax' => $this->prepare_money_response( $cart->get_fee_tax(), $decimals ), 'total_discount' => $this->prepare_money_response( $cart->get_discount_total(), $decimals ), 'total_discount_tax' => $this->prepare_money_response( $cart->get_discount_tax(), $decimals ), 'total_shipping' => $has_calculated_shipping ? $this->prepare_money_response( $cart->get_shipping_total(), $decimals ) : null, 'total_shipping_tax' => $has_calculated_shipping ? $this->prepare_money_response( $cart->get_shipping_tax(), $decimals ) : null, // Explicitly request context='edit'; default ('view') will render total as markup. 'total_price' => $this->prepare_money_response( $cart->get_total( 'edit' ), $decimals ), 'total_tax' => $this->prepare_money_response( $cart->get_total_tax(), $decimals ), 'tax_lines' => $this->get_tax_lines( $cart ), ]; } /** * Get tax lines from the cart and format to match schema. * * @param \WC_Cart $cart Cart class instance. * @return array */ protected function get_tax_lines( $cart ) { $tax_lines = []; if ( 'itemized' !== get_option( 'woocommerce_tax_total_display' ) ) { return $tax_lines; } $cart_tax_totals = $cart->get_tax_totals(); $decimals = wc_get_price_decimals(); foreach ( $cart_tax_totals as $cart_tax_total ) { $tax_lines[] = array( 'name' => $cart_tax_total->label, 'price' => $this->prepare_money_response( $cart_tax_total->amount, $decimals ), 'rate' => WC_Tax::get_rate_percent( $cart_tax_total->tax_rate_id ), ); } return $tax_lines; } /** * Get cart validation errors. * * @param \WC_Cart $cart Cart class instance. * @return array */ protected function get_cart_errors( $cart ) { $controller = new CartController(); $errors = $controller->get_cart_errors(); $cart_errors = []; foreach ( (array) $errors->errors as $code => $messages ) { foreach ( (array) $messages as $message ) { $cart_errors[] = new \WP_Error( $code, $message, $errors->get_error_data( $code ) ); } } return array_values( array_map( [ $this->error_schema, 'get_item_response' ], $cart_errors ) ); } } V1/AI/ProductSchema.php 0000644 00000001347 15073235741 0010611 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1\AI; use Automattic\WooCommerce\StoreApi\Schemas\V1\AbstractSchema; /** * ProductSchema class. * * @internal */ class ProductSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'ai/product'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'ai/product'; /** * Patterns schema properties. * * @return array */ public function get_properties() { return []; } /** * Get the Product response. * * @param array $item Item to get response for. * * @return array */ public function get_item_response( $item ) { return [ 'ai_content_generated' => true, ]; } } V1/AI/ProductsSchema.php 0000644 00000001474 15073235741 0010775 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1\AI; use Automattic\WooCommerce\StoreApi\Schemas\V1\AbstractSchema; /** * ProductsSchema class. * * @internal */ class ProductsSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'ai/products'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'ai/products'; /** * Products schema properties. * * @return array */ public function get_properties() { return []; } /** * Get the Products response. * * @param array $item Item to get response for. * * @return array */ public function get_item_response( $item ) { return [ 'ai_content_generated' => $item['ai_content_generated'], 'product_content' => $item['product_content'], ]; } } V1/AI/StoreInfoSchema.php 0000644 00000001033 15073235741 0011071 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1\AI; use Automattic\WooCommerce\StoreApi\Schemas\V1\AbstractSchema; /** * StoreInfoSchema class. * * @internal */ class StoreInfoSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'ai/store-info'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'ai/store-info'; /** * Store Info schema properties. * * @return array */ public function get_properties() { return []; } } V1/AI/BusinessDescriptionSchema.php 0000644 00000001462 15073235741 0013166 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1\AI; use Automattic\WooCommerce\StoreApi\Schemas\V1\AbstractSchema; /** * BusinessDescriptionSchema class. * * @internal */ class BusinessDescriptionSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'ai/business-description'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'ai/business-description'; /** * Business Description schema properties. * * @return array */ public function get_properties() { return []; } /** * Get the Business Description response. * * @param array $item Item to get response for. * * @return array */ public function get_item_response( $item ) { return [ 'ai_content_generated' => true, ]; } } V1/AI/ImagesSchema.php 0000644 00000001275 15073235741 0010376 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1\AI; use Automattic\WooCommerce\StoreApi\Schemas\V1\AbstractSchema; /** * ImagesSchema class. * * @internal */ class ImagesSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'ai/images'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'ai/images'; /** * Images schema properties. * * @return array */ public function get_properties() { return []; } /** * Get the Images response. * * @param array $item Item to get response for. * * @return array */ public function get_item_response( $item ) { return $item; } } V1/AI/PatternsSchema.php 0000644 00000001354 15073235741 0010767 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1\AI; use Automattic\WooCommerce\StoreApi\Schemas\V1\AbstractSchema; /** * PatternsSchema class. * * @internal */ class PatternsSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'ai/patterns'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'ai/patterns'; /** * Patterns schema properties. * * @return array */ public function get_properties() { return []; } /** * Get the Patterns response. * * @param array $item Item to get response for. * * @return array */ public function get_item_response( $item ) { return [ 'ai_content_generated' => true, ]; } } V1/AI/StoreTitleSchema.php 0000644 00000001416 15073235741 0011264 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1\AI; use Automattic\WooCommerce\StoreApi\Schemas\V1\AbstractSchema; /** * StoreTitleSchema class. * * @internal */ class StoreTitleSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'ai/store-title'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'ai/store-title'; /** * Business Description schema properties. * * @return array */ public function get_properties() { return []; } /** * Get the Business Description response. * * @param array $item Item to get response for. * * @return array */ public function get_item_response( $item ) { return [ 'ai_content_generated' => true, ]; } } V1/OrderItemSchema.php 0000644 00000005445 15073235741 0010575 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\Utilities\ProductItemTrait; /** * OrderItemSchema class. */ class OrderItemSchema extends ItemSchema { use ProductItemTrait; /** * The schema item name. * * @var string */ protected $title = 'order_item'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'order-item'; /** * Get order items data. * * @param \WC_Order_Item_Product $order_item Order item instance. * @return array */ public function get_item_response( $order_item ) { $order = $order_item->get_order(); $product = $order_item->get_product(); return [ 'key' => $order->get_order_key(), 'id' => $order_item->get_id(), 'quantity' => $order_item->get_quantity(), 'quantity_limits' => array( 'minimum' => $order_item->get_quantity(), 'maximum' => $order_item->get_quantity(), 'multiple_of' => 1, 'editable' => false, ), 'name' => $order_item->get_name(), 'short_description' => $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_short_description() ) ) ), 'description' => $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_description() ) ) ), 'sku' => $this->prepare_html_response( $product->get_sku() ), 'low_stock_remaining' => null, 'backorders_allowed' => false, 'show_backorder_badge' => false, 'sold_individually' => $product->is_sold_individually(), 'permalink' => $product->get_permalink(), 'images' => $this->get_images( $product ), 'variation' => $this->format_variation_data( $product->get_attributes(), $product ), 'item_data' => $order_item->get_all_formatted_meta_data(), 'prices' => (object) $this->prepare_product_price_response( $product, get_option( 'woocommerce_tax_display_cart' ) ), 'totals' => (object) $this->prepare_currency_response( $this->get_totals( $order_item ) ), 'catalog_visibility' => $product->get_catalog_visibility(), ]; } /** * Get totals data. * * @param \WC_Order_Item_Product $order_item Order item instance. * @return array */ public function get_totals( $order_item ) { return [ 'line_subtotal' => $this->prepare_money_response( $order_item->get_subtotal(), wc_get_price_decimals() ), 'line_subtotal_tax' => $this->prepare_money_response( $order_item->get_subtotal_tax(), wc_get_price_decimals() ), 'line_total' => $this->prepare_money_response( $order_item->get_total(), wc_get_price_decimals() ), 'line_total_tax' => $this->prepare_money_response( $order_item->get_total_tax(), wc_get_price_decimals() ), ]; } } V1/CartShippingRateSchema.php 0000644 00000026572 15073235741 0012116 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use WC_Shipping_Rate as ShippingRate; /** * CartShippingRateSchema class. */ class CartShippingRateSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'cart-shipping-rate'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'cart-shipping-rate'; /** * Cart schema properties. * * @return array */ public function get_properties() { return [ 'package_id' => [ 'description' => __( 'The ID of the package the shipping rates belong to.', 'woocommerce' ), 'type' => [ 'integer', 'string' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Name of the package.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'destination' => [ 'description' => __( 'Shipping destination address.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ 'address_1' => [ 'description' => __( 'First line of the address being shipped to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'address_2' => [ 'description' => __( 'Second line of the address being shipped to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'city' => [ 'description' => __( 'City of the address being shipped to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'state' => [ 'description' => __( 'ISO code, or name, for the state, province, or district of the address being shipped to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'postcode' => [ 'description' => __( 'Zip or Postcode of the address being shipped to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'country' => [ 'description' => __( 'ISO code for the country of the address being shipped to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], 'items' => [ 'description' => __( 'List of cart items the returned shipping rates apply to.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'key' => [ 'description' => __( 'Unique identifier for the item within the cart.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Name of the item.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'quantity' => [ 'description' => __( 'Quantity of the item in the current package.', 'woocommerce' ), 'type' => 'number', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'shipping_rates' => [ 'description' => __( 'List of shipping rates.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->get_rate_properties(), ], ], ]; } /** * Schema for a single rate. * * @return array */ protected function get_rate_properties() { return array_merge( [ 'rate_id' => [ 'description' => __( 'ID of the shipping rate.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Name of the shipping rate, e.g. Express shipping.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'description' => [ 'description' => __( 'Description of the shipping rate, e.g. Dispatched via USPS.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'delivery_time' => [ 'description' => __( 'Delivery time estimate text, e.g. 3-5 business days.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'price' => [ 'description' => __( 'Price of this shipping rate using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'taxes' => [ 'description' => __( 'Taxes applied to this shipping rate using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'method_id' => [ 'description' => __( 'ID of the shipping method that provided the rate.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'instance_id' => [ 'description' => __( 'Instance ID of the shipping method that provided the rate.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'meta_data' => [ 'description' => __( 'Meta data attached to the shipping rate.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'key' => [ 'description' => __( 'Meta key.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'value' => [ 'description' => __( 'Meta value.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'selected' => [ 'description' => __( 'True if this is the rate currently selected by the customer for the cart.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], $this->get_store_currency_properties() ); } /** * Convert a shipping rate from WooCommerce into a valid response. * * @param array $package Shipping package complete with rates from WooCommerce. * @return array */ public function get_item_response( $package ) { return [ 'package_id' => $package['package_id'], 'name' => $package['package_name'], 'destination' => $this->prepare_package_destination_response( $package ), 'items' => $this->prepare_package_items_response( $package ), 'shipping_rates' => $this->prepare_package_shipping_rates_response( $package ), ]; } /** * Gets and formats the destination address of a package. * * @param array $package Shipping package complete with rates from WooCommerce. * @return object */ protected function prepare_package_destination_response( $package ) { // If address_1 fails check address for back compatability. $address = isset( $package['destination']['address_1'] ) ? $package['destination']['address_1'] : $package['destination']['address']; return (object) $this->prepare_html_response( [ 'address_1' => $address, 'address_2' => $package['destination']['address_2'], 'city' => $package['destination']['city'], 'state' => $package['destination']['state'], 'postcode' => $package['destination']['postcode'], 'country' => $package['destination']['country'], ] ); } /** * Gets items from a package and creates an array of strings containing product names and quantities. * * @param array $package Shipping package complete with rates from WooCommerce. * @return array */ protected function prepare_package_items_response( $package ) { $items = array(); foreach ( $package['contents'] as $values ) { $items[] = [ 'key' => $values['key'], 'name' => $values['data']->get_name(), 'quantity' => $values['quantity'], ]; } return $items; } /** * Prepare an array of rates from a package for the response. * * @param array $package Shipping package complete with rates from WooCommerce. * @return array */ protected function prepare_package_shipping_rates_response( $package ) { $rates = $package['rates']; $selected_rates = wc()->session->get( 'chosen_shipping_methods', array() ); $selected_rate = isset( $selected_rates[ $package['package_id'] ] ) ? $selected_rates[ $package['package_id'] ] : ''; if ( empty( $selected_rate ) && ! empty( $package['rates'] ) ) { $selected_rate = wc_get_chosen_shipping_method_for_package( $package['package_id'], $package ); } $response = []; foreach ( $package['rates'] as $rate ) { $response[] = $this->get_rate_response( $rate, $selected_rate ); } return $response; } /** * Response for a single rate. * * @param WC_Shipping_Rate $rate Rate object. * @param string $selected_rate Selected rate. * @return array */ protected function get_rate_response( $rate, $selected_rate = '' ) { return $this->prepare_currency_response( [ 'rate_id' => $this->get_rate_prop( $rate, 'id' ), 'name' => $this->prepare_html_response( $this->get_rate_prop( $rate, 'label' ) ), 'description' => $this->prepare_html_response( $this->get_rate_prop( $rate, 'description' ) ), 'delivery_time' => $this->prepare_html_response( $this->get_rate_prop( $rate, 'delivery_time' ) ), 'price' => $this->prepare_money_response( $this->get_rate_prop( $rate, 'cost' ), wc_get_price_decimals() ), 'taxes' => $this->prepare_money_response( array_sum( (array) $this->get_rate_prop( $rate, 'taxes' ) ), wc_get_price_decimals() ), 'instance_id' => $this->get_rate_prop( $rate, 'instance_id' ), 'method_id' => $this->get_rate_prop( $rate, 'method_id' ), 'meta_data' => $this->get_rate_meta_data( $rate ), 'selected' => $selected_rate === $this->get_rate_prop( $rate, 'id' ), ] ); } /** * Gets a prop of the rate object, if callable. * * @param WC_Shipping_Rate $rate Rate object. * @param string $prop Prop name. * @return string */ protected function get_rate_prop( $rate, $prop ) { $getter = 'get_' . $prop; return \is_callable( array( $rate, $getter ) ) ? $rate->$getter() : ''; } /** * Converts rate meta data into a suitable response object. * * @param WC_Shipping_Rate $rate Rate object. * @return array */ protected function get_rate_meta_data( $rate ) { $meta_data = $rate->get_meta_data(); return array_reduce( array_keys( $meta_data ), function( $return, $key ) use ( $meta_data ) { $return[] = [ 'key' => $key, 'value' => $meta_data[ $key ], ]; return $return; }, [] ); } } V1/ItemSchema.php 0000644 00000026002 15073235741 0007571 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; /** * ItemSchema class. */ abstract class ItemSchema extends ProductSchema { /** * Item schema properties. * * @return array */ public function get_properties() { return [ 'key' => [ 'description' => __( 'Unique identifier for the item.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'type' => [ 'description' => __( 'The item type.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'id' => [ 'description' => __( 'The item product or variation ID.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'quantity' => [ 'description' => __( 'Quantity of this item.', 'woocommerce' ), 'type' => 'number', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'quantity_limits' => [ 'description' => __( 'How the quantity of this item should be controlled, for example, any limits in place.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ 'minimum' => [ 'description' => __( 'The minimum quantity allowed for this line item.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'maximum' => [ 'description' => __( 'The maximum quantity allowed for this line item.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'multiple_of' => [ 'description' => __( 'The amount that quantities increment by. Quantity must be an multiple of this value.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'default' => 1, ], 'editable' => [ 'description' => __( 'If the quantity is editable or fixed.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'default' => true, ], ], ], 'name' => [ 'description' => __( 'Product name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'short_description' => [ 'description' => __( 'Product short description in HTML format.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'description' => [ 'description' => __( 'Product full description in HTML format.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sku' => [ 'description' => __( 'Stock keeping unit, if applicable.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'low_stock_remaining' => [ 'description' => __( 'Quantity left in stock if stock is low, or null if not applicable.', 'woocommerce' ), 'type' => [ 'integer', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'backorders_allowed' => [ 'description' => __( 'True if backorders are allowed past stock availability.', 'woocommerce' ), 'type' => [ 'boolean' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'show_backorder_badge' => [ 'description' => __( 'True if the product is on backorder.', 'woocommerce' ), 'type' => [ 'boolean' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sold_individually' => [ 'description' => __( 'If true, only one item of this product is allowed for purchase in a single order.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'permalink' => [ 'description' => __( 'Product URL.', 'woocommerce' ), 'type' => 'string', 'format' => 'uri', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'images' => [ 'description' => __( 'List of images.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->image_attachment_schema->get_properties(), ], ], 'variation' => [ 'description' => __( 'Chosen attributes (for variations).', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'attribute' => [ 'description' => __( 'Variation attribute name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'value' => [ 'description' => __( 'Variation attribute value.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'item_data' => [ 'description' => __( 'Metadata related to the item', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'name' => [ 'description' => __( 'Name of the metadata.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'value' => [ 'description' => __( 'Value of the metadata.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'display' => [ 'description' => __( 'Optionally, how the metadata value should be displayed to the user.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'prices' => [ 'description' => __( 'Price data for the product in the current line item, including or excluding taxes based on the "display prices during cart and checkout" setting. Provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'price' => [ 'description' => __( 'Current product price.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'regular_price' => [ 'description' => __( 'Regular product price.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sale_price' => [ 'description' => __( 'Sale product price, if applicable.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'price_range' => [ 'description' => __( 'Price range, if applicable.', 'woocommerce' ), 'type' => [ 'object', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ 'min_amount' => [ 'description' => __( 'Price amount.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'max_amount' => [ 'description' => __( 'Price amount.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], 'raw_prices' => [ 'description' => __( 'Raw unrounded product prices used in calculations. Provided using a higher unit of precision than the currency.', 'woocommerce' ), 'type' => [ 'object', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ 'precision' => [ 'description' => __( 'Decimal precision of the returned prices.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'price' => [ 'description' => __( 'Current product price.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'regular_price' => [ 'description' => __( 'Regular product price.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sale_price' => [ 'description' => __( 'Sale product price, if applicable.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ] ), ], 'totals' => [ 'description' => __( 'Item total amounts provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'line_subtotal' => [ 'description' => __( 'Line subtotal (the price of the product before coupon discounts have been applied).', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'line_subtotal_tax' => [ 'description' => __( 'Line subtotal tax.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'line_total' => [ 'description' => __( 'Line total (the price of the product after coupon discounts have been applied).', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'line_total_tax' => [ 'description' => __( 'Line total tax.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ] ), ], 'catalog_visibility' => [ 'description' => __( 'Whether the product is visible in the catalog', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], self::EXTENDING_KEY => $this->get_extended_schema( self::IDENTIFIER ), ]; } } V1/CheckoutOrderSchema.php 0000644 00000001360 15073235741 0011434 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\SchemaController; use Automattic\WooCommerce\StoreApi\Payments\PaymentResult; use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema; /** * CheckoutOrderSchema class. */ class CheckoutOrderSchema extends CheckoutSchema { /** * The schema item name. * * @var string */ protected $title = 'checkout-order'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'checkout-order'; /** * Checkout schema properties. * * @return array */ public function get_properties() { $parent_properties = parent::get_properties(); unset( $parent_properties['create_account'] ); return $parent_properties; } } V1/OrderFeeSchema.php 0000644 00000004315 15073235741 0010371 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; /** * OrderFeeSchema class. */ class OrderFeeSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'order_fee'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'order-fee'; /** * Cart schema properties. * * @return array */ public function get_properties() { return [ 'id' => [ 'description' => __( 'Unique identifier for the fee within the cart', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Fee name', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'totals' => [ 'description' => __( 'Fee total amounts provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'total' => [ 'description' => __( 'Total amount for this fee.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_tax' => [ 'description' => __( 'Total tax amount for this fee.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ] ), ], ]; } /** * Convert a WooCommerce cart fee to an object suitable for the response. * * @param \WC_Order_Item_Fee $fee Order fee object. * @return array */ public function get_item_response( $fee ) { if ( ! $fee ) { return []; } return [ 'key' => $fee->get_id(), 'name' => $this->prepare_html_response( $fee->get_name() ), 'totals' => (object) $this->prepare_currency_response( [ 'total' => $this->prepare_money_response( $fee->get_total(), wc_get_price_decimals() ), 'total_tax' => $this->prepare_money_response( $fee->get_total_tax(), wc_get_price_decimals(), PHP_ROUND_HALF_DOWN ), ] ), ]; } } V1/ShippingAddressSchema.php 0000644 00000007026 15073235741 0011767 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils; /** * ShippingAddressSchema class. * * Provides a generic shipping address schema for composition in other schemas. */ class ShippingAddressSchema extends AbstractAddressSchema { /** * The schema item name. * * @var string */ protected $title = 'shipping_address'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'shipping-address'; /** * Convert a term object into an object suitable for the response. * * @param \WC_Order|\WC_Customer $address An object with shipping address. * * @throws RouteException When the invalid object types are provided. * @return array */ public function get_item_response( $address ) { $validation_util = new ValidationUtils(); if ( ( $address instanceof \WC_Customer || $address instanceof \WC_Order ) ) { $shipping_country = $address->get_shipping_country(); $shipping_state = $address->get_shipping_state(); if ( ! $validation_util->validate_state( $shipping_state, $shipping_country ) ) { $shipping_state = ''; } if ( $address instanceof \WC_Order ) { // get additional fields from order. $additional_address_fields = $this->additional_fields_controller->get_all_fields_from_order( $address ); } elseif ( $address instanceof \WC_Customer ) { // get additional fields from customer. $additional_address_fields = $this->additional_fields_controller->get_all_fields_from_customer( $address ); } $additional_address_fields = array_reduce( array_keys( $additional_address_fields ), function( $carry, $key ) use ( $additional_address_fields ) { if ( 0 === strpos( $key, '/shipping/' ) ) { $value = $additional_address_fields[ $key ]; $key = str_replace( '/shipping/', '', $key ); $carry[ $key ] = $value; } return $carry; }, [] ); $address_object = array_merge( [ 'first_name' => $address->get_shipping_first_name(), 'last_name' => $address->get_shipping_last_name(), 'company' => $address->get_shipping_company(), 'address_1' => $address->get_shipping_address_1(), 'address_2' => $address->get_shipping_address_2(), 'city' => $address->get_shipping_city(), 'state' => $shipping_state, 'postcode' => $address->get_shipping_postcode(), 'country' => $shipping_country, 'phone' => $address->get_shipping_phone(), ], $additional_address_fields ); // Add any missing keys from additional_fields_controller to the address response. foreach ( $this->additional_fields_controller->get_address_fields_keys() as $field ) { if ( isset( $address_object[ $field ] ) ) { continue; } $address_object[ $field ] = ''; } foreach ( $address_object as $key => $value ) { if ( isset( $this->get_properties()[ $key ]['type'] ) && 'boolean' === $this->get_properties()[ $key ]['type'] ) { $address_object[ $key ] = (bool) $value; } else { $address_object[ $key ] = $this->prepare_html_response( $value ); } } return $address_object; } throw new RouteException( 'invalid_object_type', sprintf( /* translators: Placeholders are class and method names */ __( '%1$s requires an instance of %2$s or %3$s for the address', 'woocommerce' ), 'ShippingAddressSchema::get_item_response', 'WC_Customer', 'WC_Order' ), 500 ); } } V1/ProductReviewSchema.php 0000644 00000014175 15073235741 0011505 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema; use Automattic\WooCommerce\StoreApi\SchemaController; /** * ProductReviewSchema class. */ class ProductReviewSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'product_review'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'product-review'; /** * Image attachment schema instance. * * @var ImageAttachmentSchema */ protected $image_attachment_schema; /** * Constructor. * * @param ExtendSchema $extend Rest Extending instance. * @param SchemaController $controller Schema Controller instance. */ public function __construct( ExtendSchema $extend, SchemaController $controller ) { parent::__construct( $extend, $controller ); $this->image_attachment_schema = $this->controller->get( ImageAttachmentSchema::IDENTIFIER ); } /** * Product review schema properties. * * @return array */ public function get_properties() { $properties = [ 'id' => [ 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'date_created' => [ 'description' => __( "The date the review was created, in the site's timezone.", 'woocommerce' ), 'type' => 'string', 'format' => 'date-time', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'formatted_date_created' => [ 'description' => __( "The date the review was created, in the site's timezone in human-readable format.", 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'date_created_gmt' => [ 'description' => __( 'The date the review was created, as GMT.', 'woocommerce' ), 'type' => 'string', 'format' => 'date-time', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'product_id' => [ 'description' => __( 'Unique identifier for the product that the review belongs to.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'product_name' => [ 'description' => __( 'Name of the product that the review belongs to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'product_permalink' => [ 'description' => __( 'Permalink of the product that the review belongs to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'product_image' => [ 'description' => __( 'Image of the product that the review belongs to.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => $this->image_attachment_schema->get_properties(), ], 'reviewer' => [ 'description' => __( 'Reviewer name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'review' => [ 'description' => __( 'The content of the review.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'arg_options' => [ 'sanitize_callback' => 'wp_filter_post_kses', ], 'readonly' => true, ], 'rating' => [ 'description' => __( 'Review rating (0 to 5).', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'verified' => [ 'description' => __( 'Shows if the reviewer bought the product or not.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ]; if ( get_option( 'show_avatars' ) ) { $avatar_properties = array(); $avatar_sizes = rest_get_avatar_sizes(); foreach ( $avatar_sizes as $size ) { $avatar_properties[ $size ] = array( /* translators: %d: avatar image size in pixels */ 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.', 'woocommerce' ), $size ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'embed', 'view', 'edit' ), ); } $properties['reviewer_avatar_urls'] = array( 'description' => __( 'Avatar URLs for the object reviewer.', 'woocommerce' ), 'type' => 'object', 'context' => array( 'view', 'edit' ), 'readonly' => true, 'properties' => $avatar_properties, ); } return $properties; } /** * Convert a WooCommerce product into an object suitable for the response. * * @param \WP_Comment $review Product review object. * @return array */ public function get_item_response( $review ) { $rating = get_comment_meta( $review->comment_ID, 'rating', true ) === '' ? null : (int) get_comment_meta( $review->comment_ID, 'rating', true ); return [ 'id' => (int) $review->comment_ID, 'date_created' => wc_rest_prepare_date_response( $review->comment_date ), 'formatted_date_created' => get_comment_date( 'F j, Y', $review->comment_ID ), 'date_created_gmt' => wc_rest_prepare_date_response( $review->comment_date_gmt ), 'product_id' => (int) $review->comment_post_ID, 'product_name' => get_the_title( (int) $review->comment_post_ID ), 'product_permalink' => get_permalink( (int) $review->comment_post_ID ), 'product_image' => $this->image_attachment_schema->get_item_response( get_post_thumbnail_id( (int) $review->comment_post_ID ) ), 'reviewer' => $review->comment_author, 'review' => wpautop( $review->comment_content ), 'rating' => $rating, 'verified' => wc_review_is_from_verified_owner( $review->comment_ID ), 'reviewer_avatar_urls' => rest_get_avatar_urls( $review->comment_author_email ), ]; } } V1/CartItemSchema.php 0000644 00000012124 15073235741 0010403 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\Utilities\ProductItemTrait; use Automattic\WooCommerce\StoreApi\Utilities\QuantityLimits; /** * CartItemSchema class. */ class CartItemSchema extends ItemSchema { use ProductItemTrait; /** * The schema item name. * * @var string */ protected $title = 'cart_item'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'cart-item'; /** * Convert a WooCommerce cart item to an object suitable for the response. * * @param array $cart_item Cart item array. * @return array */ public function get_item_response( $cart_item ) { $product = $cart_item['data']; /** * Filter the product permalink. * * This is a hook taken from the legacy cart/mini-cart templates that allows the permalink to be changed for a * product. This is specific to the cart endpoint. * * @since 9.9.0 * * @param string $product_permalink Product permalink. * @param array $cart_item Cart item array. * @param string $cart_item_key Cart item key. */ $product_permalink = apply_filters( 'woocommerce_cart_item_permalink', $product->get_permalink(), $cart_item, $cart_item['key'] ); return [ 'key' => $cart_item['key'], 'id' => $product->get_id(), 'type' => $product->get_type(), 'quantity' => wc_stock_amount( $cart_item['quantity'] ), 'quantity_limits' => (object) ( new QuantityLimits() )->get_cart_item_quantity_limits( $cart_item ), 'name' => $this->prepare_html_response( $product->get_title() ), 'short_description' => $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_short_description() ) ) ), 'description' => $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_description() ) ) ), 'sku' => $this->prepare_html_response( $product->get_sku() ), 'low_stock_remaining' => $this->get_low_stock_remaining( $product ), 'backorders_allowed' => (bool) $product->backorders_allowed(), 'show_backorder_badge' => (bool) $product->backorders_require_notification() && $product->is_on_backorder( $cart_item['quantity'] ), 'sold_individually' => $product->is_sold_individually(), 'permalink' => $product_permalink, 'images' => $this->get_images( $product ), 'variation' => $this->format_variation_data( $cart_item['variation'], $product ), 'item_data' => $this->get_item_data( $cart_item ), 'prices' => (object) $this->prepare_product_price_response( $product, get_option( 'woocommerce_tax_display_cart' ) ), 'totals' => (object) $this->prepare_currency_response( [ 'line_subtotal' => $this->prepare_money_response( $cart_item['line_subtotal'], wc_get_price_decimals() ), 'line_subtotal_tax' => $this->prepare_money_response( $cart_item['line_subtotal_tax'], wc_get_price_decimals() ), 'line_total' => $this->prepare_money_response( $cart_item['line_total'], wc_get_price_decimals() ), 'line_total_tax' => $this->prepare_money_response( $cart_item['line_tax'], wc_get_price_decimals() ), ] ), 'catalog_visibility' => $product->get_catalog_visibility(), self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER, $cart_item ), ]; } /** * Format cart item data removing any HTML tag. * * @param array $cart_item Cart item array. * @return array */ protected function get_item_data( $cart_item ) { /** * Filters cart item data. * * Filters the variation option name for custom option slugs. * * @since 4.3.0 * * @internal Matches filter name in WooCommerce core. * * @param array $item_data Cart item data. Empty by default. * @param array $cart_item Cart item array. * @return array */ $item_data = apply_filters( 'woocommerce_get_item_data', array(), $cart_item ); $clean_item_data = []; foreach ( $item_data as $data ) { // We will check each piece of data in the item data element to ensure it is scalar. Extensions could add arrays // to this, which would cause a fatal in wp_strip_all_tags. If it is not scalar, we will return an empty array, // which will be filtered out in get_item_data (after this function has run). foreach ( $data as $data_value ) { if ( ! is_scalar( $data_value ) ) { continue 2; } } $clean_item_data[] = $this->format_item_data_element( $data ); } return $clean_item_data; } /** * Remove HTML tags from cart item data and set the `hidden` property to `__experimental_woocommerce_blocks_hidden`. * * @param array $item_data_element Individual element of a cart item data. * @return array */ protected function format_item_data_element( $item_data_element ) { if ( array_key_exists( '__experimental_woocommerce_blocks_hidden', $item_data_element ) ) { $item_data_element['hidden'] = $item_data_element['__experimental_woocommerce_blocks_hidden']; } return array_map( 'wp_strip_all_tags', $item_data_element ); } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0 |
proxy
|
phpinfo
|
Settings