From 86123b4cd5be166760d274d93504dd79248dd769 Mon Sep 17 00:00:00 2001 From: Prospress Inc Date: Fri, 20 Apr 2018 09:51:34 +0200 Subject: [PATCH] 2.2.19 --- assets/css/admin.css | 4 + assets/js/admin/admin.js | 34 +- changelog.txt | 40 +- .../admin/class-wc-subscriptions-admin.php | 2 +- includes/admin/class-wcs-admin-post-types.php | 2 +- .../class-wcs-meta-box-related-orders.php | 10 +- .../class-wcs-report-cache-manager.php | 4 +- ...wcs-report-subscription-events-by-date.php | 26 +- ...class-wc-rest-subscriptions-controller.php | 4 +- includes/class-wc-subscription.php | 58 ++- includes/class-wc-subscriptions-addresses.php | 4 +- includes/class-wc-subscriptions-cart.php | 10 +- ...c-subscriptions-change-payment-gateway.php | 2 +- includes/class-wc-subscriptions-coupon.php | 12 +- includes/class-wc-subscriptions-order.php | 2 +- includes/class-wc-subscriptions-product.php | 34 +- includes/class-wc-subscriptions-switcher.php | 166 +++++-- includes/class-wcs-action-scheduler.php | 8 +- includes/class-wcs-cached-data-manager.php | 2 +- includes/class-wcs-cart-resubscribe.php | 26 +- ...ss-wcs-failed-scheduled-action-manager.php | 197 ++++++++ includes/class-wcs-query.php | 17 +- includes/class-wcs-template-loader.php | 27 +- .../class-wcs-subscription-data-store-cpt.php | 38 +- includes/wcs-limit-functions.php | 33 +- includes/wcs-resubscribe-functions.php | 2 +- includes/wcs-time-functions.php | 4 +- languages/woocommerce-subscriptions.pot | 432 ++++++++++-------- templates/myaccount/my-subscriptions.php | 22 +- templates/myaccount/subscription-details.php | 73 +++ templates/myaccount/subscription-totals.php | 112 +++++ templates/myaccount/subscriptions.php | 2 +- templates/myaccount/view-subscription.php | 165 +------ woocommerce-subscriptions.php | 48 +- 34 files changed, 1144 insertions(+), 478 deletions(-) create mode 100644 includes/class-wcs-failed-scheduled-action-manager.php create mode 100644 templates/myaccount/subscription-details.php create mode 100644 templates/myaccount/subscription-totals.php diff --git a/assets/css/admin.css b/assets/css/admin.css index 2243186..6980c64 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -217,6 +217,10 @@ a.close-subscriptions-search { display: none !important; } +#woocommerce-product-data .woocommerce_variation .wcs-can-not-remove-variation-msg { + opacity: 0.5; +} + /* Variation Pricing Fields with WooCommerce 3.0+ */ .wc-metaboxes-wrapper .variable_subscription_trial label, .wc-metaboxes-wrapper .variable_subscription_pricing label, diff --git a/assets/js/admin/admin.js b/assets/js/admin/admin.js index b4b1183..9774d88 100644 --- a/assets/js/admin/admin.js +++ b/assets/js/admin/admin.js @@ -30,6 +30,9 @@ jQuery(document).ready(function($){ } }, showHideVariableSubscriptionMeta: function(){ + // In order for WooCommerce not to show the stock_status_field on variable subscriptions, make sure it has the hide if variable subscription class. + $( 'p.stock_status_field' ).addClass( 'hide_if_variable-subscription' ); + if ($('select#product-type').val()=='variable-subscription') { $( 'input#_downloadable' ).prop( 'checked', false ); @@ -46,10 +49,11 @@ jQuery(document).ready(function($){ } else { - if ($('select#product-type').val()=='variable') { - $('.show_if_variable-subscription').hide(); - $('.show_if_variable').show(); - $('.hide_if_variable').hide(); + if ( 'variable' === $('select#product-type').val() ) { + $( '.show_if_variable-subscription' ).hide(); + $( '.show_if_variable' ).show(); + $( '.hide_if_variable' ).hide(); + $( 'input#_manage_stock' ).change(); } // Restore the sale price row width to half @@ -366,6 +370,18 @@ jQuery(document).ready(function($){ tab.click().parent().show(); } }, + maybeDisableRemoveLinks: function() { + $( '#variable_product_options .woocommerce_variation' ).each( function() { + var $removeLink = $( this ).find( '.remove_variation' ); + var can_remove_variation = ( '1' === $( this ).find( 'input.wcs-can-remove-variation').val() ); + var $msg = $( this ).find( '.wcs-can-not-remove-variation-msg' ); + + if ( ! can_remove_variation ) { + $msg.text( $removeLink.text() ); + $removeLink.replaceWith( $msg ); + } + } ); + }, }); $('.options_group.pricing ._sale_price_field .description').prepend(''); @@ -469,6 +485,11 @@ jQuery(document).ready(function($){ } }); + $( '#variable_product_options' ).on( 'click', '.delete.wcs-can-not-remove-variation-msg', function( e ) { + e.preventDefault(); + e.stopPropagation(); + } ); + // Notify the store manager that trashing an order via the admin orders table row action also deletes the associated subscription if it exists $( '.row-actions .submitdelete' ).click( function() { var order = $( this ).closest( '.type-shop_order' ).attr( 'id' ); @@ -611,6 +632,11 @@ jQuery(document).ready(function($){ } }); + // After variations have been loaded, check which ones are tied to subscriptions to prevent them from being deleted. + $( '#woocommerce-product-data' ).on( 'woocommerce_variations_loaded', function() { + $.maybeDisableRemoveLinks(); + } ); + // Triggered by $.disableEnableOneTimeShipping() after One Time shipping has been enabled or disabled for variations. // If the One Time Shipping field needs updating, send the ajax request to update the product setting in the backend $( '#_subscription_one_time_shipping' ).on( 'subscription_one_time_shipping_updated', function( event, is_synced_or_has_trial ) { diff --git a/changelog.txt b/changelog.txt index 3caea69..8cf94aa 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,11 +1,41 @@ *** WooCommerce Subscriptions Changelog *** +2018.04.04 - version 2.2.19 +* Fix: With Mixed Checkout disabled, only remove the add to cart notice when redirected straight to checkout. Fixes issue where error notices are removed incorrectly. PR#2541 +* Fix: With Mixed Checkout disabled, only empty cart when the variation ID is the same but with different attributes. PR#2540 +* Fix: Remove the "\n" character from variable subscription item tooltips in the admin subscriptions table. PR#2481 +* Fix: Allow users to purchase limited to any status subscription products under circumstances where the customer is blocked from purchasing because of cancelled orders. PR#2485 +* Fix: Prevent deletion of product variations in use by a subscription. PR#2507 +* Fix: Get default product price when no price arg is passed into `get_price_string()`. PR#2558 +* Fix: Avoid potential for division by zero errors when switching. PR#2574 +* Fix: Only force payment method selection during switch if a new subscription will be created. PR#2567 +* Fix: Remove trailing space from address_type variable. PR#2595 +* Fix: Ensure the opening span tag is present for all args passed to get_price_string(). PR#2597 +* Fix: Set the product's price to the sale price even if it's 0. Fixes display issues with subscription products on sale for $0. PR#2601 +* Fix: While resubscribing, ensure subscriptions with multiple line items charge the correct amount for all line items, not just the 1st. PR#2585 +* Fix: Hide product stock settings while editing variable products. PR#2563 +* Fix: Allow subscription coupons to be applied while switching subscriptions. PR#1863 +* Fix: Ensure subscriptions created via the API with an active status and a set_paid arg set to true are saved. PR#2609 +* Fix: Ensure Net Gain/Loss calculations for subscription reports convert dates correctly to match the site's timezone. PR#2593 +* Fix: Prevent the possibility for fatal errors caused by calling undeclared functions during the payment method change requests. PR#2613 +* Tweak: Ensure sign up fees are numeric before attempting to use them in calculations. PR#2551 +* Tweak: Only clear related order cache on renewal order changes. PR#2555 +* Tweak: Improve checkout performance by avoiding related renewal orders query. PR#2550 +* Tweak: Add the `$plain_text` arg to order item meta start and end hooks. PR#2515 +* Tweak: Refactor the view subscription template. PR#2575 +* Tweak: Add Subscription My Account endpoints to wpml-config.xml file. PR#2578 +* Tweak: Add 'woocommerce_subscriptions_product_limited_for_user' filter. PR#2596 +* Tweak: Clear the cart if the product being added to the cart is sold individually when mixed checkout is disabled. PR#2602 +* Tweak: Use WC_DateTime objects and `getTimestamp()` rather than `format( 'U' )` to avoid possible issues in earlier PHP versions. PR#2588 +* Tweak: Add pagination to the My Account Subscriptions table. PR#2537 +* Tweak: Catch, log and display an admin notice after a scheduled subscription action timeout. PR#2484 + 2018.02.12 - version 2.2.18 -New: Provide custom options for subscription My Account endpoints. PR#2511 -Fix: Reinitialize tooltips after changing switching settings. PR#2517 -Fix: Only add inclusive tax rates to renewal cart items if product is taxable. PR#2524 -Fix: [WC3.3] Fix switching between grouped products by ensuring the switch request arguments are posted to the add to cart form. PR#2535 -Fix: Use correct meta key and compare argument when getting subscriptions with wcs_get_subscriptions() when ordered by end or trial date. PR#2528 +* New: Provide custom options for subscription My Account endpoints. PR#2511 +* Fix: Reinitialize tooltips after changing switching settings. PR#2517 +* Fix: Only add inclusive tax rates to renewal cart items if product is taxable. PR#2524 +* Fix: [WC3.3] Fix switching between grouped products by ensuring the switch request arguments are posted to the add to cart form. PR#2535 +* Fix: Use correct meta key and compare argument when getting subscriptions with wcs_get_subscriptions() when ordered by end or trial date. PR#2528 2018.01.29 - version 2.2.17 * Fix: Use line item meta values in a WC version compatible way. PR#2469 diff --git a/includes/admin/class-wc-subscriptions-admin.php b/includes/admin/class-wc-subscriptions-admin.php index ac6cacb..7aefda2 100644 --- a/includes/admin/class-wc-subscriptions-admin.php +++ b/includes/admin/class-wc-subscriptions-admin.php @@ -396,7 +396,7 @@ class WC_Subscriptions_Admin { update_post_meta( $post_id, '_sale_price_dates_to', $date_to ); // Update price if on sale - if ( ! empty( $sale_price ) && ( ( empty( $date_to ) && empty( $date_from ) ) || ( $date_from < $now && ( empty( $date_to ) || $date_to > $now ) ) ) ) { + if ( '' !== $sale_price && ( ( empty( $date_to ) && empty( $date_from ) ) || ( $date_from < $now && ( empty( $date_to ) || $date_to > $now ) ) ) ) { $price = $sale_price; } else { $price = $subscription_price; diff --git a/includes/admin/class-wcs-admin-post-types.php b/includes/admin/class-wcs-admin-post-types.php index 14347e7..fcea48f 100644 --- a/includes/admin/class-wcs-admin-post-types.php +++ b/includes/admin/class-wcs-admin-post-types.php @@ -1002,7 +1002,7 @@ class WCS_Admin_Post_Types { $item_meta_html = wc_display_item_meta( $item, array( 'before' => '', 'after' => '', - 'separator' => '\n', + 'separator' => '', 'echo' => false, ) ); } diff --git a/includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php b/includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php index d30ea1e..6870865 100644 --- a/includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php +++ b/includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php @@ -48,10 +48,12 @@ class WCS_Meta_Box_Related_Orders { $subscriptions = array(); $orders = array(); + $is_subscription_screen = wcs_is_subscription( $post->ID ); // On the subscription page, just show related orders - if ( wcs_is_subscription( $post->ID ) ) { - $subscriptions[] = wcs_get_subscription( $post->ID ); + if ( $is_subscription_screen ) { + $this_subscription = wcs_get_subscription( $post->ID ); + $subscriptions[] = $this_subscription; } elseif ( wcs_order_contains_subscription( $post->ID, array( 'parent', 'renewal' ) ) ) { $subscriptions = wcs_get_subscriptions_for_order( $post->ID, array( 'order_type' => array( 'parent', 'renewal' ) ) ); } @@ -65,9 +67,9 @@ class WCS_Meta_Box_Related_Orders { //Resubscribed $initial_subscriptions = array(); - if ( wcs_is_subscription( $post->ID ) ) { + if ( $is_subscription_screen ) { - $initial_subscriptions = wcs_get_subscriptions_for_resubscribe_order( $post->ID ); + $initial_subscriptions = wcs_get_subscriptions_for_resubscribe_order( $this_subscription ); $resubscribed_subscriptions = get_posts( array( 'meta_key' => '_subscription_resubscribe', diff --git a/includes/admin/reports/class-wcs-report-cache-manager.php b/includes/admin/reports/class-wcs-report-cache-manager.php index 53caad1..80151fd 100644 --- a/includes/admin/reports/class-wcs-report-cache-manager.php +++ b/includes/admin/reports/class-wcs-report-cache-manager.php @@ -141,10 +141,10 @@ class WCS_Report_Cache_Manager { // On large sites, we want to run the cache update once at 4am in the site's timezone if ( $this->use_large_site_cache() ) { - $four_am_site_time = new DateTime( '4 am', wcs_get_sites_timezone() ); + $four_am_site_time = new WC_DateTime( '4 am', wcs_get_sites_timezone() ); // Convert to a UTC timestamp for scheduling - $cache_update_timestamp = $four_am_site_time->format( 'U' ); + $cache_update_timestamp = $four_am_site_time->getTimestamp(); // PHP doesn't support a "next 4am" time format equivalent, so we need to manually handle getting 4am from earlier today (which will always happen when this is run after 4am and before midnight in the site's timezone) if ( $cache_update_timestamp <= gmdate( 'U' ) ) { diff --git a/includes/admin/reports/class-wcs-report-subscription-events-by-date.php b/includes/admin/reports/class-wcs-report-subscription-events-by-date.php index 99868c9..3c40c96 100644 --- a/includes/admin/reports/class-wcs-report-subscription-events-by-date.php +++ b/includes/admin/reports/class-wcs-report-subscription-events-by-date.php @@ -44,6 +44,10 @@ class WC_Report_Subscription_Events_By_Date extends WC_Admin_Report { $args = wp_parse_args( $args, $default_args ); $query_end_date = date( 'Y-m-d', strtotime( '+1 DAY', $this->end_date ) ); + $offset = get_option( 'gmt_offset' ); + + //Convert from Decimal format(eg. 11.5) to a suitable format(eg. +11:30) for CONVERT_TZ() of SQL query. + $site_timezone = sprintf( '%+02d:%02d', (int) $offset, ( $offset - floor( $offset ) ) * 60 ); $this->report_data = new stdClass; @@ -255,11 +259,11 @@ class WC_Report_Subscription_Events_By_Date extends WC_Admin_Report { {$wpdb->posts} AS wcsubs LEFT JOIN {$wpdb->postmeta} AS wcsmeta ON wcsubs.ID = wcsmeta.post_id AND wcsmeta.meta_key = %s - ) ON DATE( wcsubs.post_date ) <= searchdate.Date + ) ON DATE( wcsubs.post_date ) < searchdate.Date AND wcsubs.post_type IN ( 'shop_subscription' ) - AND wcsubs.post_status NOT IN( 'wc-pending', 'trash', 'auto-draft' ) + AND wcsubs.post_status NOT IN( 'trash', 'auto-draft' ) AND ( - DATE( wcsmeta.meta_value ) >= searchdate.Date + DATE( CONVERT_TZ( wcsmeta.meta_value , '+00:00', %s ) ) >= searchdate.Date OR wcsmeta.meta_value = 0 OR wcsmeta.meta_value IS NULL ) @@ -268,7 +272,8 @@ class WC_Report_Subscription_Events_By_Date extends WC_Admin_Report { $query_end_date, date( 'Y-m-d', $this->start_date ), $query_end_date, - wcs_get_date_meta_key( 'end' ) + wcs_get_date_meta_key( 'end' ), + $site_timezone ); $query_hash = md5( $query ); @@ -285,13 +290,13 @@ class WC_Report_Subscription_Events_By_Date extends WC_Admin_Report { * Subscription cancellations */ $query = $wpdb->prepare( - "SELECT COUNT(DISTINCT wcsubs.ID) as count, wcsmeta_cancel.meta_value as cancel_date + "SELECT COUNT( DISTINCT wcsubs.ID ) as count, CONVERT_TZ( wcsmeta_cancel.meta_value, '+00:00', '{$site_timezone}' ) as cancel_date FROM {$wpdb->posts} as wcsubs JOIN {$wpdb->postmeta} AS wcsmeta_cancel ON wcsubs.ID = wcsmeta_cancel.post_id AND wcsmeta_cancel.meta_key = %s - WHERE wcsmeta_cancel.meta_value BETWEEN %s AND %s - GROUP BY YEAR(wcsmeta_cancel.meta_value), MONTH(wcsmeta_cancel.meta_value), DAY(wcsmeta_cancel.meta_value) + GROUP BY YEAR( cancel_date ), MONTH( cancel_date ), DAY( cancel_date ) + HAVING cancel_date BETWEEN %s AND %s ORDER BY wcsmeta_cancel.meta_value ASC", wcs_get_date_meta_key( 'cancelled' ), date( 'Y-m-d', $this->start_date ), @@ -312,14 +317,13 @@ class WC_Report_Subscription_Events_By_Date extends WC_Admin_Report { * Subscriptions ended */ $query = $wpdb->prepare( - "SELECT COUNT(DISTINCT wcsubs.ID) as count, wcsmeta_end.meta_value as end_date + "SELECT COUNT( DISTINCT wcsubs.ID ) as count, CONVERT_TZ( wcsmeta_end.meta_value, '+00:00', '{$site_timezone}' ) as end_date FROM {$wpdb->posts} as wcsubs JOIN {$wpdb->postmeta} AS wcsmeta_end ON wcsubs.ID = wcsmeta_end.post_id AND wcsmeta_end.meta_key = %s - WHERE - wcsmeta_end.meta_value BETWEEN %s AND %s - GROUP BY YEAR(wcsmeta_end.meta_value), MONTH(wcsmeta_end.meta_value), DAY(wcsmeta_end.meta_value) + GROUP BY YEAR( end_date ), MONTH( end_date ), DAY( end_date ) + HAVING end_date BETWEEN %s AND %s ORDER BY wcsmeta_end.meta_value ASC", wcs_get_date_meta_key( 'end' ), date( 'Y-m-d', $this->start_date ), diff --git a/includes/api/class-wc-rest-subscriptions-controller.php b/includes/api/class-wc-rest-subscriptions-controller.php index 89a2bce..e25a3e4 100644 --- a/includes/api/class-wc-rest-subscriptions-controller.php +++ b/includes/api/class-wc-rest-subscriptions-controller.php @@ -133,11 +133,11 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_V1_Controller { $this->update_payment_method( $subscription, $payment_data ); + $subscription->save(); + // Handle set paid. if ( true === $request['set_paid'] ) { $subscription->payment_complete( $request['transaction_id'] ); - } else { - $subscription->save(); // $subscription->payment_complete() calls $subscription->update_status() which saves the subscription, so we only need to save it if not calling that } return $subscription->get_id(); diff --git a/includes/class-wc-subscription.php b/includes/class-wc-subscription.php index ff4609a..d88ca2f 100644 --- a/includes/class-wc-subscription.php +++ b/includes/class-wc-subscription.php @@ -721,7 +721,7 @@ class WC_Subscription extends WC_Order { */ public function get_failed_payment_count() { - $failed_payment_count = ( ( $parent_order = $this->get_parent() ) && $parent_order->has_status( 'wc-failed' ) ) ? 1 : 0; + $failed_payment_count = ( ( $parent_order = $this->get_parent() ) && $parent_order->has_status( 'failed' ) ) ? 1 : 0; $failed_renewal_orders = get_posts( array( 'posts_per_page' => -1, @@ -1651,7 +1651,7 @@ class WC_Subscription extends WC_Order { } /** - * When payment is completed, either for the original purchase or a renewal payment, this function processes it. + * Process payment on the subscription, which mainly means processing it for the last order on the subscription. * * @param $transaction_id string Optional transaction id to store in post meta */ @@ -1661,7 +1661,7 @@ class WC_Subscription extends WC_Order { return; } - // Clear the cached completed payment count + // Clear the cached completed payment count, kept here for backward compat even though it's also reset in $this->process_payment_complete() $this->cached_completed_payment_count = false; // Make sure the last order's status is updated @@ -1671,6 +1671,19 @@ class WC_Subscription extends WC_Order { $last_order->payment_complete( $transaction_id ); } + $this->payment_complete_for_order( $last_order ); + } + + /** + * When payment is completed for a related order, reset any renewal related counters and reactive the subscription. + * + * @param WC_Order $order + */ + public function payment_complete_for_order( $last_order ) { + + // Clear the cached completed payment count + $this->cached_completed_payment_count = false; + // Reset suspension count $this->set_suspension_count( 0 ); @@ -1678,13 +1691,7 @@ class WC_Subscription extends WC_Order { wcs_update_users_role( $this->get_user_id(), 'default_subscriber_role' ); // Add order note depending on initial payment - if ( 0 == $this->get_total_initial_payment() && 1 == $this->get_completed_payment_count() && false != $this->get_parent() ) { - $note = __( 'Sign-up complete.', 'woocommerce-subscriptions' ); - } else { - $note = __( 'Payment received.', 'woocommerce-subscriptions' ); - } - - $this->add_order_note( $note ); + $this->add_order_note( __( 'Payment status marked complete.', 'woocommerce-subscriptions' ) ); $this->update_status( 'active' ); // also saves the subscription @@ -2149,11 +2156,12 @@ class WC_Subscription extends WC_Order { } elseif ( 'true' === $line_item->get_meta( '_has_trial' ) ) { // Sign up is amount paid for this item on original order, we can safely use 3.0 getters here because we know from the above condition 3.0 is active - $sign_up_fee = $original_order_item->get_total( 'edit' ) / $original_order_item->get_quantity( 'edit' ); + $sign_up_fee = ( (float) $original_order_item->get_total( 'edit' ) ) / $original_order_item->get_quantity( 'edit' ); } else { // Sign-up fee is any amount on top of recurring amount - $order_line_total = $original_order_item->get_total( 'edit' ) / $original_order_item->get_quantity( 'edit' ); - $subscription_line_total = $line_item->get_total( 'edit' ) / $line_item->get_quantity( 'edit' ); + $order_line_total = ( (float) $original_order_item->get_total( 'edit' ) ) / $original_order_item->get_quantity( 'edit' ); + $subscription_line_total = ( (float) $line_item->get_total( 'edit' ) ) / $line_item->get_quantity( 'edit' ); + $sign_up_fee = max( $order_line_total - $subscription_line_total, 0 ); } @@ -2397,6 +2405,30 @@ class WC_Subscription extends WC_Order { return $this->valid_date_types; } + /************************ + * WC_Order overrides + * + * Make some WC_Order methods do nothing. + ************************/ + + /** + * Avoid running the expensive get_date_paid() query on related orders. + * + * @since 2.2.19 + */ + public function maybe_set_date_paid() { + return null; + } + + /** + * Avoid running the expensive get_date_completed() query on related orders. + * + * @since 2.2.19 + */ + protected function maybe_set_date_completed() { + return null; + } + /************************ * Deprecated Functions * ************************/ diff --git a/includes/class-wc-subscriptions-addresses.php b/includes/class-wc-subscriptions-addresses.php index de508f1..381e0cc 100644 --- a/includes/class-wc-subscriptions-addresses.php +++ b/includes/class-wc-subscriptions-addresses.php @@ -74,9 +74,9 @@ class WC_Subscriptions_Addresses { } elseif ( ( ( isset( $wp->query_vars['edit-address'] ) && ! empty( $wp->query_vars['edit-address'] ) ) || isset( $_GET['address'] ) ) ) { if ( isset( $wp->query_vars['edit-address'] ) ) { - $address_type = esc_attr( $wp->query_vars['edit-address'] ) . ' '; + $address_type = esc_attr( $wp->query_vars['edit-address'] ); } else { - $address_type = ( ! isset( $_GET['address'] ) ) ? esc_attr( $_GET['address'] ) . ' ' : ''; + $address_type = ( ! isset( $_GET['address'] ) ) ? esc_attr( $_GET['address'] ) : ''; } // translators: $1: address type (Shipping Address / Billing Address), $2: opening tag, $3: closing tag diff --git a/includes/class-wc-subscriptions-cart.php b/includes/class-wc-subscriptions-cart.php index d2b049f..500d1f6 100644 --- a/includes/class-wc-subscriptions-cart.php +++ b/includes/class-wc-subscriptions-cart.php @@ -207,6 +207,10 @@ class WC_Subscriptions_Cart { if ( 'none' == self::$calculation_type ) { $sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $product ); + + // Extra check to make sure that the sign up fee is numeric before using it + $sign_up_fee = is_numeric( $sign_up_fee ) ? (float) $sign_up_fee : 0; + $trial_length = WC_Subscriptions_Product::get_trial_length( $product ); if ( $trial_length > 0 ) { @@ -821,7 +825,11 @@ class WC_Subscriptions_Cart { continue; } - $sign_up_fee += WC_Subscriptions_Product::get_sign_up_fee( $cart_item['data'] ); + $cart_item_sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $cart_item['data'] ); + // Extra check to make sure that the sign up fee is numeric before using it + $cart_item_sign_up_fee = is_numeric( $cart_item_sign_up_fee ) ? (float) $cart_item_sign_up_fee : 0; + + $sign_up_fee += $cart_item_sign_up_fee; } } diff --git a/includes/class-wc-subscriptions-change-payment-gateway.php b/includes/class-wc-subscriptions-change-payment-gateway.php index f93e8fd..6786be6 100644 --- a/includes/class-wc-subscriptions-change-payment-gateway.php +++ b/includes/class-wc-subscriptions-change-payment-gateway.php @@ -446,7 +446,7 @@ class WC_Subscriptions_Change_Payment_Gateway { public static function maybe_zero_total( $total, $subscription ) { global $wp; - if ( ! empty( $_POST['_wcsnonce'] ) && wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_change_payment_method' ) && isset( $_POST['woocommerce_change_payment'] ) && $subscription->get_order_key() == $_GET['key'] && $subscription->get_id() == absint( $_POST['woocommerce_change_payment'] ) ) { + if ( ! empty( $_POST['_wcsnonce'] ) && wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_change_payment_method' ) && isset( $_POST['woocommerce_change_payment'] ) && wcs_is_subscription( $subscription ) && $subscription->get_order_key() == $_GET['key'] && $subscription->get_id() == absint( $_POST['woocommerce_change_payment'] ) ) { $total = 0; } elseif ( ! self::$is_request_to_change_payment && isset( $wp->query_vars['order-pay'] ) && wcs_is_subscription( absint( $wp->query_vars['order-pay'] ) ) ) { // if the request to pay for the order belongs to a subscription but there's no GET params for changing payment method, the receipt page is being used to collect credit card details so we still need to $0 the total diff --git a/includes/class-wc-subscriptions-coupon.php b/includes/class-wc-subscriptions-coupon.php index 6e9ce29..6316efb 100644 --- a/includes/class-wc-subscriptions-coupon.php +++ b/includes/class-wc-subscriptions-coupon.php @@ -115,6 +115,8 @@ class WC_Subscriptions_Coupon { return $discount; } + $is_switch = ! empty( $cart_item['subscription_switch'] ); + // Set our starting discount amount to 0 $discount_amount = 0; @@ -137,7 +139,7 @@ class WC_Subscriptions_Coupon { if ( 'none' == $calculation_type ) { // If all items have a free trial we don't need to apply recurring coupons to the initial total - if ( ! WC_Subscriptions_Cart::all_cart_items_have_free_trial() ) { + if ( $is_switch || ! WC_Subscriptions_Cart::all_cart_items_have_free_trial() ) { if ( 'recurring_fee' == $coupon_type ) { $apply_initial_coupon = true; @@ -148,8 +150,8 @@ class WC_Subscriptions_Coupon { } } - // Apply sign-up discounts - if ( WC_Subscriptions_Product::get_sign_up_fee( $cart_item['data'] ) > 0 ) { + // Apply sign-up discounts. Exclude switch cart items because their initial amount is entirely sign-up fees but should be treated as initial amounts + if ( ! $is_switch && WC_Subscriptions_Product::get_sign_up_fee( $cart_item['data'] ) > 0 ) { if ( 'sign_up_fee' == $coupon_type ) { $apply_initial_coupon = true; @@ -183,7 +185,7 @@ class WC_Subscriptions_Coupon { if ( $apply_recurring_coupon || $apply_initial_coupon ) { // Recurring coupons only apply when there is no free trial (carts can have a mix of free trial and non free trial items) - if ( $apply_initial_coupon && 'recurring_fee' == $coupon_type && WC_Subscriptions_Product::get_trial_length( $cart_item['data'] ) > 0 ) { + if ( $apply_initial_coupon && 'recurring_fee' == $coupon_type && ! $is_switch && WC_Subscriptions_Product::get_trial_length( $cart_item['data'] ) > 0 ) { $discounting_amount = 0; } @@ -197,7 +199,7 @@ class WC_Subscriptions_Coupon { } elseif ( $apply_initial_percent_coupon ) { // Recurring coupons only apply when there is no free trial (carts can have a mix of free trial and non free trial items) - if ( 'recurring_percent' == $coupon_type && WC_Subscriptions_Product::get_trial_length( $cart_item['data'] ) > 0 ) { + if ( 'recurring_percent' == $coupon_type && ! $is_switch && WC_Subscriptions_Product::get_trial_length( $cart_item['data'] ) > 0 ) { $discounting_amount = 0; } diff --git a/includes/class-wc-subscriptions-order.php b/includes/class-wc-subscriptions-order.php index 92c2120..49c27ce 100644 --- a/includes/class-wc-subscriptions-order.php +++ b/includes/class-wc-subscriptions-order.php @@ -522,7 +522,7 @@ class WC_Subscriptions_Order { $subscription->update_dates( $dates ); } - $subscription->payment_complete(); + $subscription->payment_complete_for_order( $order ); $was_activated = true; } elseif ( 'failed' == $new_order_status ) { diff --git a/includes/class-wc-subscriptions-product.php b/includes/class-wc-subscriptions-product.php index c3ad593..e490ff6 100644 --- a/includes/class-wc-subscriptions-product.php +++ b/includes/class-wc-subscriptions-product.php @@ -67,6 +67,9 @@ class WC_Subscriptions_Product { // Handle bulk edits to subscription data in WC 2.4 add_action( 'woocommerce_bulk_edit_variations', __CLASS__ . '::bulk_edit_variations', 10, 4 ); + // Adds a field flagging whether the variation is safe to be removed or not. + add_action( 'woocommerce_product_after_variable_attributes', array( __CLASS__, 'add_variation_removal_flag' ), 10, 3 ); + // check product variations for sync'd or trial add_action( 'wp_ajax_wcs_product_has_trial_or_is_synced', __CLASS__ . '::check_product_variations_for_syncd_or_trial' ); @@ -252,7 +255,7 @@ class WC_Subscriptions_Product { if ( isset( $include['price'] ) ) { $price = $include['price']; } else { - $price = wcs_get_price_excluding_tax( $product, array( 'price' => $include['price'] ) ); + $price = wcs_get_price_excluding_tax( $product ); } if ( true === $include['sign_up_fee'] ) { @@ -356,7 +359,9 @@ class WC_Subscriptions_Product { $subscription_string = $price; } elseif ( $include['subscription_period'] ) { // translators: billing period (e.g. "every week") - $subscription_string = sprintf( __( 'every %s', 'woocommerce-subscriptions' ), wcs_get_subscription_period_strings( $billing_interval, $billing_period ) ); + $subscription_string = '' . sprintf( __( 'every %s', 'woocommerce-subscriptions' ), wcs_get_subscription_period_strings( $billing_interval, $billing_period ) ); + } else { + $subscription_string = ''; } // Add the length to the end @@ -895,6 +900,31 @@ class WC_Subscriptions_Product { } } + /** + * + * Hooked to `woocommerce_product_after_variable_attributes`. + * This function adds a hidden field to the backend's HTML output of product variations indicating whether the + * variation is being used in subscriptions or not. + * This is used by some admin JS code to prevent removal of certain variations and also display a tooltip message to the + * admin. + * + * @param int $loop Position of the variation inside the variations loop. + * @param array $variation_data Array of variation data. + * @param WP_Post $variation The variation's WP post. + * @since 2.2.17 + */ + public static function add_variation_removal_flag( $loop, $variation_data, $variation ) { + $related_subscriptions = wcs_get_subscriptions_for_product( $variation->ID ); + $can_remove = empty( $related_subscriptions ); + + printf( '', intval( $can_remove ) ); + + if ( ! $can_remove ) { + $msg = __( 'This variation can not be removed because it is associated with active subscriptions. To remove this variation, please cancel and delete the subscriptions for it.', 'woocommerce-subscriptions' ); + printf( '', wc_sanitize_tooltip( $msg ) ); // XSS ok. + } + } + /** * Processes an AJAX request to check if a product has a variation which is either sync'd or has a trial. * Once at least one variation with a trial or sync date is found, this will terminate and return true, otherwise false. diff --git a/includes/class-wc-subscriptions-switcher.php b/includes/class-wc-subscriptions-switcher.php index 4fc8f2e..f0a99c9 100644 --- a/includes/class-wc-subscriptions-switcher.php +++ b/includes/class-wc-subscriptions-switcher.php @@ -733,42 +733,15 @@ class WC_Subscriptions_Switcher { $subscription = wcs_get_subscription( $cart_item['subscription_switch']['subscription_id'] ); $existing_item = wcs_get_order_item( $cart_item['subscription_switch']['item_id'], $subscription ); - // If there are no more payments due on the subscription, because we're in the last billing period, we need to use the subscription's expiration date, not next payment date - if ( 0 == ( $next_payment_timestamp = $subscription->get_time( 'next_payment' ) ) ) { - $next_payment_timestamp = $subscription->get_time( 'end' ); - } - - if ( WC_Subscriptions_Product::get_period( $cart_item['data'] ) != $subscription->get_billing_period() || WC_Subscriptions_Product::get_interval( $cart_item['data'] ) != $subscription->get_billing_interval() ) { - $is_different_billing_schedule = true; - } else { - $is_different_billing_schedule = false; - } - // If we haven't calculated a first payment date, fall back to the recurring cart's next payment date if ( 0 == $cart_item['subscription_switch']['first_payment_timestamp'] ) { $cart_item['subscription_switch']['first_payment_timestamp'] = wcs_date_to_time( $recurring_cart->next_payment_date ); } - if ( 0 !== $cart_item['subscription_switch']['first_payment_timestamp'] && $next_payment_timestamp !== $cart_item['subscription_switch']['first_payment_timestamp'] ) { - $is_different_payment_date = true; - } elseif ( 0 !== $cart_item['subscription_switch']['first_payment_timestamp'] && 0 == $subscription->get_time( 'next_payment' ) ) { // if the subscription doesn't have a next payment but the switched item does - $is_different_payment_date = true; - } else { - $is_different_payment_date = false; - } - - if ( gmdate( 'Y-m-d', wcs_date_to_time( $recurring_cart->end_date ) ) !== gmdate( 'Y-m-d', $subscription->get_time( 'end' ) ) ) { - $is_different_length = true; - } else { - $is_different_length = false; - } - - // 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; - } + $is_different_billing_schedule = self::has_different_billing_schedule( $cart_item, $subscription ); + $is_different_payment_date = self::has_different_payment_date( $cart_item, $subscription ); + $is_different_length = self::has_different_length( $recurring_cart, $subscription ); + $is_single_item_subscription = self::is_single_item_subscription( $subscription ); $switched_item_data = array( 'remove_line_item' => $cart_item['subscription_switch']['item_id'] ); @@ -1400,7 +1373,7 @@ class WC_Subscriptions_Switcher { } // Find the $price per day for the old subscription's recurring total - $old_price_per_day = $old_recurring_total / $days_in_old_cycle; + $old_price_per_day = $days_in_old_cycle > 0 ? $old_recurring_total / $days_in_old_cycle : $old_recurring_total; // Find the price per day for the new subscription's recurring total // If the subscription uses the same billing interval & cycle as the old subscription, @@ -2060,19 +2033,15 @@ class WC_Subscriptions_Switcher { $subscription = wcs_get_subscription( $cart_item['subscription_switch']['subscription_id'] ); - // Check that the existing subscriptions are for $0 recurring - $old_recurring_total = $subscription->get_total(); + $is_manual_subscription = $subscription->is_manual(); // Check for $0 / period to a non-zero $ / period and manual subscription - $switch_from_zero_manual_subscription = ( 0 == $old_recurring_total && $subscription->is_manual() ); + $switch_from_zero_manual_subscription = $is_manual_subscription && 0 == $subscription->get_total(); - // Check for manual renewals accepted, in case of automatic subscription switch with no proration - $accept_manual_renewals = ( 'yes' == get_option( WC_Subscriptions_Admin::$option_prefix . '_accept_manual_renewals', 'no' ) ); + // Force payment gateway selection for new subscriptions if the old subscription was automatic or manual renewals aren't accepted + $force_automatic_payments = ! $is_manual_subscription || 'no' === get_option( WC_Subscriptions_Admin::$option_prefix . '_accept_manual_renewals', 'no' ); - // Check if old subscription is automatic - $old_subscription_automatic = ! $subscription->is_manual(); - - if ( ( $switch_from_zero_manual_subscription || ! $accept_manual_renewals || ( $accept_manual_renewals && $old_subscription_automatic ) ) && $new_recurring_total > 0 && true === $has_future_payments ) { + if ( $new_recurring_total > 0 && true === $has_future_payments && ( $switch_from_zero_manual_subscription || ( $force_automatic_payments && self::cart_contains_subscription_creating_switch() ) ) ) { WC()->cart->cart_contents[ $cart_item_key ]['subscription_switch']['force_payment'] = true; } } @@ -2344,6 +2313,121 @@ class WC_Subscriptions_Switcher { } } + /** + * Check if a cart item has a different billing schedule (period and interval) to the subscription being switched. + * + * Used to determine if a new subscription should be created as the result of a switch request. + * @see self::cart_contains_subscription_creating_switch() and self::process_checkout(). + * + * @param array $cart_item + * @param WC_Subscription $subscription + * @since 2.2.19 + */ + protected static function has_different_billing_schedule( $cart_item, $subscription ) { + return WC_Subscriptions_Product::get_period( $cart_item['data'] ) != $subscription->get_billing_period() || WC_Subscriptions_Product::get_interval( $cart_item['data'] ) != $subscription->get_billing_interval(); + } + + /** + * Check if a cart item contains a different payment timestamp to the subscription being switched. + * + * Used to determine if a new subscription should be created as the result of a switch request. + * @see self::cart_contains_subscription_creating_switch() and self::process_checkout(). + * + * @param array $cart_item + * @param WC_Subscription $subscription + * @since 2.2.19 + */ + protected static function has_different_payment_date( $cart_item, $subscription ) { + + // If there are no more payments due on the subscription, because we're in the last billing period, we need to use the subscription's expiration date, not next payment date + if ( 0 === ( $next_payment_timestamp = $subscription->get_time( 'next_payment' ) ) ) { + $next_payment_timestamp = $subscription->get_time( 'end' ); + } + + if ( 0 !== $cart_item['subscription_switch']['first_payment_timestamp'] && $next_payment_timestamp !== $cart_item['subscription_switch']['first_payment_timestamp'] ) { + $is_different_payment_date = true; + } elseif ( 0 !== $cart_item['subscription_switch']['first_payment_timestamp'] && 0 === $subscription->get_time( 'next_payment' ) ) { // if the subscription doesn't have a next payment but the switched item does + $is_different_payment_date = true; + } else { + $is_different_payment_date = false; + } + + return $is_different_payment_date; + } + + /** + * Determine if a recurring cart has a different length (end date) to a subscription. + * + * Used to determine if a new subscription should be created as the result of a switch request. + * @see self::cart_contains_subscription_creating_switch() and self::process_checkout(). + * + * @param WC_Cart $recurring_cart + * @param WC_Subscription $subscription + * @return bool + * @since 2.2.19 + */ + protected static function has_different_length( $recurring_cart, $subscription ) { + $recurring_cart_end_date = gmdate( 'Y-m-d', wcs_date_to_time( $recurring_cart->end_date ) ); + $subscription_end_date = gmdate( 'Y-m-d', $subscription->get_time( 'end' ) ); + + return $recurring_cart_end_date !== $subscription_end_date; + } + + /** + * Checks if a subscription has a single line item. + * + * Used to determine if a new subscription should be created as the result of a switch request. + * @see self::cart_contains_subscription_creating_switch() and self::process_checkout(). + * + * @param WC_Subscription $subscription + * @return bool + * @since 2.2.19 + */ + protected static function is_single_item_subscription( $subscription ) { + // WC_Abstract_Order::get_item_count() uses quantities, not just line item rows + return 1 === count( $subscription->get_items() ); + } + + /** + * Check if the cart contains a subscription switch which will result in a new subscription being created. + * + * New subscriptions will be created when: + * - The current subscription has more than 1 line item @see self::is_single_item_subscription() and + * - the recurring cart has a different length @see self::has_different_length() or + * - the switched cart item has a different payment date @see self::has_different_payment_date() or + * - the switched cart item has a different billing schedule @see self::has_different_billing_schedule() + * + * @return bool + * @since 2.2.19 + */ + public static function cart_contains_subscription_creating_switch() { + $cart_contains_subscription_creating_switch = false; + + foreach ( WC()->cart->recurring_carts as $recurring_cart_key => $recurring_cart ) { + + foreach ( $recurring_cart->get_cart() as $cart_item_key => $cart_item ) { + + if ( ! isset( $cart_item['subscription_switch']['subscription_id'] ) ) { + continue; + } + + $subscription = wcs_get_subscription( $cart_item['subscription_switch']['subscription_id'] ); + + if ( + ! self::is_single_item_subscription( $subscription ) && ( + self::has_different_length( $recurring_cart, $subscription ) || + self::has_different_payment_date( $cart_item, $subscription ) || + self::has_different_billing_schedule( $cart_item, $subscription ) ) + ) { + $cart_contains_subscription_creating_switch = true; + break 2; + } + } + } + + return $cart_contains_subscription_creating_switch; + } + /** Deprecated Methods **/ /** diff --git a/includes/class-wcs-action-scheduler.php b/includes/class-wcs-action-scheduler.php index 3b2038d..bef25b9 100644 --- a/includes/class-wcs-action-scheduler.php +++ b/includes/class-wcs-action-scheduler.php @@ -75,9 +75,15 @@ class WCS_Action_Scheduler extends WCS_Scheduler { foreach ( $this->action_hooks as $action_hook => $date_type ) { + $event_time = $subscription->get_time( $date_type ); + + // If there's no payment retry date, avoid calling get_action_args() because it calls the resource intensive WC_Subscription::get_last_order() / get_related_orders() + if ( 'payment_retry' === $date_type && 0 === $event_time ) { + continue; + } + $action_args = $this->get_action_args( $date_type, $subscription ); $next_scheduled = wc_next_scheduled_action( $action_hook, $action_args ); - $event_time = $subscription->get_time( $date_type ); // Maybe clear the existing schedule for this hook if ( false !== $next_scheduled && $next_scheduled != $event_time ) { diff --git a/includes/class-wcs-cached-data-manager.php b/includes/class-wcs-cached-data-manager.php index 70d64ce..447bcab 100644 --- a/includes/class-wcs-cached-data-manager.php +++ b/includes/class-wcs-cached-data-manager.php @@ -81,7 +81,7 @@ class WCS_Cached_Data_Manager extends WCS_Cache_Manager { public function purge_delete( $post_id, $post = null ) { $post_type = get_post_type( $post_id ); if ( 'shop_order' === $post_type ) { - foreach ( wcs_get_subscriptions_for_order( $post_id, array( 'order_type' => 'any' ) ) as $subscription ) { + foreach ( wcs_get_subscriptions_for_order( $post_id, array( 'order_type' => 'renewal' ) ) as $subscription ) { $this->log( 'Calling purge delete on ' . current_filter() . ' for ' . $subscription->get_id() ); $this->clear_related_order_cache( $subscription ); } diff --git a/includes/class-wcs-cart-resubscribe.php b/includes/class-wcs-cart-resubscribe.php index 6e92d2e..eb0cdff 100644 --- a/includes/class-wcs-cart-resubscribe.php +++ b/includes/class-wcs-cart-resubscribe.php @@ -265,14 +265,17 @@ class WCS_Cart_Resubscribe extends WCS_Cart_Renewal { * Make sure resubscribe cart item price doesn't include any recurring amount by setting a free trial. * * @since 2.1 + * @param mixed $total This parameter is unused. Its sole purpose is for returning an unchanged variable while setting the mock trial when hooked onto filters. Optional. + * @return mixed $total The unchanged $total parameter. */ public function maybe_set_free_trial( $total = '' ) { + $subscription = $this->get_order(); - foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { - $subscription = $this->get_order( $cart_item ); - if ( false !== $subscription && $subscription->has_status( 'pending-cancel' ) ) { - wcs_set_objects_property( WC()->cart->cart_contents[ $cart_item_key ]['data'], 'subscription_trial_length', 1, 'set_prop_only' ); - break; + if ( false !== $subscription && $subscription->has_status( 'pending-cancel' ) ) { + foreach ( WC()->cart->cart_contents as &$cart_item ) { + if ( isset( $cart_item[ $this->cart_item_key ] ) ) { + wcs_set_objects_property( $cart_item['data'], 'subscription_trial_length', 1, 'set_prop_only' ); + } } } @@ -283,14 +286,17 @@ class WCS_Cart_Resubscribe extends WCS_Cart_Renewal { * Remove mock free trials from resubscribe cart items. * * @since 2.1 + * @param mixed $total This parameter is unused. Its sole purpose is for returning an unchanged variable while unsetting the mock trial when hooked onto filters. Optional. + * @return mixed $total The unchanged $total parameter. */ public function maybe_unset_free_trial( $total = '' ) { + $subscription = $this->get_order(); - foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { - $subscription = $this->get_order( $cart_item ); - if ( false !== $subscription && $subscription->has_status( 'pending-cancel' ) ) { - wcs_set_objects_property( WC()->cart->cart_contents[ $cart_item_key ]['data'], 'subscription_trial_length', 0, 'set_prop_only' ); - break; + if ( false !== $subscription && $subscription->has_status( 'pending-cancel' ) ) { + foreach ( WC()->cart->cart_contents as &$cart_item ) { + if ( isset( $cart_item[ $this->cart_item_key ] ) ) { + wcs_set_objects_property( $cart_item['data'], 'subscription_trial_length', 0, 'set_prop_only' ); + } } } diff --git a/includes/class-wcs-failed-scheduled-action-manager.php b/includes/class-wcs-failed-scheduled-action-manager.php new file mode 100644 index 0000000..ea04239 --- /dev/null +++ b/includes/class-wcs-failed-scheduled-action-manager.php @@ -0,0 +1,197 @@ + 1, + 'woocommerce_scheduled_subscription_payment' => 1, + 'woocommerce_scheduled_subscription_payment_retry' => 1, + 'woocommerce_scheduled_subscription_expiration' => 1, + 'woocommerce_scheduled_subscription_end_of_prepaid_term' => 1, + ); + + /** + * WC Logger instance for logging messages. + * + * @var WC_Logger + */ + protected $logger; + + /** + * Constructor. + * + * @param WC_Logger $logger The WC Logger instance. + * @since 2.2.19 + */ + public function __construct( WC_Logger $logger ) { + $this->logger = $logger; + } + + /** + * Attach callbacks. + * + * @since 2.2.19 + */ + public function init() { + add_action( 'action_scheduler_failed_action', array( $this, 'log_action_scheduler_failure' ), 10, 2 ); + add_action( 'admin_notices', array( $this, 'maybe_show_admin_notice' ) ); + } + + /** + * Log a message to the failed-scheduled-actions log. + * + * @param string $message the message to be written to the log. + * @since 2.2.19 + */ + protected function log( $message ) { + $this->logger->add( 'failed-scheduled-actions', $message ); + } + + /** + * When a scheduled action failure is triggered, log information about the failed action to a WC logger. + * + * @param int $action_id the action which failed. + * @param int $timeout the number of seconds an action can run for before timing out. + * @since 2.2.19 + */ + public function log_action_scheduler_failure( $action_id, $timeout ) { + $action = $this->get_action( $action_id ); + + if ( ! isset( $this->tracked_scheduled_actions[ $action->get_hook() ] ) ) { + return; + } + + $subscription_action = $this->get_action_hook_label( $action->get_hook() ); + + $this->log( sprintf( 'scheduled action %s (%s) failed to finish processing after %s seconds', $action_id, $subscription_action , $timeout ) ); + $this->log( sprintf( 'action args: %s', $this->get_action_args_string( $action->get_args() ) ) ); + + // Store information about the scheduled action for displaying an admin notice + $failed_scheduled_actions = get_option( WC_Subscriptions_Admin::$option_prefix . '_failed_scheduled_actions', array() ); + + $failed_scheduled_actions[ $action_id ] = array( + 'args' => $action->get_args(), + 'type' => $subscription_action, + ); + + update_option( WC_Subscriptions_Admin::$option_prefix . '_failed_scheduled_actions', $failed_scheduled_actions ); + } + + /** + * Display an admin notice when a scheduled action failure has occurred. + * + * @since 2.2.19 + */ + public function maybe_show_admin_notice() { + $this->maybe_disable_admin_notice(); + $failed_scheduled_actions = get_option( WC_Subscriptions_Admin::$option_prefix . '_failed_scheduled_actions', array() ); + + if ( empty( $failed_scheduled_actions ) ) { + return; + } + + $affected_subscription_events = $separator = ''; + + foreach ( array_slice( $failed_scheduled_actions, -10, 10 ) as $action ) { + + if ( isset( $action['args']['subscription_id'] ) ) { + $subject = '#' . $action['args']['subscription_id'] . ''; + } elseif ( isset( $action['args']['order_id'] ) ) { + $subject = '#' . $action['args']['order_id'] . ''; + } else { + $subject = 'unknown'; + } + + $affected_subscription_events .= $separator . $action['type'] . ' for ' . $subject; + $separator = "\n"; + }?> + $value ) { + if ( is_scalar( $value ) ) { + $args_string .= $separator . $key . ': ' . $value; + $separator = ', '; + } + } + + return $args_string; + } + + /** + * Get a scheduled action object + * + * @param int $action_id the scheduled action ID + * @return ActionScheduler_Action + * @since 2.2.19 + */ + protected function get_action( $action_id ) { + $store = ActionScheduler_Store::instance(); + return $store->fetch_action( $action_id ); + } +} diff --git a/includes/class-wcs-query.php b/includes/class-wcs-query.php index 159997f..d043e37 100644 --- a/includes/class-wcs-query.php +++ b/includes/class-wcs-query.php @@ -96,7 +96,11 @@ class WCS_Query extends WC_Query { $title = ( $subscription ) ? sprintf( _x( 'Subscription #%s', 'hash before order number', 'woocommerce-subscriptions' ), $subscription->get_order_number() ) : ''; break; case 'subscriptions': - $title = __( 'Subscriptions', 'woocommerce-subscriptions' ); + if ( ! empty( $wp->query_vars['subscriptions'] ) ) { + $title = sprintf( __( 'Subscriptions (page %d)', 'woocommerce-subscriptions' ), intval( $wp->query_vars['subscriptions'] ) ); + } else { + $title = __( 'Subscriptions', 'woocommerce-subscriptions' ); + } break; default: $title = ''; @@ -138,7 +142,7 @@ class WCS_Query extends WC_Query { * @return string */ public function maybe_redirect_to_only_subscription( $url, $endpoint ) { - if ( 'subscriptions' == $endpoint ) { + if ( 'subscriptions' == $endpoint && is_account_page() ) { $subscriptions = wcs_get_users_subscriptions(); if ( 1 == count( $subscriptions ) ) { @@ -152,9 +156,14 @@ class WCS_Query extends WC_Query { /** * Endpoint HTML content. + * + * @param int $current_page */ - public function endpoint_content() { - wc_get_template( 'myaccount/subscriptions.php', array(), '', plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'templates/' ); + public function endpoint_content( $current_page = 1 ) { + + $current_page = empty( $current_page ) ? 1 : absint( $current_page ); + + wc_get_template( 'myaccount/subscriptions.php', array( 'current_page' => $current_page ), '', plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'templates/' ); } /** diff --git a/includes/class-wcs-template-loader.php b/includes/class-wcs-template-loader.php index b8b796e..c648687 100644 --- a/includes/class-wcs-template-loader.php +++ b/includes/class-wcs-template-loader.php @@ -8,9 +8,10 @@ class WCS_Template_Loader { public static function init() { - add_filter( 'wc_get_template', __CLASS__ . '::add_view_subscription_template', 10, 5 ); - - add_action( 'woocommerce_account_view-subscription_endpoint', __CLASS__ . '::get_view_subscription_template' ); + add_filter( 'wc_get_template', array( __CLASS__, 'add_view_subscription_template' ), 10, 5 ); + add_action( 'woocommerce_account_view-subscription_endpoint', array( __CLASS__, 'get_view_subscription_template' ) ); + add_action( 'woocommerce_subscription_details_table', array( __CLASS__, 'get_subscription_details_template' ) ); + add_action( 'woocommerce_subscription_totals_table', array( __CLASS__, 'get_subscription_totals_template' ) ); } /** @@ -41,5 +42,25 @@ class WCS_Template_Loader { public static function get_view_subscription_template() { wc_get_template( 'myaccount/view-subscription.php', array(), '', plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'templates/' ); } + + /** + * Get the subscription details template, which is part of the view subscription page. + * + * @param WC_Subscription $subscription Subscription object + * @since 2.2.19 + */ + public static function get_subscription_details_template( $subscription ) { + wc_get_template( 'myaccount/subscription-details.php', array( 'subscription' => $subscription ), '', plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'templates/' ); + } + + /** + * Get the subscription totals template, which is part of the view subscription page. + * + * @param WC_Subscription $subscription Subscription object + * @since 2.2.19 + */ + public static function get_subscription_totals_template( $subscription ) { + wc_get_template( 'myaccount/subscription-totals.php', array( 'subscription' => $subscription ), '', plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'templates/' ); + } } WCS_Template_Loader::init(); diff --git a/includes/data-stores/class-wcs-subscription-data-store-cpt.php b/includes/data-stores/class-wcs-subscription-data-store-cpt.php index fb21507..1f16ac1 100644 --- a/includes/data-stores/class-wcs-subscription-data-store-cpt.php +++ b/includes/data-stores/class-wcs-subscription-data-store-cpt.php @@ -117,7 +117,13 @@ class WCS_Subscription_Data_Store_CPT extends WC_Order_Data_Store_CPT implements * @since 2.2.0 */ public function update( &$subscription ) { - parent::update( $subscription ); + + // We don't want to call parent here becuase WC_Order_Data_Store_CPT includes a JIT setting of the paid date which is not needed for subscriptions, and also very resource intensive + Abstract_WC_Order_Data_Store_CPT::update( $subscription ); + + // We used to call parent::update() above, which triggered this hook, so we trigger it manually here for backward compatibilty (and to improve compatibility with 3rd party code which may run validation or additional operations on it which should also be applied to a subscription) + do_action( 'woocommerce_update_order', $subscription->get_id() ); + do_action( 'woocommerce_update_subscription', $subscription->get_id() ); } @@ -309,4 +315,34 @@ class WCS_Subscription_Data_Store_CPT extends WC_Order_Data_Store_CPT implements return $saved_dates; } + + /** + * Get the props to update, and remove order meta data that isn't used on a subscription. + * + * Important for performance, because it avoids calling getters/setters on props that don't need + * to be get/set, which in the case for get_date_paid(), or get_date_completed(), can be quite + * resource intensive as it requires doing a related orders query. Also just avoids filling up the + * post meta table more than is needed. + * + * @param WC_Data $object The WP_Data object (WC_Coupon for coupons, etc). + * @param array $meta_key_to_props A mapping of meta keys => prop names. + * @param string $meta_type The internal WP meta type (post, user, etc). + * @return array A mapping of meta keys => prop names, filtered by ones that should be updated. + */ + protected function get_props_to_update( $object, $meta_key_to_props, $meta_type = 'post' ) { + $props_to_update = parent::get_props_to_update( $object, $meta_key_to_props, $meta_type ); + + $props_to_ignore = array( + '_transaction_id' => 'transaction_id', + '_date_completed' => 'date_completed', + '_date_paid' => 'date_paid', + '_cart_hash' => 'cart_hash', + ); + + foreach ( $props_to_ignore as $meta_key => $prop ) { + unset( $props_to_update[ $meta_key ] ); + } + + return $props_to_update; + } } diff --git a/includes/wcs-limit-functions.php b/includes/wcs-limit-functions.php index bec2e5e..67db4af 100644 --- a/includes/wcs-limit-functions.php +++ b/includes/wcs-limit-functions.php @@ -38,5 +38,36 @@ function wcs_is_product_limited_for_user( $product, $user_id = 0 ) { $product = wc_get_product( $product ); } - return ( ( 'active' == wcs_get_product_limitation( $product ) && wcs_user_has_subscription( $user_id, $product->get_id(), 'on-hold' ) ) || ( 'no' !== wcs_get_product_limitation( $product ) && wcs_user_has_subscription( $user_id, $product->get_id(), wcs_get_product_limitation( $product ) ) ) ) ? true : false; + if ( empty( $user_id ) ) { + $user_id = get_current_user_id(); + } + + $is_limited_for_user = false; + $product_limitation = wcs_get_product_limitation( $product ); + + // If the subscription is limited to 1 active and the customer has a subscription to this product on-hold. + if ( 'active' == $product_limitation && wcs_user_has_subscription( $user_id, $product->get_id(), 'on-hold' ) ) { + $is_limited_for_user = true; + } elseif ( 'no' !== $product_limitation ) { + $is_limited_for_user = wcs_user_has_subscription( $user_id, $product->get_id(), $product_limitation ); + + // If the product is limited for any status, there exists a chance that the customer has cancelled subscriptions which cannot be resubscribed to as they have no completed payments. + if ( 'any' === $product_limitation && $is_limited_for_user ) { + $is_limited_for_user = false; + $user_subscriptions = wcs_get_subscriptions( array( + 'subscriptions_per_page' => -1, + 'customer_id' => $user_id, + 'product_id' => $product->get_id(), + ) ); + + foreach ( $user_subscriptions as $subscription ) { + if ( ! $subscription->has_status( 'cancelled' ) || 0 !== $subscription->get_completed_payment_count() ) { + $is_limited_for_user = true; + break; + } + } + } + } + + return apply_filters( 'woocommerce_subscriptions_product_limited_for_user', $is_limited_for_user, $product, $user_id ); } diff --git a/includes/wcs-resubscribe-functions.php b/includes/wcs-resubscribe-functions.php index 5c7a302..a0dbd7d 100644 --- a/includes/wcs-resubscribe-functions.php +++ b/includes/wcs-resubscribe-functions.php @@ -51,7 +51,7 @@ function wcs_create_resubscribe_order( $subscription ) { $resubscribe_order = wcs_create_order_from_subscription( $subscription, 'resubscribe_order' ); if ( is_wp_error( $resubscribe_order ) ) { - return new WP_Error( 'resubscribe-order-error', $renewal_order->get_error_message() ); + return new WP_Error( 'resubscribe-order-error', $resubscribe_order->get_error_message() ); } // Keep a record of the original subscription's ID on the new order diff --git a/includes/wcs-time-functions.php b/includes/wcs-time-functions.php index b24c44f..60d7fe6 100644 --- a/includes/wcs-time-functions.php +++ b/includes/wcs-time-functions.php @@ -644,9 +644,9 @@ function wcs_date_to_time( $date_string ) { return 0; } - $date_obj = new DateTime( $date_string, new DateTimeZone( 'UTC' ) ); + $date_time = new WC_DateTime( $date_string, new DateTimeZone( 'UTC' ) ); - return intval( $date_obj->format( 'U' ) ); + return intval( $date_time->getTimestamp() ); } /** diff --git a/languages/woocommerce-subscriptions.pot b/languages/woocommerce-subscriptions.pot index 07b79fa..540e408 100644 --- a/languages/woocommerce-subscriptions.pot +++ b/languages/woocommerce-subscriptions.pot @@ -2,10 +2,10 @@ # This file is distributed under the same license as the WooCommerce Subscriptions package. msgid "" msgstr "" -"Project-Id-Version: WooCommerce Subscriptions 2.2.18\n" +"Project-Id-Version: WooCommerce Subscriptions 2.2.19\n" "Report-Msgid-Bugs-To: " "https://github.com/Prospress/woocommerce-subscriptions/issues\n" -"POT-Creation-Date: 2018-02-12 01:49:18+00:00\n" +"POT-Creation-Date: 2018-04-04 07:40:31+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -180,17 +180,17 @@ msgid "Manage Subscriptions" msgstr "" #: includes/admin/class-wc-subscriptions-admin.php:873 -#: woocommerce-subscriptions.php:238 +#: woocommerce-subscriptions.php:240 msgid "Search Subscriptions" msgstr "" #: includes/admin/class-wc-subscriptions-admin.php:893 #: includes/admin/class-wc-subscriptions-admin.php:989 #: includes/admin/class-wcs-admin-reports.php:51 -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:654 -#: includes/class-wcs-query.php:99 includes/class-wcs-query.php:119 -#: includes/class-wcs-query.php:235 templates/admin/status.php:21 -#: woocommerce-subscriptions.php:229 woocommerce-subscriptions.php:242 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:658 +#: includes/class-wcs-query.php:102 includes/class-wcs-query.php:123 +#: includes/class-wcs-query.php:244 templates/admin/status.php:21 +#: woocommerce-subscriptions.php:231 woocommerce-subscriptions.php:244 msgid "Subscriptions" msgstr "" @@ -214,8 +214,8 @@ msgstr "" #: includes/class-wc-product-subscription-variation.php:98 #: includes/class-wc-product-subscription.php:72 #: includes/class-wc-product-variable-subscription.php:63 -#: includes/class-wc-subscriptions-product.php:96 -#: woocommerce-subscriptions.php:490 +#: includes/class-wc-subscriptions-product.php:99 +#: woocommerce-subscriptions.php:516 msgid "Sign Up Now" msgstr "" @@ -352,7 +352,7 @@ msgstr "" #: includes/admin/class-wc-subscriptions-admin.php:1200 #: includes/upgrades/templates/wcs-about-2-0.php:35 #: includes/upgrades/templates/wcs-about.php:34 -#: woocommerce-subscriptions.php:1029 +#: woocommerce-subscriptions.php:1057 msgid "Settings" msgstr "" @@ -534,19 +534,19 @@ msgstr[1] "" #: templates/myaccount/related-orders.php:45 #: templates/myaccount/related-subscriptions.php:21 #: templates/myaccount/related-subscriptions.php:35 -#: templates/myaccount/view-subscription.php:32 +#: templates/myaccount/subscription-details.php:18 msgid "Status" msgstr "" #: includes/admin/class-wcs-admin-post-types.php:410 -#: includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php:61 +#: includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php:63 #: templates/emails/cancelled-subscription.php:26 #: templates/emails/expired-subscription.php:26 #: templates/emails/on-hold-subscription.php:26 #: templates/emails/subscription-info.php:18 #: templates/myaccount/my-subscriptions.php:25 #: templates/myaccount/related-subscriptions.php:20 -#: woocommerce-subscriptions.php:230 +#: woocommerce-subscriptions.php:232 msgid "Subscription" msgstr "" @@ -599,12 +599,12 @@ msgid "Delete Permanently" msgstr "" #: includes/admin/class-wcs-admin-post-types.php:474 -#: includes/class-wc-subscriptions-product.php:733 +#: includes/class-wc-subscriptions-product.php:738 msgid "Restore this item from the Trash" msgstr "" #: includes/admin/class-wcs-admin-post-types.php:474 -#: includes/class-wc-subscriptions-product.php:734 +#: includes/class-wc-subscriptions-product.php:739 msgid "Restore" msgstr "" @@ -824,7 +824,7 @@ msgid "Relationship" msgstr "" #: includes/admin/meta-boxes/views/html-related-orders-table.php:19 -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:515 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:519 #: includes/admin/reports/class-wcs-report-subscription-payment-retry.php:173 #: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:205 #: templates/myaccount/related-orders.php:23 @@ -1052,185 +1052,185 @@ msgstr "" msgid "subscriptions" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:364 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:368 msgid "%s signup revenue in this period" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:365 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:369 msgid "" "The sum of all subscription parent orders, including other items, fees, tax " "and shipping." msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:371 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:375 msgid "%s renewal revenue in this period" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:372 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:376 msgid "The sum of all renewal orders including tax and shipping." msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:378 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:382 msgid "%s resubscribe revenue in this period" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:379 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:383 msgid "The sum of all resubscribe orders including tax and shipping." msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:385 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:389 msgid "%s new subscriptions" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:386 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:390 msgid "" "The number of subscriptions created during this period, either by being " "manually created, imported or a customer placing an order." msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:392 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:396 msgid "%s subscription signups" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:393 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:397 msgid "" "The number of subscription parent orders created during this period. This " "represents the new subscriptions created by customers placing an order via " "checkout." msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:399 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:403 msgid "%s subscription resubscribes" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:400 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:404 msgid "The number of resubscribe orders processed during this period." msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:406 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:410 msgid "%s subscription renewals" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:407 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:411 msgid "The number of renewal orders processed during this period." msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:413 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:417 msgid "%s subscription switches" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:414 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:418 msgid "" "The number of subscriptions upgraded, downgraded or cross-graded during " "this period." msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:420 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:424 msgid "%s subscription cancellations" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:421 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:425 msgid "" "The number of subscriptions cancelled by the customer or store manager " "during this period. The pre-paid term may not yet have ended during this " "period." msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:427 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:431 msgid "%s subscriptions ended" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:428 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:432 msgid "" "The number of subscriptions which have either expired or reached the end of " "the prepaid term if it was previously cancelled." msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:434 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:438 msgid "%s current subscriptions" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:435 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:439 msgid "" "The number of subscriptions during this period with an end date in the " "future and a status other than pending." msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:451 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:455 msgid "%s net subscription gain" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:453 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:457 msgid "%s net subscription loss" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:458 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:462 msgid "Change in subscriptions between the start and end of the period." msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:472 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:476 #: includes/admin/reports/class-wcs-report-subscription-payment-retry.php:137 msgid "Year" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:473 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:477 #: includes/admin/reports/class-wcs-report-subscription-payment-retry.php:138 msgid "Last Month" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:474 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:478 #: includes/admin/reports/class-wcs-report-subscription-payment-retry.php:139 msgid "This Month" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:475 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:479 #: includes/admin/reports/class-wcs-report-subscription-payment-retry.php:140 msgid "Last 7 Days" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:519 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:523 #: includes/admin/reports/class-wcs-report-subscription-payment-retry.php:177 #: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:209 msgid "Export CSV" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:576 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:580 msgid "Switched subscriptions" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:592 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:596 msgid "New Subscriptions" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:608 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:612 msgid "Subscriptions signups" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:623 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:627 msgid "Number of resubscribes" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:638 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:642 msgid "Number of renewals" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:670 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:674 msgid "Subscriptions Ended" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:686 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:690 msgid "Cancellations" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:701 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:705 msgid "Signup Totals" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:721 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:725 msgid "Resubscribe Totals" msgstr "" -#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:741 +#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:745 msgid "Renewal Totals" msgstr "" @@ -1487,70 +1487,66 @@ msgid "The %s date of a subscription can not be deleted. You must delete the ord msgstr "" #: includes/class-wc-subscription.php:1288 -#: includes/class-wc-subscription.php:2345 +#: includes/class-wc-subscription.php:2353 msgid "Subscription #%d: " msgstr "" -#: includes/class-wc-subscription.php:1682 -msgid "Sign-up complete." +#: includes/class-wc-subscription.php:1694 +msgid "Payment status marked complete." msgstr "" -#: includes/class-wc-subscription.php:1684 -msgid "Payment received." -msgstr "" - -#: includes/class-wc-subscription.php:1715 +#: includes/class-wc-subscription.php:1722 msgid "Payment failed." msgstr "" -#: includes/class-wc-subscription.php:1720 +#: includes/class-wc-subscription.php:1727 msgid "Subscription Cancelled: maximum number of failed payments reached." msgstr "" -#: includes/class-wc-subscription.php:1929 +#: includes/class-wc-subscription.php:1936 #: includes/class-wcs-change-payment-method-admin.php:155 msgid "Manual Renewal" msgstr "" -#: includes/class-wc-subscription.php:2008 +#: includes/class-wc-subscription.php:2015 msgid "Payment method meta must be an array." msgstr "" -#: includes/class-wc-subscription.php:2243 +#: includes/class-wc-subscription.php:2251 msgid "Invalid format. First parameter needs to be an array." msgstr "" -#: includes/class-wc-subscription.php:2247 +#: includes/class-wc-subscription.php:2255 msgid "Invalid data. First parameter was empty when passed to update_dates()." msgstr "" -#: includes/class-wc-subscription.php:2254 +#: includes/class-wc-subscription.php:2262 msgid "" "Invalid data. First parameter has a date that is not in the registered date " "types." msgstr "" -#: includes/class-wc-subscription.php:2318 +#: includes/class-wc-subscription.php:2326 msgid "The %s date must occur after the cancellation date." msgstr "" -#: includes/class-wc-subscription.php:2323 +#: includes/class-wc-subscription.php:2331 msgid "The %s date must occur after the last payment date." msgstr "" -#: includes/class-wc-subscription.php:2327 +#: includes/class-wc-subscription.php:2335 msgid "The %s date must occur after the next payment date." msgstr "" -#: includes/class-wc-subscription.php:2332 +#: includes/class-wc-subscription.php:2340 msgid "The %s date must occur after the trial end date." msgstr "" -#: includes/class-wc-subscription.php:2336 +#: includes/class-wc-subscription.php:2344 msgid "The %s date must occur after the start date." msgstr "" -#: includes/class-wc-subscription.php:2365 +#: includes/class-wc-subscription.php:2373 #: includes/class-wc-subscriptions-checkout.php:325 #: includes/wcs-order-functions.php:288 msgid "Backordered" @@ -1572,21 +1568,21 @@ msgstr "" msgid "Update the %1$s used for %2$sall%3$s of my active subscriptions" msgstr "" -#: includes/class-wc-subscriptions-cart.php:891 +#: includes/class-wc-subscriptions-cart.php:899 msgid "Please enter a valid postcode/ZIP." msgstr "" -#: includes/class-wc-subscriptions-cart.php:1062 +#: includes/class-wc-subscriptions-cart.php:1070 msgid "" "That subscription product can not be added to your cart as it already " "contains a subscription renewal." msgstr "" -#: includes/class-wc-subscriptions-cart.php:1150 +#: includes/class-wc-subscriptions-cart.php:1158 msgid "Invalid recurring shipping method." msgstr "" -#: includes/class-wc-subscriptions-cart.php:1939 +#: includes/class-wc-subscriptions-cart.php:1947 msgid "now" msgstr "" @@ -1690,61 +1686,61 @@ msgstr "" msgid "Recurring Product % Discount" msgstr "" -#: includes/class-wc-subscriptions-coupon.php:356 +#: includes/class-wc-subscriptions-coupon.php:358 msgid "" "Sorry, this coupon is only valid for an initial payment and the cart does " "not require an initial payment." msgstr "" -#: includes/class-wc-subscriptions-coupon.php:362 +#: includes/class-wc-subscriptions-coupon.php:364 msgid "Sorry, this coupon is only valid for new subscriptions." msgstr "" -#: includes/class-wc-subscriptions-coupon.php:367 +#: includes/class-wc-subscriptions-coupon.php:369 msgid "Sorry, this coupon is only valid for subscription products." msgstr "" -#: includes/class-wc-subscriptions-coupon.php:373 +#: includes/class-wc-subscriptions-coupon.php:375 #. translators: 1$: coupon code that is being removed msgid "Sorry, the \"%1$s\" coupon is only valid for renewals." msgstr "" -#: includes/class-wc-subscriptions-coupon.php:378 +#: includes/class-wc-subscriptions-coupon.php:380 msgid "" "Sorry, this coupon is only valid for subscription products with a sign-up " "fee." msgstr "" -#: includes/class-wc-subscriptions-coupon.php:404 +#: includes/class-wc-subscriptions-coupon.php:406 msgid "" "Sorry, recurring coupons can only be applied to subscriptions or " "subscription orders." msgstr "" -#: includes/class-wc-subscriptions-coupon.php:408 +#: includes/class-wc-subscriptions-coupon.php:410 #. translators: placeholder is coupon code msgid "" "Sorry, \"%s\" can only be applied to subscription parent orders which " "contain a product with signup fees." msgstr "" -#: includes/class-wc-subscriptions-coupon.php:411 +#: includes/class-wc-subscriptions-coupon.php:413 msgid "Sorry, only recurring coupons can only be applied to subscriptions." msgstr "" -#: includes/class-wc-subscriptions-coupon.php:634 +#: includes/class-wc-subscriptions-coupon.php:636 msgid "Renewal % discount" msgstr "" -#: includes/class-wc-subscriptions-coupon.php:635 +#: includes/class-wc-subscriptions-coupon.php:637 msgid "Renewal product discount" msgstr "" -#: includes/class-wc-subscriptions-coupon.php:636 +#: includes/class-wc-subscriptions-coupon.php:638 msgid "Renewal cart discount" msgstr "" -#: includes/class-wc-subscriptions-coupon.php:653 +#: includes/class-wc-subscriptions-coupon.php:655 msgid "Renewal Discount" msgstr "" @@ -1874,7 +1870,7 @@ msgstr "" msgid "Subscription cancelled for refunded order %1$s#%2$s%3$s." msgstr "" -#: includes/class-wc-subscriptions-product.php:316 +#: includes/class-wc-subscriptions-product.php:319 #: includes/wcs-formatting-functions.php:102 #: includes/wcs-formatting-functions.php:186 #. translators: 1$: recurring amount string, 2$: day of the week (e.g. "$10 @@ -1882,34 +1878,34 @@ msgstr "" msgid "%1$s every %2$s" msgstr "" -#: includes/class-wc-subscriptions-product.php:319 +#: includes/class-wc-subscriptions-product.php:322 #: includes/wcs-formatting-functions.php:111 #. translators: 1$: recurring amount string, 2$: period, 3$: day of the week #. (e.g. "$10 every 2nd week on Wednesday") msgid "%1$s every %2$s on %3$s" msgstr "" -#: includes/class-wc-subscriptions-product.php:326 +#: includes/class-wc-subscriptions-product.php:329 #: includes/wcs-formatting-functions.php:129 #. translators: placeholder is recurring amount msgid "%s on the last day of each month" msgstr "" -#: includes/class-wc-subscriptions-product.php:329 +#: includes/class-wc-subscriptions-product.php:332 #: includes/wcs-formatting-functions.php:132 #. translators: 1$: recurring amount, 2$: day of the month (e.g. "23rd") (e.g. #. "$5 every 23rd of each month") msgid "%1$s on the %2$s of each month" msgstr "" -#: includes/class-wc-subscriptions-product.php:334 +#: includes/class-wc-subscriptions-product.php:337 #: includes/wcs-formatting-functions.php:148 #. translators: 1$: recurring amount, 2$: interval (e.g. "3rd") (e.g. "$10 on #. the last day of every 3rd month") msgid "%1$s on the last day of every %2$s month" msgstr "" -#: includes/class-wc-subscriptions-product.php:337 +#: includes/class-wc-subscriptions-product.php:340 #: includes/wcs-formatting-functions.php:151 #. translators: 1$: on the, 2$: day of every, 3$: #. month (e.g. "$10 on the 23rd day of every 2nd month") @@ -1918,7 +1914,7 @@ msgstr "" msgid "%1$s on the %2$s day of every %3$s month" msgstr "" -#: includes/class-wc-subscriptions-product.php:344 +#: includes/class-wc-subscriptions-product.php:347 #: includes/wcs-formatting-functions.php:164 #. translators: 1$: on, 2$: , 3$: each year (e.g. "$15 on #. March 15th each year") @@ -1927,14 +1923,14 @@ msgstr "" msgid "%1$s on %2$s %3$s each year" msgstr "" -#: includes/class-wc-subscriptions-product.php:347 +#: includes/class-wc-subscriptions-product.php:350 #: includes/wcs-formatting-functions.php:173 #. translators: 1$: recurring amount, 2$: month (e.g. "March"), 3$: day of the #. month (e.g. "23rd") (e.g. "$15 on March 15th every 3rd year") msgid "%1$s on %2$s %3$s every %4$s year" msgstr "" -#: includes/class-wc-subscriptions-product.php:353 +#: includes/class-wc-subscriptions-product.php:356 #: includes/wcs-formatting-functions.php:184 #. translators: 1$: recurring amount, 2$: subscription period (e.g. "month" or #. "3 months") (e.g. "$15 / month" or "$15 every 2nd month") @@ -1943,31 +1939,38 @@ msgid_plural " %1$s every %2$s" msgstr[0] "" msgstr[1] "" -#: includes/class-wc-subscriptions-product.php:359 +#: includes/class-wc-subscriptions-product.php:362 #. translators: billing period (e.g. "every week") msgid "every %s" msgstr "" -#: includes/class-wc-subscriptions-product.php:365 +#: includes/class-wc-subscriptions-product.php:370 #: includes/wcs-formatting-functions.php:194 #. translators: 1$: subscription string (e.g. "$10 up front then $5 on March #. 23rd every 3rd year"), 2$: length (e.g. "4 years") msgid "%1$s for %2$s" msgstr "" -#: includes/class-wc-subscriptions-product.php:371 +#: includes/class-wc-subscriptions-product.php:376 #. translators: 1$: subscription string (e.g. "$15 on March 15th every 3 years #. for 6 years"), 2$: trial length (e.g.: "with 4 months free trial") msgid "%1$s with %2$s free trial" msgstr "" -#: includes/class-wc-subscriptions-product.php:376 +#: includes/class-wc-subscriptions-product.php:381 #. translators: 1$: subscription string (e.g. "$15 on March 15th every 3 years #. for 6 years with 2 months free trial"), 2$: signup fee price (e.g. "and a #. $30 sign-up fee") msgid "%1$s and a %2$s sign-up fee" msgstr "" +#: includes/class-wc-subscriptions-product.php:923 +msgid "" +"This variation can not be removed because it is associated with active " +"subscriptions. To remove this variation, please cancel and delete the " +"subscriptions for it." +msgstr "" + #: includes/class-wc-subscriptions-renewal-order.php:160 #. translators: placeholder is order ID msgid "Order %s created to record renewal." @@ -1988,7 +1991,7 @@ msgid "Choose a new subscription." msgstr "" #: includes/class-wc-subscriptions-switcher.php:198 -#: includes/class-wc-subscriptions-switcher.php:1036 +#: includes/class-wc-subscriptions-switcher.php:1009 msgid "" "Your cart contained an invalid subscription switch request. It has been " "removed." @@ -2080,54 +2083,54 @@ msgstr "" #: includes/class-wc-subscriptions-switcher.php:424 #: includes/class-wc-subscriptions-switcher.php:450 -#: includes/class-wc-subscriptions-switcher.php:2386 +#: includes/class-wc-subscriptions-switcher.php:2470 msgid "Upgrade or Downgrade" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:901 +#: includes/class-wc-subscriptions-switcher.php:874 msgid "Switch order cancelled due to a new switch order being created #%s." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:988 +#: includes/class-wc-subscriptions-switcher.php:961 msgid "Switch Order" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1003 +#: includes/class-wc-subscriptions-switcher.php:976 msgid "Switched Subscription" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1108 +#: includes/class-wc-subscriptions-switcher.php:1081 msgid "You can only switch to a subscription product." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1114 +#: includes/class-wc-subscriptions-switcher.php:1087 msgid "We can not find your old subscription item." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1136 +#: includes/class-wc-subscriptions-switcher.php:1109 msgid "You can not switch to the same subscription." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1183 +#: includes/class-wc-subscriptions-switcher.php:1156 msgid "" "You can not switch this subscription. It appears you do not own the " "subscription." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1224 +#: includes/class-wc-subscriptions-switcher.php:1197 msgid "There was an error locating the switch details." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1945 -#: includes/class-wc-subscriptions-switcher.php:2293 +#: includes/class-wc-subscriptions-switcher.php:1918 +#: includes/class-wc-subscriptions-switcher.php:2262 msgid "The original subscription item being switched cannot be found." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1947 +#: includes/class-wc-subscriptions-switcher.php:1920 msgid "The item on the switch order cannot be found." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:2330 +#: includes/class-wc-subscriptions-switcher.php:2299 msgid "Failed to update the subscription shipping method." msgstr "" @@ -2276,7 +2279,7 @@ msgstr "" msgid "Complete checkout to resubscribe." msgstr "" -#: includes/class-wcs-cart-resubscribe.php:313 +#: includes/class-wcs-cart-resubscribe.php:319 msgid "Customer resubscribed in order #%s" msgstr "" @@ -2284,6 +2287,35 @@ msgstr "" msgid "Please choose a valid payment gateway to change to." msgstr "" +#: includes/class-wcs-failed-scheduled-action-manager.php:128 +#. translators: $1: Opening previously translated sentence $2,$5,$9 opening +#. link tags $3 closing link tag $4 opening paragraph tag $6 closing paragraph +#. tag $7 list of affected actions wrapped in code tags $8 the log file name +#. $10 div containing a group of buttons/links +msgid "" +"%1$s Please %2$sopen a new ticket at WooCommerce Support%3$s immediately to " +"get this resolved.%4$sTo resolve this error a quickly as possible, please " +"include login details for a %5$stemporary administrator " +"account%3$s.%6$sAffected events: %7$s%4$sTo see further details, view the " +"%8$s log file from the %9$sWooCommerce logs screen.%3$s%6$s%10$s" +msgstr "" + +#: includes/class-wcs-failed-scheduled-action-manager.php:129 +msgid "An error has occurred while processing a recent subscription related event." +msgid_plural "An error has occurred while processing recent subscription related events." +msgstr[0] "" +msgstr[1] "" + +#: includes/class-wcs-failed-scheduled-action-manager.php:138 +#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:190 +msgid "Ignore this error (not recommended!)" +msgstr "" + +#: includes/class-wcs-failed-scheduled-action-manager.php:138 +#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:190 +msgid "Open up a ticket now!" +msgstr "" + #: includes/class-wcs-limiter.php:45 msgid "Limit subscription" msgstr "" @@ -2329,19 +2361,23 @@ msgstr "" msgid "%s ending in %s" msgstr "" -#: includes/class-wcs-query.php:117 +#: includes/class-wcs-query.php:100 +msgid "Subscriptions (page %d)" +msgstr "" + +#: includes/class-wcs-query.php:121 msgid "My Subscription" msgstr "" -#: includes/class-wcs-query.php:236 +#: includes/class-wcs-query.php:245 msgid "Endpoint for the My Account → Subscriptions page" msgstr "" -#: includes/class-wcs-query.php:244 +#: includes/class-wcs-query.php:253 msgid "View subscription" msgstr "" -#: includes/class-wcs-query.php:245 +#: includes/class-wcs-query.php:254 msgid "Endpoint for the My Account → View Subscription page" msgstr "" @@ -2751,14 +2787,6 @@ msgid "" "error, view the %s log file from the %sWooCommerce logs screen.%s" msgstr "" -#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:190 -msgid "Ignore this error (not recommended!)" -msgstr "" - -#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:190 -msgid "Open up a ticket now!" -msgstr "" - #: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:269 msgid "PayPal Subscription ID:" msgstr "" @@ -3999,7 +4027,19 @@ msgstr "" msgid "ID" msgstr "" -#: templates/myaccount/my-subscriptions.php:72 +#: templates/myaccount/my-subscriptions.php:70 +msgid "Previous" +msgstr "" + +#: templates/myaccount/my-subscriptions.php:74 +msgid "Next" +msgstr "" + +#: templates/myaccount/my-subscriptions.php:81 +msgid "You have reached the end of subscriptions. Go to the %sfirst page%s." +msgstr "" + +#: templates/myaccount/my-subscriptions.php:84 #. translators: placeholders are opening and closing link tags to take to the #. shop page msgid "" @@ -4023,26 +4063,26 @@ msgstr[1] "" msgid "Related Subscriptions" msgstr "" -#: templates/myaccount/view-subscription.php:20 -msgid "My Account" -msgstr "" - -#: templates/myaccount/view-subscription.php:57 +#: templates/myaccount/subscription-details.php:43 msgid "Actions" msgstr "" -#: templates/myaccount/view-subscription.php:69 +#: templates/myaccount/subscription-details.php:56 msgid "Subscription Updates" msgstr "" -#: templates/myaccount/view-subscription.php:88 +#: templates/myaccount/subscription-totals.php:17 msgid "Subscription Totals" msgstr "" -#: templates/myaccount/view-subscription.php:111 +#: templates/myaccount/subscription-totals.php:40 msgid "Are you sure you want remove this item from your subscription?" msgstr "" +#: templates/myaccount/view-subscription.php:20 +msgid "My Account" +msgstr "" + #: templates/single-product/add-to-cart/subscription.php:45 #: templates/single-product/add-to-cart/variable-subscription.php:31 msgid "You have an active subscription to this product already." @@ -4080,71 +4120,71 @@ msgstr "" msgid "Date type can not be an empty string." msgstr "" -#: woocommerce-subscriptions.php:244 +#: woocommerce-subscriptions.php:246 msgid "This is where subscriptions are stored." msgstr "" -#: woocommerce-subscriptions.php:289 +#: woocommerce-subscriptions.php:291 msgid "No Subscriptions found" msgstr "" -#: woocommerce-subscriptions.php:291 +#: woocommerce-subscriptions.php:293 msgid "" "Subscriptions will appear here for you to view and manage once purchased by " "a customer." msgstr "" -#: woocommerce-subscriptions.php:293 +#: woocommerce-subscriptions.php:295 #. translators: placeholders are opening and closing link tags msgid "%sLearn more about managing subscriptions »%s" msgstr "" -#: woocommerce-subscriptions.php:295 +#: woocommerce-subscriptions.php:297 #. translators: placeholders are opening and closing link tags msgid "%sAdd a subscription product »%s" msgstr "" -#: woocommerce-subscriptions.php:410 +#: woocommerce-subscriptions.php:425 msgid "" "A subscription renewal has been removed from your cart. Multiple " "subscriptions can not be purchased at the same time." msgstr "" -#: woocommerce-subscriptions.php:416 +#: woocommerce-subscriptions.php:431 msgid "" "A subscription has been removed from your cart. Due to payment gateway " "restrictions, different subscription products can not be purchased at the " "same time." msgstr "" -#: woocommerce-subscriptions.php:422 +#: woocommerce-subscriptions.php:437 msgid "" "A subscription has been removed from your cart. Products and subscriptions " "can not be purchased at the same time." msgstr "" -#: woocommerce-subscriptions.php:559 woocommerce-subscriptions.php:576 +#: woocommerce-subscriptions.php:585 woocommerce-subscriptions.php:602 #. translators: placeholder is a number, this is for the teens #. translators: placeholder is a number, numbers ending in 4-9, 0 msgid "%sth" msgstr "" -#: woocommerce-subscriptions.php:564 +#: woocommerce-subscriptions.php:590 #. translators: placeholder is a number, numbers ending in 1 msgid "%sst" msgstr "" -#: woocommerce-subscriptions.php:568 +#: woocommerce-subscriptions.php:594 #. translators: placeholder is a number, numbers ending in 2 msgid "%snd" msgstr "" -#: woocommerce-subscriptions.php:572 +#: woocommerce-subscriptions.php:598 #. translators: placeholder is a number, numbers ending in 3 msgid "%srd" msgstr "" -#: woocommerce-subscriptions.php:602 +#: woocommerce-subscriptions.php:628 #. translators: 1$-2$: opening and closing tags, 3$-4$: link tags, #. takes to woocommerce plugin on wp.org, 5$-6$: opening and closing link tags, #. leads to plugins.php in admin @@ -4154,7 +4194,7 @@ msgid "" "%5$sinstall & activate WooCommerce »%6$s" msgstr "" -#: woocommerce-subscriptions.php:609 +#: woocommerce-subscriptions.php:635 #. translators: 1$-2$: opening and closing tags, 3$-4$: opening and #. closing link tags, leads to plugin admin msgid "" @@ -4163,11 +4203,11 @@ msgid "" "WooCommerce to version 2.4 or newer »%4$s" msgstr "" -#: woocommerce-subscriptions.php:635 +#: woocommerce-subscriptions.php:661 msgid "Variable Subscription" msgstr "" -#: woocommerce-subscriptions.php:822 +#: woocommerce-subscriptions.php:850 #. translators: 1$-2$: opening and closing tags, 3$-4$: opening and #. closing link tags. Leads to duplicate site article on docs msgid "" @@ -4177,19 +4217,19 @@ msgid "" "environment. %3$sLearn more »%4$s." msgstr "" -#: woocommerce-subscriptions.php:824 +#: woocommerce-subscriptions.php:852 msgid "Quit nagging me (but don't enable automatic payments)" msgstr "" -#: woocommerce-subscriptions.php:825 +#: woocommerce-subscriptions.php:853 msgid "Enable automatic payments" msgstr "" -#: woocommerce-subscriptions.php:1031 +#: woocommerce-subscriptions.php:1059 msgid "Support" msgstr "" -#: woocommerce-subscriptions.php:1116 +#: woocommerce-subscriptions.php:1144 #. translators: placeholders are opening and closing tags. Leads to docs on #. version 2 msgid "" @@ -4200,14 +4240,14 @@ msgid "" "2.0 »%s" msgstr "" -#: woocommerce-subscriptions.php:1131 +#: woocommerce-subscriptions.php:1159 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:1132 +#: woocommerce-subscriptions.php:1160 msgid "" "Please upgrade the WooCommerce Subscriptions plugin to version 2.0 or newer " "immediately. If you need assistance, after upgrading to Subscriptions v2.0, " @@ -4387,22 +4427,22 @@ msgctxt "used in \"Subscription scheduled for \"" msgid "M j, Y @ G:i" msgstr "" -#: includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php:82 +#: includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php:84 msgctxt "relation to order" msgid "Resubscribed Subscription" msgstr "" -#: includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php:90 +#: includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php:92 msgctxt "relation to order" msgid "Initial Subscription" msgstr "" -#: includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php:99 +#: includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php:101 msgctxt "relation to order" msgid "Parent Order" msgstr "" -#: includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php:109 +#: includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php:111 msgctxt "relation to order" msgid "Renewal Order" msgstr "" @@ -4446,7 +4486,7 @@ msgstr "" #: templates/myaccount/my-subscriptions.php:28 #: templates/myaccount/related-orders.php:25 #: templates/myaccount/related-subscriptions.php:23 -#: templates/myaccount/view-subscription.php:96 +#: templates/myaccount/subscription-totals.php:25 msgctxt "table heading" msgid "Total" msgstr "" @@ -4464,13 +4504,13 @@ msgid "Last Order Date" msgstr "" #: templates/emails/subscription-info.php:19 -#: templates/myaccount/view-subscription.php:36 wcs-functions.php:276 +#: templates/myaccount/subscription-details.php:22 wcs-functions.php:276 msgctxt "table heading" msgid "Start Date" msgstr "" #: templates/emails/subscription-info.php:20 -#: templates/myaccount/view-subscription.php:42 wcs-functions.php:281 +#: templates/myaccount/subscription-details.php:28 wcs-functions.php:281 msgctxt "table heading" msgid "End Date" msgstr "" @@ -4534,7 +4574,7 @@ msgctxt "original denotes there is no date to display" msgid "-" msgstr "" -#: includes/class-wc-subscription.php:2281 +#: includes/class-wc-subscription.php:2289 #. 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\"." @@ -4599,7 +4639,7 @@ msgctxt "Subscription status" msgid "On-hold" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:2527 wcs-functions.php:212 +#: includes/class-wc-subscriptions-switcher.php:2611 wcs-functions.php:212 msgctxt "Subscription status" msgid "Switched" msgstr "" @@ -4721,30 +4761,30 @@ msgctxt "when to prorate first payment / subscription length" msgid "For All Subscription Products" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1831 +#: includes/class-wc-subscriptions-switcher.php:1804 msgctxt "a switch order" msgid "Downgrade" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1834 +#: includes/class-wc-subscriptions-switcher.php:1807 msgctxt "a switch order" msgid "Upgrade" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1837 +#: includes/class-wc-subscriptions-switcher.php:1810 msgctxt "a switch order" msgid "Crossgrade" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1842 +#: includes/class-wc-subscriptions-switcher.php:1815 #. 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:1958 -#: includes/class-wc-subscriptions-switcher.php:2304 +#: includes/class-wc-subscriptions-switcher.php:1931 +#: includes/class-wc-subscriptions-switcher.php:2273 #. translators: 1$: old item, 2$: new item when switching msgctxt "used in order notes" msgid "Customer switched from: %1$s to %2$s." @@ -5102,7 +5142,7 @@ msgid "" msgstr "" #: includes/upgrades/templates/wcs-about-2-0.php:36 -#: woocommerce-subscriptions.php:1030 +#: woocommerce-subscriptions.php:1058 msgctxt "short for documents" msgid "Docs" msgstr "" @@ -5326,7 +5366,7 @@ msgstr "" #: templates/checkout/form-change-payment-method.php:20 #: templates/emails/email-order-details.php:33 -#: templates/myaccount/view-subscription.php:95 +#: templates/myaccount/subscription-totals.php:24 msgctxt "table headings in notification email" msgid "Product" msgstr "" @@ -5539,22 +5579,22 @@ msgctxt "pay for a subscription" msgid "Pay" msgstr "" -#: templates/myaccount/view-subscription.php:40 +#: templates/myaccount/subscription-details.php:26 msgctxt "admin subscription table header" msgid "Last Order Date" msgstr "" -#: templates/myaccount/view-subscription.php:41 +#: templates/myaccount/subscription-details.php:27 msgctxt "admin subscription table header" msgid "Next Payment Date" msgstr "" -#: templates/myaccount/view-subscription.php:43 +#: templates/myaccount/subscription-details.php:29 msgctxt "admin subscription table header" msgid "Trial End Date" msgstr "" -#: templates/myaccount/view-subscription.php:75 +#: templates/myaccount/subscription-details.php:62 msgctxt "date on subscription updates list. Will be localized" msgid "l jS \\o\\f F Y, h:ia" msgstr "" @@ -5580,68 +5620,68 @@ msgctxt "The post title for the new subscription" msgid "Subscription – %s" msgstr "" -#: woocommerce-subscriptions.php:231 +#: woocommerce-subscriptions.php:233 msgctxt "custom post type setting" msgid "Add Subscription" msgstr "" -#: woocommerce-subscriptions.php:232 +#: woocommerce-subscriptions.php:234 msgctxt "custom post type setting" msgid "Add New Subscription" msgstr "" -#: woocommerce-subscriptions.php:233 +#: woocommerce-subscriptions.php:235 msgctxt "custom post type setting" msgid "Edit" msgstr "" -#: woocommerce-subscriptions.php:234 +#: woocommerce-subscriptions.php:236 msgctxt "custom post type setting" msgid "Edit Subscription" msgstr "" -#: woocommerce-subscriptions.php:235 +#: woocommerce-subscriptions.php:237 msgctxt "custom post type setting" msgid "New Subscription" msgstr "" -#: woocommerce-subscriptions.php:236 woocommerce-subscriptions.php:237 +#: woocommerce-subscriptions.php:238 woocommerce-subscriptions.php:239 msgctxt "custom post type setting" msgid "View Subscription" msgstr "" -#: woocommerce-subscriptions.php:240 +#: woocommerce-subscriptions.php:242 msgctxt "custom post type setting" msgid "No Subscriptions found in trash" msgstr "" -#: woocommerce-subscriptions.php:241 +#: woocommerce-subscriptions.php:243 msgctxt "custom post type setting" msgid "Parent Subscriptions" msgstr "" -#: woocommerce-subscriptions.php:309 +#: woocommerce-subscriptions.php:311 msgctxt "post status label including post count" msgid "Active (%s)" msgid_plural "Active (%s)" msgstr[0] "" msgstr[1] "" -#: woocommerce-subscriptions.php:310 +#: woocommerce-subscriptions.php:312 msgctxt "post status label including post count" msgid "Switched (%s)" msgid_plural "Switched (%s)" msgstr[0] "" msgstr[1] "" -#: woocommerce-subscriptions.php:311 +#: woocommerce-subscriptions.php:313 msgctxt "post status label including post count" msgid "Expired (%s)" msgid_plural "Expired (%s)" msgstr[0] "" msgstr[1] "" -#: woocommerce-subscriptions.php:312 +#: woocommerce-subscriptions.php:314 msgctxt "post status label including post count" msgid "Pending Cancellation (%s)" msgid_plural "Pending Cancellation (%s)" diff --git a/templates/myaccount/my-subscriptions.php b/templates/myaccount/my-subscriptions.php index 72bb8c2..d081650 100644 --- a/templates/myaccount/my-subscriptions.php +++ b/templates/myaccount/my-subscriptions.php @@ -64,13 +64,25 @@ if ( ! defined( 'ABSPATH' ) ) { - + +
+ + + + + + +
+ +

