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

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