diff --git a/assets/css/checkout.css b/assets/css/checkout.css index ec51784..104dfaa 100644 --- a/assets/css/checkout.css +++ b/assets/css/checkout.css @@ -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:""; +} diff --git a/changelog.txt b/changelog.txt index c847943..b48dfdb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -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 diff --git a/includes/abstracts/abstract-wcs-cache-manager.php b/includes/abstracts/abstract-wcs-cache-manager.php index b3b2500..c804404 100644 --- a/includes/abstracts/abstract-wcs-cache-manager.php +++ b/includes/abstracts/abstract-wcs-cache-manager.php @@ -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; } diff --git a/includes/admin/class-wc-subscriptions-admin.php b/includes/admin/class-wc-subscriptions-admin.php index 167fcc0..23a933d 100644 --- a/includes/admin/class-wc-subscriptions-admin.php +++ b/includes/admin/class-wc-subscriptions-admin.php @@ -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' ), '', '' ), + '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' ), '', '' ), '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 {

- Tweet + Tweet

@@ -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' ), '', '', '', '' ), 'id' => WC_Subscriptions_Admin::$option_prefix . '_payment_gateways_additional', 'type' => 'informational', diff --git a/includes/admin/class-wcs-admin-meta-boxes.php b/includes/admin/class-wcs-admin-meta-boxes.php index 254a9df..a43ab9d 100644 --- a/includes/admin/class-wcs-admin-meta-boxes.php +++ b/includes/admin/class-wcs-admin-meta-boxes.php @@ -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; diff --git a/includes/admin/meta-boxes/class-wcs-meta-box-subscription-data.php b/includes/admin/meta-boxes/class-wcs-meta-box-subscription-data.php index 0408f22..4460376 100644 --- a/includes/admin/meta-boxes/class-wcs-meta-box-subscription-data.php +++ b/includes/admin/meta-boxes/class-wcs-meta-box-subscription-data.php @@ -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'] ) ); diff --git a/includes/class-wc-subscription.php b/includes/class-wc-subscription.php index 9dabe2b..8533d51 100644 --- a/includes/class-wc-subscription.php +++ b/includes/class-wc-subscription.php @@ -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; + } } diff --git a/includes/class-wc-subscriptions-cart.php b/includes/class-wc-subscriptions-cart.php index 4c5d4db..d1d11ce 100644 --- a/includes/class-wc-subscriptions-cart.php +++ b/includes/class-wc-subscriptions-cart.php @@ -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 - $calculation_type = self::$calculation_type; - self::$calculation_type = 'recurring_total'; + // 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 ) { @@ -1094,7 +1097,8 @@ class WC_Subscriptions_Cart { } } - self::$calculation_type = $calculation_type; + self::$calculation_type = $calculation_type; + self::$recurring_cart_key = $recurring_cart_key_flag; } /** diff --git a/includes/class-wc-subscriptions-email.php b/includes/class-wc-subscriptions-email.php index daacd84..41e8d4c 100644 --- a/includes/class-wc-subscriptions-email.php +++ b/includes/class-wc-subscriptions-email.php @@ -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 ); } } diff --git a/includes/class-wc-subscriptions-order.php b/includes/class-wc-subscriptions-order.php index c419918..87cbb52 100644 --- a/includes/class-wc-subscriptions-order.php +++ b/includes/class-wc-subscriptions-order.php @@ -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 */ /** diff --git a/includes/class-wc-subscriptions-renewal-order.php b/includes/class-wc-subscriptions-renewal-order.php index 14ed2de..ff8d775 100644 --- a/includes/class-wc-subscriptions-renewal-order.php +++ b/includes/class-wc-subscriptions-renewal-order.php @@ -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( diff --git a/includes/class-wc-subscriptions-switcher.php b/includes/class-wc-subscriptions-switcher.php index e138612..12214c5 100644 --- a/includes/class-wc-subscriptions-switcher.php +++ b/includes/class-wc-subscriptions-switcher.php @@ -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, '' ); + $item_id = WC_Subscriptions_Checkout::add_cart_item( $subscription, $cart_item, $cart_item_key ); + 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,27 +719,36 @@ 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; + } + } + + $subscription->set_total( $subscription_shipping_total, 'shipping' ); + + $switch_order_data[ $subscription->id ]['shipping_line_items'] = $new_shipping_line_items; } } - // 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' ); - foreach ( $switch_order_data as $subscription_id => $switch_data ) { // Cancel all the switch orders linked to the switched subscription(s) which haven't been completed yet - excluding this one. @@ -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, $switched_item_data['add_line_item'], $switched_item_data['remove_line_item'] ); } - do_action( 'woocommerce_subscription_item_switched', $order, $subscription, $order_item_id, $switch_item_data['subscription_item_id'] ); - } + // remove the existing subscription item + $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 ); - // remove the existing subscription item - $old_order_item = wcs_get_order_item( $switch_item_data['subscription_item_id'], $subscription ); + 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 don't want to include switch item meta in order item name + add_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_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' ); - if ( empty( $old_order_item ) ) { - throw new Exception( __( 'The original subscription item being switched cannot be found.', 'woocommerce-subscriptions' ) ); - } else { - // We dont 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( $switched_item_data['remove_line_item'], array( 'order_item_type' => 'line_item_switched' ) ); - 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 ) ); + // 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_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 **/ /** diff --git a/includes/class-wc-subscriptions-synchroniser.php b/includes/class-wc-subscriptions-synchroniser.php index c9c3c53..a309583 100644 --- a/includes/class-wc-subscriptions-synchroniser.php +++ b/includes/class-wc-subscriptions-synchroniser.php @@ -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 * diff --git a/includes/class-wcs-cache-manager-tlc.php b/includes/class-wcs-cache-manager-tlc.php index 872829c..49c4b3a 100644 --- a/includes/class-wcs-cache-manager-tlc.php +++ b/includes/class-wcs-cache-manager-tlc.php @@ -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; } diff --git a/includes/class-wcs-cached-data-manager.php b/includes/class-wcs-cached-data-manager.php new file mode 100644 index 0000000..2c1733a --- /dev/null +++ b/includes/class-wcs-cached-data-manager.php @@ -0,0 +1,178 @@ +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 ); + } + } + } + } +} diff --git a/includes/class-wcs-cart-renewal.php b/includes/class-wcs-cart-renewal.php index 3220e8c..b390cec 100644 --- a/includes/class-wcs-cart-renewal.php +++ b/includes/class-wcs-cart-renewal.php @@ -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 */ /** diff --git a/includes/class-wcs-cart-resubscribe.php b/includes/class-wcs-cart-resubscribe.php index b3c960b..1280239 100644 --- a/includes/class-wcs-cart-resubscribe.php +++ b/includes/class-wcs-cart-resubscribe.php @@ -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 ); } } diff --git a/includes/class-wcs-cart-switch.php b/includes/class-wcs-cart-switch.php index eaed1d1..453f219 100644 --- a/includes/class-wcs-cart-switch.php +++ b/includes/class-wcs-cart-switch.php @@ -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; } } diff --git a/includes/class-wcs-download-handler.php b/includes/class-wcs-download-handler.php index 017d196..3b7eb82 100644 --- a/includes/class-wcs-download-handler.php +++ b/includes/class-wcs-download-handler.php @@ -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 */ diff --git a/includes/class-wcs-query.php b/includes/class-wcs-query.php index ad6bd5d..2717ce9 100644 --- a/includes/class-wcs-query.php +++ b/includes/class-wcs-query.php @@ -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' ); + } } /** diff --git a/includes/class-wcs-retry-manager.php b/includes/class-wcs-retry-manager.php index 5a9d7f4..907cdb1 100644 --- a/includes/class-wcs-retry-manager.php +++ b/includes/class-wcs-retry-manager.php @@ -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' ) ); diff --git a/includes/emails/class-wcs-email-customer-completed-renewal-order.php b/includes/emails/class-wcs-email-customer-completed-renewal-order.php index 8ea9fc0..0e797ab 100644 --- a/includes/emails/class-wcs-email-customer-completed-renewal-order.php +++ b/includes/emails/class-wcs-email-customer-completed-renewal-order.php @@ -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 { diff --git a/includes/emails/class-wcs-email-customer-completed-switch-order.php b/includes/emails/class-wcs-email-customer-completed-switch-order.php index 3b7b95a..cf2a612 100644 --- a/includes/emails/class-wcs-email-customer-completed-switch-order.php +++ b/includes/emails/class-wcs-email-customer-completed-switch-order.php @@ -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(); diff --git a/includes/emails/class-wcs-email-customer-processing-renewal-order.php b/includes/emails/class-wcs-email-customer-processing-renewal-order.php index e97c2db..35c2830 100644 --- a/includes/emails/class-wcs-email-customer-processing-renewal-order.php +++ b/includes/emails/class-wcs-email-customer-processing-renewal-order.php @@ -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 { diff --git a/includes/emails/class-wcs-email-new-switch-order.php b/includes/emails/class-wcs-email-new-switch-order.php index 4e8c1a5..0ff791d 100644 --- a/includes/emails/class-wcs-email-new-switch-order.php +++ b/includes/emails/class-wcs-email-new-switch-order.php @@ -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(); diff --git a/includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php b/includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php index fffbb12..87c1b6d 100644 --- a/includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php +++ b/includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php @@ -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' ), '', '', @@ -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' ), '', '', @@ -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' ), '', '', @@ -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' ), '