- ', '' ); - ?> + ', '' ); + else : + // translators: placeholders are opening and closing link tags to take to the shop page + printf( esc_html__( 'You have no active subscriptions. Find your first subscription in the %sstore%s.', 'woocommerce-subscriptions' ), '', '' ); + endif; ?>

diff --git a/templates/myaccount/subscription-details.php b/templates/myaccount/subscription-details.php new file mode 100644 index 0000000..65504bf --- /dev/null +++ b/templates/myaccount/subscription-details.php @@ -0,0 +1,73 @@ + + + + + + + + + + + + _x( 'Last Order Date', 'admin subscription table header', 'woocommerce-subscriptions' ), + 'next_payment' => _x( 'Next Payment Date', 'admin subscription table header', 'woocommerce-subscriptions' ), + 'end' => _x( 'End Date', 'table heading', 'woocommerce-subscriptions' ), + 'trial_end' => _x( 'Trial End Date', 'admin subscription table header', 'woocommerce-subscriptions' ), + ) as $date_type => $date_title ) : ?> + get_date( $date_type ); ?> + + + + + + + + + + + + + + + + +
get_status() ) ); ?>
get_date_to_display( 'date_created' ) ); ?>
get_date_to_display( $date_type ) ); ?>
+ $action ) : ?> + + +
+ +get_customer_order_notes() ) : + ?> +

