PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / 1.2.18
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO v1.2.18
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 1.3.48 1.3.47 1.3.46 All 178 releases
disco / app / Intents / IntentHelper.php

IntentHelper.php in Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO 1.2.18, at app/Intents/IntentHelper.php

604 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php //phpcs:ignore
2
3 /**
4 * Intent Helper Trait.
5 *
6 * @package Disco
7 * @subpackage \App\Intents\IntentHelper.php
8 * @since 1.0.0
9 */
10
11 namespace Disco\App\Intents;
12
13 use Disco\App\Calc\CalcFactory;
14 use Disco\App\Campaign;
15 use Disco\App\Utility\Config;
16 use Disco\App\Utility\Helper;
17 use Disco\App\Utility\Settings;
18
19 /**
20 * This Class contains all the common function for cart related intents,
21 * and the intents are Cart, Bulk, Bundle and BOGO
22 *
23 * @package Disco
24 * @subpackage Disco\App\Intents
25 * @author Ohidul Islam <wahid0003@gmail.com>
26 * @link https://webappick.com
27 * @license https://opensource.org/licenses/gpl-license.php GNU Public License
28 * @category IntentHelper
29 */
30 trait IntentHelper {//phpcs:ignore
31
32 /**
33 * Disco Prepare Intents.
34 *
35 * @param array $intents Skip Intents.
36 * Either a single intent or an array of intents.
37 * @return array
38 */
39 public function prepare_intents( $intents = [] ) {//phpcs:ignore
40 $campaigns = ( new Campaign )->get_campaigns( '1' );
41
42
43 $new_intents = array();
44
45 $settings = Settings::get();
46
47 if ( empty( $campaigns ) ) {
48 return $new_intents;
49 }
50
51 foreach ( $campaigns as $campaign ) {
52 // Check discount expiration date.
53 if ( ! Helper::is_in_valid_date( $campaign ) ) {
54 continue;
55 }
56
57 // Skip the intent if it is in the skip list.
58 if (
59 ! empty( $intents )
60 && is_array( $intents )
61 && ! in_array( $campaign->discount_intent, $intents, true )
62 ) {
63 continue;
64 }
65
66 if ( $campaign->discount_intent === 'BOGO' ) {
67 $campaign->discount_intent = 'BuyXGetX';
68
69 if ( in_array( $campaign->bogo_type, array( 'products', 'categories' ), true ) ) {
70 $campaign->discount_intent = 'BuyXGetY';
71 }
72 }
73
74 $new_intents[] = IntentFactory::get_intent( $campaign, $settings );
75 }//end foreach
76
77 return $new_intents;
78 }
79
80 /**
81 * Get discount applicable items from the cart.
82 *
83 * @param \WC_Cart $cart Cart Object.
84 * @param \Disco\App\Utility\Config $campaign Campaign Config.
85 * @return array
86 */
87 public function get_items_for_discount( $cart, $campaign ) {
88 $items = array();
89
90 foreach ( $cart->get_cart() as $item ) {
91 $id = $item['product_id'];
92
93 if ( $item['variation_id'] ) {
94 $id = $item['variation_id'];
95 }
96
97 // Continue if the product is not applicable for the campaign.
98 if ( ! $campaign->product_is_applicable( $id ) ) {
99 continue;
100 }
101
102 // Continue if the item is not passed the filter.
103 $product = wc_get_product( $id );
104
105 $info = array( 'product' => $product );
106
107 if ( ! Helper::is_filter_passed( $campaign, $info ) ) {
108 continue;
109 }
110
111 $items[] = $item;
112 }//end foreach
113
114 return $items;
115 }
116
117 /**
118 * Verify the rules.
119 *
120 * @param \Disco\App\Utility\Config $campaign Campaign Config.
121 * @param array $item Cart Item.
122 * @param array $rule Cart Item.
123 * @return bool
124 */
125 public function verify_rules( $campaign, $item, $rule ) {
126 if ( is_object( $rule ) ) {
127 $rule = (array) $rule;
128 }
129
130 $basis = $this->get_basis_for_item( $item );
131
132 if ( empty( $rule ) ) {
133 return false;
134 }
135
136 switch ( $campaign->get_discount_intent() ) {
137 case 'Product':
138 case 'Cart':
139 return isset( $rule['discount_value'], $rule['discount_type'] );
140
141 case 'Bulk':
142 return $this->verify_bulk_rule( $basis, $rule );
143
144 case 'Bundle':
145 return $this->verify_bundle_rule( $basis, $rule );
146
147 case 'BuyXGetX':
148 return $this->verify_buyxgetx_rule( $basis, $item, $rule );
149
150 case 'BuyXGetY':
151 return $this->verify_buyxgety_rule( $campaign, $basis, $item, $rule );
152
153 default:
154 return false;
155 }
156 }
157
158 /**
159 * Get cart item id.
160 *
161 * @param array $item Cart Item.
162 * @return int
163 */
164 public function get_cart_item_id( $item ) {
165 $id = $item['product_id'];
166
167 if ( $item['variation_id'] > 0 ) {
168 $id = $item['variation_id'];
169 }
170
171 return $id;
172 }
173
174 /**
175 * Get the basis for cart.
176 * Returns the quantity or price of the cart.
177 * If the cart is not valid, then return 0.
178 *
179 * @param \Disco\App\Utility\Config $campaign Discount Rules.
180 * @param \WC_Cart $cart Cart .
181 * @return float|int
182 */
183 public function get_basis_for_cart( $campaign, $cart ) {
184 if ( ! $cart instanceof \WC_Cart || $cart->is_empty() ) {
185 return 0;
186 }
187
188 if ( 'cart_quantity' === $campaign->get_discount_based_on() ) {
189 return absint( $cart->get_cart_contents_count() );
190 }
191
192 return abs( $cart->get_subtotal() );
193 }
194
195 /**
196 * Get the basis for item.
197 * Returns the quantity or price of the item.
198 * If the item is not valid, then return 0.
199 *
200 * @param array $item Cart Item.
201 * @return int
202 */
203 public function get_basis_for_item( $item ) {
204 if ( empty( $item['quantity'] ) ) {
205 return 0;
206 }
207
208 return absint( $item['quantity'] );
209 }
210
211 /**
212 * Calculate discount.
213 * Returns the discount amount.
214 *
215 * @param float $cost Item Price or Cart Subtotal.
216 * @param string $discount_type Discount Type.
217 * @param float $discount_value Discount Value.
218 * @return float
219 */
220 public function calculate_discount( $cost, $discount_type, $discount_value ) {
221 $cost = (float) $cost;
222 $discount_value = (float) $discount_value;
223
224 if ( 'percent' === $discount_type ) {
225 $cost = $cost * $discount_value / 100;
226 }
227
228 if ( 'fixed' === $discount_type || 'fixed_per_product' === $discount_type ) {
229 $cost = $discount_value;
230 }
231
232 return $cost;
233 }
234
235 /**
236 * Get the discounted price.
237 *
238 * @param float $cost Item Price or Cart Subtotal.
239 * @param float $discounted_amount Discounted Amount.
240 * @return float Discounted Price.
241 */
242 public function discounted_price( $cost, $discounted_amount ) {
243 $discounted_price = $cost - $discounted_amount;
244
245 if ( $discounted_price < 0 ) {
246 $discounted_price = $cost;
247 }
248
249 return $discounted_price;
250 }
251
252 /**
253 * Get the discounted amount.
254 *
255 * @param float $cost Item Price or Cart Subtotal.
256 * @param float $discounted_amount Discounted Amount.
257 */
258 public function get_amount_after_discount( float $cost, float $discounted_amount ): float {
259 $discounted_amount = $cost - $discounted_amount;
260
261 if ( $discounted_amount < 0 ) {
262 $discounted_amount = $cost;
263 }
264
265 return $discounted_amount;
266 }
267
268 /**
269 * Check if a number is a multiple of another number for recursive discount.
270 *
271 * @param float|int $number Number to check.
272 * @param float|int $of Number to check against.
273 * @return bool Returns true if the number is a multiple of the given number, false otherwise.
274 */
275 public function is_multiple( $number, $of ) {
276 if ( $of >= $number ) {
277 return (float) $of % (float) $number === 0;
278 }
279
280 return false;
281 }
282
283 /**
284 * Check if a product is in a category.
285 *
286 * @param int $product_id Product ID.
287 * @param array $categories Categories.
288 * @return bool
289 */
290 public function is_in_category( $product_id, $categories ) {
291 // Ensure $categories is an array
292 if ( ! is_array( $categories ) ) {
293 $categories = array( $categories );
294 }
295
296 $terms = get_the_terms( $product_id, 'product_cat' );
297
298 if ( ! $terms || is_wp_error( $terms ) ) {
299 return false;
300 }
301
302 $term_ids = wp_list_pluck( $terms, 'term_id' );
303 $common_values = array_intersect( $term_ids, $categories );
304
305 return ! empty( $common_values );
306 }
307
308 /**
309 * Prepare a discount array for cart.
310 * Returns the discount array.
311 *
312 * @param \Disco\App\Utility\Config $campaign Campaign Config.
313 * @param array $items Cart Item Object.
314 * @param \WC_Cart $cart Cart Object.
315 */
316 public function get_item_discounts( Config $campaign, array $items, \WC_Cart $cart ): array {//phpcs:ignore
317 $discounts = array();
318 // $cart = WC()->cart;
319 // Get the discount rules.
320 $rules = $campaign->get_discount_rules();
321
322 // Check if the cart is not empty.
323 if ( empty( $items ) ) {
324 return $discounts;
325 }
326
327 // Loop through the cart items.
328 foreach ( $items as $item ) {
329 // Get the item object.
330 $item_objet = $item['data'];
331
332 // Check if the campaign has rules.
333 if ( ! is_array( $rules ) || empty( $rules ) ) {
334 continue;
335 }
336
337 // Loop through the discount rules.
338 foreach ( $rules as $rule ) {
339 if ( is_object( $rule ) ) {
340 $rule = (array) $rule;
341 }
342
343 // Verify the rule by campaign settings.
344 if ( ! $this->verify_rules( $campaign, $item, $rule ) ) {
345 continue;
346 }
347
348 $r = md5( microtime() . wp_rand() );
349 $id = $this->get_cart_item_id( $item );
350
351 // Set the discount array.
352 $discounts[ $id ]['original_price'] = $item_objet->get_price();
353 $discounts[ $id ]['cart_item_key'] = $item['key'];
354
355 // Set discount applies to
356 $discounts[ $id ]['discount_applies_to'][ $r ] = CalcFactory::discount_applies_to( $rule, $campaign );
357
358 // Set Discounts.
359 if ( $rule['discount_type'] === 'free' ) {
360 $discounted_amount = array(
361 'price' => $item_objet->get_price(),
362 'discount' => 0,
363 'line_subtotal' => $item_objet->get_price() * $item['quantity'],
364 'discounted_quantities' => 0,
365 );
366 $discounts[ $id ]['free'] = true;
367 $discounts[ $id ]['get_ids'] = $rule['get_ids'];
368 $discounts[ $id ]['get_qty'] = $rule['get_quantity'];
369 } else {
370 $discounted_amount = CalcFactory::get_discount( $rule, $item, $cart, $campaign );
371 $discounts[ $id ]['free'] = false;
372 $discounts[ $id ]['get_ids'] = array();
373 $discounts[ $id ]['get_qty'] = 0;
374 }
375
376 // $discounts[ $id ]['get_ids'] = array_values( $rule['get_ids'] );
377
378 $discounts[ $id ]['discount_types'][ $r ] = $rule['discount_type'];
379 $discounts[ $id ]['intent'][ $r ] = $campaign->get_discount_intent();
380 $discounts[ $id ]['prices'][ $r ] = $discounted_amount['price'];
381 $discounts[ $id ]['discounts'][ $r ] = $discounted_amount['discount'];
382 $discounts[ $id ]['subtotals'][ $r ] = $discounted_amount['line_subtotal'];
383 $discounts[ $id ]['discounted_quantities'][ $r ] = $discounted_amount['discounted_quantities'];
384 $discounts[ $id ]['quantities'][ $r ] = $item['quantity'];
385 $offers = $this->get_item_offers( $rule );
386
387 $discounts[ $id ]['offers'][ $r ] = $offers;
388 }//end foreach
389 }//end foreach
390
391 return $discounts;
392 }
393
394 /**
395 * Prepare discounts for cart items.
396 *
397 * @param array $intents Intents.
398 * @param \WC_Cart $cart Cart.
399 * @return array|false
400 */
401 public function prepare_item_discounts( array $intents, \WC_Cart $cart ) {
402 if ( empty( $intents ) ) {
403 return false;
404 }
405
406 $discounts = array();
407
408 // Loop through the intents.
409 foreach ( $intents as $intent ) {
410 $items = $this->get_items_for_discount( $cart, $intent->campaign );
411 $get_discounts = $intent->get_discounts( $items, $cart );
412
413 if ( empty( $get_discounts ) ) {
414 continue;
415 }
416
417 foreach ( $get_discounts as $item_id => $discount ) {
418 $discounts[ $item_id ]['discounts'][] = max( $discount['discounts'] );
419 }
420 }
421
422 // Check if the discounts are empty.
423 if ( empty( $discounts ) ) {
424 return false;
425 }
426
427 // Get the min or max discount amount for each item.
428 foreach ( $discounts as $item_id => $discount ) {
429 $discounts[ $item_id ] = $this->min_max_average( $discount['discounts'] );
430 }
431
432 return $discounts;
433 }
434
435 /**
436 * Is it a bogo campaign?
437 *
438 * @param \Disco\App\Utility\Config $campaign Campaign Config.
439 */
440 public function is_bogo( Config $campaign ): bool {
441 return in_array( $campaign->get_discount_intent(), array( 'BuyXGetX', 'BuyXGetY' ), true );
442 }
443
444 /**
445 * Check cart is valid or not
446 */
447 public function cart_is_valid(): bool {
448 return WC()->cart instanceof \WC_Cart && ! WC()->cart->is_empty();
449 }
450
451 /**
452 * Get min or max price from an array discounted prices according to plugin settings.
453 *
454 * @param array $discounts Discounted Amounts.
455 */
456 public function min_max_average( array $discounts ): float {
457 $calculation_type = Settings::get( 'min_max_discount_amount' );
458
459 if ( empty( $discounts ) ) {
460 return 0.00;
461 }
462
463 if ( 'max' === $calculation_type ) {
464 return max( $discounts );
465 }
466
467 return min( $discounts );
468 }
469
470 /**
471 * Get Offer Label.
472 *
473 * @param array $rule Discount Rule.
474 */
475 private function get_item_offers( array $rule ): array {
476 $label = '';
477 $quantity = $rule['min'];
478
479 if ( ! empty( $rule['max'] ) ) {
480 $quantity .= ' - ' . $rule['max'];
481 }
482
483 if ( 'free' === $rule['discount_type'] ) {
484 $label = 'Free';
485 }
486
487 if ( 'percent' === $rule['discount_type'] ) {
488 $label = $rule['discount_value'] . '% off';
489 }
490
491 if ( 'fixed' === $rule['discount_type'] ) {
492 $label = wc_price( $rule['discount_value'] ) . ' off';
493 }
494
495 if ( ! empty( $rule['get_quantity'] ) ) {
496 $quantity = $rule['get_quantity'];
497 }
498
499 return array(
500 'quantity' => $quantity,
501 'label' => $label,
502 );
503 }
504
505 /**
506 * Verify Bulk Intent Rules.
507 *
508 * @param int $quantity Item Quantity.
509 * @param array $rule Discount Rules.
510 */
511 private function verify_bulk_rule( int $quantity, array $rule ): bool {
512 $min = abs( $rule['min'] );
513
514 if ( empty( $rule['max'] ) ) {
515 $max = PHP_INT_MAX;
516 } else {
517 $max = abs( $rule['max'] );
518 }
519
520 if ( $quantity >= $min && $quantity <= $max ) {
521 return true;
522 }
523
524 return $quantity >= $max;
525 }
526
527 /**
528 * Verify Bundle Intent Rules.
529 *
530 * @param int $quantity Item Quantity.
531 * @param array $rule Discount Rules.
532 */
533 private function verify_bundle_rule( int $quantity, array $rule ): bool {
534 $rule_basis = abs( $rule['min'] );
535
536 if ( $rule['recursive'] === 'yes' ) {
537 if ( $quantity >= $rule_basis && ( $quantity % $rule_basis === 0 || $quantity > $rule_basis ) ) {
538 return true;
539 }
540 } elseif ( $quantity >= $rule_basis ) {
541 return true;
542 }
543
544 return false;
545 }
546
547 /**
548 * Verify BuyXGetX Intent Rules.
549 *
550 * @param float $quantity Item Quantity.
551 * @param array $item Cart Item.
552 * @param array $rule Discount Rules.
553 */
554 private function verify_buyxgetx_rule( float $quantity, array $item, array $rule ): bool {//phpcs:ignore
555
556 $verified = ( $quantity >= $rule['min'] && $quantity <= $rule['max'] );
557
558 if ( $verified && $rule['recursive'] === 'no' ) {
559 return true;
560 }
561
562 $base_quantity = abs( $rule['min'] );
563
564 if ( $rule['recursive'] === 'yes' && ! isset( $rule['max'] ) ) {
565 if ( $quantity >= $base_quantity && $quantity % $base_quantity === 0 ) {
566 return true;
567 }
568 } elseif ( $quantity >= $base_quantity ) {
569 return true;
570 }
571
572 return false;
573 }
574
575 /**
576 * Verify BuyXGetY Intent Rules.
577 *
578 * @param \Disco\App\Utility\Config $campaign Campaign Config.
579 * @param float $quantity Item Quantity.
580 * @param array $item Cart Item.
581 * @param array $rule Discount Rules.
582 */
583 private function verify_buyxgety_rule( Config $campaign, float $quantity, array $item, array $rule ): bool {
584 if ( ! is_array( $rule['get_ids'] ) ) {
585 return false;
586 }
587
588 $id = $this->get_cart_item_id( $item );
589
590 $rule_ids = array_column( $rule['get_ids'], 'id' );
591
592 if ( 'products' === $campaign->get_bogo_type() && ! in_array( $id, $rule_ids, true ) ) {
593 return false;
594 }
595
596 if ( 'categories' === $campaign->get_bogo_type() && ! $this->is_in_category( $id, $rule_ids ) ) {
597 return false;
598 }
599
600 return $this->verify_buyxgetx_rule( $quantity, $item, $rule );
601 }
602
603 }
604