ExtendSchema.php000064400000025333150732357400007641 0ustar00formatters = $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.php000064400000004171150732357410010207 0ustar00 [ '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.php000064400000006304150732357410010753 0ustar00 [ '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.php000064400000073027150732357410010324 0ustar00image_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.php000064400000012235150732357410011564 0ustar00 [ '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.php000064400000031527150732357410010446 0ustar00extend = $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.php000064400000004025150732357410011645 0ustar00cart_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.php000064400000023102150732357410010436 0ustar00billing_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.php000064400000004773150732357410012212 0ustar00 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.php000064400000010271150732357410013122 0ustar00 [ '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.php000064400000004644150732357410011142 0ustar00 [ '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.php000064400000005070150732357410011730 0ustar00 [ '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.php000064400000031366150732357410007757 0ustar00item_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.php000064400000000653150732357410007720 0ustar00additional_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.php000064400000002177150732357410007773 0ustar00 [ '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.php000064400000006773150732357410012026 0ustar00image_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.php000064400000004235150732357410007606 0ustar00 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.php000064400000040315150732357410007567 0ustar00item_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.php000064400000001347150732357410010611 0ustar00 true, ]; } } V1/AI/ProductsSchema.php000064400000001474150732357410010775 0ustar00 $item['ai_content_generated'], 'product_content' => $item['product_content'], ]; } } V1/AI/StoreInfoSchema.php000064400000001033150732357410011071 0ustar00 true, ]; } } V1/AI/ImagesSchema.php000064400000001275150732357410010376 0ustar00 true, ]; } } V1/AI/StoreTitleSchema.php000064400000001416150732357410011264 0ustar00 true, ]; } } V1/OrderItemSchema.php000064400000005445150732357410010575 0ustar00get_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.php000064400000026572150732357410012116 0ustar00 [ '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.php000064400000026002150732357410007571 0ustar00 [ '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.php000064400000001360150732357410011434 0ustar00 [ '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.php000064400000007026150732357410011767 0ustar00get_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.php000064400000014175150732357410011505 0ustar00image_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.php000064400000012124150732357410010403 0ustar00get_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 ); } }