PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / 1.3.42
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO v1.3.42
1.4.18 1.4.17 1.4.16 1.4.15 1.4.14 1.4.13 1.4.12 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.54 1.3.53 1.3.52 1.3.51 1.3.50 1.3.49 All 181 releases
← All changes | app/Disco.php +96 -105 1.4.161.3.42 View file →
@@ -10,9 +10,8 @@
10 10 namespace Disco\App;
11 11
12 12 use Disco\App\Calc\CalcFactory;
13 13 use Disco\App\Features\UserLimit;
14 -use Disco\App\Utility\Config;
15 14 use Disco\App\Utility\Settings;
16 15
17 16 /**
18 17 * Class Disco
@@ -47,8 +46,12 @@
47 46 * @return float Product Price.
48 47 * @throws \Exception If setting is not found.
49 48 */
50 49 public function get_product_discounted_price( $price, $product ) {//phpcs:ignore
50 + if ( Settings::get( 'discount_priority_type' ) === 'woocommerce_coupon' ) {
51 + return $price; // Return original price if WooCommerce coupon is used.
52 + }
53 +
51 54 if ( $product instanceof \WC_Product ) {
52 55 // Init product base intents and exclude cart-based intents.
53 56 $this->intents = $this->prepare_intents( array( 'Product' ) );
54 57
@@ -65,8 +68,10 @@
65 68 }
66 69
67 70 $discounts = array();
68 71
72 + $prev_discount = PHP_INT_MAX;
73 +
69 74 // Foreach intent, apply the discount.
70 75 foreach ( $this->intents as $intent ) {
71 76 /**
72 77 * Get a discount limit form campaign.
@@ -72,9 +77,18 @@
72 77 * Get a discount limit form campaign.
73 78 *
74 79 * Compare with total product meta and apply discount
75 80 */
76 - if ( ( new UserLimit )->disco_is_limit_reached( $intent->campaign ) ) {
81 + $discount_limit = $intent->campaign->discount_max_user;
82 + $total_applied_campaign = ( new UserLimit )->disco_get_total_applied_campaign( $intent->campaign->id );
83 +
84 + if (
85 + ! empty( $discount_limit )
86 + && (
87 + $discount_limit >= 0
88 + && $total_applied_campaign >= $discount_limit
89 + )
90 + ) {
77 91 continue;
78 92 }
79 93
80 94 $discount = $intent->get_discounts( (float) $price, $product );
@@ -82,30 +96,15 @@
82 96 if ( empty( $discount ) ) {
83 97 continue;
84 98 }
85 99
86 - /**
87 - * Normalize each campaign's amount before the amounts compete.
88 - *
89 - * A percent amount is derived from the product price, which a
90 - * currency switcher has already converted, so it arrives in the
91 - * active currency. A fixed amount is the rule value as typed, so
92 - * it arrives in the store's base currency. Comparing the two
93 - * directly picks a winner by exchange rate rather than by size:
94 - * percent scales with the rate and fixed does not, so `max` would
95 - * select percent on almost every product and `min` would select
96 - * fixed. Converting here, with each campaign's own discount type,
97 - * puts every amount in the same currency before min/max runs.
98 - *
99 - * Filtering per campaign also matches what CartIntent and the
100 - * Calc classes already do, so every path now compares like for
101 - * like.
102 - */
103 - $discounts[ $intent->campaign->id ] = (float) apply_filters(
104 - 'disco_final_discounted_amount',
105 - (float) $discount,
106 - $this->campaign_discount_type( $intent->campaign )
107 - );
100 + // Get applied discount campaign.
101 + if ( $discount > 0 && $prev_discount > $discount ) {
102 + $this->applied_campaign_id = $intent->campaign->id;
103 + $prev_discount = $discount;
104 + }
105 +
106 + $discounts[] = $discount;
108 107 }
109 108
110 109 // If no discount is applied, return the original price.
111 110 if ( empty( $discounts ) ) {
@@ -111,12 +110,16 @@
111 110 if ( empty( $discounts ) ) {
112 111 return $price;
113 112 }
114 113
115 - // Pick the winning discount amount, then map it back to the campaign that produced it.
116 - $discounted_amount = $this->min_max_average( array_values( $discounts ) );
117 - $this->applied_campaign_id = (int) array_search( $discounted_amount, $discounts, true );
114 + $discount_type = $intent->campaign->discount_rules[0]['discount_type'];
118 115
116 + /**
117 + * Get the min or max discount amount according to plugin settings.
118 + * Apply the filter to modify final discounted amount based on discount types.
119 + */
120 + $discounted_amount = apply_filters( 'disco_final_discounted_amount', $this->min_max_average( $discounts ), $discount_type );
121 +
119 122 if ( $discounted_amount <= $price ) {
120 123 // Get an applied campaign from DiscountLimit class.
121 124 ( new UserLimit )->disco_start_session_on_checkout( $this->applied_campaign_id );
122 125
@@ -140,8 +143,12 @@
140 143 if ( $cart->is_empty() ) {
141 144 return $cart;
142 145 }
143 146
147 + if ( Settings::get( 'discount_priority_type' ) === 'woocommerce_coupon' ) {
148 + return $cart;
149 + }
150 +
144 151 // Init Cart - Based Intents except Product & Shipping Intent.
145 152 $this->intents = $this->prepare_intents( array( 'Cart' ) );
146 153
147 154 if ( ! empty( $this->intents ) && $cart->get_cart_contents_count() > 0 ) {
@@ -153,9 +160,18 @@
153 160 * Get a discount limit form campaign.
154 161 *
155 162 * Compare with total product meta and apply discount
156 163 */
157 - if ( ( new UserLimit )->disco_is_limit_reached( $intent->campaign ) ) {
164 + $discount_limit = $intent->campaign->discount_max_user;
165 + $total_applied_campaign = ( new UserLimit )->disco_get_total_applied_campaign( $intent->campaign->id );
166 +
167 + if (
168 + ! empty( $discount_limit )
169 + && (
170 + $discount_limit >= 0
171 + && $total_applied_campaign >= $discount_limit
172 + )
173 + ) {
158 174 continue;
159 175 }
160 176
161 177 $items = $this->get_items_for_discount( $cart, $intent->campaign );
@@ -195,10 +211,8 @@
195 211 if ( ! empty( $discount_label ) ) {
196 212 $label = $discount_label;
197 213 }
198 214
199 - $cart_fee = $this->get_discount_exclude_tax( $cart_fee, $cart );
200 -
201 215 $cart->add_fee( $label, -$cart_fee );
202 216 $cart->set_session();
203 217 }
204 218 }
@@ -221,8 +235,12 @@
221 235 if ( ! defined( 'DOING_AJAX' ) && is_admin() ) {
222 236 return $cart;
223 237 }
224 238
239 + if ( Settings::get( 'discount_priority_type' ) === 'woocommerce_coupon' ) {
240 + return $cart;
241 + }
242 +
225 243 // Init Cart - Based Intents except Product & Shipping Intent.
226 244 $this->intents = $this->prepare_intents( array( 'Bulk', 'Bundle', 'BOGO' ) );
227 245
228 246 $discounts = $this->prepare_item_discounts( $this->intents, $cart );
@@ -251,10 +269,8 @@
251 269 $total_discount += $discounts[ $id ];
252 270 }
253 271
254 272 if ( $total_discount > 0 ) {
255 - $total_discount = $this->get_discount_exclude_tax( $total_discount, $cart );
256 -
257 273 $cart->add_fee( __( 'Discount', 'disco' ), -$total_discount );
258 274 }
259 275
260 276 $cart->set_session();
@@ -270,21 +286,25 @@
270 286 * @param \WC_Cart $cart WooCommerce cart object to calculate discounts for.
271 287 * @return array|false|\WC_Cart Cart object with applied discounts or false if no discounts are applicable.
272 288 */
273 289 public function get_cart_items_discount_for_bogo( $cart ) {//phpcs:ignore
274 - if ( ! $this->cart_is_valid() ) {
275 - return $cart;
276 - }
290 + if ( ! $this->cart_is_valid() ) {
291 + return $cart;
292 + }
277 293
278 - if ( ! defined( 'DOING_AJAX' ) && is_admin() ) {
279 - return $cart;
294 + if ( ! defined( 'DOING_AJAX' ) && is_admin() ) {
295 + return $cart;
296 + }
297 +
298 + if ( Settings::get( 'discount_priority_type' ) === 'woocommerce_coupon' ) {
299 + return $cart;
280 300 }
281 301
282 - // Init Cart - Based Intents except Product & Shipping Intent.
283 - $this->intents = $this->prepare_intents( array( 'BOGO' ) );
302 + // Init Cart - Based Intents except Product & Shipping Intent.
303 + $this->intents = $this->prepare_intents( array( 'BOGO' ) );
284 304
285 305 return $this->prepare_item_discounts_bogo_free( $this->intents, $cart );
286 - }
306 + }
287 307
288 308 /**
289 309 * Apply the discount to shipping.
290 310 *
@@ -290,56 +310,55 @@
290 310 *
291 311 * @return bool
292 312 */
293 313 public function apply_free_shipping() { //phpcs:ignore
294 - return ! empty( $this->get_applied_free_shipping_campaign_ids() );
295 - }
314 + if ( ! $this->cart_is_valid() ) {
315 + return false;
316 + }
296 317
297 - /**
298 - * Evaluate the Shipping intents and return the IDs of every campaign that
299 - * currently grants free shipping (respecting per-user usage limits).
300 - *
301 - * This is the single source of truth for "which free-shipping campaigns
302 - * apply right now". It is used both to decide whether to add a free shipping
303 - * rate and to persist the campaign IDs to the order meta at checkout. It does
304 - * NOT rely on the WC session or cached shipping rates, so it is safe to call
305 - * during order creation.
306 - *
307 - * @return array<int> Applied free-shipping campaign IDs.
308 - */
309 - public function get_applied_free_shipping_campaign_ids() {
310 - if ( ! $this->cart_is_valid() ) {
311 - return array();
318 + $cart = WC()->cart;
319 +
320 + if ( Settings::get( 'discount_priority_type' ) === 'woocommerce_coupon' ) {
321 + return false; // Return false if WooCommerce coupon is used.
312 322 }
313 323
314 - $cart = WC()->cart;
315 324 $this->intents = $this->prepare_intents( array( 'Shipping' ) );
316 - $campaign_ids = array();
325 + $shippings = array();
317 326
318 - if ( empty( $this->intents ) ) {
319 - return $campaign_ids;
320 - }
327 + if ( ! empty( $this->intents ) ) {
328 + foreach ( $this->intents as $intent ) {
329 + /**
330 + * Get a discount limit form campaign.
331 + *
332 + * Compare with total product meta and apply discount
333 + */
334 + $discount_limit = $intent->campaign->discount_max_user;
335 + $applied_campaign_id = $intent->campaign->id;
336 + $total_applied_campaign = ( new UserLimit )->disco_get_total_applied_campaign( $intent->campaign->id );
321 337
322 - foreach ( $this->intents as $intent ) {
323 - /**
324 - * Get a discount limit from campaign.
325 - *
326 - * Compare with total applied count and skip if the user limit is hit.
327 - */
328 - if ( ( new UserLimit )->disco_is_limit_reached( $intent->campaign ) ) {
329 - continue;
330 - }
338 + if (
339 + ! empty( $discount_limit )
340 + && (
341 + $discount_limit >= 0
342 + && $total_applied_campaign >= $discount_limit
343 + )
344 + ) {
345 + continue;
346 + }
331 347
332 - $items = $this->get_items_for_discount( $cart, $intent->campaign );
348 + $items = $this->get_items_for_discount( $cart, $intent->campaign );
349 + $shippings[] = $intent->get_discounts( $items, $cart );
333 350
334 - if ( 'free_shipping' !== $intent->get_discounts( $items, $cart ) ) {
335 - continue;
351 + if ( !in_array( 'free_shipping', $shippings, true ) ) {
352 + continue;
353 + }
354 +
355 + // Get an applied campaign from DiscountLimit class.
356 + ( new UserLimit )->disco_start_session_on_checkout( $applied_campaign_id );
336 357 }
337 -
338 - $campaign_ids[] = (int) $intent->campaign->id;
339 358 }
340 359
341 - return $campaign_ids;
360 + return in_array( 'free_shipping', $shippings, true );
342 361 }
343 362
344 363 /**
345 364 * Get offers for a product.
@@ -454,35 +473,7 @@
454 473 return false;
455 474 }
456 475
457 476 return in_array( $page_name, $pages, true );
458 - }
459 -
460 - /**
461 - * Resolve a campaign's discount type.
462 - *
463 - * Read through get_discount_rules() rather than the raw config property.
464 - * That accessor is what the intents use, so the type reported here is the
465 - * one the discount was actually calculated with: it decodes a rules payload
466 - * stored as JSON and fills in the defaults, neither of which the magic
467 - * property does. The property is also undeclared, so PHPStan cannot see it.
468 - *
469 - * @param \Disco\App\Utility\Config $campaign Campaign config.
470 - * @return string Discount type, or an empty string when it cannot be determined.
471 - */
472 - private function campaign_discount_type( Config $campaign ): string {
473 - $rules = $campaign->get_discount_rules();
474 -
475 - if ( ! is_array( $rules ) || ! isset( $rules[0] ) || ! is_object( $rules[0] ) ) {
476 - return '';
477 - }
478 -
479 - $discount_type = $rules[0]->discount_type ?? '';
480 -
481 - if ( ! is_scalar( $discount_type ) ) {
482 - return '';
483 - }
484 -
485 - return (string) $discount_type;
486 477 }
487 478
488 479 }