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 / AdvancedQuantityOptions / QuantityManager.php

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

301 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php namespace TierPricingTable\Addons\AdvancedQuantityOptions;
2
3 use TierPricingTable\PriceManager;
4 use TierPricingTable\PricingRule;
5 use TierPricingTable\TierPricingTablePlugin;
6 use WC_Product;
7 use WP_Error;
8
9 class QuantityManager {
10
11 public function __construct() {
12
13 add_filter( 'tiered_pricing_table/tiered_pricing/last_tier_postfix',
14 function ( $postfix, $quantity, PricingRule $pricingRule ) {
15
16 if ( $pricingRule->data['maximum_quantity'] && $pricingRule->data['maximum_quantity'] <= $quantity ) {
17 return '';
18 }
19
20 return $postfix;
21 }, 10, 3 );
22
23 add_filter( 'tiered_pricing_table/price/pricing_rule', function ( PricingRule $pricingRule, $productId ) {
24 $pricingRule->data['maximum_quantity'] = DataProvider::getMaximumQuantity( $productId );
25 $pricingRule->data['group_of_quantity'] = DataProvider::getGroupOfQuantity( $productId );
26
27 return $pricingRule;
28 }, 1, 2 );
29
30 add_action( 'woocommerce_before_calculate_totals', function ( \WC_Cart $cart ) {
31
32 foreach ( $cart->get_cart_contents() as $cartItemKey => $cartItem ) {
33
34 if ( $cartItem['data'] instanceof WC_Product ) {
35
36 $productId = ! empty( $cartItem['variation_id'] ) ? $cartItem['variation_id'] : $cartItem['product_id'];
37
38 $pricingRule = PriceManager::getPricingRule( $productId );
39 $max = $pricingRule->data['maximum_quantity'] ?? null;
40 $groupOf = $pricingRule->data['group_of_quantity'] ?? null;
41
42 $cartItemQuantity = $this->getProductCartQuantity( $productId );
43
44 if ( $max ) {
45
46 $max = max( $groupOf, $max );
47
48 if ( $this->getProductCartQuantity( $productId ) > $max ) {
49 $cart->cart_contents[ $cartItemKey ]['quantity'] = $max;
50 // translators: %1$s: item name, %2$s: minimum quantity
51 wc_add_notice( sprintf( __( 'Maximum quantity for the %1$s is %2$d', 'tier-pricing-table' ),
52 $cartItem['data']->get_name(), $max ), 'error' );
53 }
54 }
55
56 if ( $groupOf ) {
57
58 if ( 0 !== $cartItemQuantity % $groupOf ) {
59 // translators: %s: quantity step
60 wc_add_notice( sprintf( __( 'Order quantity must be multiple of %s', 'tier-pricing-table' ),
61 $groupOf ), 'error' );
62
63 $cart->cart_contents[ $cartItemKey ]['quantity'] = ceil( $cartItemQuantity / $groupOf ) * $groupOf;
64 }
65 }
66 }
67 }
68 }, 10, 1 );
69
70 add_filter( 'woocommerce_quantity_input_args', function ( $args, $product ) {
71
72 if ( $product instanceof WC_Product && TierPricingTablePlugin::isSimpleProductSupported( $product ) ) {
73
74 $pricingRule = PriceManager::getPricingRule( $product->get_id() );
75
76 $max = $pricingRule->data['maximum_quantity'] ?? null;
77 $groupOf = $pricingRule->data['group_of_quantity'] ?? null;
78
79 if ( $max ) {
80
81 // Subtract what is already in the cart ONLY for the classic product-page input, where
82 // the field means "how many more can be added". The Store API reads this filter's
83 // max_value as the ABSOLUTE cart maximum (QuantityLimits::get_add_to_cart_limits), so
84 // subtracting there double-counts the cart and caps customers at half the real limit.
85 if ( ! is_cart() && ! is_checkout() && ! WC()->is_rest_api_request() ) {
86 $max = max( 1, $max - $this->getProductCartQuantity( $product->get_id() ) );
87 }
88
89 $max = max( $groupOf, $max );
90
91 $args['max_value'] = $max;
92 }
93
94 if ( $groupOf ) {
95 $args['step'] = $groupOf;
96 $args['min_value'] = max( $args['min_value'] ?? 1, $groupOf );
97 $args['value'] = max( $args['value'] ?? 1, $groupOf );
98 }
99 }
100
101 return $args;
102 }, 9999999, 2 );
103
104 add_filter( 'woocommerce_add_to_cart_validation', function ( $passed, $_productId, $qty, $variationId = null ) {
105
106 $productId = $variationId ? $variationId : $_productId;
107
108 // Do not show additional notices if there are already notices
109 if ( ! $passed ) {
110 return false;
111 }
112
113 $pricingRule = PriceManager::getPricingRule( $productId );
114
115 $max = $pricingRule->data['maximum_quantity'] ?? null;
116 $groupOf = $pricingRule->data['group_of_quantity'] ?? null;
117
118 if ( $max ) {
119
120 $max = max( $groupOf, $max );
121
122 if ( $qty + $this->getProductCartQuantity( $_productId ) > $max ) {
123
124 // translators: %s: maximum quantity
125 wc_add_notice( sprintf( __( 'Maximum order quantity for the product is %s', 'tier-pricing-table' ),
126 $max ), 'error' );
127
128 return false;
129 }
130 }
131
132 if ( $groupOf ) {
133
134 if ( 0 !== $qty % $groupOf ) {
135 // translators: %s: quantity step
136 wc_add_notice( sprintf( __( 'Order quantity must be multiple of %s', 'tier-pricing-table' ),
137 $groupOf ), 'error' );
138
139 return false;
140 }
141 }
142
143 return $passed;
144
145 }, 10, 4 );
146
147 add_action( 'wp_head', function () {
148 if ( is_product() ) {
149 ?>
150 <script>
151 jQuery(document).ready(function () {
152
153 let $quantity = jQuery('.single_variation_wrap').find('[name=quantity]');
154
155 jQuery(document).on('found_variation', function (e, variation) {
156
157 if (variation.step) {
158 $quantity.attr('step', variation.step);
159 $quantity.data('step', variation.step);
160 } else {
161 $quantity.attr('step', 1);
162 $quantity.data('step', 1);
163 }
164
165 if (variation.max_qty) {
166 $quantity.attr('max', variation.max_qty);
167 $quantity.data('max', variation.max_qty);
168 } else {
169 $quantity.removeAttr('max');
170 }
171 });
172
173 jQuery(document).on('reset_data', function () {
174 // Do not remove step attr - it can be used for some themes for +\- buttons
175 $quantity.attr('step', 1);
176 $quantity.data('step', 1);
177
178 $quantity.removeAttr('max');
179 });
180 });
181 </script>
182 <?php
183 }
184 } );
185
186 add_filter( 'woocommerce_update_cart_validation', function ( $passed, $cart_item_key, $values, $quantity ) {
187 $product = $values['data'] ?? null;
188
189 if ( ! ( $product instanceof WC_Product ) ) {
190 return $passed;
191 }
192
193 $pricingRule = PriceManager::getPricingRule( $product->get_id() );
194
195 $max = $pricingRule->data['maximum_quantity'] ? intval( $pricingRule->data['maximum_quantity'] ) : null;
196 $groupOf = $pricingRule->data['group_of_quantity'] ? intval( $pricingRule->data['group_of_quantity'] ) : null;
197
198 if ( $max ) {
199
200 if ( $quantity > $max ) {
201
202 // translators: %s: maximum quantity
203 wc_add_notice( sprintf( __( 'Maximum order quantity for the product is %s', 'tier-pricing-table' ),
204 $max ), 'error' );
205
206 return false;
207 }
208 }
209
210 if ( $groupOf ) {
211
212 if ( 0 !== $quantity % $groupOf ) {
213
214 // translators: %s: quantity step
215 wc_add_notice( sprintf( __( 'Order quantity must be multiple of %s', 'tier-pricing-table' ),
216 $groupOf ), 'error' );
217
218 return false;
219 }
220 }
221
222 return $passed;
223
224 }, 10, 4 );
225
226 // The Store API (Cart and Checkout blocks) counterpart of the filter above, WooCommerce 11.2+. The
227 // maximum and the step already reach the Store API's numeric limits through
228 // woocommerce_quantity_input_args; this keeps them in force when another extension raises those
229 // limits. A WP_Error rejects the change; the shopper sees its message in the block.
230 add_filter( 'woocommerce_store_api_cart_item_quantity_validation', function ( $valid, $quantity, $product ) {
231
232 if ( is_wp_error( $valid ) || ! ( $product instanceof WC_Product ) ) {
233 return $valid;
234 }
235
236 $pricingRule = PriceManager::getPricingRule( $product->get_id() );
237
238 $max = ! empty( $pricingRule->data['maximum_quantity'] ) ? intval( $pricingRule->data['maximum_quantity'] ) : null;
239 $groupOf = ! empty( $pricingRule->data['group_of_quantity'] ) ? intval( $pricingRule->data['group_of_quantity'] ) : null;
240
241 if ( $max && $quantity > $max ) {
242 return new WP_Error( 'tiered_pricing_table_maximum_quantity',
243 // translators: %s: maximum quantity
244 sprintf( __( 'Maximum order quantity for the product is %s', 'tier-pricing-table' ), $max ) );
245 }
246
247 if ( $groupOf && 0 !== (int) $quantity % $groupOf ) {
248 return new WP_Error( 'tiered_pricing_table_quantity_step',
249 // translators: %s: quantity step
250 sprintf( __( 'Order quantity must be multiple of %s', 'tier-pricing-table' ), $groupOf ) );
251 }
252
253 return $valid;
254
255 }, 10, 3 );
256
257 add_filter( 'woocommerce_available_variation', function ( $variation ) {
258
259 $pricingRule = PriceManager::getPricingRule( $variation['variation_id'] );
260
261 $max = $pricingRule->data['maximum_quantity'] ?? null;
262 $groupOf = $pricingRule->data['group_of_quantity'] ?? null;
263
264 if ( $max ) {
265 $max = max( 1, $max - $this->getProductCartQuantity( $variation['variation_id'], true ) );
266
267 if ( $groupOf ) {
268 $max = max( $groupOf, $max );
269 }
270
271 $variation['max_qty'] = $max;
272 }
273
274 if ( $groupOf ) {
275 $variation['step'] = $groupOf;
276 $variation['min_qty'] = max( $variation['min_qty'], $groupOf );
277 $variation['qty_value'] = max( $variation['min_qty'], $groupOf );
278 }
279
280 return $variation;
281 }, 999 );
282 }
283
284 public function getProductCartQuantity( $productId, $isVariation = false ) {
285 $qty = 0;
286
287 if ( is_array( wc()->cart->cart_contents ) ) {
288 foreach ( wc()->cart->cart_contents as $cart_content ) {
289
290 $compare = $isVariation ? ( $cart_content['variation_id'] ?? false ) : $cart_content['product_id'];
291
292 if ( $compare == $productId ) {
293 $qty += $cart_content['quantity'];
294 }
295
296 }
297 }
298
299 return $qty;
300 }
301 }