', '', '', diff --git a/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php b/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php index ec4f5b1..ce960c2 100644 --- a/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php +++ b/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php @@ -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 = wcs_create_renewal_order( $subscription ); + $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'] ); - // 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(); + 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 ); + } 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; + } } diff --git a/includes/gateways/paypal/includes/class-wcs-paypal-standard-switcher.php b/includes/gateways/paypal/includes/class-wcs-paypal-standard-switcher.php index f00348a..bf017b9 100644 --- a/includes/gateways/paypal/includes/class-wcs-paypal-standard-switcher.php +++ b/includes/gateways/paypal/includes/class-wcs-paypal-standard-switcher.php @@ -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 ); } } diff --git a/includes/libraries/action-scheduler/action-scheduler.php b/includes/libraries/action-scheduler/action-scheduler.php index 41b60a0..4536553 100644 --- a/includes/libraries/action-scheduler/action-scheduler.php +++ b/includes/libraries/action-scheduler/action-scheduler.php @@ -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__ ); } diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_CronSchedule.php b/includes/libraries/action-scheduler/classes/ActionScheduler_CronSchedule.php index cfcc9eb..029c0d5 100644 --- a/includes/libraries/action-scheduler/classes/ActionScheduler_CronSchedule.php +++ b/includes/libraries/action-scheduler/classes/ActionScheduler_CronSchedule.php @@ -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 diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_IntervalSchedule.php b/includes/libraries/action-scheduler/classes/ActionScheduler_IntervalSchedule.php index 3eae7bc..eb68a96 100644 --- a/includes/libraries/action-scheduler/classes/ActionScheduler_IntervalSchedule.php +++ b/includes/libraries/action-scheduler/classes/ActionScheduler_IntervalSchedule.php @@ -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 * diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_NullSchedule.php b/includes/libraries/action-scheduler/classes/ActionScheduler_NullSchedule.php index baaab2b..fab1538 100644 --- a/includes/libraries/action-scheduler/classes/ActionScheduler_NullSchedule.php +++ b/includes/libraries/action-scheduler/classes/ActionScheduler_NullSchedule.php @@ -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; + } } \ No newline at end of file diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_QueueRunner.php b/includes/libraries/action-scheduler/classes/ActionScheduler_QueueRunner.php index 658e5fd..99e8f06 100644 --- a/includes/libraries/action-scheduler/classes/ActionScheduler_QueueRunner.php +++ b/includes/libraries/action-scheduler/classes/ActionScheduler_QueueRunner.php @@ -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 ); } } diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_Schedule.php b/includes/libraries/action-scheduler/classes/ActionScheduler_Schedule.php index b40e4e4..d61a9f7 100644 --- a/includes/libraries/action-scheduler/classes/ActionScheduler_Schedule.php +++ b/includes/libraries/action-scheduler/classes/ActionScheduler_Schedule.php @@ -9,5 +9,10 @@ interface ActionScheduler_Schedule { * @return DateTime|null */ public function next( DateTime $after = NULL ); + + /** + * @return bool + */ + public function is_recurring(); } \ No newline at end of file diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_SimpleSchedule.php b/includes/libraries/action-scheduler/classes/ActionScheduler_SimpleSchedule.php index 58bf462..8f621e1 100644 --- a/includes/libraries/action-scheduler/classes/ActionScheduler_SimpleSchedule.php +++ b/includes/libraries/action-scheduler/classes/ActionScheduler_SimpleSchedule.php @@ -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 diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_wpCommentLogger.php b/includes/libraries/action-scheduler/classes/ActionScheduler_wpCommentLogger.php index 6f34371..58137a0 100644 --- a/includes/libraries/action-scheduler/classes/ActionScheduler_wpCommentLogger.php +++ b/includes/libraries/action-scheduler/classes/ActionScheduler_wpCommentLogger.php @@ -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' ) ); diff --git a/includes/payment-retry/class-wcs-retry-email.php b/includes/payment-retry/class-wcs-retry-email.php index 29b975f..2565669 100644 --- a/includes/payment-retry/class-wcs-retry-email.php +++ b/includes/payment-retry/class-wcs-retry-email.php @@ -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 ) { diff --git a/includes/upgrades/class-wc-subscriptions-upgrader.php b/includes/upgrades/class-wc-subscriptions-upgrader.php index 648bd2b..c3a8c7b 100644 --- a/includes/upgrades/class-wc-subscriptions-upgrader.php +++ b/includes/upgrades/class-wc-subscriptions-upgrader.php @@ -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.' ); diff --git a/includes/upgrades/templates/wcs-about-2-0.php b/includes/upgrades/templates/wcs-about-2-0.php index f59daff..f3c4ab5 100644 --- a/includes/upgrades/templates/wcs-about-2-0.php +++ b/includes/upgrades/templates/wcs-about-2-0.php @@ -34,7 +34,7 @@ $settings_page = admin_url( 'admin.php?page=wc-settings&tab=subscriptions' );

- Tweet + Tweet

@@ -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' ), '', '' ); ?>

', '', '', '' ); ?>

