mirror of
https://github.com/pronamic/woocommerce-subscriptions.git
synced 2025-10-16 14:22:56 +00:00
2.2.13
This commit is contained in:

committed by
Remco Tolsma

parent
a1f5512b45
commit
a00aceab46
@@ -1,5 +1,44 @@
|
|||||||
*** WooCommerce Subscriptions Changelog ***
|
*** WooCommerce Subscriptions Changelog ***
|
||||||
|
|
||||||
|
2017.10.13 - version 2.2.13
|
||||||
|
* Fix: Ensure wcs_get_subscriptions() returns the correct results when called with the 'customer_id' and 'product_id' arguments PR#2337
|
||||||
|
* Fix: Always set the order key when manually creating subscriptions from the admin dashboard PR#2350
|
||||||
|
* Fix: Delay granting download permissions until after the switch is complete - fixes missing download permissions after a switch PR#2339
|
||||||
|
* Tweak: Display subscription IDs in date update exceptions PR#2342
|
||||||
|
* Tweak: Cache the flag used to determine if subscriptions exist for a slight performance improvement PR#2360
|
||||||
|
* Tweak: [WC3.2] Add support for applying subscription coupon types to subscriptions and orders via admin edit subscription/order screens PR#2371
|
||||||
|
|
||||||
|
2017.09.15 - version 2.2.12
|
||||||
|
* Fix: Add missing argument number to filter for print_switch_link() - fixes PHP Warning: Missing argument 2 for WC_Subscriptions_Switcher::print_switch_link() PR#2266
|
||||||
|
* Fix: Store decimal price values in dot separated format in the database PR#2242
|
||||||
|
* Fix: Maintain backwards compatibility by triggering woocommerce_add_order_item_meta hooks when adding line items to a subscription from checkout PR#2267
|
||||||
|
* Fix: PayPal RT: Fix rounding errors and undefined index notice PR#2216
|
||||||
|
* Fix: Fix empty needle PHP notice when empty strings are returned from WC()->countries->inc_tax_or_vat() PR#1745
|
||||||
|
* Fix: Ensure updated subscription billing and shipping names are searchable in admin subscriptions table PR#2278
|
||||||
|
* Fix: Fix PayPal Standard cancellation IPN handling after paying for a failed PayPal Standard renewal order PR#2284
|
||||||
|
* Fix: Add the billing interval to trial period expiration to fix issues with calculating Upcoming Recurring Revenue report data PR#2245
|
||||||
|
* Fix: Allow payment on a subscription's most recent order for all order types not just renewal and/or parent orders PR#2295
|
||||||
|
* Fix: Ensure switch cart items form a separate recurring cart - switch items are processed through the checkout separately so should have separate recurring carts PR#2313
|
||||||
|
* Fix: [API] Save the subscription object before updating the dates PR#2308
|
||||||
|
* Fix: Fix the including of the WCS_Report_Dashboard class to ensure it's only included once PR#2300
|
||||||
|
* Fix: Never trigger gateway renewal payment hook on a subscription with an ended status PR#2318
|
||||||
|
* Fix: Only get parent order where a parent id exists PR#2322
|
||||||
|
* Fix: Set switch order item legacy values property correctly PR#2329
|
||||||
|
* Fix: Don't throw exception when an old PayPal Standard is suspended after a switch PR#2335
|
||||||
|
* Fix: Don't duplicate download permissions by checking for pre-existing download permissions on subscription when new download files are added to a product PR#2317
|
||||||
|
* Fix: Make WCS options translatable PR#2302
|
||||||
|
* Fix: Add wcs_update_order_item_type() to fix all the order item type change issues PR#2338
|
||||||
|
* Fix: Only display/add shipping if the recurring cart needs shipping PR#2269
|
||||||
|
* Tweak: Add a link to the wcs-ipn-failures log file on the failed IPN admin notice PR#2321
|
||||||
|
* Tweak: Update subscriptions shortcode PR#2306
|
||||||
|
* Tweak: Group payment method meta in subscription billing metabox by gateway ID PR#2305
|
||||||
|
* Tweak: Disable subscription report caching after errors PR#2297
|
||||||
|
* Tweak: Permanently delete subscriptions when an order is deleted PR#2288
|
||||||
|
* Tweak: Add wcs_renewal_order_payment_update_date_created filter PR#2289
|
||||||
|
* Tweak: Update WooCommerce.com support form links PR#2291
|
||||||
|
* Tweak: Refactor WC_Subscriptions_Manager::prepare_renewal() and add process_renewal() PR#2280
|
||||||
|
* Tweak: Apply Select2 to country fields on Edit Subscription screen PR#1745
|
||||||
|
|
||||||
2017.08.01 - version 2.2.11
|
2017.08.01 - version 2.2.11
|
||||||
* Fix: Do not save subscription product meta data on other post types, including WooCommerce Tab Manager and Jetpack post types. PR#2203
|
* Fix: Do not save subscription product meta data on other post types, including WooCommerce Tab Manager and Jetpack post types. PR#2203
|
||||||
* Fix: Do not apply 'woocommerce_get_price' or 'woocommerce_product_get_price' filters to tax inclusive/exclusive prices to avoid ever applying them twice or to non price prices, like sign-up fees. Introduced in Subscriptions v2.2.5. PR#2261
|
* Fix: Do not apply 'woocommerce_get_price' or 'woocommerce_product_get_price' filters to tax inclusive/exclusive prices to avoid ever applying them twice or to non price prices, like sign-up fees. Introduced in Subscriptions v2.2.5. PR#2261
|
||||||
@@ -450,7 +489,7 @@
|
|||||||
2015.12.16 - version 2.0.7
|
2015.12.16 - version 2.0.7
|
||||||
* New: Add a payment gateway filter to the dashboard admin subscriptions table
|
* New: Add a payment gateway filter to the dashboard admin subscriptions table
|
||||||
* New: Implement soft caching for get_related_orders()
|
* New: Implement soft caching for get_related_orders()
|
||||||
* Tweak: Better "Name Your Price" extension switching compatibility
|
* Tweak: Better "Name Your Price" extension switching compatibility
|
||||||
* Tweak: Trash a user's subscriptions when a user is deleted
|
* Tweak: Trash a user's subscriptions when a user is deleted
|
||||||
* Tweak: Limit the `wcs_do_subscriptions_exist()` query to 1 result
|
* Tweak: Limit the `wcs_do_subscriptions_exist()` query to 1 result
|
||||||
* Tweak: Mention subscription string methods in code comments/doc to help avoid some confusion
|
* Tweak: Mention subscription string methods in code comments/doc to help avoid some confusion
|
||||||
@@ -478,7 +517,7 @@
|
|||||||
* Fix: Add the correct `subscription_switch` meta to all subscription switches when there are multiple switches in the same order
|
* Fix: Add the correct `subscription_switch` meta to all subscription switches when there are multiple switches in the same order
|
||||||
* Fix: Allow autocomplete of orders that have multiple switches in the same order. Fixes an issue where switch emails were not being sent when multiple switched items were processed in the same order
|
* Fix: Allow autocomplete of orders that have multiple switches in the same order. Fixes an issue where switch emails were not being sent when multiple switched items were processed in the same order
|
||||||
* Fix: Adjust logic around switch label so that switching to $0 per period displays "Downgrade". Fixes an issue where "Crossgrade" was incorrectly displayed when downgrading to $0 per period
|
* Fix: Adjust logic around switch label so that switching to $0 per period displays "Downgrade". Fixes an issue where "Crossgrade" was incorrectly displayed when downgrading to $0 per period
|
||||||
* Fix: Don't initiate auto-switch when a subscription needs payment
|
* Fix: Don't initiate auto-switch when a subscription needs payment
|
||||||
* Fix: Use the 'wc-pending' status rather than 'pending' (default WordPress) status when manually creating a subscription. Fixes an issue with display of pending subscriptions on the admin subscription list table
|
* Fix: Use the 'wc-pending' status rather than 'pending' (default WordPress) status when manually creating a subscription. Fixes an issue with display of pending subscriptions on the admin subscription list table
|
||||||
* Fix: Replace use of `__DIR__` with `dirname( __FILE__ )` to maintain compatibility with PHP 5.2.4
|
* Fix: Replace use of `__DIR__` with `dirname( __FILE__ )` to maintain compatibility with PHP 5.2.4
|
||||||
* Fix: Fix the logic checking for sync day in future by comparing the full date not just the day then the month/year if that day is after the current day. This ensures we don't see a day in the future with a month and year in the past or same as today as being a date in the future
|
* Fix: Fix the logic checking for sync day in future by comparing the full date not just the day then the month/year if that day is after the current day. This ensures we don't see a day in the future with a month and year in the past or same as today as being a date in the future
|
||||||
|
@@ -210,7 +210,7 @@ class WC_Subscriptions_Admin {
|
|||||||
?><p class="form-field _subscription_price_fields _subscription_price_field">
|
?><p class="form-field _subscription_price_fields _subscription_price_field">
|
||||||
<label for="_subscription_price"><?php printf( esc_html__( 'Subscription price (%s)', 'woocommerce-subscriptions' ), esc_html( get_woocommerce_currency_symbol() ) ); ?></label>
|
<label for="_subscription_price"><?php printf( esc_html__( 'Subscription price (%s)', 'woocommerce-subscriptions' ), esc_html( get_woocommerce_currency_symbol() ) ); ?></label>
|
||||||
<span class="wrap">
|
<span class="wrap">
|
||||||
<input type="text" id="_subscription_price" name="_subscription_price" class="wc_input_price wc_input_subscription_price" placeholder="<?php echo esc_attr_x( 'e.g. 5.90', 'example price', 'woocommerce-subscriptions' ); ?>" step="any" min="0" value="<?php echo esc_attr( $chosen_price ); ?>" />
|
<input type="text" id="_subscription_price" name="_subscription_price" class="wc_input_price wc_input_subscription_price" placeholder="<?php echo esc_attr_x( 'e.g. 5.90', 'example price', 'woocommerce-subscriptions' ); ?>" step="any" min="0" value="<?php echo esc_attr( wc_format_localized_price( $chosen_price ) ); ?>" />
|
||||||
<label for="_subscription_period_interval" class="wcs_hidden_label"><?php esc_html_e( 'Subscription interval', 'woocommerce-subscriptions' ); ?></label>
|
<label for="_subscription_period_interval" class="wcs_hidden_label"><?php esc_html_e( 'Subscription interval', 'woocommerce-subscriptions' ); ?></label>
|
||||||
<select id="_subscription_period_interval" name="_subscription_period_interval" class="wc_input_subscription_period_interval">
|
<select id="_subscription_period_interval" name="_subscription_period_interval" class="wc_input_subscription_period_interval">
|
||||||
<?php foreach ( wcs_get_subscription_period_interval_strings() as $value => $label ) { ?>
|
<?php foreach ( wcs_get_subscription_period_interval_strings() as $value => $label ) { ?>
|
||||||
@@ -248,6 +248,7 @@ class WC_Subscriptions_Admin {
|
|||||||
'description' => __( 'Optionally include an amount to be charged at the outset of the subscription. The sign-up fee will be charged immediately, even if the product has a free trial or the payment dates are synced.', 'woocommerce-subscriptions' ),
|
'description' => __( 'Optionally include an amount to be charged at the outset of the subscription. The sign-up fee will be charged immediately, even if the product has a free trial or the payment dates are synced.', 'woocommerce-subscriptions' ),
|
||||||
'desc_tip' => true,
|
'desc_tip' => true,
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
|
'data_type' => 'price',
|
||||||
'custom_attributes' => array(
|
'custom_attributes' => array(
|
||||||
'step' => 'any',
|
'step' => 'any',
|
||||||
'min' => '0',
|
'min' => '0',
|
||||||
@@ -611,6 +612,11 @@ class WC_Subscriptions_Admin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( isset( $_POST['variable_subscription_sign_up_fee'][ $index ] ) ) {
|
||||||
|
$subscription_sign_up_fee = wc_format_decimal( $_POST['variable_subscription_sign_up_fee'][ $index ] );
|
||||||
|
update_post_meta( $variation_id, '_subscription_sign_up_fee', $subscription_sign_up_fee );
|
||||||
|
}
|
||||||
|
|
||||||
if ( isset( $_POST['variable_subscription_price'][ $index ] ) ) {
|
if ( isset( $_POST['variable_subscription_price'][ $index ] ) ) {
|
||||||
$subscription_price = wc_format_decimal( $_POST['variable_subscription_price'][ $index ] );
|
$subscription_price = wc_format_decimal( $_POST['variable_subscription_price'][ $index ] );
|
||||||
update_post_meta( $variation_id, '_subscription_price', $subscription_price );
|
update_post_meta( $variation_id, '_subscription_price', $subscription_price );
|
||||||
@@ -633,7 +639,6 @@ class WC_Subscriptions_Admin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$subscription_fields = array(
|
$subscription_fields = array(
|
||||||
'_subscription_sign_up_fee',
|
|
||||||
'_subscription_period',
|
'_subscription_period',
|
||||||
'_subscription_period_interval',
|
'_subscription_period_interval',
|
||||||
'_subscription_length',
|
'_subscription_length',
|
||||||
@@ -1359,34 +1364,40 @@ class WC_Subscriptions_Admin {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function do_subscriptions_shortcode( $attributes ) {
|
public static function do_subscriptions_shortcode( $attributes ) {
|
||||||
$attributes = wp_parse_args(
|
$attributes = shortcode_atts(
|
||||||
$attributes,
|
|
||||||
array(
|
array(
|
||||||
'user_id' => 0,
|
'user_id' => 0,
|
||||||
'status' => 'active',
|
'status' => 'active',
|
||||||
)
|
),
|
||||||
|
$attributes,
|
||||||
|
'subscriptions'
|
||||||
);
|
);
|
||||||
|
|
||||||
$subscriptions = wcs_get_users_subscriptions( $attributes['user_id'] );
|
$subscriptions = wcs_get_users_subscriptions( $attributes['user_id'] );
|
||||||
|
|
||||||
if ( empty( $subscriptions ) ) {
|
// Limit subscriptions to the appropriate status if it's not "any" or "all".
|
||||||
return '<ul class="user-subscriptions no-user-subscriptions">
|
if ( 'all' !== $attributes['status'] && 'any' !== $attributes['status'] ) {
|
||||||
<li>' . esc_html_x( 'No subscriptions found.', 'in [subscriptions] shortcode', 'woocommerce-subscriptions' ) . '</li>
|
/** @var WC_Subscription $subscription */
|
||||||
</ul>';
|
foreach ( $subscriptions as $index => $subscription ) {
|
||||||
}
|
if ( ! $subscription->has_status( $attributes['status'] ) ) {
|
||||||
|
unset( $subscriptions[ $index ] );
|
||||||
$list = '<ul class="user-subscriptions">';
|
}
|
||||||
|
|
||||||
foreach ( $subscriptions as $subscription ) {
|
|
||||||
if ( 'all' == $attributes['status'] || $subscription->has_status( $attributes['status'] ) ) {
|
|
||||||
// translators: order number
|
|
||||||
$shortcode_translate = sprintf( esc_html_x( 'Subscription %s', 'in [subscriptions] shortcode', 'woocommerce-subscriptions' ), $subscription->get_order_number() );
|
|
||||||
$list .= sprintf( '<li><a href="%s">%s</a></li>', $subscription->get_view_order_url(), $shortcode_translate );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$list .= '</ul>';
|
|
||||||
|
|
||||||
return $list;
|
// Load the subscription template, and return its content using Output Buffering.
|
||||||
|
ob_start();
|
||||||
|
wc_get_template(
|
||||||
|
'myaccount/my-subscriptions.php',
|
||||||
|
array(
|
||||||
|
'subscriptions' => $subscriptions,
|
||||||
|
'user_id' => $attributes['user_id'],
|
||||||
|
),
|
||||||
|
'',
|
||||||
|
plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'templates/'
|
||||||
|
);
|
||||||
|
|
||||||
|
return ob_get_clean();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -12,10 +12,6 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
exit;
|
exit;
|
||||||
} // Exit if accessed directly
|
} // Exit if accessed directly
|
||||||
|
|
||||||
if ( class_exists( 'WCS_Admin_Reports' ) ) {
|
|
||||||
return new WCS_Admin_Reports();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WCS_Admin_Reports Class
|
* WCS_Admin_Reports Class
|
||||||
*
|
*
|
||||||
@@ -161,7 +157,7 @@ class WCS_Admin_Reports {
|
|||||||
|
|
||||||
switch ( $screen->id ) {
|
switch ( $screen->id ) {
|
||||||
case 'dashboard' :
|
case 'dashboard' :
|
||||||
include( 'reports/class-wcs-report-dashboard.php' );
|
include_once( 'reports/class-wcs-report-dashboard.php' );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -102,7 +102,11 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="order_data_column">
|
<div class="order_data_column">
|
||||||
<h4><?php esc_html_e( 'Billing Details', 'woocommerce-subscriptions' ); ?> <a class="edit_address" href="#"><a href="#" class="tips load_customer_billing" data-tip="Load billing address" style="display:none;">Load billing address</a></a></h4>
|
<h3>
|
||||||
|
<?php esc_html_e( 'Billing Details', 'woocommerce-subscriptions' ); ?>
|
||||||
|
<a href="#" class="edit_address"><?php esc_html_e( 'Edit', 'woocommerce-subscriptions' ); ?></a>
|
||||||
|
<a href="#" class="tips load_customer_billing" data-tip="<?php esc_attr_e( 'Load billing address', 'woocommerce-subscriptions' ); ?>" style="display:none;"><?php esc_html_e( 'Load billing address', 'woocommerce-subscriptions' ); ?></a>
|
||||||
|
</h3>
|
||||||
<?php
|
<?php
|
||||||
// Display values
|
// Display values
|
||||||
echo '<div class="address">';
|
echo '<div class="address">';
|
||||||
@@ -148,18 +152,19 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
|
|||||||
if ( ! isset( $field['type'] ) ) {
|
if ( ! isset( $field['type'] ) ) {
|
||||||
$field['type'] = 'text';
|
$field['type'] = 'text';
|
||||||
}
|
}
|
||||||
|
if ( ! isset( $field['id'] ) ) {
|
||||||
|
$field['id'] = '_billing_' . $key;
|
||||||
|
}
|
||||||
switch ( $field['type'] ) {
|
switch ( $field['type'] ) {
|
||||||
case 'select' :
|
case 'select' :
|
||||||
// allow for setting a default value programaticaly, and draw the selectbox
|
woocommerce_wp_select( $field );
|
||||||
woocommerce_wp_select( array( 'id' => '_billing_' . $key, 'label' => $field['label'], 'options' => $field['options'], 'value' => isset( $field['value'] ) ? $field['value'] : null ) );
|
break;
|
||||||
break;
|
|
||||||
default :
|
default :
|
||||||
// allow for setting a default value programaticaly, and draw the textbox
|
woocommerce_wp_text_input( $field );
|
||||||
woocommerce_wp_text_input( array( 'id' => '_billing_' . $key, 'label' => $field['label'], 'value' => isset( $field['value'] ) ? $field['value'] : null ) );
|
break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WCS_Change_Payment_Method_Admin::display_fields( $subscription );
|
WCS_Change_Payment_Method_Admin::display_fields( $subscription );
|
||||||
|
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
@@ -169,12 +174,12 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
|
|||||||
</div>
|
</div>
|
||||||
<div class="order_data_column">
|
<div class="order_data_column">
|
||||||
|
|
||||||
<h4><?php esc_html_e( 'Shipping Details', 'woocommerce-subscriptions' ); ?>
|
<h3>
|
||||||
<a class="edit_address" href="#">
|
<?php esc_html_e( 'Shipping Details', 'woocommerce-subscriptions' ); ?>
|
||||||
<a href="#" class="tips billing-same-as-shipping" data-tip="Copy from billing" style="display:none;">Copy from billing</a>
|
<a href="#" class="edit_address"><?php esc_html_e( 'Edit', 'woocommerce-subscriptions' ); ?></a>
|
||||||
<a href="#" class="tips load_customer_shipping" data-tip="Load shipping address" style="display:none;">Load shipping address</a>
|
<a href="#" class="tips billing-same-as-shipping" data-tip="<?php esc_attr_e( 'Copy from billing', 'woocommerce-subscriptions' ); ?>" style="display:none;"><?php esc_html_e( 'Copy from billing', 'woocommerce-subscriptions' ); ?></a>
|
||||||
</a>
|
<a href="#" class="tips load_customer_shipping" data-tip="<?php esc_attr_e( 'Load shipping address', 'woocommerce-subscriptions' ); ?>" style="display:none;"><?php esc_html_e( 'Load shipping address', 'woocommerce-subscriptions' ); ?></a>
|
||||||
</h4>
|
</h3>
|
||||||
<?php
|
<?php
|
||||||
// Display values
|
// Display values
|
||||||
echo '<div class="address">';
|
echo '<div class="address">';
|
||||||
@@ -185,7 +190,7 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
|
|||||||
echo '<p class="none_set"><strong>' . esc_html__( 'Address', 'woocommerce-subscriptions' ) . ':</strong> ' . esc_html__( 'No shipping address set.', 'woocommerce-subscriptions' ) . '</p>';
|
echo '<p class="none_set"><strong>' . esc_html__( 'Address', 'woocommerce-subscriptions' ) . ':</strong> ' . esc_html__( 'No shipping address set.', 'woocommerce-subscriptions' ) . '</p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( self::$shipping_fields ) {
|
if ( ! empty( self::$shipping_fields ) ) {
|
||||||
foreach ( self::$shipping_fields as $key => $field ) {
|
foreach ( self::$shipping_fields as $key => $field ) {
|
||||||
if ( isset( $field['show'] ) && false === $field['show'] ) {
|
if ( isset( $field['show'] ) && false === $field['show'] ) {
|
||||||
continue;
|
continue;
|
||||||
@@ -204,7 +209,7 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( apply_filters( 'woocommerce_enable_order_notes_field', 'yes' == get_option( 'woocommerce_enable_order_comments', 'yes' ) ) && $post->post_excerpt ) {
|
if ( apply_filters( 'woocommerce_enable_order_notes_field', 'yes' == get_option( 'woocommerce_enable_order_comments', 'yes' ) ) && $post->post_excerpt ) {
|
||||||
echo '<p><strong>' . esc_html__( 'Customer Note:', 'woocommerce-subscriptions' ) . '</strong> ' . wp_kses_post( nl2br( $post->post_excerpt ) ) . '</p>';
|
echo '<p><strong>' . esc_html__( 'Customer Provided Note', 'woocommerce-subscriptions' ) . ':</strong> ' . wp_kses_post( nl2br( $post->post_excerpt ) ) . '</p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
@@ -212,28 +217,32 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
|
|||||||
// Display form
|
// Display form
|
||||||
echo '<div class="edit_address">';
|
echo '<div class="edit_address">';
|
||||||
|
|
||||||
if ( self::$shipping_fields ) {
|
if ( ! empty( self::$shipping_fields ) ) {
|
||||||
foreach ( self::$shipping_fields as $key => $field ) {
|
foreach ( self::$shipping_fields as $key => $field ) {
|
||||||
if ( ! isset( $field['type'] ) ) {
|
if ( ! isset( $field['type'] ) ) {
|
||||||
$field['type'] = 'text';
|
$field['type'] = 'text';
|
||||||
}
|
}
|
||||||
|
if ( ! isset( $field['id'] ) ) {
|
||||||
|
$field['id'] = '_shipping_' . $key;
|
||||||
|
}
|
||||||
|
|
||||||
switch ( $field['type'] ) {
|
switch ( $field['type'] ) {
|
||||||
case 'select' :
|
case 'select' :
|
||||||
woocommerce_wp_select( array( 'id' => '_shipping_' . $key, 'label' => $field['label'], 'options' => $field['options'] ) );
|
woocommerce_wp_select( $field );
|
||||||
break;
|
break;
|
||||||
default :
|
default :
|
||||||
woocommerce_wp_text_input( array( 'id' => '_shipping_' . $key, 'label' => $field['label'] ) );
|
woocommerce_wp_text_input( $field );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( apply_filters( 'woocommerce_enable_order_notes_field', 'yes' == get_option( 'woocommerce_enable_order_comments', 'yes' ) ) ) {
|
if ( apply_filters( 'woocommerce_enable_order_notes_field', 'yes' == get_option( 'woocommerce_enable_order_comments', 'yes' ) ) ) {
|
||||||
?>
|
?>
|
||||||
<p class="form-field form-field-wide"><label for="excerpt"><?php esc_html_e( 'Customer Note:', 'woocommerce-subscriptions' ) ?></label>
|
<p class="form-field form-field-wide"><label for="excerpt"><?php esc_html_e( 'Customer Provided Note', 'woocommerce-subscriptions' ) ?>:</label>
|
||||||
<textarea rows="1" cols="40" name="excerpt" tabindex="6" id="excerpt" placeholder="<?php esc_attr_e( 'Customer\'s notes about the order', 'woocommerce-subscriptions' ); ?>"><?php echo wp_kses_post( $post->post_excerpt ); ?></textarea></p>
|
<textarea rows="1" cols="40" name="excerpt" tabindex="6" id="excerpt" placeholder="<?php esc_attr_e( 'Customer\'s notes about the order', 'woocommerce-subscriptions' ); ?>"><?php echo wp_kses_post( $post->post_excerpt ); ?></textarea>
|
||||||
<?php
|
</p>
|
||||||
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
@@ -250,45 +259,48 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Save meta box data
|
* Save meta box data
|
||||||
|
*
|
||||||
|
* @param int $post_id
|
||||||
|
* @param WP_Post $post
|
||||||
*/
|
*/
|
||||||
public static function save( $post_id, $post = '' ) {
|
public static function save( $post_id, $post = null ) {
|
||||||
global $wpdb;
|
|
||||||
|
|
||||||
if ( 'shop_subscription' != $post->post_type || empty( $_POST['woocommerce_meta_nonce'] ) || ! wp_verify_nonce( $_POST['woocommerce_meta_nonce'], 'woocommerce_save_data' ) ) {
|
if ( 'shop_subscription' != $post->post_type || empty( $_POST['woocommerce_meta_nonce'] ) || ! wp_verify_nonce( $_POST['woocommerce_meta_nonce'], 'woocommerce_save_data' ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self::init_address_fields();
|
self::init_address_fields();
|
||||||
|
|
||||||
// Add key
|
// Get subscription object.
|
||||||
add_post_meta( $post_id, '_order_key', uniqid( 'order_' ), true );
|
$subscription = wcs_get_subscription( $post_id );
|
||||||
|
|
||||||
|
// Ensure there is an order key.
|
||||||
|
if ( ! $subscription->get_order_key() ) {
|
||||||
|
$key = 'wc_' . apply_filters( 'woocommerce_generate_order_key', uniqid( 'order_' ) );
|
||||||
|
wcs_set_objects_property( $subscription, 'order_key', $key );
|
||||||
|
}
|
||||||
|
|
||||||
// Update meta
|
// Update meta
|
||||||
update_post_meta( $post_id, '_customer_user', absint( $_POST['customer_user'] ) );
|
update_post_meta( $post_id, '_customer_user', absint( $_POST['customer_user'] ) );
|
||||||
|
|
||||||
if ( self::$billing_fields ) {
|
// Handle the billing fields.
|
||||||
foreach ( self::$billing_fields as $key => $field ) {
|
foreach ( self::$billing_fields as $key => $field ) {
|
||||||
|
$prefixed_key = "_billing_{$key}";
|
||||||
if ( ! isset( $_POST[ '_billing_' . $key ] ) ) {
|
if ( ! isset( $_POST[ $prefixed_key ] ) ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
update_post_meta( $post_id, '_billing_' . $key, wc_clean( $_POST[ '_billing_' . $key ] ) );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wcs_set_objects_property( $subscription, $prefixed_key, wc_clean( $_POST[ $prefixed_key ] ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( self::$shipping_fields ) {
|
// Handle the shipping fields.
|
||||||
foreach ( self::$shipping_fields as $key => $field ) {
|
foreach ( self::$shipping_fields as $key => $field ) {
|
||||||
|
$prefixed_key = "_shipping_{$key}";
|
||||||
if ( ! isset( $_POST[ '_shipping_' . $key ] ) ) {
|
if ( ! isset( $_POST[ $prefixed_key ] ) ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
update_post_meta( $post_id, '_shipping_' . $key, wc_clean( $_POST[ '_shipping_' . $key ] ) );
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$subscription = wcs_get_subscription( $post_id );
|
wcs_set_objects_property( $subscription, $prefixed_key, wc_clean( $_POST[ $prefixed_key ] ) );
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
WCS_Change_Payment_Method_Admin::save_meta( $subscription );
|
WCS_Change_Payment_Method_Admin::save_meta( $subscription );
|
||||||
@@ -305,5 +317,4 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
|
|||||||
|
|
||||||
do_action( 'woocommerce_process_shop_subscription_meta', $post_id, $post );
|
do_action( 'woocommerce_process_shop_subscription_meta', $post_id, $post );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -73,7 +73,6 @@ class WCS_Report_Cache_Manager {
|
|||||||
* Attach callbacks to manage cache updates
|
* Attach callbacks to manage cache updates
|
||||||
*
|
*
|
||||||
* @since 2.1
|
* @since 2.1
|
||||||
* @return null
|
|
||||||
*/
|
*/
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
|
|
||||||
@@ -101,6 +100,9 @@ class WCS_Report_Cache_Manager {
|
|||||||
|
|
||||||
// Notify store owners that report data can be out-of-date
|
// Notify store owners that report data can be out-of-date
|
||||||
add_action( 'admin_notices', array( $this, 'admin_notices' ), 0 );
|
add_action( 'admin_notices', array( $this, 'admin_notices' ), 0 );
|
||||||
|
|
||||||
|
// Add system status information.
|
||||||
|
add_filter( 'wcs_system_status', array( $this, 'add_system_status_info' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,7 +133,6 @@ class WCS_Report_Cache_Manager {
|
|||||||
* @see $this->set_reports_to_update().
|
* @see $this->set_reports_to_update().
|
||||||
*
|
*
|
||||||
* @since 2.1
|
* @since 2.1
|
||||||
* @return null
|
|
||||||
*/
|
*/
|
||||||
public function schedule_cache_updates() {
|
public function schedule_cache_updates() {
|
||||||
|
|
||||||
@@ -185,6 +186,15 @@ class WCS_Report_Cache_Manager {
|
|||||||
* @return null
|
* @return null
|
||||||
*/
|
*/
|
||||||
public function update_cache( $report_class ) {
|
public function update_cache( $report_class ) {
|
||||||
|
/**
|
||||||
|
* Filter whether Report Cache Updates are enabled.
|
||||||
|
*
|
||||||
|
* @param bool $enabled Whether report updates are enabled.
|
||||||
|
* @param string $report_class The report class to use.
|
||||||
|
*/
|
||||||
|
if ( ! apply_filters( 'wcs_report_cache_updates_enabled', 'yes' === get_option( 'woocommerce_subscriptions_cache_updates_enabled', 'yes' ), $report_class ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Validate the report class
|
// Validate the report class
|
||||||
$valid_report_class = false;
|
$valid_report_class = false;
|
||||||
@@ -200,6 +210,9 @@ class WCS_Report_Cache_Manager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hook our error catcher.
|
||||||
|
add_action( 'shutdown', array( $this, 'catch_unexpected_shutdown' ) );
|
||||||
|
|
||||||
// Load report class dependencies
|
// Load report class dependencies
|
||||||
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
|
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
|
||||||
require_once( WC()->plugin_path() . '/includes/admin/reports/class-wc-admin-report.php' );
|
require_once( WC()->plugin_path() . '/includes/admin/reports/class-wc-admin-report.php' );
|
||||||
@@ -226,6 +239,9 @@ class WCS_Report_Cache_Manager {
|
|||||||
$report->get_data( array( 'no_cache' => true ) );
|
$report->get_data( array( 'no_cache' => true ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove our error catcher.
|
||||||
|
remove_action( 'shutdown', array( $this, 'catch_unexpected_shutdown' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -274,5 +290,68 @@ class WCS_Report_Cache_Manager {
|
|||||||
wcs_add_admin_notice( __( 'Please note: data for this report is cached. The data displayed may be out of date by up to 24 hours. The cache is updated each morning at 4am in your site\'s timezone.', 'woocommerce-subscriptions' ) );
|
wcs_add_admin_notice( __( 'Please note: data for this report is cached. The data displayed may be out of date by up to 24 hours. The cache is updated each morning at 4am in your site\'s timezone.', 'woocommerce-subscriptions' ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle error instances that lead to an unexpected shutdown.
|
||||||
|
*
|
||||||
|
* This attempts to detect if there was an error, and proactively prevent errors
|
||||||
|
* from piling up.
|
||||||
|
*
|
||||||
|
* @author Jeremy Pry
|
||||||
|
*/
|
||||||
|
public function catch_unexpected_shutdown() {
|
||||||
|
$error = error_get_last();
|
||||||
|
if ( null === $error || ! isset( $error['type'] ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for the error types that matter to us.
|
||||||
|
if ( $error['type'] & ( E_ERROR | E_PARSE | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR ) ) {
|
||||||
|
$failures = get_option( 'woocommerce_subscriptions_cache_updates_failures', 0 );
|
||||||
|
$failures++;
|
||||||
|
update_option( 'woocommerce_subscriptions_cache_updates_failures', $failures, false );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter the allowed number of detected failures before we turn off cache updates.
|
||||||
|
*
|
||||||
|
* @param int $threshold The failure count threshold.
|
||||||
|
*/
|
||||||
|
if ( $failures > apply_filters( 'woocommerce_subscriptions_cache_updates_failures_threshold', 2 ) ) {
|
||||||
|
update_option( 'woocommerce_subscriptions_cache_updates_enabled', 'no', false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add system status information to include failure count and cache update status.
|
||||||
|
*
|
||||||
|
* @author Jeremy Pry
|
||||||
|
*
|
||||||
|
* @param array $data Existing status data.
|
||||||
|
*
|
||||||
|
* @return array Filtered status data.
|
||||||
|
*/
|
||||||
|
public function add_system_status_info( $data ) {
|
||||||
|
$cache_enabled = ( 'yes' === get_option( 'woocommerce_subscriptions_cache_updates_enabled', 'yes' ) );
|
||||||
|
$failures = get_option( 'woocommerce_subscriptions_cache_updates_failures', 0 );
|
||||||
|
$new_data = array(
|
||||||
|
'wcs_report_cache_enabled' => array(
|
||||||
|
'name' => _x( 'Report Cache Enabled', 'Whether the Report Cache has been enabled', 'woocommerce-subscriptions' ),
|
||||||
|
'note' => $cache_enabled ? __( 'Yes', 'woocommerce-subscriptions' ) : __( 'No', 'woocommerce-subscriptions' ),
|
||||||
|
'success' => $cache_enabled,
|
||||||
|
),
|
||||||
|
'wcs_cache_update_failures' => array(
|
||||||
|
'name' => __( 'Cache Update Failures', 'woocommerce-subscriptions' ),
|
||||||
|
/* translators: %d refers to the number of times we have detected cache update failures */
|
||||||
|
'note' => sprintf( _n( '%d failures', '%d failure', $failures, 'woocommerce-subscriptions' ), $failures ),
|
||||||
|
'success' => 0 === $failures,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$data = array_merge( $data, $new_data );
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new WCS_Report_Cache_Manager();
|
return new WCS_Report_Cache_Manager();
|
||||||
|
@@ -46,13 +46,18 @@ class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||||||
|
|
||||||
$next_payment_timestamp = strtotime( $r->scheduled_date );
|
$next_payment_timestamp = strtotime( $r->scheduled_date );
|
||||||
|
|
||||||
|
//Remove the time part of the end date, if there is one
|
||||||
|
if ( '0' !== $scheduled_ends[ $key ] ) {
|
||||||
|
$scheduled_ends[ $key ] = date( 'Y-m-d', strtotime( $scheduled_ends[ $key ] ) );
|
||||||
|
}
|
||||||
|
|
||||||
// Keep calculating all the new payments until we hit the end date of the search
|
// Keep calculating all the new payments until we hit the end date of the search
|
||||||
do {
|
do {
|
||||||
|
|
||||||
$next_payment_timestamp = wcs_add_time( $billing_intervals[ $key ], $billing_periods[ $key ], $next_payment_timestamp );
|
$next_payment_timestamp = wcs_add_time( $billing_intervals[ $key ], $billing_periods[ $key ], $next_payment_timestamp );
|
||||||
|
|
||||||
// If there are more renewals add them to the existing object or create a new one
|
// If there are more renewals add them to the existing object or create a new one
|
||||||
if ( $next_payment_timestamp <= $this->end_date && isset( $scheduled_ends[ $key ] ) && ( 0 == $scheduled_ends[ $key ] || $next_payment_timestamp < strtotime( $scheduled_ends[ $key ] ) ) ) {
|
if ( $next_payment_timestamp < $this->end_date && isset( $scheduled_ends[ $key ] ) && ( 0 == $scheduled_ends[ $key ] || $next_payment_timestamp < strtotime( $scheduled_ends[ $key ] ) ) ) {
|
||||||
$update_key = date( 'Y-m-d', $next_payment_timestamp );
|
$update_key = date( 'Y-m-d', $next_payment_timestamp );
|
||||||
|
|
||||||
if ( $next_payment_timestamp >= $this->start_date ) {
|
if ( $next_payment_timestamp >= $this->start_date ) {
|
||||||
@@ -69,7 +74,7 @@ class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||||||
$total_renewal_count += 1;
|
$total_renewal_count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while ( $next_payment_timestamp <= $this->end_date && isset( $scheduled_ends[ $key ] ) && ( 0 == $scheduled_ends[ $key ] || $next_payment_timestamp < strtotime( $scheduled_ends[ $key ] ) ) );
|
} while ( $next_payment_timestamp < $this->end_date && isset( $scheduled_ends[ $key ] ) && ( 0 == $scheduled_ends[ $key ] || $next_payment_timestamp < strtotime( $scheduled_ends[ $key ] ) ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -125,8 +125,6 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_V1_Controller {
|
|||||||
$subscription->set_total( wc_format_decimal( $request['order_total'], get_option( 'woocommerce_price_num_decimals' ) ) );
|
$subscription->set_total( wc_format_decimal( $request['order_total'], get_option( 'woocommerce_price_num_decimals' ) ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
$subscription->save();
|
|
||||||
|
|
||||||
// Store the post meta on the subscription after it's saved, this is to avoid compat. issue with the filters in WC_Subscriptions::set_payment_method_meta() expecting the $subscription to have an ID (therefore it needs to be called after the WC_Subscription has been saved)
|
// Store the post meta on the subscription after it's saved, this is to avoid compat. issue with the filters in WC_Subscriptions::set_payment_method_meta() expecting the $subscription to have an ID (therefore it needs to be called after the WC_Subscription has been saved)
|
||||||
$payment_data = ( ! empty( $request['payment_details'] ) ) ? $request['payment_details'] : array();
|
$payment_data = ( ! empty( $request['payment_details'] ) ) ? $request['payment_details'] : array();
|
||||||
if ( empty( $payment_data['payment_details']['method_id'] ) && ! empty( $request['payment_method'] ) ) {
|
if ( empty( $payment_data['payment_details']['method_id'] ) && ! empty( $request['payment_method'] ) ) {
|
||||||
@@ -375,6 +373,8 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_V1_Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$subscription->save();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ( ! empty( $dates_to_update ) ) {
|
if ( ! empty( $dates_to_update ) ) {
|
||||||
$subscription->update_dates( $dates_to_update );
|
$subscription->update_dates( $dates_to_update );
|
||||||
|
@@ -1285,7 +1285,7 @@ class WC_Subscription extends WC_Order {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( ! empty( $message ) ) {
|
if ( ! empty( $message ) ) {
|
||||||
throw new Exception( $message );
|
throw new Exception( sprintf( __( 'Subscription #%d: ', 'woocommerce-subscriptions' ), $this->get_id() ) . $message );
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->set_date_prop( $date_type, 0 );
|
$this->set_date_prop( $date_type, 0 );
|
||||||
@@ -1789,10 +1789,17 @@ class WC_Subscription extends WC_Order {
|
|||||||
/**
|
/**
|
||||||
* Get parent order object.
|
* Get parent order object.
|
||||||
*
|
*
|
||||||
* @return WC_Order
|
* @return mixed WC_Order|bool
|
||||||
*/
|
*/
|
||||||
public function get_parent() {
|
public function get_parent() {
|
||||||
return wc_get_order( $this->get_parent_id() );
|
$parent_id = $this->get_parent_id();
|
||||||
|
$order = false;
|
||||||
|
|
||||||
|
if ( $parent_id > 0 ) {
|
||||||
|
$order = wc_get_order( $parent_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $order;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2335,7 +2342,7 @@ class WC_Subscription extends WC_Order {
|
|||||||
|
|
||||||
// Don't validate dates while the subscription is being read, only dates set outside of instantiation require the strict validation rules to apply
|
// Don't validate dates while the subscription is being read, only dates set outside of instantiation require the strict validation rules to apply
|
||||||
if ( $this->object_read && ! empty( $messages ) ) {
|
if ( $this->object_read && ! empty( $messages ) ) {
|
||||||
throw new Exception( join( ' ', $messages ) );
|
throw new Exception( sprintf( __( 'Subscription #%d: ', 'woocommerce-subscriptions' ), $this->get_id() ) . join( ' ', $messages ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_merge( $dates, $delete_date_types );
|
return array_merge( $dates, $delete_date_types );
|
||||||
@@ -2480,5 +2487,4 @@ class WC_Subscription extends WC_Order {
|
|||||||
|
|
||||||
return $datetime;
|
return $datetime;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -400,9 +400,11 @@ class WC_Subscriptions_Cart {
|
|||||||
$needs_shipping = false;
|
$needs_shipping = false;
|
||||||
}
|
}
|
||||||
} elseif ( 'recurring_total' == self::$calculation_type ) {
|
} elseif ( 'recurring_total' == self::$calculation_type ) {
|
||||||
if ( true == $needs_shipping && ! self::cart_contains_subscriptions_needing_shipping() ) {
|
$cart = ( isset( self::$cached_recurring_cart ) ) ? self::$cached_recurring_cart : WC()->cart;
|
||||||
|
|
||||||
|
if ( true == $needs_shipping && ! self::cart_contains_subscriptions_needing_shipping( $cart ) ) {
|
||||||
$needs_shipping = false;
|
$needs_shipping = false;
|
||||||
} elseif ( false == $needs_shipping && self::cart_contains_subscriptions_needing_shipping() ) {
|
} elseif ( false == $needs_shipping && self::cart_contains_subscriptions_needing_shipping( $cart ) ) {
|
||||||
$needs_shipping = true;
|
$needs_shipping = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -591,16 +593,20 @@ class WC_Subscriptions_Cart {
|
|||||||
*
|
*
|
||||||
* @since 1.5.4
|
* @since 1.5.4
|
||||||
*/
|
*/
|
||||||
public static function cart_contains_subscriptions_needing_shipping() {
|
public static function cart_contains_subscriptions_needing_shipping( $cart = null ) {
|
||||||
|
|
||||||
if ( 'no' === get_option( 'woocommerce_calc_shipping' ) ) {
|
if ( 'no' === get_option( 'woocommerce_calc_shipping' ) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( null === $cart ) {
|
||||||
|
$cart = WC()->cart;
|
||||||
|
}
|
||||||
|
|
||||||
$cart_contains_subscriptions_needing_shipping = false;
|
$cart_contains_subscriptions_needing_shipping = false;
|
||||||
|
|
||||||
if ( self::cart_contains_subscription() ) {
|
if ( self::cart_contains_subscription() ) {
|
||||||
foreach ( WC()->cart->cart_contents as $cart_item_key => $values ) {
|
foreach ( $cart->cart_contents as $cart_item_key => $values ) {
|
||||||
$_product = $values['data'];
|
$_product = $values['data'];
|
||||||
if ( WC_Subscriptions_Product::is_subscription( $_product ) && $_product->needs_shipping() && false === WC_Subscriptions_Product::needs_one_time_shipping( $_product ) ) {
|
if ( WC_Subscriptions_Product::is_subscription( $_product ) && $_product->needs_shipping() && false === WC_Subscriptions_Product::needs_one_time_shipping( $_product ) ) {
|
||||||
$cart_contains_subscriptions_needing_shipping = true;
|
$cart_contains_subscriptions_needing_shipping = true;
|
||||||
@@ -690,10 +696,13 @@ class WC_Subscriptions_Cart {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( false !== strpos( $product_subtotal, WC()->countries->inc_tax_or_vat() ) ) {
|
$inc_tax_or_vat_string = WC()->countries->inc_tax_or_vat();
|
||||||
|
$ex_tax_or_vat_string = WC()->countries->ex_tax_or_vat();
|
||||||
|
|
||||||
|
if ( ! empty( $inc_tax_or_vat_string ) && false !== strpos( $product_subtotal, $inc_tax_or_vat_string ) ) {
|
||||||
$product_subtotal = str_replace( WC()->countries->inc_tax_or_vat(), '', $product_subtotal ) . ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
|
$product_subtotal = str_replace( WC()->countries->inc_tax_or_vat(), '', $product_subtotal ) . ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
|
||||||
}
|
}
|
||||||
if ( false !== strpos( $product_subtotal, WC()->countries->ex_tax_or_vat() ) ) {
|
if ( ! empty( $ex_tax_or_vat_string ) && false !== strpos( $product_subtotal, $ex_tax_or_vat_string ) ) {
|
||||||
$product_subtotal = str_replace( WC()->countries->ex_tax_or_vat(), '', $product_subtotal ) . ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
|
$product_subtotal = str_replace( WC()->countries->ex_tax_or_vat(), '', $product_subtotal ) . ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -352,8 +352,10 @@ class WC_Subscriptions_Checkout {
|
|||||||
|
|
||||||
// Allow plugins to add order item meta
|
// Allow plugins to add order item meta
|
||||||
if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
||||||
|
do_action( 'woocommerce_add_order_item_meta', $item_id, $cart_item, $cart_item_key );
|
||||||
do_action( 'woocommerce_add_subscription_item_meta', $item_id, $cart_item, $cart_item_key );
|
do_action( 'woocommerce_add_subscription_item_meta', $item_id, $cart_item, $cart_item_key );
|
||||||
} else {
|
} else {
|
||||||
|
wc_do_deprecated_action( 'woocommerce_add_order_item_meta', array( $item_id, $cart_item, $cart_item_key ), '3.0', 'CRUD and woocommerce_checkout_create_order_line_item action instead' );
|
||||||
wc_do_deprecated_action( 'woocommerce_add_subscription_item_meta', array( $item_id, $cart_item, $cart_item_key ), '3.0', 'CRUD and woocommerce_checkout_create_order_line_item action instead' );
|
wc_do_deprecated_action( 'woocommerce_add_subscription_item_meta', array( $item_id, $cart_item, $cart_item_key ), '3.0', 'CRUD and woocommerce_checkout_create_order_line_item action instead' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -36,7 +36,7 @@ class WC_Subscriptions_Coupon {
|
|||||||
add_filter( 'woocommerce_coupon_get_discount_amount', __CLASS__ . '::get_discount_amount', 10, 5 );
|
add_filter( 'woocommerce_coupon_get_discount_amount', __CLASS__ . '::get_discount_amount', 10, 5 );
|
||||||
|
|
||||||
// Validate subscription coupons
|
// Validate subscription coupons
|
||||||
add_filter( 'woocommerce_coupon_is_valid', __CLASS__ . '::validate_subscription_coupon', 10, 2 );
|
add_filter( 'woocommerce_coupon_is_valid', __CLASS__ . '::validate_subscription_coupon', 10, 3 );
|
||||||
|
|
||||||
// Remove coupons which don't apply to certain cart calculations
|
// Remove coupons which don't apply to certain cart calculations
|
||||||
add_action( 'woocommerce_before_calculate_totals', __CLASS__ . '::remove_coupons', 10 );
|
add_action( 'woocommerce_before_calculate_totals', __CLASS__ . '::remove_coupons', 10 );
|
||||||
@@ -75,11 +75,33 @@ class WC_Subscriptions_Coupon {
|
|||||||
*
|
*
|
||||||
* @since 2.0.10
|
* @since 2.0.10
|
||||||
*/
|
*/
|
||||||
public static function get_discount_amount( $discount, $discounting_amount, $cart_item, $single, $coupon ) {
|
public static function get_discount_amount( $discount, $discounting_amount, $item, $single, $coupon ) {
|
||||||
|
|
||||||
|
if ( is_a( $item, 'WC_Order_Item' ) ) { // WC 3.2 support for applying coupons to line items via admin edit subscription|order screen
|
||||||
|
$discount = self::get_discount_amount_for_line_item( $item, $discount, $discounting_amount, $single, $coupon );
|
||||||
|
} else {
|
||||||
|
$discount = self::get_discount_amount_for_cart_item( $item, $discount, $discounting_amount, $single, $coupon );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $discount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discount amount which applies for a cart item for subscription coupon types
|
||||||
|
*
|
||||||
|
* @since 2.2.13
|
||||||
|
* @param array $cart_item
|
||||||
|
* @param float $discount the original discount amount
|
||||||
|
* @param float $discounting_amount the cart item price/total which the coupon should apply to
|
||||||
|
* @param boolean $single True if discounting a single qty item, false if it's the line
|
||||||
|
* @param WC_Coupon $coupon
|
||||||
|
* @return float the discount amount which applies to the cart item
|
||||||
|
*/
|
||||||
|
public static function get_discount_amount_for_cart_item( $cart_item, $discount, $discounting_amount, $single, $coupon ) {
|
||||||
|
|
||||||
$coupon_type = wcs_get_coupon_property( $coupon, 'discount_type' );
|
$coupon_type = wcs_get_coupon_property( $coupon, 'discount_type' );
|
||||||
|
|
||||||
// Only deal with subscriptions coupon types
|
// Only deal with subscriptions coupon types which apply to cart items
|
||||||
if ( ! in_array( $coupon_type, array( 'recurring_fee', 'recurring_percent', 'sign_up_fee', 'sign_up_fee_percent', 'renewal_fee', 'renewal_percent', 'renewal_cart' ) ) ) {
|
if ( ! in_array( $coupon_type, array( 'recurring_fee', 'recurring_percent', 'sign_up_fee', 'sign_up_fee_percent', 'renewal_fee', 'renewal_percent', 'renewal_cart' ) ) ) {
|
||||||
return $discount;
|
return $discount;
|
||||||
}
|
}
|
||||||
@@ -200,6 +222,50 @@ class WC_Subscriptions_Coupon {
|
|||||||
return $discount_amount;
|
return $discount_amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discount amount which applies for a line item for subscription coupon types
|
||||||
|
*
|
||||||
|
* Uses methods and data structures introduced in WC 3.0.
|
||||||
|
*
|
||||||
|
* @since 2.2.13
|
||||||
|
* @param WC_Order_Item $line_item
|
||||||
|
* @param float $discount the original discount amount
|
||||||
|
* @param float $discounting_amount the line item price/total
|
||||||
|
* @param boolean $single True if discounting a single qty item, false if it's the line
|
||||||
|
* @param WC_Coupon $coupon
|
||||||
|
* @return float the discount amount which applies to the line item
|
||||||
|
*/
|
||||||
|
public static function get_discount_amount_for_line_item( $line_item, $discount, $discounting_amount, $single, $coupon ) {
|
||||||
|
|
||||||
|
if ( ! is_callable( array( $line_item, 'get_order' ) ) ) {
|
||||||
|
return $discount;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coupon_type = wcs_get_coupon_property( $coupon, 'discount_type' );
|
||||||
|
$order = $line_item->get_order();
|
||||||
|
$product = $line_item->get_product();
|
||||||
|
|
||||||
|
// Recurring coupons can be applied to subscriptions or any order which contains a subscription
|
||||||
|
if ( in_array( $coupon_type, array( 'recurring_fee', 'recurring_percent' ) ) && ( wcs_is_subscription( $order ) || wcs_order_contains_subscription( $order, 'any' ) ) ) {
|
||||||
|
if ( 'recurring_fee' === $coupon_type ) {
|
||||||
|
$discount = min( $coupon->get_amount(), $discounting_amount );
|
||||||
|
$discount = $single ? $discount : $discount * $line_item->get_quantity();
|
||||||
|
} else { // recurring_percent
|
||||||
|
$discount = (float) $coupon->get_amount() * ( $discounting_amount / 100 );
|
||||||
|
}
|
||||||
|
// Sign-up fee coupons apply to parent order line items which are subscription products and have a signup fee
|
||||||
|
} elseif ( in_array( $coupon_type, array( 'sign_up_fee', 'sign_up_fee_percent' ) ) && WC_Subscriptions_Product::is_subscription( $product ) && wcs_order_contains_subscription( $order, 'parent' ) && 0 !== WC_Subscriptions_Product::get_sign_up_fee( $product ) ) {
|
||||||
|
if ( 'sign_up_fee' === $coupon_type ) {
|
||||||
|
$discount = min( $coupon->get_amount(), WC_Subscriptions_Product::get_sign_up_fee( $product ) );
|
||||||
|
$discount = $single ? $discount : $discount * $line_item->get_quantity();
|
||||||
|
} else { // sign_up_fee_percent
|
||||||
|
$discount = (float) $coupon->get_amount() * ( WC_Subscriptions_Product::get_sign_up_fee( $product ) / 100 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $discount;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the cart contains a discount code of a given coupon type.
|
* Determine if the cart contains a discount code of a given coupon type.
|
||||||
*
|
*
|
||||||
@@ -239,14 +305,46 @@ class WC_Subscriptions_Coupon {
|
|||||||
/**
|
/**
|
||||||
* Check if a subscription coupon is valid before applying
|
* Check if a subscription coupon is valid before applying
|
||||||
*
|
*
|
||||||
|
* @param boolean $valid
|
||||||
|
* @param WC_Coupon $coupon
|
||||||
|
* @param WC_Discounts $discount Added in WC 3.2 the WC_Discounts object contains information about the coupon being applied to either carts or orders - Optional
|
||||||
|
* @return boolean Whether the coupon is valid or not
|
||||||
* @since 1.2
|
* @since 1.2
|
||||||
*/
|
*/
|
||||||
public static function validate_subscription_coupon( $valid, $coupon ) {
|
public static function validate_subscription_coupon( $valid, $coupon, $discount = null ) {
|
||||||
|
|
||||||
if ( ! apply_filters( 'woocommerce_subscriptions_validate_coupon_type', true, $coupon, $valid ) ) {
|
if ( ! apply_filters( 'woocommerce_subscriptions_validate_coupon_type', true, $coupon, $valid ) ) {
|
||||||
return $valid;
|
return $valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( is_a( $discount, 'WC_Discounts' ) ) { // WC 3.2+
|
||||||
|
$discount_items = $discount->get_items();
|
||||||
|
|
||||||
|
if ( is_array( $discount_items ) && ! empty( $discount_items ) ) {
|
||||||
|
$item = reset( $discount_items );
|
||||||
|
|
||||||
|
if ( isset( $item->object ) && is_a( $item->object, 'WC_Order_Item' ) ) {
|
||||||
|
$valid = self::validate_subscription_coupon_for_order( $valid, $coupon, $item->object->get_order() );
|
||||||
|
} else {
|
||||||
|
$valid = self::validate_subscription_coupon_for_cart( $valid, $coupon );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$valid = self::validate_subscription_coupon_for_cart( $valid, $coupon );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a subscription coupon is valid for the cart.
|
||||||
|
*
|
||||||
|
* @since 2.2.13
|
||||||
|
* @param boolean $valid
|
||||||
|
* @param WC_Coupon $coupon
|
||||||
|
* @return bool whether the coupon is valid
|
||||||
|
*/
|
||||||
|
public static function validate_subscription_coupon_for_cart( $valid, $coupon ) {
|
||||||
self::$coupon_error = '';
|
self::$coupon_error = '';
|
||||||
$coupon_type = wcs_get_coupon_property( $coupon, 'discount_type' );
|
$coupon_type = wcs_get_coupon_property( $coupon, 'discount_type' );
|
||||||
|
|
||||||
@@ -289,6 +387,37 @@ class WC_Subscriptions_Coupon {
|
|||||||
return $valid;
|
return $valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a subscription coupon is valid for an order/subscription.
|
||||||
|
*
|
||||||
|
* @since 2.2.13
|
||||||
|
* @param WC_Coupon $coupon The subscription coupon being validated. Can accept recurring_fee, recurring_percent, sign_up_fee or sign_up_fee_percent coupon types.
|
||||||
|
* @param WC_Order|WC_Subscription $order The order or subscription object to which the coupon is being applied
|
||||||
|
* @return bool whether the coupon is valid
|
||||||
|
*/
|
||||||
|
public static function validate_subscription_coupon_for_order( $valid, $coupon, $order ) {
|
||||||
|
$coupon_type = wcs_get_coupon_property( $coupon, 'discount_type' );
|
||||||
|
$error_message = '';
|
||||||
|
|
||||||
|
// Recurring coupons can be applied to subscriptions and renewal orders
|
||||||
|
if ( in_array( $coupon_type, array( 'recurring_fee', 'recurring_percent' ) ) && ! ( wcs_is_subscription( $order ) || wcs_order_contains_subscription( $order, 'any' ) ) ) {
|
||||||
|
$error_message = __( 'Sorry, recurring coupons can only be applied to subscriptions or subscription orders.', 'woocommerce-subscriptions' );
|
||||||
|
// Sign-up fee coupons can be applied to parent orders which contain subscription products with at least one sign up fee
|
||||||
|
} elseif ( in_array( $coupon_type, array( 'sign_up_fee', 'sign_up_fee_percent' ) ) && ! ( wcs_order_contains_subscription( $order, 'parent' ) || 0 !== WC_Subscriptions_Order::get_sign_up_fee( $order ) ) ) {
|
||||||
|
// translators: placeholder is coupon code
|
||||||
|
$error_message = sprintf( __( 'Sorry, "%s" can only be applied to subscription parent orders which contain a product with signup fees.', 'woocommerce-subscriptions' ), wcs_get_coupon_property( $coupon, 'code' ) );
|
||||||
|
// Only recurring coupons can be applied to subscriptions
|
||||||
|
} elseif ( ! in_array( $coupon_type, array( 'recurring_fee', 'recurring_percent' ) ) && wcs_is_subscription( $order ) ) {
|
||||||
|
$error_message = __( 'Sorry, only recurring coupons can only be applied to subscriptions.', 'woocommerce-subscriptions' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! empty( $error_message ) ) {
|
||||||
|
throw new Exception( $error_message );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $valid;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a subscription coupon-specific error if validation failed
|
* Returns a subscription coupon-specific error if validation failed
|
||||||
*
|
*
|
||||||
|
@@ -50,6 +50,9 @@ class WC_Subscriptions_Manager {
|
|||||||
// Order is trashed, trash subscription
|
// Order is trashed, trash subscription
|
||||||
add_action( 'wp_trash_post', __CLASS__ . '::maybe_trash_subscription', 10 );
|
add_action( 'wp_trash_post', __CLASS__ . '::maybe_trash_subscription', 10 );
|
||||||
|
|
||||||
|
// When order is deleted, delete the subscription.
|
||||||
|
add_action( 'before_delete_post', array( __CLASS__, 'maybe_delete_subscription' ) );
|
||||||
|
|
||||||
// When a user is being deleted from the site, via standard WordPress functions, make sure their subscriptions are cancelled
|
// When a user is being deleted from the site, via standard WordPress functions, make sure their subscriptions are cancelled
|
||||||
add_action( 'delete_user', __CLASS__ . '::trash_users_subscriptions' );
|
add_action( 'delete_user', __CLASS__ . '::trash_users_subscriptions' );
|
||||||
|
|
||||||
@@ -71,27 +74,40 @@ class WC_Subscriptions_Manager {
|
|||||||
/**
|
/**
|
||||||
* Sets up renewal for subscriptions managed by Subscriptions.
|
* Sets up renewal for subscriptions managed by Subscriptions.
|
||||||
*
|
*
|
||||||
* This function is hooked early on the scheduled subscription payment hook and will:
|
* This function is hooked early on the scheduled subscription payment hook.
|
||||||
* - place the subscription on-hold
|
|
||||||
* - create a renewal order, with the pending status if it requires payment, or processing/complete
|
|
||||||
* if the recurring total is $0.
|
|
||||||
*
|
*
|
||||||
* @param int $subscription_id The ID of a 'shop_subscription' post
|
* @param int $subscription_id The ID of a 'shop_subscription' post
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public static function prepare_renewal( $subscription_id ) {
|
public static function prepare_renewal( $subscription_id ) {
|
||||||
|
|
||||||
|
$order_note = _x( 'Subscription renewal payment due:', 'used in order note as reason for why subscription status changed', 'woocommerce-subscriptions' );
|
||||||
|
|
||||||
|
$renewal_order = self::process_renewal( $subscription_id, 'active', $order_note );
|
||||||
|
|
||||||
|
// Backward compatibility with Subscriptions < 2.2.12 where we returned false for an unknown reason
|
||||||
|
if ( false === $renewal_order ) {
|
||||||
|
return $renewal_order;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process renewal for a subscription.
|
||||||
|
*
|
||||||
|
* @param int $subscription_id The ID of a 'shop_subscription' post
|
||||||
|
* @param string $required_status The subscription status required to process a renewal order
|
||||||
|
* @param string $order_note Reason for subscription status change
|
||||||
|
* @since 2.2.12
|
||||||
|
*/
|
||||||
|
public static function process_renewal( $subscription_id, $required_status, $order_note ) {
|
||||||
|
|
||||||
$subscription = wcs_get_subscription( $subscription_id );
|
$subscription = wcs_get_subscription( $subscription_id );
|
||||||
|
|
||||||
if ( empty( $subscription ) || ! $subscription->has_status( 'active' ) ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the subscription is using manual payments, the gateway isn't active or it manages scheduled payments
|
// If the subscription is using manual payments, the gateway isn't active or it manages scheduled payments
|
||||||
if ( 0 == $subscription->get_total() || $subscription->is_manual() || '' == $subscription->get_payment_method() || ! $subscription->payment_method_supports( 'gateway_scheduled_payments' ) ) {
|
if ( ! empty( $subscription ) && $subscription->has_status( $required_status ) && ( 0 == $subscription->get_total() || $subscription->is_manual() || '' == $subscription->get_payment_method() || ! $subscription->payment_method_supports( 'gateway_scheduled_payments' ) ) ) {
|
||||||
|
|
||||||
// Always put the subscription on hold in case something goes wrong while trying to process renewal
|
// Always put the subscription on hold in case something goes wrong while trying to process renewal
|
||||||
$subscription->update_status( 'on-hold', _x( 'Subscription renewal payment due:', 'used in order note as reason for why subscription status changed', 'woocommerce-subscriptions' ) );
|
$subscription->update_status( 'on-hold', $order_note );
|
||||||
|
|
||||||
// Generate a renewal order for payment gateways to use to record the payment (and determine how much is due)
|
// Generate a renewal order for payment gateways to use to record the payment (and determine how much is due)
|
||||||
$renewal_order = wcs_create_renewal_order( $subscription );
|
$renewal_order = wcs_create_renewal_order( $subscription );
|
||||||
@@ -101,7 +117,7 @@ class WC_Subscriptions_Manager {
|
|||||||
$renewal_order = wcs_create_renewal_order( $subscription );
|
$renewal_order = wcs_create_renewal_order( $subscription );
|
||||||
|
|
||||||
if ( is_wp_error( $renewal_order ) ) {
|
if ( is_wp_error( $renewal_order ) ) {
|
||||||
throw new Exception( __( 'Error: Unable to create renewal order from scheduled payment. Please try again.', 'woocommerce-subscriptions' ) );
|
throw new Exception( sprintf( __( 'Error: Unable to create renewal order with note "%s"', 'woocommerce-subscriptions' ), $order_note ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +139,11 @@ class WC_Subscriptions_Manager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$renewal_order = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $renewal_order;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -754,8 +774,9 @@ class WC_Subscriptions_Manager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear all subscriptions attached to an order when it's deleted. Also make sure
|
* Trash all subscriptions attached to an order when it's trashed.
|
||||||
* all related scheduled actions are cancelled when deleting a susbcription.
|
*
|
||||||
|
* Also make sure all related scheduled actions are cancelled when deleting a subscription.
|
||||||
*
|
*
|
||||||
* @param int $post_id The post ID of the WC Subscription or WC Order being trashed
|
* @param int $post_id The post ID of the WC Subscription or WC Order being trashed
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
@@ -771,6 +792,27 @@ class WC_Subscriptions_Manager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete related subscriptions when an order is deleted.
|
||||||
|
*
|
||||||
|
* @author Jeremy Pry
|
||||||
|
*
|
||||||
|
* @param int $post_id The post ID being deleted.
|
||||||
|
*/
|
||||||
|
public static function maybe_delete_subscription( $post_id ) {
|
||||||
|
if ( 'shop_order' !== get_post_type( $post_id ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var WC_Subscription[] $subscriptions */
|
||||||
|
$subscriptions = wcs_get_subscriptions_for_order( $post_id, array(
|
||||||
|
'subscription_status' => array( 'any', 'trash' ),
|
||||||
|
) );
|
||||||
|
foreach ( $subscriptions as $subscription ) {
|
||||||
|
wp_delete_post( $subscription->get_id() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make sure a subscription is cancelled before it is trashed or deleted
|
* Make sure a subscription is cancelled before it is trashed or deleted
|
||||||
*
|
*
|
||||||
|
@@ -797,7 +797,7 @@ class WC_Subscriptions_Order {
|
|||||||
$subscriptions = wcs_get_subscriptions_for_order( wcs_get_objects_property( $order, 'id' ), array( 'order_type' => 'any' ) );
|
$subscriptions = wcs_get_subscriptions_for_order( wcs_get_objects_property( $order, 'id' ), array( 'order_type' => 'any' ) );
|
||||||
|
|
||||||
foreach ( $subscriptions as $subscription ) {
|
foreach ( $subscriptions as $subscription ) {
|
||||||
if ( wcs_get_objects_property( $order, 'id' ) != $subscription->get_last_order() ) {
|
if ( wcs_get_objects_property( $order, 'id' ) != $subscription->get_last_order( 'ids', 'any' ) ) {
|
||||||
unset( $actions['pay'] );
|
unset( $actions['pay'] );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@@ -556,7 +556,7 @@ class WC_Subscriptions_Product {
|
|||||||
$from_date = gmdate( 'Y-m-d H:i:s' );
|
$from_date = gmdate( 'Y-m-d H:i:s' );
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the subscription has a free trial period, the first renewal is the same as the expiration of the free trial
|
// If the subscription has a free trial period, the first renewal payment date is the same as the expiration of the free trial
|
||||||
if ( $trial_length > 0 ) {
|
if ( $trial_length > 0 ) {
|
||||||
|
|
||||||
$first_renewal_timestamp = wcs_date_to_time( self::get_trial_expiration_date( $product_id, $from_date ) );
|
$first_renewal_timestamp = wcs_date_to_time( self::get_trial_expiration_date( $product_id, $from_date ) );
|
||||||
|
@@ -95,8 +95,16 @@ class WC_Subscriptions_Renewal_Order {
|
|||||||
wp_update_post( $update_post_data );
|
wp_update_post( $update_post_data );
|
||||||
update_post_meta( $order_id, '_paid_date', current_time( 'mysql' ) );
|
update_post_meta( $order_id, '_paid_date', current_time( 'mysql' ) );
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
$current_time = current_time( 'timestamp', 1 );
|
||||||
|
|
||||||
|
// Prior to WC 3.0, we need to update the post date (i.e. the date created) to have a reliable representation of the paid date (both because it was in GMT and because it was always set). That's not needed in WC 3.0, but some plugins and store owners still rely on it being updated, so we want to make it possible to update it with 3.0 also.
|
||||||
|
if ( apply_filters( 'wcs_renewal_order_payment_update_date_created', false, $order, $subscriptions ) ) {
|
||||||
|
$order->set_date_created( $current_time );
|
||||||
|
}
|
||||||
|
|
||||||
// In WC 3.0, only the paid date prop represents the paid date, the post date isn't used anymore, also the paid date is stored and referenced as a MySQL date string in site timezone and a GMT timestamp
|
// In WC 3.0, only the paid date prop represents the paid date, the post date isn't used anymore, also the paid date is stored and referenced as a MySQL date string in site timezone and a GMT timestamp
|
||||||
$order->set_date_paid( current_time( 'timestamp', 1 ) );
|
$order->set_date_paid( $current_time );
|
||||||
$order->save();
|
$order->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -120,6 +120,10 @@ class WC_Subscriptions_Switcher {
|
|||||||
add_action( 'woocommerce_cart_totals_after_shipping', __CLASS__ . '::maybe_unset_free_trial' );
|
add_action( 'woocommerce_cart_totals_after_shipping', __CLASS__ . '::maybe_unset_free_trial' );
|
||||||
add_action( 'woocommerce_review_order_before_shipping', __CLASS__ . '::maybe_set_free_trial' );
|
add_action( 'woocommerce_review_order_before_shipping', __CLASS__ . '::maybe_set_free_trial' );
|
||||||
add_action( 'woocommerce_review_order_after_shipping', __CLASS__ . '::maybe_unset_free_trial' );
|
add_action( 'woocommerce_review_order_after_shipping', __CLASS__ . '::maybe_unset_free_trial' );
|
||||||
|
|
||||||
|
// Grant download permissions after the switch is complete.
|
||||||
|
add_action( 'woocommerce_grant_product_download_permissions', __CLASS__ . '::delay_granting_download_permissions', 9, 1 );
|
||||||
|
add_action( 'woocommerce_subscriptions_switch_completed', __CLASS__ . '::grant_download_permissions', 9, 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -758,10 +762,10 @@ class WC_Subscriptions_Switcher {
|
|||||||
// Add the new item
|
// Add the new item
|
||||||
if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
||||||
$item_id = WC_Subscriptions_Checkout::add_cart_item( $subscription, $cart_item, $cart_item_key );
|
$item_id = WC_Subscriptions_Checkout::add_cart_item( $subscription, $cart_item, $cart_item_key );
|
||||||
wc_update_order_item( $item_id, array( 'order_item_type' => 'line_item_pending_switch' ) );
|
wcs_update_order_item_type( $item_id, 'line_item_pending_switch', $subscription->get_id() );
|
||||||
} else {
|
} else {
|
||||||
$item = new WC_Order_Item_Pending_Switch;
|
$item = new WC_Order_Item_Pending_Switch;
|
||||||
$item->legacy_values = $cart_item['data']; // @deprecated For legacy actions.
|
$item->legacy_values = $cart_item; // @deprecated For legacy actions.
|
||||||
$item->legacy_cart_item_key = $cart_item_key; // @deprecated For legacy actions.
|
$item->legacy_cart_item_key = $cart_item_key; // @deprecated For legacy actions.
|
||||||
$item->set_props( array(
|
$item->set_props( array(
|
||||||
'quantity' => $cart_item['quantity'],
|
'quantity' => $cart_item['quantity'],
|
||||||
@@ -856,7 +860,7 @@ class WC_Subscriptions_Switcher {
|
|||||||
foreach ( $subscription->get_shipping_methods() as $shipping_line_item_id => $shipping_meta ) {
|
foreach ( $subscription->get_shipping_methods() as $shipping_line_item_id => $shipping_meta ) {
|
||||||
|
|
||||||
if ( ! in_array( $shipping_line_item_id, $current_shipping_line_items ) ) {
|
if ( ! in_array( $shipping_line_item_id, $current_shipping_line_items ) ) {
|
||||||
wc_update_order_item( $shipping_line_item_id, array( 'order_item_type' => 'shipping_pending_switch' ) );
|
wcs_update_order_item_type( $shipping_line_item_id, 'shipping_pending_switch', $subscription->get_id() );
|
||||||
$new_shipping_line_items[] = $shipping_line_item_id;
|
$new_shipping_line_items[] = $shipping_line_item_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -904,7 +908,7 @@ class WC_Subscriptions_Switcher {
|
|||||||
|
|
||||||
// First, archive all the shipping methods
|
// First, archive all the shipping methods
|
||||||
foreach ( $subscription->get_shipping_methods() as $shipping_method_id => $shipping_method ) {
|
foreach ( $subscription->get_shipping_methods() as $shipping_method_id => $shipping_method ) {
|
||||||
wc_update_order_item( $shipping_method_id, array( 'order_item_type' => 'shipping_switched' ) );
|
wcs_update_order_item_type( $shipping_method_id, 'shipping_switched', $subscription->get_id() );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then zero the order_shipping total so we have a clean slate to add to
|
// Then zero the order_shipping total so we have a clean slate to add to
|
||||||
@@ -1576,6 +1580,11 @@ class WC_Subscriptions_Switcher {
|
|||||||
add_filter( 'woocommerce_subscriptions_recurring_cart_key', __METHOD__, 10, 2 );
|
add_filter( 'woocommerce_subscriptions_recurring_cart_key', __METHOD__, 10, 2 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Append switch data to the recurring cart key so switch items are separated from other subscriptions in the cart. Switch items are processed through the checkout separately so should have separate recurring carts.
|
||||||
|
if ( isset( $cart_item['subscription_switch']['subscription_id'] ) ) {
|
||||||
|
$cart_key .= '_switch_' . $cart_item['subscription_switch']['subscription_id'];
|
||||||
|
}
|
||||||
|
|
||||||
return $cart_key;
|
return $cart_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1902,7 +1911,7 @@ class WC_Subscriptions_Switcher {
|
|||||||
|
|
||||||
// If we are adding a line item to an existing subscription
|
// If we are adding a line item to an existing subscription
|
||||||
if ( isset( $switched_item_data['add_line_item'] ) ) {
|
if ( isset( $switched_item_data['add_line_item'] ) ) {
|
||||||
wc_update_order_item( $switched_item_data['add_line_item'], array( 'order_item_type' => 'line_item' ) );
|
wcs_update_order_item_type( $switched_item_data['add_line_item'], 'line_item', $subscription->get_id() );
|
||||||
do_action( 'woocommerce_subscription_item_switched', $order, $subscription, $switched_item_data['add_line_item'], $switched_item_data['remove_line_item'] );
|
do_action( 'woocommerce_subscription_item_switched', $order, $subscription, $switched_item_data['add_line_item'], $switched_item_data['remove_line_item'] );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1921,7 +1930,7 @@ class WC_Subscriptions_Switcher {
|
|||||||
$new_item_name = wcs_get_order_item_name( $switch_order_item, array( 'attributes' => true ) );
|
$new_item_name = wcs_get_order_item_name( $switch_order_item, array( 'attributes' => true ) );
|
||||||
remove_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_true' );
|
remove_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_true' );
|
||||||
|
|
||||||
wc_update_order_item( $switched_item_data['remove_line_item'], array( 'order_item_type' => 'line_item_switched' ) );
|
wcs_update_order_item_type( $switched_item_data['remove_line_item'], 'line_item_switched', $subscription->get_id() );
|
||||||
|
|
||||||
// translators: 1$: old item, 2$: new item when switching
|
// translators: 1$: old item, 2$: new item when switching
|
||||||
$add_note = sprintf( _x( 'Customer switched from: %1$s to %2$s.', 'used in order notes', 'woocommerce-subscriptions' ), $old_item_name, $new_item_name );
|
$add_note = sprintf( _x( 'Customer switched from: %1$s to %2$s.', 'used in order notes', 'woocommerce-subscriptions' ), $old_item_name, $new_item_name );
|
||||||
@@ -1967,12 +1976,12 @@ class WC_Subscriptions_Switcher {
|
|||||||
|
|
||||||
// Archive the old subscription shipping methods
|
// Archive the old subscription shipping methods
|
||||||
foreach ( $subscription->get_shipping_methods() as $shipping_line_item_id => $item ) {
|
foreach ( $subscription->get_shipping_methods() as $shipping_line_item_id => $item ) {
|
||||||
wc_update_order_item( $shipping_line_item_id, array( 'order_item_type' => 'shipping_switched' ) );
|
wcs_update_order_item_type( $shipping_line_item_id, 'shipping_switched', $subscription->get_id() );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flip the switched shipping line items "on"
|
// Flip the switched shipping line items "on"
|
||||||
foreach ( $switch_data['shipping_line_items'] as $shipping_line_item_id ) {
|
foreach ( $switch_data['shipping_line_items'] as $shipping_line_item_id ) {
|
||||||
wc_update_order_item( $shipping_line_item_id, array( 'order_item_type' => 'shipping' ) );
|
wcs_update_order_item_type( $shipping_line_item_id, 'shipping', $subscription->get_id() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2101,6 +2110,31 @@ class WC_Subscriptions_Switcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delay granting download permissions to the subscription until the switch is processed.
|
||||||
|
*
|
||||||
|
* @param int $order_id The order the download permissions are being granted for.
|
||||||
|
* @since 2.2.13
|
||||||
|
*/
|
||||||
|
public static function delay_granting_download_permissions( $order_id ) {
|
||||||
|
if ( wcs_order_contains_switch( $order_id ) ) {
|
||||||
|
remove_action( 'woocommerce_grant_product_download_permissions', 'WCS_Download_Handler::save_downloadable_product_permissions' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grant the download permissions to the subscription after the switch is processed.
|
||||||
|
*
|
||||||
|
* @param WC_Order The switch order.
|
||||||
|
* @since 2.2.13
|
||||||
|
*/
|
||||||
|
public static function grant_download_permissions( $order ) {
|
||||||
|
WCS_Download_Handler::save_downloadable_product_permissions( wcs_get_objects_property( $order, 'id' ) );
|
||||||
|
|
||||||
|
// reattach the hook detached in @see self::delay_granting_download_permissions()
|
||||||
|
add_action( 'woocommerce_grant_product_download_permissions', 'WCS_Download_Handler::save_downloadable_product_permissions' );
|
||||||
|
}
|
||||||
|
|
||||||
/** Deprecated Methods **/
|
/** Deprecated Methods **/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2234,7 +2268,7 @@ class WC_Subscriptions_Switcher {
|
|||||||
$old_subscription_item_name = wcs_get_order_item_name( $old_order_item, array( 'attributes' => true ) );
|
$old_subscription_item_name = wcs_get_order_item_name( $old_order_item, array( 'attributes' => true ) );
|
||||||
remove_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_true' );
|
remove_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_true' );
|
||||||
|
|
||||||
wc_update_order_item( $switch_item_data['subscription_item_id'], array( 'order_item_type' => 'line_item_switched' ) );
|
wcs_update_order_item_type( $switch_item_data['subscription_item_id'], 'line_item_switched', $subscription->get_id() );
|
||||||
|
|
||||||
// translators: 1$: old item, 2$: new item when switching
|
// translators: 1$: old item, 2$: new item when switching
|
||||||
$subscription->add_order_note( sprintf( _x( 'Customer switched from: %1$s to %2$s.', 'used in order notes', 'woocommerce-subscriptions' ), $old_subscription_item_name, $new_order_item_name ) );
|
$subscription->add_order_note( sprintf( _x( 'Customer switched from: %1$s to %2$s.', 'used in order notes', 'woocommerce-subscriptions' ), $old_subscription_item_name, $new_order_item_name ) );
|
||||||
@@ -2253,7 +2287,7 @@ class WC_Subscriptions_Switcher {
|
|||||||
protected static function switch_shipping_line_items_pre_2_1_2( $subscription, $shipping_methods ) {
|
protected static function switch_shipping_line_items_pre_2_1_2( $subscription, $shipping_methods ) {
|
||||||
// Archive the old subscription shipping methods
|
// Archive the old subscription shipping methods
|
||||||
foreach ( $subscription->get_shipping_methods() as $shipping_line_item_id => $item ) {
|
foreach ( $subscription->get_shipping_methods() as $shipping_line_item_id => $item ) {
|
||||||
wc_update_order_item( $shipping_line_item_id, array( 'order_item_type' => 'shipping_switched' ) );
|
wcs_update_order_item_type( $shipping_line_item_id, 'shipping_switched', $subscription->get_id() );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the new shipping line item
|
// Add the new shipping line item
|
||||||
|
@@ -18,7 +18,7 @@ class WCS_Cached_Data_Manager extends WCS_Cache_Manager {
|
|||||||
// Add filters for update / delete / trash post to purge cache
|
// Add filters for update / delete / trash post to purge cache
|
||||||
add_action( 'trashed_post', array( $this, 'purge_delete' ), 9999 ); // trashed posts aren't included in 'any' queries
|
add_action( 'trashed_post', array( $this, 'purge_delete' ), 9999 ); // trashed posts aren't included in 'any' queries
|
||||||
add_action( 'untrashed_post', array( $this, 'purge_delete' ), 9999 ); // however untrashed posts are
|
add_action( 'untrashed_post', array( $this, 'purge_delete' ), 9999 ); // however untrashed posts are
|
||||||
add_action( 'deleted_post', array( $this, 'purge_delete' ), 9999 ); // if forced delete is enabled
|
add_action( 'before_delete_post', array( $this, 'purge_delete' ), 9999 ); // if forced delete is enabled
|
||||||
add_action( 'updated_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys
|
add_action( 'updated_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys
|
||||||
add_action( 'deleted_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys
|
add_action( 'deleted_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys
|
||||||
add_action( 'added_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys
|
add_action( 'added_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys
|
||||||
@@ -74,13 +74,17 @@ class WCS_Cached_Data_Manager extends WCS_Cache_Manager {
|
|||||||
* @param $post_id integer the ID of a post
|
* @param $post_id integer the ID of a post
|
||||||
*/
|
*/
|
||||||
public function purge_delete( $post_id ) {
|
public function purge_delete( $post_id ) {
|
||||||
if ( 'shop_order' !== get_post_type( $post_id ) ) {
|
if ( 'shop_order' === get_post_type( $post_id ) ) {
|
||||||
return;
|
foreach ( wcs_get_subscriptions_for_order( $post_id, array( 'order_type' => 'any' ) ) as $subscription ) {
|
||||||
|
$this->log( 'Calling purge delete on ' . current_filter() . ' for ' . $subscription->get_id() );
|
||||||
|
$this->clear_related_order_cache( $subscription );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ( wcs_get_subscriptions_for_order( $post_id, array( 'order_type' => 'any' ) ) as $subscription ) {
|
// Purge wcs_do_subscriptions_exist cache, but only on the before_delete_post hook.
|
||||||
$this->log( 'Calling purge delete on ' . current_filter() . ' for ' . $subscription->get_id() );
|
if ( 'shop_subscription' === get_post_type( $post_id ) && doing_action( 'before_delete_post' ) ) {
|
||||||
$this->clear_related_order_cache( $subscription );
|
$this->log( "Subscription {$post_id} deleted. Purging subscription cache." );
|
||||||
|
$this->delete_cached( 'wcs_do_subscriptions_exist' );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -67,7 +67,7 @@ class WCS_Change_Payment_Method_Admin {
|
|||||||
|
|
||||||
foreach ( $meta as $meta_key => $meta_data ) {
|
foreach ( $meta as $meta_key => $meta_data ) {
|
||||||
|
|
||||||
$field_id = sprintf( '_payment_method_meta[%s][%s]', $meta_table , $meta_key );
|
$field_id = sprintf( '_payment_method_meta[%s][%s][%s]', $payment_method_id, $meta_table, $meta_key );
|
||||||
$field_label = ( ! empty( $meta_data['label'] ) ) ? $meta_data['label'] : $meta_key ;
|
$field_label = ( ! empty( $meta_data['label'] ) ) ? $meta_data['label'] : $meta_key ;
|
||||||
$field_value = ( ! empty( $meta_data['value'] ) ) ? $meta_data['value'] : null ;
|
$field_value = ( ! empty( $meta_data['value'] ) ) ? $meta_data['value'] : null ;
|
||||||
$field_disabled = ( isset( $meta_data['disabled'] ) && true == $meta_data['disabled'] ) ? ' readonly' : '';
|
$field_disabled = ( isset( $meta_data['disabled'] ) && true == $meta_data['disabled'] ) ? ' readonly' : '';
|
||||||
@@ -76,7 +76,6 @@ class WCS_Change_Payment_Method_Admin {
|
|||||||
echo '<label for="' . esc_attr( $field_id ) . '">' . esc_html( $field_label ) . '</label>';
|
echo '<label for="' . esc_attr( $field_id ) . '">' . esc_html( $field_label ) . '</label>';
|
||||||
echo '<input type="text" class="short" name="' . esc_attr( $field_id ) . '" id="' . esc_attr( $field_id ) . '" value="' . esc_attr( $field_value ) . '" placeholder="" ' . esc_attr( $field_disabled ) . '>';
|
echo '<input type="text" class="short" name="' . esc_attr( $field_id ) . '" id="' . esc_attr( $field_id ) . '" value="' . esc_attr( $field_value ) . '" placeholder="" ' . esc_attr( $field_disabled ) . '>';
|
||||||
echo '</p>';
|
echo '</p>';
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +121,7 @@ class WCS_Change_Payment_Method_Admin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ( $meta as $meta_key => $meta_data ) {
|
foreach ( $meta as $meta_key => $meta_data ) {
|
||||||
$payment_method_meta[ $meta_table ][ $meta_key ]['value'] = isset( $_POST['_payment_method_meta'][ $meta_table ][ $meta_key ] ) ? $_POST['_payment_method_meta'][ $meta_table ][ $meta_key ] : '';
|
$payment_method_meta[ $meta_table ][ $meta_key ]['value'] = isset( $_POST['_payment_method_meta'][ $payment_method ][ $meta_table ][ $meta_key ] ) ? $_POST['_payment_method_meta'][ $payment_method ][ $meta_table ][ $meta_key ] : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -217,17 +217,28 @@ class WCS_Download_Handler {
|
|||||||
|
|
||||||
if ( ! empty( $new_download_ids ) ) {
|
if ( ! empty( $new_download_ids ) ) {
|
||||||
|
|
||||||
$existing_permissions = $wpdb->get_col( $wpdb->prepare( "SELECT order_id from {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE product_id = %d GROUP BY order_id", $product_id ) );
|
$existing_permissions = $wpdb->get_results( $wpdb->prepare( "SELECT order_id, download_id from {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE product_id = %d", $product_id ) );
|
||||||
$subscriptions = wcs_get_subscriptions_for_product( $product_id );
|
$subscriptions = wcs_get_subscriptions_for_product( $product_id );
|
||||||
|
|
||||||
|
// Arrange download id permissions by order id
|
||||||
|
$permissions_by_order_id = array();
|
||||||
|
|
||||||
|
foreach ( $existing_permissions as $permission_data ) {
|
||||||
|
|
||||||
|
$permissions_by_order_id[ $permission_data->order_id ][] = $permission_data->download_id;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ( $subscriptions as $subscription_id ) {
|
foreach ( $subscriptions as $subscription_id ) {
|
||||||
|
|
||||||
// Grant permissions to subscriptions which have no permissions for this product, pre WC3.0, or all subscriptions, post WC3.0, as WC doesn't grant them retrospectively anymore.
|
// Grant permissions to subscriptions which have no permissions for this product, pre WC3.0, or all subscriptions, post WC3.0, as WC doesn't grant them retrospectively anymore.
|
||||||
if ( ! in_array( $subscription_id, $existing_permissions ) || false === WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
if ( ! in_array( $subscription_id, array_keys( $permissions_by_order_id ) ) || false === WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
||||||
$subscription = wcs_get_subscription( $subscription_id );
|
$subscription = wcs_get_subscription( $subscription_id );
|
||||||
|
|
||||||
foreach ( $new_download_ids as $download_id ) {
|
foreach ( $new_download_ids as $download_id ) {
|
||||||
if ( $subscription && apply_filters( 'woocommerce_process_product_file_download_paths_grant_access_to_new_file', true, $download_id, $product_id, $subscription ) ) {
|
|
||||||
|
$has_permission = isset( $permissions_by_order_id[ $subscription_id ] ) && in_array( $download_id, $permissions_by_order_id[ $subscription_id ] );
|
||||||
|
|
||||||
|
if ( $subscription && ! $has_permission && apply_filters( 'woocommerce_process_product_file_download_paths_grant_access_to_new_file', true, $download_id, $product_id, $subscription ) ) {
|
||||||
wc_downloadable_file_permission( $download_id, $product_id, $subscription );
|
wc_downloadable_file_permission( $download_id, $product_id, $subscription );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -79,8 +79,7 @@ class WCS_Remove_Item {
|
|||||||
if ( ! empty( $removed_item[ $item_id ] ) && $subscription->get_id() == $removed_item[ $item_id ] ) {
|
if ( ! empty( $removed_item[ $item_id ] ) && $subscription->get_id() == $removed_item[ $item_id ] ) {
|
||||||
|
|
||||||
// restore the item
|
// restore the item
|
||||||
wc_update_order_item( $item_id, array( 'order_item_type' => 'line_item' ) );
|
wcs_update_order_item_type( $item_id, 'line_item', $subscription->get_id() );
|
||||||
unset( $removed_item[ $item_id ] );
|
|
||||||
|
|
||||||
WC()->session->set( 'removed_subscription_items', $removed_item );
|
WC()->session->set( 'removed_subscription_items', $removed_item );
|
||||||
|
|
||||||
@@ -119,7 +118,7 @@ class WCS_Remove_Item {
|
|||||||
WCS_Download_Handler::revoke_downloadable_file_permission( $product_id, $subscription->get_id(), $subscription->get_user_id() );
|
WCS_Download_Handler::revoke_downloadable_file_permission( $product_id, $subscription->get_id(), $subscription->get_user_id() );
|
||||||
|
|
||||||
// remove the line item from subscription but preserve its data in the DB
|
// remove the line item from subscription but preserve its data in the DB
|
||||||
wc_update_order_item( $item_id, array( 'order_item_type' => 'line_item_removed' ) );
|
wcs_update_order_item_type( $item_id, 'line_item_removed', $subscription->get_id() );
|
||||||
|
|
||||||
// translators: 1$: product name, 2$: product id
|
// translators: 1$: product name, 2$: product id
|
||||||
$subscription->add_order_note( sprintf( _x( 'Customer removed "%1$s" (Product ID: #%2$d) via the My Account page.', 'used in order note', 'woocommerce-subscriptions' ), wcs_get_line_item_name( $line_item ), $product_id ) );
|
$subscription->add_order_note( sprintf( _x( 'Customer removed "%1$s" (Product ID: #%2$d) via the My Account page.', 'used in order note', 'woocommerce-subscriptions' ), wcs_get_line_item_name( $line_item ), $product_id ) );
|
||||||
|
@@ -15,6 +15,18 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
*/
|
*/
|
||||||
class WCS_Email_Customer_Renewal_Invoice extends WC_Email_Customer_Invoice {
|
class WCS_Email_Customer_Renewal_Invoice extends WC_Email_Customer_Invoice {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strings to find in subjects/headings.
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $find = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strings to replace in subjects/headings.
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $replace = array();
|
||||||
|
|
||||||
// fields used in WC_Email_Customer_Invoice this class doesn't need
|
// fields used in WC_Email_Customer_Invoice this class doesn't need
|
||||||
var $subject_paid = null;
|
var $subject_paid = null;
|
||||||
var $heading_paid = null;
|
var $heading_paid = null;
|
||||||
|
@@ -204,7 +204,7 @@ class WC_Subscriptions_Payment_Gateways {
|
|||||||
throw new InvalidArgumentException( sprintf( __( 'Subscription doesn\'t exist in scheduled action: %d', 'woocommerce-subscriptions' ), $subscription_id ) );
|
throw new InvalidArgumentException( sprintf( __( 'Subscription doesn\'t exist in scheduled action: %d', 'woocommerce-subscriptions' ), $subscription_id ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! $subscription->is_manual() ) {
|
if ( ! $subscription->is_manual() && ! $subscription->has_status( wcs_get_subscription_ended_statuses() ) ) {
|
||||||
self::trigger_gateway_renewal_payment_hook( $subscription->get_last_order( 'all', 'renewal' ) );
|
self::trigger_gateway_renewal_payment_hook( $subscription->get_last_order( 'all', 'renewal' ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -170,20 +170,24 @@ class WCS_PayPal_Admin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$last_ipn_error = get_option( 'wcs_fatal_error_handling_ipn', '' );
|
$last_ipn_error = get_option( 'wcs_fatal_error_handling_ipn', '' );
|
||||||
|
$failed_ipn_log_handle = 'wcs-ipn-failures';
|
||||||
|
|
||||||
if ( ! empty( $last_ipn_error ) && ( false == get_option( 'wcs_fatal_error_handling_ipn_ignored', false ) || isset( $_GET['wcs_reveal_your_ipn_secrets'] ) ) ) {
|
if ( ! empty( $last_ipn_error ) && ( false == get_option( 'wcs_fatal_error_handling_ipn_ignored', false ) || isset( $_GET['wcs_reveal_your_ipn_secrets'] ) ) ) {
|
||||||
$notices[] = array(
|
$notices[] = array(
|
||||||
'type' => 'error',
|
'type' => 'error',
|
||||||
'text' => sprintf( esc_html__( '%sA fatal error has occurred when processing a recent subscription payment with PayPal. Please %sopen a new ticket at WooCommerce Support%s immediately to get this resolved.%sIn order to get the quickest possible response please attach a %sTemporary Admin Login%s and a copy of your PHP error logs to your support ticket.%sLast recorded error: %s', 'woocommerce-subscriptions' ),
|
'text' => sprintf( esc_html__( '%sA fatal error has occurred when processing a recent subscription payment with PayPal. Please %sopen a new ticket at WooCommerce Support%s immediately to get this resolved.%sIn order to get the quickest possible response please attach a %sTemporary Admin Login%s and a copy of your PHP error logs to your support ticket.%sLast recorded error: %sTo see the full error, view the %s log file from the %sWooCommerce logs screen.%s', 'woocommerce-subscriptions' ),
|
||||||
'<p>',
|
'<p>',
|
||||||
'<a href="https://www.woocommerce.com/my-account/create-a-ticket/" target="_blank">',
|
'<a href="https://woocommerce.com/my-account/marketplace-ticket-form/" target="_blank">',
|
||||||
'</a>',
|
'</a>',
|
||||||
'<br>',
|
'<br>',
|
||||||
'<a href="https://docs.woocommerce.com/document/create-new-admin-account-wordpress/" target="_blank">',
|
'<a href="https://docs.woocommerce.com/document/create-new-admin-account-wordpress/" target="_blank">',
|
||||||
'</a>',
|
'</a>',
|
||||||
'</p>',
|
'</p>',
|
||||||
'<code>' . esc_html( $last_ipn_error ) . '</code><div style="margin: 5px 0;"><a class="button" href="' . esc_url( wp_nonce_url( add_query_arg( 'wcs_ipn_error_notice', 'ignore' ), 'wcs_ipn_error_notice', '_wcsnonce' ) ) . '">' . esc_html__( 'Ignore this error (not recommended!)', 'woocommerce-subscriptions' ) . '</a> <a class="button button-primary" href="https://www.woocommerce.com/my-account/create-a-ticket/">' . esc_html__( 'Open up a ticket now!', 'woocommerce-subscriptions' ) . '</a></div>'
|
'<code>' . esc_html( $last_ipn_error ) . '</code><p>',
|
||||||
|
'<code>' . $failed_ipn_log_handle . '</code>',
|
||||||
|
'<a href="' . admin_url( sprintf( 'admin.php?page=wc-status&tab=logs&log_file=%s-%s-log', $failed_ipn_log_handle, sanitize_file_name( wp_hash( $failed_ipn_log_handle ) ) ) ) . '">',
|
||||||
|
'</a></p><div style="margin: 5px 0;"><a class="button" href="' . esc_url( wp_nonce_url( add_query_arg( 'wcs_ipn_error_notice', 'ignore' ), 'wcs_ipn_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>'
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -109,7 +109,7 @@ class WCS_PayPal_Reference_Transaction_API_Request {
|
|||||||
if ( 0 == $args['order']->get_total() ) {
|
if ( 0 == $args['order']->get_total() ) {
|
||||||
|
|
||||||
$this->add_parameters( array(
|
$this->add_parameters( array(
|
||||||
'PAYMENTREQUEST_0_AMT' => 0, // a zero amount is use so that no DoExpressCheckout action is required and instead CreateBillingAgreement is used to first create a billing agreement not attached to any order and then DoReferenceTransaction is used to charge both the initial order and renewal order amounts
|
'PAYMENTREQUEST_0_AMT' => 0, // a zero amount is used so that no DoExpressCheckout action is required and instead CreateBillingAgreement is used to first create a billing agreement not attached to any order and then DoReferenceTransaction is used to charge both the initial order and renewal order amounts
|
||||||
'PAYMENTREQUEST_0_ITEMAMT' => 0,
|
'PAYMENTREQUEST_0_ITEMAMT' => 0,
|
||||||
'PAYMENTREQUEST_0_SHIPPINGAMT' => 0,
|
'PAYMENTREQUEST_0_SHIPPINGAMT' => 0,
|
||||||
'PAYMENTREQUEST_0_TAXAMT' => 0,
|
'PAYMENTREQUEST_0_TAXAMT' => 0,
|
||||||
@@ -277,7 +277,7 @@ class WCS_PayPal_Reference_Transaction_API_Request {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $this->skip_line_items( $order ) ) {
|
if ( $this->skip_line_items( $order, $order_items ) ) {
|
||||||
|
|
||||||
$total_amount = $this->round( $order->get_total() );
|
$total_amount = $this->round( $order->get_total() );
|
||||||
|
|
||||||
@@ -292,7 +292,6 @@ class WCS_PayPal_Reference_Transaction_API_Request {
|
|||||||
$item_names = array();
|
$item_names = array();
|
||||||
|
|
||||||
foreach ( $order_items as $item ) {
|
foreach ( $order_items as $item ) {
|
||||||
|
|
||||||
$item_names[] = sprintf( '%1$s x %2$s', $item['NAME'], $item['QTY'] );
|
$item_names[] = sprintf( '%1$s x %2$s', $item['NAME'], $item['QTY'] );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +305,7 @@ class WCS_PayPal_Reference_Transaction_API_Request {
|
|||||||
), 0, $use_deprecated_params );
|
), 0, $use_deprecated_params );
|
||||||
|
|
||||||
// add order-level parameters
|
// add order-level parameters
|
||||||
// - Do not sent the TAXAMT due to rounding errors
|
// - Do not send the TAXAMT due to rounding errors
|
||||||
if ( $use_deprecated_params ) {
|
if ( $use_deprecated_params ) {
|
||||||
$this->add_parameters( array(
|
$this->add_parameters( array(
|
||||||
'AMT' => $total_amount,
|
'AMT' => $total_amount,
|
||||||
@@ -334,7 +333,6 @@ class WCS_PayPal_Reference_Transaction_API_Request {
|
|||||||
|
|
||||||
// add individual order items
|
// add individual order items
|
||||||
foreach ( $order_items as $item ) {
|
foreach ( $order_items as $item ) {
|
||||||
|
|
||||||
$this->add_line_item_parameters( $item, $item_count++, $use_deprecated_params );
|
$this->add_line_item_parameters( $item, $item_count++, $use_deprecated_params );
|
||||||
$calculated_total += $this->round( $item['AMT'] * $item['QTY'] );
|
$calculated_total += $this->round( $item['AMT'] * $item['QTY'] );
|
||||||
}
|
}
|
||||||
@@ -370,11 +368,6 @@ class WCS_PayPal_Reference_Transaction_API_Request {
|
|||||||
'CUSTOM' => json_encode( array( 'order_id' => wcs_get_objects_property( $order, 'id' ), 'order_key' => wcs_get_objects_property( $order, 'order_key' ) ) ),
|
'CUSTOM' => json_encode( array( 'order_id' => wcs_get_objects_property( $order, 'id' ), 'order_key' => wcs_get_objects_property( $order, 'order_key' ) ) ),
|
||||||
) );
|
) );
|
||||||
}
|
}
|
||||||
|
|
||||||
// offset the discrepency between the WooCommerce cart total and PayPal's calculated total by adjusting the cost of the first item
|
|
||||||
if ( $total_amount !== $calculated_total ) {
|
|
||||||
$this->parameters['L_PAYMENTREQUEST_0_AMT0'] = $this->parameters['L_PAYMENTREQUEST_0_AMT0'] - ( $calculated_total - $total_amount );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,10 +626,27 @@ class WCS_PayPal_Reference_Transaction_API_Request {
|
|||||||
* @param WC_Order $order Optional. The WC_Order object. Default null.
|
* @param WC_Order $order Optional. The WC_Order object. Default null.
|
||||||
* @return bool true if line items should be skipped, false otherwise
|
* @return bool true if line items should be skipped, false otherwise
|
||||||
*/
|
*/
|
||||||
private function skip_line_items( $order = null ) {
|
private function skip_line_items( $order = null, $order_items = null ) {
|
||||||
|
|
||||||
$skip_line_items = wcs_get_objects_property( $order, 'prices_include_tax' );
|
$skip_line_items = wcs_get_objects_property( $order, 'prices_include_tax' );
|
||||||
|
|
||||||
|
// Also check actual totals add up just in case totals have been manually modified to amounts that can not round correctly, see https://github.com/Prospress/woocommerce-subscriptions/issues/2213
|
||||||
|
if ( true != $skip_line_items && ! is_null( $order ) && ! is_null( $order_items ) ) {
|
||||||
|
|
||||||
|
$calculated_total = 0;
|
||||||
|
|
||||||
|
foreach ( $order_items as $item ) {
|
||||||
|
$calculated_total += $this->round( $item['AMT'] * $item['QTY'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
$calculated_total += $this->round( $order->get_total_shipping() ) + $this->round( $order->get_total_tax() );
|
||||||
|
$total_amount = $this->round( $order->get_total() );
|
||||||
|
|
||||||
|
if ( $total_amount !== $calculated_total ) {
|
||||||
|
$skip_line_items = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter whether line items should be skipped or not
|
* Filter whether line items should be skipped or not
|
||||||
*
|
*
|
||||||
|
@@ -93,9 +93,34 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the IPN is for a cancellation after a failed payment on a PayPal Standard subscription created with Subscriptions < 2.0, the subscription won't be found, but that doesn't mean we should throw an exception, we should just ignore it
|
||||||
|
if ( empty( $subscription ) && in_array( $transaction_details['txn_type'], array( 'subscr_cancel', 'subscr_eot' ) ) ) {
|
||||||
|
|
||||||
|
// Check if the reason the subscription can't be found is because it has since been changed to a new PayPal Subscription and this IPN is for the cancellation after a renewal sign-up
|
||||||
|
$subscription_id_and_key = self::get_order_id_and_key( $transaction_details, 'shop_subscription', '_old_paypal_subscriber_id' );
|
||||||
|
|
||||||
|
if ( ! empty( $subscription_id_and_key['order_id'] ) ) {
|
||||||
|
WC_Gateway_Paypal::log( 'IPN subscription cancellation request ignored - new PayPal Profile ID linked to this subscription, for subscription ' . $subscription_id_and_key['order_id'] );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the IPN is for a suspension after a switch on a PayPal Standard subscription created with Subscriptions < 2.0, the subscription won't be found, but that doesn't mean we should throw an exception, we should just ignore it
|
||||||
|
if ( empty( $subscription ) && 'recurring_payment_suspended' === $transaction_details['txn_type'] ) {
|
||||||
|
|
||||||
|
// Check if the reason the subscription can't be found is because it has since been changed after a successful subscription switch
|
||||||
|
$subscription_id_and_key = self::get_order_id_and_key( $transaction_details, 'shop_subscription', '_switched_paypal_subscription_id' );
|
||||||
|
|
||||||
|
if ( ! empty( $subscription_id_and_key['order_id'] ) ) {
|
||||||
|
WC_Gateway_Paypal::log( 'IPN subscription suspension request ignored - subscription payment gateway changed via switch' . $subscription_id_and_key['order_id'] );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( empty( $subscription ) ) {
|
if ( empty( $subscription ) ) {
|
||||||
WC_Gateway_Paypal::log( 'Subscription IPN Error: Could not find matching Subscription.' );
|
$message = 'Subscription IPN Error: Could not find matching Subscription.'; // We dont' want this to be translated, we need it in English for support
|
||||||
exit;
|
WC_Gateway_Paypal::log( $message );
|
||||||
|
throw new Exception( $message );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $subscription->get_order_key() != $subscription_key ) {
|
if ( $subscription->get_order_key() != $subscription_key ) {
|
||||||
@@ -517,7 +542,7 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler {
|
|||||||
*
|
*
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public static function get_order_id_and_key( $args, $order_type = 'shop_order' ) {
|
public static function get_order_id_and_key( $args, $order_type = 'shop_order', $meta_key = '_paypal_subscription_id' ) {
|
||||||
|
|
||||||
$order_id = $order_key = '';
|
$order_id = $order_key = '';
|
||||||
|
|
||||||
@@ -536,7 +561,7 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler {
|
|||||||
'numberposts' => 1,
|
'numberposts' => 1,
|
||||||
'orderby' => 'ID',
|
'orderby' => 'ID',
|
||||||
'order' => 'ASC',
|
'order' => 'ASC',
|
||||||
'meta_key' => '_paypal_subscription_id',
|
'meta_key' => $meta_key,
|
||||||
'meta_value' => $subscription_id,
|
'meta_value' => $subscription_id,
|
||||||
'post_type' => $order_type,
|
'post_type' => $order_type,
|
||||||
'post_status' => 'any',
|
'post_status' => 'any',
|
||||||
@@ -574,12 +599,7 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // WC < 2.3.11, we could have a variety of payloads, but something has gone wrong if we got to here as we should only be here on new purchases where the '_paypal_subscription_id' is not already set, so throw an exception
|
} else { // WC < 2.3.11, we could have a variety of payloads, but something has gone wrong if we got to here as we should only be here on new purchases where the '_paypal_subscription_id' is not already set, so throw an exception
|
||||||
|
WC_Gateway_Paypal::log( __( 'Invalid PayPal IPN Payload: unable to find matching subscription.', 'woocommerce-subscriptions' ) );
|
||||||
$message = __( 'Invalid PayPal IPN Payload: unable to find matching subscription.', 'woocommerce-subscriptions' );
|
|
||||||
|
|
||||||
WC_Gateway_Paypal::log( $message );
|
|
||||||
|
|
||||||
throw new Exception( $message );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -168,6 +168,7 @@ class WCS_PayPal_Standard_Switcher {
|
|||||||
if ( ! wcs_is_paypal_profile_a( $paypal_id, 'billing_agreement' ) ) {
|
if ( ! wcs_is_paypal_profile_a( $paypal_id, 'billing_agreement' ) ) {
|
||||||
update_post_meta( $order_id, '_old_payment_method', 'paypal_standard' );
|
update_post_meta( $order_id, '_old_payment_method', 'paypal_standard' );
|
||||||
update_post_meta( $order_id, '_old_paypal_subscription_id', $paypal_id );
|
update_post_meta( $order_id, '_old_paypal_subscription_id', $paypal_id );
|
||||||
|
update_post_meta( $subscription->get_id(), '_switched_paypal_subscription_id', $paypal_id );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -41,7 +41,7 @@ function wcs_cart_totals_shipping_html() {
|
|||||||
foreach ( WC()->cart->recurring_carts as $recurring_cart_key => $recurring_cart ) {
|
foreach ( WC()->cart->recurring_carts as $recurring_cart_key => $recurring_cart ) {
|
||||||
|
|
||||||
// Create shipping packages for each subscription item
|
// Create shipping packages for each subscription item
|
||||||
if ( WC_Subscriptions_Cart::cart_contains_subscriptions_needing_shipping() && 0 !== $recurring_cart->next_payment_date ) {
|
if ( WC_Subscriptions_Cart::cart_contains_subscriptions_needing_shipping( $recurring_cart ) && 0 !== $recurring_cart->next_payment_date ) {
|
||||||
|
|
||||||
// This will get a package with the 'recurring_cart_key' set to 'none' (because WC_Subscriptions_Cart::display_recurring_totals() set WC_Subscriptions_Cart::$calculation_type to 'recurring_total', but WC_Subscriptions_Cart::$recurring_cart_key has not been set), which ensures that it's a unique package, which we need in order to get all the available packages, not just the package for the recurring cart calculation we completed previously where WC_Subscriptions_Cart::filter_package_rates() removed all unchosen rates and which WC then cached
|
// This will get a package with the 'recurring_cart_key' set to 'none' (because WC_Subscriptions_Cart::display_recurring_totals() set WC_Subscriptions_Cart::$calculation_type to 'recurring_total', but WC_Subscriptions_Cart::$recurring_cart_key has not been set), which ensures that it's a unique package, which we need in order to get all the available packages, not just the package for the recurring cart calculation we completed previously where WC_Subscriptions_Cart::filter_package_rates() removed all unchosen rates and which WC then cached
|
||||||
$packages = $recurring_cart->get_shipping_packages();
|
$packages = $recurring_cart->get_shipping_packages();
|
||||||
|
@@ -218,7 +218,7 @@ function wcs_get_objects_property( $object, $property, $single = 'single', $defa
|
|||||||
* @param string $key The meta key name without '_' prefix
|
* @param string $key The meta key name without '_' prefix
|
||||||
* @param mixed $value The data to set as the value of the meta
|
* @param mixed $value The data to set as the value of the meta
|
||||||
* @param string $save Whether to write the data to the database or not. Use 'save' to write to the database, anything else to only update it in memory.
|
* @param string $save Whether to write the data to the database or not. Use 'save' to write to the database, anything else to only update it in memory.
|
||||||
* @param int $meta_id The meta ID of exiting meta data if you wish to overwrite an existing piece of meta.
|
* @param int $meta_id The meta ID of existing meta data if you wish to overwrite an existing piece of meta.
|
||||||
* @param string $prefix_meta_key Whether the key should be prefixed with an '_' when stored in meta. Defaulted to 'prefix_meta_key', pass any other value to bypass automatic prefixing (optional)
|
* @param string $prefix_meta_key Whether the key should be prefixed with an '_' when stored in meta. Defaulted to 'prefix_meta_key', pass any other value to bypass automatic prefixing (optional)
|
||||||
* @since 2.2.0
|
* @since 2.2.0
|
||||||
* @return mixed
|
* @return mixed
|
||||||
|
@@ -546,6 +546,24 @@ function wcs_get_order_item( $item_id, $order ) {
|
|||||||
return $item;
|
return $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper for wc_update_order_item() which consistently deletes the cached item after update, unlike WC.
|
||||||
|
*
|
||||||
|
* @param int $item_id The ID of an order item
|
||||||
|
* @param string $new_type The new type to set as the 'order_item_type' value on the order item.
|
||||||
|
* @param int $order_or_subscription_id The order or subscription ID the line item belongs to - optional. Deletes the order item cache if provided.
|
||||||
|
* @since 2.2.12
|
||||||
|
*/
|
||||||
|
function wcs_update_order_item_type( $item_id, $new_type, $order_or_subscription_id = 0 ) {
|
||||||
|
wc_update_order_item( $item_id, array( 'order_item_type' => $new_type ) );
|
||||||
|
wp_cache_delete( 'item-' . $item_id, 'order-items' );
|
||||||
|
|
||||||
|
// When possible, also clear the order items' cache for the object to which this item relates (double cache :sob:)
|
||||||
|
if ( ! empty( $order_or_subscription_id ) ) {
|
||||||
|
wp_cache_delete( 'order-items-' . $order_or_subscription_id, 'orders' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an instance of WC_Order_Item_Meta for an order item
|
* Get an instance of WC_Order_Item_Meta for an order item
|
||||||
*
|
*
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
<div class="variable_subscription_trial variable_subscription_pricing_2_3 show_if_variable-subscription variable_subscription_trial_sign_up">
|
<div class="variable_subscription_trial variable_subscription_pricing_2_3 show_if_variable-subscription variable_subscription_trial_sign_up">
|
||||||
<p class="form-row form-row-first form-field show_if_variable-subscription sign-up-fee-cell">
|
<p class="form-row form-row-first form-field show_if_variable-subscription sign-up-fee-cell">
|
||||||
<label for="variable_subscription_sign_up_fee[<?php echo esc_attr( $loop ); ?>]"><?php printf( esc_html__( 'Sign-up fee (%s)', 'woocommerce-subscriptions' ), esc_html( get_woocommerce_currency_symbol() ) ); ?></label>
|
<label for="variable_subscription_sign_up_fee[<?php echo esc_attr( $loop ); ?>]"><?php printf( esc_html__( 'Sign-up fee (%s)', 'woocommerce-subscriptions' ), esc_html( get_woocommerce_currency_symbol() ) ); ?></label>
|
||||||
<input type="text" class="wc_input_price wc_input_subscription_intial_price" name="variable_subscription_sign_up_fee[<?php echo esc_attr( $loop ); ?>]" value="<?php echo esc_attr( WC_Subscriptions_Product::get_sign_up_fee( $variation_product ) ); ?>" placeholder="<?php echo esc_attr_x( 'e.g. 9.90', 'example price', 'woocommerce-subscriptions' ); ?>">
|
<input type="text" class="wc_input_price wc_input_subscription_intial_price" name="variable_subscription_sign_up_fee[<?php echo esc_attr( $loop ); ?>]" value="<?php echo esc_attr( wc_format_localized_price( WC_Subscriptions_Product::get_sign_up_fee( $variation_product ) ) ); ?>" placeholder="<?php echo esc_attr_x( 'e.g. 9.90', 'example price', 'woocommerce-subscriptions' ); ?>">
|
||||||
</p>
|
</p>
|
||||||
<p class="form-row form-row-last show_if_variable-subscription">
|
<p class="form-row form-row-last show_if_variable-subscription">
|
||||||
<label for="variable_subscription_trial_length[<?php echo esc_attr( $loop ); ?>]">
|
<label for="variable_subscription_trial_length[<?php echo esc_attr( $loop ); ?>]">
|
||||||
@@ -42,7 +42,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
printf( esc_html__( 'Subscription price (%s)', 'woocommerce-subscriptions' ), esc_html( get_woocommerce_currency_symbol() ) );
|
printf( esc_html__( 'Subscription price (%s)', 'woocommerce-subscriptions' ), esc_html( get_woocommerce_currency_symbol() ) );
|
||||||
?>
|
?>
|
||||||
</label>
|
</label>
|
||||||
<input type="text" class="wc_input_price wc_input_subscription_price" name="variable_subscription_price[<?php echo esc_attr( $loop ); ?>]" value="<?php echo esc_attr( WC_Subscriptions_Product::get_price( $variation_product ) ); ?>" placeholder="<?php echo esc_attr_x( 'e.g. 9.90', 'example price', 'woocommerce-subscriptions' ); ?>">
|
<input type="text" class="wc_input_price wc_input_subscription_price" name="variable_subscription_price[<?php echo esc_attr( $loop ); ?>]" value="<?php echo esc_attr( wc_format_localized_price( WC_Subscriptions_Product::get_regular_price( $variation_product ) ) ); ?>" placeholder="<?php echo esc_attr_x( 'e.g. 9.90', 'example price', 'woocommerce-subscriptions' ); ?>">
|
||||||
|
|
||||||
<label for="variable_subscription_period_interval[<?php echo esc_attr( $loop ); ?>]" class="wcs_hidden_label"><?php esc_html_e( 'Billing interval:', 'woocommerce-subscriptions' ); ?></label>
|
<label for="variable_subscription_period_interval[<?php echo esc_attr( $loop ); ?>]" class="wcs_hidden_label"><?php esc_html_e( 'Billing interval:', 'woocommerce-subscriptions' ); ?></label>
|
||||||
<select name="variable_subscription_period_interval[<?php echo esc_attr( $loop ); ?>]" class="wc_input_subscription_period_interval">
|
<select name="variable_subscription_period_interval[<?php echo esc_attr( $loop ); ?>]" class="wc_input_subscription_period_interval">
|
||||||
|
@@ -35,7 +35,7 @@ foreach ( $subscriptions as $subscription ) {
|
|||||||
do_action( 'woocommerce_subscriptions_email_order_details', $subscription, $sent_to_admin, $plain_text, $email );
|
do_action( 'woocommerce_subscriptions_email_order_details', $subscription, $sent_to_admin, $plain_text, $email );
|
||||||
}
|
}
|
||||||
|
|
||||||
add_filter( 'woocommerce_order_item_meta_end', 'WC_Subscriptions_Switcher::print_switch_link', 10 );
|
add_filter( 'woocommerce_order_item_meta_end', 'WC_Subscriptions_Switcher::print_switch_link', 10, 3 );
|
||||||
echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
|
echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
|
||||||
|
|
||||||
do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email );
|
do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email );
|
||||||
|
@@ -31,6 +31,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<?php /** @var WC_Subscription $subscription */ ?>
|
||||||
<?php foreach ( $subscriptions as $subscription_id => $subscription ) : ?>
|
<?php foreach ( $subscriptions as $subscription_id => $subscription ) : ?>
|
||||||
<tr class="order">
|
<tr class="order">
|
||||||
<td class="subscription-id order-number" data-title="<?php esc_attr_e( 'ID', 'woocommerce-subscriptions' ); ?>">
|
<td class="subscription-id order-number" data-title="<?php esc_attr_e( 'ID', 'woocommerce-subscriptions' ); ?>">
|
||||||
|
@@ -54,7 +54,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
<td class="order-actions">
|
<td class="order-actions">
|
||||||
<?php $actions = array();
|
<?php $actions = array();
|
||||||
|
|
||||||
if ( in_array( $order->get_status(), apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'failed' ), $order ) ) && wcs_get_objects_property( $order, 'id' ) == $subscription->get_last_order() ) {
|
if ( in_array( $order->get_status(), apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'failed' ), $order ) ) && wcs_get_objects_property( $order, 'id' ) == $subscription->get_last_order( 'ids', 'any' ) ) {
|
||||||
$actions['pay'] = array(
|
$actions['pay'] = array(
|
||||||
'url' => $order->get_checkout_payment_url(),
|
'url' => $order->get_checkout_payment_url(),
|
||||||
'name' => esc_html_x( 'Pay', 'pay for a subscription', 'woocommerce-subscriptions' ),
|
'name' => esc_html_x( 'Pay', 'pay for a subscription', 'woocommerce-subscriptions' ),
|
||||||
|
@@ -411,7 +411,7 @@ function wcs_get_subscriptions( $args ) {
|
|||||||
'product_id' => 0,
|
'product_id' => 0,
|
||||||
'variation_id' => 0,
|
'variation_id' => 0,
|
||||||
'order_id' => 0,
|
'order_id' => 0,
|
||||||
'subscription_status' => 'any',
|
'subscription_status' => array( 'any' ),
|
||||||
'meta_query_relation' => 'AND',
|
'meta_query_relation' => 'AND',
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -421,9 +421,21 @@ function wcs_get_subscriptions( $args ) {
|
|||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure subscription_status is an array.
|
||||||
|
$args['subscription_status'] = $args['subscription_status'] ? (array) $args['subscription_status'] : array();
|
||||||
|
|
||||||
|
// Grab the native post stati, removing pending and adding any.
|
||||||
|
$builtin = get_post_stati( array( '_builtin' => true ) );
|
||||||
|
unset( $builtin['pending'] );
|
||||||
|
$builtin['any'] = 'any';
|
||||||
|
|
||||||
// Make sure status starts with 'wc-'
|
// Make sure status starts with 'wc-'
|
||||||
if ( ! in_array( $args['subscription_status'], array( 'any', 'trash' ) ) ) {
|
foreach ( $args['subscription_status'] as &$status ) {
|
||||||
$args['subscription_status'] = wcs_sanitize_subscription_status_key( $args['subscription_status'] );
|
if ( isset( $builtin[ $status ] ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = wcs_sanitize_subscription_status_key( $status );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the args for WP_Query
|
// Prepare the args for WP_Query
|
||||||
@@ -515,7 +527,7 @@ function wcs_get_subscriptions_for_product( $product_ids, $fields = 'ids' ) {
|
|||||||
|
|
||||||
// If we have an array of IDs, convert them to a comma separated list and sanatise them to make sure they're all integers
|
// If we have an array of IDs, convert them to a comma separated list and sanatise them to make sure they're all integers
|
||||||
if ( is_array( $product_ids ) ) {
|
if ( is_array( $product_ids ) ) {
|
||||||
$ids_for_query = implode( "', '", array_map( 'absint', array_unique( $product_ids ) ) );
|
$ids_for_query = implode( "', '", array_map( 'absint', array_unique( array_filter( $product_ids ) ) ) );
|
||||||
} else {
|
} else {
|
||||||
$ids_for_query = absint( $product_ids );
|
$ids_for_query = absint( $product_ids );
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
* Description: Sell products and services with recurring payments in your WooCommerce Store.
|
* Description: Sell products and services with recurring payments in your WooCommerce Store.
|
||||||
* Author: Prospress Inc.
|
* Author: Prospress Inc.
|
||||||
* Author URI: http://prospress.com/
|
* Author URI: http://prospress.com/
|
||||||
* Version: 2.2.11
|
* Version: 2.2.13
|
||||||
*
|
*
|
||||||
* Woo: 27147:6115e6d7e297b623a169fdcf5728b224
|
* Woo: 27147:6115e6d7e297b623a169fdcf5728b224
|
||||||
*
|
*
|
||||||
@@ -128,12 +128,13 @@ class WC_Subscriptions {
|
|||||||
|
|
||||||
public static $plugin_file = __FILE__;
|
public static $plugin_file = __FILE__;
|
||||||
|
|
||||||
public static $version = '2.2.11';
|
public static $version = '2.2.13';
|
||||||
|
|
||||||
private static $total_subscription_count = null;
|
private static $total_subscription_count = null;
|
||||||
|
|
||||||
private static $scheduler;
|
private static $scheduler;
|
||||||
|
|
||||||
|
/** @var WCS_Cache_Manager */
|
||||||
public static $cache;
|
public static $cache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -272,7 +273,7 @@ class WC_Subscriptions {
|
|||||||
* will appear. If that's empty, the long, explanatory one will appear in the table.
|
* will appear. If that's empty, the long, explanatory one will appear in the table.
|
||||||
*
|
*
|
||||||
* Filters:
|
* Filters:
|
||||||
* - woocommerce_subscriptions_not_empty: gets passed the option value. false or 'yes'. 'yes' means the subscriptions
|
* - woocommerce_subscriptions_not_empty: gets passed the boolean option value. 'true' means the subscriptions
|
||||||
* list is not empty, the user is familiar with how it works, and standard message appears.
|
* list is not empty, the user is familiar with how it works, and standard message appears.
|
||||||
* - woocommerce_subscriptions_not_found_label: gets the original message for other plugins to modify, in case
|
* - woocommerce_subscriptions_not_found_label: gets the original message for other plugins to modify, in case
|
||||||
* they want to add more links, or modify any of the messages.
|
* they want to add more links, or modify any of the messages.
|
||||||
@@ -281,7 +282,8 @@ class WC_Subscriptions {
|
|||||||
* @return string what appears in the list table of the subscriptions
|
* @return string what appears in the list table of the subscriptions
|
||||||
*/
|
*/
|
||||||
private static function get_not_found_text() {
|
private static function get_not_found_text() {
|
||||||
if ( true === apply_filters( 'woocommerce_subscriptions_not_empty', wcs_do_subscriptions_exist() ) ) {
|
$subscriptions_exist = self::$cache->cache_and_get( 'wcs_do_subscriptions_exist', 'wcs_do_subscriptions_exist' );
|
||||||
|
if ( true === apply_filters( 'woocommerce_subscriptions_not_empty', $subscriptions_exist ) ) {
|
||||||
$not_found_text = __( 'No Subscriptions found', 'woocommerce-subscriptions' );
|
$not_found_text = __( 'No Subscriptions found', 'woocommerce-subscriptions' );
|
||||||
} else {
|
} else {
|
||||||
$not_found_text = '<p>' . __( 'Subscriptions will appear here for you to view and manage once purchased by a customer.', 'woocommerce-subscriptions' ) . '</p>';
|
$not_found_text = '<p>' . __( 'Subscriptions will appear here for you to view and manage once purchased by a customer.', 'woocommerce-subscriptions' ) . '</p>';
|
||||||
@@ -1023,7 +1025,7 @@ class WC_Subscriptions {
|
|||||||
$plugin_links = array(
|
$plugin_links = array(
|
||||||
'<a href="' . WC_Subscriptions_Admin::settings_tab_url() . '">' . __( 'Settings', 'woocommerce-subscriptions' ) . '</a>',
|
'<a href="' . WC_Subscriptions_Admin::settings_tab_url() . '">' . __( 'Settings', 'woocommerce-subscriptions' ) . '</a>',
|
||||||
'<a href="http://docs.woocommerce.com/document/subscriptions/">' . _x( 'Docs', 'short for documents', 'woocommerce-subscriptions' ) . '</a>',
|
'<a href="http://docs.woocommerce.com/document/subscriptions/">' . _x( 'Docs', 'short for documents', 'woocommerce-subscriptions' ) . '</a>',
|
||||||
'<a href="https://www.woocommerce.com/my-account/create-a-ticket/">' . __( 'Support', 'woocommerce-subscriptions' ) . '</a>',
|
'<a href="https://woocommerce.com/my-account/marketplace-ticket-form/">' . __( 'Support', 'woocommerce-subscriptions' ) . '</a>',
|
||||||
);
|
);
|
||||||
|
|
||||||
return array_merge( $plugin_links, $links );
|
return array_merge( $plugin_links, $links );
|
||||||
@@ -1144,7 +1146,7 @@ class WC_Subscriptions {
|
|||||||
|
|
||||||
echo '<div class="update-nag">';
|
echo '<div class="update-nag">';
|
||||||
echo sprintf( esc_html__( '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.', 'woocommerce-subscriptions' ), esc_html( self::$version ) ) . '<br />';
|
echo sprintf( esc_html__( '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.', 'woocommerce-subscriptions' ), esc_html( self::$version ) ) . '<br />';
|
||||||
echo sprintf( esc_html__( 'Please upgrade the WooCommerce Subscriptions plugin to version 2.0 or newer immediately. If you need assistance, after upgrading to Subscriptions v2.0, please %sopen a support ticket%s.', 'woocommerce-subscriptions' ), '<a href="https://www.woocommerce.com/my-account/create-a-ticket/">', '</a>' );
|
echo sprintf( esc_html__( 'Please upgrade the WooCommerce Subscriptions plugin to version 2.0 or newer immediately. If you need assistance, after upgrading to Subscriptions v2.0, please %sopen a support ticket%s.', 'woocommerce-subscriptions' ), '<a href="https://woocommerce.com/my-account/marketplace-ticket-form/">', '</a>' );
|
||||||
echo '</div> ';
|
echo '</div> ';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user