PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / 1.2.76
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO v1.2.76
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 1.3.45 1.3.44 All 176 releases
disco / app / Disco.php

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

479 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 ( Settings::get( 'discount_priority_type' ) === 'woocommerce_coupon' ) {
51 return $price; // Return original price if WooCommerce coupon is used.
52 }
53
54 if ( $product instanceof \WC_Product ) {
55 // Init product base intents and exclude cart-based intents.
56 $this->intents = $this->prepare_intents( array( 'Product' ) );
57
58 if ( ! $product->is_type( 'variable' ) ) {
59 $price = CalcFactory::get_product_price( $product );
60 }
61
62 // If the product is not on sale, update the meta to indicate it's not on sale.
63 // update_post_meta( $product->get_id(), '_disco_on_sale', 'no' );
64
65 // If no intent is available, return the original price.
66 if ( empty( $this->intents ) ) {
67 return $price;
68 }
69
70 $discounts = array();
71
72 $prev_discount = PHP_INT_MAX;
73
74 // Foreach intent, apply the discount.
75 foreach ( $this->intents as $intent ) {
76 /**
77 * Get a discount limit form campaign.
78 *
79 * Compare with total product meta and apply discount
80 */
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 ) {
91 continue;
92 }
93
94 $discount = $intent->get_discounts( (float) $price, $product );
95
96 if ( empty( $discount ) ) {
97 continue;
98 }
99
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;
107 }
108
109 // If no discount is applied, return the original price.
110 if ( empty( $discounts ) ) {
111 return $price;
112 }
113
114 $discount_type = $intent->campaign->discount_rules[0]['discount_type'];
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
122 if ( $discounted_amount < $price ) {
123 // Get an applied campaign from DiscountLimit class.
124 ( new UserLimit )->disco_start_session_on_checkout( $this->applied_campaign_id );
125
126 // Mark as disco custom on sale
127 // update_post_meta( $product->get_id(), '_disco_on_sale', 'yes' );
128
129 return $this->discounted_price( $price, $discounted_amount );
130 }
131 }
132
133 return $price;
134 }
135
136 /** Get Cart Discount.
137 *
138 * @param \WC_Cart $cart Cart Object.
139 * @return \WC_Cart Cart Object.
140 * @throws \Exception If the cart is empty.
141 */
142 public function get_cart_discount( $cart ) { //phpcs:ignore
143 if ( $cart->is_empty() ) {
144 return $cart;
145 }
146
147 if ( Settings::get( 'discount_priority_type' ) === 'woocommerce_coupon' ) {
148 return $cart;
149 }
150
151 // Init Cart - Based Intents except Product & Shipping Intent.
152 $this->intents = $this->prepare_intents( array( 'Cart' ) );
153
154 if ( ! empty( $this->intents ) && $cart->get_cart_contents_count() > 0 ) {
155 $discounts = array();
156
157 foreach ( $this->intents as $intent ) { // Foreach intent, apply the discount.
158
159 /**
160 * Get a discount limit form campaign.
161 *
162 * Compare with total product meta and apply discount
163 */
164 $discount_limit = $intent->campaign->discount_max_user;
165 $discount_label = $intent->campaign->discount_rules[0]['discount_label'] ?? '';
166 $total_applied_campaign = ( new UserLimit )->disco_get_total_applied_campaign( $intent->campaign->id );
167
168 if (
169 ! empty( $discount_limit )
170 && (
171 $discount_limit >= 0
172 && $total_applied_campaign >= $discount_limit
173 )
174 ) {
175 continue;
176 }
177
178 $items = $this->get_items_for_discount( $cart, $intent->campaign );
179
180 $discount = $intent->get_discounts( $items, $cart );
181
182 // Get intent id for discount limit.
183 $intent_id = $intent->campaign->id;
184
185 if ( empty( $discount ) ) {
186 continue;
187 }
188
189 // Store discount with intent id key value.
190 $discounts[ $intent_id ] = $discount;
191 }
192
193 // Apply discount to cart subtotal .
194 if ( ! empty( $discounts ) ) {
195 /**
196 * New code.
197 * Get the min or max discount amount according to plugin settings.
198 */
199 $cart_fee = $this->min_max_average( array_values( $discounts ) );
200
201 // Get applied campaign from discount array.
202 $applied_campaign_id = array_search( $cart_fee, $discounts, true );
203
204 // Get an applied campaign from DiscountLimit class.
205 ( new UserLimit )->disco_start_session_on_checkout( (int) $applied_campaign_id );
206
207 // TODO: The Discount must be less than cart subtotal.
208 $label = __( 'Discount', 'disco' );
209
210 if ( ! empty( $discount_label ) ) {
211 $label = $discount_label;
212 }
213
214 $cart->add_fee( $label, -$cart_fee );
215 $cart->set_session();
216 }
217 }
218
219 return $cart;
220 }
221
222 /**
223 * Apply Bulk & Bundle discount to cart items.
224 *
225 * @param \WC_Cart $cart Cart Object.
226 * @return \WC_Cart|void Cart Object.
227 * @throws \Exception If translation not found.
228 */
229 public function get_cart_items_discount( $cart ) { // phpcs:ignore
230 if ( ! $this->cart_is_valid() ) {
231 return $cart;
232 }
233
234 if ( ! defined( 'DOING_AJAX' ) && is_admin() ) {
235 return $cart;
236 }
237
238 if ( Settings::get( 'discount_priority_type' ) === 'woocommerce_coupon' ) {
239 return $cart;
240 }
241
242 // Init Cart - Based Intents except Product & Shipping Intent.
243 $this->intents = $this->prepare_intents( array( 'Bulk', 'Bundle', 'BOGO' ) );
244
245 $discounts = $this->prepare_item_discounts( $this->intents, $cart );
246
247 if ( empty( $discounts ) ) {
248 return $cart;
249 }
250
251 $total_discount = 0;
252 // Identify valid BOGO + category item ID (if any)
253 $found_bogo_category_item_id = $this->get_valid_bogo_category_item_id( $this->intents, $cart, $discounts );
254
255 // Apply only the valid BOGO category discount, or all if none matched
256 foreach ( $cart->get_cart() as $item ) {
257 $id = $this->get_cart_item_id( $item );
258
259 // Skip all others if BOGO + category item is found
260 if ( $found_bogo_category_item_id !== null && $id !== $found_bogo_category_item_id ) {
261 continue;
262 }
263
264 if ( empty( $discounts[ $id ] ) ) {
265 continue;
266 }
267
268 $total_discount += $discounts[ $id ];
269 }
270
271 if ( $total_discount > 0 ) {
272 $cart->add_fee( __( 'Discount', 'disco' ), -$total_discount );
273 }
274
275 $cart->set_session();
276
277 return $cart;
278 }
279
280 /**
281 * Get cart items discount for BOGO free.
282 *
283 * Calculates which items in the cart are eligible for a free product under the BOGO offer.
284 *
285 * @param \WC_Cart $cart WooCommerce cart object to calculate discounts for.
286 * @return array|false|\WC_Cart Cart object with applied discounts or false if no discounts are applicable.
287 */
288 public function get_cart_items_discount_for_bogo( $cart ) {//phpcs:ignore
289 if ( ! $this->cart_is_valid() ) {
290 return $cart;
291 }
292
293 if ( ! defined( 'DOING_AJAX' ) && is_admin() ) {
294 return $cart;
295 }
296
297 if ( Settings::get( 'discount_priority_type' ) === 'woocommerce_coupon' ) {
298 return $cart;
299 }
300
301 // Init Cart - Based Intents except Product & Shipping Intent.
302 $this->intents = $this->prepare_intents( array( 'BOGO' ) );
303
304 return $this->prepare_item_discounts_bogo_free( $this->intents, $cart );
305 }
306
307 /**
308 * Apply the discount to shipping.
309 *
310 * @return bool
311 */
312 public function apply_free_shipping() { //phpcs:ignore
313 if ( ! $this->cart_is_valid() ) {
314 return false;
315 }
316
317 $cart = WC()->cart;
318
319 if ( Settings::get( 'discount_priority_type' ) === 'woocommerce_coupon' ) {
320 return false; // Return false if WooCommerce coupon is used.
321 }
322
323 $this->intents = $this->prepare_intents( array( 'Shipping' ) );
324 $shippings = array();
325
326 if ( ! empty( $this->intents ) ) {
327 foreach ( $this->intents as $intent ) {
328 /**
329 * Get a discount limit form campaign.
330 *
331 * Compare with total product meta and apply discount
332 */
333 $discount_limit = $intent->campaign->discount_max_user;
334 $applied_campaign_id = $intent->campaign->id;
335 $total_applied_campaign = ( new UserLimit )->disco_get_total_applied_campaign( $intent->campaign->id );
336
337 if (
338 ! empty( $discount_limit )
339 && (
340 $discount_limit >= 0
341 && $total_applied_campaign >= $discount_limit
342 )
343 ) {
344 continue;
345 }
346
347 $items = $this->get_items_for_discount( $cart, $intent->campaign );
348 $shippings[] = $intent->get_discounts( $items, $cart );
349
350 if ( !in_array( 'free_shipping', $shippings, true ) ) {
351 continue;
352 }
353
354 // Get an applied campaign from DiscountLimit class.
355 ( new UserLimit )->disco_start_session_on_checkout( $applied_campaign_id );
356 }
357 }
358
359 return in_array( 'free_shipping', $shippings, true );
360 }
361
362 /**
363 * Get offers for a product.
364 *
365 * @param array|null $items item ids.
366 * @return string
367 */
368 public function get_offers( $items = null ) {
369 // Init Cart-Based Intents except Product & Shipping Intent.
370 $this->intents = $this->prepare_intents( array( 'Product', 'Shipping' ) );
371 $table = "<table style='border-collapse: collapse;' class=''>";
372 $table .= '<tr><th>Quantity</th><th>Discount</th></tr>';
373
374 $cart = WC()->cart;
375
376 foreach ( $this->intents as $intent ) {
377 if ( is_null( $items ) || ! $cart->is_empty() ) {
378 $items = $this->get_items_for_discount( $cart, $intent->campaign );
379 } else {
380 $items = array();
381 }
382
383 $discounts = $intent->get_offers( $items, $cart );
384
385 if ( empty( $discounts ) ) {
386 continue;
387 }
388
389 foreach ( $discounts as $discount ) {
390 $table .= '<tr style="font-size: smaller;line-height:15px"><td>' . $discount['condition'] . '</td><td>' . $discount['discount'] . '</td></tr>';
391 }
392 }
393
394 return $table . '</table>';
395 }
396
397 /**
398 * Get Premium Status.
399 */
400 public static function is_pro(): bool {
401 return is_plugin_active( 'disco-pro/disco-pro.php' );
402 }
403
404 /**
405 * Get shop, category, or product page name.
406 *
407 * @return string Page name: 'product', 'category', 'shop', or empty string if none match.
408 */
409 public static function get_page_name(): string {
410 if ( is_product() ) {
411 return 'product';
412 }
413
414 if ( is_product_category() || is_product_tag() ) {
415 return 'category';
416 }
417
418 if ( is_shop() ) {
419 return 'shop';
420 }
421
422 if ( is_page( 'cart' ) || is_page( 'checkout' ) ) {
423 return 'cart';
424 }
425
426 return '';
427 }
428
429 /**
430 * Get pages name where strike through price should be shown.
431 *
432 * @return array Pages where strike through price should be shown.
433 */
434 public static function get_strike_through_pages(): array {
435 $settings = Settings::get( 'show_strike_through' );
436
437 if ( ! is_array( $settings ) ) {
438 return array();
439 }
440
441 $pages = array();
442
443 if ( isset( $settings['shop_page'] ) && $settings['shop_page'] ) {
444 $pages[] = 'shop';
445 }
446
447 if ( isset( $settings['product_page'] ) && $settings['product_page'] ) {
448 $pages[] = 'product';
449 }
450
451 if ( isset( $settings['category_page'] ) && $settings['category_page'] ) {
452 $pages[] = 'category';
453 }
454
455 if ( isset( $settings['cart_page'] ) && $settings['cart_page'] ) {
456 $pages[] = 'cart';
457 }
458
459 return $pages;
460 }
461
462 /**
463 * Get is strike through applicable for current page.
464 *
465 * @param string $page_name Page name to check.
466 * @return bool True if strike through is applicable, false otherwise.
467 */
468 public static function is_strike_through_applicable( $page_name ): bool {
469 $pages = self::get_strike_through_pages();
470
471 if ( empty( $pages ) || empty( $page_name ) ) {
472 return false;
473 }
474
475 return in_array( $page_name, $pages, true );
476 }
477
478 }
479