Merge branch 'release/2.2.19' into develop

This commit is contained in:
Remco Tolsma
2018-04-20 09:51:38 +02:00
34 changed files with 1144 additions and 478 deletions

View File

@@ -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,

View File

@@ -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('<span id="sale-price-period" style="display: none;"></span>');
@@ -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 ) {

View File

@@ -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

View File

@@ -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;

View File

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

View File

@@ -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',

View File

@@ -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' ) ) {

View File

@@ -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 ),

View File

@@ -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();

View File

@@ -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 *
************************/

View File

@@ -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 <strong> tag, $3: closing </strong> tag

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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 ) {

View File

@@ -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 = '<span class="subscription-details">' . sprintf( __( 'every %s', 'woocommerce-subscriptions' ), wcs_get_subscription_period_strings( $billing_interval, $billing_period ) );
} else {
$subscription_string = '<span class="subscription-details">';
}
// 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( '<input type="hidden" class="wcs-can-remove-variation" value="%d" />', 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( '<a href="#" class="tips delete wcs-can-not-remove-variation-msg" data-tip="%s"></a>', 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.

View File

@@ -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 **/
/**

View File

@@ -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 ) {

View File

@@ -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 );
}

View File

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

View File

@@ -0,0 +1,197 @@
<?php
/**
* Failed Scheduled Action Manager for subscription events
*
* @version 2.2.19
* @package WooCommerce Subscriptions
* @category Class
* @author Prospress
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class WCS_Failed_Scheduled_Action_Manager {
/**
* Action hooks we're interested in tracking.
*
* @var array
*/
protected $tracked_scheduled_actions = array(
'woocommerce_scheduled_subscription_trial_end' => 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 = '<a href="' . get_edit_post_link( $action['args']['subscription_id'] ) . '">#' . $action['args']['subscription_id'] . '</a>';
} elseif ( isset( $action['args']['order_id'] ) ) {
$subject = '<a href="' . get_edit_post_link( $action['args']['order_id'] ) . '">#' . $action['args']['order_id'] . '</a>';
} else {
$subject = 'unknown';
}
$affected_subscription_events .= $separator . $action['type'] . ' for ' . $subject;
$separator = "\n";
}?>
<div class="updated error">
<p><?php
// 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
echo sprintf( esc_html__( '%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', 'woocommerce-subscriptions' ),
esc_html( _n( 'An error has occurred while processing a recent subscription related event.', 'An error has occurred while processing recent subscription related events.', count( $failed_scheduled_actions ), 'woocommerce-subscriptions' ) ),
'<a href="https://woocommerce.com/my-account/marketplace-ticket-form/" target="_blank">',
'</a>',
'<p>',
'<a href="https://docs.woocommerce.com/document/create-new-admin-account-wordpress/" target="_blank">',
'</p>',
'<code style="display: block; white-space: pre-wrap">' . wp_kses( $affected_subscription_events, array( 'a' => array( 'href' => array() ) ) ) . '</code>',
'<code>failed-scheduled-actions</code>',
'<a href="' . esc_url( admin_url( sprintf( 'admin.php?page=wc-status&tab=logs&log_file=%s-%s-log', 'failed-scheduled-actions', sanitize_file_name( wp_hash( 'failed-scheduled-actions' ) ) ) ) ) . '">',
'<div style="margin: 5px 0;"><a class="button" href="' . esc_url( wp_nonce_url( add_query_arg( 'wcs_scheduled_action_timeout_error_notice', 'ignore' ), 'wcs_scheduled_action_timeout_error_notice', '_wcsnonce' ) ) . '">' . esc_html__( 'Ignore this error (not recommended!)', 'woocommerce-subscriptions' ) . '</a> <a class="button button-primary" href="https://woocommerce.com/my-account/marketplace-ticket-form/">' . esc_html__( 'Open up a ticket now!', 'woocommerce-subscriptions' ) . '</a></div>'
);?>
</p>
</div><?php
}
/**
* Handle requests to disable the failed scheduled actions admin notice.
*
* @since 2.2.19
*/
protected function maybe_disable_admin_notice() {
if ( isset( $_GET['_wcsnonce'] ) && wp_verify_nonce( $_GET['_wcsnonce'], 'wcs_scheduled_action_timeout_error_notice' ) && isset( $_GET['wcs_scheduled_action_timeout_error_notice'] ) ) {
delete_option( WC_Subscriptions_Admin::$option_prefix . '_failed_scheduled_actions' );
}
}
/**
* Retrieve a user friendly description of the scheduled action from the action hook.
*
* @param string $hook the scheduled action hook
* @return string
* @since 2.2.19
*/
protected function get_action_hook_label( $hook ) {
return str_replace( array( 'woocommerce_scheduled_', '_' ), array( '', ' ' ), $hook );
}
/**
* Retrieve a list of scheduled action args as a string.
*
* @param mixed $args the scheduled action args
* @return string
* @since 2.2.19
*/
protected function get_action_args_string( $args ) {
$args_string = $separator = '';
foreach ( $args as $key => $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 );
}
}