diff --git a/includes/wcs-cart-functions.php b/includes/wcs-cart-functions.php index d530857..4e039c5 100644 --- a/includes/wcs-cart-functions.php +++ b/includes/wcs-cart-functions.php @@ -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() { ?> label ) ); ?> - + diff --git a/includes/wcs-order-functions.php b/includes/wcs-order-functions.php index c5b4461..7028d67 100644 --- a/includes/wcs-order-functions.php +++ b/includes/wcs-order-functions.php @@ -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 ); } /** diff --git a/includes/wcs-time-functions.php b/includes/wcs-time-functions.php index 4f8f215..f0abbf8 100644 --- a/includes/wcs-time-functions.php +++ b/includes/wcs-time-functions.php @@ -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(); +} diff --git a/languages/woocommerce-subscriptions.pot b/languages/woocommerce-subscriptions.pot index 7e17d6a..17eabfa 100644 --- a/languages/woocommerce-subscriptions.pot +++ b/languages/woocommerce-subscriptions.pot @@ -1,15 +1,15 @@ -# Copyright (C) 2016 Prospress Inc. +# Copyright (C) 2017 Prospress Inc. # This file is distributed under the same license as the WooCommerce Subscriptions package. msgid "" msgstr "" -"Project-Id-Version: WooCommerce Subscriptions 2.1.1\n" +"Project-Id-Version: WooCommerce Subscriptions 2.1.4\n" "Report-Msgid-Bugs-To: " "https://github.com/Prospress/woocommerce-subscriptions/issues\n" -"POT-Creation-Date: 2016-11-25 22:40:36+00:00\n" +"POT-Creation-Date: 2017-03-06 22:34:02+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2016-MO-DA HO:MI+ZONE\n" +"PO-Revision-Date: 2017-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: Prospress Translations \n" "X-Generator: grunt-wp-i18n 0.5.4\n" @@ -110,33 +110,33 @@ msgstr "" msgid "Free trial period" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:666 +#: includes/admin/class-wc-subscriptions-admin.php:661 msgid "" "Unable to change subscription status to \"%s\". Please assign a customer to " "the subscription to activate it." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:708 +#: includes/admin/class-wc-subscriptions-admin.php:703 msgid "" "Trashing this order will also trash the subscriptions purchased with the " "order." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:721 +#: includes/admin/class-wc-subscriptions-admin.php:716 msgid "Enter the new period, either day, week, month or year:" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:722 +#: includes/admin/class-wc-subscriptions-admin.php:717 msgid "Enter a new length (e.g. 5):" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:723 +#: includes/admin/class-wc-subscriptions-admin.php:718 msgid "" "Enter a new interval as a single number (e.g. to charge every 2nd month, " "enter 2):" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:728 +#: includes/admin/class-wc-subscriptions-admin.php:723 msgid "" "You are about to trash one or more orders which contain a subscription.\n" "\n" @@ -144,7 +144,7 @@ msgid "" "orders." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:741 +#: includes/admin/class-wc-subscriptions-admin.php:736 msgid "" "WARNING: Bad things are about to happen!\n" "\n" @@ -156,13 +156,13 @@ msgid "" "gateway." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:742 +#: includes/admin/class-wc-subscriptions-admin.php:737 msgid "" "You are deleting a subscription item. You will also need to manually cancel " "and trash the subscription on the Manage Subscriptions screen." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:749 +#: includes/admin/class-wc-subscriptions-admin.php:744 msgid "" "Warning: Deleting a user will also delete the user's subscriptions. The " "user's orders will remain but be reassigned to the 'Guest' user.\n" @@ -171,46 +171,46 @@ msgid "" "subscriptions?" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:812 +#: includes/admin/class-wc-subscriptions-admin.php:807 msgid "Active subscriber?" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:855 +#: includes/admin/class-wc-subscriptions-admin.php:850 msgid "Manage Subscriptions" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:859 +#: includes/admin/class-wc-subscriptions-admin.php:854 #: woocommerce-subscriptions.php:214 msgid "Search Subscriptions" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:879 -#: includes/admin/class-wc-subscriptions-admin.php:975 +#: includes/admin/class-wc-subscriptions-admin.php:874 +#: includes/admin/class-wc-subscriptions-admin.php:970 #: includes/admin/class-wcs-admin-reports.php:55 #: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:654 -#: includes/class-wcs-query.php:93 includes/class-wcs-query.php:113 -#: includes/class-wcs-query.php:115 woocommerce-subscriptions.php:205 +#: includes/class-wcs-query.php:95 includes/class-wcs-query.php:115 +#: includes/class-wcs-query.php:117 woocommerce-subscriptions.php:205 #: woocommerce-subscriptions.php:218 msgid "Subscriptions" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1015 +#: includes/admin/class-wc-subscriptions-admin.php:1010 msgid "Button Text" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1022 +#: includes/admin/class-wc-subscriptions-admin.php:1017 msgid "Add to Cart Button Text" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1023 +#: includes/admin/class-wc-subscriptions-admin.php:1018 msgid "" "A product displays a button with the text \"Add to Cart\". By default, a " "subscription changes this to \"Sign Up Now\". You can customise the button " "text for subscriptions here." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1027 -#: includes/admin/class-wc-subscriptions-admin.php:1038 +#: includes/admin/class-wc-subscriptions-admin.php:1022 +#: includes/admin/class-wc-subscriptions-admin.php:1033 #: includes/class-wc-product-subscription-variation.php:75 #: includes/class-wc-product-subscription.php:122 #: includes/class-wc-product-variable-subscription.php:108 @@ -219,11 +219,11 @@ msgstr "" msgid "Sign Up Now" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1033 +#: includes/admin/class-wc-subscriptions-admin.php:1028 msgid "Place Order Button Text" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1034 +#: includes/admin/class-wc-subscriptions-admin.php:1029 msgid "" "Use this field to customise the text displayed on the checkout button when " "an order contains a subscription. Normally the checkout submission button " @@ -231,11 +231,11 @@ msgid "" "changed to \"Sign Up Now\"." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1046 +#: includes/admin/class-wc-subscriptions-admin.php:1041 msgid "Roles" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1049 +#: includes/admin/class-wc-subscriptions-admin.php:1044 #. translators: placeholders are tags msgid "" "Choose the default roles to assign to active and inactive subscribers. For " @@ -244,89 +244,90 @@ msgid "" "allocated these roles to prevent locking out administrators." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1054 +#: includes/admin/class-wc-subscriptions-admin.php:1049 msgid "Subscriber Default Role" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1055 +#: includes/admin/class-wc-subscriptions-admin.php:1050 msgid "" "When a subscription is activated, either manually or after a successful " "purchase, new users will be assigned this role." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1066 +#: includes/admin/class-wc-subscriptions-admin.php:1061 msgid "Inactive Subscriber Role" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1067 +#: includes/admin/class-wc-subscriptions-admin.php:1062 msgid "" "If a subscriber's subscription is manually cancelled or expires, she will " "be assigned this role." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1087 +#: includes/admin/class-wc-subscriptions-admin.php:1082 msgid "Manual Renewal Payments" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1088 +#: includes/admin/class-wc-subscriptions-admin.php:1083 msgid "Accept Manual Renewals" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1093 +#: includes/admin/class-wc-subscriptions-admin.php:1088 #. translators: placeholders are opening and closing link tags msgid "" "With manual renewals, a customer's subscription is put on-hold until they " "login and pay to renew it. %sLearn more%s." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1099 +#: includes/admin/class-wc-subscriptions-admin.php:1094 msgid "Turn off Automatic Payments" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1104 +#: includes/admin/class-wc-subscriptions-admin.php:1099 #. translators: placeholders are opening and closing link tags msgid "" -"If you never want a customer to be automatically charged for a subscription " -"renewal payment, you can turn off automatic payments completely. %sLearn " +"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." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1119 +#: includes/admin/class-wc-subscriptions-admin.php:1114 msgid "Customer Suspensions" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1126 +#: includes/admin/class-wc-subscriptions-admin.php:1121 msgid "" "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." +"Store managers will always be able to suspend an active subscription. Set " +"this to 0 to turn off the customer suspension feature completely." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1130 +#: includes/admin/class-wc-subscriptions-admin.php:1125 msgid "Mixed Checkout" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1131 +#: includes/admin/class-wc-subscriptions-admin.php:1126 msgid "Allow subscriptions and products to be purchased simultaneously." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1135 +#: includes/admin/class-wc-subscriptions-admin.php:1130 msgid "Allow subscriptions and products to be purchased in a single transaction." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1139 +#: includes/admin/class-wc-subscriptions-admin.php:1134 #: includes/upgrades/templates/wcs-about-2-0.php:108 msgid "Drip Downloadable Content" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1140 +#: includes/admin/class-wc-subscriptions-admin.php:1135 msgid "Enable dripping for downloadable content on subscription products." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1144 +#: includes/admin/class-wc-subscriptions-admin.php:1139 msgid "" "Enabling this grants access to new downloadable files added to a product " "only after the next renewal is processed.%sBy default, access to new " @@ -334,7 +335,7 @@ msgid "" "customer that has an active subscription with that product." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1180 +#: includes/admin/class-wc-subscriptions-admin.php:1175 #. translators: $1-$2: opening and closing tags, $3-$4: opening and #. closing tags msgid "" @@ -342,59 +343,59 @@ msgid "" "start selling subscriptions!%4$s" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1185 +#: includes/admin/class-wc-subscriptions-admin.php:1180 msgid "Add a Subscription Product" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1186 +#: includes/admin/class-wc-subscriptions-admin.php:1181 #: includes/upgrades/templates/wcs-about-2-0.php:35 #: includes/upgrades/templates/wcs-about.php:34 #: woocommerce-subscriptions.php:944 msgid "Settings" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1272 +#: includes/admin/class-wc-subscriptions-admin.php:1267 #. translators: placeholder is a number msgid "We can't find a subscription with ID #%d. Perhaps it was deleted?" msgstr "" +#: includes/admin/class-wc-subscriptions-admin.php:1300 #: includes/admin/class-wc-subscriptions-admin.php:1305 -#: includes/admin/class-wc-subscriptions-admin.php:1310 #. translators: placeholders are opening link tag, ID of sub, and closing link #. tag msgid "Showing orders for %sSubscription %s%s" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1334 +#: includes/admin/class-wc-subscriptions-admin.php:1329 #. translators: number of 1$: days, 2$: weeks, 3$: months, 4$: years msgid "The trial period can not exceed: %1s, %2s, %3s or %4s." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1339 +#: includes/admin/class-wc-subscriptions-admin.php:1334 #. translators: placeholder is a time period (e.g. "4 weeks") msgid "The trial period can not exceed %s." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1395 -#: includes/admin/class-wc-subscriptions-admin.php:1448 +#: includes/admin/class-wc-subscriptions-admin.php:1390 +#: includes/admin/class-wc-subscriptions-admin.php:1443 msgid "Yes" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1395 +#: includes/admin/class-wc-subscriptions-admin.php:1390 msgid "No" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1431 +#: includes/admin/class-wc-subscriptions-admin.php:1426 msgid "Automatic Recurring Payments" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1448 +#: includes/admin/class-wc-subscriptions-admin.php:1443 msgid "" "Supports automatic renewal payments with the WooCommerce Subscriptions " "extension." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1500 +#: includes/admin/class-wc-subscriptions-admin.php:1495 #. translators: $1-2: opening and closing tags of a link that takes to Woo #. marketplace / Stripe product page msgid "" @@ -403,21 +404,21 @@ msgid "" "the %1$sfree Stripe extension%2$s." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1505 +#: includes/admin/class-wc-subscriptions-admin.php:1500 msgid "Recurring Payments" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1513 +#: includes/admin/class-wc-subscriptions-admin.php:1508 #. translators: placeholders are opening and closing link tags msgid "" "Payment gateways which don't support automatic recurring payments can be " "used to process %smanual subscription renewal payments%s." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1520 +#: includes/admin/class-wc-subscriptions-admin.php:1515 #. translators: $1-$2: opening and closing tags. Link to documents->payment -#. gateways, 3$-4$: opening and closing tags. Link to woothemes extensions shop -#. page +#. gateways, 3$-4$: opening and closing tags. Link to WooCommerce extensions +#. shop page msgid "" "Find new gateways that %1$ssupport automatic subscription payments%2$s in " "the official %3$sWooCommerce Marketplace%4$s." @@ -762,7 +763,7 @@ msgstr "" msgid "Customer's notes about the order" msgstr "" -#: includes/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:275 +#: includes/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:278 #. translators: placeholder is error message from the payment gateway or #. subscriptions when updating the status msgid "Error updating some information: %s" @@ -1379,100 +1380,104 @@ msgstr "" msgid "%1$s Status changed from %2$s to %3$s." msgstr "" -#: includes/class-wc-subscription.php:409 +#: includes/class-wc-subscription.php:415 msgid "Unable to change subscription status to \"%s\". Exception: %s" msgstr "" -#: includes/class-wc-subscription.php:697 +#: includes/class-wc-subscription.php:703 #: includes/class-wc-subscriptions-manager.php:2212 #: includes/wcs-formatting-functions.php:228 #. translators: placeholder is human time diff (e.g. "3 weeks") msgid "In %s" msgstr "" -#: includes/class-wc-subscription.php:700 +#: includes/class-wc-subscription.php:706 #: includes/wcs-formatting-functions.php:231 #. translators: placeholder is human time diff (e.g. "3 weeks") msgid "%s ago" msgstr "" -#: includes/class-wc-subscription.php:707 +#: includes/class-wc-subscription.php:713 msgid "Not yet ended" msgstr "" -#: includes/class-wc-subscription.php:710 +#: includes/class-wc-subscription.php:716 msgid "Not cancelled" msgstr "" -#: includes/class-wc-subscription.php:753 -msgid "Invalid format. First parameter needs to be an array." -msgstr "" - -#: includes/class-wc-subscription.php:757 -msgid "Invalid data. First parameter was empty when passed to update_dates()." -msgstr "" - -#: includes/class-wc-subscription.php:764 -msgid "" -"Invalid data. First parameter has a date that is not in the registered date " -"types." -msgstr "" - #: includes/class-wc-subscription.php:812 -msgid "The %s date must occur after the cancellation date." -msgstr "" - -#: includes/class-wc-subscription.php:817 -msgid "The %s date must occur after the last payment date." -msgstr "" - -#: includes/class-wc-subscription.php:821 -msgid "The %s date must occur after the next payment date." -msgstr "" - -#: includes/class-wc-subscription.php:826 -msgid "The %s date must occur after the trial end date." -msgstr "" - -#: includes/class-wc-subscription.php:830 -msgid "The %s date must occur after the start date." -msgstr "" - -#: includes/class-wc-subscription.php:883 msgid "The start date of a subscription can not be deleted, only updated." msgstr "" -#: includes/class-wc-subscription.php:886 +#: includes/class-wc-subscription.php:815 msgid "" "The last payment date of a subscription can not be deleted. You must delete " "the order." msgstr "" -#: includes/class-wc-subscription.php:1293 +#: includes/class-wc-subscription.php:1226 msgid "Sign-up complete." msgstr "" -#: includes/class-wc-subscription.php:1295 +#: includes/class-wc-subscription.php:1228 msgid "Payment received." msgstr "" -#: includes/class-wc-subscription.php:1326 +#: includes/class-wc-subscription.php:1259 msgid "Payment failed." msgstr "" -#: includes/class-wc-subscription.php:1330 +#: includes/class-wc-subscription.php:1263 msgid "Subscription Cancelled: maximum number of failed payments reached." msgstr "" -#: includes/class-wc-subscription.php:1525 +#: includes/class-wc-subscription.php:1458 #: includes/class-wcs-change-payment-method-admin.php:155 msgid "Manual Renewal" msgstr "" -#: includes/class-wc-subscription.php:1590 +#: includes/class-wc-subscription.php:1523 msgid "Payment method meta must be an array." msgstr "" +#: includes/class-wc-subscription.php:1746 +msgid "Invalid format. First parameter needs to be an array." +msgstr "" + +#: includes/class-wc-subscription.php:1750 +msgid "Invalid data. First parameter was empty when passed to update_dates()." +msgstr "" + +#: includes/class-wc-subscription.php:1758 +msgid "" +"Invalid data. First parameter has a date that is not in the registered date " +"types." +msgstr "" + +#: includes/class-wc-subscription.php:1810 +msgid "The %s date must occur after the cancellation date." +msgstr "" + +#: includes/class-wc-subscription.php:1815 +msgid "The %s date must occur after the last payment date." +msgstr "" + +#: includes/class-wc-subscription.php:1819 +msgid "The %s date must occur after the next payment date." +msgstr "" + +#: includes/class-wc-subscription.php:1824 +msgid "The %s date must occur after the trial end date." +msgstr "" + +#: includes/class-wc-subscription.php:1828 +msgid "The %s date must occur after the start date." +msgstr "" + +#: includes/class-wc-subscription.php:1856 includes/wcs-order-functions.php:269 +msgid "Backordered" +msgstr "" + #: includes/class-wc-subscriptions-addresses.php:46 msgid "Change Address" msgstr "" @@ -1499,11 +1504,11 @@ msgid "" "contains a subscription renewal." msgstr "" -#: includes/class-wc-subscriptions-cart.php:1088 +#: includes/class-wc-subscriptions-cart.php:1091 msgid "Invalid recurring shipping method." msgstr "" -#: includes/class-wc-subscriptions-cart.php:1865 +#: includes/class-wc-subscriptions-cart.php:1869 msgid "now" msgstr "" @@ -1547,8 +1552,8 @@ msgid "Invalid Subscription." msgstr "" #: includes/class-wc-subscriptions-change-payment-gateway.php:197 -#: includes/class-wcs-cart-resubscribe.php:76 -#: includes/class-wcs-cart-resubscribe.php:127 +#: includes/class-wcs-cart-resubscribe.php:78 +#: includes/class-wcs-cart-resubscribe.php:129 #: includes/class-wcs-user-change-status-handler.php:103 msgid "That doesn't appear to be one of your subscriptions." msgstr "" @@ -1723,40 +1728,40 @@ msgstr "" msgid "Date Changed" msgstr "" -#: includes/class-wc-subscriptions-order.php:366 +#: includes/class-wc-subscriptions-order.php:369 msgid "Your subscription will be activated when payment clears." msgid_plural "Your subscriptions will be activated when payment clears." msgstr[0] "" msgstr[1] "" -#: includes/class-wc-subscriptions-order.php:375 +#: includes/class-wc-subscriptions-order.php:378 #. translators: placeholders are opening and closing link tags msgid "View the status of your subscription in %syour account%s." msgid_plural "View the status of your subscriptions in %syour account%s." msgstr[0] "" msgstr[1] "" -#: includes/class-wc-subscriptions-order.php:423 +#: includes/class-wc-subscriptions-order.php:426 msgid "Subscription Relationship" msgstr "" -#: includes/class-wc-subscriptions-order.php:443 +#: includes/class-wc-subscriptions-order.php:446 msgid "Renewal Order" msgstr "" -#: includes/class-wc-subscriptions-order.php:445 +#: includes/class-wc-subscriptions-order.php:448 msgid "Resubscribe Order" msgstr "" -#: includes/class-wc-subscriptions-order.php:447 +#: includes/class-wc-subscriptions-order.php:450 msgid "Parent Order" msgstr "" -#: includes/class-wc-subscriptions-order.php:685 +#: includes/class-wc-subscriptions-order.php:689 msgid "All orders types" msgstr "" -#: includes/class-wc-subscriptions-order.php:952 +#: includes/class-wc-subscriptions-order.php:956 #. translators: $1: opening link tag, $2: order number, $3: closing link tag msgid "Subscription cancelled for refunded order %1$s#%2$s%3$s." msgstr "" @@ -1855,27 +1860,27 @@ msgstr "" msgid "%1$s and a %2$s sign-up fee" msgstr "" -#: includes/class-wc-subscriptions-renewal-order.php:140 +#: includes/class-wc-subscriptions-renewal-order.php:141 #. translators: placeholder is order ID msgid "Order %s created to record renewal." msgstr "" -#: includes/class-wc-subscriptions-renewal-order.php:160 +#: includes/class-wc-subscriptions-renewal-order.php:161 msgid "Subscription renewal orders cannot be cancelled." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:154 +#: includes/class-wc-subscriptions-switcher.php:151 msgid "" "You have a subscription to this product. Choosing a new subscription will " "replace your existing subscription." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:156 +#: includes/class-wc-subscriptions-switcher.php:153 msgid "Choose a new subscription." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:178 -#: includes/class-wc-subscriptions-switcher.php:902 +#: includes/class-wc-subscriptions-switcher.php:175 +#: includes/class-wc-subscriptions-switcher.php:893 msgid "" "Your cart contained an invalid subscription switch request. It has been " "removed." @@ -1885,13 +1890,13 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: includes/class-wc-subscriptions-switcher.php:220 +#: includes/class-wc-subscriptions-switcher.php:217 msgid "" "You have already subscribed to this product and it is limited to one per " "customer. You can not purchase the product again." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:229 +#: includes/class-wc-subscriptions-switcher.php:226 #. translators: 1$: is the "You have already subscribed to this product" #. notice, 2$-4$: opening/closing link tags, 3$: an order number msgid "" @@ -1899,117 +1904,122 @@ msgid "" "subscription." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:308 +#: includes/class-wc-subscriptions-switcher.php:305 msgid "Switching" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:311 +#: includes/class-wc-subscriptions-switcher.php:308 #. translators: placeholders are opening and closing link tags msgid "" "Allow subscribers to switch (upgrade or downgrade) between different " "subscriptions. %sLearn more%s." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:316 +#: includes/class-wc-subscriptions-switcher.php:313 msgid "Allow Switching" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:317 +#: includes/class-wc-subscriptions-switcher.php:314 msgid "" "Allow subscribers to switch between subscriptions combined in a grouped " "product, different variations of a Variable subscription or don't allow " "switching." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:333 +#: includes/class-wc-subscriptions-switcher.php:330 msgid "Prorate Recurring Payment" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:334 +#: includes/class-wc-subscriptions-switcher.php:331 msgid "" "When switching to a subscription with a different recurring payment or " "billing period, should the price paid for the existing billing period be " "prorated when switching to the new subscription?" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:351 +#: includes/class-wc-subscriptions-switcher.php:348 msgid "Prorate Sign up Fee" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:352 +#: includes/class-wc-subscriptions-switcher.php:349 msgid "" "When switching to a subscription with a sign up fee, you can require the " "customer pay only the gap between the existing subscription's sign up fee " "and the new subscription's sign up fee (if any)." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:367 +#: includes/class-wc-subscriptions-switcher.php:364 msgid "Prorate Subscription Length" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:368 +#: includes/class-wc-subscriptions-switcher.php:365 msgid "" "When switching to a subscription with a length, you can take into account " "the payments already completed by the customer when determining how many " "payments the subscriber needs to make for the new subscription." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:383 +#: includes/class-wc-subscriptions-switcher.php:380 msgid "Switch Button Text" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:384 +#: includes/class-wc-subscriptions-switcher.php:381 msgid "" "Customise the text displayed on the button next to the subscription on the " "subscriber's account page. The default is \"Switch Subscription\", but you " "may wish to change this to \"Upgrade\" or \"Change Subscription\"." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:388 -#: includes/class-wc-subscriptions-switcher.php:414 -#: includes/class-wc-subscriptions-switcher.php:2112 +#: includes/class-wc-subscriptions-switcher.php:385 +#: includes/class-wc-subscriptions-switcher.php:411 +#: includes/class-wc-subscriptions-switcher.php:2159 msgid "Upgrade or Downgrade" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:765 +#: includes/class-wc-subscriptions-switcher.php:759 msgid "Switch order cancelled due to a new switch order being created #%s." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:855 +#: includes/class-wc-subscriptions-switcher.php:846 msgid "Switch Order" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:870 +#: includes/class-wc-subscriptions-switcher.php:861 msgid "Switched Subscription" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:969 +#: includes/class-wc-subscriptions-switcher.php:960 msgid "You can only switch to a subscription product." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:975 +#: includes/class-wc-subscriptions-switcher.php:966 msgid "We can not find your old subscription item." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:997 +#: includes/class-wc-subscriptions-switcher.php:988 msgid "You can not switch to the same subscription." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1044 +#: includes/class-wc-subscriptions-switcher.php:1035 msgid "" "You can not switch this subscription. It appears you do not own the " "subscription." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1079 +#: includes/class-wc-subscriptions-switcher.php:1070 msgid "There was an error locating the switch details." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1814 +#: includes/class-wc-subscriptions-switcher.php:1764 +#: includes/class-wc-subscriptions-switcher.php:2065 msgid "The original subscription item being switched cannot be found." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1867 +#: includes/class-wc-subscriptions-switcher.php:1766 +msgid "The item on the switch order cannot be found." +msgstr "" + +#: includes/class-wc-subscriptions-switcher.php:2103 msgid "Failed to update the subscription shipping method." msgstr "" @@ -2023,57 +2033,57 @@ msgid "" "a specific day of the week or month." msgstr "" -#: includes/class-wc-subscriptions-synchroniser.php:149 +#: includes/class-wc-subscriptions-synchroniser.php:146 msgid "Synchronisation" msgstr "" -#: includes/class-wc-subscriptions-synchroniser.php:158 +#: includes/class-wc-subscriptions-synchroniser.php:155 msgid "Align Subscription Renewal Day" msgstr "" -#: includes/class-wc-subscriptions-synchroniser.php:165 +#: includes/class-wc-subscriptions-synchroniser.php:162 msgid "Prorate First Payment" msgstr "" -#: includes/class-wc-subscriptions-synchroniser.php:166 +#: includes/class-wc-subscriptions-synchroniser.php:163 msgid "" "If a subscription is synchronised to a specific day of the week, month or " "year, charge a prorated amount for the subscription at the time of sign up." msgstr "" -#: includes/class-wc-subscriptions-synchroniser.php:237 +#: includes/class-wc-subscriptions-synchroniser.php:234 msgid "Month for Synchronisation" msgstr "" -#: includes/class-wc-subscriptions-synchroniser.php:603 +#: includes/class-wc-subscriptions-synchroniser.php:600 msgid "Do not synchronise" msgstr "" -#: includes/class-wc-subscriptions-synchroniser.php:611 +#: includes/class-wc-subscriptions-synchroniser.php:608 #. translators: placeholder is a day of the week msgid "%s each week" msgstr "" -#: includes/class-wc-subscriptions-synchroniser.php:617 +#: includes/class-wc-subscriptions-synchroniser.php:614 #. translators: placeholder is a number of day with language specific suffix #. applied (e.g. "1st", "3rd", "5th", etc...) msgid "%s day of the month" msgstr "" -#: includes/class-wc-subscriptions-synchroniser.php:619 +#: includes/class-wc-subscriptions-synchroniser.php:616 msgid "Last day of the month" msgstr "" -#: includes/class-wc-subscriptions-synchroniser.php:667 +#: includes/class-wc-subscriptions-synchroniser.php:664 msgid "Today!" msgstr "" -#: includes/class-wc-subscriptions-synchroniser.php:674 +#: includes/class-wc-subscriptions-synchroniser.php:671 #. translators: placeholder is a date msgid "First payment prorated. Next payment: %s" msgstr "" -#: includes/class-wc-subscriptions-synchroniser.php:677 +#: includes/class-wc-subscriptions-synchroniser.php:674 #. translators: placeholder is a date msgid "First payment: %s" msgstr "" @@ -2091,27 +2101,27 @@ msgid "View and manage subscriptions" msgstr "" #: includes/class-wcs-cart-initial-payment.php:56 -#: includes/class-wcs-cart-renewal.php:121 +#: includes/class-wcs-cart-renewal.php:129 msgid "That doesn't appear to be your order." msgstr "" -#: includes/class-wcs-cart-renewal.php:149 +#: includes/class-wcs-cart-renewal.php:157 msgid "Complete checkout to renew your subscription." msgstr "" -#: includes/class-wcs-cart-renewal.php:188 +#: includes/class-wcs-cart-renewal.php:196 #. translators: placeholder is an item name msgid "" "The %s product has been deleted and can no longer be renewed. Please choose " "a new product or contact us for assistance." msgstr "" -#: includes/class-wcs-cart-renewal.php:221 +#: includes/class-wcs-cart-renewal.php:229 #. translators: %s is subscription's number msgid "Subscription #%s has not been added to the cart." msgstr "" -#: includes/class-wcs-cart-renewal.php:347 +#: includes/class-wcs-cart-renewal.php:355 msgid "" "We couldn't find the original subscription for an item in your cart. The " "item was removed." @@ -2121,7 +2131,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: includes/class-wcs-cart-renewal.php:354 +#: includes/class-wcs-cart-renewal.php:362 msgid "" "We couldn't find the original renewal order for an item in your cart. The " "item was removed." @@ -2131,30 +2141,30 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: includes/class-wcs-cart-renewal.php:618 +#: includes/class-wcs-cart-renewal.php:626 msgid "All linked subscription items have been removed from the cart." msgstr "" -#: includes/class-wcs-cart-resubscribe.php:68 +#: includes/class-wcs-cart-resubscribe.php:70 msgid "There was an error with your request to resubscribe. Please try again." msgstr "" -#: includes/class-wcs-cart-resubscribe.php:72 +#: includes/class-wcs-cart-resubscribe.php:74 msgid "That subscription does not exist. Has it been deleted?" msgstr "" -#: includes/class-wcs-cart-resubscribe.php:80 +#: includes/class-wcs-cart-resubscribe.php:82 msgid "" "You can not resubscribe to that subscription. Please contact us if you need " "assistance." msgstr "" -#: includes/class-wcs-cart-resubscribe.php:89 -#: includes/class-wcs-cart-resubscribe.php:117 +#: includes/class-wcs-cart-resubscribe.php:91 +#: includes/class-wcs-cart-resubscribe.php:119 msgid "Complete checkout to resubscribe." msgstr "" -#: includes/class-wcs-cart-resubscribe.php:310 +#: includes/class-wcs-cart-resubscribe.php:313 msgid "Customer resubscribed in order #%s" msgstr "" @@ -2541,7 +2551,7 @@ msgstr "" #: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:109 #. translators: placeholders are opening and closing link tags. 1$-2$: to docs -#. on woothemes, 3$-4$ to gateway settings on the site +#. on woocommerce, 3$-4$ to gateway settings on the site msgid "" "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 " @@ -2550,7 +2560,7 @@ msgstr "" #: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:122 #. translators: placeholders are opening and closing strong and link tags. -#. 1$-2$: strong tags, 3$-8$ link to docs on woothemes +#. 1$-2$: strong tags, 3$-8$ link to docs on woocommerce msgid "" "%1$sPayPal Reference Transactions are not enabled on your account%2$s, some " "subscription management features are not enabled. Please contact PayPal and " @@ -2575,7 +2585,7 @@ msgstr "" #: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:164 #. translators: placeholders are opening and closing link tags. 1$-2$: docs on -#. woothemes, 3$-4$: dismiss link +#. woocommerce, 3$-4$: dismiss link msgid "" "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." @@ -2584,10 +2594,10 @@ msgstr "" #: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:178 msgid "" "%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" +"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" msgstr "" #: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:186 @@ -2628,24 +2638,24 @@ msgstr "" msgid "IPN subscription sign up completed." msgstr "" -#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:299 -#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:376 +#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:302 +#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:379 msgid "IPN subscription payment completed." msgstr "" -#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:338 +#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:341 msgid "IPN subscription failing payment method changed." msgstr "" -#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:416 +#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:428 msgid "IPN subscription suspended." msgstr "" -#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:439 +#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:451 msgid "IPN subscription cancelled." msgstr "" -#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:455 +#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:467 msgid "IPN subscription payment failure." msgstr "" @@ -2869,7 +2879,7 @@ msgstr "" #: includes/upgrades/templates/wcs-about-2-0.php:76 #. translators: placeholers are link tags: 1$-2$ new subscription page, 3$-4$: -#. docs on woothemes +#. docs on woocommerce.com msgid "" "%1$sAdd a subscription%2$s now or %3$slearn more%4$s about the new " "interface." @@ -3418,21 +3428,21 @@ msgstr "" msgid "There was an error with the update. Please refresh the page and try again." msgstr "" -#: includes/wcs-cart-functions.php:75 +#: includes/wcs-cart-functions.php:76 includes/wcs-cart-functions.php:77 msgid "Shipping via %s" msgstr "" -#: includes/wcs-cart-functions.php:95 +#: includes/wcs-cart-functions.php:96 msgid "Shipping" msgid_plural "Shipping %d" msgstr[0] "" msgstr[1] "" -#: includes/wcs-cart-functions.php:224 +#: includes/wcs-cart-functions.php:225 msgid "Free shipping coupon" msgstr "" -#: includes/wcs-cart-functions.php:327 +#: includes/wcs-cart-functions.php:328 #. translators: placeholder is a date msgid "First renewal: %s" msgstr "" @@ -3542,27 +3552,27 @@ msgstr "" msgid "MM" msgstr "" -#: includes/wcs-order-functions.php:298 +#: includes/wcs-order-functions.php:303 msgid "Subscription Renewal Order – %s" msgstr "" -#: includes/wcs-order-functions.php:301 +#: includes/wcs-order-functions.php:306 msgid "Resubscribe Order – %s" msgstr "" -#: includes/wcs-order-functions.php:320 +#: includes/wcs-order-functions.php:325 msgid "$type passed to the function was not a string." msgstr "" -#: includes/wcs-order-functions.php:325 +#: includes/wcs-order-functions.php:330 msgid "\"%s\" is not a valid new order type." msgstr "" -#: includes/wcs-order-functions.php:512 +#: includes/wcs-order-functions.php:517 msgid "Invalid data. No valid subscription / order was passed in." msgstr "" -#: includes/wcs-order-functions.php:516 +#: includes/wcs-order-functions.php:521 msgid "Invalid data. No valid item id was passed in." msgstr "" @@ -3682,10 +3692,12 @@ msgid "Recurring Totals" msgstr "" #: templates/checkout/recurring-totals.php:28 +#: templates/checkout/recurring-totals.php:29 msgid "Subtotal" msgstr "" -#: templates/checkout/recurring-totals.php:105 +#: templates/checkout/recurring-totals.php:111 +#: templates/checkout/recurring-totals.php:112 msgid "Recurring Total" msgstr "" @@ -4005,7 +4017,7 @@ msgstr "" msgid "Support" msgstr "" -#: woocommerce-subscriptions.php:1047 +#: woocommerce-subscriptions.php:1051 #. translators: placeholders are opening and closing tags. Leads to docs on #. version 2 msgid "" @@ -4016,14 +4028,14 @@ msgid "" "2.0 »%s" msgstr "" -#: woocommerce-subscriptions.php:1062 +#: woocommerce-subscriptions.php:1066 msgid "" "Warning! You are running version %s of WooCommerce Subscriptions plugin " "code but your database has been upgraded to Subscriptions version 2.0. This " "will cause major problems on your store." msgstr "" -#: woocommerce-subscriptions.php:1063 +#: woocommerce-subscriptions.php:1067 msgid "" "Please upgrade the WooCommerce Subscriptions plugin to version 2.0 or newer " "immediately. If you need assistance, after upgrading to Subscriptions v2.0, " @@ -4076,7 +4088,7 @@ msgctxt "example price" msgid "e.g. 9.90" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:767 +#: includes/admin/class-wc-subscriptions-admin.php:762 #. translators: placeholders are for HTML tags. They are 1$: "

", 2$: #. "

", 3$: "

", 4$: "", 5$: "", 6$: "", 7$: "", 8$: #. "

" @@ -4087,7 +4099,7 @@ msgid "" "%6$sVariable subscription%7$s.%8$s" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:769 +#: includes/admin/class-wc-subscriptions-admin.php:764 #. translators: placeholders are for HTML tags. They are 1$: "

", 2$: #. "

", 3$: "

", 4$: "

" msgctxt "used in admin pointer script params in javascript as price pointer content" @@ -4097,48 +4109,48 @@ msgid "" "sign-up fee and free trial.%4$s" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1080 +#: includes/admin/class-wc-subscriptions-admin.php:1075 msgctxt "option section heading" msgid "Renewals" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1112 +#: includes/admin/class-wc-subscriptions-admin.php:1107 msgctxt "options section heading" msgid "Miscellaneous" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1120 +#: includes/admin/class-wc-subscriptions-admin.php:1115 msgctxt "there's a number immediately in front of this text" msgid "suspensions per billing period." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1365 +#: includes/admin/class-wc-subscriptions-admin.php:1360 msgctxt "in [subscriptions] shortcode" msgid "No subscriptions found." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1374 +#: includes/admin/class-wc-subscriptions-admin.php:1369 #. translators: order number msgctxt "in [subscriptions] shortcode" msgid "Subscription %s" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1394 +#: includes/admin/class-wc-subscriptions-admin.php:1389 msgctxt "label that indicates whether debugging is turned on for the plugin" msgid "WCS_DEBUG" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1400 +#: includes/admin/class-wc-subscriptions-admin.php:1395 msgctxt "Live or Staging, Label on WooCommerce -> System Status page" msgid "Subscriptions Mode" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1401 +#: includes/admin/class-wc-subscriptions-admin.php:1396 msgctxt "refers to staging site" msgid "Staging" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1401 +#: includes/admin/class-wc-subscriptions-admin.php:1396 msgctxt "refers to live site" msgid "Live" msgstr "" @@ -4240,7 +4252,7 @@ msgid "Gateway ID: [%s]" msgstr "" #: includes/admin/meta-boxes/views/html-related-orders-row.php:15 -#: includes/class-wc-subscriptions-renewal-order.php:137 +#: includes/class-wc-subscriptions-renewal-order.php:138 #: templates/myaccount/my-subscriptions.php:37 #: templates/myaccount/related-orders.php:38 #: templates/myaccount/related-subscriptions.php:32 @@ -4248,7 +4260,7 @@ msgctxt "hash before order number" msgid "#%s" msgstr "" -#: includes/class-wcs-query.php:90 +#: includes/class-wcs-query.php:92 msgctxt "hash before order number" msgid "Subscription #%s" msgstr "" @@ -4269,7 +4281,7 @@ msgctxt "table heading" msgid "Total" msgstr "" -#: includes/class-wcs-retry-manager.php:95 +#: includes/class-wcs-retry-manager.php:120 msgctxt "table heading" msgid "Renewal Payment Retry" msgstr "" @@ -4342,12 +4354,12 @@ msgctxt "API response confirming order note deleted from a subscription" msgid "Permanently deleted subscription note" msgstr "" -#: includes/class-wc-subscription.php:715 +#: includes/class-wc-subscription.php:721 msgctxt "original denotes there is no date to display" msgid "-" msgstr "" -#: includes/class-wc-subscription.php:771 +#: includes/class-wc-subscription.php:1773 #. translators: placeholder is date type (e.g. "end", "next_payment"...) msgctxt "appears in an error message if date is wrong format" msgid "Invalid %s date. The date must be of the format: \"Y-m-d H:i:s\"." @@ -4377,7 +4389,7 @@ msgctxt "used in order note as reason for why subscription status changed" msgid "Subscription renewal payment due:" msgstr "" -#: includes/class-wcs-retry-manager.php:250 +#: includes/class-wcs-retry-manager.php:299 msgctxt "used in order note as reason for why subscription status changed" msgid "Subscription renewal payment retry:" msgstr "" @@ -4412,7 +4424,7 @@ msgctxt "Subscription status" msgid "On-hold" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:2253 wcs-functions.php:210 +#: includes/class-wc-subscriptions-switcher.php:2300 wcs-functions.php:210 msgctxt "Subscription status" msgid "Switched" msgstr "" @@ -4434,129 +4446,130 @@ msgctxt "used in a select box" msgid "%1$s-%2$s" msgstr "" -#: includes/class-wc-subscriptions-order.php:688 +#: includes/class-wc-subscriptions-order.php:692 msgctxt "An order type" msgid "Original" msgstr "" -#: includes/class-wc-subscriptions-order.php:689 +#: includes/class-wc-subscriptions-order.php:693 msgctxt "An order type" msgid "Subscription Parent" msgstr "" -#: includes/class-wc-subscriptions-order.php:690 +#: includes/class-wc-subscriptions-order.php:694 msgctxt "An order type" msgid "Subscription Renewal" msgstr "" -#: includes/class-wc-subscriptions-order.php:691 +#: includes/class-wc-subscriptions-order.php:695 msgctxt "An order type" msgid "Subscription Resubscribe" msgstr "" -#: includes/class-wc-subscriptions-order.php:692 +#: includes/class-wc-subscriptions-order.php:696 msgctxt "An order type" msgid "Subscription Switch" msgstr "" -#: includes/class-wc-subscriptions-order.php:693 +#: includes/class-wc-subscriptions-order.php:697 msgctxt "An order type" msgid "Non-subscription" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:324 -#: includes/class-wc-subscriptions-switcher.php:341 -#: includes/class-wc-subscriptions-switcher.php:375 -#: includes/class-wc-subscriptions-synchroniser.php:172 +#: includes/class-wc-subscriptions-switcher.php:321 +#: includes/class-wc-subscriptions-switcher.php:338 +#: includes/class-wc-subscriptions-switcher.php:372 +#: includes/class-wc-subscriptions-synchroniser.php:169 msgctxt "when to allow a setting" msgid "Never" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:325 +#: includes/class-wc-subscriptions-switcher.php:322 msgctxt "when to allow switching" msgid "Between Subscription Variations" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:326 +#: includes/class-wc-subscriptions-switcher.php:323 msgctxt "when to allow switching" msgid "Between Grouped Subscriptions" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:327 +#: includes/class-wc-subscriptions-switcher.php:324 msgctxt "when to allow switching" msgid "Between Both Variations & Grouped Subscriptions" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:342 +#: includes/class-wc-subscriptions-switcher.php:339 msgctxt "when to prorate recurring fee when switching" msgid "For Upgrades of Virtual Subscription Products Only" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:343 +#: includes/class-wc-subscriptions-switcher.php:340 msgctxt "when to prorate recurring fee when switching" msgid "For Upgrades of All Subscription Products" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:344 +#: includes/class-wc-subscriptions-switcher.php:341 msgctxt "when to prorate recurring fee when switching" msgid "For Upgrades & Downgrades of Virtual Subscription Products Only" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:345 +#: includes/class-wc-subscriptions-switcher.php:342 msgctxt "when to prorate recurring fee when switching" msgid "For Upgrades & Downgrades of All Subscription Products" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:359 +#: includes/class-wc-subscriptions-switcher.php:356 msgctxt "when to prorate signup fee when switching" msgid "Never (do not charge a sign up fee)" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:360 +#: includes/class-wc-subscriptions-switcher.php:357 msgctxt "when to prorate signup fee when switching" msgid "Never (charge the full sign up fee)" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:361 +#: includes/class-wc-subscriptions-switcher.php:358 msgctxt "when to prorate signup fee when switching" msgid "Always" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:376 -#: includes/class-wc-subscriptions-synchroniser.php:173 +#: includes/class-wc-subscriptions-switcher.php:373 +#: includes/class-wc-subscriptions-synchroniser.php:170 msgctxt "when to prorate first payment / subscription length" msgid "For Virtual Subscription Products Only" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:377 -#: includes/class-wc-subscriptions-synchroniser.php:174 +#: includes/class-wc-subscriptions-switcher.php:374 +#: includes/class-wc-subscriptions-synchroniser.php:171 msgctxt "when to prorate first payment / subscription length" msgid "For All Subscription Products" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1688 +#: includes/class-wc-subscriptions-switcher.php:1649 msgctxt "a switch order" msgid "Downgrade" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1691 +#: includes/class-wc-subscriptions-switcher.php:1652 msgctxt "a switch order" msgid "Upgrade" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1694 +#: includes/class-wc-subscriptions-switcher.php:1655 msgctxt "a switch order" msgid "Crossgrade" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1699 +#: includes/class-wc-subscriptions-switcher.php:1660 #. translators: %1: product subtotal, %2: HTML span tag, %3: direction #. (upgrade, downgrade, crossgrade), %4: closing HTML span tag msgctxt "product subtotal string" msgid "%1$s %2$s(%3$s)%4$s" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1825 +#: includes/class-wc-subscriptions-switcher.php:1777 +#: includes/class-wc-subscriptions-switcher.php:2076 #. translators: 1$: old item, 2$: new item when switching msgctxt "used in order notes" msgid "Customer switched from: %1$s to %2$s." @@ -4572,7 +4585,7 @@ msgid "" "product." msgstr "" -#: includes/class-wc-subscriptions-synchroniser.php:152 +#: includes/class-wc-subscriptions-synchroniser.php:149 #. translators: placeholders are opening and closing link tags msgctxt "used in the general subscription options page" msgid "" @@ -4580,14 +4593,14 @@ msgid "" "For example, the first day of the month. %sLearn more%s." msgstr "" -#: includes/class-wc-subscriptions-synchroniser.php:235 +#: includes/class-wc-subscriptions-synchroniser.php:232 #: templates/admin/deprecated/html-variation-synchronisation.php:36 #: templates/admin/html-variation-synchronisation.php:34 msgctxt "input field placeholder for day field for annual subscriptions" msgid "Day" msgstr "" -#: includes/class-wcs-cart-renewal.php:647 +#: includes/class-wcs-cart-renewal.php:655 msgctxt "" "Used in WooCommerce by removed item notification: \"_All linked " "subscription items were_ removed. Undo?\" Filter for item title." @@ -4611,18 +4624,29 @@ msgctxt "used in order note" msgid "Customer removed \"%1$s\" (Product ID: #%2$d) via the My Account page." msgstr "" -#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:386 -#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:393 +#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:389 +#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:398 #. translators: placeholder is payment status (e.g. "completed") msgctxt "used in order note" msgid "IPN subscription payment %s." msgstr "" -#: includes/class-wcs-retry-manager.php:182 +#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:402 +#. translators: placeholder is payment status (e.g. "completed") +msgctxt "used in order note" +msgid "IPN subscription payment %s for reason: %s." +msgstr "" + +#: includes/class-wcs-retry-manager.php:229 msgctxt "used in order note as reason for why status changed" msgid "Retry rule applied:" msgstr "" +#: includes/class-wcs-retry-manager.php:295 +msgctxt "used in order note as reason for why order status changed" +msgid "Subscription renewal payment retry:" +msgstr "" + #: includes/class-wcs-user-change-status-handler.php:56 msgctxt "order note left on subscription after user action" msgid "Subscription reactivated by the subscriber from their account page." @@ -4930,12 +4954,12 @@ msgctxt "text on submit button" msgid "Update Database" msgstr "" -#: includes/wcs-cart-functions.php:184 +#: includes/wcs-cart-functions.php:185 msgctxt "shipping method price" msgid "Free" msgstr "" -#: includes/wcs-cart-functions.php:259 +#: includes/wcs-cart-functions.php:260 #. translators: placeholder is price string, denotes tax included in cart/order #. total msgctxt "includes tax" @@ -4961,7 +4985,7 @@ msgctxt "" msgid "Invalid data. Type of copy is not a string." msgstr "" -#: includes/wcs-order-functions.php:294 wcs-functions.php:153 +#: includes/wcs-order-functions.php:299 wcs-functions.php:153 #. translators: Order date parsed by strftime msgctxt "Used in subscription post title. \"Subscription renewal order - \"" msgid "%b %d, %Y @ %I:%M %p" @@ -5003,44 +5027,44 @@ msgid_plural "%s years" msgstr[0] "" msgstr[1] "" -#: includes/wcs-time-functions.php:81 +#: includes/wcs-time-functions.php:80 msgctxt "Subscription length" msgid "Never expire" msgstr "" -#: includes/wcs-time-functions.php:86 +#: includes/wcs-time-functions.php:85 msgctxt "Subscription lengths. e.g. \"For 1 day...\"" msgid "1 day" msgstr "" -#: includes/wcs-time-functions.php:90 +#: includes/wcs-time-functions.php:89 msgctxt "Subscription lengths. e.g. \"For 1 week...\"" msgid "1 week" msgstr "" -#: includes/wcs-time-functions.php:94 +#: includes/wcs-time-functions.php:93 msgctxt "Subscription lengths. e.g. \"For 1 month...\"" msgid "1 month" msgstr "" -#: includes/wcs-time-functions.php:98 +#: includes/wcs-time-functions.php:97 msgctxt "Subscription lengths. e.g. \"For 1 year...\"" msgid "1 year" msgstr "" -#: includes/wcs-time-functions.php:148 +#: includes/wcs-time-functions.php:147 msgctxt "period interval (eg \"$10 _every_ 2 weeks\")" msgid "every" msgstr "" -#: includes/wcs-time-functions.php:152 +#: includes/wcs-time-functions.php:151 #. translators: period interval, placeholder is ordinal (eg "$10 every #. _2nd/3rd/4th_", etc) msgctxt "period interval with ordinal number (e.g. \"every 2nd\"" msgid "every %s" msgstr "" -#: includes/wcs-time-functions.php:176 +#: includes/wcs-time-functions.php:175 msgctxt "" "Used in the trial period dropdown. Number is in text field. 0, 2+ will need " "plural, 1 will need singular." @@ -5049,7 +5073,7 @@ msgid_plural "days" msgstr[0] "" msgstr[1] "" -#: includes/wcs-time-functions.php:177 +#: includes/wcs-time-functions.php:176 msgctxt "" "Used in the trial period dropdown. Number is in text field. 0, 2+ will need " "plural, 1 will need singular." @@ -5058,7 +5082,7 @@ msgid_plural "weeks" msgstr[0] "" msgstr[1] "" -#: includes/wcs-time-functions.php:178 +#: includes/wcs-time-functions.php:177 msgctxt "" "Used in the trial period dropdown. Number is in text field. 0, 2+ will need " "plural, 1 will need singular." @@ -5067,7 +5091,7 @@ msgid_plural "months" msgstr[0] "" msgstr[1] "" -#: includes/wcs-time-functions.php:179 +#: includes/wcs-time-functions.php:178 msgctxt "" "Used in the trial period dropdown. Number is in text field. 0, 2+ will need " "plural, 1 will need singular." @@ -5076,7 +5100,7 @@ msgid_plural "years" msgstr[0] "" msgstr[1] "" -#: includes/wcs-time-functions.php:197 +#: includes/wcs-time-functions.php:196 msgctxt "no trial period" msgid "no" msgstr "" diff --git a/templates/checkout/form-change-payment-method.php b/templates/checkout/form-change-payment-method.php index 140a973..3ad8504 100644 --- a/templates/checkout/form-change-payment-method.php +++ b/templates/checkout/form-change-payment-method.php @@ -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 */ diff --git a/templates/checkout/recurring-totals.php b/templates/checkout/recurring-totals.php index 50adc1e..47899fa 100644 --- a/templates/checkout/recurring-totals.php +++ b/templates/checkout/recurring-totals.php @@ -25,9 +25,11 @@ $display_th = true; - + + + + - @@ -41,9 +43,11 @@ $display_th = true; - + + + + - @@ -67,10 +71,11 @@ $display_th = true; label ); ?> + formatted_amount, $recurring_cart ) ); ?> + formatted_amount, $recurring_cart ) ); ?> - formatted_amount, $recurring_cart ) ); ?> @@ -86,10 +91,11 @@ $display_th = true; countries->tax_or_vat() ); ?> + get_taxes_total(), $recurring_cart ) ); ?> + get_taxes_total(), $recurring_cart ) ); ?> - get_taxes_total(), $recurring_cart ) ); ?> @@ -102,8 +108,10 @@ $display_th = true; - + + + + - diff --git a/templates/single-product/add-to-cart/subscription.php b/templates/single-product/add-to-cart/subscription.php index 62bf29b..09397d1 100644 --- a/templates/single-product/add-to-cart/subscription.php +++ b/templates/single-product/add-to-cart/subscription.php @@ -2,7 +2,7 @@ /** * Subscription Product Add to Cart * - * @author WooThemes + * @author Prospress * @package WooCommerce-Subscriptions/Templates * @version 2.0.18 */ diff --git a/templates/single-product/add-to-cart/variable-subscription.php b/templates/single-product/add-to-cart/variable-subscription.php index 13d745a..5e82863 100644 --- a/templates/single-product/add-to-cart/variable-subscription.php +++ b/templates/single-product/add-to-cart/variable-subscription.php @@ -2,7 +2,7 @@ /** * Variable subscription product add to cart * - * @author WooThemes + * @author Prospress * @package WooCommerce-Subscriptions/Templates * @version 2.0.9 */ diff --git a/woocommerce-subscriptions.php b/woocommerce-subscriptions.php index e7e7fcb..31e87ca 100644 --- a/woocommerce-subscriptions.php +++ b/woocommerce-subscriptions.php @@ -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 ); }