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
tier-pricing-table / src / Addons / MinQuantity / MinQuantity.php

MinQuantity.php in Tiered Pricing Table for WooCommerce 8.1.0, at src/Addons/MinQuantity/MinQuantity.php

305 lines 10.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php namespace TierPricingTable\Addons\MinQuantity;
2
3 use TierPricingTable\Addons\AbstractAddon;
4 use TierPricingTable\PriceManager;
5 use TierPricingTable\TierPricingTablePlugin;
6 use WC_Cart;
7 use WC_Product;
8 use WP_Error;
9
10 class MinQuantity extends AbstractAddon {
11
12 public function getName(): string {
13 return __( 'Minimum order quantity validation', 'tier-pricing-table' );
14 }
15
16 public function run() {
17
18 ( new StoreApiValidation() )->run();
19
20 add_action( 'woocommerce_before_calculate_totals', function ( WC_Cart $cart ) {
21 foreach ( $cart->get_cart_contents() as $cartItemKey => $cartItem ) {
22 if ( $cartItem['data'] instanceof WC_Product ) {
23
24 $productId = ! empty( $cartItem['variation_id'] ) ? $cartItem['variation_id'] : $cartItem['product_id'];
25
26 $pricingRule = PriceManager::getPricingRule( $productId );
27 $min = $pricingRule->getMinimum();
28
29 if ( ! $min ) {
30 continue;
31 }
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 ) {
38 $cart->cart_contents[ $cartItemKey ]['quantity'] = $min;
39
40 // translators: %1$s: item name, %2$s: minimum quantity
41 wc_add_notice( sprintf( __( 'Minimum quantity for the %1$s is %2$d', 'tier-pricing-table' ),
42 $cartItem['data']->get_name(), $min ), 'error' );
43 }
44 }
45 }
46 } );
47
48 add_filter( 'woocommerce_quantity_input_args', function ( $args, $product = null ) {
49
50 if ( $product instanceof WC_Product && TierPricingTablePlugin::isSimpleProductSupported( $product ) ) {
51
52 if ( is_cart() && ! apply_filters( 'tiered_pricing_table/minimum_quantity/control_cart_quantity_field', true, $product ) ) {
53 return $args;
54 }
55
56 $pricingRule = PriceManager::getPricingRule( $product->get_id() );
57 $min = $pricingRule->getMinimum();
58
59 if ( ! $min ) {
60 return $args;
61 }
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 }
69 }
70
71 return $args;
72 }, 9999, 2 );
73
74 add_filter( 'woocommerce_add_to_cart_validation', function ( $passed, $productId, $quantity, $variationId = 0 ) {
75 $productId = intval( $productId );
76 $quantity = intval( $quantity );
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 }
88
89 $pricingRule = PriceManager::getPricingRule( $productId );
90 if ( $pricingRule->isMixAndMatchMinQuantity() ) {
91 return $passed;
92 }
93 } else {
94 $pricingRule = PriceManager::getPricingRule( $productId );
95 $min = $pricingRule->getMinimum();
96
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 }
107 }
108
109 return $passed;
110
111 }, 10, 4 );
112
113 add_filter( 'woocommerce_update_cart_validation', function ( $passed, $cart_item_key, $values, $quantity ) {
114
115 $product = $values['data'] ?? null;
116
117 if ( ! ( $product instanceof WC_Product ) ) {
118 return $passed;
119 }
120
121 $pricingRule = PriceManager::getPricingRule( $product->get_id() );
122
123 if ( ! $pricingRule->getMinimum() ) {
124 return $passed;
125 }
126
127 if ( $quantity && $quantity < $pricingRule->getMinimum() ) {
128
129 // translators: %s: minimum quantity
130 wc_add_notice( sprintf( __( 'Minimum quantity for the product is %s', 'tier-pricing-table' ),
131 $pricingRule->getMinimum() ), 'error' );
132
133 return false;
134 }
135
136 return $passed;
137
138 }, 10, 4 );
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
162 add_filter( 'woocommerce_available_variation', function ( $variation ) {
163 $pricingRule = PriceManager::getPricingRule( (int) $variation['variation_id'] );
164
165 $min = $pricingRule->getMinimum();
166
167 if ( ! $min ) {
168 return $variation;
169 }
170
171 $min = max( 1, $min - $this->getProductCartQuantity( (int) $variation['variation_id'], 'variation' ) );
172
173 $variation['min_qty'] = $min;
174 $variation['qty_value'] = $min;
175
176 return $variation;
177 } );
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
240 add_action( 'wp_head', function () {
241
242 if ( ! is_product() ) {
243 return;
244 }
245
246 ?>
247 <script>
248 // Handle Minimum Quantities by Tiered Pricing Table
249 (function ($) {
250
251 $(document).on('found_variation', function (event, variation) {
252 if (typeof variation.qty_value !== "undefined") {
253 // update quantity field with a new minimum
254 $('form.cart').find('[name=quantity]').val(variation.qty_value)
255 }
256
257 if (typeof variation.min_qty !== "undefined") {
258 // update quantity field with a new minimum
259 $('form.cart').find('[name=quantity]').attr('min', variation.min_qty);
260 }
261 });
262
263 })(jQuery);
264 </script>
265 <?php
266 } );
267 }
268
269 protected function getProductCartQuantity( $productId, $type = 'product', $cart = null ) {
270 $qty = 0;
271
272 $cart = $cart ? $cart : wc()->cart;
273
274 if ( $cart && is_array( $cart->cart_contents ) ) {
275 foreach ( $cart->cart_contents as $cartItem ) {
276
277 if ( 'variation' === $type ) {
278 $compare = ! empty( $cartItem['variation_id'] ) ? $cartItem['variation_id'] : 0;
279 } else {
280 $compare = $cartItem['product_id'];
281 }
282
283 if ( $compare == $productId ) {
284 $qty += $cartItem['quantity'];
285 }
286 }
287 }
288
289 return apply_filters( 'tiered_pricing_table/minimum_quantity/item_quantity', $qty, $productId );
290 }
291
292 public function getDescription(): string {
293 return __( 'Enforce minimum order quantities for products based on tiered pricing rules.',
294 'tier-pricing-table' );
295 }
296
297 public function getIcon(): string {
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>';
299 }
300
301 public function getSlug(): string {
302 return 'minimum-quantity';
303 }
304 }
305