array(), 'rules' => array(), 'shipping' => false, 'gifts' => array() ); private array $limits = array(); private bool $running = false; public static function instance(): self { if ( ! self::$instance ) { self::$instance = new self(); } return self::$instance; } private function __construct() { add_action( 'woocommerce_before_calculate_totals', array( $this, 'calculate' ), 90 ); add_filter( 'woocommerce_get_shop_coupon_data', array( $this, 'coupon_data' ), 10, 2 ); add_filter( 'woocommerce_coupon_get_discount_type', static fn( $type, $coupon ) => self::CODE === $coupon->get_code() ? 'digidiscount' : $type, 10, 2 ); add_filter( 'woocommerce_coupon_is_valid_for_cart', array( $this, 'coupon_cart' ), 10, 2 ); add_filter( 'woocommerce_coupon_get_discount_amount', array( $this, 'discount' ), 10, 5 ); add_filter( 'woocommerce_coupon_get_items_to_apply', array( $this, 'available' ), 10, 3 ); add_filter( 'woocommerce_coupon_custom_discounts_array', array( $this, 'precise' ), 10, 2 ); add_filter( 'woocommerce_cart_totals_coupon_label', array( $this, 'label' ), 10, 2 ); add_filter( 'woocommerce_cart_totals_coupon_html', array( $this, 'coupon_html' ), 10, 3 ); add_action( 'woocommerce_checkout_create_order', array( $this, 'snapshot' ), 20 ); add_action( 'woocommerce_store_api_checkout_order_processed', array( $this, 'snapshot_store' ), 20 ); add_action( 'woocommerce_single_product_summary', array( $this, 'product_message' ), 25 ); add_action( 'woocommerce_before_cart', array( $this, 'message' ) ); add_action( 'woocommerce_before_checkout_form', array( $this, 'message' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'assets' ) ); if ( did_action( 'woocommerce_blocks_loaded' ) ) { $this->blocks(); } else { add_action( 'woocommerce_blocks_loaded', array( $this, 'blocks' ) ); } } public static function lines( $cart ): array { $factor = 10 ** wc_get_price_decimals(); $out = array(); foreach ( $cart->get_cart() as $key => $item ) { $product = $item['data']; $cats = wc_get_product_term_ids( $item['product_id'], 'product_cat' ); foreach ( $cats as $cat ) { $cats = array_merge( $cats, get_ancestors( $cat, 'product_cat', 'taxonomy' ) ); } $price = wc_prices_include_tax() ? wc_get_price_including_tax( $product, array( 'qty' => $item['quantity'] ) ) : (float) $product->get_price() * $item['quantity']; $out[ $key ] = array( 'key' => $key, 'product_id' => $item['product_id'], 'variation_id' => $item['variation_id'] ?? 0, 'qty' => (float) $item['quantity'], 'total' => (int) round( $price * $factor ), 'categories' => array_unique( array_map( 'intval', $cats ) ), 'sale' => $product->is_on_sale(), 'tags' => wc_get_product_term_ids( $item['product_id'], 'product_tag' ), 'brands' => taxonomy_exists( 'product_brand' ) ? wc_get_product_term_ids( $item['product_id'], 'product_brand' ) : array(), 'gift' => ! empty( $item['dd_gift'] ), ); } return $out; } public static function context( $cart ): array { $coupons = array_values( array_diff( $cart->get_applied_coupons(), array( self::CODE ) ) ); $user = wp_get_current_user(); return apply_filters( 'digidiscount_cart_context', array( 'currency' => get_woocommerce_currency(), 'now' => time(), 'coupons' => $coupons, 'user_id' => $user->ID, 'roles' => $user->roles, 'gateway' => WC()->session ? WC()->session->get( 'chosen_payment_method', '' ) : '', ), $cart ); } public function calculate( $cart ): void { if ( $this->running || ( is_admin() && ! wp_doing_ajax() ) ) { return; } $this->running = true; try { if ( ! has_action( 'digidiscount_sync_gifts' ) ) { foreach ( $cart->get_cart() as $key => $item ) { if ( ! empty( $item['dd_gift'] ) ) { $cart->remove_cart_item( $key ); } } } $context = self::context( $cart ); $blocked = ! wc_coupons_enabled(); foreach ( $context['coupons'] as $code ) { $coupon = new \WC_Coupon( $code ); $blocked = $blocked || $coupon->get_individual_use(); } $rules = $blocked ? array() : Rules::runtime(); $this->result = Engine::calculate( self::lines( $cart ), $rules, $context, get_option( 'digidiscount_mode', 'best' ) ); do_action( 'digidiscount_sync_gifts', $cart, $this->result ); $codes = $context['coupons']; if ( array_sum( $this->result['discounts'] ) > 0 ) { $codes[] = self::CODE; } $cart->set_applied_coupons( $codes ); if ( WC()->session ) { WC()->session->set( 'digidiscount_shipping', $this->result['shipping'] ); } } finally { $this->running = false; } } public function coupon_data( $data, $code ) { if ( self::CODE !== strtolower( $code ) ) { return $data; } if ( ! WC()->cart ) { return false; } return array( 'id' => 0, 'code' => self::CODE, 'discount_type' => 'fixed_cart', 'amount' => 0, 'individual_use' => false, 'virtual' => true ); } public function coupon_cart( $valid, $coupon ) { return self::CODE === $coupon->get_code() ? true : $valid; } public function available( $items, $coupon, $discounts ) { if ( self::CODE === $coupon->get_code() ) { $this->limits = array(); $used = $discounts->get_discounts_by_item( true ); foreach ( $items as $item ) { $this->limits[ $item->key ] = max( 0, (int) round( $item->price - ( $used[ $item->key ] ?? 0 ) ) ); } } return $items; } public function discount( $discount, $amount, $item, $single, $coupon ) { if ( self::CODE !== $coupon->get_code() || ! is_array( $item ) ) { return $discount; } $value = ( $this->result['discounts'][ $item['key'] ?? '' ] ?? 0 ) / ( 10 ** wc_get_price_decimals() ); return min( (float) $amount, $single ? $value / max( 1, (float) $item['quantity'] ) : $value ); } public function precise( $discounts, $coupon ) { if ( self::CODE === $coupon->get_code() ) { foreach ( $discounts as $key => $value ) { $discounts[ $key ] = min( $this->limits[ $key ] ?? 0, $this->result['discounts'][ $key ] ?? 0 ); } } return $discounts; } public function label( $label, $coupon ) { return self::CODE === $coupon->get_code() ? __( 'Automatic savings', 'digidiscount' ) : $label; } public function coupon_html( $html, $coupon, $discount ) { return self::CODE === $coupon->get_code() ? wp_kses_post( $discount ) : $html; } public function snapshot( $order ): void { if ( $this->result['rules'] ) { $order->update_meta_data( '_digidiscount_campaigns', $this->result['rules'] ); $order->update_meta_data( '_digidiscount_version', DIGIDISCOUNT_VERSION ); } } public function snapshot_store( $order ): void { $this->snapshot( $order ); $order->save(); } public function message(): void { if ( ! WC()->cart ) { return; } $message = apply_filters( 'digidiscount_cart_message', '', Rules::runtime(), self::lines( WC()->cart ), self::context( WC()->cart ) ); if ( $message ) { echo '