+
    + +
  1. +
    +
    +

    comment_date ) ) ); ?>

    +
    + comment_content ) ) ); ?> +
    +
    +
    +
    +
    +
  2. + +
+ diff --git a/templates/myaccount/subscription-totals.php b/templates/myaccount/subscription-totals.php new file mode 100644 index 0000000..5508b88 --- /dev/null +++ b/templates/myaccount/subscription-totals.php @@ -0,0 +1,112 @@ + + + +

+ + + + + + + + + + + + get_items() ) > 0 ) { + + foreach ( $subscription_items as $item_id => $item ) { + $_product = apply_filters( 'woocommerce_subscriptions_order_item_product', $subscription->get_product_from_item( $item ), $item ); + if ( apply_filters( 'woocommerce_order_item_visible', true, $item ) ) { + ?> + + + + + + + + has_status( array( 'completed', 'processing' ) ) && ( $purchase_note = get_post_meta( $_product->id, '_purchase_note', true ) ) ) { + ?> + + + + + + + get_order_item_totals() ) { + foreach ( $totals as $key => $total ) { + ?> + + + + + + +
 
+ + + × + + + is_visible() ) { + echo esc_html( apply_filters( 'woocommerce_order_item_name', $item['name'], $item, false ) ); + } else { + echo wp_kses_post( apply_filters( 'woocommerce_order_item_name', sprintf( '%s', get_permalink( $item['product_id'] ), $item['name'] ), $item, false ) ); + } + + echo wp_kses_post( apply_filters( 'woocommerce_order_item_quantity_html', ' ' . sprintf( '× %s', $item['qty'] ) . '', $item ) ); + + /** + * Allow other plugins to add additional product information here. + * + * @param int $item_id The subscription line item ID. + * @param WC_Order_Item|array $item The subscription line item. + * @param WC_Subscription $subscription The subscription. + * @param bool $plain_text Wether the item meta is being generated in a plain text context. + */ + do_action( 'woocommerce_order_item_meta_start', $item_id, $item, $subscription, false ); + + wcs_display_item_meta( $item, $subscription ); + + wcs_display_item_downloads( $item, $subscription ); + + /** + * Allow other plugins to add additional product information here. + * + * @param int $item_id The subscription line item ID. + * @param WC_Order_Item|array $item The subscription line item. + * @param WC_Subscription $subscription The subscription. + * @param bool $plain_text Wether the item meta is being generated in a plain text context. + */ + do_action( 'woocommerce_order_item_meta_end', $item_id, $item, $subscription, false ); + ?> + + get_formatted_line_subtotal( $item ) ); ?> +
>
diff --git a/templates/myaccount/subscriptions.php b/templates/myaccount/subscriptions.php index f258a66..53c761a 100644 --- a/templates/myaccount/subscriptions.php +++ b/templates/myaccount/subscriptions.php @@ -11,4 +11,4 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } -WC_Subscriptions::get_my_subscriptions_template(); +WC_Subscriptions::get_my_subscriptions_template( $current_page ); diff --git a/templates/myaccount/view-subscription.php b/templates/myaccount/view-subscription.php index d938efd..8388dbc 100644 --- a/templates/myaccount/view-subscription.php +++ b/templates/myaccount/view-subscription.php @@ -6,7 +6,7 @@ * * @author Prospress * @package WooCommerce_Subscription/Templates - * @version 2.2.0 + * @version 2.2.19 */ if ( ! defined( 'ABSPATH' ) ) { @@ -25,151 +25,24 @@ if ( empty( $subscription ) ) { } wc_print_notices(); + +/** + * Gets subscription details table template + * @param WC_Subscription $subscription A subscription object + * @since 2.2.19 + */ +do_action( 'woocommerce_subscription_details_table', $subscription ); + +/** + * Gets subscription totals table template + * @param WC_Subscription $subscription A subscription object + * @since 2.2.19 + */ +do_action( 'woocommerce_subscription_totals_table', $subscription ); + +do_action( 'woocommerce_subscription_details_after_subscription_table', $subscription ); + +wc_get_template( 'order/order-details-customer.php', array( 'order' => $subscription ) ); ?> - - - - - - - - - - _x( 'Last Order Date', 'admin subscription table header', 'woocommerce-subscriptions' ), - 'next_payment' => _x( 'Next Payment Date', 'admin subscription table header', 'woocommerce-subscriptions' ), - 'end' => _x( 'End Date', 'table heading', 'woocommerce-subscriptions' ), - 'trial_end' => _x( 'Trial End Date', 'admin subscription table header', 'woocommerce-subscriptions' ), - ) as $date_type => $date_title ) : ?> - get_date( $date_type ); ?> - - - - - - - - - - - - - - - - -
get_status() ) ); ?>
get_date_to_display( 'date_created' ) ); ?>
get_date_to_display( $date_type ) ); ?>
- $action ) : ?> - - -
-get_customer_order_notes() ) : - ?> -

