PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / 1.3.50
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO v1.3.50
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 / Disco.php

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

471 lines 12.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Disco Class File.
4 *
5 * @package Disco
6 * @subpackage \App\Disco.php
7 * @since 1.0.0
8 */
9
10 namespace Disco\App;
11
12 use Disco\App\Calc\CalcFactory;
13 use Disco\App\Features\UserLimit;
14 use Disco\App\Utility\Settings;
15
16 /**
17 * Class Disco
18 *
19 * @package Disco
20 * @subpackage \App
21 * @author Ohidul Islam <wahid0003@gmail.com>
22 * @link https://webappick.com
23 * @license https://opensource.org/licenses/gpl-license.php GNU Public License
24 * @category Discount
25 */
26 class Disco {
27
28 use \Disco\App\Intents\IntentHelper;
29
30 /**
31 * @var array $intents Intents.
32 */
33 private $intents;
34
35 /**
36 * @var int $applied_campaign_id Applied Campaign.
37 */
38 private $applied_campaign_id;
39
40
41 /**
42 * Apply discount to product.
43 *
44 * @param float $price Product Price.
45 * @param \WC_Product $product Product Object.
46 * @return float Product Price.
47 * @throws \Exception If setting is not found.
48 */
49 public function get_product_discounted_price( $price, $product ) {//phpcs:ignore
50 if ( $product instanceof \WC_Product ) {
51 // Init product base intents and exclude cart-based intents.
52 $this->intents = $this->prepare_intents( array( 'Product' ) );
53
54 if ( ! $product->is_type( 'variable' ) ) {
55 $price = CalcFactory::get_product_price( $product );
56 }
57
58 // If the product is not on sale, update the meta to indicate it's not on sale.
59 // update_post_meta( $product->get_id(), '_disco_on_sale', 'no' );
60
61 // If no intent is available, return the original price.
62 if ( empty( $this->intents ) ) {
63 return $price;
64 }
65
66 $discounts = array();
67
68 $prev_discount = PHP_INT_MAX;
69
70 // Foreach intent, apply the discount.
71 foreach ( $this->intents as $intent ) {
72 /**
73 * Get a discount limit form campaign.
74 *
75 * Compare with total product meta and apply discount
76 */
77 $discount_limit = $intent->campaign->discount_max_user;
78 $total_applied_campaign = ( new UserLimit )->disco_get_total_applied_campaign( $intent->campaign->id );
79
80 if (
81 ! empty( $discount_limit )
82 && (
83 $discount_limit >= 0
84 && $total_applied_campaign >= $discount_limit
85 )
86 ) {
87 continue;
88 }
89
90 $discount = $intent->get_discounts( (float) $price, $product );
91
92 if ( empty( $discount ) ) {
93 continue;
94 }
95
96 // Get applied discount campaign.
97 if ( $discount > 0 && $prev_discount > $discount ) {
98 $this->applied_campaign_id = $intent->campaign->id;
99 $prev_discount = $discount;
100 }
101
102 $discounts[] = $discount;
103 }
104
105 // If no discount is applied, return the original price.
106 if ( empty( $discounts ) ) {
107 return $price;
108 }
109
110 $discount_type = $intent->campaign->discount_rules[0]['discount_type'];
111
112 /**
113 * Get the min or max discount amount according to plugin settings.
114 * Apply the filter to modify final discounted amount based on discount types.
115 */
116 $discounted_amount = apply_filters( 'disco_final_discounted_amount', $this->min_max_average( $discounts ), $discount_type );
117
118 if ( $discounted_amount <= $price ) {
119 // Get an applied campaign from DiscountLimit class.
120 ( new UserLimit )->disco_start_session_on_checkout( $this->applied_campaign_id );
121
122 // Mark as disco custom on sale
123 // update_post_meta( $product->get_id(), '_disco_on_sale', 'yes' );
124
125 return $this->discounted_price( $price, $discounted_amount );
126 }
127 }
128
129 return $price;
130 }
131
132 /** Get Cart Discount.
133 *
134 * @param \WC_Cart $cart Cart Object.
135 * @return \WC_Cart Cart Object.
136 * @throws \Exception If the cart is empty.
137 */
138 public function get_cart_discount( $cart ) { //phpcs:ignore
139 if ( $cart->is_empty() ) {
140 return $cart;
141 }
142
143 // Init Cart - Based Intents except Product & Shipping Intent.
144 $this->intents = $this->prepare_intents( array( 'Cart' ) );
145
146 if ( ! empty( $this->intents ) && $cart->get_cart_contents_count() > 0 ) {
147 $discounts = array();
148
149 foreach ( $this->intents as $intent ) { // Foreach intent, apply the discount.
150
151 /**
152 * Get a discount limit form campaign.
153 *
154 * Compare with total product meta and apply discount
155 */
156 $discount_limit = $intent->campaign->discount_max_user;
157 $total_applied_campaign = ( new UserLimit )->disco_get_total_applied_campaign( $intent->campaign->id );
158
159 if (
160 ! empty( $discount_limit )
161 && (
162 $discount_limit >= 0
163 && $total_applied_campaign >= $discount_limit
164 )
165 ) {
166 continue;
167 }
168
169 $items = $this->get_items_for_discount( $cart, $intent->campaign );
170
171 $discount = $intent->get_discounts( $items, $cart );
172
173 // Get intent id for discount limit.
174 $intent_id = $intent->campaign->id;
175
176 if ( empty( $discount ) ) {
177 continue;
178 }
179
180 // Store discount with intent id key value.
181 $discounts[ $intent_id ] = $discount;
182 }
183
184 // Apply discount to cart subtotal .
185 if ( ! empty( $discounts ) ) {
186 /**
187 * New code.
188 * Get the min or max discount amount according to plugin settings.
189 */
190 $cart_fee = $this->min_max_average( array_values( $discounts ) );
191
192 // Get applied campaign from discount array.
193 $applied_campaign_id = array_search( $cart_fee, $discounts, true );
194 $applied_campaign = ( new Campaign )->get_campaign( $applied_campaign_id );
195 $discount_label = $applied_campaign->discount_rules[0]['discount_label'] ?? '';
196
197 // Get an applied campaign from DiscountLimit class.
198 ( new UserLimit )->disco_start_session_on_checkout( (int) $applied_campaign_id );
199
200 // TODO: The Discount must be less than cart subtotal.
201 $label = __( 'Discount', 'disco' );
202
203 if ( ! empty( $discount_label ) ) {
204 $label = $discount_label;
205 }
206
207 $cart->add_fee( $label, -$cart_fee );
208 $cart->set_session();
209 }
210 }
211
212 return $cart;
213 }
214
215 /**
216 * Apply Bulk & Bundle discount to cart items.
217 *
218 * @param \WC_Cart $cart Cart Object.
219 * @return \WC_Cart|void Cart Object.
220 * @throws \Exception If translation not found.
221 */
222 public function get_cart_items_discount( $cart ) { // phpcs:ignore
223 if ( ! $this->cart_is_valid() ) {
224 return $cart;
225 }
226
227 if ( ! defined( 'DOING_AJAX' ) && is_admin() ) {
228 return $cart;
229 }
230
231 // Init Cart - Based Intents except Product & Shipping Intent.
232 $this->intents = $this->prepare_intents( array( 'Bulk', 'Bundle', 'BOGO' ) );
233
234 $discounts = $this->prepare_item_discounts( $this->intents, $cart );
235
236 if ( empty( $discounts ) ) {
237 return $cart;
238 }
239
240 $total_discount = 0;
241 // Identify valid BOGO + category item ID (if any)
242 $found_bogo_category_item_id = $this->get_valid_bogo_category_item_id( $this->intents, $cart, $discounts );
243
244 // Apply only the valid BOGO category discount, or all if none matched
245 foreach ( $cart->get_cart() as $item ) {
246 $id = $this->get_cart_item_id( $item );
247
248 // Skip all others if BOGO + category item is found
249 if ( $found_bogo_category_item_id !== null && $id !== $found_bogo_category_item_id ) {
250 continue;
251 }
252
253 if ( empty( $discounts[ $id ] ) ) {
254 continue;
255 }
256
257 $total_discount += $discounts[ $id ];
258 }
259
260 if ( $total_discount > 0 ) {
261 $cart->add_fee( __( 'Discount', 'disco' ), -$total_discount );
262 }
263
264 $cart->set_session();
265
266 return $cart;
267 }
268
269 /**
270 * Get cart items discount for BOGO free.
271 *
272 * Calculates which items in the cart are eligible for a free product under the BOGO offer.
273 *
274 * @param \WC_Cart $cart WooCommerce cart object to calculate discounts for.
275 * @return array|false|\WC_Cart Cart object with applied discounts or false if no discounts are applicable.
276 */
277 public function get_cart_items_discount_for_bogo( $cart ) {//phpcs:ignore
278 if ( ! $this->cart_is_valid() ) {
279 return $cart;
280 }
281
282 if ( ! defined( 'DOING_AJAX' ) && is_admin() ) {
283 return $cart;
284 }
285
286 // Init Cart - Based Intents except Product & Shipping Intent.
287 $this->intents = $this->prepare_intents( array( 'BOGO' ) );
288
289 return $this->prepare_item_discounts_bogo_free( $this->intents, $cart );
290 }
291
292 /**
293 * Apply the discount to shipping.
294 *
295 * @return bool
296 */
297 public function apply_free_shipping() { //phpcs:ignore
298 return ! empty( $this->get_applied_free_shipping_campaign_ids() );
299 }
300
301 /**
302 * Evaluate the Shipping intents and return the IDs of every campaign that
303 * currently grants free shipping (respecting per-user usage limits).
304 *
305 * This is the single source of truth for "which free-shipping campaigns
306 * apply right now". It is used both to decide whether to add a free shipping
307 * rate and to persist the campaign IDs to the order meta at checkout. It does
308 * NOT rely on the WC session or cached shipping rates, so it is safe to call
309 * during order creation.
310 *
311 * @return array<int> Applied free-shipping campaign IDs.
312 */
313 public function get_applied_free_shipping_campaign_ids() {
314 if ( ! $this->cart_is_valid() ) {
315 return array();
316 }
317
318 $cart = WC()->cart;
319 $this->intents = $this->prepare_intents( array( 'Shipping' ) );
320 $campaign_ids = array();
321
322 if ( empty( $this->intents ) ) {
323 return $campaign_ids;
324 }
325
326 foreach ( $this->intents as $intent ) {
327 /**
328 * Get a discount limit from campaign.
329 *
330 * Compare with total applied count and skip if the user limit is hit.
331 */
332 $discount_limit = $intent->campaign->discount_max_user;
333
334 if ( ! empty( $discount_limit ) ) {
335 $total_applied_campaign = ( new UserLimit )->disco_get_total_applied_campaign( $intent->campaign->id );
336
337 if ( $total_applied_campaign >= $discount_limit ) {
338 continue;
339 }
340 }
341
342 $items = $this->get_items_for_discount( $cart, $intent->campaign );
343
344 if ( 'free_shipping' !== $intent->get_discounts( $items, $cart ) ) {
345 continue;
346 }
347
348 $campaign_ids[] = (int) $intent->campaign->id;
349 }
350
351 return $campaign_ids;
352 }
353
354 /**
355 * Get offers for a product.
356 *
357 * @param array|null $items item ids.
358 * @return string
359 */
360 public function get_offers( $items = null ) {
361 // Init Cart-Based Intents except Product & Shipping Intent.
362 $this->intents = $this->prepare_intents( array( 'Product', 'Shipping' ) );
363 $table = "<table style='border-collapse: collapse;' class=''>";
364 $table .= '<tr><th>Quantity</th><th>Discount</th></tr>';
365
366 $cart = WC()->cart;
367
368 foreach ( $this->intents as $intent ) {
369 if ( is_null( $items ) || ! $cart->is_empty() ) {
370 $items = $this->get_items_for_discount( $cart, $intent->campaign );
371 } else {
372 $items = array();
373 }
374
375 $discounts = $intent->get_offers( $items, $cart );
376
377 if ( empty( $discounts ) ) {
378 continue;
379 }
380
381 foreach ( $discounts as $discount ) {
382 $table .= '<tr style="font-size: smaller;line-height:15px"><td>' . $discount['condition'] . '</td><td>' . $discount['discount'] . '</td></tr>';
383 }
384 }
385
386 return $table . '</table>';
387 }
388
389 /**
390 * Get Premium Status.
391 */
392 public static function is_pro(): bool {
393 /**
394 * Here use class_exists to check whether Disco Pro is active or not.
395 * is_plugin_active() function may not work in some cases.
396 */
397 return class_exists( 'Disco_Pro' );
398 }
399
400 /**
401 * Get shop, category, or checkout page name.
402 *
403 * @return string Page name: 'category', 'shop', 'checkout', or product page if none match.
404 */
405 public static function get_page_name(): string {
406 if ( is_product_category() || is_product_tag() ) {
407 return 'category';
408 }
409
410 if ( is_shop() ) {
411 return 'shop';
412 }
413
414 if ( is_page( 'cart' ) || is_page( 'checkout' ) ) {
415 return 'cart';
416 }
417
418 return 'product';
419 }
420
421 /**
422 * Get pages name where strike through price should be shown.
423 *
424 * @return array Pages where strike through price should be shown.
425 */
426 public static function get_strike_through_pages(): array {
427 $settings = Settings::get( 'show_strike_through' );
428
429 if ( ! is_array( $settings ) ) {
430 return array();
431 }
432
433 $pages = array();
434
435 if ( isset( $settings['shop_page'] ) && $settings['shop_page'] ) {
436 $pages[] = 'shop';
437 }
438
439 if ( isset( $settings['product_pages'] ) && $settings['product_pages'] ) {
440 $pages[] = 'product';
441 }
442
443 if ( isset( $settings['category_page'] ) && $settings['category_page'] ) {
444 $pages[] = 'category';
445 }
446
447 if ( isset( $settings['cart_page'] ) && $settings['cart_page'] ) {
448 $pages[] = 'cart';
449 }
450
451 return $pages;
452 }
453
454 /**
455 * Get is strike through applicable for current page.
456 *
457 * @param string $page_name Page name to check.
458 * @return bool True if strike through is applicable, false otherwise.
459 */
460 public static function is_strike_through_applicable( $page_name ): bool {
461 $pages = self::get_strike_through_pages();
462
463 if ( empty( $pages ) || empty( $page_name ) ) {
464 return false;
465 }
466
467 return in_array( $page_name, $pages, true );
468 }
469
470 }
471