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 / Disco.php

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

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