Files
woocommerce-subscriptions/includes/core/class-wc-subscriptions-cart.php
2025-07-10 10:19:11 +00:00

2542 lines
101 KiB
PHP

<?php
/**
* Subscriptions Cart Class
*
* Mirrors a few functions in the WC_Cart class to work for subscriptions.
*
* @package WooCommerce Subscriptions
* @subpackage WC_Subscriptions_Cart
* @category Class
* @author Brent Shepherd
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0
*/
class WC_Subscriptions_Cart {
/**
* A flag to control how to modify the calculation of totals by WC_Cart::calculate_totals()
*
* Can take any one of these values:
* - 'none' used to calculate the initial total.
* - 'combined_total' used to calculate the total of sign-up fee + recurring amount.
* - 'sign_up_fee_total' used to calculate the initial amount when there is a free trial period and a sign-up fee. Different to 'combined_total' because shipping is not charged on a sign-up fee.
* - 'recurring_total' used to calculate the totals for the recurring amount when the recurring amount differs to to 'combined_total' because of coupons or sign-up fees.
* - 'free_trial_total' used to calculate the initial total when there is a free trial period and no sign-up fee. Different to 'combined_total' because shipping is not charged up-front when there is a free trial.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
private static $calculation_type = 'none';
/**
* An internal pointer to the current recurring cart calculation (if any)
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.12
*/
private static $recurring_cart_key = 'none';
/**
* A cache of the calculated recurring shipping packages
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.13
*/
private static $recurring_shipping_packages = array();
/**
* A cache of the current recurring cart being calculated
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.20
*/
private static $cached_recurring_cart = null;
/**
* A stack of recurring cart keys being calculated.
*
* Before calculating a cart's totals, we set the recurring cart key and calculation type to match that cart's key and type. @see self::set_recurring_cart_key_before_calculate_totals()
* After a cart's totals have been calculated, we restore the recurring cart key and calculation type. @see self::update_recurring_cart_key_after_calculate_totals()
*
* @var array
*/
private static $recurring_totals_calculation_stack = [];
/**
* Bootstraps the class and hooks required actions & filters.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0
*/
public static function init() {
// Make sure WC calculates total on sign up fee + price per period, and keep a record of the price per period
add_action( 'woocommerce_before_calculate_totals', __CLASS__ . '::add_calculation_price_filter', 10 );
add_action( 'woocommerce_calculate_totals', __CLASS__ . '::remove_calculation_price_filter', 10 );
add_action( 'woocommerce_after_calculate_totals', __CLASS__ . '::remove_calculation_price_filter', 10 );
add_filter( 'woocommerce_calculated_total', __CLASS__ . '::calculate_subscription_totals', 1000, 2 );
// Remove any subscriptions with a free trial from the initial shipping packages
add_filter( 'woocommerce_cart_shipping_packages', __CLASS__ . '::set_cart_shipping_packages', -10, 1 );
// Subscriptions with a free trial need extra handling to support the COD gateway
add_filter( 'woocommerce_available_payment_gateways', __CLASS__ . '::check_cod_gateway_for_free_trials' );
// Display Formatted Totals
add_filter( 'woocommerce_cart_product_subtotal', __CLASS__ . '::get_formatted_product_subtotal', 11, 4 );
// Sometimes, even if the order total is $0, the cart still needs payment
add_filter( 'woocommerce_cart_needs_payment', __CLASS__ . '::cart_needs_payment', 10, 2 );
// Make sure cart product prices correctly include/exclude taxes
add_filter( 'woocommerce_cart_product_price', __CLASS__ . '::cart_product_price', 10, 2 );
// Display grouped recurring amounts after order totals on the cart/checkout pages
add_action( 'woocommerce_cart_totals_after_order_total', __CLASS__ . '::display_recurring_totals' );
add_action( 'woocommerce_review_order_after_order_total', __CLASS__ . '::display_recurring_totals' );
add_filter( 'woocommerce_cart_needs_shipping', __CLASS__ . '::cart_needs_shipping', 11, 1 );
add_filter( 'woocommerce_cart_needs_shipping_address', __CLASS__ . '::cart_needs_shipping_address', 11, 1 );
// Remove recurring shipping methods stored in the session whenever a subscription product is removed from the cart
add_action( 'woocommerce_remove_cart_item', array( __CLASS__, 'maybe_reset_chosen_shipping_methods' ) );
wcs_add_woocommerce_dependent_action( 'woocommerce_before_cart_item_quantity_zero', array( __CLASS__, 'maybe_reset_chosen_shipping_methods' ), '3.7.0', '<' );
// Make sure we use our recurring shipping method for recurring shipping calculations not the default method
add_filter( 'woocommerce_shipping_chosen_method', array( __CLASS__, 'set_chosen_shipping_method' ), 10, 2 );
// When WooCommerce calculates rates for a recurring shipping package, make sure there is a different set of rates
add_filter( 'woocommerce_shipping_package_name', __CLASS__ . '::change_initial_shipping_package_name', 1, 3 );
// When WooCommerce determines the taxable address only return pick up shipping methods chosen for the recurring cart being calculated.
add_filter( 'woocommerce_local_pickup_methods', __CLASS__ . '::filter_recurring_cart_chosen_shipping_method', 100, 1 );
add_filter( 'wc_shipping_local_pickup_plus_chosen_shipping_methods', __CLASS__ . '::filter_recurring_cart_chosen_shipping_method', 10, 1 );
// Validate chosen recurring shipping methods
add_action( 'woocommerce_after_checkout_validation', __CLASS__ . '::validate_recurring_shipping_methods' );
add_filter( 'woocommerce_add_to_cart_handler', __CLASS__ . '::add_to_cart_handler', 10, 2 );
add_action( 'woocommerce_cart_calculate_fees', __CLASS__ . '::apply_recurring_fees', 1000, 1 );
add_action( 'woocommerce_checkout_update_order_review', __CLASS__ . '::update_chosen_shipping_methods' );
add_action( 'plugins_loaded', array( __CLASS__, 'attach_dependant_hooks' ) );
add_action( 'woocommerce_after_calculate_totals', array( __CLASS__, 'record_base_tax_rates' ) );
// Add Subscriptions data to cart items.
add_filter( 'woocommerce_get_item_data', __CLASS__ . '::woocommerce_get_item_data', 10, 2 );
// Redirect the user immediately to the checkout page after clicking "Sign Up Now" buttons to encourage immediate checkout
add_filter( 'woocommerce_add_to_cart_redirect', array( __CLASS__, 'add_to_cart_redirect' ) );
// Set the recurring cart being calculated.
add_action( 'woocommerce_before_calculate_totals', [ __CLASS__, 'set_recurring_cart_key_before_calculate_totals' ], 1 );
add_action( 'woocommerce_after_calculate_totals', [ __CLASS__, 'update_recurring_cart_key_after_calculate_totals' ], 1 );
}
/**
* Attach dependant callbacks.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.6
*/
public static function attach_dependant_hooks() {
// WooCommerce determines if free shipping is available using the WC->cart total and coupons, we need to recalculate its availability when obtaining shipping methods for a recurring cart
if ( wcs_is_woocommerce_pre( '3.2' ) ) {
add_filter( 'woocommerce_shipping_free_shipping_is_available', array( __CLASS__, 'maybe_recalculate_shipping_method_availability' ), 10, 2 );
} else {
add_filter( 'woocommerce_shipping_free_shipping_is_available', array( __CLASS__, 'recalculate_shipping_method_availability' ), 10, 3 );
}
}
/**
* Attaches the "set_subscription_prices_for_calculation" filter to the WC Product's woocommerce_get_price hook.
*
* This function is hooked to "woocommerce_before_calculate_totals" so that WC will calculate a subscription
* product's total based on the total of it's price per period and sign up fee (if any).
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function add_calculation_price_filter() {
WC()->cart->recurring_carts = array();
// Only hook when cart contains a subscription
if ( ! self::cart_contains_subscription() ) {
return;
}
// Set which price should be used for calculation
add_filter( 'woocommerce_product_get_price', __CLASS__ . '::set_subscription_prices_for_calculation', 100, 2 );
add_filter( 'woocommerce_product_variation_get_price', __CLASS__ . '::set_subscription_prices_for_calculation', 100, 2 );
}
/**
* Removes the "set_subscription_prices_for_calculation" filter from the WC Product's woocommerce_get_price hook once
* calculations are complete.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function remove_calculation_price_filter() {
remove_filter( 'woocommerce_product_get_price', __CLASS__ . '::set_subscription_prices_for_calculation', 100 );
remove_filter( 'woocommerce_product_variation_get_price', __CLASS__ . '::set_subscription_prices_for_calculation', 100 );
}
/**
* Use WC core add-to-cart handlers for subscription products.
*
* @param string $handler The name of the handler to use when adding product to the cart
* @param WC_Product $product
*/
public static function add_to_cart_handler( $handler, $product ) {
if ( WC_Subscriptions_Product::is_subscription( $product ) ) {
switch ( $handler ) {
case 'variable-subscription':
$handler = 'variable';
break;
case 'subscription':
$handler = 'simple';
break;
}
}
return $handler;
}
/**
* If we are running a custom calculation, we need to set the price returned by a product
* to be the appropriate value. This may include just the sign-up fee, a combination of the
* sign-up fee and recurring amount or just the recurring amount (default).
*
* If there are subscriptions in the cart and the product is not a subscription, then
* set the recurring total to 0.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function set_subscription_prices_for_calculation( $price, $product ) {
if ( WC_Subscriptions_Product::is_subscription( $product ) ) {
// For original calculations, we need the items price to account for sign-up fees and/or free trial
if ( 'none' == self::$calculation_type ) {
$sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $product );
// Extra check to make sure that the sign up fee is numeric before using it
$sign_up_fee = is_numeric( $sign_up_fee ) ? (float) $sign_up_fee : 0;
$trial_length = WC_Subscriptions_Product::get_trial_length( $product );
if ( $trial_length > 0 ) {
$price = $sign_up_fee;
} else {
$price = (float) $price + $sign_up_fee; // Casting to float for php8 compatibility.
}
} // else $price = recurring amount already as WC_Product->get_price() returns subscription price
$price = apply_filters( 'woocommerce_subscriptions_cart_get_price', $price, $product );
// Make sure the recurring amount for any non-subscription products in the cart with a subscription is $0
} elseif ( 'recurring_total' == self::$calculation_type ) {
$price = 0;
}
return $price;
}
/**
* Sets the recurring cart key and calculation type before calculating a carts totals.
*
* @param WC_Cart $cart The cart object being calculated.
*/
public static function set_recurring_cart_key_before_calculate_totals( $cart ) {
$recurring_cart_key = ! empty( $cart->recurring_cart_key ) ? $cart->recurring_cart_key : 'none';
// Store the recurring cart key in the stack.
array_unshift( self::$recurring_totals_calculation_stack, $recurring_cart_key );
// Set the current recurring cart key and calculation type.
self::set_recurring_cart_key( $recurring_cart_key );
self::set_calculation_type( 'none' === $recurring_cart_key ? 'none' : 'recurring_total' );
}
/**
* Updates the recurring cart key and calculation type after calculating a carts totals.
*
* @param WC_Cart $cart The cart object that finished calculating it's totals.
*/
public static function update_recurring_cart_key_after_calculate_totals( $cart ) {
// Remove the recurring cart key from the stack. It has finished calculating.
array_shift( self::$recurring_totals_calculation_stack );
$recurring_cart_key = empty( self::$recurring_totals_calculation_stack ) ? 'none' : reset( self::$recurring_totals_calculation_stack );
self::set_recurring_cart_key( $recurring_cart_key );
self::set_calculation_type( 'none' === $recurring_cart_key ? 'none' : 'recurring_total' );
}
/**
* Calculate the initial and recurring totals for all subscription products in the cart.
*
* We need to group subscriptions by billing schedule to make the display and creation of recurring totals sane,
* when there are multiple subscriptions in the cart. To do that, we use an array with keys of the form:
* '{billing_interval}_{billing_period}_{trial_interval}_{trial_period}_{length}_{billing_period}'. This key
* is used to reference WC_Cart objects for each recurring billing schedule and these are stored in the master
* cart with the billing schedule key.
*
* After we have calculated and grouped all recurring totals, we need to checks the structure of the subscription
* product prices to see whether they include sign-up fees and/or free trial periods and then recalculates the
* appropriate totals by using the @see self::$calculation_type flag and cloning the cart to run @see WC_Cart::calculate_totals()
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.3.5
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function calculate_subscription_totals( $total, $cart ) {
// If the cart doesn't contain a subscription, skip calculating recurring totals.
if ( ! self::cart_contains_subscription() && ! wcs_cart_contains_resubscribe() ) {
return $total;
}
// We're in the middle of a recalculation, let it run.
if ( 'none' !== self::$calculation_type ) {
return $total;
}
/**
* If we're in the middle of calculating recurring totals, skip this calculation to avoid infinite loops.
* We use whether there's a recurring cart key in the calculation stack (ie has started but hasn't finished) to determine if we're in the middle calculating recurring totals.
*/
if ( ! empty( array_diff( self::$recurring_totals_calculation_stack, [ 'none' ] ) ) ) {
return $total;
}
// Save the original cart values/totals, as we'll use this when there is no sign-up fee
WC()->cart->total = ( $total < 0 ) ? 0 : $total;
do_action( 'woocommerce_subscription_cart_before_grouping' );
$subscription_groups = array();
// Group the subscription items by their cart item key based on billing schedule
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
if ( WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) {
$subscription_groups[ self::get_recurring_cart_key( $cart_item ) ][] = $cart_item_key;
}
}
do_action( 'woocommerce_subscription_cart_after_grouping' );
$recurring_carts = array();
// Back up the shipping method. Chances are WC is going to wipe the chosen_shipping_methods data
WC()->session->set( 'wcs_shipping_methods', WC()->session->get( 'chosen_shipping_methods', array() ) );
// Now let's calculate the totals for each group of subscriptions
self::$calculation_type = 'recurring_total';
foreach ( $subscription_groups as $recurring_cart_key => $subscription_group ) {
// Create a clone cart to calculate and store totals for this group of subscriptions
$recurring_cart = clone WC()->cart;
$product = null;
// Set the current recurring key flag on this class, and store the recurring_cart_key to the new cart instance.
self::$recurring_cart_key = $recurring_cart_key;
$recurring_cart->recurring_cart_key = $recurring_cart_key;
// Remove any items not in this subscription group
foreach ( $recurring_cart->get_cart() as $cart_item_key => $cart_item ) {
if ( ! in_array( $cart_item_key, $subscription_group, true ) ) {
unset( $recurring_cart->cart_contents[ $cart_item_key ] );
continue;
}
if ( null === $product ) {
$product = $cart_item['data'];
}
}
$recurring_cart->start_date = apply_filters( 'wcs_recurring_cart_start_date', gmdate( 'Y-m-d H:i:s' ), $recurring_cart );
$recurring_cart->trial_end_date = apply_filters( 'wcs_recurring_cart_trial_end_date', WC_Subscriptions_Product::get_trial_expiration_date( $product, $recurring_cart->start_date ), $recurring_cart, $product );
$recurring_cart->next_payment_date = apply_filters( 'wcs_recurring_cart_next_payment_date', WC_Subscriptions_Product::get_first_renewal_payment_date( $product, $recurring_cart->start_date ), $recurring_cart, $product );
$recurring_cart->end_date = apply_filters( 'wcs_recurring_cart_end_date', WC_Subscriptions_Product::get_expiration_date( $product, $recurring_cart->start_date ), $recurring_cart, $product );
// Before calculating recurring cart totals, store this recurring cart object
self::set_cached_recurring_cart( $recurring_cart );
// No fees recur (yet)
if ( is_callable( array( $recurring_cart, 'fees_api' ) ) ) { // WC 3.2 +
$recurring_cart->fees_api()->remove_all_fees();
} else {
$recurring_cart->fees = array();
}
$recurring_cart->fee_total = 0;
$recurring_cart->calculate_totals();
// Store this groups cart details
$recurring_carts[ $recurring_cart_key ] = clone $recurring_cart;
// And remove some other floatsam
$recurring_carts[ $recurring_cart_key ]->removed_cart_contents = array();
$recurring_carts[ $recurring_cart_key ]->cart_session_data = array();
// Keep a record of the shipping packages so we can add them to the global packages later
self::$recurring_shipping_packages[ $recurring_cart_key ] = WC()->shipping->get_packages();
}
// Reset flags when we're done processing recurring carts.
self::$calculation_type = self::$recurring_cart_key = 'none';
// Only calculate the initial order cart shipping if we need to show shipping.
if ( WC()->cart->show_shipping() ) {
WC()->cart->calculate_shipping();
}
// We no longer need our backup of shipping methods
unset( WC()->session->wcs_shipping_methods );
// If there is no sign-up fee and a free trial, and no products being purchased with the subscription, we need to zero the fees for the first billing period
$remove_fees_from_cart = ( 0 == self::get_cart_subscription_sign_up_fee() && self::all_cart_items_have_free_trial() );
/**
* Allow third-parties to override whether the fees will be removed from the initial order cart.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.4.3
* @param bool $remove_fees_from_cart Whether the fees will be removed. By default fees will be removed if there is no signup fee and all cart items have a trial.
* @param WC_Cart $cart The standard WC cart object.
* @param array $recurring_carts All the recurring cart objects.
*/
if ( apply_filters( 'wcs_remove_fees_from_initial_cart', $remove_fees_from_cart, $cart, $recurring_carts ) ) {
$cart_fees = WC()->cart->get_fees();
if ( wcs_is_woocommerce_pre( '3.2' ) ) {
foreach ( $cart_fees as $fee_index => $fee ) {
WC()->cart->fees[ $fee_index ]->amount = 0;
WC()->cart->fees[ $fee_index ]->tax = 0;
}
} else {
foreach ( $cart_fees as $fee ) {
$fee->amount = 0;
$fee->tax = 0;
$fee->total = 0;
}
WC()->cart->fees_api()->set_fees( $cart_fees );
}
WC()->cart->fee_total = 0;
}
WC()->cart->recurring_carts = $recurring_carts;
$total = max( 0, round( WC()->cart->cart_contents_total + WC()->cart->tax_total + WC()->cart->shipping_tax_total + WC()->cart->shipping_total + WC()->cart->fee_total, WC()->cart->dp ) );
if ( ! self::charge_shipping_up_front() ) {
$total = max( 0, $total - WC()->cart->shipping_tax_total - WC()->cart->shipping_total );
WC()->cart->shipping_taxes = array();
WC()->cart->shipping_tax_total = 0;
WC()->cart->shipping_total = 0;
}
return apply_filters( 'woocommerce_subscriptions_calculated_total', $total );
}
/**
* Check whether shipping should be charged on the initial order.
*
* When the cart contains a physical subscription with a free trial and no other physical items, shipping
* should not be charged up-front.
*
* @internal self::all_cart_items_have_free_trial() is false if non-subscription products are in the cart.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5.4
*/
public static function charge_shipping_up_front() {
return apply_filters( 'woocommerce_subscriptions_cart_shipping_up_front', ! self::all_cart_items_have_free_trial() );
}
/**
* The cart needs shipping only if it needs shipping up front and/or for recurring items.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @param boolean $needs_shipping True if shipping is needed for the cart.
* @return boolean
*/
public static function cart_needs_shipping( $needs_shipping ) {
if ( self::cart_contains_subscription() ) {
if ( 'none' === self::$calculation_type ) {
$has_subscription_needing_shipping = self::cart_contains_subscriptions_needing_shipping();
$charge_shipping_up_front = self::charge_shipping_up_front();
// If we have a subscription that either needs shipping, or needs shipping charging up front, force true.
if ( $has_subscription_needing_shipping && $charge_shipping_up_front ) {
$needs_shipping = true;
}
} elseif ( 'recurring_total' === self::$calculation_type ) {
$cart = isset( self::$cached_recurring_cart ) ? self::$cached_recurring_cart : WC()->cart;
$needs_shipping = self::cart_contains_subscriptions_needing_shipping( $cart );
}
}
return $needs_shipping;
}
/**
* The cart needs a shipping address if any item needs shipping, including recurring items.
*
* @param boolean $needs_shipping_address True if a shipping address is needed for the cart.
* @return boolean
*/
public static function cart_needs_shipping_address( $needs_shipping_address ) {
if ( $needs_shipping_address ) {
return $needs_shipping_address;
}
if ( ! wc_ship_to_billing_address_only() && self::cart_contains_subscription() ) {
$needs_shipping_address = self::cart_contains_subscriptions_needing_shipping();
}
return $needs_shipping_address;
}
/**
* Remove all recurring shipping methods stored in the session (i.e. methods with a key that is a string)
*
* This is attached as a callback to hooks triggered whenever a product is removed from the cart.
*
* @param $cart_item_key string The key for a cart item about to be removed from the cart.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.15
*/
public static function maybe_reset_chosen_shipping_methods( $cart_item_key ) {
if ( isset( WC()->cart->cart_contents[ $cart_item_key ] ) ) {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods', array() );
// Remove all recurring methods
foreach ( $chosen_methods as $key => $methods ) {
if ( ! is_numeric( $key ) ) {
unset( $chosen_methods[ $key ] );
}
}
WC()->session->set( 'chosen_shipping_methods', $chosen_methods );
}
}
/**
* When shipping subscriptions, changes the original package to "initial shipment".
*
* @param string $package_name Package name.
* @param string|int $package_id Package ID.
* @return array $package Package contents.
*/
public static function change_initial_shipping_package_name( $package_name, $package_id, $package ) {
if ( ! self::cart_contains_subscription() || isset( $package['recurring_cart_key'] ) ) {
return $package_name;
}
return __( 'Initial Shipment', 'woocommerce-subscriptions' );
}
/**
* Create a shipping package index for a given shipping package on a recurring cart.
*
* @param string $recurring_cart_key a cart key of the form returned by @see self::get_recurring_cart_key()
* @param int $package_index the index of a package
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.12
*/
public static function get_recurring_shipping_package_key( $recurring_cart_key, $package_index ) {
return $recurring_cart_key . '_' . $package_index;
}
/**
* Create a shipping package index for a given shipping package on a recurring cart.
*
* @return array
*/
public static function get_recurring_shipping_packages() {
return self::$recurring_shipping_packages;
}
/**
* Add the shipping packages stored in @see self::$recurring_shipping_packages to WooCommerce's global
* set of packages in WC()->shipping->packages so that plugins attempting to get the details of recurring
* packages can get them with WC()->shipping->get_packages() like any other packages.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.13
*/
public static function set_global_recurring_shipping_packages() {
foreach ( self::$recurring_shipping_packages as $recurring_cart_key => $packages ) {
foreach ( $packages as $package_index => $package ) {
WC()->shipping->packages[ $package_index ] = $package;
}
}
}
/**
* Check whether all the subscription product items in the cart have a free trial.
*
* Useful for determining if certain up-front amounts should be charged.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function all_cart_items_have_free_trial() {
$all_items_have_free_trial = true;
foreach ( WC()->cart->get_cart() as $cart_item ) {
if ( ! WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) {
$all_items_have_free_trial = false;
break;
} else {
if ( 0 == WC_Subscriptions_Product::get_trial_length( $cart_item['data'] ) ) {
$all_items_have_free_trial = false;
break;
}
}
}
return apply_filters( 'woocommerce_subscriptions_all_cart_items_have_free_trial', $all_items_have_free_trial );
}
/**
* Check if the cart contains a subscription which requires shipping.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5.4
*/
public static function cart_contains_subscriptions_needing_shipping( $cart = null ) {
if ( 'no' === get_option( 'woocommerce_calc_shipping' ) ) {
return false;
}
if ( null === $cart ) {
$cart = WC()->cart;
}
$cart_contains_subscriptions_needing_shipping = false;
if ( self::cart_contains_subscription() ) {
foreach ( $cart->cart_contents as $cart_item_key => $values ) {
$_product = $values['data'];
if ( ! WC_Subscriptions_Product::is_subscription( $_product ) || ! $_product->needs_shipping() ) {
continue;
}
if ( 'recurring_total' === self::$calculation_type && WC_Subscriptions_Product::needs_one_time_shipping( $_product ) ) {
continue;
}
$cart_contains_subscriptions_needing_shipping = true;
}
}
return apply_filters( 'woocommerce_cart_contains_subscriptions_needing_shipping', $cart_contains_subscriptions_needing_shipping );
}
/**
* Filters the cart contents to remove any subscriptions with free trials (or synchronised to a date in the future)
* to make sure no shipping amount is calculated for them.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function set_cart_shipping_packages( $packages ) {
if ( ! self::cart_contains_subscription() ) {
return $packages;
}
if ( 'none' === self::$calculation_type ) {
foreach ( $packages as $index => $package ) {
foreach ( $package['contents'] as $cart_item_key => $cart_item ) {
if ( WC_Subscriptions_Product::get_trial_length( $cart_item['data'] ) > 0 ) {
unset( $packages[ $index ]['contents'][ $cart_item_key ] );
}
}
if ( empty( $packages[ $index ]['contents'] ) ) {
unset( $packages[ $index ] );
}
}
} elseif ( 'recurring_total' === self::$calculation_type ) {
/**
* This logic runs for recurring carts, not the main cart.
*/
$new_packages = array();
foreach ( $packages as $index => $package ) {
$new_package = $package;
// we need to make sure the package is different for recurring carts to bypass WC's cache
$new_package['recurring_cart_key'] = self::$recurring_cart_key;
// We need to track the original package index.
$new_package['package_index'] = $index;
foreach ( $new_package['contents'] as $cart_item_key => $cart_item ) {
if ( WC_Subscriptions_Product::needs_one_time_shipping( $cart_item['data'] ) ) {
$new_package['contents_cost'] -= $cart_item['line_total'];
unset( $new_package['contents'][ $cart_item_key ] );
}
}
if ( ! empty( $packages[ $index ]['contents'] ) ) {
$new_packages[ self::get_recurring_shipping_package_key( self::$recurring_cart_key, $index ) ] = $new_package;
}
}
$packages = $new_packages;
}
return $packages;
}
/**
* Checks whether or not the COD gateway should be available on checkout when a subscription has a free trial.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v3.0.6
*
* @param array $available_gateways The currently available payment gateways.
* @return array All of the available payment gateways.
*/
public static function check_cod_gateway_for_free_trials( $available_gateways ) {
if ( ! self::cart_contains_free_trial() ) {
return $available_gateways;
}
$all_gateways = WC()->payment_gateways->payment_gateways();
if ( ! isset( $all_gateways['cod'] ) ) {
return $available_gateways;
}
$gateway = $all_gateways['cod'];
/**
* Since the COD gateway supports shipping method restrictions we run into problems with free trials.
* We don't make packages for free trial subscriptions and thus they have no assigned shipping
* method to match against the payment gateway. We can get around this limitation by abusing
* the fact that the user has to select a shipping method for the recurring cart.
*/
$packages = WC()->shipping->packages;
self::set_global_recurring_shipping_packages();
if ( $gateway->is_available() ) {
$available_gateways['cod'] = $gateway;
} else {
// Handle the case where it was previous available but the method chosen by the recurring package
// causes it to no longer be available.
unset( $available_gateways['cod'] );
}
WC()->shipping->packages = $packages;
return $available_gateways;
}
/* Formatted Totals Functions */
/**
* Returns the subtotal for a cart item including the subscription period and duration details
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0
*/
public static function get_formatted_product_subtotal( $product_subtotal, $product, $quantity, $cart ) {
if ( WC_Subscriptions_Product::is_subscription( $product ) && ! wcs_cart_contains_renewal() ) {
$product_price_filter = is_a( $product, 'WC_Product_Variation' ) ? 'woocommerce_product_variation_get_price' : 'woocommerce_product_get_price';
// Avoid infinite loop
remove_filter( 'woocommerce_cart_product_subtotal', __CLASS__ . '::get_formatted_product_subtotal', 11 );
add_filter( $product_price_filter, 'WC_Subscriptions_Product::get_sign_up_fee_filter', 100, 2 );
// And get the appropriate sign up fee string
$sign_up_fee_string = $cart->get_product_subtotal( $product, $quantity );
remove_filter( $product_price_filter, 'WC_Subscriptions_Product::get_sign_up_fee_filter', 100 );
add_filter( 'woocommerce_cart_product_subtotal', __CLASS__ . '::get_formatted_product_subtotal', 11, 4 );
$product_subtotal = WC_Subscriptions_Product::get_price_string(
$product,
array(
'price' => $product_subtotal,
'sign_up_fee' => $sign_up_fee_string,
'tax_calculation' => wcs_is_woocommerce_pre( '4.4' ) ? WC()->cart->tax_display_cart : WC()->cart->get_tax_price_display_mode(),
)
);
$inc_tax_or_vat_string = WC()->countries->inc_tax_or_vat();
$ex_tax_or_vat_string = WC()->countries->ex_tax_or_vat();
if ( ! empty( $inc_tax_or_vat_string ) && false !== strpos( $product_subtotal, $inc_tax_or_vat_string ) ) {
$product_subtotal = str_replace( WC()->countries->inc_tax_or_vat(), '', $product_subtotal ) . ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
}
if ( ! empty( $ex_tax_or_vat_string ) && false !== strpos( $product_subtotal, $ex_tax_or_vat_string ) ) {
$product_subtotal = str_replace( WC()->countries->ex_tax_or_vat(), '', $product_subtotal ) . ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
}
$product_subtotal = '<span class="subscription-price">' . $product_subtotal . '</span>';
}
return $product_subtotal;
}
/*
* Helper functions for extracting the details of subscriptions in the cart
*/
/**
* Checks the cart to see if it contains a subscription product.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0
* @return boolean
*/
public static function cart_contains_subscription() {
if ( ! empty( WC()->cart->cart_contents ) && ! wcs_cart_contains_renewal() ) {
foreach ( WC()->cart->cart_contents as $cart_item ) {
if ( WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) {
return true;
}
}
}
return false;
}
/**
* Checks the cart to see if it contains a subscription product with a free trial
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function cart_contains_free_trial() {
$cart_contains_free_trial = false;
if ( self::cart_contains_subscription() ) {
foreach ( WC()->cart->cart_contents as $cart_item ) {
if ( WC_Subscriptions_Product::get_trial_length( $cart_item['data'] ) > 0 ) {
$cart_contains_free_trial = true;
break;
}
}
}
return $cart_contains_free_trial;
}
/**
* Gets the cart calculation type flag
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function get_calculation_type() {
return self::$calculation_type;
}
/**
* Sets the cart calculation type flag
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function set_calculation_type( $calculation_type ) {
self::$calculation_type = $calculation_type;
return $calculation_type;
}
/**
* Sets the recurring cart key flag.
*
* @internal While this is indeed stored to the cart object, some hooks such as woocommerce_cart_shipping_packages
* do not have access to this property. So we can properly set package IDs we make use of this flag.
*
* @param string $recurring_cart_key Recurring cart key used to identify the current recurring cart being processed.
*/
public static function set_recurring_cart_key( $recurring_cart_key ) {
self::$recurring_cart_key = $recurring_cart_key;
return $recurring_cart_key;
}
/**
* Update the cached recurring cart.
*
* @param \WC_Cart $recurring_cart Cart object.
*/
public static function set_cached_recurring_cart( $recurring_cart ) {
self::$cached_recurring_cart = $recurring_cart;
}
/**
* Gets the subscription sign up fee for the cart and returns it
*
* Currently short-circuits to return just the sign-up fee of the first subscription, because only
* one subscription can be purchased at a time.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0
*/
public static function get_cart_subscription_sign_up_fee() {
$sign_up_fee = 0;
if ( self::cart_contains_subscription() || wcs_cart_contains_renewal() ) {
$renewal_item = wcs_cart_contains_renewal();
foreach ( WC()->cart->cart_contents as $cart_item ) {
// Renewal items do not have sign-up fees
if ( $renewal_item == $cart_item ) {
continue;
}
$cart_item_sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $cart_item['data'] );
// Extra check to make sure that the sign up fee is numeric before using it
$cart_item_sign_up_fee = is_numeric( $cart_item_sign_up_fee ) ? (float) $cart_item_sign_up_fee : 0;
$sign_up_fee += $cart_item_sign_up_fee;
}
}
return apply_filters( 'woocommerce_subscriptions_cart_sign_up_fee', $sign_up_fee );
}
/**
* Check whether the cart needs payment even if the order total is $0
*
* @param bool $needs_payment The existing flag for whether the cart needs payment or not.
* @param WC_Cart $cart The WooCommerce cart object.
* @return bool
*/
public static function cart_needs_payment( $needs_payment, $cart ) {
// Skip checks if needs payment is already set or cart total not 0.
if ( false !== $needs_payment || 0 != $cart->total ) {
return $needs_payment;
}
// Skip checks if cart has no subscriptions.
if ( ! self::cart_contains_subscription() ) {
return $needs_payment;
}
// Skip checks if cart contains subscription switches or automatic payments are disabled.
if ( false !== wcs_cart_contains_switches( 'any' ) || wcs_is_manual_renewal_required() ) {
return $needs_payment;
}
$recurring_total = 0;
$is_one_period = true;
$contains_synced = false;
$contains_expiring_limited_coupon = false;
if ( ! empty( WC()->cart->recurring_carts ) ) {
foreach ( WC()->cart->recurring_carts as $recurring_cart ) {
$recurring_total += $recurring_cart->total;
$subscription_length = wcs_cart_pluck( $recurring_cart, 'subscription_length' );
$contains_synced = $contains_synced || (bool) WC_Subscriptions_Synchroniser::cart_contains_synced_subscription( $recurring_cart );
$contains_expiring_limited_coupon = $contains_expiring_limited_coupon || ( class_exists( 'WCS_Limited_Recurring_Coupon_Manager' ) && WCS_Limited_Recurring_Coupon_Manager::recurring_cart_contains_expiring_coupon( $recurring_cart ) );
if ( 0 == $subscription_length || wcs_cart_pluck( $recurring_cart, 'subscription_period_interval' ) != $subscription_length ) {
$is_one_period = false;
}
}
}
$needs_trial_payment = self::cart_contains_free_trial();
if ( $contains_expiring_limited_coupon || $recurring_total > 0 && ( ! $is_one_period || $needs_trial_payment || $contains_synced ) ) {
$needs_payment = true;
}
return $needs_payment;
}
/**
* Make sure cart product prices correctly include/exclude taxes.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5.8
*/
public static function cart_product_price( $price, $product ) {
if ( WC_Subscriptions_Product::is_subscription( $product ) ) {
$tax_price_display_mode = wcs_is_woocommerce_pre( '4.4' ) ? WC()->cart->tax_display_cart : WC()->cart->get_tax_price_display_mode();
$price = WC_Subscriptions_Product::get_price_string(
$product,
array(
'price' => $price,
'tax_calculation' => $tax_price_display_mode,
)
);
}
return $price;
}
/**
* Displays the recurring totals for items in the cart.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function display_recurring_totals() {
if ( self::cart_contains_subscription() && ! empty( WC()->cart->recurring_carts ) ) {
// We only want shipping for recurring amounts, and they need to be calculated again here.
self::$calculation_type = 'recurring_total';
$carts_with_multiple_payments = 0;
foreach ( WC()->cart->recurring_carts as $recurring_cart ) {
// Cart contains more than one payment.
if ( 0 != $recurring_cart->next_payment_date ) {
$carts_with_multiple_payments++;
}
}
if ( apply_filters( 'woocommerce_subscriptions_display_recurring_totals', $carts_with_multiple_payments >= 1 ) ) {
wc_get_template(
'checkout/recurring-totals.php',
array(
'shipping_methods' => array(),
'recurring_carts' => WC()->cart->recurring_carts,
'carts_with_multiple_payments' => $carts_with_multiple_payments,
),
'',
WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/' )
);
}
self::$calculation_type = 'none';
}
}
/**
* Construct a cart key based on the billing schedule of a subscription product.
*
* Subscriptions groups products by billing schedule when calculating cart totals, so that shipping and other "per order" amounts
* can be calculated for each group of items for each renewal. This method constructs a cart key based on the billing schedule
* to allow products on the same billing schedule to be grouped together - free trials and synchronisation is accounted for by
* using the first renewal date (if any) for the subscription.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_recurring_cart_key( $cart_item, $renewal_time = '' ) {
return apply_filters( 'woocommerce_subscriptions_recurring_cart_key', wcs_get_subscription_grouping_key( $cart_item['data'], $renewal_time ), $cart_item );
}
/**
* When calculating shipping for recurring carts, return a revised list of shipping methods that apply to this recurring cart.
*
* When WooCommerce determines the taxable address for local pick up methods, we only want to return pick up shipping methods
* chosen for the recurring cart being calculated instead of all methods.
*
* @param array $shipping_methods
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.13
*/
public static function filter_recurring_cart_chosen_shipping_method( $shipping_methods ) {
if ( 'recurring_total' === self::$calculation_type && 'none' !== self::$recurring_cart_key ) {
$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods', array() );
$standard_package_methods = array();
$recurring_cart_shipping_methods = array();
foreach ( $chosen_shipping_methods as $key => $method ) {
if ( is_numeric( $key ) ) {
$standard_package_methods[ $key ] = $method;
} elseif ( strpos( $key, self::$recurring_cart_key ) !== false ) {
$recurring_cart_shipping_methods[ $key ] = $method;
}
}
// pick which chosen methods apply to this recurring cart. Defaults to standard methods if there is no specific recurring cart shipping methods chosen.
$applicable_chosen_shipping_methods = ( empty( $recurring_cart_shipping_methods ) ) ? $standard_package_methods : $recurring_cart_shipping_methods;
$shipping_methods = array_intersect( $applicable_chosen_shipping_methods, $shipping_methods );
}
return $shipping_methods;
}
/**
* Validate the chosen recurring shipping methods for each recurring shipping package.
* Ensures there is at least one chosen shipping method and that the chosen method is valid considering the available
* package rates.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.14
*/
public static function validate_recurring_shipping_methods() {
$shipping_methods = WC()->checkout()->shipping_methods;
$added_invalid_notice = false;
$standard_packages = WC()->shipping->get_packages();
// Temporarily store the current calculation type and recurring cart key so we can restore them later.
$calculation_type = self::$calculation_type;
$recurring_cart_key_flag = self::$recurring_cart_key;
$cached_recurring_cart = self::$cached_recurring_cart;
self::set_calculation_type( 'recurring_total' );
foreach ( WC()->cart->recurring_carts as $recurring_cart_key => $recurring_cart ) {
if ( false === $recurring_cart->needs_shipping() || 0 == $recurring_cart->next_payment_date ) {
continue;
}
// Set the recurring cart flags so shipping calculations have the recurring cart as context.
self::set_recurring_cart_key( $recurring_cart_key );
self::set_cached_recurring_cart( $recurring_cart );
foreach ( $recurring_cart->get_shipping_packages() as $recurring_cart_package_key => $recurring_cart_package ) {
$package_index = isset( $recurring_cart_package['package_index'] ) ? $recurring_cart_package['package_index'] : 0;
$package = WC()->shipping->calculate_shipping_for_package( $recurring_cart_package );
$package_rates_match = false;
if ( isset( $standard_packages[ $package_index ] ) ) {
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
$package_rates_match = apply_filters( 'wcs_recurring_shipping_package_rates_match_standard_rates', $package['rates'] == $standard_packages[ $package_index ]['rates'], $package['rates'], $standard_packages[ $package_index ]['rates'], $recurring_cart_key );
}
if ( $package_rates_match ) {
// The recurring package rates match the initial package rates, there won't be a selected shipping method for this recurring cart package move on to the next package.
if ( apply_filters( 'wcs_cart_totals_shipping_html_price_only', true, $package, $recurring_cart ) ) {
continue;
}
}
// If the chosen shipping method is not available for this recurring cart package, display an error and unset the selected method.
if ( ! isset( $package['rates'][ $shipping_methods[ $recurring_cart_package_key ] ] ) ) {
if ( ! $added_invalid_notice ) {
wc_add_notice( __( 'Invalid recurring shipping method.', 'woocommerce-subscriptions' ), 'error' );
$added_invalid_notice = true;
}
$shipping_methods[ $recurring_cart_package_key ] = '';
}
}
}
// If there was an invalid recurring shipping method found, we need to apply the changes to WC()->checkout()->shipping_methods.
if ( $added_invalid_notice ) {
WC()->checkout()->shipping_methods = $shipping_methods;
}
// Restore the calculation type and recurring cart key.
self::set_calculation_type( $calculation_type );
self::set_recurring_cart_key( $recurring_cart_key_flag );
self::set_cached_recurring_cart( $cached_recurring_cart );
}
/**
* Checks the cart to see if it contains a specific product.
*
* @param int $product_id The product ID or variation ID to look for.
* @return bool Whether the product is in the cart.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.13
*/
public static function cart_contains_product( $product_id ) {
$cart_contains_product = false;
if ( ! empty( WC()->cart->cart_contents ) ) {
foreach ( WC()->cart->cart_contents as $cart_item ) {
if ( wcs_get_canonical_product_id( $cart_item ) === $product_id ) {
$cart_contains_product = true;
break;
}
}
}
return $cart_contains_product;
}
/**
* Checks the cart to see if it contains any subscription product other than a specific product.
*
* @param int $product_id The product ID or variation ID other than which to look for.
* @return bool Whether another subscription product is in the cart.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v3.0.5
*/
public static function cart_contains_other_subscription_products( $product_id ) {
$cart_contains_other_subscription_products = false;
if ( ! empty( WC()->cart->cart_contents ) && WC_Subscriptions_Product::is_subscription( $product_id ) ) {
$is_subscription = WC_Subscriptions_Product::is_subscription( $product_id );
foreach ( WC()->cart->cart_contents as $cart_item ) {
if ( wcs_get_canonical_product_id( $cart_item ) !== $product_id ) {
$cart_contains_other_subscription_products = true;
break;
}
}
}
return $cart_contains_other_subscription_products;
}
/**
* When calculating the free shipping method availability, WC uses the WC->cart object. During shipping calculations for
* recurring carts we need the recurring cart's total and coupons to be the base for checking its availability
*
* @param bool $is_available
* @param array $package
* @return bool $is_available a revised version of is_available based off the recurring cart object
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.20
*/
public static function maybe_recalculate_shipping_method_availability( $is_available, $package ) {
if ( ! isset( $package['recurring_cart_key'], self::$cached_recurring_cart ) || $package['recurring_cart_key'] !== self::$cached_recurring_cart->recurring_cart_key ) {
return $is_available;
}
if ( ! wcs_is_woocommerce_pre( '3.2' ) ) {
wcs_doing_it_wrong( __METHOD__, 'This method should no longer be used on WC 3.2.0 and newer. Use WC_Subscriptions_Cart::recalculate_shipping_method_availability() and pass the specific shipping method as the third parameter instead.', '2.5.6' );
}
// Take a copy of the WC global cart object so we can temporarily set it to base shipping method availability on the cached recurring cart
$global_cart = WC()->cart;
WC()->cart = self::$cached_recurring_cart;
$shipping_methods = WC()->shipping->get_shipping_methods();
$is_available = false;
remove_filter( 'woocommerce_shipping_free_shipping_is_available', __METHOD__ );
foreach ( $shipping_methods as $shipping_method ) {
if ( 'free_shipping' === $shipping_method->id && $shipping_method->get_instance_id() && $shipping_method->is_available( $package ) ) {
$is_available = true;
break;
}
}
add_filter( 'woocommerce_shipping_free_shipping_is_available', __METHOD__, 10, 2 );
WC()->cart = $global_cart;
return $is_available;
}
/**
* Calculates whether a shipping method is available for the recurring cart.
*
* By default WooCommerce core checks the initial cart for shipping method availability. For recurring carts,
* shipping method availability is based whether the recurring total and coupons meet the requirements.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.6
*
* @param bool $is_available Whether the shipping method is available or not.
* @param array $package a shipping package.
* @param WC_Shipping_Method $shipping_method An instance of a shipping method.
* @return bool Whether the shipping method is available for the recurring cart or not.
*/
public static function recalculate_shipping_method_availability( $is_available, $package, $shipping_method ) {
if ( ! isset( $package['recurring_cart_key'], self::$cached_recurring_cart ) || $package['recurring_cart_key'] !== self::$cached_recurring_cart->recurring_cart_key ) {
return $is_available;
}
// Take a copy of the WC global cart object so we can temporarily set it to base shipping method availability on the cached recurring cart
$global_cart = WC()->cart;
WC()->cart = self::$cached_recurring_cart;
remove_filter( 'woocommerce_shipping_free_shipping_is_available', __METHOD__ );
$is_available = $shipping_method->is_available( $package );
add_filter( 'woocommerce_shipping_free_shipping_is_available', __METHOD__, 10, 3 );
// Restore the global cart object.
WC()->cart = $global_cart;
return $is_available;
}
/**
* Allow third-parties to apply fees which apply to the cart to recurring carts.
*
* @param WC_Cart $cart
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.16
*/
public static function apply_recurring_fees( $cart ) {
if ( ! empty( $cart->recurring_cart_key ) ) {
foreach ( WC()->cart->get_fees() as $fee ) {
if ( apply_filters( 'woocommerce_subscriptions_is_recurring_fee', false, $fee, $cart ) ) {
if ( is_callable( array( $cart, 'fees_api' ) ) ) { // WC 3.2 +
$cart->fees_api()->add_fee( $fee );
} else {
$cart->add_fee( $fee->name, $fee->amount, $fee->taxable, $fee->tax_class );
}
}
}
}
}
/**
* Update the chosen recurring package shipping methods from posted checkout form data.
*
* Between requests, the presence of recurring package chosen shipping methods in posted
* checkout data can change. For example, when the number of available shipping methods
* change and cause the hidden elements (generated by @see wcs_cart_print_shipping_input())
* to be displayed or not displayed.
*
* When this occurs, we need to remove those chosen shipping methods from the session so
* that those packages no longer use the previously selected shipping method.
*
* @param string $encoded_form_data Encoded checkout form data.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
*/
public static function update_chosen_shipping_methods( $encoded_form_data ) {
$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods', array() );
parse_str( $encoded_form_data, $form_data );
foreach ( $chosen_shipping_methods as $package_index => $method ) {
// Remove the chosen shipping methods for recurring packages which are no longer present in posted checkout data.
if ( ! is_numeric( $package_index ) && ! isset( $form_data['shipping_method'][ $package_index ] ) ) {
unset( $chosen_shipping_methods[ $package_index ] );
}
}
WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
}
/**
* Removes all subscription products from the shopping cart.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.6.0
*/
public static function remove_subscriptions_from_cart() {
foreach ( WC()->cart->cart_contents as $cart_item_key => $cart_item ) {
if ( WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) {
WC()->cart->set_quantity( $cart_item_key, 0 );
}
}
}
/**
* Records the cart item base location tax total for later storage.
*
* If the customer is outside of the base location, WC core removes the taxes
* which apply to the base location. @see WC_Cart_Totals::adjust_non_base_location_price().
*
* We need to record these base tax rates to be able to honour grandfathered subscription
* recurring prices in renewal carts.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v3.0.10
* @param WC_Cart $cart The cart object. Could be the global (initial cart) or a recurring cart.
*/
public static function record_base_tax_rates( $cart ) {
// We only need to record the tax rates on recurring carts when prices are reduced by tax applicable to the base store location.
if ( ! isset( $cart->recurring_cart_key ) || ! apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) || ! wc_prices_include_tax() ) {
return;
}
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
// Skip non-taxable items
if ( 'taxable' !== $cart_item['data']->get_tax_status() ) {
continue;
}
$product = $cart_item['data'];
// Get the taxes which apply to the store's base location and to the customer.
$base_tax_rates = WC_Tax::get_base_tax_rates( $product->get_tax_class( 'unfiltered' ) );
$applicable_tax_rates = WC_Tax::get_rates( $product->get_tax_class(), $cart->get_customer() );
// We only need to keep track if the taxes applicable to the customer are different to the taxes which apply to the store's base location.
if ( $applicable_tax_rates !== $base_tax_rates ) {
$cart->cart_contents[ $cart_item_key ]['_subtracted_base_location_taxes'] = WC_Tax::calc_tax( $product->get_price(), $base_tax_rates, true );
$cart->cart_contents[ $cart_item_key ]['_subtracted_base_location_rates'] = $base_tax_rates;
}
}
}
/**
* Set the chosen shipping method for recurring cart calculations
*
* In WC_Shipping::calculate_shipping(), WooCommerce tries to determine the chosen shipping method
* based on the package index and stores rates. However, for recurring cart shipping selection, we
* use the recurring cart key instead of numeric index. Therefore, we need to hook in to override
* the default shipping method when WooCommerce could not find a matching shipping method.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.12
*
* @param string $default_method the default shipping method for the customer/store returned by WC_Shipping::get_default_method()
* @param array $available_methods set of shipping rates for this calculation
* @param int $package_index WC doesn't pass the package index to callbacks on the 'woocommerce_shipping_chosen_method' filter (yet) so we set a default value of 0 for it in the function params
*
* @return string
*/
public static function set_chosen_shipping_method( $default_method, $available_methods, $package_index = 0 ) {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods', array() );
$recurring_cart_package_key = self::get_recurring_shipping_package_key( self::$recurring_cart_key, $package_index );
if ( 'none' !== self::$recurring_cart_key && isset( $chosen_methods[ $recurring_cart_package_key ], $available_methods[ $chosen_methods[ $recurring_cart_package_key ] ] ) ) {
$default_method = $chosen_methods[ $recurring_cart_package_key ];
// Set the chosen shipping method (if available) to workaround WC_Shipping::get_default_method() setting the default shipping method whenever method count changes
} elseif ( isset( $chosen_methods[ $package_index ], $available_methods[ $chosen_methods[ $package_index ] ] ) && $default_method !== $chosen_methods[ $package_index ] ) {
$default_method = $chosen_methods[ $package_index ];
}
return $default_method;
}
/**
* Redirects the customer to the cart after they add a subscription to the cart.
*
* Only enabled if multiple checkout is not enabled.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v4.0.0
*
* @param string $url The cart redirect $url.
* @return string $url.
*/
public static function add_to_cart_redirect( $url ) {
if ( ! isset( $_REQUEST['add-to-cart'] ) || ! is_numeric( $_REQUEST['add-to-cart'] ) ) {
return $url;
}
// If product is of the subscription type
if ( ! WC_Subscriptions_Product::is_subscription( absint( $_REQUEST['add-to-cart'] ) ) ) {
return $url;
}
// Redirect to checkout if mixed checkout is disabled
if ( 'yes' === get_option( WC_Subscriptions_Admin::$option_prefix . '_multiple_purchase', 'no' ) ) {
return $url;
}
$quantity = isset( $_REQUEST['quantity'] ) ? wc_clean( wp_unslash( $_REQUEST['quantity'] ) ) : 1;
$product_id = wc_clean( wp_unslash( $_REQUEST['add-to-cart'] ) );
$add_to_cart_notice = wc_add_to_cart_message( array( $product_id => $quantity ), true, true );
if ( wc_has_notice( $add_to_cart_notice ) ) {
$notices = wc_get_notices();
$add_to_cart_notice_index = array_search( $add_to_cart_notice, $notices['success'] );
unset( $notices['success'][ $add_to_cart_notice_index ] );
wc_set_notices( $notices );
}
return wc_get_checkout_url();
}
/* Deprecated */
/**
* Calculates the shipping rates for a package.
*
* This function will check cached rates based on a hash of the package contents to avoid re-calculation per page load.
* If there are no rates stored in the cache for this package, it will fall back to @see WC_Shipping::calculate_shipping_for_package()
*
* @deprecated 5.4.0
*
* @param array $package A shipping package in the form returned by @see WC_Cart->get_shipping_packages()
* @return array $package
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.18
*/
public static function get_calculated_shipping_for_package( $package ) {
_deprecated_function( __METHOD__, 'subscriptions-core 5.4.0', 'WC()->shipping->calculate_shipping_for_package()' );
return WC()->shipping->calculate_shipping_for_package( $package );
}
/**
* Cache the package rates calculated by @see WC_Shipping::calculate_shipping_for_package() to avoid multiple calls of calculate_shipping_for_package() per request.
*
* @deprecated 5.4.0
*
* @param array $rates A set of WC_Shipping_Rate objects.
* @param array $package A shipping package in the form returned by @see WC_Cart->get_shipping_packages()
* @return array $rates An unaltered set of WC_Shipping_Rate objects passed to the function
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.18
*/
public static function cache_package_rates( $rates, $package ) {
_deprecated_function( __METHOD__, 'subscriptions-core 5.4.0' );
return $rates;
}
/**
* Don't allow new subscription products to be added to the cart if it contains a subscription renewal already.
*
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.6.0
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function check_valid_add_to_cart( $is_valid, $product_id, $quantity, $variation_id = '', $variations = array(), $item_data = array() ) {
_deprecated_function( __METHOD__, '2.6.0', 'WC_Subscriptions_Cart_Validator::check_valid_add_to_cart' );
if ( $is_valid && ! isset( $item_data['subscription_renewal'] ) && wcs_cart_contains_renewal() && WC_Subscriptions_Product::is_subscription( $product_id ) ) {
wc_add_notice( __( 'That subscription product can not be added to your cart as it already contains a subscription renewal.', 'woocommerce-subscriptions' ), 'error' );
$is_valid = false;
}
return $is_valid;
}
/**
* Make sure cart totals are calculated when the cart widget is populated via the get_refreshed_fragments() method
* so that @see self::get_formatted_cart_subtotal() returns the correct subtotal price string.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5.11
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.0
*/
public static function pre_get_refreshed_fragments() {
wcs_deprecated_function( __METHOD__, '2.5.0' );
if ( defined( 'DOING_AJAX' ) && true === DOING_AJAX && ! defined( 'WOOCOMMERCE_CART' ) ) {
define( 'WOOCOMMERCE_CART', true );
WC()->cart->calculate_totals();
}
}
/**
* Checks the cart to see if it contains a subscription product renewal.
*
* Returns the cart_item containing the product renewal, else false.
*
* @param string $role The role of the cart item to check for.
* @return array|false The cart item containing the renewal, else false.
*
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.3
*/
public static function cart_contains_subscription_renewal( $role = '' ) {
_deprecated_function( __METHOD__, '2.0', 'wcs_cart_contains_renewal( $role )' );
return wcs_cart_contains_renewal();
}
/**
* Checks the cart to see if it contains a subscription product renewal.
*
* Returns the cart_item containing the product renewal, else false.
*
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4
*/
public static function cart_contains_failed_renewal_order_payment() {
_deprecated_function( __METHOD__, '2.0', 'wcs_cart_contains_failed_renewal_order_payment()' );
return wcs_cart_contains_failed_renewal_order_payment();
}
/**
* Restore renewal flag when cart is reset and modify Product object with
* renewal order related info
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.3
*/
public static function get_cart_item_from_session( $session_data, $values, $key ) {
_deprecated_function( __METHOD__, '2.0', 'WCS_Cart_Renewal::get_cart_item_from_session( $session_data, $values, $key )' );
}
/**
* For subscription renewal via cart, use original order discount
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.3
*/
public static function before_calculate_totals( $cart ) {
_deprecated_function( __METHOD__, '2.0', 'WCS_Cart_Renewal::set_renewal_discounts( $cart )' );
}
/**
* For subscription renewal via cart, previously adjust item price by original order discount
*
* No longer required as of 1.3.5 as totals are calculated correctly internally.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.3
*/
public static function get_discounted_price_for_renewal( $price, $values, $cart ) {
_deprecated_function( __METHOD__, '2.0', 'WCS_Cart_Renewal::get_discounted_price_for_renewal( $price, $values, $cart )' );
}
/**
* Returns a string with the cart discount and subscription period.
*
* @return mixed formatted price or false if there are none
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_formatted_discounts_before_tax( $discount, $cart ) {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
return $discount;
}
/**
* Gets the order discount amount - these are applied after tax
*
* @return mixed formatted price or false if there are none
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_formatted_discounts_after_tax( $discount, $cart ) {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
return $discount;
}
/**
* Returns an individual coupon's formatted discount amount for WooCommerce 2.1+
*
* @param string $discount_html String of the coupon's discount amount
* @param string $coupon WC_Coupon object for the coupon to which this line item relates
* @return string formatted subscription price string if the cart includes a coupon being applied to recurring amount
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4.6
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function cart_coupon_discount_amount_html( $discount_html, $coupon ) {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
return $discount_html;
}
/**
* Returns individual coupon's formatted discount amount for WooCommerce 2.1+
*
* @param string $cart_totals_fee_html String of the coupon's discount amount
* @param string $fee WC_Coupon object for the coupon to which this line item relates
* @return string formatted subscription price string if the cart includes a coupon being applied to recurring amount
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4.6
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function cart_totals_fee_html( $cart_totals_fee_html, $fee ) {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
return $cart_totals_fee_html;
}
/**
* Includes the sign-up fee total in the cart total (after calculation).
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5.10
* @return string formatted price
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_formatted_cart_total( $cart_contents_total ) {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
return $cart_contents_total;
}
/**
* Includes the sign-up fee subtotal in the subtotal displayed in the cart.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_formatted_cart_subtotal( $cart_subtotal, $compound, $cart ) {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
return $cart_subtotal;
}
/**
* Returns an array of taxes merged by code, formatted with recurring amount ready for output.
*
* @return array Array of tax_id => tax_amounts for items in the cart
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.3.5
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_recurring_tax_totals( $tax_totals, $cart ) {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
return apply_filters( 'woocommerce_cart_recurring_tax_totals', $tax_totals, $cart );
}
/**
* Returns a string of the sum of all taxes in the cart for initial payment and
* recurring amount.
*
* @return array Array of tax_id => tax_amounts for items in the cart
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4.10
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_taxes_total_html( $total ) {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
return $total;
}
/**
* Appends the cart subscription string to a cart total using the @see self::get_cart_subscription_string and then returns it.
*
* @return string Formatted subscription price string for the cart total.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_formatted_total( $total ) {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
return $total;
}
/**
* Appends the cart subscription string to a cart total using the @see self::get_cart_subscription_string and then returns it.
*
* @return string Formatted subscription price string for the cart total.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_formatted_total_ex_tax( $total_ex_tax ) {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
return $total_ex_tax;
}
/**
* Returns an array of the recurring total fields
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_recurring_totals_fields() {
_deprecated_function( __METHOD__, '2.0', 'recurring total values stored in WC()->cart->recurring_carts' );
return array();
}
/**
* Gets the subscription period from the cart and returns it as an array (eg. array( 'month', 'day' ) )
*
* Deprecated because a cart can now contain multiple subscription products, so there is no single period for the entire cart.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_cart_subscription_period() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$period = '';
if ( self::cart_contains_subscription() ) {
foreach ( WC()->cart->cart_contents as $cart_item ) {
if ( WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) {
$period = WC_Subscriptions_Product::get_period( $cart_item['data'] );
break;
}
}
}
return apply_filters( 'woocommerce_subscriptions_cart_period', $period );
}
/**
* Gets the subscription period from the cart and returns it as an array (eg. array( 'month', 'day' ) )
*
* Deprecated because a cart can now contain multiple subscription products, so there is no single interval for the entire cart.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_cart_subscription_interval() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$interval = 0;
foreach ( WC()->cart->cart_contents as $cart_item ) {
if ( WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) {
$interval = WC_Subscriptions_Product::get_interval( $cart_item['data'] );
break;
}
}
return apply_filters( 'woocommerce_subscriptions_cart_interval', $interval );
}
/**
* Gets the subscription length from the cart and returns it as an array (eg. array( 'month', 'day' ) )
*
* Deprecated because a cart can now contain multiple subscription products, so there is no single length for the entire cart.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.1
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_cart_subscription_length() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$length = 0;
if ( self::cart_contains_subscription() ) {
foreach ( WC()->cart->cart_contents as $cart_item ) {
if ( WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) {
$length = WC_Subscriptions_Product::get_length( $cart_item['data'] );
break;
}
}
}
return apply_filters( 'woocommerce_subscriptions_cart_length', $length );
}
/**
* Gets the subscription length from the cart and returns it as an array (eg. array( 'month', 'day' ) )
*
* Deprecated because a cart can now contain multiple subscription products, so there is no single trial length for the entire cart.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.1
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_cart_subscription_trial_length() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$trial_length = 0;
if ( self::cart_contains_subscription() ) {
foreach ( WC()->cart->cart_contents as $cart_item ) {
if ( WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) {
$trial_length = WC_Subscriptions_Product::get_trial_length( $cart_item['data'] );
break;
}
}
}
return apply_filters( 'woocommerce_subscriptions_cart_trial_length', $trial_length );
}
/**
* Gets the subscription trial period from the cart and returns it as an array (eg. array( 'month', 'day' ) )
*
* Deprecated because a cart can now contain multiple subscription products, so there is no single trial period for the entire cart.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_cart_subscription_trial_period() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$trial_period = '';
// Get the original trial period
if ( self::cart_contains_subscription() ) {
foreach ( WC()->cart->cart_contents as $cart_item ) {
if ( WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) {
$trial_period = WC_Subscriptions_Product::get_trial_period( $cart_item['data'] );
break;
}
}
}
return apply_filters( 'woocommerce_subscriptions_cart_trial_period', $trial_period );
}
/**
* Get tax row amounts with or without compound taxes includes
*
* Deprecated because the cart can now contain subscriptions on multiple billing schedules so there is no one "total"
*
* @return float price
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_recurring_cart_contents_total() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$recurring_total = 0;
foreach ( WC()->cart->recurring_carts as $cart ) {
if ( ! $cart->prices_include_tax ) {
$recurring_total += $cart->cart_contents_total;
} else {
$recurring_total += $cart->cart_contents_total + $cart->tax_total;
}
}
return $recurring_total;
}
/**
* Returns the proportion of cart discount that is recurring for the product specified with $product_id
*
* Deprecated because the cart can now contain subscriptions on multiple billing schedules so there is no one "total"
*
* @return double The total recurring item subtotal amount less tax for items in the cart.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function get_recurring_subtotal_ex_tax() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$recurring_total = 0;
foreach ( WC()->cart->recurring_carts as $cart ) {
$recurring_total += $cart->subtotal_ex_tax;
}
return $recurring_total;
}
/**
* Returns the proportion of cart discount that is recurring for the product specified with $product_id
*
* Deprecated because the cart can now contain subscriptions on multiple billing schedules so there is no one "total"
*
* @return double The total recurring item subtotal amount for items in the cart.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function get_recurring_subtotal() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$recurring_total = 0;
foreach ( WC()->cart->recurring_carts as $cart ) {
$recurring_total += $cart->subtotal;
}
return $recurring_total;
}
/**
* Returns the proportion of cart discount that is recurring for the product specified with $product_id
*
* Deprecated because the cart can now contain subscriptions on multiple billing schedules so there is no one "total"
*
* @return double The total recurring cart discount amount for items in the cart.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function get_recurring_discount_cart() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$recurring_total = 0;
foreach ( WC()->cart->recurring_carts as $cart ) {
$recurring_total += $cart->discount_cart;
}
return $recurring_total;
}
/**
* Returns the cart discount tax amount for WC 2.3 and newer
*
* Deprecated because the cart can now contain subscriptions on multiple billing schedules so there is no one "total"
*
* @return double
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_recurring_discount_cart_tax() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$recurring_total = 0;
foreach ( WC()->cart->recurring_carts as $cart ) {
$recurring_total += $cart->discount_cart_tax;
}
return $recurring_total;
}
/**
* Returns the proportion of total discount that is recurring for the product specified with $product_id
*
* Deprecated because the cart can now contain subscriptions on multiple billing schedules so there is no one "total"
*
* @return double The total recurring discount amount for items in the cart.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function get_recurring_discount_total() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$recurring_total = 0;
foreach ( WC()->cart->recurring_carts as $cart ) {
$recurring_total += $cart->discount_total;
}
return $recurring_total;
}
/**
* Returns the amount of shipping tax that is recurring. As shipping only applies
* to recurring payments, and only 1 subscription can be purchased at a time,
* this is equal to @see WC_Cart::$shipping_tax_total
*
* Deprecated because the cart can now contain subscriptions on multiple billing schedules so there is no one "total"
*
* @return double The total recurring shipping tax amount for items in the cart.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function get_recurring_shipping_tax_total() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$recurring_total = 0;
foreach ( WC()->cart->recurring_carts as $cart ) {
$recurring_total += $cart->shipping_tax_total;
}
return $recurring_total;
}
/**
* Returns the recurring shipping price . As shipping only applies to recurring
* payments, and only 1 subscription can be purchased at a time, this is
* equal to @see WC_Cart::shipping_total
*
* Deprecated because the cart can now contain subscriptions on multiple billing schedules so there is no one "total"
*
* @return double The total recurring shipping amount for items in the cart.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function get_recurring_shipping_total() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$recurring_total = 0;
foreach ( WC()->cart->recurring_carts as $cart ) {
$recurring_total += $cart->shipping_total;
}
return $recurring_total;
}
/**
* Returns an array of taxes on an order with their recurring totals.
*
* Deprecated because the cart can now contain subscriptions on multiple billing schedules so there is no one "total"
*
* @return array Array of tax_id => tax_amounts for items in the cart
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function get_recurring_taxes() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$taxes = array();
$recurring_fees = array();
foreach ( WC()->cart->recurring_carts as $cart ) {
foreach ( array_keys( $cart->taxes + $cart->shipping_taxes ) as $key ) {
$taxes[ $key ] = ( isset( $cart->shipping_taxes[ $key ] ) ? $cart->shipping_taxes[ $key ] : 0 ) + ( isset( $cart->taxes[ $key ] ) ? $cart->taxes[ $key ] : 0 );
}
}
return $taxes;
}
/**
* Returns an array of recurring fees.
*
* Deprecated because the cart can now contain subscriptions on multiple billing schedules so there is no one "total"
*
* @return array Array of fee_id => fee_details for items in the cart
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4.9
*/
public static function get_recurring_fees() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$recurring_fees = array();
foreach ( WC()->cart->recurring_carts as $cart ) {
$recurring_fees = array_merge( $recurring_fees, $cart->get_fees() );
}
return $recurring_fees;
}
/**
* Get tax row amounts with or without compound taxes includes
*
* Deprecated because the cart can now contain subscriptions on multiple billing schedules so there is no one "total"
*
* @return double The total recurring tax amount tax for items in the cart (maybe not including compound taxes)
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function get_recurring_taxes_total( $compound = true ) {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$recurring_total = 0;
foreach ( WC()->cart->recurring_carts as $cart ) {
foreach ( $cart->taxes as $key => $tax ) {
if ( ! $compound && WC_Tax::is_compound( $key ) ) {
continue;
}
$recurring_total += $tax;
}
foreach ( $cart->shipping_taxes as $key => $tax ) {
if ( ! $compound && WC_Tax::is_compound( $key ) ) {
continue;
}
$recurring_total += $tax;
}
}
return $recurring_total;
}
/**
* Returns the proportion of total tax on an order that is recurring for the product specified with $product_id
*
* Deprecated because the cart can now contain subscriptions on multiple billing schedules so there is no one "total"
*
* @return double The total recurring tax amount tax for items in the cart.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function get_recurring_total_tax() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$recurring_total = 0;
foreach ( WC()->cart->recurring_carts as $cart ) {
$recurring_total += $cart->tax_total;
}
return $recurring_total;
}
/**
* Returns the proportion of total before tax on an order that is recurring for the product specified with $product_id
*
* Deprecated because the cart can now contain subscriptions on multiple billing schedules so there is no one "total"
*
* @return double The total recurring amount less tax for items in the cart.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function get_recurring_total_ex_tax() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
return self::get_recurring_total() - self::get_recurring_total_tax() - self::get_recurring_shipping_tax_total();
}
/**
* Returns the price per period for a subscription in an order.
*
* Deprecated because the cart can now contain subscriptions on multiple billing schedules so there is no one "total"
*
* @return double The total recurring amount for items in the cart.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
*/
public static function get_recurring_total() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$recurring_total = 0;
foreach ( WC()->cart->recurring_carts as $cart ) {
$recurring_total += $cart->get_total();
}
return $recurring_total;
}
/**
* Calculate the total amount of recurring shipping needed. Removes any item from the calculation that
* is not a subscription and calculates the totals.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function calculate_recurring_shipping() {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
$recurring_total = 0;
foreach ( WC()->cart->recurring_carts as $cart ) {
$recurring_total = $cart->shipping_total;
}
return $recurring_total;
}
/**
* Creates a string representation of the subscription period/term for each item in the cart
*
* @param string $initial_amount The initial amount to be displayed for the subscription as passed through the @see woocommerce_price() function.
* @param float $recurring_amount The price to display in the subscription.
* @param array $args (optional) Flags to customise to display the trial and length of the subscription. Default to false - don't display.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function get_cart_subscription_string( $initial_amount, $recurring_amount, $args = array() ) {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
if ( ! is_array( $args ) ) {
_deprecated_argument( __CLASS__ . '::' . __FUNCTION__, '1.4', 'Third parameter is now an array of name => value pairs. Use array( "include_lengths" => true ) instead.' );
$args = array(
'include_lengths' => $args,
);
}
$args = wp_parse_args(
$args,
array(
'include_lengths' => false,
'include_trial' => true,
)
);
$subscription_details = array(
'initial_amount' => $initial_amount,
'initial_description' => __( 'now', 'woocommerce-subscriptions' ),
'recurring_amount' => $recurring_amount,
'subscription_interval' => self::get_cart_subscription_interval(),
'subscription_period' => self::get_cart_subscription_period(),
'trial_length' => self::get_cart_subscription_trial_length(),
'trial_period' => self::get_cart_subscription_trial_period(),
);
$is_one_payment = self::get_cart_subscription_length() > 0 && self::get_cart_subscription_length() == self::get_cart_subscription_interval();
// Override defaults when subscription is for one billing period
if ( $is_one_payment ) {
$subscription_details['subscription_length'] = self::get_cart_subscription_length();
} else {
if ( true === $args['include_lengths'] ) {
$subscription_details['subscription_length'] = self::get_cart_subscription_length();
}
if ( false === $args['include_trial'] ) {
$subscription_details['trial_length'] = 0;
}
}
$initial_amount_string = ( is_numeric( $subscription_details['initial_amount'] ) ) ? wc_price( $subscription_details['initial_amount'] ) : $subscription_details['initial_amount'];
$recurring_amount_string = ( is_numeric( $subscription_details['recurring_amount'] ) ) ? wc_price( $subscription_details['recurring_amount'] ) : $subscription_details['recurring_amount'];
// Don't show up front fees when there is no trial period and no sign up fee and they are the same as the recurring amount
if ( self::get_cart_subscription_trial_length() == 0 && self::get_cart_subscription_sign_up_fee() == 0 && $initial_amount_string == $recurring_amount_string ) {
$subscription_details['initial_amount'] = '';
} elseif ( wc_price( 0 ) == $initial_amount_string && false === $is_one_payment && self::get_cart_subscription_trial_length() > 0 ) { // don't show $0.00 initial amount (i.e. a free trial with no non-subscription products in the cart) unless the recurring period is the same as the billing period
$subscription_details['initial_amount'] = '';
}
// Include details of a synced subscription in the cart
if ( $synchronised_cart_item = WC_Subscriptions_Synchroniser::cart_contains_synced_subscription() ) {
$subscription_details += array(
'is_synced' => true,
'synchronised_payment_day' => WC_Subscriptions_Synchroniser::get_products_payment_day( $synchronised_cart_item['data'] ),
);
}
$subscription_details = apply_filters( 'woocommerce_cart_subscription_string_details', $subscription_details, $args );
$subscription_string = wcs_price_string( $subscription_details );
return $subscription_string;
}
/**
* Uses the a subscription's combined price total calculated by WooCommerce to determine the
* total price that should be charged per period.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
public static function set_calculated_total( $total ) {
_deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' );
return $total;
}
/**
* Get the recurring amounts values from the session
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0
*/
public static function get_cart_from_session() {
_deprecated_function( __METHOD__, '2.0' );
}
/**
* Store the sign-up fee cart values in the session
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0
*/
public static function set_session() {
_deprecated_function( __METHOD__, '2.0' );
}
/**
* Reset the sign-up fee fields in the current session
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0
*/
public static function reset() {
_deprecated_function( __METHOD__, '2.0' );
}
/**
* Returns a cart item's product ID. For a variation, this will be a variation ID, for a simple product,
* it will be the product's ID.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5
*/
public static function get_items_product_id( $cart_item ) {
_deprecated_function( __METHOD__, '2.0', 'wcs_get_canonical_product_id( $cart_item )' );
return wcs_get_canonical_product_id( $cart_item );
}
/**
* Store how much discount each coupon grants.
*
* @param mixed $code
* @param mixed $amount
* @return void
*/
public static function increase_coupon_discount_amount( $code, $amount ) {
_deprecated_function( __METHOD__, '2.0', 'WC_Subscriptions_Coupon::increase_coupon_discount_amount( WC()->cart, $code, $amount )' );
if ( empty( WC()->cart->coupon_discount_amounts[ $code ] ) ) {
WC()->cart->coupon_discount_amounts[ $code ] = 0;
}
if ( 'recurring_total' != self::$calculation_type ) {
WC()->cart->coupon_discount_amounts[ $code ] += $amount;
}
}
/**
* Don't display shipping prices if the initial order won't require shipping (i.e. all the products in the cart are subscriptions with a free trial or synchronised to a date in the future)
*
* @return string Label for a shipping method
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.3
*/
public static function get_cart_shipping_method_full_label( $label, $method ) {
_deprecated_function( __METHOD__, '2.0.12' );
if ( ! self::charge_shipping_up_front() ) {
$label = $method->label;
}
return $label;
}
/**
* One time shipping can null the need for shipping needs. WooCommerce treats that as no need to ship, therefore it will call
* WC()->shipping->reset() on it, which will wipe the preferences saved. That can cause the chosen shipping method for the one
* time shipping feature to be lost, and the first default to be applied instead. To counter that, we save the chosen shipping
* method to a key that's not going to get wiped by WC's method, and then later restore it.
*
* @deprecated 7.3.0 - no longer in use internally
*/
public static function maybe_restore_chosen_shipping_method() {
wcs_deprecated_function( __METHOD__, '7.3.0', 'The use of this function is no longer recommended and will be removed in a future version.' );
$chosen_shipping_method_cache = WC()->session->get( 'wcs_shipping_methods', false );
if ( false !== $chosen_shipping_method_cache ) {
WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_method_cache );
}
}
/**
* Return a localized free trial period string.
*
* @param int $number An interval in the range 1-6
* @param string $period One of day, week, month or year.
*/
public static function format_free_trial_period( $number, $period ) {
if ( 'day' === $period ) {
// translators: placeholder is a number of days.
return sprintf( _n( '%s day', '%s days', $number, 'woocommerce-subscriptions' ), $number );
}
if ( 'week' === $period ) {
// translators: placeholder is a number of weeks.
return sprintf( _n( '%s week', '%s weeks', $number, 'woocommerce-subscriptions' ), $number );
}
if ( 'month' === $period ) {
// translators: placeholder is a number of months.
return sprintf( _n( '%s month', '%s months', $number, 'woocommerce-subscriptions' ), $number );
}
if ( 'year' === $period ) {
// translators: placeholder is a number of years.
return sprintf( _n( '%s year', '%s years', $number, 'woocommerce-subscriptions' ), $number );
}
return '';
}
/**
* Return a localized sync string, copied from WC_Subscriptions_Product::get_price_string
*
* @param WC_Product_Subscription $product The synced product.
* @param string $period One of day, week, month or year.
* @param int $interval An interval in the range 1-6
* @return string The new sync string.
*/
public static function format_sync_period( $product, string $period, int $interval ) {
global $wp_locale;
$payment_day = WC_Subscriptions_Synchroniser::get_products_payment_day( $product );
switch ( $period ) {
case 'week':
$payment_day_of_week = WC_Subscriptions_Synchroniser::get_weekday( $payment_day );
if ( 1 === $interval ) {
// translators: 1$: day of the week (e.g. "every Wednesday").
return sprintf( __( 'every %1$s', 'woocommerce-subscriptions' ), $payment_day_of_week );
} else {
return sprintf(
// translators: 1$: period, 2$: day of the week (e.g. "every 2nd week on Wednesday").
__( 'every %1$s on %2$s', 'woocommerce-subscriptions' ),
wcs_get_subscription_period_strings( $interval,$period ),
$payment_day_of_week
);
}
break;
case 'month':
if ( 1 === $interval ) {
if ( $payment_day > 27 ) {
return __( 'on the last day of each month', 'woocommerce-subscriptions' );
} else {
return sprintf(
// translators: 1$: day of the month (e.g. "23rd") (e.g. "every 23rd of each month").
__( 'on the %1$s of each month', 'woocommerce-subscriptions' ),
wcs_append_numeral_suffix( $payment_day )
);
}
} else {
if ( $payment_day > 27 ) {
return sprintf(
// translators: 1$: interval (e.g. "3rd") (e.g. "on the last day of every 3rd month").
__( 'on the last day of every %1$s month', 'woocommerce-subscriptions' ),
wcs_append_numeral_suffix( $interval )
);
} else {
return sprintf(
// translators: on the, 1$: <date> day of every, 2$: <interval> month (e.g. "on the 23rd day of every 2nd month").
__( 'on the %1$s day of every %2$s month', 'woocommerce-subscriptions' ),
wcs_append_numeral_suffix( $payment_day ),
wcs_append_numeral_suffix( $interval )
);
}
}
break;
case 'year':
if ( 1 === $interval ) {
return sprintf(
// translators: on, 1$: <date>, 2$: <month> each year (e.g. "on March 15th each year").
__( 'on %1$s %2$s each year', 'woocommerce-subscriptions' ),
$wp_locale->month[ $payment_day['month'] ],
wcs_append_numeral_suffix( $payment_day['day'] )
);
} else {
return sprintf(
// translators: 1$: month (e.g. "March"), 2$: day of the month (e.g. "23rd), 3$: interval year (r.g March 23rd every 2nd year").
__( 'on %1$s %2$s every %3$s year', 'woocommerce-subscriptions' ),
$wp_locale->month[ $payment_day['month'] ],
wcs_append_numeral_suffix( $payment_day['day'] ),
wcs_append_numeral_suffix( $interval )
);
}
break;
}
return '';
}
/**
* Adds meta data so it can be displayed in the Cart.
*/
public static function woocommerce_get_item_data( $other_data, $cart_item ) {
$product = $cart_item['data'];
if ( ! WC_Subscriptions_Product::is_subscription( $product ) ) {
return $other_data;
}
$trial_length = WC_Subscriptions_Product::get_trial_length( $product );
if ( $trial_length ) {
$other_data[] = array(
'name' => __( 'Free trial', 'woocommerce-subscriptions' ),
'value' => self::format_free_trial_period( $trial_length, WC_Subscriptions_Product::get_trial_period( $product ) ),
'hidden' => true,
'__experimental_woocommerce_blocks_hidden' => false,
);
}
$sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $product );
if ( $sign_up_fee ) {
$other_data[] = array(
'name' => __( 'Sign up fee', 'woocommerce-subscriptions' ),
'value' => wc_price( $sign_up_fee ),
'hidden' => true,
'__experimental_woocommerce_blocks_hidden' => false,
);
}
$synchronised_cart_item = WC_Subscriptions_Synchroniser::is_product_synced( $product );
if ( $synchronised_cart_item ) {
$other_data[] = array(
'name' => __( 'Renews', 'woocommerce-subscriptions' ),
'value' => self::format_sync_period( $product, WC_Subscriptions_Product::get_period( $product ), WC_Subscriptions_Product::get_interval( $product ) ),
'hidden' => true,
'__experimental_woocommerce_blocks_hidden' => false,
);
}
return $other_data;
}
/**
* Parse recurring shipping rates from the front end and put them into the $_POST['shipping_method'] used by WooCommerce.
*
* When WooCommerce takes the value of inputs for shipping methods selection from the cart and checkout pages, it uses a
* JavaScript array and therefore, can only use numerical indexes. This works for WC core, because it only needs shipping
* selection for different packages. However, we want to use string indexes to differentiate between different recurring
* cart shipping selection inputs *and* packages. To do this, we need to get our shipping methods from the $_POST['post_data']
* values and manually add them $_POST['shipping_method'] array.
*
* We can't do this on the cart page unfortunately because it doesn't pass the entire forms post data and instead only
* sends the shipping methods with a numerical index.
*
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v3.1.0
* @return void
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.12
*/
public static function add_shipping_method_post_data() {
wcs_deprecated_function( __METHOD__, '3.1.0' );
if ( ! wcs_is_woocommerce_pre( '2.6' ) ) {
return;
}
check_ajax_referer( 'update-order-review', 'security' );
parse_str( wc_clean( wp_unslash( $_POST['post_data'] ) ), $form_data );
// In case we have only free trials/sync'd products in the cart and shipping methods aren't being displayed
if ( ! isset( $_POST['shipping_method'] ) ) {
$_POST['shipping_method'] = array();
}
if ( ! isset( $form_data['shipping_method'] ) ) {
$form_data['shipping_method'] = array();
}
foreach ( $form_data['shipping_method'] as $key => $methods ) {
if ( ! is_numeric( $key ) && ! array_key_exists( $key, $_POST['shipping_method'] ) ) {
$_POST['shipping_method'][ $key ] = $methods;
}
}
}
/**
* When WooCommerce calculates rates for a recurring shipping package, we need to make sure there is a
* different number of rates to make sure WooCommerce updates the chosen method for the recurring cart
* and the 'woocommerce_shipping_chosen_method' filter is called, which we use to make sure the chosen
* method is the recurring method, not the initial method.
*
* This function is hooked to 'woocommerce_shipping_packages' called by WC_Shipping->calculate_shipping()
* which is why it accepts and returns the $packages array. It is also attached with a very high priority
* to avoid conflicts with any 3rd party plugins that may use the method count session value (only a couple
* of other hooks, including 'woocommerce_shipping_chosen_method' and 'woocommerce_shipping_method_chosen'
* are triggered between when this callback runs on 'woocommerce_shipping_packages' and when the session
* value is set again by WC_Shipping->calculate_shipping()).
*
* For more details, see: https://github.com/Prospress/woocommerce-subscriptions/pull/1187#issuecomment-186091152
*
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v3.1.0
*
* @param array $packages An array of shipping package of the form returned by WC_Cart->get_shipping_packages() which includes the package's contents, cost, customer, destination and alternative rates
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.19
*/
public static function reset_shipping_method_counts( $packages ) {
wcs_deprecated_function( __METHOD__, '3.1.0' );
if ( 'none' !== self::$recurring_cart_key ) {
WC()->session->set( 'shipping_method_counts', array() );
}
return $packages;
}
/**
* Checks to see if payment method is required on a subscription product with a $0 initial payment.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.0
* @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v4.0.0
*/
public static function zero_initial_payment_requires_payment() {
wcs_deprecated_function( __METHOD__, '4.0.0', 'WCS_Zero_Initial_Payment_Checkout_Manager::zero_initial_checkout_requires_payment() if available.' );
if ( class_exists( 'WCS_Zero_Initial_Payment_Checkout_Manager' ) ) {
return WCS_Zero_Initial_Payment_Checkout_Manager::zero_initial_checkout_requires_payment();
}
return true;
}
}