Advertisement
Guest User

class-wc-memberships-member-discounts

a guest
Nov 8th, 2016
215
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 48.74 KB | None | 0 0
  1. <?php
  2. /**
  3.  * WooCommerce Memberships
  4.  *
  5.  * This source file is subject to the GNU General Public License v3.0
  6.  * that is bundled with this package in the file license.txt.
  7.  * It is also available through the world-wide-web at this URL:
  8.  * http://www.gnu.org/licenses/gpl-3.0.html
  9.  * If you did not receive a copy of the license and are unable to
  10.  * obtain it through the world-wide-web, please send an email
  11.  * to license@skyverge.com so we can send you a copy immediately.
  12.  *
  13.  * DISCLAIMER
  14.  *
  15.  * Do not edit or add to this file if you wish to upgrade WooCommerce Memberships to newer
  16.  * versions in the future. If you wish to customize WooCommerce Memberships for your
  17.  * needs please refer to http://docs.woothemes.com/document/woocommerce-memberships/ for more information.
  18.  *
  19.  * @package   WC-Memberships/Classes
  20.  * @author    SkyVerge
  21.  * @copyright Copyright (c) 2014-2016, SkyVerge, Inc.
  22.  * @license   http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
  23.  */
  24.  
  25. defined( 'ABSPATH' ) or exit;
  26.  
  27. /**
  28.  * Member Discounts class
  29.  *
  30.  * This class handles all purchasing discounts for members
  31.  *
  32.  * @since 1.3.0
  33.  */
  34. class WC_Memberships_Member_Discounts {
  35.  
  36.  
  37.     /** @var array Lazy loading for member product discount information */
  38.     private $member_has_product_discount = array();
  39.  
  40.     /** @var array Memoization for product discounts */
  41.     private $product_discount = array();
  42.  
  43.     /** @var array Memoization for variation product discounts */
  44.     private $product_discount_variation = array();
  45.  
  46.     /** @var array Memoization for product discounts exclusion */
  47.     private $product_excluded_from_discounts = array();
  48.  
  49.     /** @var bool Whether products on sale are excluded from discounts */
  50.     private $exclude_on_sale_products = false;
  51.  
  52.     /** @var array Memoization for product on sale before discount */
  53.     private $product_is_on_sale_before_discount = array();
  54.  
  55.     /** @var bool Whether the current user, maybe member, is logged in */
  56.     private $member_is_logged_in = false;
  57.  
  58.     /** @var bool Whether discounts calculations are running for products  */
  59.     private $applying_discounts = false;
  60.  
  61.  
  62.     /**
  63.      * Set up member discounts: Welcome to the jungle.
  64.      *
  65.      * @since 1.3.0
  66.      */
  67.     public function __construct() {
  68.  
  69.         // init discounts so we don't hook to early
  70.         add_action( 'init', array( $this, 'init' ) );
  71.     }
  72.  
  73.  
  74.     /**
  75.      * Init member discounts
  76.      *
  77.      * We follow here a pattern common in many price-affecting extensions,
  78.      * due to the need to produce a "price before/after discount" type of HTML output,
  79.      * so shop customers can easily understand the deal they're being offered.
  80.      *
  81.      * To do so we need to juggle WooCommerce prices, we start off by instantiating
  82.      * this class with our discounts active, so we can be sure to always pass those
  83.      * to other extensions if a member is logged in. Then, when we want to show prices
  84.      * in front end we need to deactivate price modifications, compare the original
  85.      * price with the price resulting from discount calculations and if a discount is
  86.      * found (price difference) we strikethrough the original price to show what it was
  87.      * like before discount, so we reactivate price modifiers, and finally show prices
  88.      * after modifications.
  89.      *
  90.      * Extensions and third party code that need to know if Memberships price modifiers
  91.      * are being applied or not in these two phases, can use doing_action and hook into
  92.      * 'wc_memberships_discounts_enable_price_adjustments' and
  93.      * 'wc_memberships_discounts_disable_price_adjustments' (and their html counterparts)
  94.      * or call directly the callbacks found in this class, which we use to add and remove
  95.      * price modifier filters. Or, if there's need to deactivate or activate Memberships
  96.      * price modifiers directly, the public callback methods that these actions use could
  97.      * also be invoked for this purpose.
  98.      *
  99.      * @see \WC_Memberships_Member_Discounts::enable_price_adjustments()
  100.      * @see \WC_Memberships_Member_Discounts::disable_price_adjustments()
  101.      *
  102.      * @internal
  103.      *
  104.      * @since 1.7.1
  105.      */
  106.     public function init() {
  107.  
  108.         $this->member_is_logged_in      = wc_memberships_is_user_member( get_current_user_id() );
  109.         $this->exclude_on_sale_products = 'yes' === get_option( 'wc_memberships_exclude_on_sale_products_from_member_discounts', 'no' );
  110.  
  111.         // refreshes the mini cart upon member login
  112.         add_action( 'wp_login', array( $this, 'refresh_cart_upon_member_login' ), 10, 2 );
  113.  
  114.         // member discount class methods are available on both frontend and backend
  115.         // but the hooks below should run in frontend only for logged in members
  116.         if ( ! ( is_admin() && ! is_ajax() ) ) {
  117.  
  118.             if ( $this->member_is_logged_in ) {
  119.  
  120.                 $this->applying_discounts = true;
  121.  
  122.                 // initialize discount actions that will be called in this class methods
  123.                 add_action( 'wc_memberships_discounts_enable_price_adjustments',       array( $this, 'enable_price_adjustments' ) );
  124.                 add_action( 'wc_memberships_discounts_enable_price_html_adjustments',  array( $this, 'enable_price_html_adjustments' ) );
  125.                 add_action( 'wc_memberships_discounts_disable_price_adjustments',      array( $this, 'disable_price_adjustments' ) );
  126.                 add_action( 'wc_memberships_discounts_disable_price_html_adjustments', array( $this, 'disable_price_html_adjustments' ) );
  127.  
  128.                 // start off by activating discounts for logged in members
  129.                 do_action( 'wc_memberships_discounts_enable_price_adjustments' );
  130.                 do_action( 'wc_memberships_discounts_enable_price_html_adjustments' );
  131.  
  132.                 // force calculations in cart
  133.                 add_filter( 'woocommerce_update_cart_action_cart_updated', '__return_true' );
  134.                 // adjust cart items prices
  135.                 add_filter( 'woocommerce_cart_item_price', array( $this, 'on_cart_item_price' ), 999, 2 );
  136.  
  137.                 // member discount badges
  138.                 add_action( 'woocommerce_before_shop_loop_item_title',   'wc_memberships_show_product_loop_member_discount_badge' );
  139.                 add_action( 'woocommerce_before_single_product_summary', 'wc_memberships_show_product_member_discount_badge' );
  140.                 add_filter( 'wc_memberships_member_discount_badge',      array( $this, 'disable_discount_badge_for_excluded_products' ), 10, 3 );
  141.  
  142.                 // make sure that the display of the "On Sale" badge is honoured
  143.                 add_filter( 'woocommerce_product_is_on_sale', array( $this, 'product_is_on_sale' ), 999, 2 );
  144.  
  145.                 // if a product is on sale and has a member discount, optionally show the sale badge
  146.                 add_action( 'woocommerce_single_product_summary', array( $this, 'display_sale_badge_for_discounted_products' ), 1 );
  147.                 add_action( 'woocommerce_shop_loop_item_title',   array( $this, 'display_sale_badge_for_discounted_products' ), 1 );
  148.  
  149.             } else {
  150.  
  151.                 $this->applying_discounts = false;
  152.  
  153.                 // ensures 'wc_memberships_get_price_html' filter hook is fired nonetheless
  154.                 add_filter( 'woocommerce_get_price_html', array( $this, 'on_price_html' ), 999, 2 );
  155.             }
  156.         }
  157.     }
  158.  
  159.  
  160.     /*
  161.      * Check if the logged in member has membership discounts for a product
  162.      *
  163.      * @since 1.6.4
  164.      * @param int|\WC_Product|\WC_Product_Variable|null $the_product Optional, a product id or object to check if it has member discounts
  165.      *                                                               (if not set, looks for a current product)
  166.      * @param int|\WP_User|null $the_user Optional, the user to check if has discounts for the product
  167.      *                                    (defaults to current user)
  168.      * @return bool
  169.      */
  170.     public function user_has_member_discount( $the_product = null, $the_user = null ) {
  171.  
  172.         $has_discount = false;
  173.  
  174.         // get the product
  175.         if ( is_numeric( $the_product ) ) {
  176.             $the_product = wc_get_product( $the_product );
  177.         } elseif ( null === $the_product ) {
  178.             global $product;
  179.  
  180.             if ( $product instanceof WC_Product ) {
  181.                 $the_product = $product;
  182.             }
  183.         }
  184.  
  185.         // bail out if no product
  186.         if ( ! $the_product instanceof WC_Product ) {
  187.             return $has_discount;
  188.         }
  189.  
  190.         // get the user id
  191.         if ( null === $the_user ) {
  192.             $member_id = get_current_user_id();
  193.         } elseif ( is_numeric( $the_user ) ) {
  194.             $member_id = (int) $the_user;
  195.         } elseif ( isset( $the_user->ID ) ) {
  196.             $member_id = (int) $the_user->ID;
  197.         } else {
  198.             return $has_discount;
  199.         }
  200.  
  201.         // bail out if user is not logged in
  202.         if ( 0 === $member_id ) {
  203.             return $has_discount;
  204.         }
  205.  
  206.         $product_id = SV_WC_Plugin_Compatibility::product_get_id( $the_product );
  207.  
  208.         // use memoized entry if found, or store a new one
  209.         if ( isset( $this->member_has_product_discount[ $member_id ][ $product_id ] ) ) {
  210.  
  211.             $has_discount = $this->member_has_product_discount[ $member_id ][ $product_id ];
  212.  
  213.         } else {
  214.  
  215.             $has_discount = wc_memberships()->get_rules_instance()->user_has_product_member_discount( $member_id, $product_id );
  216.  
  217.             // if a variable product, before return false check for its variations
  218.             if ( ! $has_discount && $the_product->has_child() ) {
  219.  
  220.                 foreach ( $the_product->get_children() as $product_child_id ) {
  221.  
  222.                     $has_discount = wc_memberships()->get_rules_instance()->user_has_product_member_discount( $member_id, $product_child_id );
  223.  
  224.                     $this->member_has_product_discount[ $member_id ][ $product_child_id ] = $has_discount;
  225.  
  226.                     // if one of the child variations has a discount, it's legit
  227.                     // to say that the parent variable product has member discounts
  228.                     if ( $has_discount ) {
  229.                         $this->member_has_product_discount[ $member_id ][ $product_id ] = $has_discount;
  230.                         break;
  231.                     // unlikely occurrence but if so we can break the loop earlier
  232.                     } elseif ( $product_id === $product_child_id ) {
  233.                         break;
  234.                     }
  235.                 }
  236.  
  237.             } else {
  238.  
  239.                 $this->member_has_product_discount[ $member_id ][ $product_id ] = $has_discount;
  240.             }
  241.         }
  242.  
  243.         return $has_discount;
  244.     }
  245.  
  246.  
  247.     /**
  248.      * Check whether products on sale should be excluded from discount rules
  249.      *
  250.      * @since 1.7.0
  251.      * @return bool
  252.      */
  253.     public function excluding_on_sale_products_from_member_discounts() {
  254.         return $this->exclude_on_sale_products;
  255.     }
  256.  
  257.  
  258.     /**
  259.      * Check if a product is to be excluded from discount rules
  260.      *
  261.      * Note: even if not excluded, discount rules may or may not still apply
  262.      *
  263.      * @since 1.7.0
  264.      * @param int|\WC_Product $product Product object or id
  265.      * @return bool
  266.      */
  267.     public function is_product_excluded_from_member_discounts( $product ) {
  268.  
  269.         if ( is_numeric( $product ) ) {
  270.             $product = wc_get_product( $product );
  271.         } elseif ( $product instanceof WP_Post ) {
  272.             $product = wc_get_product( $product );
  273.         } elseif ( ! $product instanceof WC_Product ) {
  274.             return false;
  275.         }
  276.  
  277.         $product_id = SV_WC_Plugin_Compatibility::product_get_id( $product );
  278.  
  279.         // use memoization to speed up checks
  280.         if ( isset( $this->product_excluded_from_discounts[ $product_id ] ) ) {
  281.  
  282.             $exclude = $this->product_excluded_from_discounts[ $product_id ];
  283.  
  284.         } else {
  285.  
  286.             // exclude if product-level setting is enabled to exclude this product
  287.             $exclude_product = 'yes' === get_post_meta( $product_id, '_wc_memberships_exclude_discounts', true );
  288.             // exclude if on sale and global-level setting is enabled to exclude all products on sale
  289.             $exclude_on_sale = $this->excluding_on_sale_products_from_member_discounts() && $this->product_is_on_sale_before_discount( $product );
  290.  
  291.             /**
  292.              * Filter product from having discount rules applied
  293.              *
  294.              * @since 1.7.0
  295.              * @param bool $exclude Whether the product is excluded from discount rules
  296.              * @param \WC_Product $product The product object
  297.              */
  298.             $exclude = (bool) apply_filters( 'wc_memberships_exclude_product_from_member_discounts', $exclude_product || $exclude_on_sale, $product );
  299.  
  300.             $this->product_excluded_from_discounts[ $product_id ] = $exclude;
  301.         }
  302.  
  303.         return $exclude;
  304.     }
  305.  
  306.  
  307.     /**
  308.      * Check whether a variable product is discounted
  309.      *
  310.      * Notes: does not use discounts memoization, doesn't consider parent variable product discounts
  311.      *
  312.      * @since 1.7.2
  313.      * @param int $variable_product_id The variable product id
  314.      * @return bool
  315.      */
  316.     private function variable_product_has_discount( $variable_product_id ) {
  317.         return wc_memberships()->get_rules_instance()->user_has_product_member_discount( get_current_user_id(), $variable_product_id );
  318.     }
  319.  
  320.  
  321.     /**
  322.      * Handle sale status of products
  323.      *
  324.      * @since 1.6.2
  325.      * @param bool $on_sale Whether the product is on sale
  326.      * @param \WC_Product|\WC_Product_Variable $product The product object
  327.      * @return bool
  328.      */
  329.     public function product_is_on_sale( $on_sale, $product ) {
  330.  
  331.         // bail out if any of the following:
  332.         // - user is not logged in
  333.         // - product is being excluded from member discounts
  334.         // - global Memberships setting is excluding on sale products from discounts
  335.         // - current user does not have a discount for the product that may be on sale
  336.         if (    ! $this->member_is_logged_in
  337.              ||   $this->is_product_excluded_from_member_discounts( $product )
  338.              ||   ( $on_sale && $this->excluding_on_sale_products_from_member_discounts() )
  339.              ||   ( ! is_admin() && ( ! $product instanceof WC_Product || ( $this->member_is_logged_in && ! $this->user_has_member_discount( SV_WC_Plugin_Compatibility::product_get_id( $product ) ) ) ) ) ) {
  340.  
  341.             return $on_sale;
  342.         }
  343.  
  344.         return $this->product_is_on_sale_before_discount( $product );
  345.     }
  346.  
  347.  
  348.     /**
  349.      * Determine if a product is on sale before membership price adjustments
  350.      *
  351.      * This method contains code from
  352.      * @see WC_Product::is_on_sale()
  353.      * and
  354.      * @see WC_Product_Variable::is_on_sale()
  355.      * It was introduced mainly to avoid infinite loops between
  356.      * @see \WC_Memberships_Member_Discounts::product_is_on_sale()
  357.      * and
  358.      * @see \WC_Memberships_Member_Discounts::is_product_excluded_from_member_discounts()
  359.      * so it runs very late from
  360.      * @see \WC_Memberships_Member_Discounts::product_is_on_sale()
  361.      * and does not include itself the product on sale filter that would cause
  362.      * the infinite loop, and also will remove all other filters on
  363.      * 'woocommerce_product_is_on_sale' if our own filter is running
  364.      *
  365.      * @since 1.7.0
  366.      * @param int|\WC_Product|\WC_Product_Variable $product Product object or id
  367.      * @return bool
  368.      */
  369.     public function product_is_on_sale_before_discount( $product ) {
  370.  
  371.         if ( is_numeric( $product ) ) {
  372.             $product = wc_get_product( $product );
  373.         }
  374.  
  375.         $on_sale = false;
  376.  
  377.         // sanity checks
  378.         if ( ! $product instanceof WC_Product ) {
  379.             return $on_sale;
  380.         } elseif ( ! $this->member_is_logged_in ) {
  381.             return $product->is_on_sale();
  382.         }
  383.  
  384.         $product_id = SV_WC_Plugin_Compatibility::product_get_id( $product );
  385.  
  386.         if ( isset( $this->product_is_on_sale_before_discount[ $product_id ] ) ) {
  387.  
  388.             $on_sale = $this->product_is_on_sale_before_discount[ $product_id ];
  389.  
  390.         } else {
  391.  
  392.             // disable Memberships member discount adjustments
  393.             do_action( 'wc_memberships_discounts_disable_price_adjustments' );
  394.  
  395.             /** @see WC_Product_Variable::is_on_sale() */
  396.             if ( $product->is_type( array( 'variable', 'variable-subscription' ) ) ) {
  397.  
  398.                 $prices  = $product->get_variation_prices();
  399.  
  400.                 if ( $prices['regular_price'] !== $prices['sale_price'] && $prices['sale_price'] === $prices['price'] ) {
  401.                     $on_sale = true;
  402.                 }
  403.  
  404.             /** @see WC_Product::is_on_sale() */
  405.             } else {
  406.  
  407.                 $on_sale = $product->get_sale_price() !== $product->get_regular_price() && $product->get_sale_price() === $product->get_price();
  408.             }
  409.  
  410.             // re-enable Memberships member discount adjustments
  411.             do_action( 'wc_memberships_discounts_enable_price_adjustments' );
  412.  
  413.             $this->product_is_on_sale_before_discount[ $product_id ] = $on_sale;
  414.         }
  415.  
  416.         // we need this to avoid conflicts with third party code that runs
  417.         // again 'woocommerce_product_is_on_sale' filter - since Memberships
  418.         // runs its own filter very late (priority 999) it's safe enough
  419.         // to make Memberships' filter final
  420.         if ( 'woocommerce_product_is_on_sale' === current_filter() ) {
  421.  
  422.             /** @see \WC_Memberships_Member_Discounts::__construct() */
  423.             /** @see \WC_Memberships_Member_Discounts::product_is_on_sale)() */
  424.             remove_all_filters( 'woocommerce_product_is_on_sale' );
  425.         }
  426.  
  427.         return $on_sale;
  428.     }
  429.  
  430.  
  431.     /**
  432.      * Display sale badge for discounted products
  433.      *
  434.      * @since 1.7.0
  435.      */
  436.     public function display_sale_badge_for_discounted_products() {
  437.         global $post;
  438.  
  439.         $product = wc_get_product( $post );
  440.  
  441.         // sanity check
  442.         if ( ! $product instanceof WC_Product ) {
  443.             return;
  444.         }
  445.  
  446.         /**
  447.          * Controls whether or not member prices should display sale prices as well
  448.          *
  449.          * @since 1.3.0
  450.          * @param bool $display_sale_price Defaults to false
  451.          */
  452.         $display_sale_price = (bool) apply_filters( 'wc_memberships_member_prices_display_sale_price', false );
  453.  
  454.         // determine whether the product is on sale in the first place
  455.         $on_sale_before_discount = $this->product_is_on_sale_before_discount( $product );
  456.         $member_has_discount     = $this->user_has_member_discount( $product );
  457.  
  458.         // show this badge if:
  459.         // - user has a member discount, product is on sale and we want to display the sale price badge
  460.         // - user does NOT have a member discount, but product is on sale before discount rules (compatibility)
  461.         if (    (   $member_has_discount && $on_sale_before_discount && $display_sale_price )
  462.              || ( ( ! $member_has_discount || $this->is_product_excluded_from_member_discounts( $product ) ) && $on_sale_before_discount ) ) {
  463.  
  464.             add_filter( 'woocommerce_product_is_on_sale',    array( $this, 'enable_sale_price' ) );
  465.  
  466.             wc_get_template( 'single-product/sale-flash.php' );
  467.  
  468.             remove_filter( 'woocommerce_product_is_on_sale', array( $this, 'enable_sale_price' ) );
  469.         }
  470.     }
  471.  
  472.  
  473.     /**
  474.      * Enable 'on sale' callback for a product that is on sale before discounts
  475.      *
  476.      * @internal
  477.      *
  478.      * @since 1.7.0
  479.      * @return true
  480.      */
  481.     public function enable_sale_price() {
  482.         return true;
  483.     }
  484.  
  485.  
  486.     /**
  487.      * Disable 'on sale' callback for a product that is on sale before discounts
  488.      *
  489.      * @internal
  490.      *
  491.      * @since 1.3.0
  492.      * @return true
  493.      */
  494.     public function disable_sale_price() {
  495.         return false;
  496.     }
  497.  
  498.  
  499.     /**
  500.      * Get product price before discount
  501.      *
  502.      * @since 1.7.0
  503.      * @param \WC_Product|\WC_Product_Variation $product Product
  504.      * @return float Price
  505.      */
  506.     private function get_price_before_discount( $product ) {
  507.  
  508.         // temporarily disable price adjustments
  509.         do_action( 'wc_memberships_discounts_disable_price_adjustments' );
  510.  
  511.         // get the base price without discounts
  512.         $price = $product->get_display_price();
  513.  
  514.         // re-enable price adjustments
  515.         do_action( 'wc_memberships_discounts_enable_price_adjustments' );
  516.  
  517.         return $price;
  518.     }
  519.  
  520.  
  521.     /**
  522.      * Apply purchasing discounts to product price
  523.      *
  524.      * @internal
  525.      *
  526.      * @since 1.0.0
  527.      * @param string|int|float $price Price to discount (normally a float, maybe a string number)
  528.      * @param \WC_Product $product The product object
  529.      * @return float Price
  530.      */
  531.     public function on_get_price( $price, $product ) {
  532.  
  533.         // bail out if any of the following is true:
  534.         // - member is not logged in
  535.         // - product is excluded from member discounts
  536.         // - user has no member discount over the product
  537.         if (    ! $this->member_is_logged_in
  538.              ||   $this->is_product_excluded_from_member_discounts( $product )
  539.              || ! $this->user_has_member_discount( $product ) ) {
  540.  
  541.             $price = $this->get_price_before_discount( $product );
  542.  
  543.         } else {
  544.  
  545.             // get the discounted price or return the default price if no discounts apply
  546.             $discounted_price = $this->get_discounted_price( $price, $product );
  547.  
  548.             $price = is_numeric( $discounted_price ) ? $discounted_price : $this->get_price_before_discount( $product );
  549.         }
  550.  
  551.         return (float) $price;
  552.     }
  553.  
  554.  
  555.     /**
  556.      * Get variable product price HTML before membership discounts
  557.      *
  558.      * @see \WC_Product_Variable::get_price_html()
  559.      *
  560.      * @since 1.7.1
  561.      * @param \WC_Product_Variable $product Variable product
  562.      * @param bool $display_sale_price Whether to display sale price
  563.      * @return string HTML
  564.      */
  565.     private function get_variable_product_price_html_before_discount( $product, $display_sale_price ) {
  566.  
  567.         $prices = $product->get_variation_prices( true );
  568.  
  569.         if ( empty( $prices['price'] ) || '' === $product->get_price() ) {
  570.  
  571.             $html_before_discount = apply_filters( 'woocommerce_variable_empty_price_html', '', $product );
  572.  
  573.         } else {
  574.  
  575.             $regular_min = $sale_min = $product->get_variation_regular_price( 'min', true );
  576.             $regular_max = $sale_max = $product->get_variation_regular_price( 'max', true );
  577.  
  578.             $regular_price = $regular_min !== $regular_max ? sprintf( _x( '%1$s&ndash;%2$s', 'Price range: from-to', 'woocommerce-memberships' ), wc_price( $regular_min ), wc_price( $regular_max ) ) : wc_price( $regular_min );
  579.  
  580.             if ( $on_sale = $this->product_is_on_sale_before_discount( $product ) ) {
  581.  
  582.                 $sale_min = $product->get_variation_sale_price( 'min', true );
  583.                 $sale_max = $product->get_variation_sale_price( 'max', true );
  584.  
  585.                 $sale_price = $sale_min !== $sale_max ? sprintf( _x( '%1$s&ndash;%2$s', 'Price range: from-to', 'woocommerce-memberships' ), wc_price( $sale_min ), wc_price( $sale_max ) ) : wc_price( $sale_min );
  586.  
  587.                 if ( $this->user_has_member_discount( $product ) ) {
  588.                     $html_before_discount = $display_sale_price ? apply_filters( 'woocommerce_variable_sale_price_html', $sale_price, $product ) : $regular_price;
  589.                 } else {
  590.                     $html_before_discount = $html = '<del>' . $regular_price . '</del> <ins>' . $sale_price . '</ins>';
  591.                     $html_before_discount = apply_filters( 'woocommerce_variable_sale_price_html', $html_before_discount, $product );
  592.                 }
  593.  
  594.             } else {
  595.  
  596.                 $html_before_discount = $regular_price;
  597.             }
  598.  
  599.             // sanity check for free products:
  600.             // if min and max variations are 0, this product is free
  601.             $min_price = (float) current( $prices['price'] );
  602.             $max_price = (float) end( $prices['price'] );
  603.  
  604.             if ( empty( $min_price ) && empty( $max_price ) ) {
  605.                 $html_before_discount = apply_filters( 'woocommerce_variable_free_price_html', _x( 'Free!', 'Free product', 'woocommerce-memberships' ), $product );
  606.             }
  607.         }
  608.  
  609.         return $html_before_discount;
  610.     }
  611.  
  612.  
  613.     /**
  614.      *
  615.      * Get variable product price HTML before membership discounts
  616.      *
  617.      * @since 1.7.2
  618.      * @param \WC_Product_Variable $product Variable product
  619.      * @return string HTML price
  620.      */
  621.     private function get_variable_product_price_html_after_discount( $product ) {
  622.  
  623.         $price_min = null;
  624.         $price_max = null;
  625.  
  626.         if ( $this->variable_product_has_discount( $product->id ) ) {
  627.  
  628.             // temporarily disable membership price adjustments
  629.             do_action( 'wc_memberships_discounts_disable_price_adjustments' );
  630.             do_action( 'wc_memberships_discounts_disable_price_html_adjustments' );
  631.  
  632.             $price_min = $product->get_variation_price( 'min', true );
  633.             $price_min = $price_min > 0 ? $this->get_discounted_price( $price_min, $product ) : 0;
  634.             $price_max = $product->get_variation_price( 'max', true );
  635.             $price_max = $price_max > 0 ? $this->get_discounted_price( $price_max, $product ) : 0;
  636.  
  637.             // re-enable membership price adjustments
  638.             do_action( 'wc_memberships_discounts_enable_price_adjustments' );
  639.             do_action( 'wc_memberships_discounts_enable_price_html_adjustments' );
  640.         }
  641.  
  642.         // this may be a case when one or more product variations
  643.         // have discounts but the parent variable product has none
  644.         if ( ( empty( $price_min ) || empty( $price_max ) ) && ( $variations = $product->get_children() ) ) {
  645.  
  646.             $variation_prices = array();
  647.  
  648.             foreach ( $variations as $variation_id ) {
  649.                 $variation_prices[] = wc_get_product( $variation_id )->get_display_price();
  650.             }
  651.  
  652.             if ( ! empty( $variation_prices ) ) {
  653.                 $price_min = min( $variation_prices );
  654.                 $price_max = max( $variation_prices );
  655.             }
  656.         }
  657.  
  658.         if ( $price_min !== $price_max ) {
  659.             $html_after_discount = sprintf( _x( '%1$s&ndash;%2$s', 'Price range: from-to', 'woocommerce-memberships' ), wc_price( $price_min ), wc_price( $price_max ) );
  660.         } elseif ( empty( $price_min ) && empty( $price_max ) ) {
  661.             $html_after_discount = $this->get_void_product_price_html_before_discount( $product );
  662.         } else {
  663.             $html_after_discount = wc_price( $price_min );
  664.         }
  665.  
  666.         $html_after_discount .= $product->get_price_suffix( $product->get_price() );
  667.  
  668.         return $html_after_discount;
  669.     }
  670.  
  671.  
  672.     /**
  673.      * Get void product price HTML before membership discounts
  674.      *
  675.      * @see \WC_Product::get_price_html()
  676.      *
  677.      * @since 1.7.1
  678.      * @param \WC_Product $product Product object
  679.      * @return string HTML
  680.      */
  681.     private function get_void_product_price_html_before_discount( $product ) {
  682.  
  683.         $price = $product->get_price();
  684.  
  685.         if ( empty( $price ) && '' !== $price ) {
  686.  
  687.             if ( $this->product_is_on_sale_before_discount( $product ) && $product->get_regular_price() ) {
  688.  
  689.                 $price_label = $product->get_price_html_from_to( $product->get_display_price( $product->get_regular_price() ), _x( 'Free!', 'Free product', 'woocommerce-memberships' ) );
  690.  
  691.                 $html_before_discount = apply_filters( 'woocommerce_free_sale_price_html', $price_label, $this );
  692.  
  693.             } else {
  694.  
  695.                 $html_before_discount = apply_filters( 'woocommerce_free_price_html', _x( 'Free!', 'Free product', 'woocommerce-memberships' ), $product );
  696.             }
  697.  
  698.         } else {
  699.  
  700.             $html_before_discount = apply_filters( 'woocommerce_empty_price_html', '', $product );
  701.         }
  702.  
  703.         return $html_before_discount;
  704.     }
  705.  
  706.  
  707.     /**
  708.      * Get simple or variation product price before membership discounts
  709.      *
  710.      * @see \WC_Product::get_price_html()
  711.      * @see \WC_Product_Variation::get_price_html()
  712.      *
  713.      * @since 1.7.1
  714.      * @param \WC_Product|\WC_Product_Variation $product A simple product or a variation
  715.      * @param bool $display_sale_price Whether to display sale prices
  716.      * @return string HTML
  717.      */
  718.     private function get_product_price_html_before_discount( $product, $display_sale_price ) {
  719.  
  720.         $on_sale      = $this->product_is_on_sale_before_discount( $product );
  721.         $is_variation = $product->is_type( 'variation' );
  722.  
  723.         if ( $is_variation && '' === $product->get_price() ) {
  724.  
  725.             $html_before_discount = apply_filters( 'woocommerce_variation_empty_price_html', '', $product );
  726.  
  727.         } else {
  728.  
  729.             if ( $this->user_has_member_discount( $product ) && ! $this->is_product_excluded_from_member_discounts( $product ) )  {
  730.  
  731.                 if ( $on_sale ) {
  732.                     $price_before_discount = $display_sale_price ? $product->get_display_price( $product->get_sale_price() ) : $product->get_display_price( $product->get_regular_price() );
  733.                     $html_before_discount  = wc_price( $price_before_discount );
  734.                 } else {
  735.                     $price_before_discount = $product->get_display_price( $product->get_regular_price() );
  736.                     $html_before_discount  = wc_price( $price_before_discount );
  737.                 }
  738.  
  739.             } else {
  740.  
  741.                 if ( $on_sale ) {
  742.                     $html_before_discount  = $product->get_price_html_from_to( $product->get_display_price( $product->get_regular_price() ), $product->get_display_price( $product->get_sale_price() ) );
  743.                 } else {
  744.                     $price_before_discount = $product->get_display_price( $product->get_regular_price() );
  745.                     $html_before_discount  = wc_price( $price_before_discount );
  746.                 }
  747.             }
  748.  
  749.             // maybe apply wc filters before returning
  750.             if ( $on_sale ) {
  751.                 $sale_price_html_filter = $is_variation ? 'woocommerce_variation_sale_price_html' : 'woocommerce_sale_price_html';
  752.                 $html_before_discount   = apply_filters( $sale_price_html_filter, $html_before_discount, $product );
  753.             } elseif ( $is_variation && ! $product->get_price() > 0 ) {
  754.                 $html_before_discount = apply_filters( 'woocommerce_variation_free_price_html', _x( 'Free!', 'Free product', 'woocommerce-memberships' ), $product );
  755.             }
  756.         }
  757.  
  758.         return $html_before_discount;
  759.     }
  760.  
  761.  
  762.     /**
  763.      * Get product HTML price before discount
  764.      *
  765.      * @since 1.7.0
  766.      * @param \WC_Product|\WC_Product_Variable|\WC_Product_Variation $product Product
  767.      * @return string HTML
  768.      */
  769.     private function get_price_html_before_discount( $product ) {
  770.  
  771.         /**
  772.          * Controls whether or not member prices should display sale prices as well
  773.          *
  774.          * @since 1.3.0
  775.          * @param bool $display_sale_price Defaults to false
  776.          */
  777.         $display_sale_price = (bool) apply_filters( 'wc_memberships_member_prices_display_sale_price', false );
  778.  
  779.         // temporarily disable membership price adjustments
  780.         do_action( 'wc_memberships_discounts_disable_price_adjustments' );
  781.         do_action( 'wc_memberships_discounts_disable_price_html_adjustments' );
  782.  
  783.         if ( ! $product->get_price() && ! $product->is_type( array( 'variable', 'variation' ) ) ) {
  784.             // simple products with 0 or empty price
  785.             $html_before_discount = $this->get_void_product_price_html_before_discount( $product );
  786.         } elseif ( $product->is_type( 'variable' ) ) {
  787.             // variable products
  788.             $html_before_discount = $this->get_variable_product_price_html_before_discount( $product, $display_sale_price );
  789.         } else {
  790.             // simple products with non-void prices, product variations
  791.             $html_before_discount = $this->get_product_price_html_before_discount( $product, $display_sale_price );
  792.         }
  793.  
  794.         // re-enable membership price adjustments
  795.         do_action( 'wc_memberships_discounts_enable_price_adjustments' );
  796.         do_action( 'wc_memberships_discounts_enable_price_html_adjustments' );
  797.  
  798.         return $html_before_discount . $product->get_price_suffix( $this->get_price_before_discount( $product ) );
  799.     }
  800.  
  801.  
  802.     /**
  803.      * Adjust discounted product price HTML
  804.      *
  805.      * @internal
  806.      *
  807.      * @since 1.3.0
  808.      * @param string $html The price HTML maybe after discount
  809.      * @param \WC_Product|\WC_Product_Variable|\WC_Product_Variation $product The product object for which we may have discounts
  810.      * @return string The original price HTML if no discount or a new formatted string showing before/after discount
  811.      */
  812.     public function on_price_html( $html, $product ) {
  813.  
  814.         /**
  815.          * Controls whether or not member prices should use discount format when displayed
  816.          *
  817.          * @since 1.3.0
  818.          * @param bool $use_discount_format Defaults to true
  819.          */
  820.         $use_discount_format = (bool) apply_filters( 'wc_memberships_member_prices_use_discount_format', true );
  821.  
  822.         // bail out if any of the following conditions applies:
  823.         // - custom code set to not to use discount format
  824.         // - no member user is logged in
  825.         // - product is excluded from discount rules
  826.         // - current user has no discounts for the product
  827.         // - product has no applicable member discount
  828.         if (    ! $use_discount_format
  829.              || ! $this->member_is_logged_in
  830.              ||   $this->is_product_excluded_from_member_discounts( $product )
  831.              || ! $this->user_has_member_discount( $product ) ) {
  832.  
  833.             if ( $this->applying_discounts ) {
  834.                 $html = $this->get_price_html_before_discount( $product );
  835.             }
  836.  
  837.         } else {
  838.  
  839.             // get string price BEFORE discount
  840.             $html_before_discount = $this->get_price_html_before_discount( $product );
  841.  
  842.             // get string price AFTER discount
  843.             $html_after_discount = $html;
  844.  
  845.             // special handling for variable products
  846.             if ( $product->is_type( 'variable' ) ) {
  847.                 $html_after_discount = $this->get_variable_product_price_html_after_discount( $product );
  848.             }
  849.  
  850.             // string prices do not match, we have a discount
  851.             if ( $html_after_discount !== $html_before_discount ) {
  852.  
  853.                 $html = '<del>' . $html_before_discount . '</del> <ins>' . $html_after_discount . '</ins>';
  854.  
  855.                 // special handling for variable products
  856.                 if ( ! $product->is_type( 'variable' ) && $this->variable_product_has_discount( $product->id ) ) {
  857.  
  858.                     $price_before_discount = $this->get_price_before_discount( $product );
  859.                     $price_after_discount  = $product->get_price();
  860.  
  861.                     if ( empty( $price_before_discount ) && empty( $price_after_discount ) ) {
  862.  
  863.                         $html = $html_after_discount;
  864.                     }
  865.                 }
  866.             }
  867.  
  868.             // add a "Member Discount" badge for single variation prices
  869.             if ( $product->is_type( 'variation' ) ) {
  870.  
  871.                 // a "Member Discount" text label is shown when selecting this variation
  872.                 $html .= ' ' . $this->get_member_discount_badge( $product, true );
  873.             }
  874.  
  875.             /**
  876.              * Filter the HTML price after member discounts have been applied
  877.              *
  878.              * @since 1.7.2
  879.              * @param string $html The price HTML
  880.              * @param \WC_Product $product The product the discounted price is meant for
  881.              */
  882.             $html = apply_filters( 'wc_memberships_get_discounted_price_html', $html, $product );
  883.         }
  884.  
  885.         /**
  886.          * Filter the HTML price after member discounts may have been applied
  887.          *
  888.          * @since 1.7.1
  889.          * @param string $html The price HTML
  890.          * @param \WC_Product $product The product the price is meant for
  891.          */
  892.         return apply_filters( 'wc_memberships_get_price_html', $html, $product );
  893.     }
  894.  
  895.  
  896.     /**
  897.      * Adjust variation price
  898.      *
  899.      * @internal
  900.      *
  901.      * @since 1.3.0
  902.      * @param float $price The product price, maybe discounted
  903.      * @param \WC_Product|\WC_Product_Variation $product The product object
  904.      * @param string $min_or_max Min-max prices of variations
  905.      * @param bool $display If to be displayed
  906.      * @return float
  907.      */
  908.     public function on_get_variation_price( $price, $product, $min_or_max, $display ) {
  909.  
  910.         // bail out if any of the following applies:
  911.         // - no user member is logged in
  912.         // - the product is excluded from receiving member discounts
  913.         // - logged in user has no member discount over the product
  914.         if (    ! $this->member_is_logged_in
  915.              || ! $product instanceof WC_Product
  916.              ||   $this->is_product_excluded_from_member_discounts( $product )
  917.              || ! $this->user_has_member_discount( $product ) ) {
  918.  
  919.             $price_float = (float) $price;
  920.  
  921.             return $this->applying_discounts && empty( $price_float ) ? $this->get_price_before_discount( $product ) : $price;
  922.         }
  923.  
  924.         $product_id = SV_WC_Plugin_Compatibility::product_get_id( $product );
  925.  
  926.         // use memoized entry if available to speed up return
  927.         if ( isset( $this->product_discount_variation[ $product_id ][ $min_or_max ] ) ) {
  928.  
  929.             $price = $this->product_discount_variation[ $product_id ][ $min_or_max ];
  930.  
  931.         } else {
  932.  
  933.             // defaults
  934.             $calc_price = $price;
  935.             $min_price  = $price;
  936.             $max_price  = $price;
  937.  
  938.             // get variation ids
  939.             $children = $product->get_children();
  940.  
  941.             if ( ! empty( $children ) ) {
  942.  
  943.                 foreach ( $children as $variation_id ) {
  944.  
  945.                     // make sure we start from the normal un-discounted price
  946.                     do_action( 'wc_memberships_discounts_disable_price_adjustments' );
  947.  
  948.                     if ( $display ) {
  949.  
  950.                         if ( $variation = $product->get_child( $variation_id ) ) {
  951.  
  952.                             // in display mode, we need to account for taxes
  953.                             $base_price = $variation->get_price() > 0 ? $variation->get_display_price() : 0;
  954.                             $calc_price = $base_price;
  955.  
  956.                             // try getting the discounted price for the variation
  957.                             $discounted_price = $this->get_discounted_price( $base_price, $variation->id );
  958.  
  959.                             // if there's a difference, grab discounted price
  960.                             if ( is_numeric( $discounted_price ) && $base_price !== $discounted_price ) {
  961.                                 $calc_price = $discounted_price;
  962.                             }
  963.                         }
  964.  
  965.                     } else {
  966.  
  967.                         $calc_price = (float) get_post_meta( $variation_id, '_price', true );
  968.  
  969.                         // try getting the discounted price for the variation
  970.                         $discounted_price = $this->get_discounted_price( $calc_price, $variation_id );
  971.  
  972.                         // if there's a difference, grab discounted price
  973.                         if ( is_numeric( $discounted_price ) && $calc_price !== $discounted_price ) {
  974.                             $calc_price = $discounted_price;
  975.                         }
  976.                     }
  977.  
  978.                     // re-enable discounts in pricing flow
  979.                     do_action( 'wc_memberships_discounts_enable_price_adjustments' );
  980.  
  981.                     if ( $min_price === null || $calc_price < $min_price ) {
  982.                         $min_price = $calc_price;
  983.                     }
  984.  
  985.                     if ( $max_price === null || $calc_price > $max_price ) {
  986.                         $max_price = $calc_price;
  987.                     }
  988.                 }
  989.             }
  990.  
  991.             if ( $min_or_max === 'min' ) {
  992.                 $price = (float) $min_price;
  993.             } elseif ( $min_or_max === 'max' ) {
  994.                 $price = (float) $max_price;
  995.             }
  996.  
  997.             $this->product_discount_variation[ $product_id ][ $min_or_max ] = $price;
  998.         }
  999.  
  1000.         return (float) $price;
  1001.     }
  1002.  
  1003.  
  1004.     /**
  1005.      * Adjust discounted cart item price HTML
  1006.      *
  1007.      * @internal
  1008.      *
  1009.      * @since 1.3.0
  1010.      * @param string $html Price HTML
  1011.      * @param array $cart_item The cart item data
  1012.      * @return string
  1013.      */
  1014.     public function on_cart_item_price( $html, $cart_item ) {
  1015.  
  1016.         // get the product
  1017.         $product = ! empty( $cart_item['data'] ) ? $cart_item['data'] : false;
  1018.  
  1019.         // bail out if any of the following applies:
  1020.         // - no user member is logged in
  1021.         // - no product (sanity check)
  1022.         // - the product is excluded from receiving member discounts
  1023.         // - logged in user has no member discount over the product
  1024.         if (    ! $this->member_is_logged_in
  1025.              || ! $product instanceof WC_Product
  1026.              ||   $this->is_product_excluded_from_member_discounts( $product )
  1027.              || ! $this->user_has_member_discount( $product ) ) {
  1028.  
  1029.             return $html;
  1030.         }
  1031.  
  1032.         // temporarily disable our price adjustments
  1033.         do_action( 'wc_memberships_discounts_disable_price_adjustments' );
  1034.  
  1035.         // so we can get the base price without member discounts
  1036.         // also, in cart we need to account for tax display
  1037.         $price = $product->get_display_price();
  1038.  
  1039.         // re-enable disable our price adjustments
  1040.         do_action( 'wc_memberships_discounts_enable_price_adjustments' );
  1041.  
  1042.         if ( $this->has_discounted_price( $price, $product ) ) {
  1043.  
  1044.             // in cart, we need to account for tax display
  1045.             $discounted_price = $product->get_display_price();
  1046.  
  1047.             /** This filter is documented in class-wc-memberships-member-discounts.php **/
  1048.             $use_discount_format = apply_filters( 'wc_memberships_use_discount_format', true );
  1049.  
  1050.             // output html price before/after discount
  1051.             if ( $use_discount_format && $discounted_price < $price ) {
  1052.                 $html = '<del>' . wc_price( $price ) . '</del> <ins>' . wc_price( $discounted_price ) . '</ins>';
  1053.             }
  1054.         }
  1055.  
  1056.         return $html;
  1057.     }
  1058.  
  1059.  
  1060.     /**
  1061.      * Add the current user ID to the variation prices hash for caching.
  1062.      *
  1063.      * @internal
  1064.      *
  1065.      * @since 1.3.2
  1066.      * @param array $data The existing hash data
  1067.      * @param \WC_Product $product The current product variation
  1068.      * @return array $data The hash data with a user ID added if applicable
  1069.      */
  1070.     public function set_user_variation_prices_hash( $data, $product ) {
  1071.  
  1072.         // bail out if member is not logged in
  1073.         // or logged in user has no membership discount over the product
  1074.         if (      $this->member_is_logged_in
  1075.              && ! $this->is_product_excluded_from_member_discounts( $product )
  1076.              &&   $this->user_has_member_discount( $product ) ) {
  1077.  
  1078.             $data[] = get_current_user_id();
  1079.         }
  1080.  
  1081.         return $data;
  1082.     }
  1083.  
  1084.  
  1085.     /**
  1086.      * Get member discount badge
  1087.      *
  1088.      * @since 1.6.4
  1089.      * @param \WC_Product $product The product object to output a badge for (passed to filter)
  1090.      * @param bool $variation Whether to output a discount badge specific for a product variation (default false)
  1091.      * @return string
  1092.      */
  1093.     public function get_member_discount_badge( $product, $variation = false ) {
  1094.  
  1095.         $label = __( 'Member discount!', 'woocommerce-memberships' );
  1096.  
  1097.         // we have a slight different output for badge classes and filter
  1098.         if ( true !== $variation ) {
  1099.             global $post;
  1100.  
  1101.             // used in filter for backwards compatibility reasons
  1102.             $the_post = $post;
  1103.  
  1104.             if ( ! $the_post instanceof WP_Post ) {
  1105.                 $the_post = $product->post;
  1106.             }
  1107.  
  1108.             $badge = '<span class="onsale wc-memberships-member-discount">' . esc_html( $label ) . '</span>';
  1109.  
  1110.             /**
  1111.              * Filter the member discount badge
  1112.              *
  1113.              * @since 1.0.0
  1114.              * @param string $badge The badge HTML
  1115.              * @param \WP_Post $post The product post object
  1116.              * @param \WC_Product_Variation $variation The product variation
  1117.              */
  1118.             $badge = apply_filters( 'wc_memberships_member_discount_badge', $badge, $the_post, $product );
  1119.  
  1120.         } else {
  1121.  
  1122.             $badge = '<span class="wc-memberships-variation-member-discount">' . esc_html( $label ) . '</span>';
  1123.  
  1124.             /**
  1125.              * Filter the variation member discount badge
  1126.              *
  1127.              * @since 1.3.2
  1128.              * @param string $badge The badge HTML
  1129.              * @param \WC_Product|\WC_Product_Variation $variation The product variation
  1130.              */
  1131.             $badge = apply_filters( 'wc_memberships_variation_member_discount_badge', $badge, $product );
  1132.  
  1133.         }
  1134.  
  1135.         return $badge;
  1136.     }
  1137.  
  1138.  
  1139.     /**
  1140.      * Filter the member discount badge for products excluded
  1141.      * from member discount rules
  1142.      *
  1143.      * @internal
  1144.      *
  1145.      * @since 1.7.0
  1146.      * @param string $badge Badge HTML
  1147.      * @param \WP_Post $post The post object
  1148.      * @param \WC_Product $product The product object
  1149.      * @return string Empty string if product is excluded from member discounts
  1150.      */
  1151.     public function disable_discount_badge_for_excluded_products( $badge, $post, $product ) {
  1152.         return $this->is_product_excluded_from_member_discounts( $product ) ? '' : $badge;
  1153.     }
  1154.  
  1155.  
  1156.     /**
  1157.      * Get product discounted price for member
  1158.      *
  1159.      * @since 1.3.0
  1160.      * @param float $base_price Original price
  1161.      * @param int|\WC_Product $product Product ID or product object
  1162.      * @param int|null $member_id Optional, defaults to current user id
  1163.      * @return float|null The discounted price or null if no discount applies
  1164.      */
  1165.     public function get_discounted_price( $base_price, $product, $member_id = null ) {
  1166.  
  1167.         if ( ! $member_id ) {
  1168.             $member_id = get_current_user_id();
  1169.         }
  1170.  
  1171.         if ( ! is_object( $product ) ) {
  1172.             $product = wc_get_product( $product );
  1173.         }
  1174.  
  1175.         $price          = null;
  1176.         $product_id     = null;
  1177.         $discount_rules = array();
  1178.  
  1179.         // we need a product and a user to get a member discounted price
  1180.         if ( $product instanceof WC_Product && $member_id > 0 ) {
  1181.  
  1182.             $product_id     = SV_WC_Plugin_Compatibility::product_get_id( $product );
  1183.             $discount_rules = wc_memberships()->get_rules_instance()->get_user_product_purchasing_discount_rules( $member_id, $product_id );
  1184.         }
  1185.  
  1186.         if ( $product_id && ! empty( $discount_rules ) ) {
  1187.  
  1188.             if ( isset( $this->product_discount[ $member_id ][ $product_id ] ) ) {
  1189.  
  1190.                 $price = $this->product_discount[ $member_id ][ $product_id ];
  1191.  
  1192.             } else {
  1193.  
  1194.                 /**
  1195.                  * Filter whether to allow stacking product discounts
  1196.                  * for members of multiple plans with overlapping discount rules
  1197.                  * for the same products
  1198.                  *
  1199.                  * @since 1.7.0
  1200.                  *
  1201.                  * @param bool $allow_cumulative_discounts Default true (allow)
  1202.                  * @param int $member_id The user id discounts are calculated for
  1203.                  * @param \WC_Product $product The product object being discounted
  1204.                  */
  1205.                 $allow_cumulative_discounts = apply_filters( 'wc_memberships_allow_cumulative_member_discounts', true, $member_id, $product );
  1206.  
  1207.                 $price  = (float) $base_price;
  1208.                 $prices = array();
  1209.  
  1210.                 // find out the discounted price for the current user
  1211.                 foreach ( $discount_rules as $rule ) {
  1212.  
  1213.                     switch ( $rule->get_discount_type() ) {
  1214.  
  1215.                         case 'percentage':
  1216.                             $discounted_price = $price * ( 100 - $rule->get_discount_amount() ) / 100;
  1217.                         break;
  1218.  
  1219.                         case 'amount':
  1220.                             $discounted_price = $price - $rule->get_discount_amount();
  1221.                         break;
  1222.                     }
  1223.  
  1224.                     // make sure that the lowest price gets applied and doesn't become negative
  1225.                     if ( isset( $discounted_price ) && $discounted_price < $price ) {
  1226.  
  1227.                         if ( true === $allow_cumulative_discounts ) {
  1228.                             $price = max( $discounted_price, 0 );
  1229.                         } else {
  1230.                             $prices[] = max( $discounted_price, 0 );
  1231.                         }
  1232.                     }
  1233.                 }
  1234.  
  1235.                 // pick the lowest price
  1236.                 if ( ! empty( $prices ) ) {
  1237.                     $price = min( $prices );
  1238.                 }
  1239.  
  1240.                 // sanity check
  1241.                 if ( $price >= $base_price ) {
  1242.                     $price = null;
  1243.                 }
  1244.  
  1245.                 $this->product_discount[ $product_id ] = $price;
  1246.             }
  1247.         }
  1248.  
  1249.         /**
  1250.          * Filter discounted price of a membership product
  1251.          *
  1252.          * @since 1.7.1
  1253.          * @param null|float $price The discounted price or null if no discount applies
  1254.          * @param float $base_price The original price (not discounted by Memberships)
  1255.          * @param int $product_id The id of the product (or variation) the price is for
  1256.          * @param int $member_id THe id of the logged in member (it's zero for non logged in users)
  1257.          */
  1258.         return apply_filters( 'wc_memberships_get_discounted_price', $price, $base_price, $product_id, $member_id );
  1259.     }
  1260.  
  1261.  
  1262.     /**
  1263.      * Check if the product is discounted for the user
  1264.      *
  1265.      * @since 1.3.0
  1266.      * @param float $base_price Original price
  1267.      * @param int|\WC_Product $product Product ID or object
  1268.      * @param null|int $user_id Optional, defaults to current user id
  1269.      * @return bool
  1270.      */
  1271.     public function has_discounted_price( $base_price, $product, $user_id = null ) {
  1272.  
  1273.         if ( ! is_object( $product ) ) {
  1274.             $product = wc_get_product( $product );
  1275.         }
  1276.  
  1277.         $has_discounted_price = is_numeric( $this->get_discounted_price( $base_price, $product, $user_id ) );
  1278.  
  1279.         if ( ! $has_discounted_price && $product->is_type( 'variable' ) && ( $variations = $product->get_children() ) ) {
  1280.  
  1281.             $variations_discounts = array();
  1282.  
  1283.             foreach ( $variations as $variation_id ) {
  1284.  
  1285.                 $variations_discounts[] = $this->has_discounted_price( $base_price, $variation_id, $user_id );
  1286.             }
  1287.  
  1288.             $has_discounted_price = in_array( true, $variations_discounts, true );
  1289.         }
  1290.  
  1291.         return $has_discounted_price;
  1292.     }
  1293.  
  1294.  
  1295.     /**
  1296.      * Refresh cart fragments upon member login
  1297.      *
  1298.      * This is useful if a non-logged in member added items to cart
  1299.      * which should have otherwise membership discounts applied
  1300.      *
  1301.      * @internal
  1302.      *
  1303.      * @see \WC_Cart::reset()
  1304.      *
  1305.      * @since 1.6.4
  1306.      * @param string $user_login User login name
  1307.      * @param \WP_User $user User that just logged in
  1308.      */
  1309.     public function refresh_cart_upon_member_login( $user_login, $user ) {
  1310.  
  1311.         // small "hack" to trigger a refresh in cart contents
  1312.         // that will set any membership discounts to products that apply
  1313.         if ( $user_login && wc_memberships_is_user_active_member( $user, null, false ) ) {
  1314.  
  1315.             $this->reset_cart_session_data();
  1316.         }
  1317.     }
  1318.  
  1319.  
  1320.     /**
  1321.      * Reset cart session data
  1322.      *
  1323.      * @see \WC_Cart::reset() private method
  1324.      *
  1325.      * @since 1.6.4
  1326.      */
  1327.     private function reset_cart_session_data() {
  1328.  
  1329.         $wc = WC();
  1330.  
  1331.         // some very zealous sanity checks here
  1332.         if ( $wc && isset( $wc->cart->cart_session_data ) ) {
  1333.  
  1334.             $session_data = $wc->cart->cart_session_data;
  1335.  
  1336.             if ( ! empty( $session_data ) ) {
  1337.  
  1338.                 foreach ( $session_data as $key => $default ) {
  1339.  
  1340.                     if ( isset( $wc->session->$key ) ) {
  1341.                         unset( $wc->session->$key );
  1342.                     }
  1343.                 }
  1344.             }
  1345.  
  1346.             // WooCommerce core filter
  1347.             do_action( 'woocommerce_cart_reset', $wc->cart, true );
  1348.         }
  1349.     }
  1350.  
  1351.  
  1352.     /**
  1353.      * Enable price adjustments
  1354.      *
  1355.      * Calling this method will **enable** Membership adjustments
  1356.      * for product prices that have member discounts for logged in members
  1357.      *
  1358.      * @see \WC_Memberships_Member_Discounts::__construct() docblock for additional notes
  1359.      * @see \WC_Memberships_Member_Discounts::enable_price_html_adjustments() which you'll probably want to use too
  1360.      *
  1361.      * @since 1.3.0
  1362.      */
  1363.     public function enable_price_adjustments() {
  1364.  
  1365.         // apply membership discount to product price
  1366.         add_filter( 'woocommerce_get_price',                 array( $this, 'on_get_price' ), 999, 2 );
  1367.         add_filter( 'woocommerce_variation_prices_price',    array( $this, 'on_get_price' ), 999, 2 );
  1368.         add_filter( 'woocommerce_get_variation_price',       array( $this, 'on_get_variation_price' ), 999, 4 );
  1369.         add_filter( 'woocommerce_get_variation_prices_hash', array( $this, 'set_user_variation_prices_hash' ), 999, 2 );
  1370.     }
  1371.  
  1372.  
  1373.     /**
  1374.      * Disable price adjustments
  1375.      *
  1376.      * Calling this method will **disable** Membership adjustments
  1377.      * for product prices that have member discounts for logged in members
  1378.      *
  1379.      * @see \WC_Memberships_Member_Discounts::__construct() docblock for additional notes
  1380.      * @see \WC_Memberships_Member_Discounts::disable_price_html_adjustments() which you'll probably want to use too
  1381.      *
  1382.      * @since 1.3.0
  1383.      */
  1384.     public function disable_price_adjustments() {
  1385.  
  1386.         // restore price to original amount before membership discount
  1387.         remove_filter( 'woocommerce_get_price',                 array( $this, 'on_get_price' ), 999 );
  1388.         remove_filter( 'woocommerce_variation_prices_price',    array( $this, 'on_get_price' ), 999 );
  1389.         remove_filter( 'woocommerce_get_variation_price',       array( $this, 'on_get_variation_price' ), 999 );
  1390.         remove_filter( 'woocommerce_get_variation_prices_hash', array( $this, 'set_user_variation_prices_hash' ), 999 );
  1391.     }
  1392.  
  1393.  
  1394.     /**
  1395.      * Enable price HTML adjustments
  1396.      *
  1397.      * @see \WC_Memberships_Member_Discounts::__construct() docblock for additional notes
  1398.      * @see \WC_Memberships_Member_Discounts::enable_price_adjustments() which you'll probably want to use too
  1399.      *
  1400.      * @since 1.3.0
  1401.      */
  1402.     public function enable_price_html_adjustments() {
  1403.  
  1404.         // filter the prices to apply member discounts
  1405.         add_filter( 'woocommerce_variation_price_html', array( $this, 'on_price_html' ), 999, 2 );
  1406.         add_filter( 'woocommerce_get_price_html',       array( $this, 'on_price_html' ), 999, 2 );
  1407.     }
  1408.  
  1409.  
  1410.     /**
  1411.      * Disable price HTML adjustments
  1412.      *
  1413.      * @see \WC_Memberships_Member_Discounts::__construct() docblock for additional notes
  1414.      * @see \WC_Memberships_Member_Discounts::disable_price_adjustments() which you'll probably want to use too
  1415.      *
  1416.      * @since 1.3.0
  1417.      */
  1418.     public function disable_price_html_adjustments() {
  1419.  
  1420.         // so we can display prices before discount
  1421.         remove_filter( 'woocommerce_get_price_html',       array( $this, 'on_price_html' ), 999 );
  1422.         remove_filter( 'woocommerce_variation_price_html', array( $this, 'on_price_html' ), 999 );
  1423.     }
  1424.  
  1425.  
  1426. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement