Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Commit

Permalink
Update product API
Browse files Browse the repository at this point in the history
  • Loading branch information
mikejolley committed Dec 16, 2021
1 parent 1ae773b commit 044d6b1
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ export const AddToCartFormStateContextProvider = ( {
showFormElements: showFormElements && supportsFormElements,
quantity: addToCartFormState.quantity,
minQuantity: 1,
maxQuantity: product.quantity_limit || 99,
maxQuantity: product?.quantity_limits?.maximum || 99,
requestParams: addToCartFormState.requestParams,
isIdle: addToCartFormState.status === STATUS.IDLE,
isDisabled: addToCartFormState.status === STATUS.DISABLED,
Expand Down
6 changes: 5 additions & 1 deletion assets/js/shared/context/product-data-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ const defaultProductData = {
is_on_backorder: false,
low_stock_remaining: null,
sold_individually: false,
quantity_limit: 99,
quantity_limits: {
minimum: 1,
maximum: 99,
multiple_of: 1,
},
add_to_cart: {
text: 'Add to cart',
description: 'Add to cart',
Expand Down
6 changes: 5 additions & 1 deletion assets/js/types/type-defs/product-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ export interface ProductResponseItem {
is_on_backorder: boolean;
low_stock_remaining: null | number;
sold_individually: boolean;
quantity_limit: number;
quantity_limits: {
minimum: number;
maximum: number;
multiple_of: number;
};
add_to_cart: {
text: string;
description: string;
Expand Down
8 changes: 4 additions & 4 deletions bin/hook-docs/data/actions.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"file": "BlockTypes/LegacyTemplate.php",
"type": "action",
"doc": {
"description": "Hook: woocommerce_after_main_content.",
"description": "Woocommerce_after_main_content hook.",
"long_description": "",
"tags": [
{
Expand All @@ -84,7 +84,7 @@
"file": "BlockTypes/LegacyTemplate.php",
"type": "action",
"doc": {
"description": "Woocommerce_after_main_content hook.",
"description": "Hook: woocommerce_after_main_content.",
"long_description": "",
"tags": [
{
Expand Down Expand Up @@ -228,7 +228,7 @@
},
{
"name": "woocommerce_blocks_cart_enqueue_data",
"file": "BlockTypes/Cart.php",
"file": "BlockTypes/MiniCart.php",
"type": "action",
"doc": {
"description": "Fires after cart block data is registered.",
Expand All @@ -240,7 +240,7 @@
},
{
"name": "woocommerce_blocks_cart_enqueue_data",
"file": "BlockTypes/MiniCart.php",
"file": "BlockTypes/Cart.php",
"type": "action",
"doc": {
"description": "Fires after cart block data is registered.",
Expand Down
2 changes: 1 addition & 1 deletion bin/hook-docs/data/filters.json
Original file line number Diff line number Diff line change
Expand Up @@ -904,7 +904,7 @@
},
{
"name": "woocommerce_store_api_product_quantity_limit",
"file": "StoreApi/Schemas/ProductSchema.php",
"file": "StoreApi/Utilities/QuantityLimits.php",
"type": "filter",
"doc": {
"description": "Filters the quantity limit for a product being added to the cart via the Store API.",
Expand Down
8 changes: 4 additions & 4 deletions docs/extensibility/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ File: [StoreApi/Utilities/CartController.php](../src/StoreApi/Utilities/CartCont
## woocommerce_after_main_content


Hook: woocommerce_after_main_content.
Woocommerce_after_main_content hook.

```php
do_action( 'woocommerce_after_main_content' )
Expand All @@ -91,7 +91,7 @@ File: [BlockTypes/LegacyTemplate.php](../src/BlockTypes/LegacyTemplate.php)
## woocommerce_after_main_content


Woocommerce_after_main_content hook.
Hook: woocommerce_after_main_content.

```php
do_action( 'woocommerce_after_main_content' )
Expand Down Expand Up @@ -218,7 +218,7 @@ do_action( 'woocommerce_blocks_cart_enqueue_data' )
### Source


File: [BlockTypes/Cart.php](../src/BlockTypes/Cart.php)
File: [BlockTypes/MiniCart.php](../src/BlockTypes/MiniCart.php)

---

Expand All @@ -234,7 +234,7 @@ do_action( 'woocommerce_blocks_cart_enqueue_data' )
### Source


File: [BlockTypes/MiniCart.php](../src/BlockTypes/MiniCart.php)
File: [BlockTypes/Cart.php](../src/BlockTypes/Cart.php)

---

Expand Down
2 changes: 1 addition & 1 deletion docs/extensibility/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -810,7 +810,7 @@ apply_filters( 'woocommerce_store_api_product_quantity_limit', integer $quantity
### Source


File: [StoreApi/Schemas/ProductSchema.php](../src/StoreApi/Schemas/ProductSchema.php)
File: [StoreApi/Utilities/QuantityLimits.php](../src/StoreApi/Utilities/QuantityLimits.php)

---

Expand Down
16 changes: 1 addition & 15 deletions src/StoreApi/Schemas/CartItemSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -321,20 +321,6 @@ public function get_properties() {
];
}

/**
* Return quantity limits e.g. min, max, and allowed multiples, for line items.
*
* These values will be enforced when adding and updating items in the cart.
*
* @param array $cart_item Cart item array.
* @return object
*/
public function get_quantity_limits( $cart_item ) {
$quantity_limits = new QuantityLimits();

return $quantity_limits->get_quantity_limits( $cart_item );
}

/**
* Convert a WooCommerce cart item to an object suitable for the response.
*
Expand All @@ -348,7 +334,7 @@ public function get_item_response( $cart_item ) {
'key' => $cart_item['key'],
'id' => $product->get_id(),
'quantity' => wc_stock_amount( $cart_item['quantity'] ),
'quantity_limits' => $this->get_quantity_limits( $cart_item ),
'quantity_limits' => ( new QuantityLimits() )->get_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() ) ) ),
Expand Down
58 changes: 26 additions & 32 deletions src/StoreApi/Schemas/ProductSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas;

use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi;

use Automattic\WooCommerce\Blocks\StoreApi\Utilities\QuantityLimits;

/**
* ProductSchema class.
Expand Down Expand Up @@ -392,11 +392,32 @@ public function get_properties() {
'context' => [ 'view', 'edit' ],
'readonly' => true,
],
'quantity_limit' => [
'description' => __( 'The maximum quantity than can be added to the cart at once.', 'woo-gutenberg-products-block' ),
'type' => 'integer',
'quantity_limits' => [
'description' => __( 'How the quantity of this item should be controlled, for example, any limits in place.', 'woo-gutenberg-products-block' ),
'type' => [ 'object', 'boolean' ],
'context' => [ 'view', 'edit' ],
'readonly' => true,
'properties' => [
'minimum' => [
'description' => __( 'The minimum quantity allowed in the cart for this line item.', 'woo-gutenberg-products-block' ),
'type' => 'integer',
'context' => [ 'view', 'edit' ],
'readonly' => true,
],
'maximum' => [
'description' => __( 'The maximum quantity allowed in the cart for this line item.', 'woo-gutenberg-products-block' ),
'type' => 'integer',
'context' => [ 'view', 'edit' ],
'readonly' => true,
],
'multiple_of' => [
'description' => __( 'The amount that quantities increment by. Quantity must be an increment of this value.', 'woo-gutenberg-products-block' ),
'type' => 'integer',
'context' => [ 'view', 'edit' ],
'readonly' => true,
'default' => 1,
],
],
],
'add_to_cart' => [
'description' => __( 'Add to cart button parameters.', 'woo-gutenberg-products-block' ),
Expand Down Expand Up @@ -460,7 +481,7 @@ public function get_item_response( $product ) {
'is_on_backorder' => 'onbackorder' === $product->get_stock_status(),
'low_stock_remaining' => $this->get_low_stock_remaining( $product ),
'sold_individually' => $product->is_sold_individually(),
'quantity_limit' => $this->get_product_quantity_limit( $product ),
'quantity_limits' => ( new QuantityLimits() )->get_quantity_limits( $product ),
'add_to_cart' => (object) $this->prepare_html_response(
[
'text' => $product->add_to_cart_text(),
Expand Down Expand Up @@ -512,33 +533,6 @@ protected function get_low_stock_remaining( \WC_Product $product ) {
return null;
}

/**
* Get the quantity limit for an item in the cart.
*
* @param \WC_Product $product Product instance.
* @return int
*/
protected function get_product_quantity_limit( \WC_Product $product ) {
$limits = [ 99 ];

if ( $product->is_sold_individually() ) {
$limits[] = 1;
} elseif ( ! $product->backorders_allowed() ) {
$limits[] = $this->get_remaining_stock( $product );
}

/**
* Filters the quantity limit for a product being added to the cart via the Store API.
*
* Filters the variation option name for custom option slugs.
*
* @param integer $quantity_limit Quantity limit which defaults to 99 unless sold individually.
* @param \WC_Product $product Product instance.
* @return integer
*/
return apply_filters( 'woocommerce_store_api_product_quantity_limit', max( min( array_filter( $limits ) ), 1 ), $product );
}

/**
* Returns true if the given attribute is valid.
*
Expand Down
125 changes: 77 additions & 48 deletions src/StoreApi/Utilities/QuantityLimits.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,70 @@ class QuantityLimits {
use DraftOrderTrait;

/**
* Check that a given quantity is valid according to any limits in place.
* Get quantity limits (min, max, step/multiple) for a product or cart item.
*
* @param integer $quantity Quantity to validate.
* @param \WC_Product|array $cart_item_or_product Either a cart item or a product instance.
* @return \WP_Error|true
* @return object
*/
public function get_quantity_limits( $cart_item_or_product ) {
$product = $cart_item_or_product instanceof \WC_Product ? $cart_item_or_product : $cart_item_or_product['data'];

if ( ! $product instanceof \WC_Product ) {
return [
'minimum' => 1,
'maximum' => null,
'multiple_of' => 1,
];
}

$multiple_of = $this->filter_value( 1, 'multiple_of', $cart_item_or_product );
$minimum = $this->filter_value( 1, 'minimum', $cart_item_or_product );
$maximum = $this->filter_value( $this->get_product_quantity_limit( $product ), 'maximum', $cart_item_or_product );

return (object) [
'minimum' => $this->limit_to_multiple( $minimum, $multiple_of, 'ceil' ),
'maximum' => $this->limit_to_multiple( $maximum, $multiple_of, 'floor' ),
'multiple_of' => $multiple_of,
];
}

/**
* Get the limit for the total number of a product allowed in the cart.
*
* This is based on product properties, including remaining stock, and defaults to a maximum of 99 of any product
* in the cart at once.
*
* @param \WC_Product $product Product instance.
* @return int
*/
protected function get_product_quantity_limit( \WC_Product $product ) {
$limits = [ 99 ];

if ( $product->is_sold_individually() ) {
$limits[] = 1;
} elseif ( ! $product->backorders_allowed() ) {
$limits[] = $this->get_remaining_stock( $product );
}

/**
* Filters the quantity limit for a product being added to the cart via the Store API.
*
* Filters the variation option name for custom option slugs.
*
* @param integer $quantity_limit Quantity limit which defaults to 99 unless sold individually.
* @param \WC_Product $product Product instance.
* @return integer
*/
return apply_filters( 'woocommerce_store_api_product_quantity_limit', max( min( array_filter( $limits ) ), 1 ), $product );
}

/**
* Check that a given quantity is valid according to any limits in place.
*
* @param integer $quantity Quantity to validate.
* @param \WC_Product|array $cart_item_or_product Either a cart item or a product instance.
* @return \WP_Error|true
*/
public function validate_quantity( $quantity, $cart_item_or_product ) {
$limits = $this->get_quantity_limits( $cart_item_or_product );

Expand Down Expand Up @@ -61,35 +119,6 @@ public function validate_quantity( $quantity, $cart_item_or_product ) {
return true;
}

/**
* Get quantity limits (min, max, step/multiple) for a product or cart item.
*
* @param \WC_Product|array $cart_item_or_product Either a cart item or a product instance.
* @return object
*/
public function get_quantity_limits( $cart_item_or_product ) {
$product = $cart_item_or_product instanceof \WC_Product ? $cart_item_or_product : $cart_item_or_product['data'];

if ( ! $product instanceof \WC_Product ) {
return [
'minimum' => 1,
'maximum' => null,
'multiple_of' => 1,
];
}

$product_limit = $product->is_sold_individually() ? 1 : $this->get_remaining_stock( $product );
$multiple_of = $this->filter_value( 1, 'multiple_of', $cart_item_or_product );
$minimum = $this->filter_value( 1, 'minimum', $cart_item_or_product );
$maximum = $this->filter_value( $product_limit ? $product_limit : $this->limit_to_multiple( 99, $multiple_of, 'ceil' ), 'maximum', $cart_item_or_product );

return (object) [
'minimum' => $this->limit_to_multiple( $minimum, $multiple_of, 'ceil' ),
'maximum' => $this->limit_to_multiple( $maximum, $multiple_of, 'floor' ),
'multiple_of' => $multiple_of,
];
}

/**
* Returns the remaining stock for a product if it has stock.
*
Expand All @@ -109,6 +138,22 @@ protected function get_remaining_stock( \WC_Product $product ) {
return $product->get_stock_quantity() - $reserved_stock;
}

/**
* Return a number using the closest multiple of another number. Used to enforce step/multiple values.
*
* @param int $number Number to round.
* @param int $multiple_of The multiple.
* @param string $rounding_function ceil, floor, or round.
* @return int
*/
protected function limit_to_multiple( int $number, int $multiple_of, string $rounding_function = 'ceil' ) {
if ( $multiple_of <= 1 ) {
return $number;
}
$rounding_function = in_array( $rounding_function, [ 'ceil', 'floor', 'round' ], true ) ? $rounding_function : 'round';
return $rounding_function( $number / $multiple_of ) * $multiple_of;
}

/**
* Get a quantity for a product or cart item by running it through a filter hook.
*
Expand All @@ -133,20 +178,4 @@ protected function filter_value( $value, string $value_type, $cart_item_or_produ
*/
return (int) apply_filters( "woocommerce_store_api_{$type}_quantity_{$value_type}", $value, $cart_item_or_product );
}

/**
* Return a number using the closest multiple of another number. Used to enforce step/multiple values.
*
* @param int $number Number to round.
* @param int $multiple_of The multiple.
* @param string $rounding_function ceil, floor, or round.
* @return int
*/
protected function limit_to_multiple( int $number, int $multiple_of, string $rounding_function = 'ceil' ) {
if ( $multiple_of <= 1 ) {
return $number;
}
$rounding_function = in_array( $rounding_function, [ 'ceil', 'floor', 'round' ], true ) ? $rounding_function : 'round';
return $rounding_function( $number / $multiple_of ) * $multiple_of;
}
}
Loading

0 comments on commit 044d6b1

Please sign in to comment.