PluginProbe
Tiered Pricing Table for WooCommerce / 8.1.0
Tiered Pricing Table for WooCommerce v8.1.0
8.1.0 8.0.2 7.1.7 7.1.5 7.1.4 7.1.1 6.5.0 6.4.0 6.1.0 trunk 1.0 1.1 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2 2.2.3 2.3.0 2.3.1 2.3.2 All 92 releases
← All changes | src/Addons/MinQuantity/MinQuantity.php +180 -95 6.5.0 → 8.1.0 View file →
@@ -4,162 +4,246 @@
4 4 use TierPricingTable\PriceManager;
5 5 use TierPricingTable\TierPricingTablePlugin;
6 6 use WC_Cart;
7 7 use WC_Product;
8 +use WP_Error;
8 9
9 10 class MinQuantity extends AbstractAddon {
10 -
11 +
11 12 public function getName(): string {
12 13 return __( 'Minimum order quantity validation', 'tier-pricing-table' );
13 14 }
14 -
15 +
15 16 public function run() {
16 -
17 +
18 + ( new StoreApiValidation() )->run();
19 +
17 20 add_action( 'woocommerce_before_calculate_totals', function ( WC_Cart $cart ) {
18 21 foreach ( $cart->get_cart_contents() as $cartItemKey => $cartItem ) {
19 22 if ( $cartItem['data'] instanceof WC_Product ) {
20 -
23 +
21 24 $productId = ! empty( $cartItem['variation_id'] ) ? $cartItem['variation_id'] : $cartItem['product_id'];
22 -
25 +
23 26 $pricingRule = PriceManager::getPricingRule( $productId );
24 27 $min = $pricingRule->getMinimum();
25 -
28 +
26 29 if ( ! $min ) {
27 - return;
30 + continue;
28 31 }
29 -
30 - if ( $this->getProductCartQuantity( $cartItem['product_id'], 'product', $cart ) < $min ) {
32 +
33 + $cartQty = ! empty( $cartItem['variation_id'] )
34 + ? $this->getProductCartQuantity( $cartItem['variation_id'], 'variation', $cart )
35 + : $this->getProductCartQuantity( $cartItem['product_id'], 'product', $cart );
36 +
37 + if ( $cartQty < $min ) {
31 38 $cart->cart_contents[ $cartItemKey ]['quantity'] = $min;
32 -
39 +
33 40 // translators: %1$s: item name, %2$s: minimum quantity
34 41 wc_add_notice( sprintf( __( 'Minimum quantity for the %1$s is %2$d', 'tier-pricing-table' ),
35 - $cartItem['data']->get_name(), $min ), 'error' );
42 + $cartItem['data']->get_name(), $min ), 'error' );
36 43 }
37 44 }
38 45 }
39 46 } );
40 -
47 +
41 48 add_filter( 'woocommerce_quantity_input_args', function ( $args, $product = null ) {
42 -
49 +
43 50 if ( $product instanceof WC_Product && TierPricingTablePlugin::isSimpleProductSupported( $product ) ) {
44 - $pricingRule = PriceManager::getPricingRule( $product->get_id() );
45 - $min = $pricingRule->getMinimum();
46 51
47 - if ( ! $min ) {
52 + if ( is_cart() && ! apply_filters( 'tiered_pricing_table/minimum_quantity/control_cart_quantity_field', true, $product ) ) {
48 53 return $args;
49 54 }
50 -
51 - $min = max( 1, $min - $this->getProductCartQuantity( $product->get_id() ) );
52 - $args['min_value'] = $min;
53 - }
54 -
55 - return $args;
56 - }, 9999, 2 );
57 -
58 - // Quantity field in the cart
59 - add_filter( 'woocommerce_quantity_input_args', function ( $args, ?WC_Product $product ) {
60 -
61 - if ( ! apply_filters( 'tiered_pricing_table/minimum_quantity/control_cart_quantity_field', true,
62 - $product ) ) {
63 - return $args;
64 - }
65 -
66 - if ( ! is_cart() ) {
67 - return $args;
68 - }
69 -
70 - if ( $product instanceof WC_Product && TierPricingTablePlugin::isSimpleProductSupported( $product ) ) {
55 +
71 56 $pricingRule = PriceManager::getPricingRule( $product->get_id() );
72 57 $min = $pricingRule->getMinimum();
73 -
58 +
74 59 if ( ! $min ) {
75 60 return $args;
76 61 }
77 -
78 - $args['min_value'] = $pricingRule->getMinimum();
62 +
63 + if ( is_cart() ) {
64 + $args['min_value'] = $min;
65 + } else {
66 + $min = max( 1, $min - $this->getProductCartQuantity( $product->get_id() ) );
67 + $args['min_value'] = $min;
68 + }
79 69 }
80 -
70 +
81 71 return $args;
82 - }, 10, 3 );
83 -
84 - add_filter( 'woocommerce_add_to_cart_validation', function ( $passed, $productId, $quantity ) {
72 + }, 9999, 2 );
73 +
74 + add_filter( 'woocommerce_add_to_cart_validation', function ( $passed, $productId, $quantity, $variationId = 0 ) {
85 75 $productId = intval( $productId );
86 76 $quantity = intval( $quantity );
87 -
88 - $pricingRule = PriceManager::getPricingRule( $productId );
89 - $min = $pricingRule->getMinimum();
90 -
91 - if ( ! $min ) {
92 - return $passed;
93 - }
94 -
95 - $min = max( 1, $min - $this->getProductCartQuantity( $productId ) );
96 -
97 - if ( $quantity < $min ) {
77 +
78 + if ( $variationId ) {
79 + $variationRule = PriceManager::getPricingRule( $variationId );
80 + $varMin = $variationRule->getMinimum();
81 + if ( $varMin ) {
82 + $remainingMin = max( 1, $varMin - $this->getProductCartQuantity( $variationId, 'variation' ) );
83 + if ( $quantity < $remainingMin ) {
84 + wc_add_notice( sprintf( __( 'Minimum quantity for the product is %s', 'tier-pricing-table' ), $varMin ), 'error' );
85 + return false;
86 + }
87 + }
98 88
99 - // translators: %s: minimum quantity
100 - wc_add_notice( sprintf( __( 'Minimum quantity for the product is %s', 'tier-pricing-table' ), $min ),
101 - 'error' );
89 + $pricingRule = PriceManager::getPricingRule( $productId );
90 + if ( $pricingRule->isMixAndMatchMinQuantity() ) {
91 + return $passed;
92 + }
93 + } else {
94 + $pricingRule = PriceManager::getPricingRule( $productId );
95 + $min = $pricingRule->getMinimum();
102 96
103 - return false;
97 + if ( $min ) {
98 + $cartQty = $this->getProductCartQuantity( $productId, 'product' );
99 + $remainingMin = max( 1, $min - $cartQty );
100 +
101 + if ( $quantity < $remainingMin ) {
102 + // translators: %s: minimum quantity
103 + wc_add_notice( sprintf( __( 'Minimum quantity for the product is %s', 'tier-pricing-table' ), $min ), 'error' );
104 + return false;
105 + }
106 + }
104 107 }
105 -
108 +
106 109 return $passed;
107 -
108 - }, 10, 3 );
109 -
110 +
111 + }, 10, 4 );
112 +
110 113 add_filter( 'woocommerce_update_cart_validation', function ( $passed, $cart_item_key, $values, $quantity ) {
111 -
114 +
112 115 $product = $values['data'] ?? null;
113 -
116 +
114 117 if ( ! ( $product instanceof WC_Product ) ) {
115 118 return $passed;
116 119 }
117 -
120 +
118 121 $pricingRule = PriceManager::getPricingRule( $product->get_id() );
119 -
122 +
120 123 if ( ! $pricingRule->getMinimum() ) {
121 124 return $passed;
122 125 }
123 -
124 - $min = max( 1, $pricingRule->getMinimum() - $this->getProductCartQuantity( $values['product_id'] ) );
125 -
126 +
126 127 if ( $quantity && $quantity < $pricingRule->getMinimum() ) {
127 -
128 +
128 129 // translators: %s: minimum quantity
129 - wc_add_notice( sprintf( __( 'Minimum quantity for the product is %s', 'tier-pricing-table' ), $pricingRule->getMinimum() ),
130 - 'error' );
131 -
130 + wc_add_notice( sprintf( __( 'Minimum quantity for the product is %s', 'tier-pricing-table' ),
131 + $pricingRule->getMinimum() ), 'error' );
132 +
132 133 return false;
133 134 }
134 -
135 +
135 136 return $passed;
136 -
137 +
137 138 }, 10, 4 );
138 -
139 +
140 + // The Store API (Cart and Checkout blocks) counterpart of the filter above, WooCommerce 11.2+.
141 + // The minimum already reaches the Store API's numeric limits through woocommerce_quantity_input_args;
142 + // this keeps the rule in force when another extension raises those limits. A WP_Error rejects the
143 + // change; the shopper sees its message in the block.
144 + add_filter( 'woocommerce_store_api_cart_item_quantity_validation', function ( $valid, $quantity, $product ) {
145 +
146 + if ( is_wp_error( $valid ) || ! ( $product instanceof WC_Product ) ) {
147 + return $valid;
148 + }
149 +
150 + $minimum = PriceManager::getPricingRule( $product->get_id() )->getMinimum();
151 +
152 + if ( $minimum && $quantity && $quantity < $minimum ) {
153 + return new WP_Error( 'tiered_pricing_table_minimum_quantity',
154 + // translators: %s: minimum quantity
155 + sprintf( __( 'Minimum quantity for the product is %s', 'tier-pricing-table' ), $minimum ) );
156 + }
157 +
158 + return $valid;
159 +
160 + }, 9, 3 ); // before the maximum and step checks, so a too-small quantity gets the minimum message
161 +
139 162 add_filter( 'woocommerce_available_variation', function ( $variation ) {
140 163 $pricingRule = PriceManager::getPricingRule( (int) $variation['variation_id'] );
141 -
164 +
142 165 $min = $pricingRule->getMinimum();
143 -
166 +
144 167 if ( ! $min ) {
145 168 return $variation;
146 169 }
147 -
170 +
148 171 $min = max( 1, $min - $this->getProductCartQuantity( (int) $variation['variation_id'], 'variation' ) );
149 -
172 +
150 173 $variation['min_qty'] = $min;
151 174 $variation['qty_value'] = $min;
152 -
175 +
153 176 return $variation;
154 177 } );
155 -
178 +
179 + add_action( 'woocommerce_check_cart_items', function () {
180 + $cart = wc()->cart;
181 + if ( ! $cart ) {
182 + return;
183 + }
184 +
185 + $parentQuantities = array();
186 +
187 + foreach ( $cart->get_cart_contents() as $cartItem ) {
188 + if ( $cartItem['data'] instanceof WC_Product ) {
189 + $itemPricingRule = PriceManager::getPricingRule( $cartItem['data']->get_id() );
190 + $itemMin = $itemPricingRule->getMinimum();
191 + if ( $itemMin && $cartItem['quantity'] < $itemMin ) {
192 + // translators: 1: Product name, 2: Minimum quantity.
193 + wc_add_notice( sprintf( __( 'Minimum quantity for %1$s is %2$s.', 'tier-pricing-table' ),
194 + $cartItem['data']->get_name(), $itemMin ), 'error' );
195 + }
196 +
197 + $parentId = $cartItem['data']->get_parent_id();
198 + if ( $parentId ) {
199 + if ( ! isset( $parentQuantities[ $parentId ] ) ) {
200 + $parentQuantities[ $parentId ] = 0;
201 + }
202 + $parentQuantities[ $parentId ] += $cartItem['quantity'];
203 + }
204 + }
205 + }
206 +
207 + foreach ( $parentQuantities as $parentId => $qty ) {
208 + $pricingRule = PriceManager::getPricingRule( $parentId );
209 + if ( $pricingRule->isMixAndMatchMinQuantity() && $pricingRule->getMinimum() ) {
210 + if ( $qty < $pricingRule->getMinimum() ) {
211 + $product = wc_get_product( $parentId );
212 + // translators: 1: Minimum quantity, 2: Product name.
213 + wc_add_notice( sprintf( __( 'You need to order at least %1$s items of %2$s to meet the minimum order quantity.',
214 + 'tier-pricing-table' ), $pricingRule->getMinimum(),
215 + $product ? $product->get_name() : '' ), 'error' );
216 + }
217 + }
218 + }
219 + } );
220 +
221 + add_action( 'woocommerce_add_to_cart',
222 + function ( $cart_item_key, $productId, $quantity, $variationId, $variation, $cartItemData ) {
223 + if ( $variationId ) {
224 + $parentPricingRule = PriceManager::getPricingRule( $productId );
225 + if ( $parentPricingRule->isMixAndMatchMinQuantity() && $parentPricingRule->getMinimum() ) {
226 + $cartQty = $this->getProductCartQuantity( $productId, 'product' );
227 + $min = $parentPricingRule->getMinimum();
228 + if ( $cartQty < $min ) {
229 + $missing = $min - $cartQty;
230 + $product = wc_get_product( $productId );
231 + // translators: 1: Missing quantity, 2: Product name, 3: Minimum quantity.
232 + wc_add_notice( sprintf( __( 'You need %1$s more items of %2$s to meet the minimum order quantity of %3$s.',
233 + 'tier-pricing-table' ), $missing, $product ? $product->get_name() : '', $min ),
234 + 'notice' );
235 + }
236 + }
237 + }
238 + }, 10, 6 );
239 +
156 240 add_action( 'wp_head', function () {
157 -
241 +
158 242 if ( ! is_product() ) {
159 243 return;
160 244 }
161 -
245 +
162 246 ?>
163 247 <script>
164 248 // Handle Minimum Quantities by Tiered Pricing Table
165 249 (function ($) {
@@ -180,40 +264,41 @@
180 264 </script>
181 265 <?php
182 266 } );
183 267 }
184 -
268 +
185 269 protected function getProductCartQuantity( $productId, $type = 'product', $cart = null ) {
186 270 $qty = 0;
187 -
271 +
188 272 $cart = $cart ? $cart : wc()->cart;
189 -
273 +
190 274 if ( $cart && is_array( $cart->cart_contents ) ) {
191 275 foreach ( $cart->cart_contents as $cartItem ) {
192 -
276 +
193 277 if ( 'variation' === $type ) {
194 278 $compare = ! empty( $cartItem['variation_id'] ) ? $cartItem['variation_id'] : 0;
195 279 } else {
196 280 $compare = $cartItem['product_id'];
197 281 }
198 -
282 +
199 283 if ( $compare == $productId ) {
200 284 $qty += $cartItem['quantity'];
201 285 }
202 286 }
203 287 }
204 -
288 +
205 289 return apply_filters( 'tiered_pricing_table/minimum_quantity/item_quantity', $qty, $productId );
206 290 }
207 -
291 +
208 292 public function getDescription(): string {
209 - return __( 'Enforce minimum order quantities for products based on tiered pricing rules.', 'tier-pricing-table' );
293 + return __( 'Enforce minimum order quantities for products based on tiered pricing rules.',
294 + 'tier-pricing-table' );
210 295 }
211 -
296 +
212 297 public function getIcon(): string {
213 298 return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zM1 2v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.08-.14.12-.31.12-.48 0-.55-.45-1-1-1H5.21l-.94-2H1zm16 16c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z"/></svg>';
214 299 }
215 -
300 +
216 301 public function getSlug(): string {
217 302 return 'minimum-quantity';
218 303 }
219 304 }