View File

@@ -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/' );
}
/**

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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 );
}

View File

@@ -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

View File

@@ -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() );
}
/**

File diff suppressed because it is too large Load Diff

View File

@@ -64,13 +64,25 @@ if ( ! defined( 'ABSPATH' ) ) {
</tbody>
</table>
<?php else : ?>
<?php if ( 1 < $max_num_pages ) : ?>
<div class="woocommerce-pagination woocommerce-pagination--without-numbers woocommerce-Pagination">
<?php if ( 1 !== $current_page ) : ?>
<a class="woocommerce-button woocommerce-button--previous woocommerce-Button woocommerce-Button--previous button" href="<?php echo esc_url( wc_get_endpoint_url( 'subscriptions', $current_page - 1 ) ); ?>"><?php esc_html_e( 'Previous', 'woocommerce-subscriptions' ); ?></a>
<?php endif; ?>
<?php if ( intval( $max_num_pages ) !== $current_page ) : ?>
<a class="woocommerce-button woocommerce-button--next woocommerce-Button woocommerce-Button--next button" href="<?php echo esc_url( wc_get_endpoint_url( 'subscriptions', $current_page + 1 ) ); ?>"><?php esc_html_e( 'Next', 'woocommerce-subscriptions' ); ?></a>
<?php endif; ?>
</div>
<?php endif; ?>
<?php else : ?>
<p class="no_subscriptions">
<?php
// 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' ), '<a href="' . esc_url( apply_filters( 'woocommerce_subscriptions_message_store_url', get_permalink( wc_get_page_id( 'shop' ) ) ) ) . '">', '</a>' );
?>
<?php if ( 1 < $current_page ) :
printf( esc_html__( 'You have reached the end of subscriptions. Go to the %sfirst page%s.', 'woocommerce-subscriptions' ), '<a href="' . esc_url( wc_get_endpoint_url( 'subscriptions', 1 ) ) . '">', '</a>' );
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' ), '<a href="' . esc_url( apply_filters( 'woocommerce_subscriptions_message_store_url', get_permalink( wc_get_page_id( 'shop' ) ) ) ) . '">', '</a>' );
endif; ?>
</p>
<?php endif; ?>

View File

@@ -0,0 +1,73 @@
<?php
/**
* Subscription details table
*
* @author Prospress
* @package WooCommerce_Subscription/Templates
* @since 2.2.19
* @version 2.2.19
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
?>
<table class="shop_table subscription_details">
<tr>
<td><?php esc_html_e( 'Status', 'woocommerce-subscriptions' ); ?></td>
<td><?php echo esc_html( wcs_get_subscription_status_name( $subscription->get_status() ) ); ?></td>
</tr>
<tr>
<td><?php echo esc_html_x( 'Start Date', 'table heading', 'woocommerce-subscriptions' ); ?></td>
<td><?php echo esc_html( $subscription->get_date_to_display( 'date_created' ) ); ?></td>
</tr>
<?php foreach ( array(
'last_order_date_created' => _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 ) : ?>
<?php $date = $subscription->get_date( $date_type ); ?>
<?php if ( ! empty( $date ) ) : ?>
<tr>
<td><?php echo esc_html( $date_title ); ?></td>
<td><?php echo esc_html( $subscription->get_date_to_display( $date_type ) ); ?></td>
</tr>
<?php endif; ?>
<?php endforeach; ?>
<?php do_action( 'woocommerce_subscription_before_actions', $subscription ); ?>
<?php $actions = wcs_get_all_user_actions_for_subscription( $subscription, get_current_user_id() ); ?>
<?php if ( ! empty( $actions ) ) : ?>
<tr>
<td><?php esc_html_e( 'Actions', 'woocommerce-subscriptions' ); ?></td>
<td>
<?php foreach ( $actions as $key => $action ) : ?>
<a href="<?php echo esc_url( $action['url'] ); ?>" class="button <?php echo sanitize_html_class( $key ) ?>"><?php echo esc_html( $action['name'] ); ?></a>
<?php endforeach; ?>
</td>
</tr>
<?php endif; ?>
<?php do_action( 'woocommerce_subscription_after_actions', $subscription ); ?>
</table>
<?php if ( $notes = $subscription->get_customer_order_notes() ) :
?>
<h2><?php esc_html_e( 'Subscription Updates', 'woocommerce-subscriptions' ); ?></h2>
<ol class="commentlist notes">
<?php foreach ( $notes as $note ) : ?>
<li class="comment note">
<div class="comment_container">
<div class="comment-text">
<p class="meta"><?php echo esc_html( date_i18n( _x( 'l jS \o\f F Y, h:ia', 'date on subscription updates list. Will be localized', 'woocommerce-subscriptions' ), wcs_date_to_time( $note->comment_date ) ) ); ?></p>
<div class="description">
<?php echo wp_kses_post( wpautop( wptexturize( $note->comment_content ) ) ); ?>
</div>
<div class="clear"></div>
</div>
<div class="clear"></div>
</div>
</li>
<?php endforeach; ?>
</ol>
<?php endif; ?>

View File

@@ -0,0 +1,112 @@
<?php
/**
* Subscription totals table
*
* @author Prospress
* @package WooCommerce_Subscription/Templates
* @since 2.2.19
* @version 2.2.19
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
?>
<?php $allow_remove_items = wcs_can_items_be_removed( $subscription ); ?>
<h2><?php esc_html_e( 'Subscription Totals', 'woocommerce-subscriptions' ); ?></h2>
<table class="shop_table order_details">
<thead>
<tr>
<?php if ( $allow_remove_items ) : ?>
<th class="product-remove" style="width: 3em;">&nbsp;</th>
<?php endif; ?>
<th class="product-name"><?php echo esc_html_x( 'Product', 'table headings in notification email', 'woocommerce-subscriptions' ); ?></th>
<th class="product-total"><?php echo esc_html_x( 'Total', 'table heading', 'woocommerce-subscriptions' ); ?></th>
</tr>
</thead>
<tbody>
<?php
if ( sizeof( $subscription_items = $subscription->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 ) ) {
?>
<tr class="<?php echo esc_attr( apply_filters( 'woocommerce_order_item_class', 'order_item', $item, $subscription ) ); ?>">
<?php if ( $allow_remove_items ) : ?>
<td class="remove_item">
<?php if ( wcs_can_item_be_removed( $item, $subscription ) ) : ?>
<?php $confirm_notice = apply_filters( 'woocommerce_subscriptions_order_item_remove_confirmation_text', __( 'Are you sure you want remove this item from your subscription?', 'woocommerce-subscriptions' ), $item, $_product, $subscription );?>
<a href="<?php echo esc_url( WCS_Remove_Item::get_remove_url( $subscription->get_id(), $item_id ) );?>" class="remove" onclick="return confirm('<?php printf( esc_html( $confirm_notice ) ); ?>');">&times;</a>
<?php endif; ?>
</td>
<?php endif; ?>
<td class="product-name">
<?php
if ( $_product && ! $_product->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( '<a href="%s">%s</a>', get_permalink( $item['product_id'] ), $item['name'] ), $item, false ) );
}
echo wp_kses_post( apply_filters( 'woocommerce_order_item_quantity_html', ' <strong class="product-quantity">' . sprintf( '&times; %s', $item['qty'] ) . '</strong>', $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 );
?>
</td>
<td class="product-total">
<?php echo wp_kses_post( $subscription->get_formatted_line_subtotal( $item ) ); ?>
</td>
</tr>
<?php
}
if ( $subscription->has_status( array( 'completed', 'processing' ) ) && ( $purchase_note = get_post_meta( $_product->id, '_purchase_note', true ) ) ) {
?>
<tr class="product-purchase-note">
<td colspan="3"><?php echo wp_kses_post( wpautop( do_shortcode( $purchase_note ) ) ); ?></td>
</tr>
<?php
}
}
}
?>
</tbody>
<tfoot>
<?php
if ( $totals = $subscription->get_order_item_totals() ) {
foreach ( $totals as $key => $total ) {
?>
<tr>
<th scope="row" <?php echo ( $allow_remove_items ) ? 'colspan="2"' : ''; ?>><?php echo esc_html( $total['label'] ); ?></th>
<td><?php echo wp_kses_post( $total['value'] ); ?></td>
</tr>
<?php
}
} ?>
</tfoot>
</table>

View File

@@ -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 );

View File

@@ -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 ) );
?>
<table class="shop_table subscription_details">
<tr>
<td><?php esc_html_e( 'Status', 'woocommerce-subscriptions' ); ?></td>
<td><?php echo esc_html( wcs_get_subscription_status_name( $subscription->get_status() ) ); ?></td>
</tr>
<tr>
<td><?php echo esc_html_x( 'Start Date', 'table heading', 'woocommerce-subscriptions' ); ?></td>
<td><?php echo esc_html( $subscription->get_date_to_display( 'date_created' ) ); ?></td>
</tr>
<?php foreach ( array(
'last_order_date_created' => _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 ) : ?>
<?php $date = $subscription->get_date( $date_type ); ?>
<?php if ( ! empty( $date ) ) : ?>
<tr>
<td><?php echo esc_html( $date_title ); ?></td>
<td><?php echo esc_html( $subscription->get_date_to_display( $date_type ) ); ?></td>
</tr>
<?php endif; ?>
<?php endforeach; ?>
<?php do_action( 'woocommerce_subscription_before_actions', $subscription ); ?>
<?php $actions = wcs_get_all_user_actions_for_subscription( $subscription, get_current_user_id() ); ?>
<?php if ( ! empty( $actions ) ) : ?>
<tr>
<td><?php esc_html_e( 'Actions', 'woocommerce-subscriptions' ); ?></td>
<td>
<?php foreach ( $actions as $key => $action ) : ?>
<a href="<?php echo esc_url( $action['url'] ); ?>" class="button <?php echo sanitize_html_class( $key ) ?>"><?php echo esc_html( $action['name'] ); ?></a>
<?php endforeach; ?>
</td>
</tr>
<?php endif; ?>
<?php do_action( 'woocommerce_subscription_after_actions', $subscription ); ?>
</table>
<?php if ( $notes = $subscription->get_customer_order_notes() ) :
?>
<h2><?php esc_html_e( 'Subscription Updates', 'woocommerce-subscriptions' ); ?></h2>
<ol class="commentlist notes">
<?php foreach ( $notes as $note ) : ?>
<li class="comment note">
<div class="comment_container">
<div class="comment-text">
<p class="meta"><?php echo esc_html( date_i18n( _x( 'l jS \o\f F Y, h:ia', 'date on subscription updates list. Will be localized', 'woocommerce-subscriptions' ), wcs_date_to_time( $note->comment_date ) ) ); ?></p>
<div class="description">
<?php echo wp_kses_post( wpautop( wptexturize( $note->comment_content ) ) ); ?>
</div>
<div class="clear"></div>
</div>
<div class="clear"></div>
</div>
</li>
<?php endforeach; ?>
</ol>
<?php endif; ?>
<?php $allow_remove_items = wcs_can_items_be_removed( $subscription ); ?>
<h2><?php esc_html_e( 'Subscription Totals', 'woocommerce-subscriptions' ); ?></h2>
<table class="shop_table order_details">
<thead>
<tr>
<?php if ( $allow_remove_items ) : ?>
<th class="product-remove" style="width: 3em;">&nbsp;</th>
<?php endif; ?>
<th class="product-name"><?php echo esc_html_x( 'Product', 'table headings in notification email', 'woocommerce-subscriptions' ); ?></th>
<th class="product-total"><?php echo esc_html_x( 'Total', 'table heading', 'woocommerce-subscriptions' ); ?></th>
</tr>
</thead>
<tbody>
<?php
if ( sizeof( $subscription_items = $subscription->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 ) ) {
?>
<tr class="<?php echo esc_attr( apply_filters( 'woocommerce_order_item_class', 'order_item', $item, $subscription ) ); ?>">
<?php if ( $allow_remove_items ) : ?>
<td class="remove_item">
<?php if ( wcs_can_item_be_removed( $item, $subscription ) ) : ?>
<?php $confirm_notice = apply_filters( 'woocommerce_subscriptions_order_item_remove_confirmation_text', __( 'Are you sure you want remove this item from your subscription?', 'woocommerce-subscriptions' ), $item, $_product, $subscription );?>
<a href="<?php echo esc_url( WCS_Remove_Item::get_remove_url( $subscription->get_id(), $item_id ) );?>" class="remove" onclick="return confirm('<?php printf( esc_html( $confirm_notice ) ); ?>');">&times;</a>
<?php endif; ?>
</td>
<?php endif; ?>
<td class="product-name">
<?php
if ( $_product && ! $_product->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( '<a href="%s">%s</a>', get_permalink( $item['product_id'] ), $item['name'] ), $item, false ) );
}
echo wp_kses_post( apply_filters( 'woocommerce_order_item_quantity_html', ' <strong class="product-quantity">' . sprintf( '&times; %s', $item['qty'] ) . '</strong>', $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 );
?>
</td>
<td class="product-total">
<?php echo wp_kses_post( $subscription->get_formatted_line_subtotal( $item ) ); ?>
</td>
</tr>
<?php
}
if ( $subscription->has_status( array( 'completed', 'processing' ) ) && ( $purchase_note = get_post_meta( $_product->id, '_purchase_note', true ) ) ) {
?>
<tr class="product-purchase-note">
<td colspan="3"><?php echo wp_kses_post( wpautop( do_shortcode( $purchase_note ) ) ); ?></td>
</tr>
<?php
}
}
}
?>
</tbody>
<tfoot>
<?php
if ( $totals = $subscription->get_order_item_totals() ) {
foreach ( $totals as $key => $total ) {
?>
<tr>
<th scope="row" <?php echo ( $allow_remove_items ) ? 'colspan="2"' : ''; ?>><?php echo esc_html( $total['label'] ); ?></th>
<td><?php echo wp_kses_post( $total['value'] ); ?></td>
</tr>
<?php
}
} ?>
</tfoot>
</table>
<?php do_action( 'woocommerce_subscription_details_after_subscription_table', $subscription ); ?>
<?php wc_get_template( 'order/order-details-customer.php', array( 'order' => $subscription ) ); ?>
<div class="clear"></div>

View File

@@ -5,7 +5,7 @@
* Description: Sell products and services with recurring payments in your WooCommerce Store.
* Author: Prospress Inc.
* Author URI: http://prospress.com/
* Version: 2.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();
}
/**