diff --git a/changelog.txt b/changelog.txt index cc2629d..c01c596 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,45 @@ *** WooCommerce Subscriptions Changelog *** +2017.07.20 - version 2.2.10 +* Fix: Do not attempt to call WC 3.0 only method when WC 2.6 is active by fixing version check from 2.6 to 3.0. Props @lkraav. PR#2236 +* Fix: Display "Limit Subscription" option on Edit Product screen even when Reviews are disabled. PR#2233 +* Fix: Hide "from" price html string if all variations have identical price and billing schedule. PR#2142 +* Fix: Guard against proration miscalculation in double switching within one billing period. PR#2225 +* Tweak: Log old version and WooCommerce versions on update. PR#2230 + +2017.07.12 - version 2.2.9 +* Fix: WooCommerce 3.1: Notices when loading the WooCommerce > Subscriptions administration page. PR#2206 +* Fix: WooCommerce 3.1: Notices on cart redrection hook becuase of use of 'add_to_cart_fragments' hook. PR#2221 +* Fix: WooCommerce 3.0: Fix download links in first (processing order) email. PR#2185 +* Fix: WooCommerce 3.0: Repair subscriptions with missing _contains_synced_subscription meta not set when WooCommerce 3.0 was active with Subscriptions 2.2.0 through to 2.2.7. PR#2183 +* Fix: WooCommerce 3.0: Fix inactive role being assigned to users when subscriptions are suspended with WooCommerce 3.0. PR#2195 +* Tweak: New hook to allow feees to be modified on renewals and resubscribes: 'woocommerce_adjust_order_fees_for_setup_cart_for_renewal'. PR#2208 +* Tweak: Truncate cache files after a threshold (50MB) to avoid growing indefinitely. PR#1774 +* Fix: Set default recurring shipping method when initial shipping method is not available in the recurring options. PR#2214 +* Fix: Get active price instead of only subscription price product meta to honour sale prices. Fixes prorated switching costs as well as incorrect price being displayed for variable products where one variation was cheaped because it was on sale. PR#2215 +* Fix: Allow payment on only most recent order relating to a subscription that is pending payment to avoid assorted issues stemming from paying for older orders. PR#2209 +* Fix: Remove old PayPal IPN payload handling to match changes in WooCommerce 3.1.1 and fix potential exploit. PR#2220 +* Fix: Fix default value for product limitations for products which do not have the product limitation value saved or set on them. PR#2219 +* Fix: Ignore URL scheme in duplicate site check to avoid incorrectly identifying site as a staging site with v2.2.8 when running on a site with WP_SITEURL set, but being overridden, either by a host like WP Engine, or by some other constant, like FORCE_SSL_ADMIN. PR#2227 + +2017.06.22 - version 2.2.8 +* Fix: WooCommerce 3.0: Update use of deprecated 'woocommerce_stock_html' filter by using new wc_get_stock_html() method. PR#2175 & PR#2193 +* Fix: Make sure subscriptions suspended at PayPal.com are also correctly suspended in WooCommerce. PR#2199 +* Fix: Do not display manual gateways when paying for failed renewal with an automatic retry. PR#2177 +* Fix: Compare the site URL stored by Subscriptions against the WP_SITEURL constant when that constant is set, instead of always comparing against the 'siteurl' option value. PR#2167 +* Fix: Apply discounts from renewal orders rather than subscriptions for renewal carts so that any code or manual application of a discount to the renewal order is applied correctly. PR#2156 +* Fix: Do not cancel auto-draft subscriptions before post deletion as they do not need to be cancelled and cancellation emails can cause confusion. PR#2173 +* Fix: Add 3rd param to 'woocommerce_order_item_name' filter calls to make sure they match parameters on WooCommerce filters. PR#2159 +* Fix: Only return parent product IDs if a parent product exists. Fixes broken switch URLs for grouped products. PR#2160 +* Fix: Update subscription payment method when renewal has been paid and has pending status and retry. PR#2141 +* Fix: Make sure trial calculations for synced variation products are correct with PayPal Standard. PR#2168 +* Fix: Set correct PHPDoc for returned type in WC_Subscription::get_parent(). Props @davefx. PR#2169 +* Fix: Set correct PHPDoc return type in wcs-renewal-functions.php. PR#2198 +* Fix: Copy custom, non-prefixed, subscription meta data to renewal orders. PR#2157 +* Fix: Take sale price into account when displaying "From" price for variable products. PR#2176 +* Fix: Take sale price into account when switching. PR#2171 +* Fix: Fix undefined var $switched_subscriptions. PR#2192 + 2017.05.26 - version 2.2.7 * Tweak: Integrate with My Account > Payment Methods actions to make sure when a customer deletes a payment method, subscriptions using that payment method are automatically updated to use a different token. Or if there are no other payment methods on the customer's account, a customer can't delete the payment method used for automatic payments. PR#1866 * Tweak: Do not display 'Free' on free shipping methods to improve compatibility with the approached used in WooCommerce 2.6 and newer. PR#1766 diff --git a/includes/admin/class-wcs-admin-post-types.php b/includes/admin/class-wcs-admin-post-types.php index fd9f76c..d410692 100644 --- a/includes/admin/class-wcs-admin-post-types.php +++ b/includes/admin/class-wcs-admin-post-types.php @@ -564,70 +564,15 @@ class WCS_Admin_Post_Types { break; case 1 : foreach ( $subscription_items as $item ) { - $_product = apply_filters( 'woocommerce_order_item_product', $the_subscription->get_product_from_item( $item ), $item ); - $item_meta = wcs_get_order_item_meta( $item, $_product ); - $item_meta_html = $item_meta->display( true, true ); - $item_quantity = absint( $item['qty'] ); - - $item_name = ''; - if ( wc_product_sku_enabled() && $_product && $_product->get_sku() ) { - $item_name .= $_product->get_sku() . ' - '; - } - - $item_name .= apply_filters( 'woocommerce_order_item_name', $item['name'], $item ); - $item_name = esc_html( $item_name ); - - if ( $item_quantity > 1 ) { - $item_name = sprintf( '%s × %s', absint( $item_quantity ), $item_name ); - } - if ( $_product ) { - $item_name = sprintf( '%s', get_edit_post_link( ( $_product->is_type( 'variation' ) ) ? wcs_get_objects_property( $_product, 'parent_id' ) : $_product->get_id() ), $item_name ); - } - - $column_content .= '
'; - $column_content .= wp_kses( $item_name, array( 'a' => array( 'href' => array() ) ) ); - - if ( $item_meta_html ) { - $column_content .= wcs_help_tip( $item_meta_html ); - } - - $column_content .= '
'; + $column_content .= self::get_item_display( $item, $the_subscription ); } break; default : $column_content .= '' . esc_html( apply_filters( 'woocommerce_admin_order_item_count', sprintf( _n( '%d item', '%d items', $the_subscription->get_item_count(), 'woocommerce-subscriptions' ), $the_subscription->get_item_count() ), $the_subscription ) ) . ''; $column_content .= ''; - foreach ( $the_subscription->get_items() as $item ) { - $_product = apply_filters( 'woocommerce_order_item_product', $the_subscription->get_product_from_item( $item ), $item ); - $item_meta = wcs_get_order_item_meta( $item, $_product ); - $item_meta_html = $item_meta->display( true, true ); - ob_start(); - ?> - - - - - '; @@ -1018,6 +963,135 @@ class WCS_Admin_Post_Types { return $actions; } + + /** + * Get the HTML for an order item to display on the Subscription list table. + * + * @param array $actions + * @param object $post + * @return array + */ + protected static function get_item_display( $item, $the_subscription, $element = 'div' ) { + + $_product = apply_filters( 'woocommerce_order_item_product', $the_subscription->get_product_from_item( $item ), $item ); + $item_meta_html = self::get_item_meta_html( $item, $_product ); + + if ( 'div' === $element ) { + $item_html = self::get_item_display_div( $item, self::get_item_name_html( $item, $_product ), $item_meta_html ); + } else { + $item_html = self::get_item_display_row( $item, self::get_item_name_html( $item, $_product, 'do_not_include_quantity' ), $item_meta_html ); + } + + return $item_html; + } + + /** + * Get the HTML for order item meta to display on the Subscription list table. + * + * @param WC_Order_Item $item + * @param WC_Product $product + * @return string + */ + protected static function get_item_meta_html( $item, $_product ) { + + if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) { + $item_meta = wcs_get_order_item_meta( $item, $_product ); + $item_meta_html = $item_meta->display( true, true ); + } else { + $item_meta_html = wc_display_item_meta( $item, array( + 'before' => '', + 'after' => '', + 'separator' => '\n', + 'echo' => false, + ) ); + } + + return $item_meta_html; + } + + /** + * Get the HTML for order item meta to display on the Subscription list table. + * + * @param WC_Order_Item $item + * @param WC_Product $product + * @return string + */ + protected static function get_item_name_html( $item, $_product, $include_quantity = 'include_quantity' ) { + + $item_quantity = absint( $item['qty'] ); + + $item_name = ''; + + if ( wc_product_sku_enabled() && $_product && $_product->get_sku() ) { + $item_name .= $_product->get_sku() . ' - '; + } + + $item_name .= apply_filters( 'woocommerce_order_item_name', $item['name'], $item, false ); + $item_name = esc_html( $item_name ); + + if ( 'include_quantity' === $include_quantity && $item_quantity > 1 ) { + $item_name = sprintf( '%s × %s', absint( $item_quantity ), $item_name ); + } + + if ( $_product ) { + $item_name = sprintf( '%s', get_edit_post_link( ( $_product->is_type( 'variation' ) ) ? wcs_get_objects_property( $_product, 'parent_id' ) : $_product->get_id() ), $item_name ); + } + + return $item_name; + } + + /** + * Get the HTML for order item to display on the Subscription list table using a div element + * as the wrapper, which is done for subscriptions with a single line item. + * + * @param array $actions + * @param object $post + * @return array + */ + protected static function get_item_display_div( $item, $item_name, $item_meta_html ) { + + $item_html = '
'; + $item_html .= wp_kses( $item_name, array( 'a' => array( 'href' => array() ) ) ); + + if ( $item_meta_html ) { + $item_html .= wcs_help_tip( $item_meta_html ); + } + + $item_html .= '
'; + + return $item_html; + } + + /** + * Get the HTML for order item to display on the Subscription list table using a table element + * as the wrapper, which is done for subscriptions with multilpe line items. + * + * @param array $actions + * @param object $post + * @return array + */ + protected static function get_item_display_row( $item, $item_name, $item_meta_html ) { + + ob_start(); + ?> + + + + + $price ) ) : wcs_get_price_excluding_tax( $this, array( 'price' => $price ) ); - $price = apply_filters( 'woocommerce_variable_price_html', wcs_get_price_html_from_text( $this ) . wc_price( $price ) . $this->get_price_suffix(), $this ); + $price = $this->get_price_prefix( $prices ) . wc_price( $price ) . $this->get_price_suffix(); + $price = apply_filters( 'woocommerce_variable_price_html', $price, $this ); $price = WC_Subscriptions_Product::get_price_string( $this, array( 'price' => $price ) ); return apply_filters( 'woocommerce_variable_subscription_price_html', apply_filters( 'woocommerce_get_price_html', $price, $this ), $this ); @@ -226,4 +227,28 @@ class WC_Product_Variable_Subscription extends WC_Product_Variable { // Sync prices with children self::sync( $product_id ); } + + /** + * Get the suffix to display before prices. + * + * @return string + */ + protected function get_price_prefix( $prices ) { + + // Are the subscription details of all variations identical? + $child_variation_ids = array_keys( $prices['price'] ); + $variation_hash = md5( json_encode( $child_variation_ids ) ); + + if ( empty( $this->min_max_variation_data[ $variation_hash ] ) ) { + $this->min_max_variation_data[ $variation_hash ] = wcs_get_min_max_variation_data( $this, $child_variation_ids ); + } + + if ( $this->min_max_variation_data[ $variation_hash ]['identical'] ) { + $prefix = ''; + } else { + $prefix = wcs_get_price_html_from_text( $this ); + } + + return $prefix; + } } diff --git a/includes/class-wc-subscription.php b/includes/class-wc-subscription.php index 0e1a7bf..8f9a13b 100644 --- a/includes/class-wc-subscription.php +++ b/includes/class-wc-subscription.php @@ -480,7 +480,6 @@ class WC_Subscription extends WC_Order { case 'on-hold' : // Record date of suspension - 'post_modified' column? $this->set_suspension_count( $this->get_suspension_count() + 1 ); - wcs_maybe_make_user_inactive( $this->get_user_id() ); break; case 'cancelled' : case 'switched' : @@ -498,7 +497,6 @@ class WC_Subscription extends WC_Order { } $this->update_dates( $dates_to_update ); - wcs_maybe_make_user_inactive( $this->get_user_id() ); break; } @@ -1790,7 +1788,7 @@ class WC_Subscription extends WC_Order { /** * Get parent order object. * - * @return int + * @return WC_Order */ public function get_parent() { return wc_get_order( $this->get_parent_id() ); @@ -2047,7 +2045,15 @@ class WC_Subscription extends WC_Order { * @return bool */ public function is_download_permitted() { - return apply_filters( 'woocommerce_order_is_download_permitted', ( $this->has_status( 'active' ) || $this->has_status( 'pending-cancel' ) ), $this ); + $sending_email = did_action( 'woocommerce_email_before_order_table' ) > did_action( 'woocommerce_email_after_order_table' ); + $is_download_permitted = $this->has_status( 'active' ) || $this->has_status( 'pending-cancel' ); + + // WC Emails are sent before the subscription status is updated to active etc. so we need a way to ensure download links are added to the emails before being sent + if ( $sending_email && ! $is_download_permitted ) { + $is_download_permitted = true; + } + + return apply_filters( 'woocommerce_order_is_download_permitted', $is_download_permitted, $this ); } /** diff --git a/includes/class-wc-subscriptions-coupon.php b/includes/class-wc-subscriptions-coupon.php index 9689995..bc304c9 100644 --- a/includes/class-wc-subscriptions-coupon.php +++ b/includes/class-wc-subscriptions-coupon.php @@ -48,6 +48,8 @@ class WC_Subscriptions_Coupon { // WC 3.0 only sets a coupon type if it is a pre-defined supported type, so we need to temporarily add our pseudo types. We don't want to add these on admin pages. add_filter( 'woocommerce_coupon_discount_types', __CLASS__ . '::add_pseudo_coupon_types' ); } + + add_filter( 'woocommerce_cart_totals_coupon_label', __CLASS__ . '::get_pseudo_coupon_label', 10, 2 ); } /** @@ -441,14 +443,14 @@ class WC_Subscriptions_Coupon { $subtotal = 0; - foreach ( $renewal_coupons as $subscription_id => $coupons ) { + foreach ( $renewal_coupons as $order_id => $coupons ) { foreach ( $coupons as $coupon_code => $coupon_properties ) { if ( $coupon_code == $code ) { - if ( $subscription = wcs_get_subscription( $subscription_id ) ) { - $subtotal = $subscription->get_subtotal(); + if ( $order = wc_get_order( $order_id ) ) { + $subtotal = $order->get_subtotal(); } break; } @@ -507,6 +509,24 @@ class WC_Subscriptions_Coupon { ); } + /** + * Filter the default coupon cart label for renewal pseudo coupons + * + * @param string $label + * @param WC_Coupon $coupon + * @return string + * @since 2.2.8 + */ + public static function get_pseudo_coupon_label( $label, $coupon ) { + + // If the coupon is one of our pseudo coupons, rather than displaying "Coupon: discount_renewal" display a nicer label. + if ( 'renewal_cart' === wcs_get_coupon_property( $coupon, 'discount_type' ) ) { + $label = esc_html( __( 'Renewal Discount', 'woocommerce-subscriptions' ) ); + } + + return $label; + } + /* Deprecated */ /** diff --git a/includes/class-wc-subscriptions-manager.php b/includes/class-wc-subscriptions-manager.php index ef8bf8d..8edbcca 100644 --- a/includes/class-wc-subscriptions-manager.php +++ b/includes/class-wc-subscriptions-manager.php @@ -779,7 +779,7 @@ class WC_Subscriptions_Manager { */ public static function maybe_cancel_subscription( $post_id ) { - if ( 'shop_subscription' == get_post_type( $post_id ) ) { + if ( 'shop_subscription' == get_post_type( $post_id ) && 'auto-draft' !== get_post_status( $post_id ) ) { $subscription = wcs_get_subscription( $post_id ); diff --git a/includes/class-wc-subscriptions-order.php b/includes/class-wc-subscriptions-order.php index 8363b7d..772e497 100644 --- a/includes/class-wc-subscriptions-order.php +++ b/includes/class-wc-subscriptions-order.php @@ -65,6 +65,8 @@ class WC_Subscriptions_Order { add_action( 'woocommerce_subscription_details_after_subscription_table', __CLASS__ . '::get_related_orders_template', 10, 1 ); + add_filter( 'woocommerce_my_account_my_orders_actions', __CLASS__ . '::maybe_remove_pay_action', 10, 2 ); + add_action( 'woocommerce_order_partially_refunded', __CLASS__ . '::maybe_cancel_subscription_on_partial_refund' ); add_action( 'woocommerce_order_fully_refunded', __CLASS__ . '::maybe_cancel_subscription_on_full_refund' ); @@ -784,6 +786,27 @@ class WC_Subscriptions_Order { } } + /** + * Unset pay action for an order if a more recent order exists + * + * @since 2.2.9 + */ + public static function maybe_remove_pay_action( $actions, $order ) { + + if ( isset( $actions['pay'] ) && wcs_order_contains_subscription( $order, array( 'any' ) ) ) { + $subscriptions = wcs_get_subscriptions_for_order( wcs_get_objects_property( $order, 'id' ), array( 'order_type' => 'any' ) ); + + foreach ( $subscriptions as $subscription ) { + if ( wcs_get_objects_property( $order, 'id' ) != $subscription->get_last_order() ) { + unset( $actions['pay'] ); + break; + } + } + } + + return $actions; + } + /** * Allow subscription order items to be edited in WC 2.2. until Subscriptions 2.0 introduces * its own WC_Subscription object. diff --git a/includes/class-wc-subscriptions-product.php b/includes/class-wc-subscriptions-product.php index 080af38..a4fb111 100644 --- a/includes/class-wc-subscriptions-product.php +++ b/includes/class-wc-subscriptions-product.php @@ -382,14 +382,25 @@ class WC_Subscriptions_Product { } /** - * Returns the price per period for a product if it is a subscription. + * Returns the active price per period for a product if it is a subscription. * * @param mixed $product A WC_Product object or product ID * @return float The price charged per period for the subscription, or an empty string if the product is not a subscription. * @since 1.0 */ public static function get_price( $product ) { - return apply_filters( 'woocommerce_subscriptions_product_price', self::get_meta_data( $product, 'subscription_price', 0 ), self::maybe_get_product_instance( $product ) ); + + $product = self::maybe_get_product_instance( $product ); + + $subscription_price = self::get_meta_data( $product, 'subscription_price', 0 ); + $sale_price = self::get_sale_price( $product ); + $active_price = ( $subscription_price ) ? $subscription_price : self::get_regular_price( $product ); + + if ( $product->is_on_sale() && $subscription_price > $sale_price ) { + $active_price = $sale_price; + } + + return apply_filters( 'woocommerce_subscriptions_product_price', $active_price, $product ); } /** @@ -1057,7 +1068,7 @@ class WC_Subscriptions_Product { global $wpdb; $parent_product_ids = array(); - if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) { + if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) && $product->get_parent() ) { $parent_product_ids[] = $product->get_parent(); } else { $parent_product_ids = $wpdb->get_col( $wpdb->prepare( diff --git a/includes/class-wc-subscriptions-renewal-order.php b/includes/class-wc-subscriptions-renewal-order.php index ba30c05..c23b692 100644 --- a/includes/class-wc-subscriptions-renewal-order.php +++ b/includes/class-wc-subscriptions-renewal-order.php @@ -106,12 +106,16 @@ class WC_Subscriptions_Renewal_Order { // Do we need to activate a subscription? if ( $order_completed && ! $subscription->has_status( wcs_get_subscription_ended_statuses() ) && ! $subscription->has_status( 'active' ) ) { + // Included here because calling payment_complete sets the retry status to 'cancelled' + $is_failed_renewal_order = ( 'failed' === $orders_old_status ) ? true : false; + $is_failed_renewal_order = apply_filters( 'woocommerce_subscriptions_is_failed_renewal_order', $is_failed_renewal_order, $order_id, $orders_old_status ); + if ( $order_needed_payment ) { $subscription->payment_complete(); $was_activated = true; } - if ( 'failed' === $orders_old_status ) { + if ( $is_failed_renewal_order ) { do_action( 'woocommerce_subscriptions_paid_for_failed_renewal_order', wc_get_order( $order_id ), $subscription ); } } elseif ( 'failed' == $orders_new_status ) { diff --git a/includes/class-wc-subscriptions-switcher.php b/includes/class-wc-subscriptions-switcher.php index 00218d1..859f7cb 100644 --- a/includes/class-wc-subscriptions-switcher.php +++ b/includes/class-wc-subscriptions-switcher.php @@ -954,9 +954,9 @@ class WC_Subscriptions_Switcher { */ public static function switch_order_meta_box_rows( $post ) { - $subscriptions = array(); - $switched_ids = array(); - $orders = array(); + $subscriptions = array(); + $switched_subscriptions = array(); + $orders = array(); // On the subscription page, just show related orders if ( wcs_is_subscription( $post->ID ) ) { @@ -1365,6 +1365,16 @@ class WC_Subscriptions_Switcher { // Find the actual recurring amount charged for the old subscription (we need to use the '_recurring_line_total' meta here rather than '_subscription_recurring_amount' because we want the recurring amount to include extra from extensions, like Product Add-ons etc.) $old_recurring_total = $existing_item['line_total']; + // Use previous parent or renewal order's actual line item total instead of what is due, to guard against not yet paid amounts in multi-switching + $last_order = $subscription->get_last_order( 'all' ); + $last_order_items = $last_order->get_items(); + foreach ( $last_order_items as $last_order_item ) { + if ( wcs_get_canonical_product_id( $last_order_item ) == $product_id ) { + $old_recurring_total = $last_order_item['line_total']; + break; + } + } + if ( $subscription->get_prices_include_tax() ) { $old_recurring_total += $existing_item['line_tax']; } @@ -1384,8 +1394,10 @@ class WC_Subscriptions_Switcher { $days_in_new_cycle = wcs_get_days_in_cycle( WC_Subscriptions_Product::get_period( $item_data ), WC_Subscriptions_Product::get_interval( $item_data ) ); } - // We need to use the cart items price to ensure we include extras added by extensions like Product Add-ons + // We need to use the cart items price to ensure we include extras added by extensions like Product Add-ons, but we don't want the sign-up fee accounted for in the price, so make sure WC_Subscriptions_Cart::set_subscription_prices_for_calculation() isn't adding that. + remove_filter( 'woocommerce_product_get_price', 'WC_Subscriptions_Cart::set_subscription_prices_for_calculation', 100 ); $new_price_per_day = ( WC_Subscriptions_Product::get_price( $item_data ) * $cart_item['quantity'] ) / $days_in_new_cycle; + add_filter( 'woocommerce_product_get_price', 'WC_Subscriptions_Cart::set_subscription_prices_for_calculation', 100, 2 ); if ( $old_price_per_day < $new_price_per_day ) { diff --git a/includes/class-wc-subscriptions-synchroniser.php b/includes/class-wc-subscriptions-synchroniser.php index 1f1a490..341c3c9 100644 --- a/includes/class-wc-subscriptions-synchroniser.php +++ b/includes/class-wc-subscriptions-synchroniser.php @@ -1043,8 +1043,12 @@ class WC_Subscriptions_Synchroniser { * @since 2.2.3 */ public static function maybe_add_meta_for_new_line_item( $item_id, $item, $subscription_id ) { - if ( is_callable( array( $item, 'get_product_id' ) ) && self::is_product_synced( $item->get_product_id() ) ) { - self::maybe_add_subscription_meta( $subscription_id ); + if ( is_callable( array( $item, 'get_product_id' ) ) ) { + $product_id = wcs_get_canonical_product_id( $item ); + + if ( self::is_product_synced( $product_id ) ) { + self::maybe_add_subscription_meta( $subscription_id ); + } } } diff --git a/includes/class-wcs-cached-data-manager.php b/includes/class-wcs-cached-data-manager.php index 9b0834c..f227581 100644 --- a/includes/class-wcs-cached-data-manager.php +++ b/includes/class-wcs-cached-data-manager.php @@ -22,6 +22,9 @@ class WCS_Cached_Data_Manager extends WCS_Cache_Manager { add_action( 'updated_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys add_action( 'deleted_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys add_action( 'added_post_meta', array( $this, 'purge_from_metadata' ), 9999, 4 ); // tied to '_subscription_renewal', '_subscription_resubscribe' & '_subscription_switch' keys + + add_action( 'admin_init', array( $this, 'initialize_cron_check_size' ) ); // setup cron task to truncate big logs. + add_filter( 'cron_schedules', array( $this, 'add_weekly_cron_schedule' ) ); // create a weekly cron schedule } /** @@ -135,6 +138,70 @@ class WCS_Cached_Data_Manager extends WCS_Cache_Manager { delete_transient( $key ); } + /** + * If the log is bigger than a threshold it will be + * truncated to 0 bytes. + */ + public static function cleanup_logs() { + $file = wc_get_log_file_path( 'wcs-cache' ); + $max_cache_size = apply_filters( 'wcs_max_log_size', 50 * 1024 * 1024 ); + + if ( filesize( $file ) >= $max_cache_size ) { + $size_to_keep = apply_filters( 'wcs_log_size_to_keep', 25 * 1024 ); + $lines_to_keep = apply_filters( 'wcs_log_lines_to_keep', 1000 ); + + $fp = fopen( $file, 'r' ); + fseek( $fp, -1 * $size_to_keep, SEEK_END ); + $data = ''; + while ( ! feof( $fp ) ) { + $data .= fread( $fp, $size_to_keep ); + } + fclose( $fp ); + + // Remove first line (which is probably incomplete) and also any empty line + $lines = explode( "\n", $data ); + $lines = array_filter( array_slice( $lines, 1 ) ); + $lines = array_slice( $lines, -1000 ); + $lines[] = '---- log file automatically truncated ' . gmdate( 'Y-m-d H:i:s' ) . ' ---'; + + file_put_contents( $file, implode( "\n", $lines ), LOCK_EX ); + } + } + + /** + * Check once each week if the log file has exceeded the limits. + * + * @since 2.2.9 + */ + public function initialize_cron_check_size() { + + $hook = 'wcs_cleanup_big_logs'; + + if ( ! wp_next_scheduled( $hook ) ) { + wp_schedule_event( time(), 'weekly', $hook ); + } + + add_action( $hook, __CLASS__ . '::cleanup_logs' ); + } + + /** + * Add a weekly schedule for clearing up the cache + * + * @param $scheduled array + * @since 2.2.9 + */ + function add_weekly_cron_schedule( $schedules ) { + + if ( ! isset( $schedules['weekly'] ) ) { + $schedules['weekly'] = array( + 'interval' => WEEK_IN_SECONDS, + 'display' => __( 'Weekly', 'woocommerce-subscriptions' ), + ); + } + + return $schedules; + } + /* Deprecated Functions */ /** diff --git a/includes/class-wcs-cart-renewal.php b/includes/class-wcs-cart-renewal.php index 6833621..691598c 100644 --- a/includes/class-wcs-cart-renewal.php +++ b/includes/class-wcs-cart-renewal.php @@ -98,7 +98,7 @@ class WCS_Cart_Renewal { add_action( 'template_redirect', array( &$this, 'maybe_setup_cart' ), 100 ); // Apply renewal discounts as pseudo coupons - add_action( 'wcs_after_renewal_setup_cart_subscription', array( &$this, 'maybe_setup_discounts' ), 10, 1 ); + add_action( 'wcs_after_renewal_setup_cart_subscription', array( &$this, 'maybe_setup_discounts' ), 10, 2 ); add_filter( 'woocommerce_get_shop_coupon_data', array( &$this, 'renewal_coupon_data' ), 10, 2 ); add_action( 'wcs_before_renewal_setup_cart_subscriptions', array( &$this, 'clear_coupons' ), 10 ); @@ -288,17 +288,20 @@ class WCS_Cart_Renewal { * @param object $subscription subscription * @since 2.0.10 */ - public function maybe_setup_discounts( $subscription ) { + public function maybe_setup_discounts( $subscription, $order = null ) { + if ( null === $order ) { + // If no order arg is passed, to honor backward compatibility, apply discounts which apply to the subscription + $order = $subscription; + } - if ( wcs_is_subscription( $subscription ) ) { + if ( wcs_is_subscription( $order ) || wcs_order_contains_renewal( $order ) ) { - $used_coupons = $subscription->get_used_coupons(); - $subscription_discount = wcs_get_objects_property( $subscription, 'cart_discount' ); + $used_coupons = $order->get_used_coupons(); + $order_discount = wcs_get_objects_property( $order, 'cart_discount' ); // Add any used coupon discounts to the cart (as best we can) using our pseudo renewal coupons if ( ! empty( $used_coupons ) ) { - - $coupon_items = $subscription->get_items( 'coupon' ); + $coupon_items = $order->get_items( 'coupon' ); foreach ( $coupon_items as $coupon_item ) { @@ -309,7 +312,7 @@ class WCS_Cart_Renewal { // If the coupon still exists we can use the existing/available coupon properties if ( true === wcs_get_coupon_property( $coupon, 'exists' ) ) { - // But we only want to handle recurring coupons that have been applied to the subscription + // But we only want to handle recurring coupons that have been applied to the order if ( in_array( $coupon_type, array( 'recurring_percent', 'recurring_fee' ) ) ) { // Set the coupon type to be a renewal equivalent for correct validation and calculations @@ -323,13 +326,14 @@ class WCS_Cart_Renewal { $coupon_code = wcs_get_coupon_property( $coupon, 'code' ); } } else { - // If the coupon doesn't exist we can only really apply the discount amount we know about - so we'll apply a cart style pseudo coupon and then set the amount wcs_set_coupon_property( $coupon, 'discount_type', 'renewal_cart' ); - wcs_set_coupon_property( $coupon, 'coupon_amount', $coupon_item['item_meta']['discount_amount']['0'] ); // Adjust coupon code to reflect that it is being applied to a renewal - $coupon_code = wcs_get_coupon_property( $coupon, 'code' ); + $coupon_code = wcs_get_coupon_property( $coupon, 'code' ); + $coupon_amount = is_callable( array( $coupon_item, 'get_discount' ) ) ? $coupon_item->get_discount() : $coupon_item['item_meta']['discount_amount']['0']; + + wcs_set_coupon_property( $coupon, 'coupon_amount', $coupon_amount ); } // Now that we have a coupon we know we want to apply @@ -337,11 +341,11 @@ class WCS_Cart_Renewal { // Set renewal order products as the product ids on the coupon if ( ! WC_Subscriptions::is_woocommerce_pre( '2.5' ) ) { - wcs_set_coupon_property( $coupon, 'product_ids', $this->get_products( $subscription ) ); + wcs_set_coupon_property( $coupon, 'product_ids', $this->get_products( $order ) ); } // Store the coupon info for later - $this->store_coupon( $subscription->get_id(), $coupon ); + $this->store_coupon( wcs_get_objects_property( $order, 'id' ), $coupon ); // Add the coupon to the cart - the actually coupon values / data are grabbed when needed later if ( WC()->cart && ! WC()->cart->has_discount( $coupon_code ) ) { @@ -350,22 +354,21 @@ class WCS_Cart_Renewal { } } // If there are no coupons but there is still a discount (i.e. it might have been manually added), we need to account for that as well - } elseif ( ! empty( $subscription_discount ) ) { - + } elseif ( ! empty( $order_discount ) ) { $coupon = new WC_Coupon( 'discount_renewal' ); // Apply our cart style pseudo coupon and the set the amount wcs_set_coupon_property( $coupon, 'discount_type', 'renewal_cart' ); - wcs_set_coupon_property( $coupon, 'coupon_amount', $subscription_discount ); + wcs_set_coupon_property( $coupon, 'coupon_amount', $order_discount ); // Set renewal order products as the product ids on the coupon if ( ! WC_Subscriptions::is_woocommerce_pre( '2.5' ) ) { - wcs_set_coupon_property( $coupon, 'product_ids', $this->get_products( $subscription ) ); + wcs_set_coupon_property( $coupon, 'product_ids', $this->get_products( $order ) ); } // Store the coupon info for later - $this->store_coupon( $subscription->get_id(), $coupon ); + $this->store_coupon( wcs_get_objects_property( $order, 'id' ), $coupon ); // Add the coupon to the cart if ( WC()->cart && ! WC()->cart->has_discount( 'discount_renewal' ) ) { @@ -767,7 +770,7 @@ class WCS_Cart_Renewal { return $data; } - foreach ( $renewal_coupons as $subscription_id => $coupons ) { + foreach ( $renewal_coupons as $order_id => $coupons ) { foreach ( $coupons as $coupon_code => $coupon_properties ) { @@ -795,17 +798,17 @@ class WCS_Cart_Renewal { /** * Get original products for a renewal order - so that we can ensure renewal coupons are only applied to those * - * @param object $subscription subscription - * @return array $product_ids an array of product ids on a subscription renewal order + * @param object WC_Order | WC_Subscription $order + * @return array $product_ids an array of product ids on a subscription/order * @since 2.0.10 */ - protected function get_products( $subscription ) { + protected function get_products( $order ) { $product_ids = array(); - if ( wcs_is_subscription( $subscription ) ) { - foreach ( $subscription->get_items() as $item ) { - $product_id = ( $item['variation_id'] ) ? $item['variation_id'] : $item['product_id']; + if ( is_a( $order, 'WC_Abstract_Order' ) ) { + foreach ( $order->get_items() as $item ) { + $product_id = wcs_get_canonical_product_id( $item ); if ( ! empty( $product_id ) ) { $product_ids[] = $product_id; } @@ -822,8 +825,8 @@ class WCS_Cart_Renewal { * @param object $coupon coupon * @since 2.0.10 */ - protected function store_coupon( $subscription_id, $coupon ) { - if ( ! empty( $subscription_id ) && ! empty( $coupon ) ) { + protected function store_coupon( $order_id, $coupon ) { + if ( ! empty( $order_id ) && ! empty( $coupon ) ) { $renewal_coupons = WC()->session->get( 'wcs_renewal_coupons', array() ); $use_bools = WC_Subscriptions::is_woocommerce_pre( '3.0' ); // Some coupon properties have changed from accepting 'no' and 'yes' to true and false args. $coupon_properties = array(); @@ -868,10 +871,10 @@ class WCS_Cart_Renewal { } // Subscriptions may have multiple coupons, store coupons in an array - if ( array_key_exists( $subscription_id, $renewal_coupons ) ) { - $renewal_coupons[ $subscription_id ][ wcs_get_coupon_property( $coupon, 'code' ) ] = $coupon_properties; + if ( array_key_exists( $order_id, $renewal_coupons ) ) { + $renewal_coupons[ $order_id ][ wcs_get_coupon_property( $coupon, 'code' ) ] = $coupon_properties; } else { - $renewal_coupons[ $subscription_id ] = array( wcs_get_coupon_property( $coupon, 'code' ) => $coupon_properties ); + $renewal_coupons[ $order_id ] = array( wcs_get_coupon_property( $coupon, 'code' ) => $coupon_properties ); } WC()->session->set( 'wcs_renewal_coupons', $renewal_coupons ); @@ -889,7 +892,7 @@ class WCS_Cart_Renewal { // Remove the coupons from the cart if ( ! empty( $renewal_coupons ) ) { - foreach ( $renewal_coupons as $subscription_id => $coupons ) { + foreach ( $renewal_coupons as $order_id => $coupons ) { foreach ( $coupons as $coupon_code => $coupon_properties ) { WC()->cart->remove_coupons( $coupon_code ); } @@ -912,6 +915,16 @@ class WCS_Cart_Renewal { $order = $this->get_order( $cart_item ); + /** + * Allow other plugins to remove/add fees of an existing order prior to building the cart without changing the saved order values + * (e.g. payment gateway based fees can remove fees and later can add new fees depending on the actual selected payment gateway) + * + * @param WC_Order $order is renderd by reference - change meta data of this object + * @param WC_Cart $cart + * @since 2.2.9 + */ + do_action( 'woocommerce_adjust_order_fees_for_setup_cart_for_' . $this->cart_item_key, $order, $cart ); + if ( $order instanceof WC_Order ) { foreach ( $order->get_fees() as $fee ) { $cart->add_fee( $fee['name'], $fee['line_total'], abs( $fee['line_tax'] ) > 0, $fee['tax_class'] ); diff --git a/includes/class-wcs-limiter.php b/includes/class-wcs-limiter.php index 8bb8716..30b31ea 100644 --- a/includes/class-wcs-limiter.php +++ b/includes/class-wcs-limiter.php @@ -17,7 +17,7 @@ class WCS_Limiter { public static function init() { //Add limiting subscriptions options on edit product page - add_action( 'woocommerce_product_options_reviews', __CLASS__ . '::admin_edit_product_fields' ); + add_action( 'woocommerce_product_options_advanced', __CLASS__ . '::admin_edit_product_fields' ); add_filter( 'woocommerce_subscription_is_purchasable', __CLASS__ . '::is_purchasable_switch', 12, 2 ); @@ -37,7 +37,6 @@ class WCS_Limiter { public static function admin_edit_product_fields() { global $post; - echo ''; echo '
'; // Only one Subscription per customer @@ -52,6 +51,7 @@ class WCS_Limiter { 'any' => __( 'Limit to one of any status', 'woocommerce-subscriptions' ), ), ) ); + echo '
'; do_action( 'woocommerce_subscriptions_product_options_advanced' ); } diff --git a/includes/class-wcs-retry-manager.php b/includes/class-wcs-retry-manager.php index 7a26d2a..859ef03 100644 --- a/includes/class-wcs-retry-manager.php +++ b/includes/class-wcs-retry-manager.php @@ -55,6 +55,8 @@ class WCS_Retry_Manager { add_action( 'woocommerce_subscription_renewal_payment_failed', __CLASS__ . '::maybe_apply_retry_rule', 10, 2 ); add_action( 'woocommerce_scheduled_subscription_payment_retry', __CLASS__ . '::maybe_retry_payment' ); + + add_filter( 'woocommerce_subscriptions_is_failed_renewal_order', __CLASS__ . '::compare_order_and_retry_statuses', 10, 3 ); } } @@ -324,6 +326,23 @@ class WCS_Retry_Manager { } } + /** + * Determines if a renewal order and the last retry statuses are the same (used to determine if a payment method + * change is needed) + * + * @since 2.2.8 + */ + public static function compare_order_and_retry_statuses( $is_failed_order, $order_id, $order_status ) { + + $last_retry = self::store()->get_last_retry_for_order( $order_id ); + + if ( null !== $last_retry && $order_status === $last_retry->get_rule()->get_status_to_apply( 'order' ) ) { + $is_failed_order = true; + } + + return $is_failed_order; + } + /** * Access the object used to interface with the database * diff --git a/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php b/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php index 6b91884..565049c 100644 --- a/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php +++ b/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php @@ -401,8 +401,11 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler { // Admins can suspend subscription at PayPal triggering this IPN case 'recurring_payment_suspended': + // When a subscriber suspends a PayPal Standard subscription, PayPal will notify WooCommerce by sending an IPN that uses an Express Checkout Recurring Payment payload, instead of an IPN payload for a PayPal Standard Subscription. This means the payload uses the 'recurring_payment_id' key for the subscription ID, not the 'subscr_id' key. + $ipn_profile_id = ( isset( $transaction_details['subscr_id'] ) ) ? $transaction_details['subscr_id'] : $transaction_details['recurring_payment_id']; + // Make sure subscription hasn't been linked to a new payment method - if ( wcs_get_paypal_id( $subscription ) != $transaction_details['subscr_id'] ) { + if ( wcs_get_paypal_id( $subscription ) != $ipn_profile_id ) { WC_Gateway_Paypal::log( sprintf( 'IPN "recurring_payment_suspended" ignored for subscription %d - PayPal profile ID has changed', $subscription->id ) ); @@ -546,59 +549,37 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler { } } - // Couldn't find the order ID by subscr_id, so it's either not set on the order yet or the $args doesn't have a subscr_id, either way, let's get it from the args + // Couldn't find the order ID by subscr_id, so it's either not set on the order yet or the $args doesn't have a subscr_id (?!), either way, let's get it from the args if ( empty( $order_id ) && isset( $args['custom'] ) ) { - // WC < 1.6.5 - if ( is_numeric( $args['custom'] ) && 'shop_order' == $order_type ) { - $order_id = $args['custom']; - $order_key = $args['invoice']; + $order_details = json_decode( $args['custom'] ); - } else { + if ( is_object( $order_details ) ) { // WC 2.3.11+ converted the custom value to JSON, if we have an object, we've got valid JSON - $order_details = json_decode( $args['custom'] ); + if ( 'shop_order' == $order_type ) { + $order_id = $order_details->order_id; + $order_key = $order_details->order_key; + } elseif ( isset( $order_details->subscription_id ) ) { + // Subscription created with Subscriptions 2.0+ + $order_id = $order_details->subscription_id; + $order_key = $order_details->subscription_key; + } else { + // Subscription created with Subscriptions < 2.0 + $subscriptions = wcs_get_subscriptions_for_order( absint( $order_details->order_id ), array( 'order_type' => array( 'parent' ) ) ); - if ( is_object( $order_details ) ) { // WC 2.3.11+ converted the custom value to JSON, if we have an object, we've got valid JSON - - if ( 'shop_order' == $order_type ) { - $order_id = $order_details->order_id; - $order_key = $order_details->order_key; - } elseif ( isset( $order_details->subscription_id ) ) { - // Subscription created with Subscriptions 2.0+ - $order_id = $order_details->subscription_id; - $order_key = $order_details->subscription_key; - } else { - // Subscription created with Subscriptions < 2.0 - $subscriptions = wcs_get_subscriptions_for_order( $order_details->order_id, array( 'order_type' => array( 'parent' ) ) ); - - if ( ! empty( $subscriptions ) ) { - $subscription = array_pop( $subscriptions ); - $order_id = $subscription->get_id(); - $order_key = $subscription->get_order_key(); - } + if ( ! empty( $subscriptions ) ) { + $subscription = array_pop( $subscriptions ); + $order_id = $subscription->get_id(); + $order_key = $subscription->get_order_key(); } - } elseif ( preg_match( '/^a:2:{/', $args['custom'] ) && ! preg_match( '/[CO]:\+?[0-9]+:"/', $args['custom'] ) && ( $order_details = maybe_unserialize( $args['custom'] ) ) ) { // WC 2.0 - WC 2.3.11, only allow serialized data in the expected format, do not allow objects or anything nasty to sneak in - - if ( 'shop_order' == $order_type ) { - $order_id = $order_details[0]; - $order_key = $order_details[1]; - } else { - - // Subscription, but we didn't have the subscription data in old, serialized value, so we need to pull it based on the order - $subscriptions = wcs_get_subscriptions_for_order( $order_details[0], array( 'order_type' => array( 'parent' ) ) ); - - if ( ! empty( $subscriptions ) ) { - $subscription = array_pop( $subscriptions ); - $order_id = $subscription->get_id(); - $order_key = $subscription->get_order_key(); - } - } - } else { // WC 1.6.5 - WC 2.0 or invalid data - - $order_id = str_replace( WCS_PayPal::get_option( 'invoice_prefix' ), '', $args['invoice'] ); - $order_key = $args['custom']; - } + } 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 + + $message = __( 'Invalid PayPal IPN Payload: unable to find matching subscription.', 'woocommerce-subscriptions' ); + + WC_Gateway_Paypal::log( $message ); + + throw new Exception( $message ); } } diff --git a/includes/upgrades/class-wc-subscriptions-upgrader.php b/includes/upgrades/class-wc-subscriptions-upgrader.php index 09a9d2b..5558d2e 100644 --- a/includes/upgrades/class-wc-subscriptions-upgrader.php +++ b/includes/upgrades/class-wc-subscriptions-upgrader.php @@ -90,6 +90,8 @@ class WC_Subscriptions_Upgrader { add_action( 'woocommerce_subscriptions_upgraded', __CLASS__ . '::maybe_redirect_after_upgrade_complete', 100, 2 ); add_action( 'wcs_repair_end_of_prepaid_term_actions', __CLASS__ . '::repair_end_of_prepaid_term_actions' ); + + add_action( 'wcs_repair_subscriptions_containing_synced_variations', __CLASS__ . '::repair_subscription_contains_sync_meta' ); } /** @@ -192,6 +194,12 @@ class WC_Subscriptions_Upgrader { WCS_Upgrade_2_2_7::schedule_end_of_prepaid_term_repair(); } + // Repair missing _contains_synced_subscription post meta + if ( version_compare( get_option( 'woocommerce_db_version' ), '3.0', '>=' ) && version_compare( self::$active_version, '2.2.0', '>=' ) && version_compare( self::$active_version, '2.2.9', '<' ) ) { + include_once( 'class-wcs-upgrade-2-2-9.php' ); + WCS_Upgrade_2_2_9::schedule_repair(); + } + self::upgrade_complete(); } @@ -748,6 +756,16 @@ class WC_Subscriptions_Upgrader { WCS_Upgrade_2_2_7::repair_pending_cancelled_subscriptions(); } + /** + * Repair subscriptions with missing contains_synced_subscription post meta. + * + * @since 2.2.9 + */ + public static function repair_subscription_contains_sync_meta() { + include_once( 'class-wcs-upgrade-2-2-9.php' ); + WCS_Upgrade_2_2_9::repair_subscriptions_containing_synced_variations(); + } + /** * Used to check if a user ID is greater than the last user upgraded to version 1.4. * diff --git a/includes/upgrades/class-wcs-upgrade-2-2-9.php b/includes/upgrades/class-wcs-upgrade-2-2-9.php new file mode 100644 index 0000000..9eb555d --- /dev/null +++ b/includes/upgrades/class-wcs-upgrade-2-2-9.php @@ -0,0 +1,123 @@ +get_items() as $item ) { + $product_id = wcs_get_canonical_product_id( $item ); + + if ( WC_Subscriptions_Synchroniser::is_product_synced( $product_id ) ) { + update_post_meta( $subscription->get_id(), '_contains_synced_subscription', 'true' ); + self::log( sprintf( 'Subscription %d repaired for synced product ID: %d', $subscription_id, $product_id ) ); + $subscription_updated = true; + break; + } + } + + if ( false === $subscription_updated ) { + self::log( sprintf( 'Subscription %d wasn\'t repaired - no synced product found', $subscription_id ) ); + } + } catch ( Exception $e ) { + self::log( sprintf( '--- Exception caught repairing subscription %d - exception message: %s ---', $subscription_id, $e->getMessage() ) ); + } + + // Flag this subscription as repaired so we don't pull it into a following batch + $repaired_subscriptions[] = $subscription_id; + update_option( self::$repaired_subscriptions_option, $repaired_subscriptions, 'no' ); + } + + // If we've processed a full batch, schedule the next batch to be repaired + if ( count( $subscriptions_to_repair ) === self::$batch_size ) { + self::schedule_repair(); + } else { + self::log( '2.2.9 repair missing _contains_synced_subscription post meta complete' ); + } + } + + /** + * Get a batch of subscriptions to repair. + * + * @since 2.2.9 + * @param array $repaired_subscriptions A list of subscription post IDs to ignore. + * @return array A list of subscription ids which may need to be repaired. + */ + public static function get_subscriptions_to_repair( $repaired_subscriptions ) { + $subscriptions_to_repair = get_posts( array( + 'post_type' => 'shop_subscription', + 'posts_per_page' => self::$batch_size, + 'post_status' => 'any', + 'fields' => 'ids', + 'post__not_in' => $repaired_subscriptions, + 'meta_query' => array( + array( + 'key' => '_contains_synced_subscription', + 'compare' => 'NOT EXISTS', + ), + array( + 'key' => '_order_version', // Try to narrow the focus to subscriptions created after 3.0.0 as they are the only ones affected and needing repair (tough all subscriptions instantiated after 3.0 will also have their _order_version updated) + 'value' => '3.0.0', + 'compare' => '>=', + ), + ), + ) ); + + return $subscriptions_to_repair; + } + + /** + * Add a message to the wcs-upgrade-subscriptions-containing-synced-variations log + * + * @param string The message to be logged + * @since 2.2.9 + */ + protected static function log( $message ) { + WCS_Upgrade_Logger::add( $message, 'wcs-upgrade-subscriptions-containing-synced-variations' ); + } +} diff --git a/includes/upgrades/class-wcs-upgrade-logger.php b/includes/upgrades/class-wcs-upgrade-logger.php index f4dd7c5..872bb17 100644 --- a/includes/upgrades/class-wcs-upgrade-logger.php +++ b/includes/upgrades/class-wcs-upgrade-logger.php @@ -25,7 +25,7 @@ class WCS_Upgrade_Logger { public static function init() { - add_action( 'woocommerce_subscriptions_upgraded', __CLASS__ . '::schedule_cleanup' ); + add_action( 'woocommerce_subscriptions_upgraded', __CLASS__ . '::schedule_cleanup', 10, 2 ); } /** @@ -66,8 +66,9 @@ class WCS_Upgrade_Logger { /** * Schedule a hook to automatically clear the log after 8 weeks */ - public static function schedule_cleanup() { - self::add( sprintf( '%s upgrade complete.', WC_Subscriptions::$version ) ); + public static function schedule_cleanup( $current_version, $old_version ) { + $wc_version = defined( 'WC_VERSION' ) ? WC_VERSION : 'undefined'; + self::add( sprintf( '%s upgrade complete from Subscriptions v%s while WooCommerce WC_VERSION %s and database version %s was active.', $current_version, $old_version, $wc_version, get_option( 'woocommerce_db_version' ) ) ); } } WCS_Upgrade_Logger::init(); diff --git a/includes/wcs-cart-functions.php b/includes/wcs-cart-functions.php index 8c782e6..0e6a90f 100644 --- a/includes/wcs-cart-functions.php +++ b/includes/wcs-cart-functions.php @@ -64,7 +64,14 @@ function wcs_cart_totals_shipping_html() { } $chosen_initial_method = isset( WC()->session->chosen_shipping_methods[ $i ] ) ? WC()->session->chosen_shipping_methods[ $i ] : ''; - $chosen_recurring_method = isset( WC()->session->chosen_shipping_methods[ $recurring_cart_key . '_' . $i ] ) ? WC()->session->chosen_shipping_methods[ $recurring_cart_key . '_' . $i ] : $chosen_initial_method; + + if ( isset( WC()->session->chosen_shipping_methods[ $recurring_cart_key . '_' . $i ] ) ) { + $chosen_recurring_method = WC()->session->chosen_shipping_methods[ $recurring_cart_key . '_' . $i ]; + } elseif ( in_array( $chosen_initial_method, $package['rates'] ) ) { + $chosen_recurring_method = $chosen_initial_method; + } else { + $chosen_recurring_method = current( $package['rates'] )->id; + } $shipping_selection_displayed = false; diff --git a/includes/wcs-compatibility-functions.php b/includes/wcs-compatibility-functions.php index 58ff539..33dc8e6 100644 --- a/includes/wcs-compatibility-functions.php +++ b/includes/wcs-compatibility-functions.php @@ -219,11 +219,11 @@ function wcs_get_objects_property( $object, $property, $single = 'single', $defa * @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 int $meta_id The meta ID of exiting meta data if you wish to overwrite an existing piece of meta. - * @param bool|string $prefix An optional prefix to add to the $key. Default '_'. Set to boolean false to have no prefix added. + * @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 * @return mixed */ -function wcs_set_objects_property( &$object, $key, $value, $save = 'save', $meta_id = '' ) { +function wcs_set_objects_property( &$object, $key, $value, $save = 'save', $meta_id = '', $prefix_meta_key = 'prefix_meta_key' ) { $prefixed_key = wcs_maybe_prefix_key( $key ); @@ -265,7 +265,8 @@ function wcs_set_objects_property( &$object, $key, $value, $save = 'save', $meta // If there is no setter, treat as meta within the 3.0.x object. } elseif ( is_callable( array( $object, 'update_meta_data' ) ) ) { - $object->update_meta_data( $prefixed_key, $value, $meta_id ); + $meta_key = ( 'prefix_meta_key' === $prefix_meta_key ) ? $prefixed_key : $key; + $object->update_meta_data( $meta_key, $value, $meta_id ); // 2.6.x handling for name which is not meta. } elseif ( 'name' === $key ) { @@ -285,11 +286,12 @@ function wcs_set_objects_property( &$object, $key, $value, $save = 'save', $meta } elseif ( 'name' === $key ) { // the replacement for post_title added in 3.0, need to update post_title not post meta wp_update_post( array( 'ID' => wcs_get_objects_property( $object, 'id' ), 'post_title' => $value ) ); } else { + $meta_key = ( 'prefix_meta_key' === $prefix_meta_key ) ? $prefixed_key : $key; if ( ! empty( $meta_id ) ) { - update_metadata_by_mid( 'post', $meta_id, $value, $prefixed_key ); + update_metadata_by_mid( 'post', $meta_id, $value, $meta_key ); } else { - update_post_meta( wcs_get_objects_property( $object, 'id' ), $prefixed_key, $value ); + update_post_meta( wcs_get_objects_property( $object, 'id' ), $meta_key, $value ); } } } diff --git a/includes/wcs-limit-functions.php b/includes/wcs-limit-functions.php index 04a7c4f..bec2e5e 100644 --- a/includes/wcs-limit-functions.php +++ b/includes/wcs-limit-functions.php @@ -24,7 +24,7 @@ function wcs_get_product_limitation( $product ) { $product = wc_get_product( $product ); } - return apply_filters( 'woocommerce_subscriptions_product_limitation', WC_Subscriptions_Product::get_meta_data( $product, 'subscription_limit', 0 ), $product ); + return apply_filters( 'woocommerce_subscriptions_product_limitation', WC_Subscriptions_Product::get_meta_data( $product, 'subscription_limit', 'no', 'use_default_value' ), $product ); } /** diff --git a/includes/wcs-order-functions.php b/includes/wcs-order-functions.php index 5df1a7d..1eb3a9c 100644 --- a/includes/wcs-order-functions.php +++ b/includes/wcs-order-functions.php @@ -178,7 +178,7 @@ function wcs_copy_order_meta( $from_order, $to_order, $type = 'subscription' ) { $meta = apply_filters( 'wcs_' . $type . '_meta', $meta, $to_order, $from_order ); foreach ( $meta as $meta_item ) { - wcs_set_objects_property( $to_order, $meta_item['meta_key'], maybe_unserialize( $meta_item['meta_value'] ) ); + wcs_set_objects_property( $to_order, $meta_item['meta_key'], maybe_unserialize( $meta_item['meta_value'] ), 'save', '', 'omit_key_prefix' ); } } @@ -554,6 +554,9 @@ function wcs_get_order_item( $item_id, $order ) { * @since 2.0 */ function wcs_get_order_item_meta( $item, $product = null ) { + if ( false === WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) { + wcs_deprecated_function( __FUNCTION__, '3.1 of WooCommerce and 2.2.9 of Subscriptions', 'WC_Order_Item_Product->get_formatted_meta_data() or wc_display_item_meta()' ); + } return new WC_Order_Item_Meta( $item, $product ); } diff --git a/includes/wcs-product-functions.php b/includes/wcs-product-functions.php index 6be6d09..af3f849 100644 --- a/includes/wcs-product-functions.php +++ b/includes/wcs-product-functions.php @@ -161,6 +161,14 @@ function wcs_calculate_min_max_variations( $variations_data ) { $variable_subscription_sign_up_fee = $variable_subscription_trial_period = $variable_subscription_trial_length = $variable_subscription_length = $variable_subscription_sign_up_fee = $variable_subscription_trial_period = $variable_subscription_trial_length = $variable_subscription_length = ''; $min_variation_id = $max_variation_id = null; + $variations_data_prices_list = array(); + $variations_data_sign_up_fees_list = array(); + $variations_data_periods_list = array(); + $variations_data_intervals_list = array(); + $variations_data_trial_lengths_list = array(); + $variations_data_trial_periods_list = array(); + $variations_data_lengths_list = array(); + foreach ( $variations_data as $variation_id => $variation_data ) { $is_max = $is_min = false; @@ -169,6 +177,14 @@ function wcs_calculate_min_max_variations( $variations_data ) { continue; } + $variations_data_prices_list = array_unique( array_merge( $variations_data_prices_list, array( $variation_data['price'] ) ) ); + $variations_data_sign_up_fees_list = array_unique( array_merge( $variations_data_sign_up_fees_list, array( empty( $variation_data['subscription']['sign_up_fee'] ) ? 0 : $variation_data['subscription']['sign_up_fee'] ) ) ); + $variations_data_periods_list = array_unique( array_merge( $variations_data_periods_list, array( $variation_data['subscription']['period'] ) ) ); + $variations_data_intervals_list = array_unique( array_merge( $variations_data_intervals_list, array( $variation_data['subscription']['interval'] ) ) ); + $variations_data_trial_lengths_list = array_unique( array_merge( $variations_data_trial_lengths_list, array( empty( $variation_data['subscription']['trial_length'] ) ? 0 : $variation_data['subscription']['trial_length'] ) ) ); + $variations_data_trial_periods_list = array_unique( array_merge( $variations_data_trial_periods_list, array( $variation_data['subscription']['trial_period'] ) ) ); + $variations_data_lengths_list = array_unique( array_merge( $variations_data_lengths_list, array( $variation_data['subscription']['length'] ) ) ); + $has_free_trial = ( '' !== $variation_data['subscription']['trial_length'] && $variation_data['subscription']['trial_length'] > 0 ) ? true : false; // Determine some recurring price flags @@ -381,6 +397,24 @@ function wcs_calculate_min_max_variations( $variations_data ) { } } + if ( sizeof( array_unique( $variations_data_prices_list ) ) > 1 ) { + $subscription_details_identical = false; + } elseif ( sizeof( array_unique( $variations_data_sign_up_fees_list ) ) > 1 ) { + $subscription_details_identical = false; + } elseif ( sizeof( array_unique( $variations_data_periods_list ) ) > 1 ) { + $subscription_details_identical = false; + } elseif ( sizeof( array_unique( $variations_data_intervals_list ) ) > 1 ) { + $subscription_details_identical = false; + } elseif ( sizeof( array_unique( $variations_data_trial_lengths_list ) ) > 1 ) { + $subscription_details_identical = false; + } elseif ( sizeof( array_unique( $variations_data_trial_periods_list ) ) > 1 ) { + $subscription_details_identical = false; + } elseif ( sizeof( array_unique( $variations_data_lengths_list ) ) > 1 ) { + $subscription_details_identical = false; + } else { + $subscription_details_identical = true; + } + return array( 'min' => array( 'variation_id' => $min_variation_id, @@ -404,5 +438,6 @@ function wcs_calculate_min_max_variations( $variations_data ) { 'trial_length' => $variable_subscription_trial_length, 'length' => $variable_subscription_length, ), + 'identical' => $subscription_details_identical, ); } diff --git a/includes/wcs-renewal-functions.php b/includes/wcs-renewal-functions.php index f708206..af8250a 100644 --- a/includes/wcs-renewal-functions.php +++ b/includes/wcs-renewal-functions.php @@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) { * passed to it. * * @param int | WC_Subscription $subscription Post ID of a 'shop_subscription' post, or instance of a WC_Subscription object - * @return WC_Subscription + * @return WC_Order | WP_Error * @since 2.0 */ function wcs_create_renewal_order( $subscription ) { @@ -94,8 +94,10 @@ function wcs_cart_contains_failed_renewal_order_payment() { $cart_item = wcs_cart_contains_renewal(); if ( false !== $cart_item && isset( $cart_item['subscription_renewal']['renewal_order_id'] ) ) { - $renewal_order = wc_get_order( $cart_item['subscription_renewal']['renewal_order_id'] ); - if ( $renewal_order->has_status( 'failed' ) ) { + $renewal_order = wc_get_order( $cart_item['subscription_renewal']['renewal_order_id'] ); + $is_failed_renewal_order = apply_filters( 'woocommerce_subscriptions_is_failed_renewal_order', $renewal_order->has_status( 'failed' ), $cart_item['subscription_renewal']['renewal_order_id'], $renewal_order->get_status() ); + + if ( $is_failed_renewal_order ) { $contains_renewal = $cart_item; } } diff --git a/includes/wcs-user-functions.php b/includes/wcs-user-functions.php index 9add8b1..7b6fe32 100644 --- a/includes/wcs-user-functions.php +++ b/includes/wcs-user-functions.php @@ -41,6 +41,22 @@ function wcs_maybe_make_user_inactive( $user_id ) { } } +/** + * Wrapper for wcs_maybe_make_user_inactive() that accepts a subscription instead of a user ID. + * Handy for hooks that pass a subscription object. + * + * @since 2.2.9 + * @param WC_Subscription|WC_Order + */ +function wcs_maybe_make_user_inactive_for( $subscription ) { + wcs_maybe_make_user_inactive( $subscription->get_user_id() ); +} +add_action( 'woocommerce_subscription_status_failed', 'wcs_maybe_make_user_inactive_for', 10, 1 ); +add_action( 'woocommerce_subscription_status_on-hold', 'wcs_maybe_make_user_inactive_for', 10, 1 ); +add_action( 'woocommerce_subscription_status_cancelled', 'wcs_maybe_make_user_inactive_for', 10, 1 ); +add_action( 'woocommerce_subscription_status_switched', 'wcs_maybe_make_user_inactive_for', 10, 1 ); +add_action( 'woocommerce_subscription_status_expired', 'wcs_maybe_make_user_inactive_for', 10, 1 ); + /** * Update a user's role to a special subscription's role * diff --git a/languages/woocommerce-subscriptions.pot b/languages/woocommerce-subscriptions.pot index 6615b2d..dd960fd 100644 --- a/languages/woocommerce-subscriptions.pot +++ b/languages/woocommerce-subscriptions.pot @@ -2,10 +2,10 @@ # This file is distributed under the same license as the WooCommerce Subscriptions package. msgid "" msgstr "" -"Project-Id-Version: WooCommerce Subscriptions 2.2.7\n" +"Project-Id-Version: WooCommerce Subscriptions 2.2.10\n" "Report-Msgid-Bugs-To: " "https://github.com/Prospress/woocommerce-subscriptions/issues\n" -"POT-Creation-Date: 2017-05-27 00:30:00+00:00\n" +"POT-Creation-Date: 2017-07-20 05:32:36+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -215,7 +215,7 @@ msgstr "" #: includes/class-wc-product-subscription.php:72 #: includes/class-wc-product-variable-subscription.php:63 #: includes/class-wc-subscriptions-product.php:96 -#: woocommerce-subscriptions.php:480 +#: woocommerce-subscriptions.php:483 msgid "Sign Up Now" msgstr "" @@ -350,7 +350,7 @@ msgstr "" #: includes/admin/class-wc-subscriptions-admin.php:1195 #: includes/upgrades/templates/wcs-about-2-0.php:35 #: includes/upgrades/templates/wcs-about.php:34 -#: woocommerce-subscriptions.php:991 +#: woocommerce-subscriptions.php:1022 msgid "Settings" msgstr "" @@ -575,12 +575,12 @@ msgid "End Date" msgstr "" #: includes/admin/class-wcs-admin-post-types.php:457 -#: includes/wcs-user-functions.php:276 +#: includes/wcs-user-functions.php:292 msgid "Reactivate" msgstr "" #: includes/admin/class-wcs-admin-post-types.php:458 -#: includes/wcs-user-functions.php:271 +#: includes/wcs-user-functions.php:287 msgid "Suspend" msgstr "" @@ -595,12 +595,12 @@ msgid "Delete Permanently" msgstr "" #: includes/admin/class-wcs-admin-post-types.php:473 -#: includes/class-wc-subscriptions-product.php:722 +#: includes/class-wc-subscriptions-product.php:733 msgid "Restore this item from the Trash" msgstr "" #: includes/admin/class-wcs-admin-post-types.php:473 -#: includes/class-wc-subscriptions-product.php:723 +#: includes/class-wc-subscriptions-product.php:734 msgid "Restore" msgstr "" @@ -630,65 +630,65 @@ msgstr "" msgid "Show more details" msgstr "" -#: includes/admin/class-wcs-admin-post-types.php:598 +#: includes/admin/class-wcs-admin-post-types.php:571 msgid "%d item" msgid_plural "%d items" msgstr[0] "" msgstr[1] "" -#: includes/admin/class-wcs-admin-post-types.php:642 +#: includes/admin/class-wcs-admin-post-types.php:587 #: templates/myaccount/my-subscriptions.php:48 #. translators: placeholder is the display name of a payment gateway a #. subscription was paid by msgid "Via %s" msgstr "" -#: includes/admin/class-wcs-admin-post-types.php:656 +#: includes/admin/class-wcs-admin-post-types.php:601 msgid "Y/m/d g:i:s A" msgstr "" -#: includes/admin/class-wcs-admin-post-types.php:659 +#: includes/admin/class-wcs-admin-post-types.php:604 msgid "" "This date should be treated as an estimate only. The payment gateway for " "this subscription controls when payments are processed." msgstr "" -#: includes/admin/class-wcs-admin-post-types.php:932 -#: includes/admin/class-wcs-admin-post-types.php:935 -#: includes/admin/class-wcs-admin-post-types.php:938 +#: includes/admin/class-wcs-admin-post-types.php:877 +#: includes/admin/class-wcs-admin-post-types.php:880 +#: includes/admin/class-wcs-admin-post-types.php:883 msgid "Subscription updated." msgstr "" -#: includes/admin/class-wcs-admin-post-types.php:933 +#: includes/admin/class-wcs-admin-post-types.php:878 msgid "Custom field updated." msgstr "" -#: includes/admin/class-wcs-admin-post-types.php:934 +#: includes/admin/class-wcs-admin-post-types.php:879 msgid "Custom field deleted." msgstr "" -#: includes/admin/class-wcs-admin-post-types.php:939 +#: includes/admin/class-wcs-admin-post-types.php:884 msgid "Subscription saved." msgstr "" -#: includes/admin/class-wcs-admin-post-types.php:940 +#: includes/admin/class-wcs-admin-post-types.php:885 msgid "Subscription submitted." msgstr "" -#: includes/admin/class-wcs-admin-post-types.php:942 +#: includes/admin/class-wcs-admin-post-types.php:887 #. translators: php date string msgid "Subscription scheduled for: %1$s." msgstr "" -#: includes/admin/class-wcs-admin-post-types.php:943 +#: includes/admin/class-wcs-admin-post-types.php:888 msgid "Subscription draft updated." msgstr "" -#: includes/admin/class-wcs-admin-post-types.php:979 +#: includes/admin/class-wcs-admin-post-types.php:924 msgid "Any Payment Method" msgstr "" -#: includes/admin/class-wcs-admin-post-types.php:980 +#: includes/admin/class-wcs-admin-post-types.php:925 msgid "None" msgstr "" @@ -1403,109 +1403,109 @@ msgstr "" msgid "Unable to change subscription status to \"%s\"." msgstr "" -#: includes/class-wc-subscription.php:522 +#: includes/class-wc-subscription.php:520 msgid "Unable to change subscription status to \"%s\". Exception: %s" msgstr "" -#: includes/class-wc-subscription.php:544 +#: includes/class-wc-subscription.php:542 #. translators: 1: old subscription status 2: new subscription status msgid "Status changed from %1$s to %2$s." msgstr "" -#: includes/class-wc-subscription.php:556 +#: includes/class-wc-subscription.php:554 #. translators: %s: new order status msgid "Status set to %s." msgstr "" -#: includes/class-wc-subscription.php:1150 +#: includes/class-wc-subscription.php:1148 #: includes/class-wc-subscriptions-manager.php:2221 #: includes/wcs-formatting-functions.php:228 #. translators: placeholder is human time diff (e.g. "3 weeks") msgid "In %s" msgstr "" -#: includes/class-wc-subscription.php:1153 +#: includes/class-wc-subscription.php:1151 #: includes/wcs-formatting-functions.php:231 #. translators: placeholder is human time diff (e.g. "3 weeks") msgid "%s ago" msgstr "" -#: includes/class-wc-subscription.php:1160 +#: includes/class-wc-subscription.php:1158 msgid "Not yet ended" msgstr "" -#: includes/class-wc-subscription.php:1163 +#: includes/class-wc-subscription.php:1161 msgid "Not cancelled" msgstr "" -#: includes/class-wc-subscription.php:1278 +#: includes/class-wc-subscription.php:1276 msgid "The start date of a subscription can not be deleted, only updated." msgstr "" -#: includes/class-wc-subscription.php:1282 +#: includes/class-wc-subscription.php:1280 msgid "The %s date of a subscription can not be deleted. You must delete the order." msgstr "" -#: includes/class-wc-subscription.php:1683 +#: includes/class-wc-subscription.php:1681 msgid "Sign-up complete." msgstr "" -#: includes/class-wc-subscription.php:1685 +#: includes/class-wc-subscription.php:1683 msgid "Payment received." msgstr "" -#: includes/class-wc-subscription.php:1716 +#: includes/class-wc-subscription.php:1714 msgid "Payment failed." msgstr "" -#: includes/class-wc-subscription.php:1721 +#: includes/class-wc-subscription.php:1719 msgid "Subscription Cancelled: maximum number of failed payments reached." msgstr "" -#: includes/class-wc-subscription.php:1923 +#: includes/class-wc-subscription.php:1921 #: includes/class-wcs-change-payment-method-admin.php:156 msgid "Manual Renewal" msgstr "" -#: includes/class-wc-subscription.php:2002 +#: includes/class-wc-subscription.php:2000 msgid "Payment method meta must be an array." msgstr "" -#: includes/class-wc-subscription.php:2229 +#: includes/class-wc-subscription.php:2235 msgid "Invalid format. First parameter needs to be an array." msgstr "" -#: includes/class-wc-subscription.php:2233 +#: includes/class-wc-subscription.php:2239 msgid "Invalid data. First parameter was empty when passed to update_dates()." msgstr "" -#: includes/class-wc-subscription.php:2240 +#: includes/class-wc-subscription.php:2246 msgid "" "Invalid data. First parameter has a date that is not in the registered date " "types." msgstr "" -#: includes/class-wc-subscription.php:2304 +#: includes/class-wc-subscription.php:2310 msgid "The %s date must occur after the cancellation date." msgstr "" -#: includes/class-wc-subscription.php:2309 +#: includes/class-wc-subscription.php:2315 msgid "The %s date must occur after the last payment date." msgstr "" -#: includes/class-wc-subscription.php:2313 +#: includes/class-wc-subscription.php:2319 msgid "The %s date must occur after the next payment date." msgstr "" -#: includes/class-wc-subscription.php:2318 +#: includes/class-wc-subscription.php:2324 msgid "The %s date must occur after the trial end date." msgstr "" -#: includes/class-wc-subscription.php:2322 +#: includes/class-wc-subscription.php:2328 msgid "The %s date must occur after the start date." msgstr "" -#: includes/class-wc-subscription.php:2351 +#: includes/class-wc-subscription.php:2357 #: includes/class-wc-subscriptions-checkout.php:313 #: includes/wcs-order-functions.php:279 msgid "Backordered" @@ -1629,59 +1629,63 @@ msgstr "" msgid "Error %d: Unable to create order. Please try again." msgstr "" -#: includes/class-wc-subscriptions-coupon.php:63 +#: includes/class-wc-subscriptions-coupon.php:65 msgid "Sign Up Fee Discount" msgstr "" -#: includes/class-wc-subscriptions-coupon.php:64 +#: includes/class-wc-subscriptions-coupon.php:66 msgid "Sign Up Fee % Discount" msgstr "" -#: includes/class-wc-subscriptions-coupon.php:65 +#: includes/class-wc-subscriptions-coupon.php:67 msgid "Recurring Product Discount" msgstr "" -#: includes/class-wc-subscriptions-coupon.php:66 +#: includes/class-wc-subscriptions-coupon.php:68 msgid "Recurring Product % Discount" msgstr "" -#: includes/class-wc-subscriptions-coupon.php:256 +#: includes/class-wc-subscriptions-coupon.php:258 msgid "" "Sorry, this coupon is only valid for an initial payment and the cart does " "not require an initial payment." msgstr "" -#: includes/class-wc-subscriptions-coupon.php:262 +#: includes/class-wc-subscriptions-coupon.php:264 msgid "Sorry, this coupon is only valid for new subscriptions." msgstr "" -#: includes/class-wc-subscriptions-coupon.php:267 +#: includes/class-wc-subscriptions-coupon.php:269 msgid "Sorry, this coupon is only valid for subscription products." msgstr "" -#: includes/class-wc-subscriptions-coupon.php:273 +#: includes/class-wc-subscriptions-coupon.php:275 #. translators: 1$: coupon code that is being removed msgid "Sorry, the \"%1$s\" coupon is only valid for renewals." msgstr "" -#: includes/class-wc-subscriptions-coupon.php:278 +#: includes/class-wc-subscriptions-coupon.php:280 msgid "" "Sorry, this coupon is only valid for subscription products with a sign-up " "fee." msgstr "" -#: includes/class-wc-subscriptions-coupon.php:503 +#: includes/class-wc-subscriptions-coupon.php:505 msgid "Renewal % discount" msgstr "" -#: includes/class-wc-subscriptions-coupon.php:504 +#: includes/class-wc-subscriptions-coupon.php:506 msgid "Renewal product discount" msgstr "" -#: includes/class-wc-subscriptions-coupon.php:505 +#: includes/class-wc-subscriptions-coupon.php:507 msgid "Renewal cart discount" msgstr "" +#: includes/class-wc-subscriptions-coupon.php:524 +msgid "Renewal Discount" +msgstr "" + #: includes/class-wc-subscriptions-manager.php:104 msgid "" "Error: Unable to create renewal order from scheduled payment. Please try " @@ -1772,40 +1776,40 @@ msgstr "" msgid "Date Changed" msgstr "" -#: includes/class-wc-subscriptions-order.php:369 +#: includes/class-wc-subscriptions-order.php:371 msgid "Your subscription will be activated when payment clears." msgid_plural "Your subscriptions will be activated when payment clears." msgstr[0] "" msgstr[1] "" -#: includes/class-wc-subscriptions-order.php:378 +#: includes/class-wc-subscriptions-order.php:380 #. translators: placeholders are opening and closing link tags msgid "View the status of your subscription in %syour account%s." msgid_plural "View the status of your subscriptions in %syour account%s." msgstr[0] "" msgstr[1] "" -#: includes/class-wc-subscriptions-order.php:426 +#: includes/class-wc-subscriptions-order.php:428 msgid "Subscription Relationship" msgstr "" -#: includes/class-wc-subscriptions-order.php:446 +#: includes/class-wc-subscriptions-order.php:448 msgid "Renewal Order" msgstr "" -#: includes/class-wc-subscriptions-order.php:448 +#: includes/class-wc-subscriptions-order.php:450 msgid "Resubscribe Order" msgstr "" -#: includes/class-wc-subscriptions-order.php:450 +#: includes/class-wc-subscriptions-order.php:452 msgid "Parent Order" msgstr "" -#: includes/class-wc-subscriptions-order.php:673 +#: includes/class-wc-subscriptions-order.php:675 msgid "All orders types" msgstr "" -#: includes/class-wc-subscriptions-order.php:940 +#: includes/class-wc-subscriptions-order.php:963 #. translators: $1: opening link tag, $2: order number, $3: closing link tag msgid "Subscription cancelled for refunded order %1$s#%2$s%3$s." msgstr "" @@ -1904,12 +1908,12 @@ msgstr "" msgid "%1$s and a %2$s sign-up fee" msgstr "" -#: includes/class-wc-subscriptions-renewal-order.php:148 +#: includes/class-wc-subscriptions-renewal-order.php:152 #. translators: placeholder is order ID msgid "Order %s created to record renewal." msgstr "" -#: includes/class-wc-subscriptions-renewal-order.php:168 +#: includes/class-wc-subscriptions-renewal-order.php:172 msgid "Subscription renewal orders cannot be cancelled." msgstr "" @@ -2016,7 +2020,7 @@ msgstr "" #: includes/class-wc-subscriptions-switcher.php:404 #: includes/class-wc-subscriptions-switcher.php:430 -#: includes/class-wc-subscriptions-switcher.php:2311 +#: includes/class-wc-subscriptions-switcher.php:2323 msgid "Upgrade or Downgrade" msgstr "" @@ -2054,16 +2058,16 @@ msgstr "" msgid "There was an error locating the switch details." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1902 -#: includes/class-wc-subscriptions-switcher.php:2217 +#: includes/class-wc-subscriptions-switcher.php:1914 +#: includes/class-wc-subscriptions-switcher.php:2229 msgid "The original subscription item being switched cannot be found." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1904 +#: includes/class-wc-subscriptions-switcher.php:1916 msgid "The item on the switch order cannot be found." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:2255 +#: includes/class-wc-subscriptions-switcher.php:2267 msgid "Failed to update the subscription shipping method." msgstr "" @@ -2144,6 +2148,10 @@ msgstr "" msgid "View and manage subscriptions" msgstr "" +#: includes/class-wcs-cached-data-manager.php:198 +msgid "Weekly" +msgstr "" + #: includes/class-wcs-cart-initial-payment.php:56 #: includes/class-wcs-cart-renewal.php:156 msgid "That doesn't appear to be your order." @@ -2165,7 +2173,7 @@ msgstr "" msgid "Subscription #%s has not been added to the cart." msgstr "" -#: includes/class-wcs-cart-renewal.php:406 +#: includes/class-wcs-cart-renewal.php:409 msgid "" "We couldn't find the original subscription for an item in your cart. The " "item was removed." @@ -2175,7 +2183,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: includes/class-wcs-cart-renewal.php:413 +#: includes/class-wcs-cart-renewal.php:416 msgid "" "We couldn't find the original renewal order for an item in your cart. The " "item was removed." @@ -2185,7 +2193,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: includes/class-wcs-cart-renewal.php:688 +#: includes/class-wcs-cart-renewal.php:691 msgid "All linked subscription items have been removed from the cart." msgstr "" @@ -2216,26 +2224,26 @@ msgstr "" msgid "Please choose a valid payment gateway to change to." msgstr "" -#: includes/class-wcs-limiter.php:46 +#: includes/class-wcs-limiter.php:45 msgid "Limit subscription" msgstr "" -#: includes/class-wcs-limiter.php:48 +#: includes/class-wcs-limiter.php:47 #. translators: placeholders are opening and closing link tags msgid "" "Only allow a customer to have one subscription to this product. %sLearn " "more%s." msgstr "" -#: includes/class-wcs-limiter.php:50 +#: includes/class-wcs-limiter.php:49 msgid "Do not limit" msgstr "" -#: includes/class-wcs-limiter.php:51 +#: includes/class-wcs-limiter.php:50 msgid "Limit to one active subscription" msgstr "" -#: includes/class-wcs-limiter.php:52 +#: includes/class-wcs-limiter.php:51 msgid "Limit to one of any status" msgstr "" @@ -2713,18 +2721,22 @@ msgstr "" msgid "IPN subscription failing payment method changed." msgstr "" -#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:414 +#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:417 msgid "IPN subscription suspended." msgstr "" -#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:437 +#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:440 msgid "IPN subscription cancelled." msgstr "" -#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:453 +#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:456 msgid "IPN subscription payment failure." msgstr "" +#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:578 +msgid "Invalid PayPal IPN Payload: unable to find matching subscription." +msgstr "" + #: includes/gateways/paypal/includes/class-wcs-paypal-status-manager.php:42 msgid "Subscription cancelled with PayPal" msgstr "" @@ -2836,13 +2848,13 @@ msgstr "" msgid "No retries found in trash" msgstr "" -#: includes/upgrades/class-wc-subscriptions-upgrader.php:284 +#: includes/upgrades/class-wc-subscriptions-upgrader.php:292 #. translators: placeholder is a list of version numbers (e.g. "1.3 & 1.4 & #. 1.5") msgid "Database updated to version %s" msgstr "" -#: includes/upgrades/class-wc-subscriptions-upgrader.php:307 +#: includes/upgrades/class-wc-subscriptions-upgrader.php:315 #. translators: 1$: number of action scheduler hooks upgraded, 2$: #. "{execution_time}", will be replaced on front end with actual time msgid "" @@ -2850,24 +2862,24 @@ msgid "" "seconds)." msgstr "" -#: includes/upgrades/class-wc-subscriptions-upgrader.php:323 +#: includes/upgrades/class-wc-subscriptions-upgrader.php:331 #. translators: 1$: number of subscriptions upgraded, 2$: "{execution_time}", #. will be replaced on front end with actual time it took msgid "Migrated %1$s subscriptions to the new structure (in %2$s seconds)." msgstr "" -#: includes/upgrades/class-wc-subscriptions-upgrader.php:336 +#: includes/upgrades/class-wc-subscriptions-upgrader.php:344 #. translators: 1$: error message, 2$: opening link tag, 3$: closing link tag msgid "" "Unable to upgrade subscriptions.
Error: %1$s
Please refresh the " "page and try again. If problem persists, %2$scontact support%3$s." msgstr "" -#: includes/upgrades/class-wc-subscriptions-upgrader.php:591 +#: includes/upgrades/class-wc-subscriptions-upgrader.php:599 msgid "Welcome to WooCommerce Subscriptions 2.1" msgstr "" -#: includes/upgrades/class-wc-subscriptions-upgrader.php:591 +#: includes/upgrades/class-wc-subscriptions-upgrader.php:599 msgid "About WooCommerce Subscriptions" msgstr "" @@ -3498,21 +3510,21 @@ msgstr "" msgid "There was an error with the update. Please refresh the page and try again." msgstr "" -#: includes/wcs-cart-functions.php:76 includes/wcs-cart-functions.php:77 +#: includes/wcs-cart-functions.php:83 includes/wcs-cart-functions.php:84 msgid "Shipping via %s" msgstr "" -#: includes/wcs-cart-functions.php:96 +#: includes/wcs-cart-functions.php:103 msgid "Shipping" msgid_plural "Shipping %d" msgstr[0] "" msgstr[1] "" -#: includes/wcs-cart-functions.php:225 +#: includes/wcs-cart-functions.php:232 msgid "Free shipping coupon" msgstr "" -#: includes/wcs-cart-functions.php:329 +#: includes/wcs-cart-functions.php:336 #. translators: placeholder is a date msgid "First renewal: %s" msgstr "" @@ -3670,8 +3682,8 @@ msgid_plural "a %s-year" msgstr[0] "" msgstr[1] "" -#: includes/wcs-user-functions.php:283 -#: templates/single-product/add-to-cart/subscription.php:41 +#: includes/wcs-user-functions.php:299 +#: templates/single-product/add-to-cart/subscription.php:43 #: templates/single-product/add-to-cart/variable-subscription.php:29 msgid "Resubscribe" msgstr "" @@ -3949,7 +3961,7 @@ msgstr "" msgid "Are you sure you want remove this item from your subscription?" msgstr "" -#: templates/single-product/add-to-cart/subscription.php:43 +#: templates/single-product/add-to-cart/subscription.php:45 #: templates/single-product/add-to-cart/variable-subscription.php:31 msgid "You have an active subscription to this product already." msgstr "" @@ -4029,28 +4041,28 @@ msgid "" "can not be purchased at the same time." msgstr "" -#: woocommerce-subscriptions.php:549 woocommerce-subscriptions.php:566 +#: woocommerce-subscriptions.php:552 woocommerce-subscriptions.php:569 #. translators: placeholder is a number, this is for the teens #. translators: placeholder is a number, numbers ending in 4-9, 0 msgid "%sth" msgstr "" -#: woocommerce-subscriptions.php:554 +#: woocommerce-subscriptions.php:557 #. translators: placeholder is a number, numbers ending in 1 msgid "%sst" msgstr "" -#: woocommerce-subscriptions.php:558 +#: woocommerce-subscriptions.php:561 #. translators: placeholder is a number, numbers ending in 2 msgid "%snd" msgstr "" -#: woocommerce-subscriptions.php:562 +#: woocommerce-subscriptions.php:565 #. translators: placeholder is a number, numbers ending in 3 msgid "%srd" msgstr "" -#: woocommerce-subscriptions.php:592 +#: woocommerce-subscriptions.php:595 #. translators: 1$-2$: opening and closing tags, 3$-4$: link tags, #. takes to woocommerce plugin on wp.org, 5$-6$: opening and closing link tags, #. leads to plugins.php in admin @@ -4060,7 +4072,7 @@ msgid "" "%5$sinstall & activate WooCommerce »%6$s" msgstr "" -#: woocommerce-subscriptions.php:599 +#: woocommerce-subscriptions.php:602 #. translators: 1$-2$: opening and closing tags, 3$-4$: opening and #. closing link tags, leads to plugin admin msgid "" @@ -4069,11 +4081,11 @@ msgid "" "WooCommerce to version 2.4 or newer »%4$s" msgstr "" -#: woocommerce-subscriptions.php:625 +#: woocommerce-subscriptions.php:628 msgid "Variable Subscription" msgstr "" -#: woocommerce-subscriptions.php:812 +#: woocommerce-subscriptions.php:815 #. translators: 1$-2$: opening and closing tags, 3$-4$: opening and #. closing link tags. Leads to duplicate site article on docs msgid "" @@ -4083,19 +4095,19 @@ msgid "" "environment. %3$sLearn more »%4$s." msgstr "" -#: woocommerce-subscriptions.php:814 +#: woocommerce-subscriptions.php:817 msgid "Quit nagging me (but don't enable automatic payments)" msgstr "" -#: woocommerce-subscriptions.php:815 +#: woocommerce-subscriptions.php:818 msgid "Enable automatic payments" msgstr "" -#: woocommerce-subscriptions.php:993 +#: woocommerce-subscriptions.php:1024 msgid "Support" msgstr "" -#: woocommerce-subscriptions.php:1098 +#: woocommerce-subscriptions.php:1129 #. translators: placeholders are opening and closing tags. Leads to docs on #. version 2 msgid "" @@ -4106,14 +4118,14 @@ msgid "" "2.0 »%s" msgstr "" -#: woocommerce-subscriptions.php:1113 +#: woocommerce-subscriptions.php:1144 msgid "" "Warning! You are running version %s of WooCommerce Subscriptions plugin " "code but your database has been upgraded to Subscriptions version 2.0. This " "will cause major problems on your store." msgstr "" -#: woocommerce-subscriptions.php:1114 +#: woocommerce-subscriptions.php:1145 msgid "" "Please upgrade the WooCommerce Subscriptions plugin to version 2.0 or newer " "immediately. If you need assistance, after upgrading to Subscriptions v2.0, " @@ -4260,7 +4272,7 @@ msgstr "" #: includes/admin/class-wcs-admin-post-types.php:246 #: includes/admin/class-wcs-admin-post-types.php:459 #: includes/class-wc-subscriptions-manager.php:1771 -#: includes/wcs-user-functions.php:292 +#: includes/wcs-user-functions.php:308 #: templates/myaccount/related-orders.php:67 msgctxt "an action on a subscription" msgid "Cancel" @@ -4288,13 +4300,13 @@ msgctxt "Subscription title on admin table. (e.g.: #211 for John Doe)" msgid "%1$s#%2$s%3$s for %4$s" msgstr "" -#: includes/admin/class-wcs-admin-post-types.php:937 +#: includes/admin/class-wcs-admin-post-types.php:882 #. translators: placeholder is previous post title msgctxt "used in post updated messages" msgid "Subscription restored to revision from %s" msgstr "" -#: includes/admin/class-wcs-admin-post-types.php:942 +#: includes/admin/class-wcs-admin-post-types.php:887 msgctxt "used in \"Subscription scheduled for \"" msgid "M j, Y @ G:i" msgstr "" @@ -4334,7 +4346,7 @@ msgid "Gateway ID: [%s]" msgstr "" #: includes/admin/meta-boxes/views/html-related-orders-row.php:19 -#: includes/class-wc-subscriptions-renewal-order.php:145 +#: includes/class-wc-subscriptions-renewal-order.php:149 #: templates/myaccount/my-subscriptions.php:37 #: templates/myaccount/related-orders.php:39 #: templates/myaccount/related-subscriptions.php:32 @@ -4363,7 +4375,7 @@ msgctxt "table heading" msgid "Total" msgstr "" -#: includes/class-wcs-retry-manager.php:120 +#: includes/class-wcs-retry-manager.php:122 msgctxt "table heading" msgid "Renewal Payment Retry" msgstr "" @@ -4436,12 +4448,12 @@ msgctxt "API response confirming order note deleted from a subscription" msgid "Permanently deleted subscription note" msgstr "" -#: includes/class-wc-subscription.php:1168 +#: includes/class-wc-subscription.php:1166 msgctxt "original denotes there is no date to display" msgid "-" msgstr "" -#: includes/class-wc-subscription.php:2267 +#: includes/class-wc-subscription.php:2273 #. translators: placeholder is date type (e.g. "end", "next_payment"...) msgctxt "appears in an error message if date is wrong format" msgid "Invalid %s date. The date must be of the format: \"Y-m-d H:i:s\"." @@ -4471,7 +4483,7 @@ msgctxt "used in order note as reason for why subscription status changed" msgid "Subscription renewal payment due:" msgstr "" -#: includes/class-wcs-retry-manager.php:300 +#: includes/class-wcs-retry-manager.php:302 msgctxt "used in order note as reason for why subscription status changed" msgid "Subscription renewal payment retry:" msgstr "" @@ -4506,7 +4518,7 @@ msgctxt "Subscription status" msgid "On-hold" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:2452 wcs-functions.php:212 +#: includes/class-wc-subscriptions-switcher.php:2464 wcs-functions.php:212 msgctxt "Subscription status" msgid "Switched" msgstr "" @@ -4528,32 +4540,32 @@ msgctxt "used in a select box" msgid "%1$s-%2$s" msgstr "" -#: includes/class-wc-subscriptions-order.php:676 +#: includes/class-wc-subscriptions-order.php:678 msgctxt "An order type" msgid "Original" msgstr "" -#: includes/class-wc-subscriptions-order.php:677 +#: includes/class-wc-subscriptions-order.php:679 msgctxt "An order type" msgid "Subscription Parent" msgstr "" -#: includes/class-wc-subscriptions-order.php:678 +#: includes/class-wc-subscriptions-order.php:680 msgctxt "An order type" msgid "Subscription Renewal" msgstr "" -#: includes/class-wc-subscriptions-order.php:679 +#: includes/class-wc-subscriptions-order.php:681 msgctxt "An order type" msgid "Subscription Resubscribe" msgstr "" -#: includes/class-wc-subscriptions-order.php:680 +#: includes/class-wc-subscriptions-order.php:682 msgctxt "An order type" msgid "Subscription Switch" msgstr "" -#: includes/class-wc-subscriptions-order.php:681 +#: includes/class-wc-subscriptions-order.php:683 msgctxt "An order type" msgid "Non-subscription" msgstr "" @@ -4628,30 +4640,30 @@ msgctxt "when to prorate first payment / subscription length" msgid "For All Subscription Products" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1788 +#: includes/class-wc-subscriptions-switcher.php:1800 msgctxt "a switch order" msgid "Downgrade" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1791 +#: includes/class-wc-subscriptions-switcher.php:1803 msgctxt "a switch order" msgid "Upgrade" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1794 +#: includes/class-wc-subscriptions-switcher.php:1806 msgctxt "a switch order" msgid "Crossgrade" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1799 +#: includes/class-wc-subscriptions-switcher.php:1811 #. translators: %1: product subtotal, %2: HTML span tag, %3: direction #. (upgrade, downgrade, crossgrade), %4: closing HTML span tag msgctxt "product subtotal string" msgid "%1$s %2$s(%3$s)%4$s" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1915 -#: includes/class-wc-subscriptions-switcher.php:2228 +#: includes/class-wc-subscriptions-switcher.php:1927 +#: includes/class-wc-subscriptions-switcher.php:2240 #. translators: 1$: old item, 2$: new item when switching msgctxt "used in order notes" msgid "Customer switched from: %1$s to %2$s." @@ -4682,7 +4694,7 @@ msgctxt "input field placeholder for day field for annual subscriptions" msgid "Day" msgstr "" -#: includes/class-wcs-cart-renewal.php:717 +#: includes/class-wcs-cart-renewal.php:720 msgctxt "" "Used in WooCommerce by removed item notification: \"_All linked " "subscription items were_ removed. Undo?\" Filter for item title." @@ -4726,12 +4738,12 @@ msgctxt "used in order note" msgid "IPN subscription payment %s for reason: %s." msgstr "" -#: includes/class-wcs-retry-manager.php:230 +#: includes/class-wcs-retry-manager.php:232 msgctxt "used in order note as reason for why status changed" msgid "Retry rule applied:" msgstr "" -#: includes/class-wcs-retry-manager.php:296 +#: includes/class-wcs-retry-manager.php:298 msgctxt "used in order note as reason for why order status changed" msgid "Subscription renewal payment retry:" msgstr "" @@ -4954,21 +4966,21 @@ msgctxt "Admin menu name" msgid "Renewal Payment Retries" msgstr "" -#: includes/upgrades/class-wc-subscriptions-upgrader.php:295 +#: includes/upgrades/class-wc-subscriptions-upgrader.php:303 #. translators: placeholder is number of upgraded subscriptions msgctxt "used in the subscriptions upgrader" msgid "Marked %s subscription products as \"sold individually\"." msgstr "" -#: includes/upgrades/class-wc-subscriptions-upgrader.php:326 -#: includes/upgrades/class-wc-subscriptions-upgrader.php:376 +#: includes/upgrades/class-wc-subscriptions-upgrader.php:334 +#: includes/upgrades/class-wc-subscriptions-upgrader.php:384 #. translators: placeholder is "{time_left}", will be replaced on front end #. with actual time msgctxt "Message that gets sent to front end." msgid "Estimated time left (minutes:seconds): %s" msgstr "" -#: includes/upgrades/class-wc-subscriptions-upgrader.php:355 +#: includes/upgrades/class-wc-subscriptions-upgrader.php:363 #. translators: placeholder is the number of subscriptions repaired msgctxt "Repair message that gets sent to front end." msgid "" @@ -4976,7 +4988,7 @@ msgid "" "customer notes." msgstr "" -#: includes/upgrades/class-wc-subscriptions-upgrader.php:361 +#: includes/upgrades/class-wc-subscriptions-upgrader.php:369 #. translators: placeholder is number of subscriptions that were checked and #. did not need repairs. There's a space at the beginning! msgctxt "Repair message that gets sent to front end." @@ -4985,14 +4997,14 @@ msgid_plural "%d other subscriptions were checked and did not need any repairs." msgstr[0] "" msgstr[1] "" -#: includes/upgrades/class-wc-subscriptions-upgrader.php:365 +#: includes/upgrades/class-wc-subscriptions-upgrader.php:373 #. translators: placeholder is "{execution_time}", which will be replaced on #. front end with actual time msgctxt "Repair message that gets sent to front end." msgid "(in %s seconds)" msgstr "" -#: includes/upgrades/class-wc-subscriptions-upgrader.php:368 +#: includes/upgrades/class-wc-subscriptions-upgrader.php:376 #. translators: $1: "Repaired x subs with incorrect dates...", $2: "X others #. were checked and no repair needed", $3: "(in X seconds)". Ordering for RTL #. languages. @@ -5000,7 +5012,7 @@ msgctxt "The assembled repair message that gets sent to front end." msgid "%1$s%2$s %3$s" msgstr "" -#: includes/upgrades/class-wc-subscriptions-upgrader.php:387 +#: includes/upgrades/class-wc-subscriptions-upgrader.php:395 #. translators: 1$: error message, 2$: opening link tag, 3$: closing link tag msgctxt "Error message that gets sent to front end when upgrading Subscriptions" msgid "" @@ -5009,7 +5021,7 @@ msgid "" msgstr "" #: includes/upgrades/templates/wcs-about-2-0.php:36 -#: woocommerce-subscriptions.php:992 +#: woocommerce-subscriptions.php:1023 msgctxt "short for documents" msgid "Docs" msgstr "" @@ -5049,12 +5061,12 @@ msgctxt "text on submit button" msgid "Update Database" msgstr "" -#: includes/wcs-cart-functions.php:185 +#: includes/wcs-cart-functions.php:192 msgctxt "shipping method price" msgid "Free" msgstr "" -#: includes/wcs-cart-functions.php:260 +#: includes/wcs-cart-functions.php:267 #. translators: placeholder is price string, denotes tax included in cart/order #. total msgctxt "includes tax" diff --git a/templates/myaccount/related-orders.php b/templates/myaccount/related-orders.php index 72415ba..1210ea2 100644 --- a/templates/myaccount/related-orders.php +++ b/templates/myaccount/related-orders.php @@ -54,7 +54,7 @@ if ( ! defined( 'ABSPATH' ) ) {
- get_sku() ) { - $item_name .= $_product->get_sku() . ' - '; - } - - $item_name .= apply_filters( 'woocommerce_order_item_name', $item['name'], $item ); - $item_name = esc_html( $item_name ); - - if ( $_product ) { - $item_name = sprintf( '%s', get_edit_post_link( $_product->get_id() ), $item_name ); - } - - echo wp_kses( $item_name, array( 'a' => array( 'href' => array() ) ) ); - if ( $item_meta_html ) { - echo wcs_help_tip( $item_meta_html ); - } ?> -
+ array( 'href' => array() ) ) ); + + if ( $item_meta_html ) { + echo wcs_help_tip( $item_meta_html ); + } ?> +
get_status(), apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'failed' ), $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() ) { $actions['pay'] = array( 'url' => $order->get_checkout_payment_url(), 'name' => esc_html_x( 'Pay', 'pay for a subscription', 'woocommerce-subscriptions' ), diff --git a/templates/myaccount/view-subscription.php b/templates/myaccount/view-subscription.php index 7cc6331..acb2252 100644 --- a/templates/myaccount/view-subscription.php +++ b/templates/myaccount/view-subscription.php @@ -111,9 +111,9 @@ wc_print_notices(); is_visible() ) { - echo esc_html( apply_filters( 'woocommerce_order_item_name', $item['name'], $item ) ); + echo esc_html( apply_filters( 'woocommerce_order_item_name', $item['name'], $item, false ) ); } else { - echo wp_kses_post( apply_filters( 'woocommerce_order_item_name', sprintf( '%s', get_permalink( $item['product_id'] ), $item['name'] ), $item ) ); + echo wp_kses_post( apply_filters( 'woocommerce_order_item_name', sprintf( '%s', get_permalink( $item['product_id'] ), $item['name'] ), $item, false ) ); } echo wp_kses_post( apply_filters( 'woocommerce_order_item_quantity_html', ' ' . sprintf( '× %s', $item['qty'] ) . '', $item ) ); diff --git a/templates/single-product/add-to-cart/subscription.php b/templates/single-product/add-to-cart/subscription.php index 2c41725..c3bee91 100644 --- a/templates/single-product/add-to-cart/subscription.php +++ b/templates/single-product/add-to-cart/subscription.php @@ -20,11 +20,13 @@ if ( ! $product->is_purchasable() && ( ! is_user_logged_in() || 'no' == wcs_get_ $user_id = get_current_user_id(); -// Availability -$availability = $product->get_availability(); - -if ( $availability['availability'] ) : - echo wp_kses_post( apply_filters( 'woocommerce_stock_html', '

'.$availability['availability'].'

', $availability['availability'] ) ); +if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) : + $availability = $product->get_availability(); + if ( $availability['availability'] ) : + echo wp_kses_post( apply_filters( 'woocommerce_stock_html', '

'.$availability['availability'].'

', $availability['availability'] ) ); + endif; +else : + echo wp_kses_post( wc_get_stock_html( $product ) ); endif; if ( ! $product->is_in_stock() ) : ?> diff --git a/woocommerce-subscriptions.php b/woocommerce-subscriptions.php index bff36a1..a5cd2d5 100644 --- a/woocommerce-subscriptions.php +++ b/woocommerce-subscriptions.php @@ -5,9 +5,9 @@ * Description: Sell products and services with recurring payments in your WooCommerce Store. * Author: Prospress Inc. * Author URI: http://prospress.com/ - * Version: 2.2.7 + * Version: 2.2.10 * - * Copyright 2016 Prospress, Inc. (email : freedoms@prospress.com) + * Copyright 2017 Prospress, Inc. (email : freedoms@prospress.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -126,7 +126,7 @@ class WC_Subscriptions { public static $plugin_file = __FILE__; - public static $version = '2.2.7'; + public static $version = '2.2.10'; private static $total_subscription_count = null; @@ -414,9 +414,12 @@ class WC_Subscriptions { self::add_notice( __( 'A subscription has been removed from your cart. Products and subscriptions can not be purchased at the same time.', 'woocommerce-subscriptions' ), 'notice' ); - // Redirect to cart page to remove subscription & notify shopper - add_filter( 'add_to_cart_fragments', __CLASS__ . '::redirect_ajax_add_to_cart' ); - + if ( WC_Subscriptions::is_woocommerce_pre( '3.0.8' ) ) { + // Redirect to cart page to remove subscription & notify shopper + add_filter( 'add_to_cart_fragments', __CLASS__ . '::redirect_ajax_add_to_cart' ); + } else { + add_filter( 'woocommerce_add_to_cart_fragments', __CLASS__ . '::redirect_ajax_add_to_cart' ); + } } return $valid; @@ -973,7 +976,35 @@ class WC_Subscriptions { */ public static function is_duplicate_site() { - $is_duplicate = ( get_site_url() !== self::get_site_url() ) ? true : false; + if ( defined( 'WP_SITEURL' ) ) { + $site_url = WP_SITEURL; + } else { + $site_url = get_site_url(); + } + + $wp_site_url_parts = wp_parse_url( $site_url ); + $wcs_site_url_parts = wp_parse_url( self::get_site_url() ); + + if ( ! isset( $wp_site_url_parts['path'] ) && ! isset( $wcs_site_url_parts['path'] ) ) { + $paths_match = true; + } elseif ( isset( $wp_site_url_parts['path'] ) && isset( $wcs_site_url_parts['path'] ) && $wp_site_url_parts['path'] == $wcs_site_url_parts['path'] ) { + $paths_match = true; + } else { + $paths_match = false; + } + + if ( isset( $wp_site_url_parts['host'] ) && isset( $wcs_site_url_parts['host'] ) && $wp_site_url_parts['host'] == $wcs_site_url_parts['host'] ) { + $hosts_match = true; + } else { + $hosts_match = false; + } + + // Check the host and path, do not check the protocol/scheme to avoid issues with WP Engine and other occasions where the WP_SITEURL constant may be set, but being overridden (e.g. by FORCE_SSL_ADMIN) + if ( $paths_match && $hosts_match ) { + $is_duplicate = false; + } else { + $is_duplicate = true; + } return apply_filters( 'woocommerce_subscriptions_is_duplicate_site', $is_duplicate ); }