PluginProbe
Maximum, Minimum and Multiple Quantity for WooCommerce Shops / 2.1
Maximum, Minimum and Multiple Quantity for WooCommerce Shops v2.1
3.0 1.0 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3 1.4 1.4.1 1.4.2 1.4.3 1.5 1.5.1 1.5.2 1.6 All 32 releases
woocommerce-max-quantity / woocommerce-max-quantity.php

woocommerce-max-quantity.php in Maximum, Minimum and Multiple Quantity for WooCommerce Shops 2.1, at woocommerce-max-quantity.php

401 lines 14.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Maximum Quantity for WooCommerce Shops
4 * Plugin URI:
5 * Description: Set a limit for the maximum quantity that can be added to the cart, globally or per product.
6 * Version: 2.1
7 * Author: PT Woo Plugins (by Webdados)
8 * Author URI: https://ptwooplugins.com
9 * Text Domain: woocommerce-max-quantity
10 * Requires at least: 5.6
11 * Tested up to: 6.6
12 * Requires PHP: 7.0
13 * WC requires at least: 5.0
14 * WC tested up to: 9.1
15 * Requires Plugins: woocommerce
16 * License: GPLv3
17 */
18
19 namespace PTWooPlugins\MaxQuantityWC;
20
21 if ( ! defined( 'ABSPATH' ) ) {
22 exit; // Exit if accessed directly
23 }
24
25
26 /**
27 * Load plugin
28 *
29 * @since 2.0
30 * @return void
31 */
32 function init() {
33 // Load textdomain
34 load_plugin_textdomain( 'woocommerce-max-quantity' );
35 // Check for: WooCommerce (maybe later also check for required version)
36 if ( class_exists( 'WooCommerce' ) ) {
37 add_filter( 'woocommerce_inventory_settings', __NAMESPACE__ . '\wc_max_qty_options' );
38 add_filter( 'woocommerce_quantity_input_args', __NAMESPACE__ . '\wc_max_qty_input_args', 10, 2 );
39 add_filter( 'woocommerce_available_variation', __NAMESPACE__ . '\wc_max_qty_variation_input_qty_max', 10, 3 );
40 add_filter( 'woocommerce_add_to_cart_validation', __NAMESPACE__ . '\wc_max_qty_add_to_cart_validation', 1, 4 );
41 add_filter( 'woocommerce_update_cart_validation', __NAMESPACE__ . '\wc_max_qty_update_cart_validation', 1, 4 );
42 add_action( 'woocommerce_product_options_inventory_product_data', __NAMESPACE__ . '\wc_max_qty_add_product_field' );
43 add_action( 'woocommerce_process_product_meta', __NAMESPACE__ . '\wc_max_qty_save_product_field' );
44 add_filter( 'woocommerce_store_api_product_quantity_limit', __NAMESPACE__ . '\blocks_cart_max_qty', 10, 2 );
45 }
46 }
47 add_action( 'plugins_loaded', __NAMESPACE__ . '\init' );
48
49
50 /**
51 * Add the option to WooCommerce products tab
52 *
53 * @param array $settings The current settings.
54 */
55 function wc_max_qty_options( $settings ) {
56 $updated_settings = array();
57 foreach ( $settings as $section ) {
58 // At the bottom of the Inventory Options section
59 if ( isset( $section['id'] ) && 'product_inventory_options' === $section['id'] && isset( $section['type'] ) && 'sectionend' === $section['type'] ) {
60 $updated_settings[] = array(
61 'title' => __( 'Maximum quantity per product', 'woocommerce-max-quantity' ),
62 'desc' => __( 'This is the default maximum quantity that can be added to the cart per product. To overide this for a specific product, set it on the "Max quantity per order" field on the product Inventory tab.', 'woocommerce-max-quantity' ),
63 'id' => 'isa_woocommerce_max_qty_limit',
64 'css' => 'width: 75px;',
65 'type' => 'number',
66 'custom_attributes' => array(
67 'min' => 0,
68 'step' => 1,
69 ),
70 'default' => '',
71 'autoload' => false,
72 'desc_tip' => false,
73 );
74 }
75 $updated_settings[] = $section;
76 }
77 return $updated_settings;
78 }
79
80
81 /**
82 * Display the product's "Max quantity per order" field in the Product Data metabox
83 *
84 * @since 1.4
85 */
86 function wc_max_qty_add_product_field() {
87 $default_max = (int) get_option( 'isa_woocommerce_max_qty_limit' );
88 echo '<div class="options_group">';
89 woocommerce_wp_text_input(
90 array(
91 'id' => '_isa_wc_max_qty_product_max',
92 'label' => __( 'Max quantity per order', 'woocommerce-max-quantity' ),
93 'placeholder' => $default_max > 0 ? sprintf(
94 /* translators: %d: Default maximum quantity */
95 esc_attr__( 'Store-wide threshold (%d)', 'woocommerce-max-quantity' ),
96 $default_max
97 ) : '',
98 'description' => __( 'Optional. Set a maximum quantity limit allowed per order. Enter a number, 1 or greater.', 'woocommerce-max-quantity' ),
99 )
100 );
101 echo '</div>';
102 }
103
104
105 /**
106 * Save product's Max Quantity field
107 *
108 * @param int $post_id WP post id.
109 * @since 1.4
110 */
111 function wc_max_qty_save_product_field( $post_id ) {
112 $product = wc_get_product( $post_id );
113 $val = $product->get_meta( '_isa_wc_max_qty_product_max' );
114 // phpcs:ignore WordPress.Security.NonceVerification.Missing
115 $new = isset( $_POST['_isa_wc_max_qty_product_max'] ) ? sanitize_text_field( wp_unslash( $_POST['_isa_wc_max_qty_product_max'] ) ) : ''; // Nonce verification is already taken care by WooCommerce
116 if ( $val !== $new ) {
117 $product->update_meta_data( '_isa_wc_max_qty_product_max', $new );
118 $product->save();
119 }
120 }
121
122
123 /**
124 * Get the individual product max limit
125 *
126 * @param int $product_id The product ID.
127 * @return int|bool $limit The max limit number for this product, if set, otherwise false.
128 * @since 1.4
129 */
130 function wc_get_product_max_limit( $product_id ) {
131 $product = wc_get_product( $product_id );
132 $qty = $product->get_meta( '_isa_wc_max_qty_product_max' );
133 if ( empty( $qty ) ) {
134 // Honor the Sold individually setting
135 $limit = $product->is_sold_individually() ? 1 : false;
136 } else {
137 $limit = (int) $qty;
138 }
139 return $limit;
140 }
141
142
143 /**
144 * Set the max attribute value for the quantity input field for Add to cart forms.
145 * This applies to Simple product Add To Cart forms, and ALL (simple and variable) products on the Cart page quantity field.
146 *
147 * @param array $args The current arguments.
148 * @param object $product The product.
149 * @return array $args
150 * @since 1.1.6
151 */
152 function wc_max_qty_input_args( $args, $product ) {
153 $default_max = (int) get_option( 'isa_woocommerce_max_qty_limit' );
154 $product_id = $product->get_parent_id() ? $product->get_parent_id() : $product->get_id();
155 $product_max = wc_get_product_max_limit( $product_id );
156 // Set product max or default max
157 if ( ! empty( $product_max ) ) {
158 $args['max_value'] = $product_max;
159 } elseif ( ! empty( $default_max ) ) {
160 $args['max_value'] = $default_max;
161 }
162 // Limit our max by the available stock, if stock is lower
163 if ( ! empty( $args['max_value'] ) ) {
164 if ( $product->managing_stock() && ! $product->backorders_allowed() ) {
165 $stock = $product->get_stock_quantity();
166 $args['max_value'] = min( $stock, $args['max_value'] );
167 }
168 }
169 return $args;
170 }
171
172
173 /**
174 * Filter the available variation to enforce the max on the quantity input field
175 * on Add to cart forms for Variable Products.
176 *
177 * @param array $args The current arguments.
178 * @param object $product The product.
179 * @param object $variation The product variation.
180 */
181 function wc_max_qty_variation_input_qty_max( $args, $product, $variation ) {
182 if ( is_admin() && ! is_ajax() ) {
183 return $args;
184 }
185 $default_max = (int) get_option( 'isa_woocommerce_max_qty_limit' );
186 $product_max = wc_get_product_max_limit( $variation->get_parent_id() );
187 // Set product max or default max
188 if ( ! empty( $product_max ) ) {
189 $args['max_qty'] = $product_max;
190 } elseif ( ! empty( $default_max ) ) {
191 $args['max_qty'] = $default_max;
192 }
193 // Limit our max by the available stock, if stock is lower
194 if ( ! empty( $args['max_qty'] ) ) {
195 if ( $variation->managing_stock() && ! $variation->backorders_allowed() ) {
196 $stock = $variation->get_stock_quantity();
197 $args['max_qty'] = min( $stock, $args['max_qty'] );
198 }
199 }
200 return $args;
201 }
202
203
204 /**
205 * Find out how many of this product are already in the cart
206 *
207 * @param mixed $product_id ID of the product in question.
208 * @param string $cart_item_key The cart key for this item in case of Updating cart.
209 *
210 * @return integer $running_qty The total quantity of this item, parent item in case of variations, in cart
211 * @since 1.1.6
212 */
213 function wc_max_qty_get_cart_qty( $product_id, $cart_item_key = '' ) {
214 $running_qty = 0; // Keep a running total to count variations
215 // Search the cart for the product in question
216 foreach ( WC()->cart->get_cart() as $other_cart_item_keys => $values ) {
217 if ( intval( $product_id ) === intval( $values['product_id'] ) ) {
218 /*
219 * In case of updating the cart quantity, don't count this cart item key
220 otherwise they won't be able to REDUCE the number of items in cart becuase it will think it is adding the new quantity on top of the existing quantity, when in fact it is reducing the existing quantity to the new quantity.
221 */
222 if ( $cart_item_key === $other_cart_item_keys ) {
223 continue;
224 }
225 // Add that quantity to our running total qty for this product
226 $running_qty += (int) $values['quantity'];
227 }
228 }
229 return $running_qty;
230 }
231
232
233 /**
234 * Validate product quantity when Added to cart
235 *
236 * @param bool $passed If the validation passed.
237 * @param int $product_id The product ID.
238 * @param int $quantity The quantity added to cart.
239 * @param int $variation_id The product variation ID.
240 *
241 * @since 1.1.6
242 */
243 function wc_max_qty_add_to_cart_validation( $passed, $product_id, $quantity, $variation_id = '' ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
244 // If it hasn't passed, we don't need to bother validating further
245 if ( $passed ) {
246 $default_max = (int) get_option( 'isa_woocommerce_max_qty_limit' );
247 $product_max = wc_get_product_max_limit( $product_id );
248 // Set product max or default max
249 if ( ! empty( $product_max ) ) {
250 $new_max = $product_max;
251 } elseif ( ! empty( $default_max ) ) {
252 $new_max = $default_max;
253 } else {
254 return $passed;
255 }
256 $already_in_cart = wc_max_qty_get_cart_qty( $product_id );
257 $product = wc_get_product( $product_id );
258 $product_title = $product->get_title();
259 if ( ! empty( $already_in_cart ) ) {
260 // There was already a quantity of this item in cart prior to this addition.
261 // Check if the total of already_in_cart + current addition quantity is more than our max.
262 if ( ( $already_in_cart + $quantity ) > $new_max ) {
263 // oops. too much.
264 $passed = false;
265 // Add compatibility with WooCommerce Direct Checkout
266 if ( class_exists( 'WooCommerce_Direct_Checkout' ) ) {
267 $direct_checkout = get_option( 'direct_checkout_enabled' );
268 $direct_checkout_url = get_option( 'direct_checkout_cart_redirect_url' );
269 if ( $direct_checkout && $direct_checkout_url ) {
270 // Redirect to submit page
271 wp_safe_redirect( esc_url_raw( $direct_checkout_url ) );
272 exit;
273 }
274 }
275 wc_add_notice(
276 apply_filters(
277 'isa_wc_max_qty_error_message_already_had',
278 sprintf(
279 /* translators: %1$s maximum items, %2$s product name, %3$s: cart link, %4$s number of items in cart */
280 __( 'You can add a maximum of %1$s %2$s’s to %3$s. You already have %4$s.', 'woocommerce-max-quantity' ),
281 $new_max,
282 $product_title,
283 '<a href="' . esc_url( wc_get_cart_url() ) . '">' . __( 'your cart', 'woocommerce-max-quantity' ) . '</a>',
284 $already_in_cart
285 ),
286 $new_max,
287 $already_in_cart
288 ),
289 'error'
290 );
291 }
292 } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
293 // none were in cart previously
294 // just in case they manually type in an amount greater than we allow, check the input number here too
295 if ( $quantity > $new_max ) {
296 // oops. too much.
297 wc_add_notice(
298 apply_filters(
299 'isa_wc_max_qty_error_message',
300 sprintf(
301 /* translators: %1$s maximum items, %2$s product name, %3$s: cart link */
302 __( 'You can add a maximum of %1$s %2$s’s to %3$s.', 'woocommerce-max-quantity' ),
303 $new_max,
304 $product_title,
305 '<a href="' . esc_url( wc_get_cart_url() ) . '">' . __( 'your cart', 'woocommerce-max-quantity' ) . '</a>'
306 ),
307 $new_max
308 ),
309 'error'
310 );
311 $passed = false;
312 }
313 }
314 }
315 return $passed;
316 }
317
318 /**
319 * Validate product quantity when cart is UPDATED - Not working when updating quantity inside the block-based cart
320 * Just in case they manually type in an amount greater than we allow and the HTML5 Constraint validation doesn't work.
321 *
322 * @param bool $passed If the validation passed.
323 * @param string $cart_item_key The key of the updated cart item.
324 * @param array $values Cart item values.
325 * @param int $quantity The quantity updated to cart.
326 * @since 1.1.9
327 */
328 function wc_max_qty_update_cart_validation( $passed, $cart_item_key, $values, $quantity ) {
329 // If it hasn't passed, we don't need to bother validating further
330 if ( $passed ) {
331 $default_max = (int) get_option( 'isa_woocommerce_max_qty_limit' );
332 $product_max = wc_get_product_max_limit( $values['product_id'] );
333 // Set product max or default max
334 if ( ! empty( $product_max ) ) {
335 $new_max = $product_max;
336 } elseif ( ! empty( $default_max ) ) {
337 $new_max = $default_max;
338 } else {
339 return $passed;
340 }
341 $already_in_cart = wc_max_qty_get_cart_qty( $values['product_id'], $cart_item_key );
342 $product = wc_get_product( $values['product_id'] );
343 if ( ( $already_in_cart + $quantity ) > $new_max ) {
344 wc_add_notice(
345 apply_filters(
346 'isa_wc_max_qty_error_message',
347 sprintf(
348 /* translators: %1$s maximum items, %2$s product name, %3$s: cart link */
349 __( 'You can add a maximum of %1$s %2$s’s to %3$s.', 'woocommerce-max-quantity' ),
350 $new_max,
351 $product->get_name(),
352 '<a href="' . esc_url( wc_get_cart_url() ) . '">' . __( 'your cart', 'woocommerce-max-quantity' ) . '</a>'
353 ),
354 $new_max
355 ),
356 'error'
357 );
358 $passed = false;
359 }
360 }
361 return $passed;
362 }
363
364
365 /**
366 * Set maximum quantity on the block-based cart
367 *
368 * @since 2.0
369 * @param int $cart_max Current maximum quantity.
370 * @param object $product Current product.
371 * @return int
372 */
373 function blocks_cart_max_qty( $cart_max, $product ) {
374 if ( ! empty( $cart_max ) ) {
375 $default_max = (int) get_option( 'isa_woocommerce_max_qty_limit' );
376 $product_max = wc_get_product_max_limit( $product->get_parent_id() ? $product->get_parent_id() : $product->get_id() );
377 // Set product max or default max
378 if ( ! empty( $product_max ) ) {
379 $new_max = $product_max;
380 } elseif ( ! empty( $default_max ) ) {
381 $new_max = $default_max;
382 }
383 if ( ! empty( $new_max ) ) {
384 $cart_max = min( $cart_max, $new_max );
385 }
386 }
387 return $cart_max;
388 }
389
390
391 /* Declare WooCommerce HPOS Compatibility */
392 add_action(
393 'before_woocommerce_init',
394 function () {
395 if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
396 \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
397 \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', __FILE__, true );
398 }
399 }
400 );
401