This commit is contained in:
Prospress Inc
2017-03-23 15:06:36 +01:00
committed by Remco Tolsma
parent 5f3551468f
commit 643ec33376
48 changed files with 1275 additions and 640 deletions

View File

@@ -19,3 +19,9 @@
.shipping.recurring-total ul .amount {
font-weight: 700;
}
.woocommerce-page table.shop_table_responsive tbody .recurring-totals th {
display:table-cell;
}
.woocommerce-page table.shop_table_responsive tr.recurring-total td:not([data-title]):before {
content:"";
}

View File

@@ -1,5 +1,45 @@
*** WooCommerce Subscriptions Changelog ***
2017.03.06 - version 2.1.4
* Tweak: Lengthen PayPal IPN lock to 5 days to prevent creating duplicate renewal orders when a fatal error occurs in processing the IPN. Prevoiusly the lock was set to 5 minutes, meaning on the 2nd IPN would be ignored. Given fatal errors will rarely be fixed within 5 minutes, Subscriptions will now ignore 5 days worth of IPN retries.(PR#1838
* Tweak: Log exceptions caught while updating subscription statuses to make it easier to diagnose issues with 3rd party code and hosting environment that occur during status transitions. PR#1842
* Tweak: Remove lingering compatibility code for WooCommerce prior to version 2.4 as version 2.3 and older have not been supported since v2.1. PR#1750
* Fix: Changing payment method on manually added subscriptions. When manually creating a subscription via the WooCommerce > Add Subscription administration screen, make sure the order key meta data is set so its available when changing payment methods on that subscription. PR#1849
* Fix: Make sure customer and admin retry emails are always sent after the initial retry by making sure the WooCommerce mailer has be setup. PR#1840
* Fix: Do not suspend subscriptions using PayPal Standard as the payment method when switching using PayPal Reference Transactions as the payment method. PR#1831
* Fix: Calculate correct next payment date for subscriptions suspended for more than 30 billing periods e.g. 1 month for a daily subscription, 2.5 years for a month subscription etc. PR#1846
* Fix: Do not update the status of a failed or pending renewal order when changing the payment method on a subscription. PR#1720
* Fix: Make sure free shipping is available as a recurring shipping method for subscription products with a free trial or synced to a date in the future if the recurring total exceeds the Minimum Order Amount required for free shipping. PR#1830
* Fix: Do not incorrectly double slash some JavaScript file URLs. Fixes JavaScript on the WooCommerce > Edit Subscription administration screen with some hosts. PR#1853
* Fix: Do not copy backorder line item meta data to subscriptions or renewal orders for in-stock products. PR#1855
* Fix: Do not apply dynamic discounts from the WooCommerce Dynamic Pricing extension to renewal or resubcribe carts to avoid discounts being compounded when manually renewing or resubscribing. PR#1852
* Fix: Do not switch to staging mode when site is using the WP_SITEURL constant to set the site URL to a value that differs to the URL stored in the database. Uses WP_SITEURL constant now for site url when it is set, and get_site_url() only its not set. PR#1717
* Fix: Correct typo on the Customer Suspensions tooltip on the WooCommerce > Settings > Subscriptions > Miscellaneous administration screen. PR#1868
* Fix: Only setup automatic payments after switching if automatic payments are enabled. PR#1865
2017.01.24 - version 2.1.3
* Tweak: Set renewal order's status to 'pending' before payment retry to make sure that the status change hooks are triggered by WC_Order::update_status(). Fixes compatibility with custom retry rules that use an order status other than pending. (PR#1818)
* Tweak: Automatically transition resubscribe orders for $0 to 'complete' rather than 'processing', because the existing subscription had the pending-cancellation status and the new subscriptions first renewal will be when the first shipment is due to be sent. (PR#1796)
* Tweak: Update Action Scheduler to v1.5.2. (PR#1773)
* Tweak: Exclude Action Scheduler comments from comments RSS feed. (PR#1773 & Prospress/action-scheduler#66)
* Fix: Never reschedule single actions in Action Scheduler, even if the action has a scheduled date in the future. Fixes a duplicate payment processed when PHP's default timezone was being set to a time other than UTC, and Subscriptions < 2.1 scheduled the next payment in that timezone, then when the payment becomes due, PHP's default timezone has been changed back to UTC. (PR#1773 & Prospress/action-scheduler#69)
* Fix: Update renewal order line item cart data when renewal line item IDs are updated. Fixes assorted PHP warnings and notices that resulted from outdated item IDs, including "Division by zero" and "Undefined offset" notices. (PR#1814)
* Fix: Cancel & trash retries when corresponding renewal order is trashed/deleted to make sure the retry is not attempted (with no ill effect). (PR#1768)
* Fix: Undefined variable notice on occasion when viewing WooCommerce > Add Order screen when the retry system is enabled. (PR#1803)
* Fix: Display correct "New Subscription Details" in emails sent when switching (upgrading/downgrading) a subscription. (PR#1819)
* Fix: Responsiveness of recurring totals sections of checkout table. (PR#1809)
2016.12.12 - version 2.1.2
* Tweak: Reword the description on the Disable Automatic Payments option to make it clear it does not change existing subscriptions. (PR#1771)
* Fix: Renewal issues with servers running Memcached, which does not handle expiration times greater than 30 days, by no longer using TLC transients, which sets all transient expiration dates to 1 year in future. (PR#1776)
* Fix: Do not require InnoDB/SQL transactions for switching. Fixes "The original subscription item being switched cannot be found" errors when a customer attempts to upgrade or downgrade on servers without InnoDB active. (PR#1779)
* Fix: Pass entire product object from cart to check whether it is a subscription when removing subscription products from the cart. Fixes compatibility with 3rd party extensions with custom subscription product types. (PR#1778)
* Fix: Make sure all 'woocommerce_valid_order_statuses_for_payment' calls include an order object as the 2nd parameter for callbacks to match current WooCommerce behaviour. (PR#1789)
* Fix: Incorrect template displayed when browsing to a page with URL /subscriptions/ when using WooCommerce prior to version 2.6. (PR#1660)
* Fix: Do not add '_synced' to recurring cart keys if 3rd party code is calling calculate totals again, only add it once. (PR#1780)
* Fix: Display "Retry Payment" option in the "Order Actions" menu if the payment is due, even if the order has a status applied by customer Retry Rules. (PR#1767)
* Fix: Compatibility with switching and WPML. (PR#1780)
2016.11.26 - version 2.1.1
* Tweak: Delete Subscriptions transients when using the "Clear Transients" tool via the WooCommerce > System Status > Tools administration screen. (PR#1758)
* Tweak: Avoid infinite loops with old versions of the Shipping Multiple Addresses extension and potentially other extension that may manually instantiate the WC_Checkout object, which is normally a singleton accessed via WC()->checkout, WC()->checkout or WC_Checkout::instance(). (PR#1757)
@@ -694,10 +734,10 @@
* Tweak: Reinstate enctype='multipart/form-data' on the subscription add to cart form to fix compatibilty with file uploads via Product Addons extension (related to http://docs.woocommerce.com/document/subscriptions/faq/#section-45)
* Tweak: Only output P tags for product meta on My Subscriptions table when product has meta
* Tweak: add enable/disable field for the Customer Renewal Invoice email
* Fix: set correct order date and ID in renewal order emails' subject & heading when sending more than one email in the same request (workaround until woothemes/woocommerce#5168 is implemented in WC 2.2)
* Fix: set correct order date and ID in renewal order emails' subject & heading when sending more than one email in the same request (workaround until woocommerce/woocommerce#5168 is implemented in WC 2.2)
* Fix: correctly close P tag when display product meta data on the My Subscriptions table
* Fix: display correct tax and shipping total on cart page when using Local Pickup shipping method and estimating shipping with the "Calculate Shipping" method
* Fix: display correct tax string when the "Display Tax Totals" setting is set to "As a single total" - requires new 'woocommerce_cart_totals_taxes_total_html' filter introduced with WC 2.1. See woothemes/woocommerce#5184 for details.
* Fix: display correct tax string when the "Display Tax Totals" setting is set to "As a single total" - requires new 'woocommerce_cart_totals_taxes_total_html' filter introduced with WC 2.1. See woocommerce/woocommerce#5184 for details.
* Fix: capitalise custom attribute names in the product meta section of the My Subscriptions table
* Fix: allow standard products variations to be deleted (but still prevent subscription variations from being permanently deleted)
* Fix: Maximum execution time error when attempting to switch from a paid subscription to a free subscription

View File

@@ -16,7 +16,7 @@ abstract class WCS_Cache_Manager {
/**
* Modeled after WP_Session_Tokens
*/
$manager = apply_filters( 'wcs_cache_manager_class', 'WCS_Cache_Manager_TLC' );
$manager = apply_filters( 'wcs_cache_manager_class', 'WCS_Cached_Data_Manager' );
return new $manager;
}

View File

@@ -75,7 +75,7 @@ class WC_Subscriptions_Admin {
add_action( 'save_post', __CLASS__ . '::save_variable_subscription_meta', 11 );
// Save variable subscription meta
add_action( 'woocommerce_process_product_meta_variable-subscription', __CLASS__ . '::process_product_meta_variable_subscription' ); // WC < 2.4
add_action( 'woocommerce_process_product_meta_variable-subscription', __CLASS__ . '::process_product_meta_variable_subscription' );
add_action( 'woocommerce_ajax_save_product_variations', __CLASS__ . '::process_product_meta_variable_subscription' );
add_action( 'woocommerce_subscription_pre_update_status', __CLASS__ . '::check_customer_is_set', 10, 3 );
@@ -137,10 +137,10 @@ class WC_Subscriptions_Admin {
'wc_report_subscription_events_by_date',
);
// Load all transients with the prefix tlc_
// Get all related order and subscription ranges transients
$results = $wpdb->get_col( "SELECT DISTINCT `option_name`
FROM `$wpdb->options`
WHERE `option_name` LIKE '%transient_tlc_%'" );
WHERE `option_name` LIKE '%wcs-related-orders-to-%' OR `option_name` LIKE '%wcs-sub-ranges-%'" );
foreach ( $results as $column ) {
$name = explode( 'transient_', $column, 2 );
@@ -572,11 +572,6 @@ class WC_Subscriptions_Admin {
// Make sure WooCommerce calculates correct prices
$_POST['variable_regular_price'] = isset( $_POST['variable_subscription_price'] ) ? $_POST['variable_subscription_price'] : 0;
// Run WooCommerce core saving routine for WC < 2.4
if ( ! is_ajax() ) {
WC_Meta_Box_Product_Data::save_variations( $post_id, get_post( $post_id ) );
}
if ( ! isset( $_REQUEST['variable_post_id'] ) ) {
return;
}
@@ -1101,7 +1096,7 @@ class WC_Subscriptions_Admin {
'default' => 'no',
'type' => 'checkbox',
// translators: placeholders are opening and closing link tags
'desc_tip' => sprintf( __( 'If you never want a customer to be automatically charged for a subscription renewal payment, you can turn off automatic payments completely. %sLearn more%s.', 'woocommerce-subscriptions' ), '<a href="http://docs.woocommerce.com/document/subscriptions/store-manager-guide/#turn-off-automatic-payments">', '</a>' ),
'desc_tip' => sprintf( __( 'If you don\'t want new subscription purchases to automatically charge renewal payments, you can turn off automatic payments. Existing automatic subscriptions will continue to charge customers automatically. %sLearn more%s.', 'woocommerce-subscriptions' ), '<a href="http://docs.woocommerce.com/document/subscriptions/store-manager-guide/#turn-off-automatic-payments">', '</a>' ),
'checkboxgroup' => 'end',
'show_if_checked' => 'yes',
),
@@ -1123,7 +1118,7 @@ class WC_Subscriptions_Admin {
'default' => 0,
'type' => 'select',
'options' => apply_filters( 'woocommerce_subscriptions_max_customer_suspension_range', array_merge( range( 0, 12 ), array( 'unlimited' => 'Unlimited' ) ) ),
'desc_tip' => __( 'Set a maximum number of times a customer can suspend their account for each billing period. For example, for a value of 3 and a subscription billed yearly, if the customer has suspended their account 3 times, they will not be presented with the option to suspend their account until the next year. Store managers will always be able able to suspend an active subscription. Set this to 0 to turn off the customer suspension feature completely.', 'woocommerce-subscriptions' ),
'desc_tip' => __( 'Set a maximum number of times a customer can suspend their account for each billing period. For example, for a value of 3 and a subscription billed yearly, if the customer has suspended their account 3 times, they will not be presented with the option to suspend their account until the next year. Store managers will always be able to suspend an active subscription. Set this to 0 to turn off the customer suspension feature completely.', 'woocommerce-subscriptions' ),
),
array(
@@ -1184,7 +1179,7 @@ class WC_Subscriptions_Admin {
<p class="submit">
<a href="<?php echo esc_url( self::add_subscription_url() ); ?>" class="button button-primary"><?php esc_html_e( 'Add a Subscription Product', 'woocommerce-subscriptions' ); ?></a>
<a href="<?php echo esc_url( self::settings_tab_url() ); ?>" class="docs button button-primary"><?php esc_html_e( 'Settings', 'woocommerce-subscriptions' ); ?></a>
<a href="https://twitter.com/share" class="twitter-share-button" data-url="http://www.woocommerce.com/products/woocommerce-subscriptions/" data-text="Woot! I can sell subscriptions with #WooCommerce" data-via="WooThemes" data-size="large">Tweet</a>
<a href="https://twitter.com/share" class="twitter-share-button" data-url="http://www.woocommerce.com/products/woocommerce-subscriptions/" data-text="Woot! I can sell subscriptions with #WooCommerce" data-via="WooCommerce" data-size="large">Tweet</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
</p>
</div>
@@ -1516,7 +1511,7 @@ class WC_Subscriptions_Admin {
),
array(
// translators: $1-$2: opening and closing tags. Link to documents->payment gateways, 3$-4$: opening and closing tags. Link to woothemes extensions shop page
// translators: $1-$2: opening and closing tags. Link to documents->payment gateways, 3$-4$: opening and closing tags. Link to WooCommerce extensions shop page
'desc' => sprintf( __( 'Find new gateways that %1$ssupport automatic subscription payments%2$s in the official %3$sWooCommerce Marketplace%4$s.', 'woocommerce-subscriptions' ), '<a href="' . esc_url( 'http://docs.woocommerce.com/document/subscriptions/payment-gateways/' ) . '">', '</a>', '<a href="' . esc_url( 'http://www.woocommerce.com/product-category/woocommerce-extensions/' ) . '">', '</a>' ),
'id' => WC_Subscriptions_Admin::$option_prefix . '_payment_gateways_additional',
'type' => 'informational',

View File

@@ -95,11 +95,11 @@ class WCS_Admin_Meta_Boxes {
if ( 'shop_subscription' == $screen->id ) {
wp_register_script( 'jstz', plugin_dir_url( WC_Subscriptions::$plugin_file ) . '/assets/js/admin/jstz.min.js' );
wp_register_script( 'jstz', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/jstz.min.js' );
wp_register_script( 'momentjs', plugin_dir_url( WC_Subscriptions::$plugin_file ) . '/assets/js/admin/moment.min.js' );
wp_register_script( 'momentjs', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/moment.min.js' );
wp_enqueue_script( 'wcs-admin-meta-boxes-subscription', plugin_dir_url( WC_Subscriptions::$plugin_file ) . '/assets/js/admin/meta-boxes-subscription.js', array( 'wc-admin-meta-boxes', 'jstz', 'momentjs' ), WC_VERSION );
wp_enqueue_script( 'wcs-admin-meta-boxes-subscription', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/meta-boxes-subscription.js', array( 'wc-admin-meta-boxes', 'jstz', 'momentjs' ), WC_VERSION );
wp_localize_script( 'wcs-admin-meta-boxes-subscription', 'wcs_admin_meta_boxes', apply_filters( 'woocommerce_subscriptions_admin_meta_boxes_script_parameters', array(
'i18n_start_date_notice' => __( 'Please enter a start date in the past.', 'woocommerce-subscriptions' ),
@@ -115,7 +115,7 @@ class WCS_Admin_Meta_Boxes {
) ) );
} else if ( 'shop_order' == $screen->id ) {
wp_enqueue_script( 'wcs-admin-meta-boxes-order', plugin_dir_url( WC_Subscriptions::$plugin_file ) . '/assets/js/admin/wcs-meta-boxes-order.js' );
wp_enqueue_script( 'wcs-admin-meta-boxes-order', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/wcs-meta-boxes-order.js' );
wp_localize_script( 'wcs-admin-meta-boxes-order', 'wcs_admin_order_meta_boxes', array(
'retry_renewal_payment_action_warning' => __( "Are you sure you want to retry payment for this renewal order?\n\nThis will attempt to charge the customer and send renewal order emails (if emails are enabled).", 'woocommerce-subscriptions' ),
@@ -229,8 +229,7 @@ class WCS_Admin_Meta_Boxes {
$can_be_retried = false;
if ( wcs_order_contains_renewal( $order ) && $order->has_status( 'failed' ) && ! empty( $order->payment_method ) && $order->get_total() > 0 ) {
if ( wcs_order_contains_renewal( $order ) && $order->needs_payment() && ! empty( $order->payment_method ) ) {
$order_payment_gateway = wc_get_payment_gateway_by_order( $order );
$order_payment_gateway_supports = ( isset( $order_payment_gateway->id ) ) ? has_action( 'woocommerce_scheduled_subscription_payment_' . $order_payment_gateway->id ) : false;

View File

@@ -245,6 +245,9 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
self::init_address_fields();
// Add key
add_post_meta( $post_id, '_order_key', uniqid( 'order_' ), true );
// Update meta
update_post_meta( $post_id, '_customer_user', absint( $_POST['customer_user'] ) );

View File

@@ -401,6 +401,12 @@ class WC_Subscription extends WC_Order {
$this->add_order_note( trim( sprintf( __( '%1$s Status changed from %2$s to %3$s.', 'woocommerce-subscriptions' ), $note, wcs_get_subscription_status_name( $old_status ), wcs_get_subscription_status_name( $new_status ) ) ), 0, $manual );
} catch ( Exception $e ) {
// Log any exceptions to a WC logger
$log = new WC_Logger();
$log_entry = print_r( $e, true );
$log_entry .= 'Exception Trace: ' . print_r( $e->getTraceAsString(), true );
$log->add( 'wcs-update-status-failures', $log_entry );
// Make sure the old status is restored
wp_update_post( array( 'ID' => $this->id, 'post_status' => $old_status_key ) );
@@ -749,97 +755,20 @@ class WC_Subscription extends WC_Order {
public function update_dates( $dates, $timezone = 'gmt' ) {
global $wpdb;
if ( ! is_array( $dates ) ) {
throw new InvalidArgumentException( __( 'Invalid format. First parameter needs to be an array.', 'woocommerce-subscriptions' ) );
}
$dates = $this->validate_date_updates( $dates, $timezone );
if ( empty( $dates ) ) {
throw new InvalidArgumentException( __( 'Invalid data. First parameter was empty when passed to update_dates().', 'woocommerce-subscriptions' ) );
}
// If an exception hasn't been thrown by this point, we can safely update the dates
$is_updated = false;
$allowed_date_keys = array_keys( wcs_get_subscription_date_types() );
$passed_date_keys = array_keys( $dates );
$extra_keys = array_diff( str_replace( '_date', '', $passed_date_keys ), $allowed_date_keys );
if ( ! empty( $extra_keys ) ) {
throw new InvalidArgumentException( __( 'Invalid data. First parameter has a date that is not in the registered date types.', 'woocommerce-subscriptions' ) );
}
$timestamps = array();
foreach ( $dates as $date_type => $datetime ) {
if ( ! empty( $datetime ) && false === wcs_is_datetime_mysql_format( $datetime ) ) {
// translators: placeholder is date type (e.g. "end", "next_payment"...)
throw new InvalidArgumentException( sprintf( _x( 'Invalid %s date. The date must be of the format: "Y-m-d H:i:s".', 'appears in an error message if date is wrong format', 'woocommerce-subscriptions' ), $date_type ) );
}
$date_type = str_replace( '_date', '', $date_type );
if ( empty( $datetime ) ) {
$timestamps[ $date_type ] = 0;
} else {
if ( 'gmt' !== strtolower( $timezone ) ) {
$datetime = get_gmt_from_date( $datetime );
}
$timestamps[ $date_type ] = wcs_date_to_time( $datetime );
}
}
foreach ( $allowed_date_keys as $date_type ) {
if ( ! array_key_exists( $date_type, $timestamps ) ) {
$timestamps[ $date_type ] = $this->get_time( $date_type );
}
if ( 0 == $timestamps[ $date_type ] ) {
// Last payment is not in the UI, and it should NOT be deleted as that would mess with scheduling
// Delete dates with a 0 date time
if ( 0 == $datetime ) {
if ( 'last_payment' != $date_type && 'start' != $date_type ) {
$this->delete_date( $date_type );
}
unset( $timestamps[ $date_type ] );
continue;
}
}
$messages = array();
// And then iterate over them. We need the two separate loops as we need a full array before we start checking the relationships between them.
foreach ( $timestamps as $date_type => $datetime ) {
switch ( $date_type ) {
case 'end' :
if ( array_key_exists( 'cancelled', $timestamps ) && $datetime < $timestamps['cancelled'] ) {
$messages[] = sprintf( __( 'The %s date must occur after the cancellation date.', 'woocommerce-subscriptions' ), $date_type );
}
case 'cancelled' :
if ( array_key_exists( 'last_payment', $timestamps ) && $datetime < $timestamps['last_payment'] ) {
$messages[] = sprintf( __( 'The %s date must occur after the last payment date.', 'woocommerce-subscriptions' ), $date_type );
}
if ( array_key_exists( 'next_payment', $timestamps ) && $datetime <= $timestamps['next_payment'] ) {
$messages[] = sprintf( __( 'The %s date must occur after the next payment date.', 'woocommerce-subscriptions' ), $date_type );
}
case 'next_payment' :
// Guarantees that end is strictly after trial_end, because if next_payment and end can't be at same time
if ( array_key_exists( 'trial_end', $timestamps ) && $datetime < $timestamps['trial_end'] ) {
$messages[] = sprintf( __( 'The %s date must occur after the trial end date.', 'woocommerce-subscriptions' ), $date_type );
}
case 'trial_end' :
if ( $datetime <= $timestamps['start'] ) {
$messages[] = sprintf( __( 'The %s date must occur after the start date.', 'woocommerce-subscriptions' ), $date_type );
}
}
}
if ( ! empty( $messages ) ) {
throw new Exception( join( ' ', $messages ) );
}
$is_updated = false;
foreach ( $timestamps as $date_type => $timestamp ) {
$datetime = gmdate( 'Y-m-d H:i:s', $timestamp );
if ( $datetime == $this->get_date( $date_type ) ) {
continue;
@@ -1019,7 +948,7 @@ class WC_Subscription extends WC_Order {
// Make sure the next payment is more than 2 hours in the future, this ensures changes to the site's timezone because of daylight savings will never cause a 2nd renewal payment to be processed on the same day
$i = 1;
while ( $next_payment_timestamp < ( current_time( 'timestamp', true ) + 2 * HOUR_IN_SECONDS ) && $i < 30 ) {
while ( $next_payment_timestamp < ( current_time( 'timestamp', true ) + 2 * HOUR_IN_SECONDS ) && $i < 3000 ) {
$next_payment_timestamp = wcs_add_time( $this->billing_interval, $this->billing_period, $next_payment_timestamp );
$i += 1;
}
@@ -1272,6 +1201,10 @@ class WC_Subscription extends WC_Order {
*/
public function payment_complete( $transaction_id = '' ) {
if ( WC_Subscriptions_Change_Payment_Gateway::$is_request_to_change_payment ) {
return;
}
// Clear the cached completed payment count
$this->cached_completed_payment_count = false;
@@ -1798,4 +1731,131 @@ class WC_Subscription extends WC_Order {
return apply_filters( 'woocommerce_subscription_is_one_payment', $is_one_payment, $this );
}
/**
* Validates subscription date updates ensuring the proposed date changes are in the correct format and are compatible with
* the current subscription dates. Also returns the dates in the gmt timezone - ready for setting/deleting.
*
* @param array $dates array containing dates with keys: 'start', 'trial_end', 'next_payment', 'last_payment' or 'end'. Values are time
* @param string $timezone The timezone of the $datetime param. Default 'gmt'.
* @return array $dates array of dates in gmt timezone.
*/
public function validate_date_updates( $dates, $timezone = 'gmt' ) {
if ( ! is_array( $dates ) ) {
throw new InvalidArgumentException( __( 'Invalid format. First parameter needs to be an array.', 'woocommerce-subscriptions' ) );
}
if ( empty( $dates ) ) {
throw new InvalidArgumentException( __( 'Invalid data. First parameter was empty when passed to update_dates().', 'woocommerce-subscriptions' ) );
}
$subscription_date_keys = array_keys( wcs_get_subscription_date_types() );
$passed_date_keys = str_replace( '_date', '', array_keys( $dates ) );
$extra_keys = array_diff( $passed_date_keys, $subscription_date_keys );
if ( ! empty( $extra_keys ) ) {
throw new InvalidArgumentException( __( 'Invalid data. First parameter has a date that is not in the registered date types.', 'woocommerce-subscriptions' ) );
}
$timestamps = $delete_date_types = array();
$dates = array_combine( $passed_date_keys, array_values( $dates ) );
// Get a full set of subscription dates made up of passed and current dates
foreach ( $subscription_date_keys as $date_type ) {
// Honour passed values first
if ( isset( $dates[ $date_type ] ) ) {
$datetime = $dates[ $date_type ];
if ( ! empty( $datetime ) && false === wcs_is_datetime_mysql_format( $datetime ) ) {
// translators: placeholder is date type (e.g. "end", "next_payment"...)
throw new InvalidArgumentException( sprintf( _x( 'Invalid %s date. The date must be of the format: "Y-m-d H:i:s".', 'appears in an error message if date is wrong format', 'woocommerce-subscriptions' ), $date_type ) );
}
if ( empty( $datetime ) ) {
$timestamps[ $date_type ] = 0;
} else {
if ( 'gmt' !== strtolower( $timezone ) ) {
$datetime = get_gmt_from_date( $datetime );
}
$timestamps[ $date_type ] = wcs_date_to_time( $datetime );
}
// otherwise get the current subscription time
} else {
$timestamps[ $date_type ] = $this->get_time( $date_type );
}
if ( 0 == $timestamps[ $date_type ] ) {
// Last payment is not in the UI, and it should NOT be deleted as that would mess with scheduling
if ( 'last_payment' != $date_type && 'start' != $date_type ) {
// We need to separate the dates which need deleting, so they don't interfere in the remaining validation
$delete_date_types[ $date_type ] = 0;
}
unset( $timestamps[ $date_type ] );
}
}
$messages = array();
// And then iterate over them checking the relationships between them.
foreach ( $timestamps as $date_type => $datetime ) {
switch ( $date_type ) {
case 'end' :
if ( array_key_exists( 'cancelled', $timestamps ) && $datetime < $timestamps['cancelled'] ) {
$messages[] = sprintf( __( 'The %s date must occur after the cancellation date.', 'woocommerce-subscriptions' ), $date_type );
}
case 'cancelled' :
if ( array_key_exists( 'last_payment', $timestamps ) && $datetime < $timestamps['last_payment'] ) {
$messages[] = sprintf( __( 'The %s date must occur after the last payment date.', 'woocommerce-subscriptions' ), $date_type );
}
if ( array_key_exists( 'next_payment', $timestamps ) && $datetime <= $timestamps['next_payment'] ) {
$messages[] = sprintf( __( 'The %s date must occur after the next payment date.', 'woocommerce-subscriptions' ), $date_type );
}
case 'next_payment' :
// Guarantees that end is strictly after trial_end, because if next_payment and end can't be at same time
if ( array_key_exists( 'trial_end', $timestamps ) && $datetime < $timestamps['trial_end'] ) {
$messages[] = sprintf( __( 'The %s date must occur after the trial end date.', 'woocommerce-subscriptions' ), $date_type );
}
case 'trial_end' :
if ( $datetime <= $timestamps['start'] ) {
$messages[] = sprintf( __( 'The %s date must occur after the start date.', 'woocommerce-subscriptions' ), $date_type );
}
}
$dates[ $date_type ] = gmdate( 'Y-m-d H:i:s', $datetime );
}
if ( ! empty( $messages ) ) {
throw new Exception( join( ' ', $messages ) );
}
return array_merge( $dates, $delete_date_types );
}
/**
* Add a product line item to the subscription.
*
* @since 2.1.4
* @param WC_Product product
* @param int line item quantity.
* @param array args
* @return int|bool Item ID or false.
*/
public function add_product( $product, $qty = 1, $args = array() ) {
$item_id = parent::add_product( $product, $qty, $args );
// Remove backordered meta if it has been added
if ( $item_id && $product->backorders_require_notification() && $product->is_on_backorder( $qty ) ) {
wc_delete_order_item_meta( $item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce-subscriptions' ) ) );
}
return $item_id;
}
}

View File

@@ -1059,9 +1059,10 @@ class WC_Subscriptions_Cart {
$added_invalid_notice = false;
$standard_packages = WC()->shipping->get_packages();
// temporarily store the current calculation type so we can restore it later
// temporarily store the current calculation type and recurring cart key so we can restore them later
$calculation_type = self::$calculation_type;
self::$calculation_type = 'recurring_total';
$recurring_cart_key_flag = self::$recurring_cart_key;
foreach ( WC()->cart->recurring_carts as $recurring_cart_key => $recurring_cart ) {
@@ -1069,6 +1070,8 @@ class WC_Subscriptions_Cart {
continue;
}
self::$recurring_cart_key = $recurring_cart_key;
$packages = $recurring_cart->get_shipping_packages();
foreach ( $packages as $package_index => $base_package ) {
@@ -1095,6 +1098,7 @@ class WC_Subscriptions_Cart {
}
self::$calculation_type = $calculation_type;
self::$recurring_cart_key = $recurring_cart_key_flag;
}
/**

View File

@@ -74,6 +74,7 @@ class WC_Subscriptions_Email {
add_action( 'woocommerce_subscription_status_updated', __CLASS__ . '::send_cancelled_email', 10, 2 );
add_action( 'woocommerce_subscription_status_expired', __CLASS__ . '::send_expired_email', 10, 2 );
add_action( 'woocommerce_customer_changed_subscription_to_on-hold', __CLASS__ . '::send_on_hold_email', 10, 2 );
add_action( 'woocommerce_subscriptions_switch_completed', __CLASS__ . '::send_switch_order_email', 10 );
$order_email_actions = array(
'woocommerce_order_status_pending_to_processing',
@@ -90,7 +91,6 @@ class WC_Subscriptions_Email {
foreach ( $order_email_actions as $action ) {
add_action( $action, __CLASS__ . '::maybe_remove_woocommerce_email', 9 );
add_action( $action, __CLASS__ . '::send_renewal_order_email', 10 );
add_action( $action, __CLASS__ . '::send_switch_order_email', 10 );
add_action( $action, __CLASS__ . '::maybe_reattach_woocommerce_email', 11 );
}
}

View File

@@ -69,6 +69,9 @@ class WC_Subscriptions_Order {
add_action( 'woocommerce_order_fully_refunded', __CLASS__ . '::maybe_cancel_subscription_on_full_refund' );
add_filter( 'woocommerce_order_needs_shipping_address', __CLASS__ . '::maybe_display_shipping_address', 10, 3 );
// Autocomplete subscription orders when they only contain a synchronised subscription or a resubscribe
add_filter( 'woocommerce_payment_complete_order_status', __CLASS__ . '::maybe_autocomplete_order', 10, 2 );
}
/*
@@ -471,7 +474,8 @@ class WC_Subscriptions_Order {
$subscriptions = wcs_get_subscriptions_for_order( $order_id, array( 'order_type' => 'parent' ) );
$was_activated = false;
$order_completed = in_array( $new_order_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id ), 'processing', 'completed' ) ) && in_array( $old_order_status, apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'on-hold', 'failed' ) ) );
$order = wc_get_order( $order_id );
$order_completed = in_array( $new_order_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id ), 'processing', 'completed' ) ) && in_array( $old_order_status, apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'on-hold', 'failed' ), $order ) );
foreach ( $subscriptions as $subscription ) {
@@ -1017,6 +1021,57 @@ class WC_Subscriptions_Order {
return $needs_shipping;
}
/**
* Automatically set the order's status to complete if the order total is zero and all the subscriptions
* in an order are synced or the order contains a resubscribe.
*
* @param string $new_order_status
* @param int $order_id
* @return string $new_order_status
*
* @since 2.1.3
*/
public static function maybe_autocomplete_order( $new_order_status, $order_id ) {
$order = wc_get_order( $order_id );
if ( 'processing' == $new_order_status && $order->get_total() == 0 && wcs_order_contains_subscription( $order ) ) {
if ( wcs_order_contains_resubscribe( $order ) ) {
$new_order_status = 'completed';
} elseif ( wcs_order_contains_switch( $order ) ) {
$all_switched = true;
foreach ( $order->get_items() as $item ) {
if ( ! isset( $item['switched_subscription_price_prorated'] ) ) {
$all_switched = false;
break;
}
}
if ( $all_switched || 1 == count( $order->get_items() ) ) {
$new_order_status = 'completed';
}
} else {
$subscriptions = wcs_get_subscriptions_for_order( $order_id );
$all_synced = true;
foreach ( $subscriptions as $subscription_id => $subscription ) {
if ( ! WC_Subscriptions_Synchroniser::subscription_contains_synced_product( $subscription_id ) ) {
$all_synced = false;
break;
}
}
if ( $all_synced ) {
$new_order_status = 'completed';
}
}
}
return $new_order_status;
}
/* Deprecated Functions */
/**

View File

@@ -79,8 +79,9 @@ class WC_Subscriptions_Renewal_Order {
$subscriptions = wcs_get_subscriptions_for_renewal_order( $order_id );
$was_activated = false;
$order = wc_get_order( $order_id );
$order_completed = in_array( $orders_new_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id ), 'processing', 'completed' ) );
$order_needed_payment = in_array( $orders_old_status, apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'on-hold', 'failed' ) ) );
$order_needed_payment = in_array( $orders_old_status, apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'on-hold', 'failed' ), $order ) );
if ( $order_completed && $order_needed_payment ) {
$update_post_data = array(

View File

@@ -60,9 +60,6 @@ class WC_Subscriptions_Switcher {
// Don't display free trials when switching a subscription, because no free trials are provided
add_filter( 'woocommerce_subscriptions_product_price_string_inclusions', __CLASS__ . '::customise_product_string_inclusions', 12, 2 );
// Autocomplete subscription switch orders
add_action( 'woocommerce_payment_complete_order_status', __CLASS__ . '::subscription_switch_autocomplete', 10, 2 );
// Don't carry switch meta data to renewal orders
add_filter( 'wcs_renewal_order_meta_query', __CLASS__ . '::remove_renewal_order_meta_query', 10 );
@@ -573,6 +570,15 @@ class WC_Subscriptions_Switcher {
}
}
}
// Store the order line item id so it can be retrieved when we're processing the switch on checkout
foreach ( WC()->cart->recurring_carts as $recurring_cart_key => $recurring_cart ) {
// If this cart item belongs to this recurring cart
if ( in_array( $cart_item_key, array_keys( $recurring_cart->cart_contents ) ) ) {
WC()->cart->recurring_carts[ $recurring_cart_key ]->cart_contents[ $cart_item_key ]['subscription_switch']['order_line_item_id'] = $order_item_id;
}
}
}
}
@@ -620,8 +626,6 @@ class WC_Subscriptions_Switcher {
$switch_order_data = array();
try {
// Start transaction if available
$wpdb->query( 'START TRANSACTION' );
foreach ( WC()->cart->recurring_carts as $recurring_cart_key => $recurring_cart ) {
@@ -664,41 +668,23 @@ class WC_Subscriptions_Switcher {
$is_different_length = false;
}
// WC_Abstract_Order::get_item_count() uses quantites, not just line item rows
// WC_Abstract_Order::get_item_count() uses quantities, not just line item rows
if ( 1 == count( $subscription->get_items() ) ) {
$is_single_item_subscription = true;
} else {
$is_single_item_subscription = false;
}
$order_item_id = '';
foreach ( $order_items as $item_id => $item ) {
if ( wcs_get_canonical_product_id( $item ) == wcs_get_canonical_product_id( $cart_item ) && ( empty( $switch_order_data['switches'] ) || ! in_array( $item_id, array_keys( $switch_order_data['switches'] ) ) ) ) {
$order_item_id = $item_id;
$switch_order_data[ $subscription->id ]['switches'][ $item_id ]['subscription_item_id'] = $cart_item['subscription_switch']['item_id'];
break;
}
}
$switched_item_data = array( 'remove_line_item' => $cart_item['subscription_switch']['item_id'] );
// If the item is on the same schedule, we can just add it to the new subscription and remove the old item
if ( $is_single_item_subscription || ( false === $is_different_billing_schedule && false === $is_different_payment_date && false === $is_different_length ) ) {
// Add the new item
$item_id = WC_Subscriptions_Checkout::add_cart_item( $subscription, $cart_item, $cart_item_key );
$item_meta = wc_get_order_item_meta( $item_id, '' );
wc_update_order_item( $item_id, array( 'order_item_type' => 'line_item_pending_switch' ) );
// We can't use the prorated order item price upon successful payment so store the cart price
$switch_order_data[ $subscription->id ]['switches'][ $order_item_id ]['add_order_item_data'] = array(
'totals' => array(
'subtotal' => $cart_item['line_subtotal'],
'subtotal_tax' => $cart_item['line_subtotal_tax'],
'total' => $cart_item['line_total'],
'tax' => $cart_item['line_tax'],
'tax_data' => $cart_item['line_tax_data'],
),
'meta' => $item_meta,
);
$switched_item_data['add_line_item'] = $item_id;
// Remove the item from the cart so that WC_Subscriptions_Checkout doesn't add it to a subscription
if ( 1 == count( WC()->cart->recurring_carts[ $recurring_cart_key ]->get_cart() ) ) {
@@ -709,13 +695,12 @@ class WC_Subscriptions_Switcher {
}
}
$switch_order_data[ $subscription->id ]['switches'][ $cart_item['subscription_switch']['order_line_item_id'] ] = $switched_item_data;
// If the old subscription has just one item, we can safely update its billing schedule
if ( $is_single_item_subscription ) {
if ( $is_different_billing_schedule ) {
update_post_meta( $subscription->id, '_billing_period', $cart_item['data']->subscription_period );
update_post_meta( $subscription->id, '_billing_interval', absint( $cart_item['data']->subscription_period_interval ) );
$switch_order_data[ $subscription->id ]['billing_schedule']['_billing_period'] = $cart_item['data']->subscription_period;
$switch_order_data[ $subscription->id ]['billing_schedule']['_billing_interval'] = absint( $cart_item['data']->subscription_period_interval );
}
@@ -723,8 +708,8 @@ class WC_Subscriptions_Switcher {
$updated_dates = array();
if ( '1' == $cart_item['data']->subscription_length || ( 0 != $recurring_cart->end_date && gmdate( 'Y-m-d H:i:s', $cart_item['subscription_switch']['first_payment_timestamp'] ) >= $recurring_cart->end_date ) ) {
$subscription->delete_date( 'next_payment' );
$switch_order_data[ $subscription->id ]['dates']['delete'][] = 'next_payment';
// Delete the next payment date.
$updated_dates['next_payment'] = 0;
} else if ( $is_different_payment_date ) {
$updated_dates['next_payment'] = gmdate( 'Y-m-d H:i:s', $cart_item['subscription_switch']['first_payment_timestamp'] );
}
@@ -734,26 +719,35 @@ class WC_Subscriptions_Switcher {
}
if ( ! empty( $updated_dates ) ) {
$subscription->update_dates( $updated_dates );
$subscription->validate_date_updates( $updated_dates );
$switch_order_data[ $subscription->id ]['dates']['update'] = $updated_dates;
}
}
// Remove the old item from the subscription but don't delete it completely by changing its line item type to "line_item_switched"
wc_update_order_item( $cart_item['subscription_switch']['item_id'], array( 'order_item_type' => 'line_item_switched' ) );
// Add the shipping
// Keep a record of the current shipping line items so we can flip any new shipping items to a _pending_switch shipping item.
$current_shipping_line_items = array_keys( $subscription->get_shipping_methods() );
$new_shipping_line_items = array();
// Change the shipping
self::update_shipping_methods( $subscription, $recurring_cart );
$switch_order_data[ $subscription->id ]['shipping_methods'] = $subscription->get_shipping_methods();
// Keep a record of the subscription shipping total. Adding shipping methods will cause a new shipping total to be set, we'll need to set it back after.
$subscription_shipping_total = $subscription->order_shipping;
// Finally, change the addresses but only if they've changed
self::maybe_update_subscription_address( $order, $subscription );
WC_Subscriptions_Checkout::add_shipping( $subscription, $recurring_cart );
// Set all new shipping methods to shipping_pending_switch line items
foreach ( $subscription->get_shipping_methods() as $shipping_line_item_id => $shipping_meta ) {
if ( ! in_array( $shipping_line_item_id, $current_shipping_line_items ) ) {
wc_update_order_item( $shipping_line_item_id, array( 'order_item_type' => 'shipping_pending_switch' ) );
$new_shipping_line_items[] = $shipping_line_item_id;
}
}
// Everything seems to be in order.
// Rollback the changes and store the required meta on the order so it can be processed on successful payment.
$wpdb->query( 'ROLLBACK' );
$subscription->set_total( $subscription_shipping_total, 'shipping' );
$switch_order_data[ $subscription->id ]['shipping_line_items'] = $new_shipping_line_items;
}
}
foreach ( $switch_order_data as $subscription_id => $switch_data ) {
@@ -765,9 +759,6 @@ class WC_Subscriptions_Switcher {
$switch_order->update_status( 'cancelled', sprintf( __( 'Switch order cancelled due to a new switch order being created #%s.', 'woocommerce-subscriptions' ), $order->get_order_number() ) );
}
}
// Despite rolling back the DB queries, the cache can still contain subscription changes (eg _billing_period post meta), so make sure we delete the cache for all subscriptions we've altered.
wp_cache_delete( $subscription_id, 'post_meta' );
}
update_post_meta( $order_id, '_subscription_switch_data', $switch_order_data );
@@ -892,7 +883,7 @@ class WC_Subscriptions_Switcher {
}
if ( isset( WC()->cart ) ) {
// We use WC()->cart->cart_contents instead of WC()->cart->get_cart() to prevent recursion caused when get_cart_from_session() too early is called ref: https://github.com/woothemes/woocommerce/commit/1f3365f2066b1e9d7e84aca7b1d7e89a6989c213
// We use WC()->cart->cart_contents instead of WC()->cart->get_cart() to prevent recursion caused when get_cart_from_session() too early is called ref: https://github.com/woocommerce/woocommerce/commit/1f3365f2066b1e9d7e84aca7b1d7e89a6989c213
foreach ( WC()->cart->cart_contents as $cart_item_key => $cart_item ) {
if ( isset( $cart_item['subscription_switch'] ) ) {
if ( wcs_is_subscription( $cart_item['subscription_switch']['subscription_id'] ) ) {
@@ -1478,36 +1469,6 @@ class WC_Subscriptions_Switcher {
return WCS_Limiter::is_purchasable_switch( $is_purchasable, $product );
}
/**
* Automatically set a switch order's status to complete (even if the items require shipping because
* the order is simply a record of the switch and not indicative of an item needing to be shipped)
*
* @since 1.5
*/
public static function subscription_switch_autocomplete( $new_order_status, $order_id ) {
if ( 'processing' == $new_order_status && wcs_order_contains_switch( $order_id ) ) {
$order = wc_get_order( $order_id );
$all_switched = true;
if ( 0 == $order->get_total() ) {
foreach ( $order->get_items() as $item ) {
if ( ! isset( $item['switched_subscription_price_prorated'] ) ) {
$all_switched = false;
break;
}
}
if ( $all_switched || 1 == count( $order->get_items() ) ) {
$new_order_status = 'completed';
}
}
}
return $new_order_status;
}
/**
* Do not carry over switch related meta data to renewal orders.
*
@@ -1772,57 +1733,49 @@ class WC_Subscriptions_Switcher {
return;
}
foreach ( $switch_order_data as $subcription_id => $switch_data ) {
foreach ( $switch_order_data as $subscription_id => $switch_data ) {
$subscription = wcs_get_subscription( $subcription_id );
$subscription = wcs_get_subscription( $subscription_id );
if ( ! $subscription instanceof WC_Subscription ) {
continue;
}
// Add the new line items
if ( ! empty( $switch_data['switches'] ) ) {
if ( ! empty( $switch_data['switches'] ) && is_array( $switch_data['switches'] ) ) {
foreach ( $switch_data['switches'] as $order_item_id => $switch_item_data ) {
// If the switch data is in the old format
if ( ! array_key_exists( 'remove_line_item', reset( $switch_data['switches'] ) ) ) {
self::switch_line_items_pre_2_1_2( $switch_data['switches'], $order, $subscription );
} else {
foreach ( $switch_data['switches'] as $order_item_id => $switched_item_data ) {
$order_item = wcs_get_order_item( $order_item_id, $order );
// If we are adding a line item to an existing subscription
if ( isset( $switched_item_data['add_line_item'] ) ) {
wc_update_order_item( $switched_item_data['add_line_item'], array( 'order_item_type' => 'line_item' ) );
// if we are simply adding this product to an existing subscription
if ( isset( $switch_item_data['add_order_item_data'] ) ) {
$product = WC_Subscriptions::get_product( wcs_get_canonical_product_id( $order_item ) );
$line_tax_data = wc_get_order_item_meta( $order_item_id, '_line_tax_data', true );
$variation_attributes = ( method_exists( $product, 'get_variation_attributes' ) ) ? $product->get_variation_attributes() : array();
$item_id = $subscription->add_product( $product, $order_item['qty'], array(
'variation' => $variation_attributes,
'totals' => $switch_item_data['add_order_item_data']['totals'],
) );
foreach ( $switch_item_data['add_order_item_data']['meta'] as $key => $value ) {
if ( ! array_key_exists( 'attribute_' . $key, $variation_attributes ) ) {
wc_add_order_item_meta( $item_id, $key, reset( $value ), true );
}
}
do_action( 'woocommerce_subscription_item_switched', $order, $subscription, $order_item_id, $switch_item_data['subscription_item_id'] );
do_action( 'woocommerce_subscription_item_switched', $order, $subscription, $switched_item_data['add_line_item'], $switched_item_data['remove_line_item'] );
}
// remove the existing subscription item
$old_order_item = wcs_get_order_item( $switch_item_data['subscription_item_id'], $subscription );
$old_subscription_item = wcs_get_order_item( $switched_item_data['remove_line_item'], $subscription );
$switch_order_item = wcs_get_order_item( $order_item_id, $order );
if ( empty( $old_order_item ) ) {
if ( empty( $old_subscription_item ) ) {
throw new Exception( __( 'The original subscription item being switched cannot be found.', 'woocommerce-subscriptions' ) );
} elseif ( empty( $switch_order_item ) ) {
throw new Exception( __( 'The item on the switch order cannot be found.', 'woocommerce-subscriptions' ) );
} else {
// We dont want to include switch item meta in order item name
// We don't want to include switch item meta in order item name
add_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_true' );
$new_order_item_name = wcs_get_order_item_name( $order_item, array( 'attributes' => true ) );
$old_subscription_item_name = wcs_get_order_item_name( $old_order_item, array( 'attributes' => true ) );
$old_item_name = wcs_get_order_item_name( $old_subscription_item, array( 'attributes' => true ) );
$new_item_name = wcs_get_order_item_name( $switch_order_item, array( 'attributes' => true ) );
remove_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_true' );
wc_update_order_item( $switch_item_data['subscription_item_id'], array( 'order_item_type' => 'line_item_switched' ) );
wc_update_order_item( $switched_item_data['remove_line_item'], array( 'order_item_type' => 'line_item_switched' ) );
// translators: 1$: old item, 2$: new item when switching
$subscription->add_order_note( sprintf( _x( 'Customer switched from: %1$s to %2$s.', 'used in order notes', 'woocommerce-subscriptions' ), $old_subscription_item_name, $new_order_item_name ) );
$subscription->add_order_note( sprintf( _x( 'Customer switched from: %1$s to %2$s.', 'used in order notes', 'woocommerce-subscriptions' ), $old_item_name, $new_item_name ) );
}
}
}
}
@@ -1831,7 +1784,7 @@ class WC_Subscriptions_Switcher {
// Update the billing schedule
foreach ( $switch_data['billing_schedule'] as $meta_key => $value ) {
update_post_meta( $subcription_id, $meta_key, $value );
update_post_meta( $subscription_id, $meta_key, $value );
}
}
@@ -1849,35 +1802,19 @@ class WC_Subscriptions_Switcher {
}
}
// If the shipping data is in the old format
if ( ! empty( $switch_data['shipping_methods'] ) ) {
self::switch_shipping_line_items_pre_2_1_2( $subscription, $switch_data['shipping_methods'] );
} else if ( ! empty( $switch_data['shipping_line_items'] ) && is_array( $switch_data['shipping_line_items'] ) ) {
// Archive the old subscription shipping methods
foreach ( $subscription->get_shipping_methods() as $shipping_line_item_id => $item ) {
wc_update_order_item( $shipping_line_item_id, array( 'order_item_type' => 'shipping_switched' ) );
}
// Add the new shipping line item
foreach ( $switch_data['shipping_methods'] as $shipping_line_item ) {
$item_id = wc_add_order_item( $subscription->id, array(
'order_item_name' => $shipping_line_item['name'],
'order_item_type' => 'shipping',
) );
if ( ! $item_id || empty( $shipping_line_item['method_id'] ) || empty( $shipping_line_item['cost'] ) || empty( $shipping_line_item['taxes'] ) ) {
throw new Exception( __( 'Failed to update the subscription shipping method.', 'woocommerce-subscriptions' ) );
}
// Add shipping order item meta
wc_add_order_item_meta( $item_id, 'method_id', $shipping_line_item['method_id'] );
wc_add_order_item_meta( $item_id, 'cost', wc_format_decimal( $shipping_line_item['cost'] ) );
$taxes = array_map( 'wc_format_decimal', maybe_unserialize( $shipping_line_item['taxes'] ) );
wc_add_order_item_meta( $item_id, 'taxes', $taxes );
// Add custom shipping order item meta added by third-party plugins
foreach ( $shipping_line_item['item_meta'] as $key => $value ) {
wc_add_order_item_meta( $item_id, $key, $value );
}
// Flip the switched shipping line items "on"
foreach ( $switch_data['shipping_line_items'] as $shipping_line_item_id ) {
wc_update_order_item( $shipping_line_item_id, array( 'order_item_type' => 'shipping' ) );
}
}
@@ -1975,6 +1912,11 @@ class WC_Subscriptions_Switcher {
*/
public static function maybe_set_payment_method_after_switch( $order ) {
// Only set manual subscriptions to automatic if automatic payments are enabled
if ( 'yes' == get_option( WC_Subscriptions_Admin::$option_prefix . '_turn_off_automatic_payments', 'no' ) ) {
return;
}
foreach ( wcs_get_subscriptions_for_switch_order( $order->id ) as $subscription ) {
if ( false === $subscription->is_manual() ) {
@@ -1997,6 +1939,17 @@ class WC_Subscriptions_Switcher {
/** Deprecated Methods **/
/**
* Automatically set a switch order's status to complete (even if the items require shipping because
* the order is simply a record of the switch and not indicative of an item needing to be shipped)
*
* @since 1.5
*/
public static function subscription_switch_autocomplete( $new_order_status, $order_id ) {
_deprecated_function( __METHOD__, '2.1.3', 'WC_Subscriptions_Order::maybe_autocomplete_order' );
return WC_Subscriptions_Order::maybe_autocomplete_order( $new_order_status, $order_id );
}
/**
* Once payment is processed on a switch from a $0 / period subscription to a non-zero $ / period subscription, if
* payment was completed with a payment method which supports automatic payments, update the payment on the subscription
@@ -2070,6 +2023,100 @@ class WC_Subscriptions_Switcher {
return $total;
}
/**
* Switch subscription line items provided line item data in the 2.1 switch order meta format.
*
* @param array $switches an array of switch items and its meta
* @param WC_Order $order the switch order
* @param WC_Subscription $subscription the subscription being switched
* @since 2.1.2
* TODO Remove this function in 2.1.n - compatibility code for 2.1 - 2.1.2
*/
protected static function switch_line_items_pre_2_1_2( $switches, $order, $subscription ) {
foreach ( $switches as $order_item_id => $switch_item_data ) {
$order_item = wcs_get_order_item( $order_item_id, $order );
// if we are simply adding this product to an existing subscription
if ( isset( $switch_item_data['add_order_item_data'] ) ) {
$product = WC_Subscriptions::get_product( wcs_get_canonical_product_id( $order_item ) );
$line_tax_data = wc_get_order_item_meta( $order_item_id, '_line_tax_data', true );
$variation_attributes = ( method_exists( $product, 'get_variation_attributes' ) ) ? $product->get_variation_attributes() : array();
$item_id = $subscription->add_product( $product, $order_item['qty'], array(
'variation' => $variation_attributes,
'totals' => $switch_item_data['add_order_item_data']['totals'],
) );
foreach ( $switch_item_data['add_order_item_data']['meta'] as $key => $value ) {
if ( ! array_key_exists( 'attribute_' . $key, $variation_attributes ) ) {
wc_add_order_item_meta( $item_id, $key, reset( $value ), true );
}
}
do_action( 'woocommerce_subscription_item_switched', $order, $subscription, $order_item_id, $switch_item_data['subscription_item_id'] );
}
// remove the existing subscription item
$old_order_item = wcs_get_order_item( $switch_item_data['subscription_item_id'], $subscription );
if ( empty( $old_order_item ) ) {
throw new Exception( __( 'The original subscription item being switched cannot be found.', 'woocommerce-subscriptions' ) );
} else {
// We don't want to include switch item meta in order item name
add_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_true' );
$new_order_item_name = wcs_get_order_item_name( $order_item, array( 'attributes' => true ) );
$old_subscription_item_name = wcs_get_order_item_name( $old_order_item, array( 'attributes' => true ) );
remove_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_true' );
wc_update_order_item( $switch_item_data['subscription_item_id'], array( 'order_item_type' => 'line_item_switched' ) );
// translators: 1$: old item, 2$: new item when switching
$subscription->add_order_note( sprintf( _x( 'Customer switched from: %1$s to %2$s.', 'used in order notes', 'woocommerce-subscriptions' ), $old_subscription_item_name, $new_order_item_name ) );
}
}
}
/**
* Switch subscription shipping line items provided shipping line item data in the 2.1 switch order meta format.
*
* @param WC_Subscription $subscription the subscription being switched
* @param array $shipping_methods an array of shipping line items and meta
* @since 2.1.2
* TODO Remove this function in 2.1.n - compatibility code for 2.1 - 2.1.2
*/
protected static function switch_shipping_line_items_pre_2_1_2( $subscription, $shipping_methods ) {
// Archive the old subscription shipping methods
foreach ( $subscription->get_shipping_methods() as $shipping_line_item_id => $item ) {
wc_update_order_item( $shipping_line_item_id, array( 'order_item_type' => 'shipping_switched' ) );
}
// Add the new shipping line item
foreach ( $shipping_methods as $shipping_line_item ) {
$item_id = wc_add_order_item( $subscription->id, array(
'order_item_name' => $shipping_line_item['name'],
'order_item_type' => 'shipping',
) );
if ( ! $item_id || empty( $shipping_line_item['method_id'] ) || empty( $shipping_line_item['cost'] ) || empty( $shipping_line_item['taxes'] ) ) {
throw new Exception( __( 'Failed to update the subscription shipping method.', 'woocommerce-subscriptions' ) );
}
// Add shipping order item meta
wc_add_order_item_meta( $item_id, 'method_id', $shipping_line_item['method_id'] );
wc_add_order_item_meta( $item_id, 'cost', wc_format_decimal( $shipping_line_item['cost'] ) );
$taxes = array_map( 'wc_format_decimal', maybe_unserialize( $shipping_line_item['taxes'] ) );
wc_add_order_item_meta( $item_id, 'taxes', $taxes );
// Add custom shipping order item meta added by third-party plugins
foreach ( $shipping_line_item['item_meta'] as $key => $value ) {
wc_add_order_item_meta( $item_id, $key, $value );
}
}
}
/** Deprecated Methods **/
/**

View File

@@ -63,7 +63,7 @@ class WC_Subscriptions_Synchroniser {
add_action( 'woocommerce_process_product_meta_subscription', __CLASS__ . '::save_subscription_meta', 10 );
// Save sync options when a variable subscription product is saved
add_action( 'woocommerce_process_product_meta_variable-subscription', __CLASS__ . '::process_product_meta_variable_subscription' ); // WC < 2.4
add_action( 'woocommerce_process_product_meta_variable-subscription', __CLASS__ . '::process_product_meta_variable_subscription' );
add_action( 'woocommerce_ajax_save_product_variations', __CLASS__ . '::process_product_meta_variable_subscription' );
// Make sure the expiration dates are calculated from the synced start date
@@ -101,9 +101,6 @@ class WC_Subscriptions_Synchroniser {
// Make sure the sign-up fee for a synchronised subscription is correct
add_filter( 'woocommerce_subscriptions_sign_up_fee', __CLASS__ . '::get_synced_sign_up_fee', 1, 3 );
// Autocomplete subscription orders when they only contain a synchronised subscription
add_filter( 'woocommerce_payment_complete_order_status', __CLASS__ . '::order_autocomplete', 10, 2 );
// If it's an initial sync order and the total is zero, and nothing needs to be shipped, do not reduce stock
add_filter( 'woocommerce_order_item_quantity', __CLASS__ . '::maybe_do_not_reduce_stock', 10, 3 );
@@ -921,37 +918,6 @@ class WC_Subscriptions_Synchroniser {
return $weekday;
}
/**
* Automatically set the order's status to complete if all the subscriptions in an order
* are synced and the order total is zero.
*
* @since 1.5.17
*/
public static function order_autocomplete( $new_order_status, $order_id ) {
$order = wc_get_order( $order_id );
if ( 'processing' == $new_order_status && $order->get_total() == 0 && wcs_order_contains_subscription( $order ) ) {
$subscriptions = wcs_get_subscriptions_for_order( $order_id );
$all_synced = true;
foreach ( $subscriptions as $subscription_id => $subscription ) {
if ( ! self::subscription_contains_synced_product( $subscription_id ) ) {
$all_synced = false;
break;
}
}
if ( $all_synced ) {
$new_order_status = 'completed';
}
}
return $new_order_status;
}
/**
* Override quantities used to lower stock levels by when using synced subscriptions. If it's a synced product
* that does not have proration enabled and the payment date is not today, do not lower stock levels.
@@ -1060,7 +1026,7 @@ class WC_Subscriptions_Synchroniser {
public static function add_to_recurring_cart_key( $cart_key, $cart_item ) {
$product = $cart_item['data'];
if ( self::is_product_synced( $product ) ) {
if ( false === strpos( $cart_key, '_synced' ) && self::is_product_synced( $product ) ) {
$cart_key .= '_synced';
}
@@ -1069,6 +1035,17 @@ class WC_Subscriptions_Synchroniser {
/* Deprecated Functions */
/**
* Automatically set the order's status to complete if all the subscriptions in an order
* are synced and the order total is zero.
*
* @since 1.5.17
*/
public static function order_autocomplete( $new_order_status, $order_id ) {
_deprecated_function( __METHOD__, '2.1.3', 'WC_Subscriptions_Order::maybe_autocomplete_order' );
return WC_Subscriptions_Order::maybe_autocomplete_order( $new_order_status, $order_id );
}
/**
* Add the first payment date to the end of the subscription to clarify when the first payment will be processed
*

View File

@@ -15,6 +15,7 @@ class WCS_Cache_Manager_TLC extends WCS_Cache_Manager {
public $logger = null;
public function __construct() {
_deprecated_function( __METHOD__, '2.1.2' );
add_action( 'woocommerce_loaded', array( $this, 'load_logger' ) );
// Add filters for update / delete / trash post to purge cache
@@ -30,6 +31,7 @@ class WCS_Cache_Manager_TLC extends WCS_Cache_Manager {
* Attaches logger
*/
public function load_logger() {
_deprecated_function( __METHOD__, '2.1.2', 'WC_Subscriptions::$cache->' . __FUNCTION__ );
$this->logger = new WC_Logger();
}
@@ -39,6 +41,7 @@ class WCS_Cache_Manager_TLC extends WCS_Cache_Manager {
* @param string $message Message to log
*/
public function log( $message ) {
_deprecated_function( __METHOD__, '2.1.2', 'WC_Subscriptions::$cache->' . __FUNCTION__ );
if ( defined( 'WCS_DEBUG' ) && WCS_DEBUG ) {
$this->logger->add( 'wcs-cache', $message );
}
@@ -55,6 +58,7 @@ class WCS_Cache_Manager_TLC extends WCS_Cache_Manager {
* @return bool|mixed
*/
public function cache_and_get( $key, $callback, $params = array(), $expires = WEEK_IN_SECONDS ) {
_deprecated_function( __METHOD__, '2.1.2', 'WC_Subscriptions::$cache->' . __FUNCTION__ );
$expires = absint( $expires );
$transient = tlc_transient( $key )
@@ -70,6 +74,7 @@ class WCS_Cache_Manager_TLC extends WCS_Cache_Manager {
* @param $post_id integer the ID of an order / subscription
*/
public function purge_subscription_cache_on_update( $post_id ) {
_deprecated_function( __METHOD__, '2.1.2', 'WC_Subscriptions::$cache->' . __FUNCTION__ );
$post_type = get_post_type( $post_id );
if ( 'shop_subscription' !== $post_type && 'shop_order' !== $post_type ) {
@@ -104,6 +109,7 @@ class WCS_Cache_Manager_TLC extends WCS_Cache_Manager {
* @param $post_id integer the ID of a post
*/
public function purge_delete( $post_id ) {
_deprecated_function( __METHOD__, '2.1.2', 'WC_Subscriptions::$cache->' . __FUNCTION__ );
if ( 'shop_order' !== get_post_type( $post_id ) ) {
return;
}
@@ -127,6 +133,7 @@ class WCS_Cache_Manager_TLC extends WCS_Cache_Manager {
* @param $_meta_value mixed the value we're deleting / adding / updating
*/
public function purge_from_metadata( $meta_id, $object_id, $meta_key, $_meta_value ) {
_deprecated_function( __METHOD__, '2.1.2', 'WC_Subscriptions::$cache->' . __FUNCTION__ );
if ( '_subscription_renewal' !== $meta_key || 'shop_order' !== get_post_type( $object_id ) ) {
return;
}
@@ -143,6 +150,7 @@ class WCS_Cache_Manager_TLC extends WCS_Cache_Manager {
* @param null $id
*/
public function wcs_clear_related_order_cache( $id = null ) {
_deprecated_function( __METHOD__, '2.1.2', 'WC_Subscriptions::$cache->' . __FUNCTION__ );
// if nothing was passed in, there's nothing to delete
if ( null === $id ) {
return;
@@ -170,6 +178,7 @@ class WCS_Cache_Manager_TLC extends WCS_Cache_Manager {
* @param string $key Key that needs deleting
*/
public function delete_cached( $key ) {
_deprecated_function( __METHOD__, '2.1.2', 'WC_Subscriptions::$cache->' . __FUNCTION__ );
if ( ! is_string( $key ) || empty( $key ) ) {
return;
}

View File

@@ -0,0 +1,178 @@
<?php
/**
* Subscription Cached Data Manager Class
*
* @class WCS_Cached_Data_Manager
* @version 2.1.2
* @package WooCommerce Subscriptions/Classes
* @category Class
* @author Prospress
*/
class WCS_Cached_Data_Manager extends WCS_Cache_Manager {
public $logger = null;
public function __construct() {
add_action( 'woocommerce_loaded', array( $this, 'load_logger' ) );
// Add filters for update / delete / trash post to purge cache
add_action( 'trashed_post', array( $this, 'purge_delete' ), 9999 ); // trashed posts aren't included in 'any' queries
add_action( 'untrashed_post', array( $this, 'purge_delete' ), 9999 ); // however untrashed posts are
add_action( 'deleted_post', array( $this, 'purge_delete' ), 9999 ); // if forced delete is enabled
add_action( 'updated_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys
add_action( 'deleted_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys
add_action( 'added_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys
}
/**
* Attaches logger
*/
public function load_logger() {
$this->logger = new WC_Logger();
}
/**
* Wrapper function around WC_Logger->log
*
* @param string $message Message to log
*/
public function log( $message ) {
if ( defined( 'WCS_DEBUG' ) && WCS_DEBUG ) {
$this->logger->add( 'wcs-cache', $message );
}
}
/**
* Helper function for fetching cached data or updating and storing new data provided by callback.
*
* @param string $key The key to cache/fetch the data with
* @param string|array $callback name of function, or array of class - method that fetches the data
* @param array $params arguments passed to $callback
* @param integer $expires number of seconds to keep the cache. Don't set it to 0, as the cache will be autoloaded. Default is a week.
*
* @return bool|mixed
*/
public function cache_and_get( $key, $callback, $params = array(), $expires = WEEK_IN_SECONDS ) {
$expires = absint( $expires );
$data = get_transient( $key );
// if there isn't a transient currently stored and we have a callback update function, fetch and store
if ( false === $data && ! empty( $callback ) ) {
$data = call_user_func_array( $callback, $params );
set_transient( $key, $data, $expires );
}
return $data;
}
/**
* Clearing cache when a post is deleted
*
* @param $post_id integer the ID of a post
*/
public function purge_delete( $post_id ) {
if ( 'shop_order' !== get_post_type( $post_id ) ) {
return;
}
foreach ( wcs_get_subscriptions_for_order( $post_id, array( 'order_type' => 'any' ) ) as $linked_subscription ) {
$this->log( 'Calling purge delete on ' . current_filter() . ' for ' . $linked_subscription->id );
$this->clear_related_order_cache( $linked_subscription );
}
}
/**
* When subscription related metadata is added / deleted / updated on an order, we need to invalidate the subscription related orders cache.
*
* @param $meta_id integer the ID of the meta in the meta table
* @param $object_id integer the ID of the post we're updating on, only concerned with order IDs
* @param $meta_key string the meta_key in the table, only concerned with '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys
* @param $meta_value mixed the ID of the subscription that relates to the order
*/
public function purge_from_metadata( $meta_id, $object_id, $meta_key, $meta_value ) {
if ( ! in_array( $meta_key, array( '_subscription_renewal', '_subscription_resubscribe', '_subscription_switch' ) ) || 'shop_order' !== get_post_type( $object_id ) ) {
return;
}
$this->log( 'Calling purge from ' . current_filter() . ' on object ' . $object_id . ' and meta value ' . $meta_value . ' due to ' . $meta_key . ' meta key.' );
$this->clear_related_order_cache( $meta_value );
}
/**
* Wrapper function to clear the cache that relates to related orders
*
* @param null $subscription_id
*/
protected function clear_related_order_cache( $subscription_id ) {
// if it's not a Subscription, we don't deal with it
if ( is_object( $subscription_id ) && $subscription_id instanceof WC_Subscription ) {
$subscription_id = $subscription_id->id;
} elseif ( is_numeric( $subscription_id ) ) {
$subscription_id = absint( $subscription_id );
} else {
return;
}
$key = 'wcs-related-orders-to-' . $subscription_id;
$this->log( 'In the clearing, key being purged is this: ' . print_r( $key, true ) );
$this->delete_cached( $key );
}
/**
* Delete cached data with key
*
* @param string $key Key that needs deleting
*/
public function delete_cached( $key ) {
if ( ! is_string( $key ) || empty( $key ) ) {
return;
}
delete_transient( $key );
}
/* Deprecated Functions */
/**
* Wrapper function to clear cache that relates to related orders
*
* @param null $subscription_id
*/
public function wcs_clear_related_order_cache( $subscription_id = null ) {
_deprecated_function( __METHOD__, '2.1.2', __CLASS__ . '::clear_related_order_cache( $subscription_id )' );
$this->clear_related_order_cache( $subscription_id );
}
/**
* Clearing for orders / subscriptions with sanitizing bits
*
* @param $post_id integer the ID of an order / subscription
*/
public function purge_subscription_cache_on_update( $post_id ) {
_deprecated_function( __METHOD__, '2.1.2', __CLASS__ . '::clear_related_order_cache( $subscription_id )' );
$post_type = get_post_type( $post_id );
if ( 'shop_subscription' === $post_type ) {
$this->clear_related_order_cache( $post_id );
} elseif ( 'shop_order' === $post_type ) {
$subscriptions = wcs_get_subscriptions_for_order( $post_id, array( 'order_type' => 'any' ) );
if ( empty( $subscriptions ) ) {
$this->log( 'No subscriptions for this ID: ' . $post_id );
} else {
foreach ( $subscriptions as $subscription ) {
$this->log( 'Got subscription, calling clear_related_order_cache for ' . $subscription->id );
$this->clear_related_order_cache( $subscription );
}
}
}
}
}

View File

@@ -43,6 +43,14 @@ class WCS_Cart_Renewal {
// When a user is prevented from paying for a failed/pending renewal order because they aren't logged in, redirect them back after login
add_filter( 'woocommerce_login_redirect', array( &$this, 'maybe_redirect_after_login' ), 10 , 1 );
// When a renewal order's line items are being updated, update the line item IDs stored in cart data.
add_action( 'woocommerce_add_order_item_meta', array( &$this, 'update_line_item_cart_data' ), 10, 3 );
// Once we have finished updating the renewal order on checkout, update the session cart so the cart changes are honoured.
add_action( 'woocommerce_checkout_order_processed', array( &$this, 'update_session_cart_after_updating_renewal_order' ), 10 );
add_filter( 'wc_dynamic_pricing_apply_cart_item_adjustment', array( &$this, 'prevent_compounding_dynamic_discounts' ), 10, 2 );
}
/**
@@ -899,6 +907,53 @@ class WCS_Cart_Renewal {
return $redirect;
}
/**
* After updating renewal order line items, update the values stored in cart item data
* which would now reference old line item IDs.
*
* @since 2.1.3
*/
public function update_line_item_cart_data( $item_id, $cart_item_data, $cart_item_key ) {
if ( isset( $cart_item_data[ $this->cart_item_key ] ) ) {
// Update the line_item_id to the new corresponding item_id
WC()->cart->cart_contents[ $cart_item_key ][ $this->cart_item_key ]['line_item_id'] = $item_id;
}
}
/**
* Force an update to the session cart after updating renewal order line items.
* This is required so that changes made by @see WCS_Cart_Renewal->update_line_item_cart_data()
* are also reflected in the session cart.
*
* @since 2.1.3
*/
public function update_session_cart_after_updating_renewal_order() {
if ( $this->cart_contains() ) {
// Update the cart stored in the session with the new data
WC()->session->cart = WC()->cart->get_cart_for_session();
}
}
/**
* Prevent compounding dynamic discounts on cart items.
* Dynamic discounts are copied from the subscription to the renewal order and so don't need to be applied again in the cart.
*
* @param bool Whether to apply the dynamic discount
* @param string The cart item key of the cart item the dynamic discount is being applied to.
* @return bool
* @since 2.1.4
*/
function prevent_compounding_dynamic_discounts( $adjust_price, $cart_item_key ) {
if ( $adjust_price && isset( WC()->cart->cart_contents[ $cart_item_key ][ $this->cart_item_key ] ) ) {
$adjust_price = false;
}
return $adjust_price;
}
/* Deprecated */
/**

View File

@@ -47,6 +47,8 @@ class WCS_Cart_Resubscribe extends WCS_Cart_Renewal {
add_action( 'woocommerce_review_order_after_shipping', array( &$this, 'maybe_unset_free_trial' ) );
add_action( 'woocommerce_order_status_changed', array( &$this, 'maybe_cancel_existing_subscription' ), 10, 3 );
add_filter( 'wc_dynamic_pricing_apply_cart_item_adjustment', array( &$this, 'prevent_compounding_dynamic_discounts' ), 10, 2 );
}
/**
@@ -302,12 +304,13 @@ class WCS_Cart_Resubscribe extends WCS_Cart_Renewal {
*/
public function maybe_cancel_existing_subscription( $order_id, $old_order_status, $new_order_status ) {
if ( wcs_order_contains_subscription( $order_id ) && wcs_order_contains_resubscribe( $order_id ) ) {
$order = wc_get_order( $order_id );
$order_completed = in_array( $new_order_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id ), 'processing', 'completed' ) );
$order_needed_payment = in_array( $old_order_status, apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'on-hold', 'failed' ) ) );
$order_needed_payment = in_array( $old_order_status, apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'on-hold', 'failed' ), $order ) );
foreach ( wcs_get_subscriptions_for_resubscribe_order( $order_id ) as $subscription ) {
if ( $subscription->has_status( 'pending-cancel' ) ) {
$cancel_note = sprintf( __( 'Customer resubscribed in order #%s', 'woocommerce-subscriptions' ), wc_get_order( $order_id )->get_order_number() );
$cancel_note = sprintf( __( 'Customer resubscribed in order #%s', 'woocommerce-subscriptions' ), $order->get_order_number() );
$subscription->update_status( 'cancelled', $cancel_note );
}
}

View File

@@ -68,14 +68,20 @@ class WCS_Cart_Switch extends WCS_Cart_Renewal{
foreach ( $order->get_items() as $item_id => $line_item ) {
// clear the GET args so we can add non-switch items to the cart cleanly
unset( $_GET['switch-subscription'] );
unset( $_GET['item'] );
// check if this order item is for a switch
foreach ( $switch_order_data as $subscription_id => $switch_data ) {
if ( isset( $switch_data['switches'] ) && in_array( $item_id, array_keys( $switch_data['switches'] ) ) ) {
$_GET['switch-subscription'] = $subscription_id;
$_GET['item'] = $switch_data['switches'][ $item_id ]['subscription_item_id'];
// Backwards compatibility (2.1 -> 2.1.2)
$subscription_item_id_key = ( isset( $switch_data['switches'][ $item_id ]['subscription_item_id'] ) ) ? 'subscription_item_id' : 'remove_line_item';
$_GET['item'] = $switch_data['switches'][ $item_id ][ $subscription_item_id_key ];
break;
}
}

View File

@@ -157,7 +157,7 @@ class WCS_Download_Handler {
/**
* Repairs a glitch in WordPress's save function. You cannot save a null value on update, see
* https://github.com/woothemes/woocommerce/issues/7861 for more info on this.
* https://github.com/woocommerce/woocommerce/issues/7861 for more info on this.
*
* @param integer $post_id The ID of the subscription
*/

View File

@@ -34,8 +34,10 @@ class WCS_Query extends WC_Query {
public function init_query_vars() {
$this->query_vars = array(
'view-subscription' => get_option( 'woocommerce_myaccount_view_subscriptions_endpoint', 'view-subscription' ),
'subscriptions' => get_option( 'woocommerce_myaccount_subscriptions_endpoint', 'subscriptions' ),
);
if ( ! WC_Subscriptions::is_woocommerce_pre( '2.6' ) ) {
$this->query_vars['subscriptions'] = get_option( 'woocommerce_myaccount_subscriptions_endpoint', 'subscriptions' );
}
}
/**

View File

@@ -41,8 +41,13 @@ class WCS_Retry_Manager {
add_filter( 'init', array( self::store(), 'init' ) );
add_filter( 'woocommerce_valid_order_statuses_for_payment', __CLASS__ . '::check_order_statuses_for_payment', 10, 2 );
add_filter( 'woocommerce_subscription_dates', __CLASS__ . '::add_retry_date_type' );
add_action( 'delete_post', __CLASS__ . '::maybe_cancel_retry_for_order' );
add_action( 'wp_trash_post', __CLASS__ . '::maybe_cancel_retry_for_order' );
add_action( 'woocommerce_subscription_status_updated', __CLASS__ . '::maybe_cancel_retry', 0, 3 );
add_action( 'woocommerce_subscriptions_retry_status_updated', __CLASS__ . '::maybe_delete_payment_retry_date', 0, 2 );
@@ -53,6 +58,26 @@ class WCS_Retry_Manager {
}
}
/**
* Adds any extra status that may be needed for a given order to check if it may
* need payment
*
* @param Array $statuses
* @param WC_Order $order
* @return array
* @since 2.2.1
*/
public static function check_order_statuses_for_payment( $statuses, $order ) {
$last_retry = self::store()->get_last_retry_for_order( $order );
if ( $last_retry ) {
$statuses[] = $last_retry->get_rule()->get_status_to_apply( 'order' );
$statuses = array_unique( $statuses );
}
return $statuses;
}
/**
* A helper function to check if the retry system has been enabled or not
*
@@ -125,6 +150,28 @@ class WCS_Retry_Manager {
}
}
/**
* When a (renewal) order is trashed or deleted, make sure its retries are also trashed/deleted.
*
* @param int $post_id
*/
public static function maybe_cancel_retry_for_order( $post_id ) {
if ( 'shop_order' == get_post_type( $post_id ) ) {
$last_retry = self::store()->get_last_retry_for_order( $post_id );
// Make sure the last retry is cancelled first so that it is unscheduled via self::maybe_delete_payment_retry_date()
if ( null !== $last_retry && 'cancelled' !== $last_retry->get_status() ) {
$last_retry->update_status( 'cancelled' );
}
foreach ( self::store()->get_retry_ids_for_order( $post_id ) as $retry_id ) {
wp_trash_post( $retry_id );
}
}
}
/**
* When a retry's status is updated, if it's no longer pending or processing and it's the most recent retry,
* delete the retry date on the subscriptions related to the order
@@ -245,6 +292,8 @@ class WCS_Retry_Manager {
// if both statuses are still the same or there no special status was applied and the order still needs payment (i.e. there has been no manual intervention), trigger the payment hook
if ( $valid_order_status && $valid_subscription_status ) {
$last_order->update_status( 'pending', _x( 'Subscription renewal payment retry:', 'used in order note as reason for why order status changed', 'woocommerce-subscriptions' ), true );
// Make sure the subscription is on hold in case something goes wrong while trying to process renewal and in case gateways expect the subscription to be on-hold, which is normally the case with a renewal payment
foreach ( $subscriptions as $subscription ) {
$subscription->update_status( 'on-hold', _x( 'Subscription renewal payment retry:', 'used in order note as reason for why subscription status changed', 'woocommerce-subscriptions' ) );

View File

@@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) {
* @class WC_Email_Customer_Completed_Order
* @version 2.0.0
* @package WooCommerce/Classes/Emails
* @author WooThemes
* @author Prospress
* @extends WC_Email
*/
class WCS_Email_Completed_Renewal_Order extends WC_Email_Customer_Completed_Order {

View File

@@ -11,7 +11,7 @@ if ( ! defined( 'ABSPATH' ) ) {
* @class WCS_Email_Completed_Switch_Order
* @version 2.0.0
* @package WooCommerce/Classes/Emails
* @author WooThemes
* @author Prospress
* @extends WC_Email
*/
class WCS_Email_Completed_Switch_Order extends WC_Email_Customer_Completed_Order {
@@ -39,7 +39,7 @@ class WCS_Email_Completed_Switch_Order extends WC_Email_Customer_Completed_Order
$this->subject_downloadable = $this->get_option( 'subject_downloadable', __( 'Your {blogname} subscription change from {order_date} is complete - download your files', 'woocommerce-subscriptions' ) );
// Triggers for this email
add_action( 'woocommerce_order_status_completed_switch_notification', array( $this, 'trigger' ) );
add_action( 'woocommerce_subscriptions_switch_completed_switch_notification', array( $this, 'trigger' ) );
// We want most of the parent's methods, with none of its properties, so call its parent's constructor
WC_Email::__construct();

View File

@@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) {
* @class WC_Email_Customer_Completed_Order
* @version 2.0.0
* @package WooCommerce/Classes/Emails
* @author WooThemes
* @author Prospress
* @extends WC_Email
*/
class WCS_Email_Processing_Renewal_Order extends WC_Email_Customer_Processing_Order {

View File

@@ -30,12 +30,7 @@ class WCS_Email_New_Switch_Order extends WC_Email_New_Order {
$this->template_base = plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'templates/';
// Triggers for this email
add_action( 'woocommerce_order_status_pending_to_processing_switch_notification', array( $this, 'trigger' ) );
add_action( 'woocommerce_order_status_pending_to_completed_switch_notification', array( $this, 'trigger' ) );
add_action( 'woocommerce_order_status_pending_to_on-hold_switch_notification', array( $this, 'trigger' ) );
add_action( 'woocommerce_order_status_failed_to_processing_switch_notification', array( $this, 'trigger' ) );
add_action( 'woocommerce_order_status_failed_to_completed_switch_notification', array( $this, 'trigger' ) );
add_action( 'woocommerce_order_status_failed_to_on-hold_switch_notification', array( $this, 'trigger' ) );
add_action( 'woocommerce_subscriptions_switch_completed_switch_notification', array( $this, 'trigger' ) );
// We want all the parent's methods, with none of its properties, so call its parent's constructor, rather than my parent constructor
WC_Email::__construct();

View File

@@ -105,7 +105,7 @@ class WCS_PayPal_Admin {
$notices[] = array(
'type' => 'warning',
// translators: placeholders are opening and closing link tags. 1$-2$: to docs on woothemes, 3$-4$ to gateway settings on the site
// translators: placeholders are opening and closing link tags. 1$-2$: to docs on woocommerce, 3$-4$ to gateway settings on the site
'text' => sprintf( esc_html__( 'PayPal is inactive for subscription transactions. Please %1$sset up the PayPal IPN%2$s and %3$senter your API credentials%4$s to enable PayPal for Subscriptions.', 'woocommerce-subscriptions' ),
'<a href="http://docs.woocommerce.com/document/subscriptions/store-manager-guide/#section-4" target="_blank">',
'</a>',
@@ -118,7 +118,7 @@ class WCS_PayPal_Admin {
$notices[] = array(
'type' => 'warning',
// translators: placeholders are opening and closing strong and link tags. 1$-2$: strong tags, 3$-8$ link to docs on woothemes
// translators: placeholders are opening and closing strong and link tags. 1$-2$: strong tags, 3$-8$ link to docs on woocommerce
'text' => sprintf( esc_html__( '%1$sPayPal Reference Transactions are not enabled on your account%2$s, some subscription management features are not enabled. Please contact PayPal and request they %3$senable PayPal Reference Transactions%4$s on your account. %5$sCheck PayPal Account%6$s %7$sLearn more %8$s', 'woocommerce-subscriptions' ),
'<strong>',
'</strong>',
@@ -160,7 +160,7 @@ class WCS_PayPal_Admin {
if ( 'yes' == get_option( 'wcs_paypal_invalid_profile_id' ) ) {
$notices[] = array(
'type' => 'error',
// translators: placeholders are opening and closing link tags. 1$-2$: docs on woothemes, 3$-4$: dismiss link
// translators: placeholders are opening and closing link tags. 1$-2$: docs on woocommerce, 3$-4$: dismiss link
'text' => sprintf( esc_html__( 'There is a problem with PayPal. Your PayPal account is issuing out-of-date subscription IDs. %1$sLearn more%2$s. %3$sDismiss%4$s.', 'woocommerce-subscriptions' ),
'<a href="https://docs.woocommerce.com/document/subscriptions-canceled-suspended-paypal/#section-3" target="_blank">',
'</a>',
@@ -175,7 +175,7 @@ class WCS_PayPal_Admin {
if ( ! empty( $last_ipn_error ) && ( false == get_option( 'wcs_fatal_error_handling_ipn_ignored', false ) || isset( $_GET['wcs_reveal_your_ipn_secrets'] ) ) ) {
$notices[] = array(
'type' => 'error',
'text' => sprintf( esc_html__( '%sA fatal error has occurred when processing a recent subscription payment with PayPal. Please %sopen a new ticket at WooThemes Support%s immediately to get this resolved.%sIn order to get the quickest possible response please attach a %sTemporary Admin Login%s and a copy of your PHP error logs to your support ticket.%sLast recorded error: %s', 'woocommerce-subscriptions' ),
'text' => sprintf( esc_html__( '%sA fatal error has occurred when processing a recent subscription payment with PayPal. Please %sopen a new ticket at WooCommerce Support%s immediately to get this resolved.%sIn order to get the quickest possible response please attach a %sTemporary Admin Login%s and a copy of your PHP error logs to your support ticket.%sLast recorded error: %s', 'woocommerce-subscriptions' ),
'<p>',
'<a href="https://www.woocommerce.com/my-account/create-a-ticket/" target="_blank">',
'</a>',

View File

@@ -132,8 +132,8 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler {
exit;
}
// Set a transient to block IPNs with this transaction ID for the next 5 minutes
set_transient( $ipn_lock_transient_name, 'in-progress', apply_filters( 'woocommerce_subscriptions_paypal_ipn_request_lock_time', 5 * MINUTE_IN_SECONDS ) );
// Set a transient to block IPNs with this transaction ID for the next 4 days (An IPN message may be present in PayPal up to 4 days after the original was sent)
set_transient( $ipn_lock_transient_name, 'in-progress', apply_filters( 'woocommerce_subscriptions_paypal_ipn_request_lock_time', 4 * DAY_IN_SECONDS ) );
}
@@ -284,7 +284,10 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler {
}
// Generate a renewal order to record the payment (and determine how much is due)
$renewal_order = $this->get_renewal_order_by_transaction_id( $subscription, $transaction_details['txn_id'] );
if ( is_null( $renewal_order ) ) {
$renewal_order = wcs_create_renewal_order( $subscription );
}
// Set PayPal as the payment method (we can't use $renewal_order->set_payment_method() here as it requires an object we don't have)
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
@@ -389,14 +392,18 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler {
update_post_meta( $renewal_order->id, '_transaction_id', $transaction_details['txn_id'] );
if ( 'failed' == strtolower( $transaction_details['payment_status'] ) ) {
$subscription->payment_failed();
// translators: placeholder is payment status (e.g. "completed")
$this->add_order_note( sprintf( _x( 'IPN subscription payment %s.', 'used in order note', 'woocommerce-subscriptions' ), $transaction_details['payment_status'] ), $renewal_order, $transaction_details );
$subscription->payment_failed();
} else {
$renewal_order->update_status( 'on-hold' );
// translators: placeholder is payment status (e.g. "completed")
$this->add_order_note( sprintf( _x( 'IPN subscription payment %s for reason: %s.', 'used in order note', 'woocommerce-subscriptions' ), $transaction_details['payment_status'], $transaction_details['pending_reason'] ), $renewal_order, $transaction_details );
}
}
WC_Gateway_Paypal::log( 'IPN subscription payment failed for subscription ' . $subscription->id );
WC_Gateway_Paypal::log( sprintf( 'IPN subscription payment %s for subscription %d ', $transaction_details['payment_status'], $subscription->id ) );
} else {
WC_Gateway_Paypal::log( 'IPN subscription payment notification received for subscription ' . $subscription->id . ' with status ' . $transaction_details['payment_status'] );
@@ -408,7 +415,12 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler {
// Admins can suspend subscription at PayPal triggering this IPN
case 'recurring_payment_suspended':
if ( $subscription->has_status( 'active' ) ) {
// Make sure subscription hasn't been linked to a new payment method
if ( wcs_get_paypal_id( $subscription ) != $transaction_details['subscr_id'] ) {
WC_Gateway_Paypal::log( sprintf( 'IPN "recurring_payment_suspended" ignored for subscription %d - PayPal profile ID has changed', $subscription->id ) );
} else if ( $subscription->has_status( 'active' ) ) {
// We don't need to suspend the subscription at PayPal because it's already on-hold there
remove_action( 'woocommerce_subscription_on-hold_paypal', 'WCS_PayPal_Status_Manager::suspend_subscription' );
@@ -669,4 +681,27 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler {
$order->add_order_note( $note );
}
}
/**
* Get a renewal order associated with a subscription that has a specified transaction id.
*
* @param WC_Subscription object $subscription
* @param int $transaction_id Id from transaction details as provided by PayPal
* @return WC_Order|null If order with that transaction id, WC_Order object, otherwise null
* @since 2.1
*/
protected function get_renewal_order_by_transaction_id( $subscription, $transaction_id ) {
$orders = $subscription->get_related_orders( 'all', 'renewal' );
$renewal_order = null;
foreach ( $orders as $order ) {
if ( $order->get_transaction_id() == $transaction_id ) {
$renewal_order = $order;
break;
}
}
return $renewal_order;
}
}

View File

@@ -240,10 +240,10 @@ class WCS_PayPal_Standard_Switcher {
_deprecated_function( __METHOD__, '2.1', __CLASS__ . 'cancel_paypal_standard_after_switch( $order )' );
$order_completed = in_array( $new_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id ), 'processing', 'completed' ) ) && in_array( $old_status, apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'on-hold', 'failed' ) ) );
$order = wc_get_order( $order_id );
$order_completed = in_array( $new_status, array( apply_filters( 'woocommerce_payment_complete_order_status', 'processing', $order_id ), 'processing', 'completed' ) ) && in_array( $old_status, apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'on-hold', 'failed' ), $order ) );
if ( $order_completed && wcs_order_contains_switch( $order_id ) ) {
$order = wc_get_order( $order_id );
self::cancel_paypal_standard_after_switch( $order );
}
}

View File

@@ -5,24 +5,24 @@ Plugin URI: https://github.com/prospress/action-scheduler
Description: A robust action scheduler for WordPress
Author: Prospress
Author URI: http://prospress.com/
Version: 1.5
Version: 1.5.2
*/
if ( ! function_exists( 'action_scheduler_register_1_dot_5' ) ) {
if ( ! function_exists( 'action_scheduler_register_1_dot_5_dot_2' ) ) {
if ( ! class_exists( 'ActionScheduler_Versions' ) ) {
require_once( 'classes/ActionScheduler_Versions.php' );
add_action( 'plugins_loaded', array( 'ActionScheduler_Versions', 'initialize_latest_version' ), 1, 0 );
}
add_action( 'plugins_loaded', 'action_scheduler_register_1_dot_5', 0, 0 );
add_action( 'plugins_loaded', 'action_scheduler_register_1_dot_5_dot_2', 0, 0 );
function action_scheduler_register_1_dot_5() {
function action_scheduler_register_1_dot_5_dot_2() {
$versions = ActionScheduler_Versions::instance();
$versions->register( '1.5', 'action_scheduler_initialize_1_dot_5' );
$versions->register( '1.5.2', 'action_scheduler_initialize_1_dot_5_dot_2' );
}
function action_scheduler_initialize_1_dot_5() {
function action_scheduler_initialize_1_dot_5_dot_2() {
require_once( 'classes/ActionScheduler.php' );
ActionScheduler::init( __FILE__ );
}

View File

@@ -24,6 +24,12 @@ class ActionScheduler_CronSchedule implements ActionScheduler_Schedule {
return $this->cron->getNextRunDate($after, 0, false);
}
/**
* @return bool
*/
public function is_recurring() {
return true;
}
/**
* For PHP 5.2 compat, since DateTime objects can't be serialized

View File

@@ -28,6 +28,13 @@ class ActionScheduler_IntervalSchedule implements ActionScheduler_Schedule {
return clone $this->start;
}
/**
* @return bool
*/
public function is_recurring() {
return true;
}
/**
* @param DateTime $after
*

View File

@@ -8,5 +8,12 @@ class ActionScheduler_NullSchedule implements ActionScheduler_Schedule {
public function next( DateTime $after = NULL ) {
return NULL;
}
/**
* @return bool
*/
public function is_recurring() {
return false;
}
}

View File

@@ -105,8 +105,11 @@ class ActionScheduler_QueueRunner {
}
protected function schedule_next_instance( ActionScheduler_Action $action ) {
$next = $action->get_schedule()->next( as_get_datetime_object() );
if ( $next ) {
$schedule = $action->get_schedule();
$next = $schedule->next( as_get_datetime_object() );
if ( ! is_null( $next ) && $schedule->is_recurring() ) {
$this->store->save_action( $action, $next );
}
}

View File

@@ -9,5 +9,10 @@ interface ActionScheduler_Schedule {
* @return DateTime|null
*/
public function next( DateTime $after = NULL );
/**
* @return bool
*/
public function is_recurring();
}

View File

@@ -20,6 +20,13 @@ class ActionScheduler_SimpleSchedule implements ActionScheduler_Schedule {
return ( $after > $this->date ) ? NULL : clone $this->date;
}
/**
* @return bool
*/
public function is_recurring() {
return false;
}
/**
* For PHP 5.2 compat, since DateTime objects can't be serialized
* @return array

View File

@@ -108,12 +108,37 @@ class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
*/
public function filter_comment_query_clauses( $clauses, $query ) {
if ( !empty($query->query_vars['action_log_filter']) ) {
global $wpdb;
$clauses['where'] .= sprintf(" AND {$wpdb->comments}.comment_type != '%s'", self::TYPE);
$clauses['where'] .= $this->get_where_clause();
}
return $clauses;
}
/**
* Make sure Action Scheduler logs are excluded from comment feeds, which use WP_Query, not
* the WP_Comment_Query class handled by @see self::filter_comment_queries().
*
* @param string $where
* @param WP_Query $query
*
* @return string
*/
public function filter_comment_feed( $where, $query ) {
if ( is_comment_feed() ) {
$where .= $this->get_where_clause();
}
return $where;
}
/**
* Return a SQL clause to exclude Action Scheduler comments.
*
* @return string
*/
protected function get_where_clause() {
global $wpdb;
return sprintf( " AND {$wpdb->comments}.comment_type != '%s'", self::TYPE );
}
/**
* Remove action log entries from wp_count_comments()
*
@@ -203,6 +228,7 @@ class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
add_action( 'action_scheduler_reset_action', array( $this, 'log_reset_action' ), 10, 1 );
add_action( 'pre_get_comments', array( $this, 'filter_comment_queries' ), 10, 1 );
add_action( 'wp_count_comments', array( $this, 'filter_comment_count' ), 20, 2 ); // run after WC_Comments::wp_count_comments() to make sure we exclude order notes and action logs
add_action( 'comment_feed_where', array( $this, 'filter_comment_feed' ), 10, 2 );
// Delete comments count cache whenever there is a new comment or a comment status changes
add_action( 'wp_insert_comment', array( $this, 'delete_comment_count_cache' ) );

View File

@@ -60,6 +60,7 @@ class WCS_Retry_Email {
* @since 2.1
*/
public static function send_email( $retry_rule, $last_order ) {
WC()->mailer();
// maybe send emails about the renewal payment failure
foreach ( array( 'customer', 'admin' ) as $recipient ) {

View File

@@ -173,7 +173,7 @@ class WC_Subscriptions_Upgrader {
if ( '0' != self::$active_version && version_compare( self::$active_version, '2.1.0', '<' ) ) {
// Delete cached subscription length ranges to force an update with 2.1
WC_Subscriptions::$cache->delete_cached( tlc_transient( 'wcs-sub-ranges-' . get_locale() )->key );
WC_Subscriptions::$cache->delete_cached( 'wcs-sub-ranges-' . get_locale() );
WCS_Upgrade_Logger::add( 'v2.1: Deleted cached subscription ranges.' );

View File

@@ -34,7 +34,7 @@ $settings_page = admin_url( 'admin.php?page=wc-settings&tab=subscriptions' );
<p class="woocommerce-actions">
<a href="<?php echo esc_url( $settings_page ); ?>" class="button button-primary"><?php esc_html_e( 'Settings', 'woocommerce-subscriptions' ); ?></a>
<a class="docs button button-primary" href="<?php echo esc_url( apply_filters( 'woocommerce_docs_url', 'http://docs.woocommerce.com/documentation/subscriptions/', 'woocommerce-subscriptions' ) ); ?>"><?php echo esc_html_x( 'Docs', 'short for documents', 'woocommerce-subscriptions' ); ?></a>
<a href="https://twitter.com/share" class="twitter-share-button" data-url="http://www.woocommerce.com/products/woocommerce-subscriptions/" data-text="I just upgraded to WooCommerce Subscriptions v2.0, woot!" data-via="WooThemes" data-size="large" data-hashtags="WooCommerce">Tweet</a>
<a href="https://twitter.com/share" class="twitter-share-button" data-url="http://www.woocommerce.com/products/woocommerce-subscriptions/" data-text="I just upgraded to WooCommerce Subscriptions v2.0, woot!" data-via="WooCommerce" data-size="large" data-hashtags="WooCommerce">Tweet</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
</p>
@@ -72,7 +72,7 @@ $settings_page = admin_url( 'admin.php?page=wc-settings&tab=subscriptions' );
printf( esc_html__( 'The new interface is also built on the existing %sEdit Order%s screen. If you\'ve ever modified an order, you already know how to modify a subscription.', 'woocommerce-subscriptions' ), '<strong>', '</strong>' ); ?>
</p>
<p><?php
// translators: placeholers are link tags: 1$-2$ new subscription page, 3$-4$: docs on woothemes
// translators: placeholers are link tags: 1$-2$ new subscription page, 3$-4$: docs on woocommerce.com
printf( esc_html__( '%1$sAdd a subscription%2$s now or %3$slearn more%4$s about the new interface.', 'woocommerce-subscriptions' ), '<a href="' . esc_url( admin_url( 'post-new.php?post_type=shop_subscription' ) ) . '">', '</a>', '<a href="' . esc_url( 'http://docs.woocommerce.com/document/subscriptions/version-2/#section-3' ) . '">', '</a>' ); ?>
</p>
</div>

View File

@@ -49,6 +49,7 @@ function wcs_cart_totals_shipping_html() {
foreach ( $packages as $i => $base_package ) {
$product_names = array();
$base_package['recurring_cart_key'] = $recurring_cart_key;
$package = WC_Subscriptions_Cart::get_calculated_shipping_for_package( $base_package );
$index = sprintf( '%1$s_%2$d', $recurring_cart_key, $i );
@@ -73,7 +74,7 @@ function wcs_cart_totals_shipping_html() {
?>
<tr class="shipping recurring-total <?php echo esc_attr( $recurring_cart_key ); ?>">
<th><?php echo esc_html( sprintf( __( 'Shipping via %s', 'woocommerce-subscriptions' ), $shipping_method->label ) ); ?></th>
<td>
<td data-title="<?php echo esc_attr( sprintf( __( 'Shipping via %s', 'woocommerce-subscriptions' ), $shipping_method->label ) ); ?>">
<?php echo wp_kses_post( wcs_cart_totals_shipping_method_price_label( $shipping_method, $recurring_cart ) ); ?>
<?php if ( 1 === count( $package['rates'] ) ) : ?>
<?php wcs_cart_print_shipping_input( $index, $shipping_method ); ?>

View File

@@ -264,6 +264,11 @@ function wcs_create_order_from_subscription( $subscription, $type ) {
}
}
// Backorders
if ( $product->backorders_require_notification() && $product->is_on_backorder( $item['qty'] ) ) {
wc_add_order_item_meta( $recurring_item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce-subscriptions' ) ), $item['qty'] - max( 0, $product->get_total_stock() ) );
}
do_action( 'woocommerce_order_add_product', $new_order->id, $recurring_item_id, $product, $item['qty'], $args );
}
}
@@ -534,14 +539,7 @@ function wcs_get_order_item( $item_id, $order ) {
* @since 2.0
*/
function wcs_get_order_item_meta( $item, $product = null ) {
if ( WC_Subscriptions::is_woocommerce_pre( '2.4' ) ) {
$item_meta = new WC_Order_Item_Meta( $item['item_meta'], $product );
} else {
$item_meta = new WC_Order_Item_Meta( $item, $product );
}
return $item_meta;
return new WC_Order_Item_Meta( $item, $product );
}
/**

View File

@@ -70,10 +70,9 @@ function wcs_get_subscription_trial_period_strings( $number = 1, $period = '' )
* M for months; allowable range is 1 to 24
* Y for years; allowable range is 1 to 5
*
* @param string (optional) One of day, week, month or year. If empty, all subscription ranges are returned.
* @since 2.0
* @since 2.1.2
*/
function wcs_get_subscription_ranges_tlc() {
function wcs_get_non_cached_subscription_ranges() {
foreach ( array( 'day', 'week', 'month', 'year' ) as $period ) {
@@ -126,7 +125,7 @@ function wcs_get_subscription_ranges( $subscription_period = '' ) {
$locale = get_locale();
$subscription_ranges = WC_Subscriptions::$cache->cache_and_get( 'wcs-sub-ranges-' . $locale, 'wcs_get_subscription_ranges_tlc', array(), 3 * HOUR_IN_SECONDS );
$subscription_ranges = WC_Subscriptions::$cache->cache_and_get( 'wcs-sub-ranges-' . $locale, 'wcs_get_non_cached_subscription_ranges', array(), 3 * HOUR_IN_SECONDS );
$subscription_ranges = apply_filters( 'woocommerce_subscription_lengths', $subscription_ranges, $subscription_period );
@@ -758,3 +757,23 @@ function wcs_get_sites_timezone() {
return $local_timezone;
}
/* Deprecated Functions */
/**
* Returns an array of subscription lengths.
*
* PayPal Standard Allowable Ranges
* D for days; allowable range is 1 to 90
* W for weeks; allowable range is 1 to 52
* M for months; allowable range is 1 to 24
* Y for years; allowable range is 1 to 5
*
* @param string (optional) One of day, week, month or year. If empty, all subscription ranges are returned.
* @since 2.0
*/
function wcs_get_subscription_ranges_tlc() {
_deprecated_function( __FUNCTION__, '2.1.2', 'wcs_get_non_cached_subscription_ranges' );
return wcs_get_non_cached_subscription_ranges();
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
* Pay for order form displayed after a customer has clicked the "Change Payment method" button
* next to a subscription on their My Account page.
*
* @author WooThemes
* @author Prospress
* @package WooCommerce/Templates
* @version 1.6.4
*/

View File

@@ -26,8 +26,10 @@ $display_th = true;
<tr class="cart-subtotal recurring-total">
<?php if ( $display_th ) : $display_th = false; ?>
<th rowspan="<?php echo esc_attr( $carts_with_multiple_payments ); ?>"><?php esc_html_e( 'Subtotal', 'woocommerce-subscriptions' ); ?></th>
<?php endif; ?>
<td data-title="<?php esc_attr_e( 'Subtotal', 'woocommerce-subscriptions' ); ?>"><?php wcs_cart_totals_subtotal_html( $recurring_cart ); ?></td>
<?php else : ?>
<td><?php wcs_cart_totals_subtotal_html( $recurring_cart ); ?></td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
<?php $display_th = true; ?>
@@ -42,8 +44,10 @@ $display_th = true;
<tr class="cart-discount coupon-<?php echo esc_attr( $code ); ?> recurring-total">
<?php if ( $display_th ) : $display_th = false; ?>
<th rowspan="<?php echo esc_attr( $carts_with_multiple_payments ); ?>"><?php wc_cart_totals_coupon_label( $coupon ); ?></th>
<?php endif; ?>
<td data-title="<?php wc_cart_totals_coupon_label( $coupon ); ?>"><?php wcs_cart_totals_coupon_html( $recurring_coupon, $recurring_cart ); ?></td>
<?php else : ?>
<td><?php wcs_cart_totals_coupon_html( $recurring_coupon, $recurring_cart ); ?></td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
<?php endforeach; ?>
@@ -67,10 +71,11 @@ $display_th = true;
<tr class="tax-rate tax-rate-<?php echo esc_attr( sanitize_title( $recurring_code ) ); ?> recurring-total">
<?php if ( $display_th ) : $display_th = false; ?>
<th><?php echo esc_html( $recurring_tax->label ); ?></th>
<td data-title="<?php echo esc_attr( $recurring_tax->label ); ?>"><?php echo wp_kses_post( wcs_cart_price_string( $recurring_tax->formatted_amount, $recurring_cart ) ); ?></td>
<?php else : ?>
<th></th>
<?php endif; ?>
<td><?php echo wp_kses_post( wcs_cart_price_string( $recurring_tax->formatted_amount, $recurring_cart ) ); ?></td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
<?php endforeach; ?>
@@ -86,10 +91,11 @@ $display_th = true;
<tr class="tax-total recurring-total">
<?php if ( $display_th ) : $display_th = false; ?>
<th><?php echo esc_html( WC()->countries->tax_or_vat() ); ?></th>
<td data-title="<?php echo esc_attr( WC()->countries->tax_or_vat() ); ?>"><?php echo wp_kses_post( wcs_cart_price_string( $recurring_cart->get_taxes_total(), $recurring_cart ) ); ?></td>
<?php else : ?>
<th></th>
<?php endif; ?>
<td><?php echo wp_kses_post( wcs_cart_price_string( $recurring_cart->get_taxes_total(), $recurring_cart ) ); ?></td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
<?php $display_th = true; ?>
@@ -103,7 +109,9 @@ $display_th = true;
<tr class="order-total recurring-total">
<?php if ( $display_th ) : $display_th = false; ?>
<th rowspan="<?php echo esc_attr( $carts_with_multiple_payments ); ?>"><?php esc_html_e( 'Recurring Total', 'woocommerce-subscriptions' ); ?></th>
<?php endif; ?>
<td data-title="<?php esc_attr_e( 'Recurring Total', 'woocommerce-subscriptions' ); ?>"><?php wcs_cart_totals_order_total_html( $recurring_cart ); ?></td>
<?php else : ?>
<td><?php wcs_cart_totals_order_total_html( $recurring_cart ); ?></td>
<?php endif; ?>
</tr>
<?php endforeach; ?>

View File

@@ -2,7 +2,7 @@
/**
* Subscription Product Add to Cart
*
* @author WooThemes
* @author Prospress
* @package WooCommerce-Subscriptions/Templates
* @version 2.0.18
*/

View File

@@ -2,7 +2,7 @@
/**
* Variable subscription product add to cart
*
* @author WooThemes
* @author Prospress
* @package WooCommerce-Subscriptions/Templates
* @version 2.0.9
*/

View File

@@ -5,7 +5,7 @@
* Description: Sell products and services with recurring payments in your WooCommerce Store.
* Author: Prospress Inc.
* Author URI: http://prospress.com/
* Version: 2.1.1
* Version: 2.1.4
*
* Copyright 2016 Prospress, Inc. (email : freedoms@prospress.com)
*
@@ -95,7 +95,7 @@ require_once( 'includes/class-wcs-action-scheduler.php' );
require_once( 'includes/abstracts/abstract-wcs-cache-manager.php' );
require_once( 'includes/class-wcs-cache-manager-tlc.php' );
require_once( 'includes/class-wcs-cached-data-manager.php' );
require_once( 'includes/class-wcs-cart-renewal.php' );
@@ -126,7 +126,7 @@ class WC_Subscriptions {
public static $plugin_file = __FILE__;
public static $version = '2.1.1';
public static $version = '2.1.4';
private static $total_subscription_count = null;
@@ -411,7 +411,7 @@ class WC_Subscriptions {
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['product_id'] ) ) {
if ( WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) {
WC()->cart->set_quantity( $cart_item_key, 0 );
}
}
@@ -962,7 +962,11 @@ class WC_Subscriptions {
*/
public static function get_current_sites_duplicate_lock() {
$site_url = get_option( 'siteurl' );
if ( defined( 'WP_SITEURL' ) ) {
$site_url = WP_SITEURL;
} else {
$site_url = get_site_url();
}
return substr_replace( $site_url, '_[wc_subscriptions_siteurl]_', strlen( $site_url ) / 2, 0 );
}