Advertisement
Guest User

calss-wc-memberships-member-discounts.php

a guest
Nov 1st, 2016
709
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 46.47 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.      * Handle sale status of products
  309.      *
  310.      * @since 1.6.2
  311.      * @param bool $on_sale Whether the product is on sale
  312.      * @param \WC_Product|\WC_Product_Variable $product The product object
  313.      * @return bool
  314.      */
  315.     public function product_is_on_sale( $on_sale, $product ) {
  316.  
  317.         // bail out if any of the following:
  318.         // - user is not logged in
  319.         // - product is being excluded from member discounts
  320.         // - global Memberships setting is excluding on sale products from discounts
  321.         // - current user does not have a discount for the product that may be on sale
  322.         if (    ! $this->member_is_logged_in
  323.              ||   $this->is_product_excluded_from_member_discounts( $product )
  324.              ||   ( $on_sale && $this->excluding_on_sale_products_from_member_discounts() )
  325.              ||   ( ! is_admin() && ( ! $product instanceof WC_Product || ( $this->member_is_logged_in && ! $this->user_has_member_discount( SV_WC_Plugin_Compatibility::product_get_id( $product ) ) ) ) ) ) {
  326.  
  327.             return $on_sale;
  328.         }
  329.  
  330.         return $this->product_is_on_sale_before_discount( $product );
  331.     }
  332.  
  333.  
  334.     /**
  335.      * Determine if a product is on sale before membership price adjustments
  336.      *
  337.      * This method contains code from
  338.      * @see WC_Product::is_on_sale()
  339.      * and
  340.      * @see WC_Product_Variable::is_on_sale()
  341.      * It was introduced mainly to avoid infinite loops between
  342.      * @see \WC_Memberships_Member_Discounts::product_is_on_sale()
  343.      * and
  344.      * @see \WC_Memberships_Member_Discounts::is_product_excluded_from_member_discounts()
  345.      * so it runs very late from
  346.      * @see \WC_Memberships_Member_Discounts::product_is_on_sale()
  347.      * and does not include itself the product on sale filter that would cause
  348.      * the infinite loop, and also will remove all other filters on
  349.      * 'woocommerce_product_is_on_sale' if our own filter is running
  350.      *
  351.      * @since 1.7.0
  352.      * @param int|\WC_Product|\WC_Product_Variable $product Product object or id
  353.      * @return bool
  354.      */
  355.     public function product_is_on_sale_before_discount( $product ) {
  356.  
  357.         if ( is_numeric( $product ) ) {
  358.             $product = wc_get_product( $product );
  359.         }
  360.  
  361.         $on_sale = false;
  362.  
  363.         // sanity checks
  364.         if ( ! $product instanceof WC_Product ) {
  365.             return $on_sale;
  366.         } elseif ( ! $this->member_is_logged_in ) {
  367.             return $product->is_on_sale();
  368.         }
  369.  
  370.         $product_id = SV_WC_Plugin_Compatibility::product_get_id( $product );
  371.  
  372.         if ( isset( $this->product_is_on_sale_before_discount[ $product_id ] ) ) {
  373.  
  374.             $on_sale = $this->product_is_on_sale_before_discount[ $product_id ];
  375.  
  376.         } else {
  377.  
  378.             // disable Memberships member discount adjustments
  379.             do_action( 'wc_memberships_discounts_disable_price_adjustments' );
  380.  
  381.             /** @see WC_Product_Variable::is_on_sale() */
  382.             if ( $product->is_type( array( 'variable', 'variable-subscription' ) ) ) {
  383.  
  384.                 $prices  = $product->get_variation_prices();
  385.  
  386.                 if ( $prices['regular_price'] !== $prices['sale_price'] && $prices['sale_price'] === $prices['price'] ) {
  387.                     $on_sale = true;
  388.                 }
  389.  
  390.             /** @see WC_Product::is_on_sale() */
  391.             } else {
  392.  
  393.                 $on_sale = $product->get_sale_price() !== $product->get_regular_price() && $product->get_sale_price() === $product->get_price();
  394.             }
  395.  
  396.             // re-enable Memberships member discount adjustments
  397.             do_action( 'wc_memberships_discounts_enable_price_adjustments' );
  398.  
  399.             $this->product_is_on_sale_before_discount[ $product_id ] = $on_sale;
  400.         }
  401.  
  402.         // we need this to avoid conflicts with third party code that runs
  403.         // again 'woocommerce_product_is_on_sale' filter - since Memberships
  404.         // runs its own filter very late (priority 999) it's safe enough
  405.         // to make Memberships' filter final
  406.         if ( 'woocommerce_product_is_on_sale' === current_filter() ) {
  407.  
  408.             /** @see \WC_Memberships_Member_Discounts::__construct() */
  409.             /** @see \WC_Memberships_Member_Discounts::product_is_on_sale)() */
  410.             remove_all_filters( 'woocommerce_product_is_on_sale' );
  411.         }
  412.  
  413.         return $on_sale;
  414.     }
  415.  
  416.  
  417.     /**
  418.      * Display sale badge for discounted products
  419.      *
  420.      * @since 1.7.0
  421.      */
  422.     public function display_sale_badge_for_discounted_products() {
  423.         global $post;
  424.  
  425.         $product = wc_get_product( $post );
  426.  
  427.         // sanity check
  428.         if ( ! $product instanceof WC_Product ) {
  429.             return;
  430.         }
  431.  
  432.         /**
  433.          * Controls whether or not member prices should display sale prices as well
  434.          *
  435.          * @since 1.3.0
  436.          * @param bool $display_sale_price Defaults to false
  437.          */
  438.         $display_sale_price = (bool) apply_filters( 'wc_memberships_member_prices_display_sale_price', false );
  439.  
  440.         // determine whether the product is on sale in the first place
  441.         $on_sale_before_discount = $this->product_is_on_sale_before_discount( $product );
  442.         $member_has_discount     = $this->user_has_member_discount( $product );
  443.  
  444.         // show this badge if:
  445.         // - user has a member discount, product is on sale and we want to display the sale price badge
  446.         // - user does NOT have a member discount, but product is on sale before discount rules (compatibility)
  447.         if (    (   $member_has_discount && $on_sale_before_discount && $display_sale_price )
  448.              || ( ( ! $member_has_discount || $this->is_product_excluded_from_member_discounts( $product ) ) && $on_sale_before_discount ) ) {
  449.  
  450.             add_filter( 'woocommerce_product_is_on_sale',    array( $this, 'enable_sale_price' ) );
  451.  
  452.             wc_get_template( 'single-product/sale-flash.php' );
  453.  
  454.             remove_filter( 'woocommerce_product_is_on_sale', array( $this, 'enable_sale_price' ) );
  455.         }
  456.     }
  457.  
  458.  
  459.     /**
  460.      * Enable 'on sale' callback for a product that is on sale before discounts
  461.      *
  462.      * @internal
  463.      *
  464.      * @since 1.7.0
  465.      * @return true
  466.      */
  467.     public function enable_sale_price() {
  468.         return true;
  469.     }
  470.  
  471.  
  472.     /**
  473.      * Disable 'on sale' callback for a product that is on sale before discounts
  474.      *
  475.      * @internal
  476.      *
  477.      * @since 1.3.0
  478.      * @return true
  479.      */
  480.     public function disable_sale_price() {
  481.         return false;
  482.     }
  483.  
  484.  
  485.     /**
  486.      * Get product price before discount
  487.      *
  488.      * @since 1.7.0
  489.      * @param \WC_Product|\WC_Product_Variation $product Product
  490.      * @return float Price
  491.      */
  492.     private function get_price_before_discount( $product ) {
  493.  
  494.         // temporarily disable price adjustments
  495.         do_action( 'wc_memberships_discounts_disable_price_adjustments' );
  496.  
  497.         // get the base price without discounts
  498.         $price = $product->get_display_price();
  499.  
  500.         // re-enable price adjustments
  501.         do_action( 'wc_memberships_discounts_enable_price_adjustments' );
  502.  
  503.         return $price;
  504.     }
  505.  
  506.  
  507.     /**
  508.      * Apply purchasing discounts to product price
  509.      *
  510.      * @internal
  511.      *
  512.      * @since 1.0.0
  513.      * @param string|int|float $price Price to discount (normally a float, maybe a string number)
  514.      * @param \WC_Product $product The product object
  515.      * @return float Price
  516.      */
  517.     public function on_get_price( $price, $product ) {
  518.  
  519.         // bail out if any of the following is true:
  520.         // - member is not logged in
  521.         // - product is excluded from member discounts
  522.         // - user has no member discount over the product
  523.         if (    ! $this->member_is_logged_in
  524.              ||   $this->is_product_excluded_from_member_discounts( $product )
  525.              || ! $this->user_has_member_discount( $product ) ) {
  526.  
  527.             $price = $this->get_price_before_discount( $product );
  528.  
  529.         } else {
  530.  
  531.             // get the discounted price or return the default price if no discounts apply
  532.             $discounted_price = $this->get_discounted_price( $price, $product );
  533.  
  534.             $price = is_numeric( $discounted_price ) ? $discounted_price : $this->get_price_before_discount( $product );
  535.         }
  536.  
  537.         return (float) $price;
  538.     }
  539.  
  540.  
  541.     /**
  542.      * Get variable product price HTML before membership discounts
  543.      *
  544.      * @see \WC_Product_Variable::get_price_html()
  545.      *
  546.      * @since 1.7.1
  547.      * @param \WC_Product_Variable $product Variable product
  548.      * @param bool $display_sale_price Whether to display sale price
  549.      * @return string HTML
  550.      */
  551.     private function get_variable_product_price_html_before_discount( $product, $display_sale_price ) {
  552.  
  553.         $prices = $product->get_variation_prices( true );
  554.  
  555.         if ( empty( $prices['price'] ) || '' === $product->get_price() ) {
  556.  
  557.             $html_before_discount = apply_filters( 'woocommerce_variable_empty_price_html', '', $product );
  558.  
  559.         } else {
  560.  
  561.             $regular_min = $sale_min = $product->get_variation_regular_price( 'min', true );
  562.             $regular_max = $sale_max = $product->get_variation_regular_price( 'max', true );
  563.  
  564.             $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 );
  565.  
  566.             if ( $on_sale = $this->product_is_on_sale_before_discount( $product ) ) {
  567.  
  568.                 $sale_min = $product->get_variation_sale_price( 'min', true );
  569.                 $sale_max = $product->get_variation_sale_price( 'max', true );
  570.  
  571.                 $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 );
  572.  
  573.                 if ( $this->user_has_member_discount( $product ) ) {
  574.                     $html_before_discount = $display_sale_price ? apply_filters( 'woocommerce_variable_sale_price_html', $sale_price, $product ) : $regular_price;
  575.                 } else {
  576.                     $html_before_discount = $html = '<del>' . $regular_price . '</del> <ins>' . $sale_price . '</ins>';
  577.                     $html_before_discount = apply_filters( 'woocommerce_variable_sale_price_html', $html_before_discount, $product );
  578.                 }
  579.  
  580.             } else {
  581.  
  582.                 $html_before_discount = $regular_price;
  583.             }
  584.  
  585.             // sanity check for free products:
  586.             // if min and max variations are 0, this product is free
  587.             $min_price = (float) current( $prices['price'] );
  588.             $max_price = (float) end( $prices['price'] );
  589.  
  590.             if ( empty( $min_price ) && empty( $max_price ) ) {
  591.                 $html_before_discount = apply_filters( 'woocommerce_variable_free_price_html', _x( 'Free!', 'Free product', 'woocommerce-memberships' ), $product );
  592.             }
  593.         }
  594.  
  595.         return $html_before_discount;
  596.     }
  597.  
  598.  
  599.     /**
  600.      * Get void product price HTML before membership discounts
  601.      *
  602.      * @see \WC_Product::get_price_html()
  603.      *
  604.      * @since 1.7.1
  605.      * @param \WC_Product $product Product object
  606.      * @return string HTML
  607.      */
  608.     private function get_void_product_price_html_before_discount( $product ) {
  609.  
  610.         $price = $product->get_price();
  611.  
  612.         if ( empty( $price ) && '' !== $price ) {
  613.  
  614.             if ( $this->product_is_on_sale_before_discount( $product ) && $product->get_regular_price() ) {
  615.  
  616.                 $price_label = $product->get_price_html_from_to( $product->get_display_price( $product->get_regular_price() ), _x( 'Free!', 'Free product', 'woocommerce-memberships' ) );
  617.  
  618.                 $html_before_discount = apply_filters( 'woocommerce_free_sale_price_html', $price_label, $this );
  619.  
  620.             } else {
  621.  
  622.                 $html_before_discount = apply_filters( 'woocommerce_free_price_html', _x( 'Free!', 'Free product', 'woocommerce-memberships' ), $product );
  623.             }
  624.  
  625.         } else {
  626.  
  627.             $html_before_discount = apply_filters( 'woocommerce_empty_price_html', '', $product );
  628.         }
  629.  
  630.         return $html_before_discount;
  631.     }
  632.  
  633.  
  634.     /**
  635.      * Get simple or variation product price before membership discounts
  636.      *
  637.      * @see \WC_Product::get_price_html()
  638.      * @see \WC_Product_Variation::get_price_html()
  639.      *
  640.      * @since 1.7.1
  641.      * @param \WC_Product|\WC_Product_Variation $product A simple product or a variation
  642.      * @param bool $display_sale_price Whether to display sale prices
  643.      * @return string HTML
  644.      */
  645.     private function get_product_price_html_before_discount( $product, $display_sale_price ) {
  646.  
  647.         $on_sale      = $this->product_is_on_sale_before_discount( $product );
  648.         $is_variation = $product->is_type( 'variation' );
  649.  
  650.         if ( $is_variation && '' === $product->get_price() ) {
  651.  
  652.             $html_before_discount = apply_filters( 'woocommerce_variation_empty_price_html', '', $product );
  653.  
  654.         } else {
  655.  
  656.             if ( $this->user_has_member_discount( $product ) && ! $this->is_product_excluded_from_member_discounts( $product ) )  {
  657.  
  658.                 if ( $on_sale ) {
  659.                     $price_before_discount = $display_sale_price ? $product->get_display_price( $product->get_sale_price() ) : $product->get_display_price( $product->get_regular_price() );
  660.                     $html_before_discount  = wc_price( $price_before_discount );
  661.                 } else {
  662.                     $price_before_discount = $product->get_display_price( $product->get_regular_price() );
  663.                     $html_before_discount  = wc_price( $price_before_discount );
  664.                 }
  665.  
  666.             } else {
  667.  
  668.                 if ( $on_sale ) {
  669.                     $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() ) );
  670.                 } else {
  671.                     $price_before_discount = $product->get_display_price( $product->get_regular_price() );
  672.                     $html_before_discount  = wc_price( $price_before_discount );
  673.                 }
  674.             }
  675.  
  676.             // maybe apply wc filters before returning
  677.             if ( $on_sale ) {
  678.                 $sale_price_html_filter = $is_variation ? 'woocommerce_variation_sale_price_html' : 'woocommerce_sale_price_html';
  679.                 $html_before_discount   = apply_filters( $sale_price_html_filter, $html_before_discount, $product );
  680.             } elseif ( $is_variation && ! $product->get_price() > 0 ) {
  681.                 $html_before_discount = apply_filters( 'woocommerce_variation_free_price_html', _x( 'Free!', 'Free product', 'woocommerce-memberships' ), $product );
  682.             }
  683.         }
  684.  
  685.         return $html_before_discount;
  686.     }
  687.  
  688.  
  689.     /**
  690.      * Get product HTML price before discount
  691.      *
  692.      * @since 1.7.0
  693.      * @param \WC_Product|\WC_Product_Variable|\WC_Product_Variation $product Product
  694.      * @return string HTML
  695.      */
  696.     private function get_price_html_before_discount( $product ) {
  697.  
  698.         /**
  699.          * Controls whether or not member prices should display sale prices as well
  700.          *
  701.          * @since 1.3.0
  702.          * @param bool $display_sale_price Defaults to false
  703.          */
  704.         $display_sale_price = (bool) apply_filters( 'wc_memberships_member_prices_display_sale_price', false );
  705.  
  706.         // temporarily disable membership price adjustments
  707.         do_action( 'wc_memberships_discounts_disable_price_adjustments' );
  708.         do_action( 'wc_memberships_discounts_disable_price_html_adjustments' );
  709.  
  710.         if ( ! $product->get_price() && ! $product->is_type( array( 'variable', 'variation' ) ) ) {
  711.             // simple products with 0 or empty price
  712.             $html_before_discount = $this->get_void_product_price_html_before_discount( $product );
  713.         } elseif ( $product->is_type( 'variable' ) ) {
  714.             // variable products
  715.             $html_before_discount = $this->get_variable_product_price_html_before_discount( $product, $display_sale_price );
  716.         } else {
  717.             // simple products with non-void prices, product variations
  718.             $html_before_discount = $this->get_product_price_html_before_discount( $product, $display_sale_price );
  719.         }
  720.  
  721.         // re-enable membership price adjustments
  722.         do_action( 'wc_memberships_discounts_enable_price_adjustments' );
  723.         do_action( 'wc_memberships_discounts_enable_price_html_adjustments' );
  724.  
  725.         return $html_before_discount . $product->get_price_suffix( $this->get_price_before_discount( $product ) );
  726.     }
  727.  
  728.  
  729.     /**
  730.      * Adjust discounted product price HTML
  731.      *
  732.      * @internal
  733.      *
  734.      * @since 1.3.0
  735.      * @param string $html The price HTML maybe after discount
  736.      * @param \WC_Product|\WC_Product_Variable|\WC_Product_Variation $product The product object for which we may have discounts
  737.      * @return string The original price HTML if no discount or a new formatted string showing before/after discount
  738.      */
  739.     public function on_price_html( $html, $product ) {
  740.  
  741.         /**
  742.          * Controls whether or not member prices should use discount format when displayed
  743.          *
  744.          * @since 1.3.0
  745.          * @param bool $use_discount_format Defaults to true
  746.          */
  747.         $use_discount_format = (bool) apply_filters( 'wc_memberships_member_prices_use_discount_format', true );
  748.  
  749.         // bail out if any of the following conditions applies:
  750.         // - custom code set to not to use discount format
  751.         // - no member user is logged in
  752.         // - product is excluded from discount rules
  753.         // - current user has no discounts for the product
  754.         // - product has no applicable member discount
  755.         if (    ! $use_discount_format
  756.              || ! $this->member_is_logged_in
  757.              ||   $this->is_product_excluded_from_member_discounts( $product )
  758.              || ! $this->user_has_member_discount( $product ) ) {
  759.  
  760.             if ( $this->applying_discounts ) {
  761.                 $html = $this->get_price_html_before_discount( $product );
  762.             }
  763.  
  764.         } else {
  765.  
  766.             if ( $product->is_type( 'variable' ) ) {
  767.  
  768.                 $price_min = $product->get_variation_price( 'min', true );
  769.                 $price_min = $price_min > 0 ? $this->get_discounted_price( $price_min, $product ) : 0;
  770.                 $price_max = $product->get_variation_price( 'max', true );
  771.                 $price_max = $price_max > 0 ? $this->get_discounted_price( $price_max, $product ) : 0;
  772.  
  773.                 // this may be a case when one or more product variations
  774.                 // have discounts but the parent variable product has none
  775.                 if ( empty( $price_min ) && empty( $price_max ) && ( $variations = $product->get_children() ) ) {
  776.  
  777.                     $variation_prices = array();
  778.  
  779.                     foreach ( $variations as $variation_id ) {
  780.                         $variation_prices[] = wc_get_product( $variation_id )->get_display_price();
  781.                     }
  782.  
  783.                     if ( ! empty( $variation_prices ) ) {
  784.                         $price_min = min( $variation_prices );
  785.                         $price_max = max( $variation_prices );
  786.                     }
  787.                 }
  788.  
  789.                 if ( $price_min !== $price_max ) {
  790.                     $html_after_discount = sprintf( _x( '%1$s&ndash;%2$s', 'Price range: from-to', 'woocommerce-memberships' ), wc_price( $price_min ), wc_price( $price_max ) );
  791.                 } elseif ( empty( $price_min ) && empty( $price_max ) ) {
  792.                     $html_after_discount = $this->get_void_product_price_html_before_discount( $product );
  793.                 } else {
  794.                     $html_after_discount = wc_price( $price_min );
  795.                 }
  796.  
  797.                 $html_after_discount .= $product->get_price_suffix( $product->get_price() );
  798.  
  799.             } else {
  800.  
  801.                 $html_after_discount = $html;
  802.             }
  803.  
  804.             $html_before_discount = $this->get_price_html_before_discount( $product );
  805.  
  806.             // string prices do not match, we have a discount
  807.             if ( $html_after_discount !== $html_before_discount ) {
  808.  
  809.                 $html = '<del>' . $html_before_discount . '</del> <ins>' . $html_after_discount . '</ins>';
  810.  
  811.                 if ( ! $product->is_type( 'variable' ) ) {
  812.  
  813.                     $price_before_discount = $this->get_price_before_discount( $product );
  814.                     $price_after_discount  = $product->get_price();
  815.  
  816.                     if ( empty( $price_before_discount ) && empty( $price_after_discount ) ) {
  817.  
  818.                         $html = $html_after_discount;
  819.                     }
  820.                 }
  821.             }
  822.  
  823.             // add a "Member Discount" badge for single variation prices
  824.             if ( $product->is_type( 'variation' ) ) {
  825.  
  826.                 $html .= ' ' . $this->get_member_discount_badge( $product, true );
  827.             }
  828.         }
  829.  
  830.         /**
  831.          * Filter the HTML price after member discounts may have been applied
  832.          *
  833.          * @since 1.7.1
  834.          * @param string $price_html The price HTML
  835.          * @param \WC_Product $product The product the price is meant for
  836.          */
  837.         return apply_filters( 'wc_memberships_get_price_html', $html, $product );
  838.     }
  839.  
  840.  
  841.     /**
  842.      * Adjust variation price
  843.      *
  844.      * @internal
  845.      *
  846.      * @since 1.3.0
  847.      * @param float $price The product price, maybe discounted
  848.      * @param \WC_Product|\WC_Product_Variation $product The product object
  849.      * @param string $min_or_max Min-max prices of variations
  850.      * @param bool $display If to be displayed
  851.      * @return float
  852.      */
  853.     public function on_get_variation_price( $price, $product, $min_or_max, $display ) {
  854.  
  855.         // bail out if any of the following applies:
  856.         // - no user member is logged in
  857.         // - the product is excluded from receiving member discounts
  858.         // - logged in user has no member discount over the product
  859.         if (    ! $this->member_is_logged_in
  860.              || ! $product instanceof WC_Product
  861.              ||   $this->is_product_excluded_from_member_discounts( $product )
  862.              || ! $this->user_has_member_discount( $product ) ) {
  863.  
  864.             $price_float = (float) $price;
  865.  
  866.             return $this->applying_discounts && empty( $price_float ) ? $this->get_price_before_discount( $product ) : $price;
  867.         }
  868.  
  869.         $product_id = SV_WC_Plugin_Compatibility::product_get_id( $product );
  870.  
  871.         // use memoized entry if available to speed up return
  872.         if ( isset( $this->product_discount_variation[ $product_id ][ $min_or_max ] ) ) {
  873.  
  874.             $price = $this->product_discount_variation[ $product_id ][ $min_or_max ];
  875.  
  876.         } else {
  877.  
  878.             // defaults
  879.             $calc_price = $price;
  880.             $min_price  = $price;
  881.             $max_price  = $price;
  882.  
  883.             // get variation ids
  884.             $children = $product->get_children();
  885.  
  886.             if ( ! empty( $children ) ) {
  887.  
  888.                 foreach ( $children as $variation_id ) {
  889.  
  890.                     if ( $display ) {
  891.  
  892.                         if ( $variation = $product->get_child( $variation_id ) ) {
  893.  
  894.                             // make sure we start from the normal un-discounted price
  895.                             do_action( 'wc_memberships_discounts_disable_price_adjustments' );
  896.  
  897.                             // in display mode, we need to account for taxes
  898.                             $base_price = $variation->get_price() > 0 ? $variation->get_display_price() : 0;
  899.                             $calc_price = $base_price;
  900.  
  901.                             // try getting the discounted price for the variation
  902.                             $discounted_price = $this->get_discounted_price( $base_price, $variation->id );
  903.  
  904.                             // if there's a difference, grab discounted price
  905.                             if ( is_numeric( $discounted_price ) && $base_price !== $discounted_price ) {
  906.                                 $calc_price = $discounted_price;
  907.                             }
  908.  
  909.                             // re-enable discounts in pricing flow
  910.                             do_action( 'wc_memberships_discounts_enable_price_adjustments' );
  911.                         }
  912.  
  913.                     } else {
  914.  
  915.                         $calc_price = (float) get_post_meta( $variation_id, '_price', true );
  916.                     }
  917.  
  918.                     if ( $min_price === null || $calc_price < $min_price ) {
  919.                         $min_price = $calc_price;
  920.                     }
  921.  
  922.                     if ( $max_price === null || $calc_price > $max_price ) {
  923.                         $max_price = $calc_price;
  924.                     }
  925.                 }
  926.             }
  927.  
  928.             if ( $min_or_max === 'min' ) {
  929.                 $price = (float) $min_price;
  930.             } elseif ( $min_or_max === 'max' ) {
  931.                 $price = (float) $max_price;
  932.             }
  933.  
  934.             $this->product_discount_variation[ $product_id ][ $min_or_max ] = $price;
  935.         }
  936.  
  937.         return (float) $price;
  938.     }
  939.  
  940.  
  941.     /**
  942.      * Adjust discounted cart item price HTML
  943.      *
  944.      * @internal
  945.      *
  946.      * @since 1.3.0
  947.      * @param string $html Price HTML
  948.      * @param array $cart_item The cart item data
  949.      * @return string
  950.      */
  951.     public function on_cart_item_price( $html, $cart_item ) {
  952.  
  953.         // get the product
  954.         $product = ! empty( $cart_item['data'] ) ? $cart_item['data'] : false;
  955.  
  956.         // bail out if any of the following applies:
  957.         // - no user member is logged in
  958.         // - no product (sanity check)
  959.         // - the product is excluded from receiving member discounts
  960.         // - logged in user has no member discount over the product
  961.         if (    ! $this->member_is_logged_in
  962.              || ! $product instanceof WC_Product
  963.              ||   $this->is_product_excluded_from_member_discounts( $product )
  964.              || ! $this->user_has_member_discount( $product ) ) {
  965.  
  966.             return $html;
  967.         }
  968.  
  969.         // temporarily disable our price adjustments
  970.         do_action( 'wc_memberships_discounts_disable_price_adjustments' );
  971.  
  972.         // so we can get the base price without member discounts
  973.         // also, in cart we need to account for tax display
  974.         $price = $product->get_display_price();
  975.  
  976.         // re-enable disable our price adjustments
  977.         do_action( 'wc_memberships_discounts_enable_price_adjustments' );
  978.  
  979.         if ( $this->has_discounted_price( $price, $product ) ) {
  980.  
  981.             // in cart, we need to account for tax display
  982.             $discounted_price = $product->get_display_price();
  983.  
  984.             /** This filter is documented in class-wc-memberships-member-discounts.php **/
  985.             $use_discount_format = apply_filters( 'wc_memberships_use_discount_format', true );
  986.  
  987.             // output html price before/after discount
  988.             if ( $use_discount_format && $discounted_price < $price ) {
  989.                 $html = '<del>' . wc_price( $price ) . '</del> <ins>' . wc_price( $discounted_price ) . '</ins>';
  990.             }
  991.         }
  992.  
  993.         return $html;
  994.     }
  995.  
  996.  
  997.     /**
  998.      * Add the current user ID to the variation prices hash for caching.
  999.      *
  1000.      * @internal
  1001.      *
  1002.      * @since 1.3.2
  1003.      * @param array $data The existing hash data
  1004.      * @param \WC_Product $product The current product variation
  1005.      * @return array $data The hash data with a user ID added if applicable
  1006.      */
  1007.     public function set_user_variation_prices_hash( $data, $product ) {
  1008.  
  1009.         // bail out if member is not logged in
  1010.         // or logged in user has no membership discount over the product
  1011.         if (      $this->member_is_logged_in
  1012.              && ! $this->is_product_excluded_from_member_discounts( $product )
  1013.              &&   $this->user_has_member_discount( $product ) ) {
  1014.  
  1015.             $data[] = get_current_user_id();
  1016.         }
  1017.  
  1018.         return $data;
  1019.     }
  1020.  
  1021.  
  1022.     /**
  1023.      * Get member discount badge
  1024.      *
  1025.      * @since 1.6.4
  1026.      * @param \WC_Product $product The product object to output a badge for (passed to filter)
  1027.      * @param bool $variation Whether to output a discount badge specific for a product variation (default false)
  1028.      * @return string
  1029.      */
  1030.     public function get_member_discount_badge( $product, $variation = false ) {
  1031.        
  1032.           $label = __( 'Member discount!', 'woocommerce-memberships' );
  1033.         // we have a slight different output for badge classes and filter
  1034.         if ( true !== $variation ) {
  1035.             global $post;
  1036.  
  1037.             // used in filter for backwards compatibility reasons
  1038.             $the_post = $post;
  1039.  
  1040.             if ( ! $the_post instanceof WP_Post ) {
  1041.                 $the_post = $product->post;
  1042.             }
  1043.  
  1044.             $badge = '<span class="onsale wc-memberships-member-discount">' . esc_html( $label ) . '</span>';
  1045.  
  1046.             /**
  1047.              * Filter the member discount badge
  1048.              *
  1049.              * @since 1.0.0
  1050.              * @param string $badge The badge HTML
  1051.              * @param \WP_Post $post The product post object
  1052.              * @param \WC_Product_Variation $variation The product variation
  1053.              */
  1054.             $badge = apply_filters( 'wc_memberships_member_discount_badge', $badge, $the_post, $product );
  1055.  
  1056.         } else {
  1057.  
  1058.             $badge = '<span class="wc-memberships-variation-member-discount">' . esc_html( $label ) . '</span>';
  1059.  
  1060.             /**
  1061.              * Filter the variation member discount badge
  1062.              *
  1063.              * @since 1.3.2
  1064.              * @param string $badge The badge HTML
  1065.              * @param \WC_Product|\WC_Product_Variation $variation The product variation
  1066.              */
  1067.             $badge = apply_filters( 'wc_memberships_variation_member_discount_badge', $badge, $product );
  1068.  
  1069.         }
  1070.  
  1071.         return $badge;
  1072.     }
  1073.  
  1074.  
  1075.     /**
  1076.      * Filter the member discount badge for products excluded
  1077.      * from member discount rules
  1078.      *
  1079.      * @internal
  1080.      *
  1081.      * @since 1.7.0
  1082.      * @param string $badge Badge HTML
  1083.      * @param \WP_Post $post The post object
  1084.      * @param \WC_Product $product The product object
  1085.      * @return string Empty string if product is excluded from member discounts
  1086.      */
  1087.     public function disable_discount_badge_for_excluded_products( $badge, $post, $product ) {
  1088.         return $this->is_product_excluded_from_member_discounts( $product ) ? '' : $badge;
  1089.     }
  1090.  
  1091.  
  1092.     /**
  1093.      * Get product discounted price for member
  1094.      *
  1095.      * @since 1.3.0
  1096.      * @param float $base_price Original price
  1097.      * @param int|\WC_Product $product Product ID or product object
  1098.      * @param int|null $member_id Optional, defaults to current user id
  1099.      * @return float|null The discounted price or null if no discount applies
  1100.      */
  1101.     public function get_discounted_price( $base_price, $product, $member_id = null ) {
  1102.  
  1103.         if ( ! $member_id ) {
  1104.             $member_id = get_current_user_id();
  1105.         }
  1106.  
  1107.         if ( ! is_object( $product ) ) {
  1108.             $product = wc_get_product( $product );
  1109.         }
  1110.  
  1111.         $price          = null;
  1112.         $product_id     = null;
  1113.         $discount_rules = array();
  1114.  
  1115.         // we need a product and a user to get a member discounted price
  1116.         if ( $product instanceof WC_Product && $member_id > 0 ) {
  1117.  
  1118.             $product_id     = SV_WC_Plugin_Compatibility::product_get_id( $product );
  1119.             $discount_rules = wc_memberships()->get_rules_instance()->get_user_product_purchasing_discount_rules( $member_id, $product_id );
  1120.         }
  1121.  
  1122.         if ( $product_id && ! empty( $discount_rules ) ) {
  1123.  
  1124.             if ( isset( $this->product_discount[ $member_id ][ $product_id ] ) ) {
  1125.  
  1126.                 $price = $this->product_discount[ $member_id ][ $product_id ];
  1127.  
  1128.             } else {
  1129.  
  1130.                 /**
  1131.                  * Filter whether to allow stacking product discounts
  1132.                  * for members of multiple plans with overlapping discount rules
  1133.                  * for the same products
  1134.                  *
  1135.                  * @since 1.7.0
  1136.                  *
  1137.                  * @param bool $allow_cumulative_discounts Default true (allow)
  1138.                  * @param int $member_id The user id discounts are calculated for
  1139.                  * @param \WC_Product $product The product object being discounted
  1140.                  */
  1141.                 $allow_cumulative_discounts = apply_filters( 'wc_memberships_allow_cumulative_member_discounts', true, $member_id, $product );
  1142.  
  1143.                 $price  = (float) $base_price;
  1144.                 $prices = array();
  1145.  
  1146.                 // find out the discounted price for the current user
  1147.                 foreach ( $discount_rules as $rule ) {
  1148.  
  1149.                     switch ( $rule->get_discount_type() ) {
  1150.  
  1151.                         case 'percentage':
  1152.                             $discounted_price = $price * ( 100 - $rule->get_discount_amount() ) / 100;
  1153.                         break;
  1154.  
  1155.                         case 'amount':
  1156.                             $discounted_price = $price - $rule->get_discount_amount();
  1157.                         break;
  1158.                     }
  1159.  
  1160.                     // make sure that the lowest price gets applied and doesn't become negative
  1161.                     if ( isset( $discounted_price ) && $discounted_price < $price ) {
  1162.  
  1163.                         if ( true === $allow_cumulative_discounts ) {
  1164.                             $price = max( $discounted_price, 0 );
  1165.                         } else {
  1166.                             $prices[] = max( $discounted_price, 0 );
  1167.                         }
  1168.                     }
  1169.                 }
  1170.  
  1171.                 // pick the lowest price
  1172.                 if ( ! empty( $prices ) ) {
  1173.                     $price = min( $prices );
  1174.                 }
  1175.  
  1176.                 // sanity check
  1177.                 if ( $price >= $base_price ) {
  1178.                     $price = null;
  1179.                 }
  1180.  
  1181.                 $this->product_discount[ $product_id ] = $price;
  1182.             }
  1183.         }
  1184.  
  1185.         /**
  1186.          * Filter discounted price of a membership product
  1187.          *
  1188.          * @since 1.7.1
  1189.          * @param null|float $price The discounted price or null if no discount applies
  1190.          * @param float $base_price The original price (not discounted by Memberships)
  1191.          * @param int $product_id The id of the product (or variation) the price is for
  1192.          * @param int $member_id THe id of the logged in member (it's zero for non logged in users)
  1193.          */
  1194.         return apply_filters( 'wc_memberships_get_discounted_price', $price, $base_price, $product_id, $member_id );
  1195.     }
  1196.  
  1197.  
  1198.     /**
  1199.      * Check if the product is discounted for the user
  1200.      *
  1201.      * @since 1.3.0
  1202.      * @param float $base_price Original price
  1203.      * @param int|\WC_Product $product Product ID or object
  1204.      * @param null|int $user_id Optional, defaults to current user id
  1205.      * @return bool
  1206.      */
  1207.     public function has_discounted_price( $base_price, $product, $user_id = null ) {
  1208.  
  1209.         if ( ! is_object( $product ) ) {
  1210.             $product = wc_get_product( $product );
  1211.         }
  1212.  
  1213.         $has_discounted_price = is_numeric( $this->get_discounted_price( $base_price, $product, $user_id ) );
  1214.  
  1215.         if ( ! $has_discounted_price && $product->is_type( 'variable' ) && ( $variations = $product->get_children() ) ) {
  1216.  
  1217.             $variations_discounts = array();
  1218.  
  1219.             foreach ( $variations as $variation_id ) {
  1220.  
  1221.                 $variations_discounts[] = $this->has_discounted_price( $base_price, $variation_id, $user_id );
  1222.             }
  1223.  
  1224.             $has_discounted_price = in_array( true, $variations_discounts, true );
  1225.         }
  1226.  
  1227.         return $has_discounted_price;
  1228.     }
  1229.  
  1230.  
  1231.     /**
  1232.      * Refresh cart fragments upon member login
  1233.      *
  1234.      * This is useful if a non-logged in member added items to cart
  1235.      * which should have otherwise membership discounts applied
  1236.      *
  1237.      * @internal
  1238.      *
  1239.      * @see \WC_Cart::reset()
  1240.      *
  1241.      * @since 1.6.4
  1242.      * @param string $user_login User login name
  1243.      * @param \WP_User $user User that just logged in
  1244.      */
  1245.     public function refresh_cart_upon_member_login( $user_login, $user ) {
  1246.  
  1247.         // small "hack" to trigger a refresh in cart contents
  1248.         // that will set any membership discounts to products that apply
  1249.         if ( $user_login && wc_memberships_is_user_active_member( $user, null, false ) ) {
  1250.  
  1251.             $this->reset_cart_session_data();
  1252.         }
  1253.     }
  1254.  
  1255.  
  1256.     /**
  1257.      * Reset cart session data
  1258.      *
  1259.      * @see \WC_Cart::reset() private method
  1260.      *
  1261.      * @since 1.6.4
  1262.      */
  1263.     private function reset_cart_session_data() {
  1264.  
  1265.         $wc = WC();
  1266.  
  1267.         // some very zealous sanity checks here
  1268.         if ( $wc && isset( $wc->cart->cart_session_data ) ) {
  1269.  
  1270.             $session_data = $wc->cart->cart_session_data;
  1271.  
  1272.             if ( ! empty( $session_data ) ) {
  1273.  
  1274.                 foreach ( $session_data as $key => $default ) {
  1275.  
  1276.                     if ( isset( $wc->session->$key ) ) {
  1277.                         unset( $wc->session->$key );
  1278.                     }
  1279.                 }
  1280.             }
  1281.  
  1282.             // WooCommerce core filter
  1283.             do_action( 'woocommerce_cart_reset', $wc->cart, true );
  1284.         }
  1285.     }
  1286.  
  1287.  
  1288.     /**
  1289.      * Enable price adjustments
  1290.      *
  1291.      * Calling this method will **enable** Membership adjustments
  1292.      * for product prices that have member discounts for logged in members
  1293.      *
  1294.      * @see \WC_Memberships_Member_Discounts::__construct() docblock for additional notes
  1295.      * @see \WC_Memberships_Member_Discounts::enable_price_html_adjustments() which you'll probably want to use too
  1296.      *
  1297.      * @since 1.3.0
  1298.      */
  1299.     public function enable_price_adjustments() {
  1300.  
  1301.         // apply membership discount to product price
  1302.         add_filter( 'woocommerce_get_price',                 array( $this, 'on_get_price' ), 999, 2 );
  1303.         add_filter( 'woocommerce_variation_prices_price',    array( $this, 'on_get_price' ), 999, 2 );
  1304.         add_filter( 'woocommerce_get_variation_price',       array( $this, 'on_get_variation_price' ), 999, 4 );
  1305.         add_filter( 'woocommerce_get_variation_prices_hash', array( $this, 'set_user_variation_prices_hash' ), 999, 2 );
  1306.     }
  1307.  
  1308.  
  1309.     /**
  1310.      * Disable price adjustments
  1311.      *
  1312.      * Calling this method will **disable** Membership adjustments
  1313.      * for product prices that have member discounts for logged in members
  1314.      *
  1315.      * @see \WC_Memberships_Member_Discounts::__construct() docblock for additional notes
  1316.      * @see \WC_Memberships_Member_Discounts::disable_price_html_adjustments() which you'll probably want to use too
  1317.      *
  1318.      * @since 1.3.0
  1319.      */
  1320.     public function disable_price_adjustments() {
  1321.  
  1322.         // restore price to original amount before membership discount
  1323.         remove_filter( 'woocommerce_get_price',                 array( $this, 'on_get_price' ), 999 );
  1324.         remove_filter( 'woocommerce_variation_prices_price',    array( $this, 'on_get_price' ), 999 );
  1325.         remove_filter( 'woocommerce_get_variation_price',       array( $this, 'on_get_variation_price' ), 999 );
  1326.         remove_filter( 'woocommerce_get_variation_prices_hash', array( $this, 'set_user_variation_prices_hash' ), 999 );
  1327.     }
  1328.  
  1329.  
  1330.     /**
  1331.      * Enable price HTML adjustments
  1332.      *
  1333.      * @see \WC_Memberships_Member_Discounts::__construct() docblock for additional notes
  1334.      * @see \WC_Memberships_Member_Discounts::enable_price_adjustments() which you'll probably want to use too
  1335.      *
  1336.      * @since 1.3.0
  1337.      */
  1338.     public function enable_price_html_adjustments() {
  1339.  
  1340.         // filter the prices to apply member discounts
  1341.         add_filter( 'woocommerce_variation_price_html', array( $this, 'on_price_html' ), 999, 2 );
  1342.         add_filter( 'woocommerce_get_price_html',       array( $this, 'on_price_html' ), 999, 2 );
  1343.     }
  1344.  
  1345.  
  1346.     /**
  1347.      * Disable price HTML adjustments
  1348.      *
  1349.      * @see \WC_Memberships_Member_Discounts::__construct() docblock for additional notes
  1350.      * @see \WC_Memberships_Member_Discounts::disable_price_adjustments() which you'll probably want to use too
  1351.      *
  1352.      * @since 1.3.0
  1353.      */
  1354.     public function disable_price_html_adjustments() {
  1355.  
  1356.         // so we can display prices before discount
  1357.         remove_filter( 'woocommerce_get_price_html',       array( $this, 'on_price_html' ), 999 );
  1358.         remove_filter( 'woocommerce_variation_price_html', array( $this, 'on_price_html' ), 999 );
  1359.     }
  1360.  
  1361.  
  1362. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement