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 ) . ' ' . WC()->countries->inc_tax_or_vat() . ''; } 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 ) . ' ' . WC()->countries->ex_tax_or_vat() . ''; } $product_subtotal = '' . $product_subtotal . ''; } 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 ) { if ( empty( WC()->cart->cart_contents ) || ! WC_Subscriptions_Product::is_subscription( $product_id ) ) { return false; } foreach ( WC()->cart->cart_contents as $cart_item ) { $item_product_id = wcs_get_canonical_product_id( $cart_item ); $is_subscription = isset( $item_product_id ) ? WC_Subscriptions_Product::is_subscription( $item_product_id ) : false; if ( $item_product_id !== $product_id && $is_subscription ) { return true; } } // Return false because no other subscription product was found in the cart. return false; } /** * 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$: day of every, 2$: 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$: , 2$: 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; } }