false, 'hidden_default' => false, 'product_id' => null, 'should_render' => true ); public $settings = array(); public $product_settings = array(); public function __construct() { } public function init($product = null) { if ( !class_exists('WooCommerce')) return; if ( !get_option('xstore_sales_booster_settings_'.self::$option_name) ) return; $this->args['tab_prefix'] = 'et_'.self::$option_name; $this->args['product'] = $product; if ( $this->args['product'] ) $this->args['product_id'] = $this->args['product']->get_ID(); $this->args['is_admin'] = is_admin(); $this->args['spb'] = !!get_option( 'etheme_single_product_builder', false ); add_action('wp', array($this, 'add_actions')); /** * Fix quantity discount not applied on ajax request. */ add_filter( 'woocommerce_cart_subtotal', array( $this, 'get_cart_subtotal_filter' ), 100, 1 ); add_filter('woocommerce_get_cart_contents', array($this, 'get_cart_contents'), 20, 1); add_filter('woocommerce_cart_get_total', array($this, 'get_cart_total_filter'), 20, 1); add_filter('woocommerce_cart_item_price', array($this, 'calculate_price_discounts_filter'), 20, 3); add_filter('woocommerce_cart_item_subtotal', array($this, 'calculate_price_discounts_subtotal_filter'), 20, 3); add_filter('woocommerce_get_cart_item_from_session', array($this, 'filter_cart_item_from_session'), 20, 3); if ( $this->args['is_admin'] ) { add_action( 'woocommerce_product_write_panel_tabs', array($this, 'panel_tab') ); add_action( 'woocommerce_product_data_panels', array($this, 'panel_data') ); add_action( 'woocommerce_process_product_meta', array($this, 'save_panel_data') ); } } /** * Add global actions/filters. */ public function add_actions() { if( $this->args['product'] ){ $this->args['is_local_product'] = true; } elseif ( is_singular('product') ) { $this->args['product'] = wc_get_product(); $this->args['product_id'] = $this->args['product']->get_ID(); } else { return; } if ( $this->args['product']->is_sold_individually() || !$this->args['product']->is_purchasable() || !$this->args['product']->is_in_stock() ) { $this->args['should_render'] = false; return; } if ( in_array($this->args['product']->get_type(), array('variable'))) { $this->args['hidden_default'] = true; // if ( !get_theme_mod( 'enable_swatch', 1 ) ) { // $this->args['should_render'] = false; // return; // } } // prevent showing quantity discounts on product which does not support ajax add to cart (it means the product is // external/grouped/etc) elseif ( !$this->args['product']->supports( 'ajax_add_to_cart' ) ) { $this->args['should_render'] = false; return; } if ( etheme_is_catalog() ) return; $this->set_settings(array(), $this->args['product_id']); if ( !$this->args['is_local_product']) { $action = 'woocommerce_after_add_to_cart_button'; $priority = 15; $apply_action = true; if ( $this->args['spb'] ) { $action = 'etheme_woocommerce_template_single_excerpt'; $priority = 5; } switch ( $this->settings['position'] ) { case 'before_cart_form': $action = 'woocommerce_before_add_to_cart_form'; $priority = 5; break; case 'after_cart_form': $action = 'woocommerce_after_add_to_cart_form'; $priority = 15; break; case 'before_woocommerce_share': $action = 'woocommerce_share'; $priority = - 999; break; case 'after_woocommerce_share': $action = 'woocommerce_share'; $priority = 999; break; case 'before_product_meta': $this->args['tag'] = 'span'; $action = 'woocommerce_product_meta_start'; $priority = 5; break; case 'after_product_meta': $this->args['tag'] = 'span'; $action = 'woocommerce_product_meta_end'; $priority = 15; break; case 'shortcode': $apply_action = false; break; default: if ( $this->args['spb'] ) { switch ( $this->settings['position'] ) { case 'before_atc': $action = 'etheme_woocommerce_template_single_add_to_cart'; $priority = 5; break; case 'after_atc': $action = 'etheme_woocommerce_template_single_add_to_cart'; $priority = 15; break; case 'before_excerpt': $action = 'etheme_woocommerce_template_single_excerpt'; $priority = 5; break; case 'after_excerpt': $action = 'etheme_woocommerce_template_single_excerpt'; $priority = 15; break; } } break; } if ( $apply_action ) { add_action( $action, array( $this, 'output' ), $priority ); if ( has_action('after_page_wrapper', 'etheme_sticky_add_to_cart') ) { add_action( 'after_page_wrapper', function () use ($action, $priority) { remove_action( $action, array( $this, 'output' ), $priority ); }, -1 ); add_action( 'after_page_wrapper', function () use ($action, $priority) { add_action( $action, array( $this, 'output' ), $priority ); }, 2 ); } } } } /** * Filter product price which are loaded from session (cache) * @param $session_data * @param $values * @param $key * @return mixed */ public function filter_cart_item_from_session($session_data, $values, $key) { $product = $session_data['data']; if ( isset($session_data['et_discount_price']) ) { unset($session_data['et_discount_price']); } if ( isset($session_data['et_discount_price_done']) ) { unset($session_data['et_discount_price_done']); } // $origin_settings = $this->set_settings(); // $local_settings = $origin_settings; // if ( get_post_meta( $session_data['product_id'], $this->args['tab_prefix'].'_settings', true ) != '' ) { $local_settings = $this->set_settings(array(), $session_data['product_id']); // } if (!count($local_settings)) return $session_data; $type = $local_settings['type']; // percentage/fixed price $steps_intervals = $local_settings[$local_settings['rules'].'_ready']; $simple_price = $product->get_price(); switch ($local_settings['rules']) { case 'intervals': foreach ($steps_intervals as $step_interval) { if ( $session_data['quantity'] >= $step_interval['min'] && (empty($step_interval['max']) || $session_data['quantity'] <= $step_interval['max']) ) { if ( $type == 'percentage') $price = $simple_price - ($simple_price / 100 * $step_interval['percentage']); else $price = $simple_price >= $step_interval['percentage'] ? $simple_price - $step_interval['percentage'] : $simple_price; // in case product price is less than discount - leave origin price for product $session_data['et_discount_price'] = $price; $session_data['et_origin_price'] = $simple_price; $product->set_price($price); } } break; case 'steps': foreach ($steps_intervals as $step_interval) { if ( $session_data['quantity'] == $step_interval['every'] ) { if ( $type == 'percentage') $price = $simple_price - ($simple_price / 100 * $step_interval['percentage']); else $price = $simple_price >= $step_interval['percentage'] ? $simple_price - $step_interval['percentage'] : $simple_price; $session_data['et_discount_price'] = $price; $session_data['et_origin_price'] = $simple_price; $product->set_price($price); } } break; } $session_data['data'] = $product; return $session_data; } /** * Filter global total with products have discounts * @param $total * @return float|int */ public function get_cart_total_filter($total) { $new_total = $total; $items = WC()->cart->get_cart_contents(); foreach ( $items as $item => $values ) { /** * @var \WC_Product $product_obj */ $quantity = $values['quantity']; if ( isset($values['et_discount_price'])) $new_total -= ($values['data']->get_price() - $values['et_discount_price']) * $quantity; // else // $new_total += $values['data']->get_price() * $quantity; } return $new_total; } /** * Filter global subtotal with products have discounts * @param $subtotal * @return mixed */ public function get_cart_subtotal_filter( $subtotal ) { $new_subtotal = 0; $items = WC()->cart->get_cart_contents(); $had_taxable = false; foreach ( $items as $item => $values ) { /** * @var \WC_Product $product_obj */ $quantity = $values['quantity']; $local_price = $values['data']->get_price(); if ( isset($values['et_discount_price'])) { if ( $local_price > $values['et_discount_price']) $local_price = $values['et_discount_price']; } if ( $values['data']->is_taxable() ) { $had_taxable = true; if ( WC()->cart->display_prices_including_tax() ) { $row_price = wc_get_price_including_tax( $values['data'], array( 'qty' => $values['quantity'], 'price' => $local_price ) ); $new_subtotal += $row_price; // if ( ! wc_prices_include_tax() && WC()->cart->get_subtotal_tax() > 0 ) { // $new_price .= ' ' . WC()->countries->inc_tax_or_vat() . ''; // } // return $new_price; } else { $row_price = wc_get_price_excluding_tax( $values['data'], array( 'qty' => $values['quantity'], 'price' => $local_price ) ); $new_subtotal += $row_price; // if ( wc_prices_include_tax() && WC()->cart->get_subtotal_tax() > 0 ) { // $new_price .= ' ' . WC()->countries->ex_tax_or_vat() . ''; // } // return $new_price; } } else { $new_subtotal += $local_price * $quantity; } } $subtotal = wc_price( $new_subtotal ); if ( $had_taxable ) { if ( WC()->cart->display_prices_including_tax() ) { if ( ! wc_prices_include_tax() && WC()->cart->get_subtotal_tax() > 0 ) { $subtotal .= ' ' . WC()->countries->inc_tax_or_vat() . ''; } } else { if ( wc_prices_include_tax() && WC()->cart->get_subtotal_tax() > 0 ) { $subtotal .= ' ' . WC()->countries->ex_tax_or_vat() . ''; } } } return $subtotal; } /** * Set product price with discount if all conditions are met * @param $contents * @return mixed */ public function get_cart_contents($contents) { // return $contents; $new_contents = $contents; // $origin_settings = $this->set_settings(); foreach ( $contents as $item => $values ) { if ( isset($values['et_discount_price_done'])) continue; // if ( isset($values['et_discount_price']) ) { // unset($values['et_discount_price']); // } // if ( isset($values['et_discount_price_done']) ) { // unset($values['et_discount_price_done']); // } // $local_settings = $origin_settings; // if ( get_post_meta( $values['product_id'], $this->args['tab_prefix'].'_settings', true ) == 'custom' ) { $local_settings = $this->set_settings(array(), $values['product_id']); // } if (!count($local_settings)) continue; $type = $local_settings['type']; // percentage/fixed price $steps_intervals = $local_settings[$local_settings['rules'].'_ready']; if (!count($steps_intervals)) continue; // Use it to prevent server error "The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later." // Method "get_price" causes server error in "WC_Product_Subscription" object // Can be removed after solution from "WooCommerce Memberships" plugin - By SkyVerge if ( class_exists('WC_Product_Subscription') && $values['data'] instanceof WC_Product_Subscription ) { $values['data'] = new WC_product($values['data']->get_ID()); } $simple_price = isset($values['et_origin_price']) ? $values['et_origin_price'] : $values['data']->get_price(); switch ($local_settings['rules']) { case 'intervals': foreach ($steps_intervals as $step_interval) { if ( $values['quantity'] >= $step_interval['min'] && (empty($step_interval['max']) || $values['quantity'] <= $step_interval['max']) ) { if ( $type == 'percentage') { $price = $simple_price - ($simple_price / 100 * $step_interval['percentage']); } else $price = $simple_price >= $step_interval['percentage'] ? $simple_price - $step_interval['percentage'] : $simple_price; $values['et_discount_price'] = $price; $values['et_origin_price'] = $simple_price; $values['et_discount_price_done'] = true; } } break; case 'steps': foreach ($steps_intervals as $step_interval) { if ( $values['quantity'] == $step_interval['every'] ) { if ( $type == 'percentage') $price = $simple_price - ($simple_price / 100 * $step_interval['percentage']); else $price = $simple_price >= $step_interval['percentage'] ? $simple_price - $step_interval['percentage'] : $simple_price; $values['et_discount_price'] = $price; $values['et_origin_price'] = $simple_price; $values['et_discount_price_done'] = true; } } break; } $new_contents[$item] = $values; } return $new_contents; } /** * Filter product price from discounted if such one exists * @param $price * @param $cart_item * @param $cart_item_key * @return mixed */ public function calculate_price_discounts_filter($price, $cart_item, $cart_item_key) { $local_price = $cart_item['data']->get_price(); if ( isset($cart_item['et_discount_price']) ) { $local_price = $cart_item['et_discount_price']; } if ( $cart_item['data']->is_taxable() ) { if ( WC()->cart->display_prices_including_tax() ) { $row_price = wc_get_price_including_tax( $cart_item['data'], array( 'price' => $local_price ) ); $new_price = wc_price( $row_price ); if ( ! wc_prices_include_tax() && WC()->cart->get_subtotal_tax() > 0 ) { $new_price .= ' ' . WC()->countries->inc_tax_or_vat() . ''; } return $new_price; } else { $row_price = wc_get_price_excluding_tax( $cart_item['data'], array( 'price' => $local_price ) ); $new_price = wc_price( $row_price ); if ( wc_prices_include_tax() && WC()->cart->get_subtotal_tax() > 0 ) { $new_price .= ' ' . WC()->countries->ex_tax_or_vat() . ''; } return $new_price; } } else { return wc_price($local_price); } } /** * Filter product price with discount if all conditions are passed and quantities are in the ones set in options * @param $product_subtotal * @param $cart_item * @param $cart_item_key * @return string */ public function calculate_price_discounts_subtotal_filter($product_subtotal, $cart_item, $cart_item_key) { $local_price = $cart_item['data']->get_price(); if ( isset($cart_item['et_discount_price']) ) $local_price = $cart_item['et_discount_price']; if ( $cart_item['data']->is_taxable() ) { if ( WC()->cart->display_prices_including_tax() ) { $row_price = wc_get_price_including_tax( $cart_item['data'], array( 'qty' => $cart_item['quantity'], 'price' => $local_price ) ); $product_subtotal = wc_price( $row_price ); if ( ! wc_prices_include_tax() && WC()->cart->get_subtotal_tax() > 0 ) { $product_subtotal .= ' ' . WC()->countries->inc_tax_or_vat() . ''; } } else { $row_price = wc_get_price_excluding_tax( $cart_item['data'], array( 'qty' => $cart_item['quantity'], 'price' => $local_price ) ); $product_subtotal = wc_price( $row_price ); if ( wc_prices_include_tax() && WC()->cart->get_subtotal_tax() > 0 ) { $product_subtotal .= ' ' . WC()->countries->ex_tax_or_vat() . ''; } } } else { $row_price = (float) $local_price * (float) $cart_item['quantity']; $product_subtotal = wc_price( $row_price ); } // } return $product_subtotal; } /** * Setting settings globally or for specific product if $product_id param is set * @param array $custom_settings * @param null $product_id * @return array|mixed */ public function set_settings($custom_settings = array(), $product_id = null) { if ( !$product_id && count($this->settings)) return $this->settings; if ( $product_id && isset($this->product_settings[$product_id]) && count($this->product_settings[$product_id])) return $this->product_settings[$product_id]; $this->settings = array( 'type' => 'percentage', // fixed 'rules' => 'intervals', // steps 'intervals' => [], 'intervals_ready' => [], 'steps' => [], 'steps_ready' => [], 'title' => esc_html__('Buy more save more!', 'xstore'), 'title_tag' => 'h4', 'discount_title_tag' => 'h5', 'button_icon' => 'et_icon-shopping-bag', 'custom_icon' => false, 'icon_position' => 'left', 'button_text' => esc_html__('Add', 'xstore'), 'add_quantity' => false, 'shown_on_quick_view' => false, 'position' => $this->args['spb'] ? 'after_excerpt' : 'after_summary', 'force_load_assets' => true ); $local_settings = $this->settings; $settings = (array)get_option('xstore_sales_booster_settings', array()); if (count($settings) && isset($settings[self::$option_name])) { $local_settings = wp_parse_args( $settings[ self::$option_name ], $this->settings ); } // single product page backend custom options $product_custom_options = array(); $local_product_id = $product_id ? $product_id : $this->args['product_id']; $product_discount_type = get_post_meta( $local_product_id, $this->args['tab_prefix'].'_settings', true ); if ( $product_discount_type == 'none' ) return $this->settings; if ( $product_discount_type == 'custom' ) { $rules = get_post_meta( $local_product_id, $this->args['tab_prefix'].'_rules', true ); $option_value = get_post_meta($local_product_id, $this->args['tab_prefix'] . '_' . $rules, true); if ($option_value) { $local_settings['rules'] = $rules; $local_settings['type'] = get_post_meta( $local_product_id, $this->args['tab_prefix'].'_type', true ); $steps_intervals_ready = array(); $local_settings[$rules.'_ready'] = array(); foreach ($option_value as $option) { $prefixed_rules = $this->args['tab_prefix'] . '_' . $rules; $steps_intervals_ready['percentage'] = $option[$prefixed_rules . '_percentage']; if ( !$steps_intervals_ready['percentage'] ) continue; if ( $rules == 'intervals' ) { $steps_intervals_ready['min'] = $option[$prefixed_rules . '_min']; $steps_intervals_ready['max'] = $option[$prefixed_rules . '_max']; } else { $steps_intervals_ready['every'] = $option[$prefixed_rules . '_every']; } if ( array_filter($steps_intervals_ready ) ) $local_settings[$rules.'_ready'][] = $steps_intervals_ready; } } } else { if ( !isset($local_settings[$local_settings['rules']]) || !$local_settings[$local_settings['rules']] ) return $this->settings; $steps_intervals = explode(',', $local_settings[$local_settings['rules']]); if ( count($steps_intervals) < 1) return $this->settings; $local_settings[$local_settings['rules'].'_ready'] = array(); foreach ($steps_intervals as $steps_interval) { $steps_intervals_ready = array(); $steps_intervals_ready['percentage'] = $local_settings[$steps_interval . '_percentage']; if ( !$steps_intervals_ready['percentage'] ) continue; if ( $local_settings['rules'] == 'intervals' ) { $steps_intervals_ready['min'] = $local_settings[$steps_interval . '_min']; $steps_intervals_ready['max'] = $local_settings[$steps_interval . '_max']; } else { $steps_intervals_ready['every'] = $local_settings[$steps_interval . '_every']; } if ( array_filter($steps_intervals_ready ) ) $local_settings[$local_settings['rules'].'_ready'][] = $steps_intervals_ready; } // empty all values if ( !array_filter($local_settings[$local_settings['rules'].'_ready'])) return $this->settings; } $this->settings = wp_parse_args( $product_custom_options, $local_settings ); $this->settings = wp_parse_args( $custom_settings, $this->settings ); if ( $product_id ) $this->product_settings[$product_id] = $this->settings; return $this->settings; } /** * Output quantity discounts block if all conditions are passed */ public function output() { if ( !apply_filters('etheme_sales_booster_quantity_discounts', true) ) return; if ( !$this->args['should_render']) return; $settings = apply_filters($this->args['tab_prefix'].'_settings', isset($this->product_settings[$this->args['product_id']]) ? $this->product_settings[$this->args['product_id']] : $this->settings, $this->args); if ( !isset($settings[$settings['rules'].'_ready']) || !count($settings[$settings['rules'].'_ready'])) return; if ( !isset($this->args['product_id']) || !$this->args['product_id']) return; ob_start(); $this->calculated_discounts($settings); $discounts = ob_get_clean(); if ( !$discounts) { $this->args['hidden_default'] = true; // return; } if ( $settings['force_load_assets'] ) etheme_enqueue_style( 'sale-booster-quantity-discounts', true ); ?>
'; ?> args['product']->get_stock_quantity(); foreach ($settings[$settings['rules'].'_ready'] as $discount) { $intervals = $settings['rules'] == 'intervals'; if ( ($intervals && !$this->args['product']->has_enough_stock( $discount['min'] )) || ( !$intervals && !$this->args['product']->has_enough_stock( $discount['every'] ) ) ) continue; $max_quantity = ''; if ( $intervals ) { if ($discount['max']) { $max_quantity = $discount['max']; if ($stock_quantity && $stock_quantity <= $max_quantity) { $max_quantity = $stock_quantity; } } } else { if ($discount['every']) { $max_quantity = $discount['every']; if ($stock_quantity && $stock_quantity <= $max_quantity) { $max_quantity = $stock_quantity; } } } $should_display_qty = $intervals && !($max_quantity && $max_quantity == $discount['min']); ?>