PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / 1.3.10
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO v1.3.10
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.3.10, at app/Intents/IntentHelper.php

907 lines 24.0 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\Features\UserLimit;
16 use Disco\App\Utility\Config;
17 use Disco\App\Utility\Helper;
18 use Disco\App\Utility\Settings;
19
20 /**
21 * This Class contains all the common function for cart related intents,
22 * and the intents are Cart, Bulk, Bundle and BOGO
23 *
24 * @package Disco
25 * @subpackage Disco\App\Intents
26 * @author Ohidul Islam <wahid0003@gmail.com>
27 * @link https://webappick.com
28 * @license https://opensource.org/licenses/gpl-license.php GNU Public License
29 * @category IntentHelper
30 */
31 trait IntentHelper {//phpcs:ignore
32
33 /**
34 * Disco Prepare Intents.
35 *
36 * @param array $intents Skip Intents.
37 * Either a single intent or an array of intents.
38 * @return array
39 */
40 public function prepare_intents( $intents = [] ) {//phpcs:ignore
41 $campaigns = ( new Campaign )->get_campaigns( '1' );
42
43
44 $new_intents = array();
45
46 $settings = Settings::get();
47
48 if ( empty( $campaigns ) ) {
49 return $new_intents;
50 }
51
52 foreach ( $campaigns as $campaign ) {
53 // Check discount expiration date.
54 if ( ! Helper::is_in_valid_date( $campaign ) ) {
55 continue;
56 }
57
58 // Skip the intent if it is in the skip list.
59 if (
60 ! empty( $intents )
61 && is_array( $intents )
62 && ! in_array( $campaign->discount_intent, $intents, true )
63 ) {
64 continue;
65 }
66
67 if ( $campaign->discount_intent === 'BOGO' ) {
68 $campaign->discount_intent = 'BuyXGetX';
69
70 if ( in_array( $campaign->bogo_type, array( 'products', 'categories' ), true ) ) {
71 $campaign->discount_intent = 'BuyXGetY';
72 }
73 }
74
75 $new_intents[] = IntentFactory::get_intent( $campaign, $settings );
76 }//end foreach
77
78 return $new_intents;
79 }
80
81 /**
82 * Get discount applicable items from the cart.
83 *
84 * @param \WC_Cart $cart Cart Object.
85 * @param \Disco\App\Utility\Config $campaign Campaign Config.
86 * @return array
87 */
88 public function get_items_for_discount( $cart, $campaign ) { //phpcs:ignore
89 $items = array();
90 $discount_type = $campaign->get_discount_intent();
91 $cart_items = $cart->get_cart();
92 $bogo_type = $campaign->get_bogo_type();
93 $y_products = array();
94 $verified_yproducts = false;
95
96 /**
97 * Check if the discount type is BuyXGetY and the Y product is in the cart.
98 * If so, add the Y product to the items array.
99 */
100 if ( $discount_type === 'BuyXGetY' ) {
101 $rule_ids = $campaign->get_rule_product_ids();
102 $y_products = $this->get_y_product( $rule_ids, $cart_items, $bogo_type );
103 $verified_yproducts = $this->verify_yproduct_in_cart( $rule_ids, $cart_items, $bogo_type );
104 }
105
106 foreach ( $cart_items as $item ) {
107 // Skip free products - they should not count towards BOGO buy quantity
108 if ( ! empty( $item['is_free_product'] ) ) {
109 continue;
110 }
111
112 $id = $item['product_id'];
113
114 if ( $item['variation_id'] ) {
115 $id = $item['variation_id'];
116 }
117
118 // Continue if the product is not applicable for the campaign.
119 if ( ! $campaign->product_is_applicable( $id ) ) {
120 continue;
121 }
122
123 // Continue if the item is not passed the filter.
124 $product = wc_get_product( $id );
125
126 $info = array( 'product' => $product );
127
128 if ( ! Helper::is_filter_passed( $campaign, $info ) ) {
129 continue;
130 }
131
132 /**
133 * Check if the discount type is BuyXGetY and the Y product is in the cart.
134 * If so, add the Y product to the items array.
135 * These products not need to be passed the filter.
136 */
137 if ( $discount_type === 'BuyXGetY' && $verified_yproducts ) {
138 $items = array_merge( $items, $y_products );
139 }
140
141 $items[] = $item;
142 }//end foreach
143
144 return $items;
145 }
146
147 /**
148 * Verify the rules.
149 *
150 * @param \Disco\App\Utility\Config $campaign Campaign Config.
151 * @param array $item Cart Item.
152 * @param array $rule Cart Item.
153 * @return bool
154 */
155 public function verify_rules( $campaign, $item, $rule ) {
156 if ( is_object( $rule ) ) {
157 $rule = (array) $rule;
158 }
159
160 $basis = $this->get_basis_for_item( $item );
161
162 if ( empty( $rule ) ) {
163 return false;
164 }
165
166 switch ( $campaign->get_discount_intent() ) {
167 case 'Product':
168 case 'Cart':
169 return isset( $rule['discount_value'], $rule['discount_type'] );
170
171 case 'Bulk':
172 return $this->verify_bulk_rule( $basis, $rule );
173
174 case 'Bundle':
175 return $this->verify_bundle_rule( $basis, $rule );
176
177 case 'BuyXGetX':
178 return $this->verify_buyxgetx_rule( $basis, $item, $rule );
179
180 case 'BuyXGetY':
181 return $this->verify_buyxgety_rule( $campaign, $basis, $item, $rule );
182
183 default:
184 return false;
185 }
186 }
187
188 /**
189 * Get cart item id.
190 *
191 * @param array $item Cart Item.
192 * @return int
193 */
194 public function get_cart_item_id( $item ) {
195 $id = $item['product_id'];
196
197 if ( $item['variation_id'] > 0 ) {
198 $id = $item['variation_id'];
199 }
200
201 return $id;
202 }
203
204 /**
205 * Get the basis for cart.
206 * Returns the quantity or price of the cart.
207 * If the cart is not valid, then return 0.
208 *
209 * @param \Disco\App\Utility\Config $campaign Discount Rules.
210 * @param \WC_Cart $cart Cart .
211 * @return float|int
212 */
213 public function get_basis_for_cart( $campaign, $cart ) {
214 if ( ! $cart instanceof \WC_Cart || $cart->is_empty() ) {
215 return 0;
216 }
217
218 if ( 'cart_quantity' === $campaign->get_discount_based_on() ) {
219 return absint( $cart->get_cart_contents_count() );
220 }
221
222 return abs( $cart->get_subtotal() );
223 }
224
225 /**
226 * Get the basis for item.
227 * Returns the quantity or price of the item.
228 * If the item is not valid, then return 0.
229 *
230 * @param array $item Cart Item.
231 * @return int
232 */
233 public function get_basis_for_item( $item ) {
234 if ( empty( $item['quantity'] ) ) {
235 return 0;
236 }
237
238 return absint( $item['quantity'] );
239 }
240
241 /**
242 * Calculate discount.
243 * Returns the discount amount.
244 *
245 * @param float $cost Item Price or Cart Subtotal.
246 * @param string $discount_type Discount Type.
247 * @param float $discount_value Discount Value.
248 * @return float
249 */
250 public function calculate_discount( $cost, $discount_type, $discount_value ) {
251 $cost = (float) $cost;
252 $discount_value = (float) $discount_value;
253
254 if ( 'percent' === $discount_type ) {
255 $cost = $cost * $discount_value / 100;
256 }
257
258 if ( 'fixed' === $discount_type || 'fixed_per_product' === $discount_type ) {
259 $cost = $discount_value;
260 }
261
262 return $cost;
263 }
264
265 /**
266 * Get the discounted price.
267 *
268 * @param float $cost Item Price or Cart Subtotal.
269 * @param float $discounted_amount Discounted Amount.
270 * @return float Discounted Price.
271 */
272 public function discounted_price( $cost, $discounted_amount ) {
273 $discounted_price = $cost - $discounted_amount;
274
275 if ( $discounted_price < 0 ) {
276 $discounted_price = $cost;
277 }
278
279 return $discounted_price;
280 }
281
282 /**
283 * Get the discounted amount.
284 *
285 * @param float $cost Item Price or Cart Subtotal.
286 * @param float $discounted_amount Discounted Amount.
287 */
288 public function get_amount_after_discount( float $cost, float $discounted_amount ): float {
289 $discounted_amount = $cost - $discounted_amount;
290
291 if ( $discounted_amount < 0 ) {
292 $discounted_amount = $cost;
293 }
294
295 return $discounted_amount;
296 }
297
298 /**
299 * Check if a number is a multiple of another number for recursive discount.
300 *
301 * @param float|int $number Number to check.
302 * @param float|int $of Number to check against.
303 * @return bool Returns true if the number is a multiple of the given number, false otherwise.
304 */
305 public function is_multiple( $number, $of ) {
306 if ( $of >= $number ) {
307 return (float) $of % (float) $number === 0;
308 }
309
310 return false;
311 }
312
313 /**
314 * Check if a product is in a category.
315 *
316 * @param int $product_id Product ID.
317 * @param array $categories Categories.
318 * @return bool
319 */
320 public function is_in_category( $product_id, $categories ) {
321 // Ensure $categories is an array
322 if ( ! is_array( $categories ) ) {
323 $categories = array( $categories );
324 }
325
326 $get_product = wc_get_product( $product_id );
327
328 if ( ! $get_product ) {
329 return false;
330 }
331
332 if ( $get_product->get_type() === 'variation' ) {
333 $product_id = $get_product->get_parent_id();
334 }
335
336 $terms = get_the_terms( $product_id, 'product_cat' );
337
338 if ( ! $terms || is_wp_error( $terms ) ) {
339 return false;
340 }
341
342 $term_ids = array();
343
344 foreach ( $terms as $term ) {
345 $term_ids[] = $term->term_id;
346
347 // Include all parent category IDs
348 $parent_ids = get_ancestors( $term->term_id, 'product_cat' );
349
350 if ( empty( $parent_ids ) ) {
351 continue;
352 }
353
354 $term_ids = array_merge( $term_ids, $parent_ids );
355 }
356
357 // Remove duplicates just in case
358 $term_ids = array_unique( $term_ids );
359 $common_values = array_intersect( $term_ids, $categories );
360
361 return ! empty( $common_values );
362 }
363
364 /**
365 * Prepare a discount array for cart.
366 * Returns the discount array.
367 *
368 * @param \Disco\App\Utility\Config $campaign Campaign Config.
369 * @param array $items Cart Item Object.
370 * @param \WC_Cart $cart Cart Object.
371 */
372 public function get_item_discounts( Config $campaign, array $items, \WC_Cart $cart ): array {//phpcs:ignore
373 $discounts = array();
374 // $cart = WC()->cart;
375 // Get the discount rules.
376 $rules = $campaign->get_discount_rules();
377
378 // Check if the cart is not empty.
379 if ( empty( $items ) ) {
380 return $discounts;
381 }
382
383 // Loop through the cart items.
384 foreach ( $items as $item ) {
385 // Get the item object.
386 $item_objet = $item['data'];
387
388 // Check if the campaign has rules.
389 if ( ! is_array( $rules ) || empty( $rules ) ) {
390 continue;
391 }
392
393 // Loop through the discount rules.
394 foreach ( $rules as $rule ) {
395 if ( is_object( $rule ) ) {
396 $rule = (array) $rule;
397 }
398
399 // Verify the rule by campaign settings.
400 if ( ! $this->verify_rules( $campaign, $item, $rule ) ) {
401 continue;
402 }
403
404 $r = md5( microtime() . wp_rand() );
405 $id = $this->get_cart_item_id( $item );
406
407 // Set the discount array.
408 $discounts[ $id ]['original_price'] = $item_objet->get_price();
409 $discounts[ $id ]['cart_item_key'] = $item['key'];
410
411 // Set discount applies to
412 $discounts[ $id ]['discount_applies_to'][ $r ] = CalcFactory::discount_applies_to( $rule, $campaign );
413
414 // Set Discounts.
415 if ( $rule['discount_type'] === 'free' ) {
416 $discounted_amount = array(
417 'price' => $item_objet->get_price(),
418 'discount' => 0,
419 'line_subtotal' => $item_objet->get_price() * $item['quantity'],
420 'discounted_quantities' => 0,
421 );
422 $discounts[ $id ]['free'] = true;
423 $discounts[ $id ]['get_ids'] = $rule['get_ids'];
424 $discounts[ $id ]['get_qty'] = CalcFactory::get_free_quantity( $rule, $item );
425 } else {
426 $discounted_amount = CalcFactory::get_discount( $rule, $item, $cart, $campaign );
427 $discounts[ $id ]['free'] = false;
428 $discounts[ $id ]['get_ids'] = array();
429 $discounts[ $id ]['get_qty'] = 0;
430 }
431
432 // $discounts[ $id ]['get_ids'] = array_values( $rule['get_ids'] );
433
434 $discounts[ $id ]['discount_types'][ $r ] = $rule['discount_type'];
435 $discounts[ $id ]['intent'][ $r ] = $campaign->get_discount_intent();
436 $discounts[ $id ]['prices'][ $r ] = $discounted_amount['price'];
437 $discounts[ $id ]['discounts'][ $r ] = $discounted_amount['discount'];
438 $discounts[ $id ]['subtotals'][ $r ] = $discounted_amount['line_subtotal'];
439 $discounts[ $id ]['discounted_quantities'][ $r ] = $discounted_amount['discounted_quantities'];
440 $discounts[ $id ]['quantities'][ $r ] = $item['quantity'];
441 $offers = $this->get_item_offers( $rule );
442
443 $discounts[ $id ]['offers'][ $r ] = $offers;
444 }//end foreach
445 }//end foreach
446
447 return $discounts;
448 }
449
450 /**
451 * Prepare discounts for cart items.
452 *
453 * @param array $intents Intents.
454 * @param \WC_Cart $cart Cart.
455 * @return array|false
456 */
457 public function prepare_item_discounts( array $intents, \WC_Cart $cart ) { //phpcs:ignore
458 if ( empty( $intents ) ) {
459 return false;
460 }
461
462 $discounts = array();
463 $applied_campaign_id = 0;
464
465 // Loop through the intents.
466 foreach ( $intents as $intent ) {
467 /**
468 * Get a discount limit form campaign.
469 *
470 * Compare with total product meta and apply discount
471 */
472 $discount_limit = $intent->campaign->discount_max_user;
473 $applied_campaign_id = $intent->campaign->id;
474 $total_applied_campaign = ( new UserLimit )->disco_get_total_applied_campaign( $intent->campaign->id );
475
476 if ( ! empty( $discount_limit ) && ( $discount_limit >= 0 && $total_applied_campaign >= $discount_limit ) ) {
477 continue;
478 }
479
480 $items = $this->get_items_for_discount( $cart, $intent->campaign );
481 $get_discounts = $intent->get_discounts( $items, $cart );
482
483 if ( empty( $get_discounts ) ) {
484 continue;
485 }
486
487 foreach ( $get_discounts as $item_id => $discount ) {
488 $discounts[ $item_id ]['discounts'][] = max( $discount['discounts'] );
489 }
490 }
491
492 // Check if the discounts are empty.
493 if ( empty( $discounts ) ) {
494 return false;
495 }
496
497 // Get the min or max discount amount for each item.
498 foreach ( $discounts as $item_id => $discount ) {
499 $discounts[ $item_id ] = $this->min_max_average( $discount['discounts'] );
500 }
501
502 // Set applied campaign on DiscountLimit class.
503 ( new UserLimit )->disco_start_session_on_checkout( $applied_campaign_id );
504
505 return $discounts;
506 }
507
508 /**
509 * Get valid BOGO category item ID.
510 *
511 * @param array $intents Intents.
512 * @param \WC_Cart $cart Cart.
513 * @param array $discounts Discounts.
514 * @return int|null
515 */
516 public function get_valid_bogo_category_item_id( array $intents, \WC_Cart $cart, array $discounts ) {
517 foreach ( $intents as $intent ) {
518 if (
519 $intent->campaign->get_discount_intent() !== 'BuyXGetY' ||
520 $intent->campaign->get_bogo_type() !== 'categories'
521 ) {
522 continue;
523 }
524
525 foreach ( $cart->get_cart() as $item ) {
526 $item_id = $this->get_cart_item_id( $item );
527
528 if ( isset( $discounts[ $item_id ] ) && $discounts[ $item_id ] > 0 ) {
529 return $item_id;
530 }
531 }
532 }
533
534 return null;
535 }
536
537 /**
538 * Prepare discounts for cart items.
539 *
540 * @param array $intents Intents.
541 * @param \WC_Cart $cart Cart.
542 * @return array|false
543 */
544 public function prepare_item_discounts_bogo_free( array $intents, \WC_Cart $cart ) { //phpcs:ignore
545 if ( empty( $intents ) ) {
546 return false;
547 }
548
549 $discounts = array();
550
551 $applied_campaign_id = 0;
552
553 // Loop through the intents.
554 foreach ( $intents as $intent ) {
555 /**
556 * Get a discount limit form campaign.
557 *
558 * Compare with total product meta and apply discount
559 */
560 $discount_limit = $intent->campaign->discount_max_user;
561 $applied_campaign_id = $intent->campaign->id;
562 $total_applied_campaign = ( new UserLimit )->disco_get_total_applied_campaign( $intent->campaign->id );
563
564 if ( ! empty( $discount_limit ) && ( $discount_limit >= 0 && $total_applied_campaign >= $discount_limit ) ) {
565 continue;
566 }
567
568 $items = $this->get_items_for_discount( $cart, $intent->campaign );
569 $get_discounts = $intent->get_discounts( $items, $cart );
570
571 if ( empty( $get_discounts ) ) {
572 continue;
573 }
574
575 foreach ( $get_discounts as $item_id => $discount ) {
576 $discounts[ $item_id ]['discounts'][] = max( $discount['discounts'] );
577 $discounts[ $item_id ]['free'] = $discount['free'];
578 $discounts[ $item_id ]['get_ids'] = $discount['get_ids'];
579 $discounts[ $item_id ]['get_qty'] = $discount['get_qty'];
580 $discounts[ $item_id ]['bogo_type'] = $intent->campaign->get_bogo_type();
581 }
582 }
583
584 // Check if the discounts are empty.
585 if ( empty( $discounts ) ) {
586 return false;
587 }
588
589 // Get the min or max discount amount for each item.
590 foreach ( $discounts as $item_id => $discount ) {
591 $discounts['discount'] = $this->min_max_average( $discount['discounts'] );
592 $discounts['free'] = $discount['free'];
593 $discounts['get_ids'] = $discount['get_ids'] ? array_column( $discount['get_ids'], 'id' ) : array( $item_id ); //@phpcs:ignore
594 $discounts['get_qty'] = $discount['get_qty'];
595 $discounts['bogo_type'] = $discount['bogo_type'] ?? 'products';
596 }
597
598 // Set applied campaign on DiscountLimit class.
599 ( new UserLimit )->disco_start_session_on_checkout( $applied_campaign_id );
600
601 return $discounts;
602 }
603
604 /**
605 * Is it a bogo campaign?
606 *
607 * @param \Disco\App\Utility\Config $campaign Campaign Config.
608 */
609 public function is_bogo( Config $campaign ): bool {
610 return in_array( $campaign->get_discount_intent(), array( 'BuyXGetX', 'BuyXGetY' ), true );
611 }
612
613 /**
614 * Check cart is valid or not
615 */
616 public function cart_is_valid(): bool {
617 return WC()->cart instanceof \WC_Cart && ! WC()->cart->is_empty();
618 }
619
620 /**
621 * Get min or max price from an array discounted prices according to plugin settings.
622 *
623 * @param array $discounts Discounted Amounts.
624 */
625 public function min_max_average( array $discounts ): float {
626 $calculation_type = Settings::get( 'min_max_discount_amount' );
627
628 if ( empty( $discounts ) ) {
629 return 0.00;
630 }
631
632 if ( 'max' === $calculation_type ) {
633 return max( $discounts );
634 }
635
636 return min( $discounts );
637 }
638
639 /**
640 * Get Offer Label.
641 *
642 * @param array $rule Discount Rule.
643 */
644 private function get_item_offers( array $rule ): array {
645 $label = '';
646 $quantity = $rule['min'];
647
648 if ( ! empty( $rule['max'] ) ) {
649 $quantity .= ' - ' . $rule['max'];
650 }
651
652 if ( 'free' === $rule['discount_type'] ) {
653 $label = 'Free';
654 }
655
656 if ( 'percent' === $rule['discount_type'] ) {
657 $label = $rule['discount_value'] . '% off';
658 }
659
660 if ( 'fixed' === $rule['discount_type'] ) {
661 $label = wc_price( $rule['discount_value'] ) . ' off';
662 }
663
664 if ( ! empty( $rule['get_quantity'] ) ) {
665 $quantity = $rule['get_quantity'];
666 }
667
668 return array(
669 'quantity' => $quantity,
670 'label' => $label,
671 );
672 }
673
674 /**
675 * Verify Bulk Intent Rules.
676 *
677 * @param int $quantity Item Quantity.
678 * @param array $rule Discount Rules.
679 */
680 private function verify_bulk_rule( int $quantity, array $rule ): bool {
681 $min = abs( $rule['min'] );
682
683 if ( empty( $rule['max'] ) ) {
684 $max = PHP_INT_MAX;
685 } else {
686 $max = abs( $rule['max'] );
687 }
688
689 if ( $quantity >= $min && $quantity <= $max ) {
690 return true;
691 }
692
693 return $quantity >= $max;
694 }
695
696 /**
697 * Verify Bundle Intent Rules.
698 *
699 * @param int $quantity Item Quantity.
700 * @param array $rule Discount Rules.
701 */
702 private function verify_bundle_rule( int $quantity, array $rule ): bool {
703 $rule_basis = abs( $rule['min'] );
704
705 if ( $rule['recursive'] === 'yes' ) {
706 if ( $quantity >= $rule_basis && ( $quantity % $rule_basis === 0 || $quantity > $rule_basis ) ) {
707 return true;
708 }
709 } elseif ( $quantity >= $rule_basis ) {
710 return true;
711 }
712
713 return false;
714 }
715
716 /**
717 * Verify BuyXGetX Intent Rules.
718 *
719 * @param float $quantity Item Quantity.
720 * @param array $item Cart Item.
721 * @param array $rule Discount Rules.
722 */
723 private function verify_buyxgetx_rule( float $quantity, array $item, array $rule ): bool {//phpcs:ignore
724
725 $verified = ( $quantity >= $rule['min'] && $quantity <= $rule['max'] );
726
727 if ( $verified && $rule['recursive'] === 'no' ) {
728 return true;
729 }
730
731 $base_quantity = abs( $rule['min'] );
732
733 if ( $rule['recursive'] === 'yes' && ! isset( $rule['max'] ) ) {
734 if ( $quantity >= $base_quantity && $quantity % $base_quantity === 0 ) {
735 return true;
736 }
737 } elseif ( $quantity >= $base_quantity ) {
738 return true;
739 }
740
741 return false;
742 }
743
744 /**
745 * Verify BuyXGetY Intent Rules.
746 *
747 * @param \Disco\App\Utility\Config $campaign Campaign Config.
748 * @param float $quantity Item Quantity.
749 * @param array $item Cart Item.
750 * @param array $rule Discount Rules.
751 */
752 private function verify_buyxgety_rule( Config $campaign, float $quantity, array $item, array $rule ): bool {
753 if ( ! is_array( $rule['get_ids'] ) ) {
754 return false;
755 }
756
757 $id = $this->get_cart_item_id( $item );
758 $rule_ids = array_column( $rule['get_ids'], 'id' );
759
760 /**
761 * This code use for automatically apply free items rule for all product BOGO campaigns.
762 */
763 if ( $rule['discount_type'] === 'free' && 'all' === $campaign->get_bogo_type() ) {
764 return $this->verify_buyxgetx_rule( $quantity, $item, $rule );
765 }
766
767 /**
768 * This code use for automatically apply free items rule product based BOGO campaigns.
769 */
770 if ( $rule['discount_type'] === 'free' && 'products' === $campaign->get_bogo_type() ) {
771 return $this->verify_xproduct_cart_rule( $campaign, $rule );
772 }
773
774 if ( 'products' === $campaign->get_bogo_type() && in_array( $id, $rule_ids, true ) ) {
775 return $this->verify_xproduct_cart_rule( $campaign, $rule );
776 }
777
778 if ( 'categories' === $campaign->get_bogo_type() && $this->is_in_category( $id, $rule_ids ) ) {
779 return $this->verify_xproduct_cart_rule( $campaign, $rule );
780 }
781
782 return false;
783 }
784
785 /**
786 * Verify XProduct Cart Rule.
787 *
788 * @param \Disco\App\Utility\Config $campaign Campaign Config.
789 * @param array $rule Discount Rules.
790 */
791 private function verify_xproduct_cart_rule( Config $campaign, array $rule ): bool {
792 $cart = WC()->cart->get_cart();
793
794 foreach ( $cart as $item ) {
795 $quantity = $item['quantity'];
796 $id = $item['product_id'];
797
798 if ( ! empty( $item['variation_id'] ) ) {
799 $id = $item['variation_id'];
800 }
801
802 if ( $campaign->product_is_applicable( $id ) && $this->verify_buyxgetx_rule( $quantity, $item, $rule ) ) {
803 $product = wc_get_product( $id );
804 $info = array( 'product' => $product );
805
806 if ( ! Helper::is_filter_passed( $campaign, $info ) ) {
807 continue;
808 }
809
810 return true;
811 }
812 }
813
814 return false;
815 }
816
817 /**
818 * Verify YProduct in Cart.
819 *
820 * @param array $rule_ids Rule Product IDs.
821 * @param array $cart Cart.
822 * @param string $bogo_type BOGO Type.
823 * @return bool Returns true if a valid Y product is found in the cart, false otherwise.
824 */
825 private function verify_yproduct_in_cart( array $rule_ids, array $cart, string $bogo_type ): bool { //phpcs:ignore
826 if ( 'categories' === $bogo_type && ! empty( $rule_ids ) ) {
827 foreach ( $cart as $item ) {
828 $id = $item['product_id'];
829
830 if ( ! empty( $item['variation_id'] ) ) {
831 $id = $item['variation_id'];
832 }
833
834 if ( ! $this->is_in_category( $id, $rule_ids ) ) {
835 continue;
836 }
837
838 return true; // Found a valid Y product
839 }
840 }
841
842 if ( 'products' === $bogo_type && ! empty( $rule_ids ) ) {
843 foreach ( $cart as $item ) {
844 $id = $item['product_id'];
845
846 if ( ! empty( $item['variation_id'] ) ) {
847 $id = $item['variation_id'];
848 }
849
850 if ( ! in_array( $id, $rule_ids, true ) ) {
851 continue;
852 }
853
854 return true; // Found a valid Y product
855 }
856 }
857
858 return false;
859 }
860
861 /**
862 * Get Y products from cart.
863 *
864 * @param array $rule_ids Rule Product IDs.
865 * @param array $cart Cart.
866 * @param string $bogo_type BOGO Type.
867 */
868 private function get_y_product( array $rule_ids, array $cart, string $bogo_type ): array { //phpcs:ignore
869 $y_products = array();
870
871 if ( 'categories' === $bogo_type && ! empty( $rule_ids ) ) {
872 foreach ( $cart as $item ) {
873 $id = $item['product_id'];
874
875 if ( ! empty( $item['variation_id'] ) ) {
876 $id = $item['variation_id'];
877 }
878
879 if ( ! $this->is_in_category( $id, $rule_ids ) ) {
880 continue;
881 }
882
883 $y_products[] = $item; // Push the full cart item
884 }
885 }
886
887 if ( 'products' === $bogo_type && ! empty( $rule_ids ) ) {
888 foreach ( $cart as $item ) {
889 $id = $item['product_id'];
890
891 if ( ! empty( $item['variation_id'] ) ) {
892 $id = $item['variation_id'];
893 }
894
895 if ( ! in_array( $id, $rule_ids, true ) ) {
896 continue;
897 }
898
899 $y_products[] = $item; // Push the full cart item
900 }
901 }
902
903 return $y_products;
904 }
905
906 }
907