-
    - -
  1. -
    -
    -

    comment_date ) ) ); ?>

    -
    - comment_content ) ) ); ?> -
    -
    -
    -
    -
    -
  2. - -
- - -

- - - - - - - - - - - - get_items() ) > 0 ) { - - foreach ( $subscription_items as $item_id => $item ) { - $_product = apply_filters( 'woocommerce_subscriptions_order_item_product', $subscription->get_product_from_item( $item ), $item ); - if ( apply_filters( 'woocommerce_order_item_visible', true, $item ) ) { - ?> - - - - - - - - has_status( array( 'completed', 'processing' ) ) && ( $purchase_note = get_post_meta( $_product->id, '_purchase_note', true ) ) ) { - ?> - - - - - - - get_order_item_totals() ) { - foreach ( $totals as $key => $total ) { - ?> - - - - - - -
 
- - - × - - - is_visible() ) { - echo esc_html( apply_filters( 'woocommerce_order_item_name', $item['name'], $item, false ) ); - } else { - echo wp_kses_post( apply_filters( 'woocommerce_order_item_name', sprintf( '%s', get_permalink( $item['product_id'] ), $item['name'] ), $item, false ) ); - } - - echo wp_kses_post( apply_filters( 'woocommerce_order_item_quantity_html', ' ' . sprintf( '× %s', $item['qty'] ) . '', $item ) ); - - // Allow other plugins to add additional product information here - do_action( 'woocommerce_order_item_meta_start', $item_id, $item, $subscription ); - - wcs_display_item_meta( $item, $subscription ); - - wcs_display_item_downloads( $item, $subscription ); - - // Allow other plugins to add additional product information here - do_action( 'woocommerce_order_item_meta_end', $item_id, $item, $subscription ); - ?> - - get_formatted_line_subtotal( $item ) ); ?> -
>
- - - - $subscription ) ); ?> -
diff --git a/woocommerce-subscriptions.php b/woocommerce-subscriptions.php index 1706c40..97f4f44 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.2.18 + * Version: 2.2.19 * * WC requires at least: 2.5 * WC tested up to: 3.3 @@ -117,6 +117,8 @@ require_once( 'includes/class-wcs-limiter.php' ); require_once( 'includes/legacy/class-wcs-array-property-post-meta-black-magic.php' ); +require_once( 'includes/class-wcs-failed-scheduled-action-manager.php' ); + /** * The main subscriptions class. * @@ -130,7 +132,7 @@ class WC_Subscriptions { public static $plugin_file = __FILE__; - public static $version = '2.2.18'; + public static $version = '2.2.19'; private static $total_subscription_count = null; @@ -165,7 +167,7 @@ class WC_Subscriptions { add_action( 'wcopc_subscription_add_to_cart', __CLASS__ . '::wcopc_subscription_add_to_cart' ); // One Page Checkout compatibility // Ensure a subscription is never in the cart with products - add_filter( 'woocommerce_add_to_cart_validation', __CLASS__ . '::maybe_empty_cart', 10, 4 ); + add_filter( 'woocommerce_add_to_cart_validation', __CLASS__ . '::maybe_empty_cart', 10, 5 ); // Enqueue front-end styles, run after Storefront because it sets the styles to be empty add_filter( 'woocommerce_enqueue_styles', __CLASS__ . '::enqueue_styles', 100, 1 ); @@ -358,13 +360,20 @@ class WC_Subscriptions { * Loads the my-subscriptions.php template on the My Account page. * * @since 1.0 + * @param int $current_page */ - public static function get_my_subscriptions_template() { + public static function get_my_subscriptions_template( $current_page = 1 ) { - $subscriptions = wcs_get_users_subscriptions(); - $user_id = get_current_user_id(); + $all_subscriptions = wcs_get_users_subscriptions(); - wc_get_template( 'myaccount/my-subscriptions.php', array( 'subscriptions' => $subscriptions, 'user_id' => $user_id ), '', plugin_dir_path( __FILE__ ) . 'templates/' ); + $current_page = empty( $current_page ) ? 1 : absint( $current_page ); + $posts_per_page = get_option( 'posts_per_page' ); + + $max_num_pages = ceil( count( $all_subscriptions ) / $posts_per_page ); + + $subscriptions = array_slice( $all_subscriptions, ( $current_page - 1 ) * $posts_per_page, $posts_per_page ); + + wc_get_template( 'myaccount/my-subscriptions.php', array( 'subscriptions' => $subscriptions, 'current_page' => $current_page, 'max_num_pages' => $max_num_pages, 'paginate' => true ), '', plugin_dir_path( __FILE__ ) . 'templates/' ); } /** @@ -390,7 +399,7 @@ class WC_Subscriptions { * * @since 1.0 */ - public static function maybe_empty_cart( $valid, $product_id, $quantity, $variation_id = '' ) { + public static function maybe_empty_cart( $valid, $product_id, $quantity, $variation_id = '', $variations = array() ) { $is_subscription = WC_Subscriptions_Product::is_subscription( $product_id ); $cart_contains_subscription = WC_Subscriptions_Cart::cart_contains_subscription(); @@ -400,7 +409,13 @@ class WC_Subscriptions { if ( $is_subscription && 'yes' != get_option( WC_Subscriptions_Admin::$option_prefix . '_multiple_purchase', 'no' ) ) { - if ( ! WC_Subscriptions_Cart::cart_contains_product( $canonical_product_id ) ) { + // Generate a cart item key from variation and cart item data - which may be added by other plugins + $cart_item_data = (array) apply_filters( 'woocommerce_add_cart_item_data', array(), $product_id, $variation_id ); + $cart_item_id = WC()->cart->generate_cart_id( $product_id, $variation_id, $variations, $cart_item_data ); + $product = wc_get_product( $product_id ); + + // If the product is sold individually or if the cart doesn't already contain this product, empty the cart. + if ( ( $product && $product->is_sold_individually() ) || ! WC()->cart->find_product_in_cart( $cart_item_id ) ) { WC()->cart->empty_cart(); } } elseif ( $is_subscription && wcs_cart_contains_renewal() && ! $multiple_subscriptions_possible && ! $manual_renewals_enabled ) { @@ -463,7 +478,18 @@ class WC_Subscriptions { // Redirect to checkout if mixed checkout is disabled if ( 'yes' != get_option( WC_Subscriptions_Admin::$option_prefix . '_multiple_purchase', 'no' ) ) { - wc_clear_notices(); + $quantity = isset( $_REQUEST['quantity'] ) ? $_REQUEST['quantity'] : 1; + $product_id = $_REQUEST['add-to-cart']; + + $add_to_cart_notice = wc_add_to_cart_message( array( $product_id => $quantity ), true, true ); + + if ( wc_has_notice( $add_to_cart_notice ) ) { + $notices = wc_get_notices(); + $add_to_cart_notice_index = array_search( $add_to_cart_notice, $notices['success'] ); + + unset( $notices['success'][ $add_to_cart_notice_index ] ); + wc_set_notices( $notices ); + } $url = wc_get_checkout_url(); @@ -773,6 +799,8 @@ class WC_Subscriptions { require_once( 'includes/deprecated/class-wcs-dynamic-filter-deprecator.php' ); } + $failed_scheduled_action_manager = new WCS_Failed_Scheduled_Action_Manager( new WC_Logger() ); + $failed_scheduled_action_manager->init(); } /**