diff --git a/assets/css/about.css b/assets/css/about.css old mode 100755 new mode 100644 diff --git a/assets/css/admin.css b/assets/css/admin.css old mode 100755 new mode 100644 diff --git a/assets/css/checkout.css b/assets/css/checkout.css old mode 100755 new mode 100644 diff --git a/assets/css/dashboard.css b/assets/css/dashboard.css old mode 100755 new mode 100644 diff --git a/assets/css/view-subscription.css b/assets/css/view-subscription.css old mode 100755 new mode 100644 diff --git a/assets/css/wcs-upgrade.css b/assets/css/wcs-upgrade.css old mode 100755 new mode 100644 diff --git a/assets/images/add-edit-subscription-screen.png b/assets/images/add-edit-subscription-screen.png old mode 100755 new mode 100644 diff --git a/assets/images/admin-change-payment-method.jpg b/assets/images/admin-change-payment-method.jpg old mode 100755 new mode 100644 diff --git a/assets/images/ajax-loader.gif b/assets/images/ajax-loader.gif old mode 100755 new mode 100644 diff --git a/assets/images/ajax-loader@2x.gif b/assets/images/ajax-loader@2x.gif old mode 100755 new mode 100644 diff --git a/assets/images/billing-schedules-meta-box.png b/assets/images/billing-schedules-meta-box.png old mode 100755 new mode 100644 diff --git a/assets/images/checkout-recurring-totals.png b/assets/images/checkout-recurring-totals.png old mode 100755 new mode 100644 diff --git a/assets/images/drip-downloadable-content.jpg b/assets/images/drip-downloadable-content.jpg old mode 100755 new mode 100644 diff --git a/assets/images/gift-subscription.png b/assets/images/gift-subscription.png old mode 100755 new mode 100644 diff --git a/assets/images/renewal-retry-settings.png b/assets/images/renewal-retry-settings.png old mode 100755 new mode 100644 diff --git a/assets/images/subscribe-all-the-things.png b/assets/images/subscribe-all-the-things.png old mode 100755 new mode 100644 diff --git a/assets/images/subscription-reports.png b/assets/images/subscription-reports.png old mode 100755 new mode 100644 diff --git a/assets/images/subscription-suspended-email.jpg b/assets/images/subscription-suspended-email.jpg old mode 100755 new mode 100644 diff --git a/assets/images/subscriptions-importer-exporter.png b/assets/images/subscriptions-importer-exporter.png old mode 100755 new mode 100644 diff --git a/assets/images/view-subscription.png b/assets/images/view-subscription.png old mode 100755 new mode 100644 diff --git a/assets/images/woocommerce_subscriptions_logo.png b/assets/images/woocommerce_subscriptions_logo.png old mode 100755 new mode 100644 diff --git a/assets/js/admin/admin-pointers.js b/assets/js/admin/admin-pointers.js old mode 100755 new mode 100644 diff --git a/assets/js/admin/admin.js b/assets/js/admin/admin.js old mode 100755 new mode 100644 index 7c0b49b..d0eaff7 --- a/assets/js/admin/admin.js +++ b/assets/js/admin/admin.js @@ -312,7 +312,7 @@ jQuery(document).ready(function($){ }); // if we haven't found a variation synced or with a trial at this point check the backend for other product variations - if ( ( number_of_pages > 1 || 0 == variations.size() ) && false == is_synced_or_has_trial ) { + if ( ( number_of_pages > 1 || 0 == variations.length ) && false == is_synced_or_has_trial ) { var data = { action: 'wcs_product_has_trial_or_is_synced', diff --git a/assets/js/admin/jquery.flot.axislabels.js b/assets/js/admin/jquery.flot.axislabels.js old mode 100755 new mode 100644 diff --git a/assets/js/admin/jquery.flot.axislabels.min.js b/assets/js/admin/jquery.flot.axislabels.min.js old mode 100755 new mode 100644 diff --git a/assets/js/admin/jquery.flot.orderBars.js b/assets/js/admin/jquery.flot.orderBars.js old mode 100755 new mode 100644 diff --git a/assets/js/admin/jquery.flot.orderBars.min.js b/assets/js/admin/jquery.flot.orderBars.min.js old mode 100755 new mode 100644 diff --git a/assets/js/admin/jstz.js b/assets/js/admin/jstz.js old mode 100755 new mode 100644 diff --git a/assets/js/admin/jstz.min.js b/assets/js/admin/jstz.min.js old mode 100755 new mode 100644 diff --git a/assets/js/admin/meta-boxes-coupon.js b/assets/js/admin/meta-boxes-coupon.js old mode 100755 new mode 100644 diff --git a/assets/js/admin/meta-boxes-subscription.js b/assets/js/admin/meta-boxes-subscription.js old mode 100755 new mode 100644 index 871a3bd..bfc00cc --- a/assets/js/admin/meta-boxes-subscription.js +++ b/assets/js/admin/meta-boxes-subscription.js @@ -58,21 +58,8 @@ jQuery(document).ready(function($){ seconds: one_hour_from_now.format( 'ss' ) }); - - // Make sure start date is before now - if ( 'start' == date_type ) { - - if ( false === chosen_date.isBefore( time_now ) ) { - alert( wcs_admin_meta_boxes.i18n_start_date_notice ); - $date_input.val( time_now.year() + '-' + ( zeroise( time_now.months() + 1 ) ) + '-' + ( time_now.format( 'DD' ) ) ); - $hour_input.val( time_now.format( 'HH' ) ); - $minute_input.val( time_now.format( 'mm' ) ); - } - - } - // Make sure trial end and next payment are after start date - else if ( ( 'trial_end' == date_type || 'next_payment' == date_type ) && '' != $( '#start_timestamp_utc' ).val() ) { + if ( ( 'trial_end' == date_type || 'next_payment' == date_type ) && '' != $( '#start_timestamp_utc' ).val() ) { var change_date = false, start = moment.unix( $('#start_timestamp_utc').val() ); diff --git a/assets/js/admin/moment.js b/assets/js/admin/moment.js old mode 100755 new mode 100644 diff --git a/assets/js/admin/moment.min.js b/assets/js/admin/moment.min.js old mode 100755 new mode 100644 diff --git a/assets/js/admin/reports.js b/assets/js/admin/reports.js old mode 100755 new mode 100644 diff --git a/assets/js/admin/wcs-meta-boxes-order.js b/assets/js/admin/wcs-meta-boxes-order.js old mode 100755 new mode 100644 diff --git a/assets/js/frontend/single-product.js b/assets/js/frontend/single-product.js new file mode 100644 index 0000000..12b0d6c --- /dev/null +++ b/assets/js/frontend/single-product.js @@ -0,0 +1,52 @@ +(function ( document, $ ) { + + var $cache = {}; + + /** + * Cache our DOM selectors. + */ + function generate_cache() { + $cache.document = $( document ); + $cache.first_payment_date = $( '.first-payment-date' ); + $cache.is_variable_subscription = 0 < $( 'div.product-type-variable-subscription' ).length; + } + + /** + * Attach DOM events. + */ + function attach_events() { + if ( $cache.is_variable_subscription ) { + $cache.document.on( 'found_variation', update_first_payment_element ); + $cache.document.on( 'reset_data', clear_first_payment_element ); + } + } + + /** + * Update the variation's first payment element. + * + * @param {jQuery.Event} event + * @param {object} variation_data + */ + function update_first_payment_element( event, variation_data ) { + $cache.first_payment_date.html( variation_data.first_payment_html ); + } + + /** + * Clear the variation's first payment element. + */ + function clear_first_payment_element() { + $cache.first_payment_date.html( '' ); + } + + /** + * Initialise. + */ + function init() { + generate_cache(); + attach_events(); + } + + $( init ); + +})( document, jQuery ); + diff --git a/assets/js/wcs-upgrade.js b/assets/js/wcs-upgrade.js old mode 100755 new mode 100644 diff --git a/changelog.txt b/changelog.txt old mode 100755 new mode 100644 index e7807c5..ba82742 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,70 @@ *** WooCommerce Subscriptions Changelog *** +2018.11.28 - version 2.4.5 +* Fix: Fix caching on data for the Upcoming Recurring Revenue and Subscriptions By Date reports. PR#3069. +* Fix: Fix compatibility with Disable Report Cache Updates mini-plugin. PR#3069. +* Fix: Make Stripe source ID unique while setting through REST API. PR#3090. +* Fix: Display synced variation product's next payment date after the variation is selected by a customer on the product page. PR#3094. +* Fix: Activate subscription after processing an IPN on parent orders previously processed by PDT. PR#3097. +* Tweak: Update incomplete retry migration status label styling and wording to "In-Progress" to avoid confusing in-progress migrations as being a problem. PR#3088 + +2018.11.20 - version 2.4.4 +* Fix: Prevent fatal errors when the retry migration doesn't receive a valid retry object. PR#3042 +* Fix: Fix PHP notices in subscription events by date report. PR#3078 +* Fix: Don't trigger payment gateways while processing retries on staging sites. PR#2870 +* Fix: Apply coupons and discounts to initial payment and early renewal carts. PR#3048 +* Fix: Use the correct `for` attribute in the variable_subscription_trial_period label element. PR#3081 +* Fix: [PayPal Standard] Prevent creating multiple orders to record a single sign up transaction that was previously handled by a PDT request. PR#3074 +* Tweak: Remove the nonce from early renewal urls. PR#3085 + +2018.11.13 - version 2.4.3 +* Fix: Pass the quantity argument to woocommerce_add_cart_item_data filter. PR#3051 +* Dev: Update Action Scheduler to 2.1.1. PR#3059 +* Dev: Add a new filter (`wcs_remove_fees_from_initial_cart`) which can be used to override the removal of fees from the initial cart. PR#3046 + +2018.10.29 - version 2.4.2 +* Fix: Always get the subscription's customer user from post meta. Fixes issues on sites running WC 3.5 where the subscription customer was incorrectly being set to an admin user. PR#3037 +* Fix: Use the current timestamp of site's timezone for comparison during synchronisation first payment date calculations. PR#3003 +* Fix: Sort the user subscriptions cache to ensure subscriptions appear in the correct order on the My Account > Subscriptions page. PR#2965 +* Fix: Correctly account for tax inclusive sign up fees for synced products. PR#2818 +* Fix: Fix PHP 7.1.x warnings related to get_sign_up_fee() and product variations. PR#3014 +* Fix: Use the correct breadcrumbs on "change payment method" and "change address" pages. PR#3008 +* Fix: Fix product purchasability issues when switching between limited variable products. PR#3007 +* Fix: Remove the erasure tools link from the description of the erasure settings for users without the manage_privacy_options capability. PR#3004 +* Dev: Add switch proration filters. PR#3011 +* Dev: Use admin_created_subscription hook to generate download permissions. PR#3024 +* Dev: Deprecated wcs_get_sites_timezone() fixes get_local_timezone() deprecated warnings. PR#3032 + +2018.10.16 - version 2.4.1 +* Fix: Cache the retry data needs migration logic to avoid a get_posts() call on every request. PR#2994 +* Fix: Load the retry store instance on WordPress init rather than on Subscriptions plugin load. Fixes errors with third-party plugins hooking into get post queries and calling undeclared WP functions. PR#3000 +* Fix: Fix an undefined product ID error which could occur during a switch request when trying to access a product from the old subscription format. PR#2961 +* Fix: Fix a missing closing `a` tag in the downgrade notice. PR#3002 +* Dev: Add a new hook ('woocommerce_admin_created_subscription') which is triggered after a subscription is created manually via the admin screen. PR#2988 +* Dev: Add new hooks to allow third-parties to customize subscription filtering behavior on the admin subscriptions table. PR#2989 + +2018.10.09 - version 2.4.0 +* New: Update Action Scheduler to v2.1.0. +* Fix: Remove the URL scheme when generating the site's duplicate lock URL. Fixes an issue where staging sites with a URL length equal the scheme would incorrectly not trigger staging mode. PR#2599 +* Fix: Move callbacks attached specifically for renewal carts to the constructor so that all extended cart classes are not hooked on. PR#2963 +* Fix: Allow products which are password protected to be manually renewed via the cart. PR#2912 +* Fix: Remove unnecessary leading space in translation string. PR#2976 +* Fix: Add group by clause to subscriber count reports query. PR#2970 +* Fix: Fix an error which would occur when calling the deprecated get_related_orders_query(). PR#2954 +* Tweak: Separate a subscription start from the creation date. PR#2594 +* Tweak: Display the expected site URL in staging site notice and system status. PR#2950 +* Dev: Implement an Autoloader for loading plugin class files. PR#2412 +* Dev: Replace WC_Logger type hinting in function signatures to use lower level WC_Logger_Interface. PR#2902 +* Dev: Remove switch_line_items_pre_2_1_2() and switch_shipping_line_items_pre_2_1_2() functions. PR#2900 +* Dev: [WC 3.5] Move subscription customer ID's into the post author column on sites running WC 3.5 and above. PR#2559 +* Dev: Move payment retry data to custom tables. PR#2753 +* Dev: Update file inclusions to use relative paths. PR#2936 +* Dev: Add an API for third-parties to register their custom subscription date types. PR#2840 +* Dev: Update the hook used for adding Subscriptions Settings. PR#2939 +* Dev: Add a 'wcs_payment_gateways_change_payment_method' filter to allow third-parties to filter which payment methods are displayed when changing payment methods. PR#2571 +* Dev: Trigger a new 'wcs_before_replace_pay_shortcode' action before displaying the change payment shortcode. +* Dev: Add the 'wcs_update_all_subscriptions_addresses_checked' filter to allow third-parties to filter the default checked status of the all_subscriptions_addresses field. PR#2948 + 2018.09.25 - version 2.3.7 * Fix: Don't apply subscription payment gateway filters while on the order-pay page. PR#2927 * Fix: Prevent admin users from changing product type when the product has active subscriptions. PR#2932 diff --git a/composer.lock b/composer.lock old mode 100755 new mode 100644 diff --git a/includes/abstracts/abstract-wcs-background-updater.php b/includes/abstracts/abstract-wcs-background-updater.php old mode 100755 new mode 100644 index 513cd7f..a251cf7 --- a/includes/abstracts/abstract-wcs-background-updater.php +++ b/includes/abstracts/abstract-wcs-background-updater.php @@ -125,8 +125,8 @@ abstract class WCS_Background_Updater { * Schedule the instance's hook to run in $this->time_limit seconds, if it's not already scheduled. */ protected function schedule_background_update() { - if ( false === wc_next_scheduled_action( $this->scheduled_hook ) ) { - wc_schedule_single_action( gmdate( 'U' ) + $this->time_limit, $this->scheduled_hook ); + if ( false === as_next_scheduled_action( $this->scheduled_hook ) ) { + as_schedule_single_action( gmdate( 'U' ) + $this->time_limit, $this->scheduled_hook ); } } @@ -134,7 +134,7 @@ abstract class WCS_Background_Updater { * Unschedule the instance's hook in Action Scheduler */ protected function unschedule_background_updates() { - wc_unschedule_action( $this->scheduled_hook ); + as_unschedule_action( $this->scheduled_hook ); } /** diff --git a/includes/abstracts/abstract-wcs-background-upgrader.php b/includes/abstracts/abstract-wcs-background-upgrader.php old mode 100755 new mode 100644 index 8b1bf2e..f816ea0 --- a/includes/abstracts/abstract-wcs-background-upgrader.php +++ b/includes/abstracts/abstract-wcs-background-upgrader.php @@ -20,7 +20,7 @@ abstract class WCS_Background_Upgrader extends WCS_Background_Updater { /** * WC Logger instance for logging messages. * - * @var WC_Logger + * @var WC_Logger_Interface */ protected $logger; diff --git a/includes/abstracts/abstract-wcs-cache-manager.php b/includes/abstracts/abstract-wcs-cache-manager.php old mode 100755 new mode 100644 diff --git a/includes/abstracts/abstract-wcs-customer-store.php b/includes/abstracts/abstract-wcs-customer-store.php old mode 100755 new mode 100644 diff --git a/includes/abstracts/abstract-wcs-debug-tool-cache-updater.php b/includes/abstracts/abstract-wcs-debug-tool-cache-updater.php old mode 100755 new mode 100644 diff --git a/includes/abstracts/abstract-wcs-debug-tool.php b/includes/abstracts/abstract-wcs-debug-tool.php old mode 100755 new mode 100644 diff --git a/includes/abstracts/abstract-wcs-dynamic-hook-deprecator.php b/includes/abstracts/abstract-wcs-dynamic-hook-deprecator.php old mode 100755 new mode 100644 diff --git a/includes/abstracts/abstract-wcs-hook-deprecator.php b/includes/abstracts/abstract-wcs-hook-deprecator.php old mode 100755 new mode 100644 diff --git a/includes/abstracts/abstract-wcs-migrator.php b/includes/abstracts/abstract-wcs-migrator.php new file mode 100644 index 0000000..c86fe4f --- /dev/null +++ b/includes/abstracts/abstract-wcs-migrator.php @@ -0,0 +1,133 @@ +source_store = $source_store; + $this->destination_store = $destination_store; + $this->logger = $logger; + } + + /** + * Should this entry be migrated. + * + * @param int $entry_id + * + * @return bool + * @since 2.4 + */ + abstract public function should_migrate_entry( $entry_id ); + + /** + * Gets the item from the source store. + * + * @param int $entry_id + * + * @return mixed + * @since 2.4 + */ + abstract public function get_source_store_entry( $entry_id ); + + /** + * save the item to the destination store. + * + * @param int $entry_id + * + * @return mixed + * @since 2.4 + */ + abstract public function save_destination_store_entry( $entry_id ); + + /** + * deletes the item from the source store. + * + * @param int $entry_id + * + * @return mixed + * @since 2.4 + */ + abstract public function delete_source_store_entry( $entry_id ); + + /** + * Runs after a entry has been migrated. + * + * @param int $old_entry_id + * @param mixed $new_entry + * + * @return mixed + */ + abstract protected function migrated_entry( $old_entry_id, $new_entry ); + + /** + * Migrates our entry. + * + * @param int $entry_id + * + * @return mixed + * @since 2.4 + */ + public function migrate_entry( $entry_id ) { + $source_store_item = $this->get_source_store_entry( $entry_id ); + if ( $source_store_item ) { + $destination_store_item = $this->save_destination_store_entry( $entry_id ); + $this->delete_source_store_entry( $entry_id ); + + $this->migrated_entry( $entry_id, $destination_store_item ); + + return $destination_store_item; + } + + return false; + } + + /** + * Add a message to the log + * + * @param string $message The message to be logged + * + * @since 2.4.0 + */ + protected function log( $message ) { + $this->logger->add( $this->log_handle, $message ); + } +} diff --git a/includes/abstracts/abstract-wcs-related-order-store.php b/includes/abstracts/abstract-wcs-related-order-store.php old mode 100755 new mode 100644 diff --git a/includes/abstracts/abstract-wcs-retry-store.php b/includes/abstracts/abstract-wcs-retry-store.php old mode 100755 new mode 100644 index 05d7d88..b1cb4a5 --- a/includes/abstracts/abstract-wcs-retry-store.php +++ b/includes/abstracts/abstract-wcs-retry-store.php @@ -2,11 +2,11 @@ /** * An interface for creating a store for retry details. * - * @package WooCommerce Subscriptions - * @subpackage WCS_Retry_Store - * @category Class - * @author Prospress - * @since 2.1 + * @package WooCommerce Subscriptions + * @subpackage WCS_Retry_Store + * @category Class + * @author Prospress + * @since 2.1 */ abstract class WCS_Retry_Store { @@ -18,6 +18,7 @@ abstract class WCS_Retry_Store { * Save the details of a retry to the database * * @param WCS_Retry $retry + * * @return int the retry's ID */ abstract public function save( WCS_Retry $retry ); @@ -26,27 +27,56 @@ abstract class WCS_Retry_Store { * Get the details of a retry from the database * * @param int $retry_id + * * @return WCS_Retry */ abstract public function get_retry( $retry_id ); + /** + * Deletes a retry. + * + * @param int $retry_id + * + * @since 2.4 + */ + public function delete_retry( $retry_id ) { + wcs_doing_it_wrong( __FUNCTION__, sprintf( "Method '%s' must be overridden.", __METHOD__ ), '2.4' ); + } + /** * Get a set of retries from the database * - * @param array $args A set of filters: - * 'status': filter to only retries of a certain status, either 'pending', 'processing', 'failed' or 'complete'. Default: 'any', which will return all retries. - * 'date_query': array of dates to filter retries those that occur 'after' or 'before' a certain (or inbetween those two dates). Should be a MySQL formated date/time string. - * @return array An array of WCS_Retry objects + * @param array $args A set of filters: + * 'status': filter to only retries of a certain status, either 'pending', 'processing', 'failed' or 'complete'. Default: 'any', which will return all retries. + * 'date_query': array of dates to filter retries to those that occur 'after' or 'before' a certain date (or between those two dates). Should be a MySQL formated date/time string. + * 'orderby': Order by which property? + * 'order': Order in ASC/DESC. + * 'order_id': filter retries to those which belong to a certain order ID. + * 'limit': How many retries we want to get. + * @param string $return Defines in which format return the entries. options: + * 'objects': Returns an array of WCS_Retry objects + * 'ids': Returns an array of ids. + * + * @return array An array of WCS_Retry objects or ids. + * @since 2.4 */ - abstract public function get_retries( $args ); + abstract public function get_retries( $args = array(), $return = 'objects' ); /** * Get the IDs of all retries from the database for a given order * * @param int $order_id + * * @return array + * @since 2.4 */ - abstract protected function get_retry_ids_for_order( $order_id ); + public function get_retry_ids_for_order( $order_id ) { + return array_values( $this->get_retries( array( + 'order_id' => $order_id, + 'orderby' => 'ID', + 'order' => 'ASC', + ), 'ids' ) ); + } /** * Setup the class, if required @@ -59,34 +89,28 @@ abstract class WCS_Retry_Store { * Get the details of all retries (if any) for a given order * * @param int $order_id + * * @return array */ public function get_retries_for_order( $order_id ) { - - $retries = array(); - - foreach ( $this->get_retry_ids_for_order( $order_id ) as $retry_id ) { - $retries[ $retry_id ] = $this->get_retry( $retry_id ); - } - - return $retries; + return $this->get_retries( array( 'order_id' => $order_id ) ); } /** * Get the details of the last retry (if any) recorded for a given order * * @param int $order_id + * * @return WCS_Retry | null */ public function get_last_retry_for_order( $order_id ) { - $retry_ids = $this->get_retry_ids_for_order( $order_id ); + $retry_ids = $this->get_retry_ids_for_order( $order_id ); + $last_retry = null; if ( ! empty( $retry_ids ) ) { $last_retry_id = array_pop( $retry_ids ); $last_retry = $this->get_retry( $last_retry_id ); - } else { - $last_retry = null; } return $last_retry; @@ -96,6 +120,7 @@ abstract class WCS_Retry_Store { * Get the number of retries stored in the database for a given order * * @param int $order_id + * * @return int */ public function get_retry_count_for_order( $order_id ) { diff --git a/includes/abstracts/abstract-wcs-scheduler.php b/includes/abstracts/abstract-wcs-scheduler.php old mode 100755 new mode 100644 diff --git a/includes/abstracts/abstract-wcs-table-maker.php b/includes/abstracts/abstract-wcs-table-maker.php new file mode 100644 index 0000000..7925530 --- /dev/null +++ b/includes/abstracts/abstract-wcs-table-maker.php @@ -0,0 +1,128 @@ +tables as $table ) { + $wpdb->tables[] = $table; + $name = $this->get_full_table_name( $table ); + $wpdb->$table = $name; + } + + // create the tables + if ( $this->schema_update_required() ) { + foreach ( $this->tables as $table ) { + $this->update_table( $table ); + } + $this->mark_schema_update_complete(); + } + } + + /** + * @param string $table The name of the table + * + * @return string The CREATE TABLE statement, suitable for passing to dbDelta + */ + abstract protected function get_table_definition( $table ); + + /** + * Determine if the database schema is out of date + * by comparing the integer found in $this->schema_version + * with the option set in the WordPress options table + * + * @return bool + */ + private function schema_update_required() { + $version_found_in_db = $this->get_schema_option(); + + return version_compare( $version_found_in_db, $this->schema_version, '<' ); + } + + /** + * Gets the schema version name. + * + * @return string + */ + private function get_schema_option_name() { + return 'wcs-schema-' . get_class( $this ); + } + + /** + * Gets the schema version we have. + * + * @return mixed + */ + private function get_schema_option() { + return get_option( $this->get_schema_option_name(), 0 ); + } + + /** + * Update the option in WordPress to indicate that + * our schema is now up to date + */ + private function mark_schema_update_complete() { + $option_name = $this->get_schema_option_name(); + + // work around race conditions and ensure that our option updates + $value_to_save = (string) $this->schema_version . '.0.' . time(); + + update_option( $option_name, $value_to_save ); + } + + /** + * Update the schema for the given table + * + * @param string $table The name of the table to update + */ + private function update_table( $table ) { + require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); + $definition = $this->get_table_definition( $table ); + if ( $definition ) { + $updated = dbDelta( $definition ); + foreach ( $updated as $updated_table => $update_description ) { + if ( strpos( $update_description, 'Created table' ) === 0 ) { + do_action( 'wcs_created_table', $updated_table, $table ); + } + } + } + } + + /** + * @param string $table + * + * @return string The full name of the table, including the + * table prefix for the current blog + */ + protected function get_full_table_name( $table ) { + return $GLOBALS['wpdb']->prefix . $table; + } +} diff --git a/includes/admin/class-wc-subscriptions-admin.php b/includes/admin/class-wc-subscriptions-admin.php old mode 100755 new mode 100644 index 9dfc987..6cd4428 --- a/includes/admin/class-wc-subscriptions-admin.php +++ b/includes/admin/class-wc-subscriptions-admin.php @@ -92,7 +92,7 @@ class WC_Subscriptions_Admin { add_filter( 'woocommerce_settings_tabs_array', __CLASS__ . '::add_subscription_settings_tab', 50 ); - add_action( 'woocommerce_settings_tabs_subscriptions', __CLASS__ . '::subscription_settings_page' ); + add_action( 'woocommerce_settings_subscriptions', __CLASS__ . '::subscription_settings_page' ); add_action( 'woocommerce_update_options_' . self::$tab_name, __CLASS__ . '::update_subscription_settings' ); @@ -150,6 +150,10 @@ class WC_Subscriptions_Admin { 'wc_report_subscription_by_product', 'wc_report_subscription_by_customer', 'wc_report_subscription_events_by_date', + 'wcs_report_subscription_by_product', + 'wcs_report_subscription_by_customer', + 'wcs_report_subscription_events_by_date', + 'wcs_report_upcoming_recurring_revenue', ); // Get all related order and subscription ranges transients @@ -938,11 +942,6 @@ class WC_Subscriptions_Admin { public static function get_subscriptions_list_table() { if ( ! isset( self::$subscriptions_list_table ) ) { - - if ( ! class_exists( 'WC_Subscriptions_List_Table' ) ) { - require_once( 'class-wc-subscriptions-list-table.php' ); - } - self::$subscriptions_list_table = new WC_Subscriptions_List_Table(); } @@ -1744,7 +1743,7 @@ class WC_Subscriptions_Admin { */ public static function related_orders_meta_box( $post ) { _deprecated_function( __METHOD__, '2.0', 'WCS_Meta_Box_Related_Orders::output()' ); - WCS_Meta_Box_Related_Orders::output(); + WCS_Meta_Box_Related_Orders::output( $post ); } /** @@ -1800,5 +1799,3 @@ class WC_Subscriptions_Admin { _deprecated_function( __METHOD__, '2.0' ); } } - -WC_Subscriptions_Admin::init(); diff --git a/includes/admin/class-wcs-admin-meta-boxes.php b/includes/admin/class-wcs-admin-meta-boxes.php old mode 100755 new mode 100644 index 7a162df..12b4d61 --- a/includes/admin/class-wcs-admin-meta-boxes.php +++ b/includes/admin/class-wcs-admin-meta-boxes.php @@ -296,5 +296,3 @@ class WCS_Admin_Meta_Boxes { return $can_be_retried; } } - -new WCS_Admin_Meta_Boxes(); diff --git a/includes/admin/class-wcs-admin-notice.php b/includes/admin/class-wcs-admin-notice.php old mode 100755 new mode 100644 diff --git a/includes/admin/class-wcs-admin-post-types.php b/includes/admin/class-wcs-admin-post-types.php old mode 100755 new mode 100644 index 6be191b..4d9409a --- a/includes/admin/class-wcs-admin-post-types.php +++ b/includes/admin/class-wcs-admin-post-types.php @@ -13,7 +13,7 @@ if ( ! defined( 'ABSPATH' ) ) { } // Exit if accessed directly if ( class_exists( 'WCS_Admin_Post_Types' ) ) { - return new WCS_Admin_Post_Types(); + return; } /** @@ -628,7 +628,7 @@ class WCS_Admin_Post_Types { */ public static function get_date_column_content( $subscription, $column ) { - $date_type_map = array( 'start_date' => 'date_created', 'last_payment_date' => 'last_order_date_created' ); + $date_type_map = array( 'last_payment_date' => 'last_order_date_created' ); $date_type = array_key_exists( $column, $date_type_map ) ? $date_type_map[ $column ] : $column; if ( 0 == $subscription->get_time( $date_type, 'gmt' ) ) { @@ -655,7 +655,7 @@ class WCS_Admin_Post_Types { $sortable_columns = array( 'order_title' => 'ID', 'recurring_total' => 'order_total', - 'start_date' => 'date', + 'start_date' => 'start_date', 'trial_end_date' => 'trial_end_date', 'next_payment_date' => 'next_payment_date', 'last_payment_date' => 'last_payment_date', @@ -746,13 +746,23 @@ class WCS_Admin_Post_Types { // Filter the orders by the posted customer. if ( isset( $_GET['_customer_user'] ) && $_GET['_customer_user'] > 0 ) { - $subscription_ids = WCS_Customer_Store::instance()->get_users_subscription_ids( absint( $_GET['_customer_user'] ) ); + $customer_id = absint( $_GET['_customer_user'] ); + $subscription_ids = apply_filters( + 'wcs_admin_request_query_subscriptions_for_customer', + WCS_Customer_Store::instance()->get_users_subscription_ids( $customer_id ), + $customer_id + ); $vars = self::set_post__in_query_var( $vars, $subscription_ids ); } if ( isset( $_GET['_wcs_product'] ) && $_GET['_wcs_product'] > 0 ) { - $subscription_ids = wcs_get_subscriptions_for_product( $_GET['_wcs_product'] ); - $subscription_ids = array_keys( $subscription_ids ); + $product_id = absint( $_GET['_wcs_product'] ); + $subscription_ids = wcs_get_subscriptions_for_product( $product_id ); + $subscription_ids = apply_filters( + 'wcs_admin_request_query_subscriptions_for_product', + array_keys( $subscription_ids ), + $product_id + ); $vars = self::set_post__in_query_var( $vars, $subscription_ids ); } @@ -804,6 +814,7 @@ class WCS_Admin_Post_Types { case 'last_payment_date' : add_filter( 'posts_clauses', array( $this, 'posts_clauses' ), 10, 2 ); break; + case 'start_date': case 'trial_end_date' : case 'next_payment_date' : case 'end_date' : @@ -1124,5 +1135,3 @@ class WCS_Admin_Post_Types { Reports admin section add_filter( 'woocommerce_admin_reports', __CLASS__ . '::initialize_reports', 12, 1 ); - // Add the reports layout to the WooCommerce -> Reports admin section - add_filter( 'wc_admin_reports_path', __CLASS__ . '::initialize_reports_path', 12, 3 ); - // Add any necessary scripts add_action( 'admin_enqueue_scripts', __CLASS__ . '::reports_scripts' ); // Add any actions we need based on the screen add_action( 'current_screen', __CLASS__ . '::conditional_reporting_includes' ); - } /** @@ -54,31 +49,31 @@ class WCS_Admin_Reports { 'title' => __( 'Subscription Events by Date', 'woocommerce-subscriptions' ), 'description' => '', 'hide_title' => true, - 'callback' => array( 'WC_Admin_Reports', 'get_report' ), + 'callback' => array( 'WCS_Admin_Reports', 'get_report' ), ), 'upcoming_recurring_revenue' => array( 'title' => __( 'Upcoming Recurring Revenue', 'woocommerce-subscriptions' ), 'description' => '', 'hide_title' => true, - 'callback' => array( 'WC_Admin_Reports', 'get_report' ), + 'callback' => array( 'WCS_Admin_Reports', 'get_report' ), ), 'retention_rate' => array( 'title' => __( 'Retention Rate', 'woocommerce-subscriptions' ), 'description' => '', 'hide_title' => true, - 'callback' => array( 'WC_Admin_Reports', 'get_report' ), + 'callback' => array( 'WCS_Admin_Reports', 'get_report' ), ), 'subscription_by_product' => array( 'title' => __( 'Subscriptions by Product', 'woocommerce-subscriptions' ), 'description' => '', 'hide_title' => true, - 'callback' => array( 'WC_Admin_Reports', 'get_report' ), + 'callback' => array( 'WCS_Admin_Reports', 'get_report' ), ), 'subscription_by_customer' => array( 'title' => __( 'Subscriptions by Customer', 'woocommerce-subscriptions' ), 'description' => '', 'hide_title' => true, - 'callback' => array( 'WC_Admin_Reports', 'get_report' ), + 'callback' => array( 'WCS_Admin_Reports', 'get_report' ), ), ), ); @@ -88,32 +83,13 @@ class WCS_Admin_Reports { 'title' => __( 'Failed Payment Retries', 'woocommerce-subscriptions' ), 'description' => '', 'hide_title' => true, - 'callback' => array( 'WC_Admin_Reports', 'get_report' ), + 'callback' => array( 'WCS_Admin_Reports', 'get_report' ), ); } return $reports; } - /** - * If we hit one of our reports in the WC get_report function, change the path to our dir. - * - * @param report_path the parth to the report. - * @param name the name of the report. - * @param class the class of the report. - * @return string path to the report template. - * @since 2.1 - */ - public static function initialize_reports_path( $report_path, $name, $class ) { - - if ( in_array( strtolower( $class ), array( 'wc_report_subscription_events_by_date', 'wc_report_upcoming_recurring_revenue', 'wc_report_retention_rate', 'wc_report_subscription_by_product', 'wc_report_subscription_by_customer', 'wc_report_subscription_payment_retry' ) ) ) { - $report_path = dirname( __FILE__ ) . '/reports/class-wcs-report-' . $name . '.php'; - } - - return $report_path; - - } - /** * Add any subscriptions report javascript to the admin pages. * @@ -157,11 +133,54 @@ class WCS_Admin_Reports { switch ( $screen->id ) { case 'dashboard' : - include_once( 'reports/class-wcs-report-dashboard.php' ); - break; + new WCS_Report_Dashboard(); + break; } } -} -new WCS_Admin_Reports(); + /** + * Get a report from one of our classes. + * + * @param string $name + */ + public static function get_report( $name ) { + $name = sanitize_title( str_replace( '_', '-', $name ) ); + $class = 'WCS_Report_' . str_replace( '-', '_', $name ); + + if ( ! class_exists( $class ) ) { + return; + } + + $report = new $class(); + $report->output_report(); + } + + /** + * If we hit one of our reports in the WC get_report function, change the path to our dir. + * + * @param string $report_path the parth to the report. + * @param string $name the name of the report. + * @param string $class the class of the report. + * + * @return string path to the report template. + * @since 2.1 + * @deprecated in favor of autoloading + * @access private + */ + public static function initialize_reports_path( $report_path, $name, $class ) { + _deprecated_function( __METHOD__, '2.4.0' ); + if ( in_array( strtolower( $class ), array( + 'wc_report_subscription_events_by_date', + 'wc_report_upcoming_recurring_revenue', + 'wc_report_retention_rate', + 'wc_report_subscription_by_product', + 'wc_report_subscription_by_customer', + 'wc_report_subscription_payment_retry', + ) ) ) { + $report_path = dirname( __FILE__ ) . '/reports/classwcsreport' . $name . '.php'; + } + + return $report_path; + } +} diff --git a/includes/admin/class-wcs-admin-system-status.php b/includes/admin/class-wcs-admin-system-status.php old mode 100755 new mode 100644 index c23e03b..c866533 --- a/includes/admin/class-wcs-admin-system-status.php +++ b/includes/admin/class-wcs-admin-system-status.php @@ -37,6 +37,7 @@ class WCS_Admin_System_Status { self::set_debug_mode( $subscriptions_data ); self::set_staging_mode( $subscriptions_data ); + self::set_live_site_url( $subscriptions_data ); self::set_theme_overrides( $subscriptions_data ); self::set_subscription_statuses( $subscriptions_data ); self::set_woocommerce_account_data( $subscriptions_data ); @@ -91,23 +92,38 @@ class WCS_Admin_System_Status { $debug_data['wcs_debug'] = array( 'name' => _x( 'WCS_DEBUG', 'label that indicates whether debugging is turned on for the plugin', 'woocommerce-subscriptions' ), 'label' => 'WCS_DEBUG', - 'note' => ( $is_wcs_debug ) ? __( 'Yes', 'woocommerce-subscriptions' ) : __( 'No', 'woocommerce-subscriptions' ), + 'note' => ( $is_wcs_debug ) ? __( 'Yes', 'woocommerce-subscriptions' ) : __( 'No', 'woocommerce-subscriptions' ), 'success' => $is_wcs_debug ? 0 : 1, ); } /** * Include the staging/live mode the store is running in. + * + * @param array $debug_data */ private static function set_staging_mode( &$debug_data ) { $debug_data['wcs_staging'] = array( 'name' => _x( 'Subscriptions Mode', 'Live or Staging, Label on WooCommerce -> System Status page', 'woocommerce-subscriptions' ), 'label' => 'Subscriptions Mode', - 'note' => '' . ( ( WC_Subscriptions::is_duplicate_site() ) ? _x( 'Staging', 'refers to staging site', 'woocommerce-subscriptions' ) : _x( 'Live', 'refers to live site', 'woocommerce-subscriptions' ) ) . '', + 'note' => '' . ( ( WC_Subscriptions::is_duplicate_site() ) ? _x( 'Staging', 'refers to staging site', 'woocommerce-subscriptions' ) : _x( 'Live', 'refers to live site', 'woocommerce-subscriptions' ) ) . '', 'success' => ( WC_Subscriptions::is_duplicate_site() ) ? 0 : 1, ); } + /** + * @param array $debug_data + */ + private static function set_live_site_url( &$debug_data ) { + $debug_data['wcs_live_site_url'] = array( + 'name' => _x( 'Subscriptions Live URL', 'Live URL, Label on WooCommerce -> System Status page', 'woocommerce-subscriptions' ), + 'label' => 'Subscriptions Live URL', + 'note' => '' . esc_html( WC_Subscriptions::get_site_url_from_source( 'subscriptions_install' ) ) . '', + 'mark' => '', + 'mark_icon' => '', + ); + } + /** * List any Subscriptions template overrides. */ diff --git a/includes/admin/debug-tools/class-wcs-debug-tool-cache-background-updater.php b/includes/admin/debug-tools/class-wcs-debug-tool-cache-background-updater.php old mode 100755 new mode 100644 diff --git a/includes/admin/debug-tools/class-wcs-debug-tool-cache-eraser.php b/includes/admin/debug-tools/class-wcs-debug-tool-cache-eraser.php old mode 100755 new mode 100644 diff --git a/includes/admin/debug-tools/class-wcs-debug-tool-cache-generator.php b/includes/admin/debug-tools/class-wcs-debug-tool-cache-generator.php old mode 100755 new mode 100644 diff --git a/includes/admin/debug-tools/class-wcs-debug-tool-factory.php b/includes/admin/debug-tools/class-wcs-debug-tool-factory.php old mode 100755 new mode 100644 index 12377a2..b9bdd65 --- a/includes/admin/debug-tools/class-wcs-debug-tool-factory.php +++ b/includes/admin/debug-tools/class-wcs-debug-tool-factory.php @@ -30,14 +30,13 @@ final class WCS_Debug_Tool_Factory { * @param string $tool_name The section name given to the tool on the admin screen. * @param string $tool_desc The long description for the tool on the admin screen. * @param WCS_Cache_Updater $data_store + * @throws InvalidArgumentException When a class for the given tool is not found. */ public static function add_cache_tool( $tool_type, $tool_name, $tool_desc, WCS_Cache_Updater $data_store ) { - if ( ! is_admin() && ! defined( 'DOING_CRON' ) && ! defined( 'WP_CLI' ) ) { return; } - self::load_cache_tool_file( $tool_type ); $tool_class_name = self::get_cache_tool_class_name( $tool_type ); $tool_key = self::get_tool_key( $tool_name ); if ( 'generator' === $tool_type ) { @@ -46,6 +45,8 @@ final class WCS_Debug_Tool_Factory { } else { $tool = new $tool_class_name( $tool_key, $tool_name, $tool_desc, $data_store ); } + + /** @var WCS_Debug_Tool $tool */ $tool->init(); } @@ -66,49 +67,20 @@ final class WCS_Debug_Tool_Factory { * * To make sure the class's file is loaded, call @see self::load_cache_tool_class() first. * - * @param array $cache_tool_type The type of cache tool. Known tools are 'eraser' and 'generator'. + * @param string $cache_tool_type The type of cache tool. Known tools are 'eraser' and 'generator'. * @return string The cache tool's class name. */ protected static function get_cache_tool_class_name( $cache_tool_type ) { $tool_class_name = sprintf( 'WCS_Debug_Tool_Cache_%s', ucfirst( $cache_tool_type ) ); if ( ! class_exists( $tool_class_name ) ) { - throw new InvalidArgumentException( sprintf( '%s() requires a path to load %s. Class does not exist after loading %s.', __METHOD__, $class_name, $file_path ) ); + throw new InvalidArgumentException( sprintf( + '%s() requires a valid tool name. Class "%s" does not exist.', + __METHOD__, + $tool_class_name + ) ); } return $tool_class_name; } - - /** - * Load a cache tool file in the default file path. - * - * For example, load_cache_tool( 'related-order', 'generator' ) will load the file includes/admin/debug-tools/class-wcs-debug-tool-related-order-cache-generator.php - * - * @param array $cache_tool_type The type of cache tool. Known tools are 'eraser' and 'generator'. - */ - protected static function load_cache_tool_file( $cache_tool_type ) { - $file_path = sprintf( 'includes/admin/debug-tools/class-wcs-debug-tool-cache-%s.php', $cache_tool_type ); - $file_path = plugin_dir_path( WC_Subscriptions::$plugin_file ) . $file_path; - - if ( ! file_exists( $file_path ) ) { - throw new InvalidArgumentException( sprintf( '%s() requires a cache name linked to a valid debug tool. File does not exist: %s', __METHOD__, $rel_file_path ) ); - } - - self::load_required_classes( $cache_tool_type ); - require_once( $file_path ); - } - - /** - * Load classes that debug tools extend. - * - * @param array $cache_tool_type The type of cache tool. Known tools are 'eraser' and 'generator'. - */ - protected static function load_required_classes( $cache_tool_type ) { - - require_once( plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'includes/abstracts/abstract-wcs-debug-tool-cache-updater.php' ); - - if ( 'generator' === $cache_tool_type ) { - require_once( plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'includes/admin/debug-tools/class-wcs-debug-tool-cache-background-updater.php' ); - } - } } diff --git a/includes/admin/meta-boxes/class-wcs-meta-box-payment-retries.php b/includes/admin/meta-boxes/class-wcs-meta-box-payment-retries.php old mode 100755 new mode 100644 index d7ab577..25e0ca1 --- a/includes/admin/meta-boxes/class-wcs-meta-box-payment-retries.php +++ b/includes/admin/meta-boxes/class-wcs-meta-box-payment-retries.php @@ -28,7 +28,7 @@ class WCS_Meta_Box_Payment_Retries { $retries = WCS_Retry_Manager::store()->get_retries_for_order( $post->ID ); - include_once( 'views/html-retries-table.php' ); + include_once( dirname( __FILE__ ) . '/views/html-retries-table.php' ); do_action( 'woocommerce_subscriptions_retries_meta_box', $post->ID, $retries ); } diff --git a/includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php b/includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php old mode 100755 new mode 100644 index d8e9270..2edd545 --- a/includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php +++ b/includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php @@ -33,7 +33,7 @@ class WCS_Meta_Box_Related_Orders { add_action( 'woocommerce_subscriptions_related_orders_meta_box_rows', __CLASS__ . '::output_rows', 10 ); - include_once( 'views/html-related-orders-table.php' ); + include_once( dirname( __FILE__ ) . '/views/html-related-orders-table.php' ); do_action( 'woocommerce_subscriptions_related_orders_meta_box', $order, $post ); } @@ -115,7 +115,7 @@ class WCS_Meta_Box_Related_Orders { if ( wcs_get_objects_property( $order, 'id' ) == $post->ID ) { continue; } - include( 'views/html-related-orders-row.php' ); + include( dirname( __FILE__ ) . '/views/html-related-orders-row.php' ); } } } diff --git a/includes/admin/meta-boxes/class-wcs-meta-box-subscription-schedule.php b/includes/admin/meta-boxes/class-wcs-meta-box-schedule.php old mode 100755 new mode 100644 similarity index 96% rename from includes/admin/meta-boxes/class-wcs-meta-box-subscription-schedule.php rename to includes/admin/meta-boxes/class-wcs-meta-box-schedule.php index 7c2ab41..2c5c255 --- a/includes/admin/meta-boxes/class-wcs-meta-box-subscription-schedule.php +++ b/includes/admin/meta-boxes/class-wcs-meta-box-schedule.php @@ -27,7 +27,7 @@ class WCS_Meta_Box_Schedule { $the_subscription = wcs_get_subscription( $post->ID ); } - include( 'views/html-subscription-schedule.php' ); + include( dirname( __FILE__ ) . '/views/html-subscription-schedule.php' ); } /** diff --git a/includes/admin/meta-boxes/class-wcs-meta-box-subscription-data.php b/includes/admin/meta-boxes/class-wcs-meta-box-subscription-data.php old mode 100755 new mode 100644 index 00d1d6c..79e5f96 --- a/includes/admin/meta-boxes/class-wcs-meta-box-subscription-data.php +++ b/includes/admin/meta-boxes/class-wcs-meta-box-subscription-data.php @@ -358,9 +358,17 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data { wcs_add_admin_notice( sprintf( __( 'Error updating some information: %s', 'woocommerce-subscriptions' ), $e->getMessage() ), 'error' ); } - // Grant download permissions on initial save. if ( isset( $_POST['original_post_status'] ) && 'auto-draft' === $_POST['original_post_status'] ) { - wc_downloadable_product_permissions( $post_id ); + $subscription->set_created_via( 'admin' ); + $subscription->save(); + + /** + * Fire an action after a subscription is created via the admin screen. + * + * @since 2.4.1 + * @param WC_Subscription $subscription The subscription object. + */ + do_action( 'woocommerce_admin_created_subscription', $subscription ); } do_action( 'woocommerce_process_shop_subscription_meta', $post_id, $post ); diff --git a/includes/admin/meta-boxes/views/html-related-orders-row.php b/includes/admin/meta-boxes/views/html-related-orders-row.php old mode 100755 new mode 100644 diff --git a/includes/admin/meta-boxes/views/html-related-orders-table.php b/includes/admin/meta-boxes/views/html-related-orders-table.php old mode 100755 new mode 100644 diff --git a/includes/admin/meta-boxes/views/html-retries-table.php b/includes/admin/meta-boxes/views/html-retries-table.php old mode 100755 new mode 100644 diff --git a/includes/admin/meta-boxes/views/html-subscription-schedule.php b/includes/admin/meta-boxes/views/html-subscription-schedule.php old mode 100755 new mode 100644 diff --git a/includes/admin/reports/class-wcs-report-cache-manager.php b/includes/admin/reports/class-wcs-report-cache-manager.php old mode 100755 new mode 100644 index 97d071e..eb40b48 --- a/includes/admin/reports/class-wcs-report-cache-manager.php +++ b/includes/admin/reports/class-wcs-report-cache-manager.php @@ -27,30 +27,30 @@ class WCS_Report_Cache_Manager { */ private $update_events_and_classes = array( 'woocommerce_subscriptions_reports_schedule_cache_updates' => array( // a custom hook that can be called to schedule a full cache update, used by WC_Subscriptions_Upgrader - 0 => 'WC_Report_Subscription_Events_By_Date', - 1 => 'WC_Report_Upcoming_Recurring_Revenue', - 3 => 'WC_Report_Subscription_By_Product', - 4 => 'WC_Report_Subscription_By_Customer', + 0 => 'WCS_Report_Subscription_Events_By_Date', + 1 => 'WCS_Report_Upcoming_Recurring_Revenue', + 3 => 'WCS_Report_Subscription_By_Product', + 4 => 'WCS_Report_Subscription_By_Customer', ), 'woocommerce_subscription_payment_complete' => array( // this hook takes care of renewal, switch and initial payments - 0 => 'WC_Report_Subscription_Events_By_Date', - 4 => 'WC_Report_Subscription_By_Customer', + 0 => 'WCS_Report_Subscription_Events_By_Date', + 4 => 'WCS_Report_Subscription_By_Customer', ), 'woocommerce_subscriptions_switch_completed' => array( - 0 => 'WC_Report_Subscription_Events_By_Date', + 0 => 'WCS_Report_Subscription_Events_By_Date', ), 'woocommerce_subscription_status_changed' => array( - 0 => 'WC_Report_Subscription_Events_By_Date', // we really only need cancelled, expired and active status here, but we'll use a more generic hook for convenience - 4 => 'WC_Report_Subscription_By_Customer', + 0 => 'WCS_Report_Subscription_Events_By_Date', // we really only need cancelled, expired and active status here, but we'll use a more generic hook for convenience + 4 => 'WCS_Report_Subscription_By_Customer', ), 'woocommerce_subscription_status_active' => array( - 1 => 'WC_Report_Upcoming_Recurring_Revenue', + 1 => 'WCS_Report_Upcoming_Recurring_Revenue', ), 'woocommerce_new_order_item' => array( - 3 => 'WC_Report_Subscription_By_Product', + 3 => 'WCS_Report_Subscription_By_Product', ), 'woocommerce_update_order_item' => array( - 3 => 'WC_Report_Subscription_By_Product', + 3 => 'WCS_Report_Subscription_By_Product', ), ); @@ -141,15 +141,7 @@ class WCS_Report_Cache_Manager { // On large sites, we want to run the cache update once at 4am in the site's timezone if ( $this->use_large_site_cache() ) { - $four_am_site_time = new WC_DateTime( '4 am', wcs_get_sites_timezone() ); - - // Convert to a UTC timestamp for scheduling - $cache_update_timestamp = $four_am_site_time->getTimestamp(); - - // PHP doesn't support a "next 4am" time format equivalent, so we need to manually handle getting 4am from earlier today (which will always happen when this is run after 4am and before midnight in the site's timezone) - if ( $cache_update_timestamp <= gmdate( 'U' ) ) { - $cache_update_timestamp += DAY_IN_SECONDS; - } + $cache_update_timestamp = $this->get_large_site_cache_update_timestamp(); // Schedule one update event for each class to avoid updating cache more than once for the same class for different events foreach ( $this->reports_to_update as $index => $report_class ) { @@ -217,11 +209,6 @@ class WCS_Report_Cache_Manager { require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' ); require_once( WC()->plugin_path() . '/includes/admin/reports/class-wc-admin-report.php' ); - $report_name = strtolower( str_replace( '_', '-', str_replace( 'WC_Report_', '', $report_class ) ) ); - $report_path = WCS_Admin_Reports::initialize_reports_path( '', $report_name, $report_class ); - - require_once( $report_path ); - $reflector = new ReflectionMethod( $report_class, 'get_data' ); // Some report classes extend WP_List_Table which has a constructor using methods not available on WP-Cron (and unable to be loaded with a __doing_it_wrong() notice), so they have a static get_data() method and do not need to be instantiated @@ -354,6 +341,21 @@ class WCS_Report_Cache_Manager { return $data; } -} -return new WCS_Report_Cache_Manager(); + /** + * Get the scheduled update cache time for large sites. + * + * @return int The timestamp of the next occurring 4 am in the site's timezone converted to UTC. + */ + protected function get_large_site_cache_update_timestamp() { + // Get the timestamp for 4 am in the site's timezone converted to the UTC equivalent. + $cache_update_timestamp = wc_string_to_timestamp( '4 am', current_time( 'timestamp' ) ) - wc_timezone_offset(); + + // PHP doesn't support a "next 4am" time format equivalent, so we need to manually handle getting 4am from earlier today (which will always happen when this is run after 4am and before midnight in the site's timezone) + if ( $cache_update_timestamp <= gmdate( 'U' ) ) { + $cache_update_timestamp += DAY_IN_SECONDS; + } + + return $cache_update_timestamp; + } +} diff --git a/includes/admin/reports/class-wcs-report-dashboard.php b/includes/admin/reports/class-wcs-report-dashboard.php old mode 100755 new mode 100644 index 2f004a7..748d869 --- a/includes/admin/reports/class-wcs-report-dashboard.php +++ b/includes/admin/reports/class-wcs-report-dashboard.php @@ -96,5 +96,3 @@ class WCS_Report_Dashboard { wp_enqueue_style( 'wcs-dashboard-report', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/css/dashboard.css', array(), WC_Subscriptions::$version ); } } - -return new WCS_Report_Dashboard(); diff --git a/includes/admin/reports/class-wcs-report-retention-rate.php b/includes/admin/reports/class-wcs-report-retention-rate.php old mode 100755 new mode 100644 index 0ecfa1e..d7f1bca --- a/includes/admin/reports/class-wcs-report-retention-rate.php +++ b/includes/admin/reports/class-wcs-report-retention-rate.php @@ -5,13 +5,13 @@ * Find the number of periods between when each subscription is created and ends or ended * then plot all subscriptions using this data to provide a curve of retention rates. * - * @package WooCommerce Subscriptions - * @subpackage WC_Subscriptions_Admin_Reports - * @category Class - * @author Prospress - * @since 2.1 + * @package WooCommerce Subscriptions + * @subpackage WC_Subscriptions_Admin_Reports + * @category Class + * @author Prospress + * @since 2.1 */ -class WC_Report_Retention_Rate extends WC_Admin_Report { +class WCS_Report_Retention_Rate extends WC_Admin_Report { public $chart_colours = array(); diff --git a/includes/admin/reports/class-wcs-report-subscription-by-customer.php b/includes/admin/reports/class-wcs-report-subscription-by-customer.php old mode 100755 new mode 100644 index 079bf0e..5fa1868 --- a/includes/admin/reports/class-wcs-report-subscription-by-customer.php +++ b/includes/admin/reports/class-wcs-report-subscription-by-customer.php @@ -4,13 +4,13 @@ * * Creates the subscription admin reports area. * - * @package WooCommerce Subscriptions - * @subpackage WC_Subscriptions_Admin_Reports - * @category Class - * @author Prospress - * @since 2.1 + * @package WooCommerce Subscriptions + * @subpackage WC_Subscriptions_Admin_Reports + * @category Class + * @author Prospress + * @since 2.1 */ -class WC_Report_Subscription_By_Customer extends WP_List_Table { +class WCS_Report_Subscription_By_Customer extends WP_List_Table { private $totals; diff --git a/includes/admin/reports/class-wcs-report-subscription-by-product.php b/includes/admin/reports/class-wcs-report-subscription-by-product.php old mode 100755 new mode 100644 index 9b53c50..2fa4f62 --- a/includes/admin/reports/class-wcs-report-subscription-by-product.php +++ b/includes/admin/reports/class-wcs-report-subscription-by-product.php @@ -4,13 +4,13 @@ * * Creates the subscription admin reports area. * - * @package WooCommerce Subscriptions - * @subpackage WC_Subscriptions_Admin_Reports - * @category Class - * @author Prospress - * @since 2.1 + * @package WooCommerce Subscriptions + * @subpackage WC_Subscriptions_Admin_Reports + * @category Class + * @author Prospress + * @since 2.1 */ -class WC_Report_Subscription_By_Product extends WP_List_Table { +class WCS_Report_Subscription_By_Product extends WP_List_Table { /** * Constructor. diff --git a/includes/admin/reports/class-wcs-report-subscription-events-by-date.php b/includes/admin/reports/class-wcs-report-subscription-events-by-date.php old mode 100755 new mode 100644 index 541908d..d394bc3 --- a/includes/admin/reports/class-wcs-report-subscription-events-by-date.php +++ b/includes/admin/reports/class-wcs-report-subscription-events-by-date.php @@ -4,13 +4,13 @@ * * Display important historical data for subscription revenue and events, like switches and cancellations. * - * @package WooCommerce Subscriptions - * @subpackage WC_Subscriptions_Admin_Reports - * @category Class - * @author Prospress - * @since 2.1 + * @package WooCommerce Subscriptions + * @subpackage WC_Subscriptions_Admin_Reports + * @category Class + * @author Prospress + * @since 2.1 */ -class WC_Report_Subscription_Events_By_Date extends WC_Admin_Report { +class WCS_Report_Subscription_Events_By_Date extends WC_Admin_Report { public $chart_colours = array(); @@ -257,7 +257,7 @@ class WC_Report_Subscription_Events_By_Date extends WC_Admin_Report { $query = $wpdb->prepare( "SELECT searchdate.Date as date, COUNT( DISTINCT wcsubs.ID) as count FROM ( - SELECT DATE_FORMAT(last_thousand_days.Date,'%%Y-%%m-%%d') as Date + SELECT DATE(last_thousand_days.Date) as Date FROM ( SELECT DATE(%s) - INTERVAL(units.digit + (10 * tens.digit) + (100 * hundreds.digit)) DAY as Date FROM ( @@ -295,6 +295,7 @@ class WC_Report_Subscription_Events_By_Date extends WC_Admin_Report { OR wcsmeta.meta_value = 0 OR wcsmeta.meta_value IS NULL ) + GROUP BY searchdate.Date ORDER BY searchdate.Date ASC", $query_end_date, date( 'Y-m-d', $this->start_date ), @@ -380,7 +381,7 @@ class WC_Report_Subscription_Events_By_Date extends WC_Admin_Report { $this->report_data->switch_orders_total_count = absint( array_sum( wp_list_pluck( $this->report_data->switch_counts, 'count' ) ) ); $this->report_data->total_subscriptions_cancelled = absint( array_sum( wp_list_pluck( $this->report_data->cancel_counts, 'count' ) ) ); $this->report_data->total_subscriptions_ended = absint( array_sum( wp_list_pluck( $this->report_data->ended_counts, 'count' ) ) ); - $this->report_data->total_subscriptions_at_period_end = absint( end( $this->report_data->subscriber_counts )->count ); + $this->report_data->total_subscriptions_at_period_end = $this->report_data->subscriber_counts ? absint( end( $this->report_data->subscriber_counts )->count ) : 0; $this->report_data->total_subscriptions_at_period_start = isset( $this->report_data->subscriber_counts[0]->count ) ? absint( $this->report_data->subscriber_counts[0]->count ) : 0; } diff --git a/includes/admin/reports/class-wcs-report-subscription-payment-retry.php b/includes/admin/reports/class-wcs-report-subscription-payment-retry.php old mode 100755 new mode 100644 index 77d7574..235b6c9 --- a/includes/admin/reports/class-wcs-report-subscription-payment-retry.php +++ b/includes/admin/reports/class-wcs-report-subscription-payment-retry.php @@ -4,13 +4,13 @@ * * Creates the subscription admin reports area. * - * @package WooCommerce Subscriptions - * @subpackage WC_Subscriptions_Admin_Reports - * @category Class - * @author Prospress - * @since 2.1 + * @package WooCommerce Subscriptions + * @subpackage WC_Subscriptions_Admin_Reports + * @category Class + * @author Prospress + * @since 2.1 */ -class WC_Report_Subscription_Payment_Retry extends WC_Admin_Report { +class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report { private $chart_colours = array(); diff --git a/includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php b/includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php old mode 100755 new mode 100644 index 89524f0..c438d37 --- a/includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php +++ b/includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php @@ -5,13 +5,13 @@ * Display the renewal order count and revenue that will be processed for all currently active subscriptions * for a given period of time in the future. * - * @package WooCommerce Subscriptions - * @subpackage WC_Subscriptions_Admin_Reports - * @category Class - * @author Prospress - * @since 2.1 + * @package WooCommerce Subscriptions + * @subpackage WC_Subscriptions_Admin_Reports + * @category Class + * @author Prospress + * @since 2.1 */ -class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report { +class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report { public $chart_colours = array(); @@ -119,7 +119,7 @@ class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report { // Query based on whole days, not minutes/hours so that we can cache the query for at least 24 hours $base_query = $wpdb->prepare( "SELECT - DATE_FORMAT(ms.meta_value, '%s') as scheduled_date, + DATE(ms.meta_value) as scheduled_date, SUM(mo.meta_value) as recurring_total, COUNT(mo.meta_value) as total_renewals, group_concat(p.ID) as subscription_ids, @@ -148,7 +148,6 @@ class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report { AND me.meta_key = '_schedule_end ' GROUP BY {$this->group_by_query} ORDER BY ms.meta_value ASC", - '%Y-%m-%d', date( 'Y-m-d', strtotime( '+1 DAY', $this->end_date ) ), date( 'Y-m-d', $this->start_date ), date( 'Y-m-d', strtotime( '+1 DAY', $this->end_date ) ) diff --git a/includes/admin/reports/deprecated/class-wc-report-retention-rate.php b/includes/admin/reports/deprecated/class-wc-report-retention-rate.php new file mode 100644 index 0000000..e222fd0 --- /dev/null +++ b/includes/admin/reports/deprecated/class-wc-report-retention-rate.php @@ -0,0 +1,20 @@ +data['resubscribed_subscription'] = strval( reset( $resubscribed_subscriptions ) ); // Subscriptions can only be resubscribed to once so return the first and only element. foreach ( array( 'start', 'trial_end', 'next_payment', 'end' ) as $date_type ) { - $date_type_key = ( 'start' === $date_type ) ? 'date_created' : $date_type; - $date = $subscription->get_date( $date_type_key ); - + $date = $subscription->get_date( $date_type ); $response->data[ $date_type . '_date' ] = ( ! empty( $date ) ) ? wc_rest_prepare_date_response( $date ) : ''; } @@ -296,6 +294,10 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_V1_Controller { $payment_method_meta = apply_filters( 'woocommerce_subscription_payment_meta', array(), $subscription ); + // Reload the subscription to update the meta values. + // In particular, the update_post_meta() called while _stripe_card_id is updated to _stripe_source_id + $subscription = wcs_get_subscription( $subscription->get_id() ); + if ( isset( $payment_method_meta[ $payment_method ] ) ) { $payment_method_meta = $payment_method_meta[ $payment_method ]; @@ -318,6 +320,9 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_V1_Controller { $subscription->set_payment_method( $payment_method, $payment_method_meta ); + // Save the subscription to reflect the new values + $subscription->save(); + } catch ( Exception $e ) { $subscription->set_payment_method(); $subscription->save(); @@ -346,14 +351,14 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_V1_Controller { if ( ! is_null( $value ) ) { switch ( $key ) { - case 'billing' : - case 'shipping' : + case 'billing': + case 'shipping': $this->update_address( $subscription, $value, $key ); break; - case 'line_items' : - case 'shipping_lines' : - case 'fee_lines' : - case 'coupon_lines' : + case 'line_items': + case 'shipping_lines': + case 'fee_lines': + case 'coupon_lines': if ( is_array( $value ) ) { foreach ( $value as $item ) { if ( is_array( $item ) ) { @@ -366,14 +371,13 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_V1_Controller { } } break; - case 'start_date' : - case 'trial_end_date' : - case 'next_payment_date' : - case 'end_date' : - $date_type_key = ( 'start_date' === $key ) ? 'date_created' : $key; - $dates_to_update[ $date_type_key ] = $value; + case 'start_date': + case 'trial_end_date': + case 'next_payment_date': + case 'end_date': + $dates_to_update[ $key ] = $value; break; - default : + default: if ( is_callable( array( $subscription, "set_{$key}" ) ) ) { $subscription->{"set_{$key}"}( $value ); } diff --git a/includes/api/legacy/class-wc-api-subscriptions-customers.php b/includes/api/legacy/class-wc-api-subscriptions-customers.php old mode 100755 new mode 100644 diff --git a/includes/api/legacy/class-wc-api-subscriptions.php b/includes/api/legacy/class-wc-api-subscriptions.php old mode 100755 new mode 100644 index 057e1a3..423901a --- a/includes/api/legacy/class-wc-api-subscriptions.php +++ b/includes/api/legacy/class-wc-api-subscriptions.php @@ -397,10 +397,8 @@ class WC_API_Subscriptions extends WC_API_Orders { $dates_to_update = array(); foreach ( array( 'start', 'trial_end', 'end', 'next_payment' ) as $date_type ) { - if ( isset( $data[ $date_type . '_date' ] ) ) { - $date_type_key = ( 'start' === $date_type ) ? 'date_created' : $date_type; - $dates_to_update[ $date_type_key ] = $data[ $date_type . '_date' ]; + $dates_to_update[ $date_type ] = $data[ $date_type . '_date' ]; } } @@ -461,7 +459,7 @@ class WC_API_Subscriptions extends WC_API_Orders { $subscription_data['billing_schedule'] = array( 'period' => $subscription->get_billing_period(), 'interval' => $subscription->get_billing_interval(), - 'start_at' => $this->get_formatted_datetime( $subscription, 'date_created' ), + 'start_at' => $this->get_formatted_datetime( $subscription, 'start' ), 'trial_end_at' => $this->get_formatted_datetime( $subscription, 'trial_end' ), 'next_payment_at' => $this->get_formatted_datetime( $subscription, 'next_payment' ), 'end_at' => $this->get_formatted_datetime( $subscription, 'end' ), diff --git a/includes/api/legacy/class-wc-rest-subscription-notes-controller.php b/includes/api/legacy/class-wc-rest-subscription-notes-controller.php old mode 100755 new mode 100644 diff --git a/includes/api/legacy/class-wc-rest-subscriptions-controller.php b/includes/api/legacy/class-wc-rest-subscriptions-controller.php old mode 100755 new mode 100644 index 4ea4b0d..acb3eef --- a/includes/api/legacy/class-wc-rest-subscriptions-controller.php +++ b/includes/api/legacy/class-wc-rest-subscriptions-controller.php @@ -85,7 +85,7 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller { $response->data['billing_period'] = $subscription->get_billing_period(); $response->data['billing_interval'] = $subscription->get_billing_interval(); - $response->data['start_date'] = wc_rest_prepare_date_response( $subscription->get_date( 'date_created' ) ); + $response->data['start_date'] = wc_rest_prepare_date_response( $subscription->get_date( 'start_date' ) ); $response->data['trial_end_date'] = wc_rest_prepare_date_response( $subscription->get_date( 'trial_end' ) ); $response->data['next_payment_date'] = wc_rest_prepare_date_response( $subscription->get_date( 'next_payment' ) ); $response->data['end_date'] = wc_rest_prepare_date_response( $subscription->get_date( 'end_date' ) ); diff --git a/includes/class-wc-order-item-pending-switch.php b/includes/class-wc-order-item-pending-switch.php old mode 100755 new mode 100644 diff --git a/includes/class-wc-product-subscription-variation.php b/includes/class-wc-product-subscription-variation.php old mode 100755 new mode 100644 diff --git a/includes/class-wc-product-subscription.php b/includes/class-wc-product-subscription.php old mode 100755 new mode 100644 diff --git a/includes/class-wc-product-variable-subscription.php b/includes/class-wc-product-variable-subscription.php old mode 100755 new mode 100644 index 32b253d..f1e2ef2 --- a/includes/class-wc-product-variable-subscription.php +++ b/includes/class-wc-product-variable-subscription.php @@ -312,4 +312,23 @@ class WC_Product_Variable_Subscription extends WC_Product_Variable { return $prefix; } + + /** + * Get an array of available variations for the current product. + * + * @return array + */ + public function get_available_variations() { + $available_variations_data = parent::get_available_variations(); + + foreach ( $available_variations_data as $index => $variation_data ) { + + // Add the product's synced first payment date to the variation data if applicable. + if ( isset( $variation_data['variation_id'] ) ) { + $available_variations_data[ $index ]['first_payment_html'] = WC_Subscriptions_Synchroniser::get_products_first_payment_date( wc_get_product( $variation_data['variation_id'] ) ); + } + } + + return $available_variations_data; + } } diff --git a/includes/class-wc-subscription.php b/includes/class-wc-subscription.php old mode 100755 new mode 100644 index 364cc21..c5c778c --- a/includes/class-wc-subscription.php +++ b/includes/class-wc-subscription.php @@ -67,6 +67,7 @@ class WC_Subscription extends WC_Order { 'schedule_cancelled' => null, 'schedule_end' => null, 'schedule_payment_retry' => null, + 'schedule_start' => null, 'switch_data' => array(), ); @@ -98,8 +99,22 @@ class WC_Subscription extends WC_Order { */ public function __construct( $subscription ) { - parent::__construct( $subscription ); + // Add subscription date types as extra subscription data. + foreach ( wcs_get_subscription_date_types() as $date_type => $date_name ) { + // The last payment date is derived from other sources and shouldn't be stored on a subscription. + if ( 'last_payment' === $date_type ) { + continue; + } + $date_type_key = wcs_maybe_prefix_key( $date_type, 'schedule_' ); + + // Skip any custom dates which are already core date types. + if ( ! isset( $this->extra_data[ $date_type_key ] ) ) { + $this->extra_data[ $date_type_key ] = null; + } + } + + parent::__construct( $subscription ); $this->order_type = 'shop_subscription'; } @@ -1147,7 +1162,7 @@ class WC_Subscription extends WC_Order { // Delete dates with a 0 date time if ( 0 == $datetime ) { - if ( ! in_array( $date_type, array( 'date_created', 'last_order_date_created', 'last_order_date_modified' ) ) ) { + if ( ! in_array( $date_type, array( 'date_created', 'start', 'last_order_date_created', 'last_order_date_modified' ) ) ) { $this->delete_date( $date_type ); } continue; @@ -1210,6 +1225,9 @@ class WC_Subscription extends WC_Order { // Make sure some dates are before next payment date switch ( $date_type ) { case 'date_created' : + $message = __( 'The creation date of a subscription can not be deleted, only updated.', 'woocommerce-subscriptions' ); + break; + case 'start' : $message = __( 'The start date of a subscription can not be deleted, only updated.', 'woocommerce-subscriptions' ); break; case 'last_order_date_created' : @@ -1241,6 +1259,7 @@ class WC_Subscription extends WC_Order { public function can_date_be_updated( $date_type ) { switch ( $date_type ) { + case 'start': case 'date_created' : if ( $this->has_status( array( 'auto-draft', 'pending' ) ) ) { $can_date_be_updated = true; @@ -1330,7 +1349,7 @@ class WC_Subscription extends WC_Order { $next_payment_date = 0; // If the subscription is not active, there is no next payment date - $start_time = $this->get_time( 'date_created' ); + $start_time = $this->get_time( 'start' ); $next_payment_time = $this->get_time( 'next_payment' ); $trial_end_time = $this->get_time( 'trial_end' ); $last_payment_time = max( $this->get_time( 'last_order_date_created' ), $this->get_time( 'last_order_date_paid' ) ); @@ -1754,8 +1773,8 @@ class WC_Subscription extends WC_Order { * @return array */ public function get_related_orders_query( $subscription_id ) { - wcs_deprecated_function( __METHOD__, '2.3.0', 'WCS_Subscription_Data_Store_CPT::get_related_order_ids( $subscription_id ) via WCS_Subscription_Data_Store::instance()' ); - return WCS_Related_Order_Store::instance()->get_related_order_ids( $subscription_id, 'renewal' ); + wcs_deprecated_function( __METHOD__, '2.3.0', 'WCS_Subscription_Data_Store_CPT::get_related_order_ids( wcs_get_subscription( $subscription_id ) ) via WCS_Subscription_Data_Store::instance()' ); + return WCS_Related_Order_Store::instance()->get_related_order_ids( wcs_get_subscription( $subscription_id ), 'renewal' ); } /** @@ -1959,28 +1978,7 @@ class WC_Subscription extends WC_Order { do_action( 'woocommerce_subscription_validate_payment_meta', $payment_method_id, $payment_meta, $this ); do_action( 'woocommerce_subscription_validate_payment_meta_' . $payment_method_id, $payment_meta, $this ); - foreach ( $payment_meta as $meta_table => $meta ) { - foreach ( $meta as $meta_key => $meta_data ) { - if ( isset( $meta_data['value'] ) ) { - switch ( $meta_table ) { - case 'user_meta': - case 'usermeta': - update_user_meta( $this->get_user_id(), $meta_key, $meta_data['value'] ); - break; - case 'post_meta': - case 'postmeta': - $this->update_meta_data( $meta_key, $meta_data['value'] ); - break; - case 'options': - update_option( $meta_key, $meta_data['value'] ); - break; - default: - do_action( 'wcs_save_other_payment_meta', $this, $meta_table, $meta_key, $meta_data['value'] ); - } - } - } - } - + wcs_set_payment_meta( $this, $payment_meta ); } /** @@ -2098,6 +2096,13 @@ class WC_Subscription extends WC_Order { $sign_up_fee = ( (float) $original_order_item->get_total( 'edit' ) ) / $original_order_item->get_quantity( 'edit' ); } elseif ( $original_order_item->meta_exists( '_synced_sign_up_fee' ) ) { $sign_up_fee = ( (float) $original_order_item->get_meta( '_synced_sign_up_fee' ) ) / $original_order_item->get_quantity( 'edit' ); + + // The synced sign up fee meta contains the raw product sign up fee, if the subscription totals are inclusive of tax, we need to adjust the synced sign up fee to match tax inclusivity. + if ( $this->get_prices_include_tax() ) { + $line_item_total = (float) $original_order_item->get_total( 'edit' ) + $original_order_item->get_total_tax( 'edit' ); + $signup_fee_portion = $sign_up_fee / $line_item_total; + $sign_up_fee = (float) $original_order_item->get_total( 'edit' ) * $signup_fee_portion; + } } else { // Sign-up fee is any amount on top of recurring amount $order_line_total = ( (float) $original_order_item->get_total( 'edit' ) ) / $original_order_item->get_quantity( 'edit' ); @@ -2106,17 +2111,13 @@ class WC_Subscription extends WC_Order { $sign_up_fee = max( $order_line_total - $subscription_line_total, 0 ); } - if ( ! empty( $original_order_item ) && ! empty( $sign_up_fee ) ) { + // If prices don't inc tax, ensure that the sign up fee amount includes the tax. + if ( 'inclusive_of_tax' === $tax_inclusive_or_exclusive && ! empty( $original_order_item ) && ! empty( $sign_up_fee ) ) { $sign_up_fee_proportion = $sign_up_fee / ( $original_order_item->get_total( 'edit' ) / $original_order_item->get_quantity( 'edit' ) ); - $sign_up_fee_tax = wc_round_tax_total( $original_order_item->get_total_tax( 'edit' ) * $sign_up_fee_proportion ); + $sign_up_fee_tax = $original_order_item->get_total_tax( 'edit' ) * $sign_up_fee_proportion; - // If prices don't inc tax, ensure that the sign up fee amount includes the tax. - if ( 'inclusive_of_tax' === $tax_inclusive_or_exclusive && ! $this->get_prices_include_tax() ) { - $sign_up_fee += $sign_up_fee_tax; - // If prices inc tax and the request is for prices exclusive of tax, remove the taxes. - } elseif ( 'inclusive_of_tax' !== $tax_inclusive_or_exclusive && $this->get_prices_include_tax() ) { - $sign_up_fee -= $sign_up_fee_tax; - } + $sign_up_fee += $sign_up_fee_tax; + $sign_up_fee = wc_format_decimal( $sign_up_fee, wc_get_price_decimals() ); } } @@ -2135,7 +2136,7 @@ class WC_Subscription extends WC_Order { if ( 0 != ( $end_time = $this->get_time( 'end' ) ) ) { - $from_timestamp = $this->get_time( 'date_created' ); + $from_timestamp = $this->get_time( 'start' ); if ( 0 != $this->get_time( 'trial_end' ) || WC_Subscriptions_Synchroniser::subscription_contains_synced_product( $this ) ) { @@ -2218,8 +2219,8 @@ class WC_Subscription extends WC_Order { // Get a full set of subscription dates made up of passed and current dates foreach ( $this->get_valid_date_types() as $date_type ) { - // While 'start' & 'last_payment' are valid date types, they are deprecated and we use 'date_created' & 'last_order_date_created' to refer to them now instead - if ( in_array( $date_type, array( 'last_payment', 'start' ) ) ) { + // While 'last_payment' is a valid date type, it is deprecated and we use 'last_order_date_created' now instead + if ( 'last_payment' === $date_type ) { continue; } @@ -2288,7 +2289,7 @@ class WC_Subscription extends WC_Order { $messages[] = sprintf( __( 'The %s date must occur after the trial end date.', 'woocommerce-subscriptions' ), $date_type ); } case 'trial_end' : - if ( $timestamp <= $timestamps['date_created'] ) { + if ( ! in_array( $date_type, array( 'end', 'cancelled' ) ) && $timestamp <= $timestamps['start'] ) { $messages[] = sprintf( __( 'The %s date must occur after the start date.', 'woocommerce-subscriptions' ), $date_type ); } } @@ -2353,6 +2354,24 @@ class WC_Subscription extends WC_Order { return $this->valid_date_types; } + /** + * Get the subscription's payment method meta. + * + * @since 2.4.3 + * @return array The subscription's payment meta in the format returned by the woocommerce_subscription_payment_meta filter. + */ + public function get_payment_method_meta() { + WC()->payment_gateways(); + + if ( $this->is_manual() ) { + return array(); + } + + $payment_meta = apply_filters( 'woocommerce_subscription_payment_meta', array(), $this ); + + return isset( $payment_meta[ $this->get_payment_method() ] ) ? $payment_meta[ $this->get_payment_method() ]: array(); + } + /************************ * WC_Order overrides * diff --git a/includes/class-wc-subscriptions-addresses.php b/includes/class-wc-subscriptions-addresses.php old mode 100755 new mode 100644 index 381e0cc..d03162f --- a/includes/class-wc-subscriptions-addresses.php +++ b/includes/class-wc-subscriptions-addresses.php @@ -28,6 +28,7 @@ class WC_Subscriptions_Addresses { add_filter( 'woocommerce_address_to_edit', __CLASS__ . '::maybe_populate_subscription_addresses', 10 ); + add_filter( 'woocommerce_get_breadcrumb', __CLASS__ . '::change_addresses_breadcrumb', 10, 1 ); } /** @@ -80,12 +81,15 @@ class WC_Subscriptions_Addresses { } // translators: $1: address type (Shipping Address / Billing Address), $2: opening tag, $3: closing tag - $label = sprintf( __( 'Update the %1$s used for %2$sall%3$s of my active subscriptions', 'woocommerce-subscriptions' ), wcs_get_address_type_to_display( $address_type ), '', '' ); + $label = sprintf( esc_html__( 'Update the %1$s used for %2$sall%3$s of my active subscriptions', 'woocommerce-subscriptions' ), wcs_get_address_type_to_display( $address_type ), '', '' ); - woocommerce_form_field( 'update_all_subscriptions_addresses', array( - 'type' => 'checkbox', - 'class' => array( 'form-row-wide' ), - 'label' => $label, + woocommerce_form_field( + 'update_all_subscriptions_addresses', + array( + 'type' => 'checkbox', + 'class' => array( 'form-row-wide' ), + 'label' => $label, + 'default' => apply_filters( 'wcs_update_all_subscriptions_addresses_checked', false ), ) ); } @@ -176,6 +180,39 @@ class WC_Subscriptions_Addresses { public static function maybe_update_order_address( $subscription, $address_fields ) { _deprecated_function( __METHOD__, '2.0', 'WC_Order::set_address() or WC_Subscription::set_address()' ); } -} -WC_Subscriptions_Addresses::init(); + /** + * Replace the change address breadcrumbs structure to include a link back to the subscription. + * + * @param array $crumbs + * @return array + * @since 2.4.2 + */ + public static function change_addresses_breadcrumb( $crumbs ) { + if ( isset( $_GET['subscription'] ) && is_wc_endpoint_url() && 'edit-address' === WC()->query->get_current_endpoint() ) { + global $wp_query; + $subscription = wcs_get_subscription( absint( $_GET['subscription'] ) ); + + if ( ! $subscription ) { + return $crumbs; + } + + $crumbs[1] = array( + get_the_title( wc_get_page_id( 'myaccount' ) ), + get_permalink( wc_get_page_id( 'myaccount' ) ), + ); + + $crumbs[2] = array( + sprintf( _x( 'Subscription #%s', 'hash before order number', 'woocommerce-subscriptions' ), $subscription->get_order_number() ), + esc_url( $subscription->get_view_order_url() ), + ); + + $crumbs[3] = array( + sprintf( _x( 'Change %s address', 'change billing or shipping address', 'woocommerce-subscriptions' ), $wp_query->query_vars['edit-address'] ), + '', + ); + } + + return $crumbs; + } +} diff --git a/includes/class-wc-subscriptions-cart.php b/includes/class-wc-subscriptions-cart.php old mode 100755 new mode 100644 index e9d3729..e1a3e82 --- a/includes/class-wc-subscriptions-cart.php +++ b/includes/class-wc-subscriptions-cart.php @@ -91,7 +91,7 @@ class WC_Subscriptions_Cart { add_action( 'woocommerce_cart_totals_after_order_total', __CLASS__ . '::display_recurring_totals' ); add_action( 'woocommerce_review_order_after_order_total', __CLASS__ . '::display_recurring_totals' ); - add_action( 'woocommerce_add_to_cart_validation', __CLASS__ . '::check_valid_add_to_cart', 10, 6 ); + add_filter( 'woocommerce_add_to_cart_validation', __CLASS__ . '::check_valid_add_to_cart', 10, 6 ); add_filter( 'woocommerce_cart_needs_shipping', __CLASS__ . '::cart_needs_shipping', 11, 1 ); @@ -349,7 +349,17 @@ class WC_Subscriptions_Cart { unset( WC()->session->wcs_shipping_methods ); // If there is no sign-up fee and a free trial, and no products being purchased with the subscription, we need to zero the fees for the first billing period - if ( 0 == self::get_cart_subscription_sign_up_fee() && self::all_cart_items_have_free_trial() ) { + $remove_fees_from_cart = ( 0 == self::get_cart_subscription_sign_up_fee() && self::all_cart_items_have_free_trial() ); + + /** + * Allow third-parties to override whether the fees will be removed from the initial order cart. + * + * @since 2.4.3 + * @param bool $remove_fees_from_cart Whether the fees will be removed. By default fees will be removed if there is no signup fee and all cart items have a trial. + * @param WC_Cart $cart The standard WC cart object. + * @param array $recurring_carts All the recurring cart objects. + */ + if ( apply_filters( 'wcs_remove_fees_from_initial_cart', $remove_fees_from_cart, $cart, $recurring_carts ) ) { $cart_fees = WC()->cart->get_fees(); if ( WC_Subscriptions::is_woocommerce_pre( '3.2' ) ) { @@ -2154,4 +2164,3 @@ class WC_Subscriptions_Cart { return $package_rates; } } -WC_Subscriptions_Cart::init(); diff --git a/includes/class-wc-subscriptions-change-payment-gateway.php b/includes/class-wc-subscriptions-change-payment-gateway.php old mode 100755 new mode 100644 index facede0..ff166e7 --- a/includes/class-wc-subscriptions-change-payment-gateway.php +++ b/includes/class-wc-subscriptions-change-payment-gateway.php @@ -62,6 +62,9 @@ class WC_Subscriptions_Change_Payment_Gateway { // Change the "Pay for Order" page title to "Change Payment Method" add_filter( 'the_title', __CLASS__ . '::change_payment_method_page_title', 100 ); + // Change the "Pay for Order" breadcrumb to "Change Payment Method" + add_filter( 'woocommerce_get_breadcrumb', __CLASS__ . '::change_payment_method_breadcrumb', 10, 1 ); + // Maybe filter subscriptions_needs_payment to return false when processing change-payment-gateway requests add_filter( 'woocommerce_subscription_needs_payment', __CLASS__ . '::maybe_override_needs_payment', 10, 1 ); } @@ -149,6 +152,18 @@ class WC_Subscriptions_Change_Payment_Gateway { $subscription_key = isset( $_GET['key'] ) ? wc_clean( $_GET['key'] ) : ''; $subscription = wcs_get_subscription( absint( $wp->query_vars['order-pay'] ) ); + /** + * wcs_before_replace_pay_shortcode + * + * Action that allows payment methods to modify the subscription object so, for example, + * if the new payment method still hasn't been set, they can set it temporarily (without saving) + * + * @since 2.4.0 + * + * @param WC_Subscription $subscription + */ + do_action( 'wcs_before_replace_pay_shortcode', $subscription ); + if ( $subscription->get_id() == absint( $wp->query_vars['order-pay'] ) && $subscription->get_order_key() == $subscription_key ) { ?> @@ -438,9 +453,29 @@ class WC_Subscriptions_Change_Payment_Gateway { * @since 1.4 */ public static function get_available_payment_gateways( $available_gateways ) { + $is_change_payment_method_request = isset( $_GET['change_payment_method'] ); - // The customer change payment method flow uses the order pay endpoint and so we only need to check for order pay endpoints along side cart related conditions. - if ( isset( $_GET['change_payment_method'] ) || ( ! is_wc_endpoint_url( 'order-pay' ) && wcs_cart_contains_failed_renewal_order_payment() ) ) { + // If we're on a order-pay page but not changing a subscription's payment method, exit early - we don't want to filter the available payment gateways while the customer pays for an order. + if ( ! $is_change_payment_method_request && is_wc_endpoint_url( 'order-pay' ) ) { + return $available_gateways; + } + + $renewal_order_cart_item = wcs_cart_contains_failed_renewal_order_payment(); + $cart_contains_failed_renewal = (bool) $renewal_order_cart_item; + $cart_contains_failed_manual_renewal = false; + + /** + * If there's no change payment request and the cart contains a failed renewal order, check if the subscription is manual. + * + * We update failing, non-manual subscriptions in @see WC_Subscriptions_Change_Payment_Gateway::change_failing_payment_method() so we + * don't need to apply our available payment gateways filter if the subscription is manual. + */ + if ( ! $is_change_payment_method_request && $cart_contains_failed_renewal ) { + $subscription = wcs_get_subscription( $renewal_order_cart_item['subscription_renewal']['subscription_id'] ); + $cart_contains_failed_manual_renewal = $subscription->is_manual(); + } + + if ( apply_filters( 'wcs_payment_gateways_change_payment_method', $is_change_payment_method_request || ( $cart_contains_failed_renewal && ! $cart_contains_failed_manual_renewal ) ) ) { foreach ( $available_gateways as $gateway_id => $gateway ) { if ( true !== $gateway->supports( 'subscription_payment_method_change_customer' ) ) { unset( $available_gateways[ $gateway_id ] ); @@ -555,6 +590,42 @@ class WC_Subscriptions_Change_Payment_Gateway { return $title; } + /** + * Replace the breadcrumbs structure to add a link to the subscription page and change the current page to "Change Payment Method" + * + * @param array $crumbs + * @return array + * @since 2.4.2 + */ + public static function change_payment_method_breadcrumb( $crumbs ) { + + if ( is_main_query() && is_page() && is_checkout_pay_page() && self::$is_request_to_change_payment ) { + global $wp_query; + $subscription = wcs_get_subscription( absint( $wp_query->query_vars['order-pay'] ) ); + + if ( ! $subscription ) { + return $crumbs; + } + + $crumbs[1] = array( + get_the_title( wc_get_page_id( 'myaccount' ) ), + get_permalink( wc_get_page_id( 'myaccount' ) ), + ); + + $crumbs[2] = array( + sprintf( _x( 'Subscription #%s', 'hash before order number', 'woocommerce-subscriptions' ), $subscription->get_order_number() ), + esc_url( $subscription->get_view_order_url() ), + ); + + $crumbs[3] = array( + _x( 'Change Payment Method', 'the page title of the change payment method form', 'woocommerce-subscriptions' ), + '', + ); + } + + return $crumbs; + } + /** * When processing a change_payment_method request on a subscription that has a failed or pending renewal, * we don't want the `$order->needs_payment()` check inside WC_Shortcode_Checkout::order_pay() to pass. @@ -640,4 +711,3 @@ class WC_Subscriptions_Change_Payment_Gateway { return $subscription_can_be_changed; } } -WC_Subscriptions_Change_Payment_Gateway::init(); diff --git a/includes/class-wc-subscriptions-checkout.php b/includes/class-wc-subscriptions-checkout.php old mode 100755 new mode 100644 index 5c46076..51c5f6f --- a/includes/class-wc-subscriptions-checkout.php +++ b/includes/class-wc-subscriptions-checkout.php @@ -505,5 +505,3 @@ class WC_Subscriptions_Checkout { return $actions; } } - -WC_Subscriptions_Checkout::init(); diff --git a/includes/class-wc-subscriptions-coupon.php b/includes/class-wc-subscriptions-coupon.php old mode 100755 new mode 100644 index 5478aba..c7add64 --- a/includes/class-wc-subscriptions-coupon.php +++ b/includes/class-wc-subscriptions-coupon.php @@ -188,7 +188,7 @@ class WC_Subscriptions_Coupon { $coupon_type = wcs_get_coupon_property( $coupon, 'discount_type' ); // Only deal with subscriptions coupon types which apply to cart items - if ( ! in_array( $coupon_type, array( 'recurring_fee', 'recurring_percent', 'sign_up_fee', 'sign_up_fee_percent', 'renewal_fee', 'renewal_percent', 'renewal_cart' ) ) ) { + if ( ! in_array( $coupon_type, array( 'recurring_fee', 'recurring_percent', 'sign_up_fee', 'sign_up_fee_percent', 'renewal_fee', 'renewal_percent', 'renewal_cart', 'initial_cart' ) ) ) { return $discount; } @@ -227,7 +227,7 @@ class WC_Subscriptions_Coupon { // If all items have a free trial we don't need to apply recurring coupons to the initial total if ( $is_switch || ! WC_Subscriptions_Cart::all_cart_items_have_free_trial() ) { - if ( 'recurring_fee' == $coupon_type ) { + if ( 'recurring_fee' === $coupon_type || 'initial_cart' === $coupon_type ) { $apply_initial_coupon = true; } @@ -437,7 +437,7 @@ class WC_Subscriptions_Coupon { $coupon_type = wcs_get_coupon_property( $coupon, 'discount_type' ); // ignore non-subscription coupons - if ( ! in_array( $coupon_type, array( 'recurring_fee', 'sign_up_fee', 'recurring_percent', 'sign_up_fee_percent', 'renewal_fee', 'renewal_percent', 'renewal_cart' ) ) ) { + if ( ! in_array( $coupon_type, array( 'recurring_fee', 'sign_up_fee', 'recurring_percent', 'sign_up_fee_percent', 'renewal_fee', 'renewal_percent', 'renewal_cart', 'initial_cart' ) ) ) { // but make sure there is actually something for the coupon to be applied to (i.e. not a free trial) if ( ( wcs_cart_contains_renewal() || WC_Subscriptions_Cart::cart_contains_subscription() ) && 0 == WC()->cart->subtotal ) { @@ -496,7 +496,7 @@ class WC_Subscriptions_Coupon { $error_message = sprintf( __( 'Sorry, "%s" can only be applied to subscription parent orders which contain a product with signup fees.', 'woocommerce-subscriptions' ), wcs_get_coupon_property( $coupon, 'code' ) ); // Only recurring coupons can be applied to subscriptions } elseif ( ! in_array( $coupon_type, array( 'recurring_fee', 'recurring_percent' ) ) && wcs_is_subscription( $order ) ) { - $error_message = __( 'Sorry, only recurring coupons can only be applied to subscriptions.', 'woocommerce-subscriptions' ); + $error_message = __( 'Sorry, only recurring coupons can be applied to subscriptions.', 'woocommerce-subscriptions' ); } if ( ! empty( $error_message ) ) { @@ -597,7 +597,7 @@ class WC_Subscriptions_Coupon { public static function filter_product_coupon_types( $product_coupon_types ) { if ( is_array( $product_coupon_types ) ) { - $product_coupon_types = array_merge( $product_coupon_types, array( 'recurring_fee', 'recurring_percent', 'sign_up_fee', 'sign_up_fee_percent', 'renewal_fee', 'renewal_percent', 'renewal_cart' ) ); + $product_coupon_types = array_merge( $product_coupon_types, array( 'recurring_fee', 'recurring_percent', 'sign_up_fee', 'sign_up_fee_percent', 'renewal_fee', 'renewal_percent', 'renewal_cart', 'initial_cart' ) ); } return $product_coupon_types; @@ -681,6 +681,7 @@ class WC_Subscriptions_Coupon { 'renewal_percent' => __( 'Renewal % discount', 'woocommerce-subscriptions' ), 'renewal_fee' => __( 'Renewal product discount', 'woocommerce-subscriptions' ), 'renewal_cart' => __( 'Renewal cart discount', 'woocommerce-subscriptions' ), + 'initial_cart' => __( 'Initial payment discount', 'woocommerce-subscriptions' ), ) ); } @@ -694,10 +695,14 @@ class WC_Subscriptions_Coupon { * @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' ) ); + switch ( $coupon->get_discount_type() ) { + case 'renewal_cart': + $label = esc_html( __( 'Renewal Discount', 'woocommerce-subscriptions' ) ); + break; + case 'initial_cart': + $label = esc_html( __( 'Discount', 'woocommerce-subscriptions' ) ); + break; } return $label; @@ -1253,5 +1258,3 @@ class WC_Subscriptions_Coupon { _deprecated_function( __METHOD__, '2.0', 'WooCommerce 2.3 removed after tax discounts. Use ' . __CLASS__ .'::apply_subscription_discount( $original_price, $cart_item, $cart )' ); } } - -WC_Subscriptions_Coupon::init(); diff --git a/includes/class-wc-subscriptions-email.php b/includes/class-wc-subscriptions-email.php old mode 100755 new mode 100644 index 722b0d3..dbca723 --- a/includes/class-wc-subscriptions-email.php +++ b/includes/class-wc-subscriptions-email.php @@ -36,17 +36,6 @@ class WC_Subscriptions_Email { * @since 1.4 */ public static function add_emails( $email_classes ) { - - require_once( 'emails/class-wcs-email-new-renewal-order.php' ); - require_once( 'emails/class-wcs-email-new-switch-order.php' ); - require_once( 'emails/class-wcs-email-customer-processing-renewal-order.php' ); - require_once( 'emails/class-wcs-email-customer-completed-renewal-order.php' ); - require_once( 'emails/class-wcs-email-customer-completed-switch-order.php' ); - require_once( 'emails/class-wcs-email-customer-renewal-invoice.php' ); - require_once( 'emails/class-wcs-email-cancelled-subscription.php' ); - require_once( 'emails/class-wcs-email-expired-subscription.php' ); - require_once( 'emails/class-wcs-email-on-hold-subscription.php' ); - $email_classes['WCS_Email_New_Renewal_Order'] = new WCS_Email_New_Renewal_Order(); $email_classes['WCS_Email_New_Switch_Order'] = new WCS_Email_New_Switch_Order(); $email_classes['WCS_Email_Processing_Renewal_Order'] = new WCS_Email_Processing_Renewal_Order(); @@ -363,5 +352,3 @@ class WC_Subscriptions_Email { _deprecated_function( __FUNCTION__, '2.0' ); } } - -WC_Subscriptions_Email::init(); diff --git a/includes/class-wc-subscriptions-manager.php b/includes/class-wc-subscriptions-manager.php old mode 100755 new mode 100644 index fc42da0..86e216d --- a/includes/class-wc-subscriptions-manager.php +++ b/includes/class-wc-subscriptions-manager.php @@ -2360,5 +2360,3 @@ class WC_Subscriptions_Manager { _deprecated_function( __METHOD__, '2.0' ); } } - -WC_Subscriptions_Manager::init(); diff --git a/includes/class-wc-subscriptions-order.php b/includes/class-wc-subscriptions-order.php old mode 100755 new mode 100644 index 6e2456b..b23c47e --- a/includes/class-wc-subscriptions-order.php +++ b/includes/class-wc-subscriptions-order.php @@ -485,12 +485,12 @@ class WC_Subscriptions_Order { // Do we need to activate a subscription? if ( $order_completed && ! $subscription->has_status( wcs_get_subscription_ended_statuses() ) && ! $subscription->has_status( 'active' ) ) { - $new_start_date_offset = current_time( 'timestamp', true ) - $subscription->get_time( 'date_created' ); + $new_start_date_offset = current_time( 'timestamp', true ) - $subscription->get_time( 'start' ); // if the payment has been processed more than an hour after the order was first created, let's update the dates on the subscription to account for that, because it may have even been processed days after it was first placed - if ( $new_start_date_offset > HOUR_IN_SECONDS ) { + if ( abs( $new_start_date_offset ) > HOUR_IN_SECONDS ) { - $dates = array( 'date_created' => current_time( 'mysql', true ) ); + $dates = array( 'start' => current_time( 'mysql', true ) ); if ( WC_Subscriptions_Synchroniser::subscription_contains_synced_product( $subscription ) ) { @@ -498,15 +498,15 @@ class WC_Subscriptions_Order { $next_payment = $subscription->get_time( 'next_payment' ); // if either there is a free trial date or a next payment date that falls before now, we need to recalculate all the sync'd dates - if ( ( $trial_end > 0 && $trial_end < wcs_date_to_time( $dates['date_created'] ) ) || ( $next_payment > 0 && $next_payment < wcs_date_to_time( $dates['date_created'] ) ) ) { + if ( ( $trial_end > 0 && $trial_end < wcs_date_to_time( $dates['start'] ) ) || ( $next_payment > 0 && $next_payment < wcs_date_to_time( $dates['start'] ) ) ) { foreach ( $subscription->get_items() as $item ) { $product_id = wcs_get_canonical_product_id( $item ); if ( WC_Subscriptions_Synchroniser::is_product_synced( $product_id ) ) { - $dates['trial_end'] = WC_Subscriptions_Product::get_trial_expiration_date( $product_id, $dates['date_created'] ); - $dates['next_payment'] = WC_Subscriptions_Synchroniser::calculate_first_payment_date( $product_id, 'mysql', $dates['date_created'] ); - $dates['end'] = WC_Subscriptions_Product::get_expiration_date( $product_id, $dates['date_created'] ); + $dates['trial_end'] = WC_Subscriptions_Product::get_trial_expiration_date( $product_id, $dates['start'] ); + $dates['next_payment'] = WC_Subscriptions_Synchroniser::calculate_first_payment_date( $product_id, 'mysql', $dates['start'] ); + $dates['end'] = WC_Subscriptions_Product::get_expiration_date( $product_id, $dates['start'] ); break; } } @@ -2120,4 +2120,3 @@ class WC_Subscriptions_Order { return wcs_get_objects_property( $order, 'currency' ); } } -WC_Subscriptions_Order::init(); diff --git a/includes/class-wc-subscriptions-product.php b/includes/class-wc-subscriptions-product.php old mode 100755 new mode 100644 index 454d0d4..b12dd56 --- a/includes/class-wc-subscriptions-product.php +++ b/includes/class-wc-subscriptions-product.php @@ -361,7 +361,7 @@ class WC_Subscriptions_Product { } } else { // translators: 1$: recurring amount, 2$: subscription period (e.g. "month" or "3 months") (e.g. "$15 / month" or "$15 every 2nd month") - $subscription_string = sprintf( _n( '%1$s / %2$s', ' %1$s every %2$s', $billing_interval, 'woocommerce-subscriptions' ), $price, wcs_get_subscription_period_strings( $billing_interval, $billing_period ) ); + $subscription_string = sprintf( _n( '%1$s / %2$s', '%1$s every %2$s', $billing_interval, 'woocommerce-subscriptions' ), $price, wcs_get_subscription_period_strings( $billing_interval, $billing_period ) ); } } elseif ( $include['subscription_price'] ) { $subscription_string = $price; @@ -398,7 +398,7 @@ class WC_Subscriptions_Product { * 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. + * @return string 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 ) { @@ -420,7 +420,7 @@ class WC_Subscriptions_Product { * Returns the sale price per period for a product if it is a subscription. * * @param mixed $product A WC_Product object or product ID - * @return float + * @return string * @since 2.2.0 */ public static function get_regular_price( $product, $context = 'view' ) { @@ -438,7 +438,7 @@ class WC_Subscriptions_Product { * Returns the regular price per period for a product if it is a subscription. * * @param mixed $product A WC_Product object or product ID - * @return float + * @return string * @since 2.2.0 */ public static function get_sale_price( $product, $context = 'view' ) { @@ -511,7 +511,7 @@ class WC_Subscriptions_Product { * Returns the sign-up fee for a subscription, if it is a subscription. * * @param mixed $product A WC_Product object or product ID - * @return float The value of the sign-up fee, or 0 if the product is not a subscription or the subscription has no sign-up fee + * @return int|string The value of the sign-up fee, or 0 if the product is not a subscription or the subscription has no sign-up fee * @since 1.0 */ public static function get_sign_up_fee( $product ) { @@ -1248,5 +1248,3 @@ class WC_Subscriptions_Product { return wcs_get_price_excluding_tax( $product, array( 'qty' => $qty, 'price' => WC_Subscriptions_Product::get_sign_up_fee( $product ) ) ); } } - -WC_Subscriptions_Product::init(); diff --git a/includes/class-wc-subscriptions-renewal-order.php b/includes/class-wc-subscriptions-renewal-order.php old mode 100755 new mode 100644 index 4af4008..7af7464 --- a/includes/class-wc-subscriptions-renewal-order.php +++ b/includes/class-wc-subscriptions-renewal-order.php @@ -661,4 +661,3 @@ class WC_Subscriptions_Renewal_Order { _deprecated_function( __METHOD__, '2.0', __CLASS__ . '::maybe_record_subscription_payment( $order_id, $orders_old_status, $orders_new_status )' ); } } -WC_Subscriptions_Renewal_Order::init(); diff --git a/includes/class-wc-subscriptions-switcher.php b/includes/class-wc-subscriptions-switcher.php old mode 100755 new mode 100644 index e68b401..7cd7fff --- a/includes/class-wc-subscriptions-switcher.php +++ b/includes/class-wc-subscriptions-switcher.php @@ -21,7 +21,7 @@ class WC_Subscriptions_Switcher { add_action( 'woocommerce_loaded', __CLASS__ . '::attach_dependant_hooks' ); // Check if the current request is for switching a subscription and if so, start he switching process - add_filter( 'template_redirect', __CLASS__ . '::subscription_switch_handler', 100 ); + add_action( 'template_redirect', __CLASS__ . '::subscription_switch_handler', 100 ); // Pass in the filter switch to the group items add_filter( 'woocommerce_grouped_product_list_link', __CLASS__ . '::add_switch_query_arg_grouped', 12 ); @@ -31,7 +31,7 @@ class WC_Subscriptions_Switcher { add_filter( 'woocommerce_subscription_settings', __CLASS__ . '::add_settings' ); // Add the "Switch" button to the View Subscription table - add_filter( 'woocommerce_order_item_meta_end', __CLASS__ . '::print_switch_link', 10, 3 ); + add_action( 'woocommerce_order_item_meta_end', __CLASS__ . '::print_switch_link', 10, 3 ); // We need to create subscriptions on checkout and want to do it after almost all other extensions have added their products/items/fees add_action( 'woocommerce_checkout_order_processed', __CLASS__ . '::process_checkout', 50, 2 ); @@ -46,13 +46,13 @@ class WC_Subscriptions_Switcher { add_filter( 'woocommerce_add_to_cart_validation', __CLASS__ . '::validate_switch_request', 10, 4 ); // Record subscription switching in the cart - add_action( 'woocommerce_add_cart_item_data', __CLASS__ . '::set_switch_details_in_cart', 10, 3 ); + add_filter( 'woocommerce_add_cart_item_data', __CLASS__ . '::set_switch_details_in_cart', 10, 3 ); // Make sure the 'switch_subscription' cart item data persists - add_action( 'woocommerce_get_cart_item_from_session', __CLASS__ . '::get_cart_from_session', 10, 3 ); + add_filter( 'woocommerce_get_cart_item_from_session', __CLASS__ . '::get_cart_from_session', 10, 3 ); // Set totals for subscription switch orders (needs to be hooked just before WC_Subscriptions_Cart::calculate_subscription_totals()) - add_filter( 'woocommerce_before_calculate_totals', __CLASS__ . '::calculate_prorated_totals', 99, 1 ); + add_action( 'woocommerce_before_calculate_totals', __CLASS__ . '::calculate_prorated_totals', 99, 1 ); // Don't display free trials when switching a subscription, because no free trials are provided add_filter( 'woocommerce_subscriptions_product_price_string_inclusions', __CLASS__ . '::customise_product_string_inclusions', 12, 2 ); @@ -73,7 +73,7 @@ class WC_Subscriptions_Switcher { add_action( 'addons_add_to_cart_url', __CLASS__ . '::addons_add_to_cart_url', 10 ); // Make sure the switch process persists when having to choose product addons - add_action( 'woocommerce_hidden_order_itemmeta', __CLASS__ . '::hidden_order_itemmeta', 10 ); + add_filter( 'woocommerce_hidden_order_itemmeta', __CLASS__ . '::hidden_order_itemmeta', 10 ); // Add/remove the print switch link filters when printing HTML/plain subscription emails add_action( 'woocommerce_email_before_subscription_table', __CLASS__ . '::remove_print_switch_link' ); @@ -101,7 +101,7 @@ class WC_Subscriptions_Switcher { add_filter( 'woocommerce_cart_needs_payment', __CLASS__ . '::cart_needs_payment' , 50, 2 ); // Require payment when switching from a $0 / period subscription to a non-zero subscription to process automatic payments - add_filter( 'woocommerce_subscriptions_switch_completed', __CLASS__ . '::maybe_set_payment_method_after_switch' , 10, 1 ); + add_action( 'woocommerce_subscriptions_switch_completed', __CLASS__ . '::maybe_set_payment_method_after_switch' , 10, 1 ); // Do not reduce product stock when the order item is simply to record a switch add_filter( 'woocommerce_order_item_quantity', __CLASS__ . '::maybe_do_not_reduce_stock', 10, 3 ); @@ -1152,7 +1152,7 @@ class WC_Subscriptions_Switcher { if ( ! current_user_can( 'switch_shop_subscription', $subscription->get_id() ) ) { wc_add_notice( __( 'You can not switch this subscription. It appears you do not own the subscription.', 'woocommerce-subscriptions' ), 'error' ); WC()->cart->empty_cart( true ); - wp_redirect( get_permalink( $subscription['product_id'] ) ); + wp_redirect( get_permalink( $product_id ) ); exit(); } @@ -1293,15 +1293,15 @@ class WC_Subscriptions_Switcher { continue; } - $subscription = wcs_get_subscription( $cart_item['subscription_switch']['subscription_id'] ); - $existing_item = wcs_get_order_item( $cart_item['subscription_switch']['item_id'], $subscription ); + $subscription = wcs_get_subscription( $cart_item['subscription_switch']['subscription_id'] ); + $existing_item = wcs_get_order_item( $cart_item['subscription_switch']['item_id'], $subscription ); if ( empty( $existing_item ) ) { WC()->cart->remove_cart_item( $cart_item_key ); continue; } - $item_data = $cart_item['data']; + $product_in_cart = $cart_item['data']; $product_id = wcs_get_canonical_product_id( $cart_item ); $product = wc_get_product( $product_id ); $is_virtual_product = $product->is_virtual(); @@ -1345,13 +1345,15 @@ class WC_Subscriptions_Switcher { $days_until_next_payment = ceil( ( $next_payment_timestamp - gmdate( 'U' ) ) / ( 60 * 60 * 24 ) ); // If the subscription contains a synced product and the next payment is actually the first payment, determine the days in the "old" cycle from the subscription object - if ( WC_Subscriptions_Synchroniser::subscription_contains_synced_product( $subscription->get_id() ) && WC_Subscriptions_Synchroniser::calculate_first_payment_date( $product, 'timestamp', $subscription->get_date( 'date_created' ) ) == $next_payment_timestamp ) { + if ( WC_Subscriptions_Synchroniser::subscription_contains_synced_product( $subscription->get_id() ) && WC_Subscriptions_Synchroniser::calculate_first_payment_date( $product, 'timestamp', $subscription->get_date( 'start' ) ) == $next_payment_timestamp ) { $days_in_old_cycle = wcs_get_days_in_cycle( $subscription->get_billing_period(), $subscription->get_billing_interval() ); } else { // Find the number of days between the two $days_in_old_cycle = $days_until_next_payment + $days_since_last_payment; } + $days_in_old_cycle = apply_filters( 'wcs_switch_proration_days_in_old_cycle', $days_in_old_cycle, $subscription, $cart_item ); + // 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']; @@ -1371,15 +1373,16 @@ class WC_Subscriptions_Switcher { // Find the $price per day for the old subscription's recurring total $old_price_per_day = $days_in_old_cycle > 0 ? $old_recurring_total / $days_in_old_cycle : $old_recurring_total; + $old_price_per_day = apply_filters( 'wcs_switch_proration_old_price_per_day', $old_price_per_day, $subscription, $cart_item, $old_recurring_total, $days_in_old_cycle ); // Find the price per day for the new subscription's recurring total based on billing schedule - $days_in_new_cycle = wcs_get_days_in_cycle( WC_Subscriptions_Product::get_period( $item_data ), WC_Subscriptions_Product::get_interval( $item_data ) ); + $days_in_new_cycle = wcs_get_days_in_cycle( WC_Subscriptions_Product::get_period( $product_in_cart ), WC_Subscriptions_Product::get_interval( $product_in_cart ) ); // Whether the days in new cycle match the days in old,ignoring any rounding. $days_in_new_and_old_cycle_match = ceil( $days_in_new_cycle ) == $days_in_old_cycle || floor( $days_in_new_cycle ) == $days_in_old_cycle; // Whether the new item uses the same billing interval & cycle as the old subscription, - $matching_billing_cycle = WC_Subscriptions_Product::get_period( $item_data ) == $subscription->get_billing_period() && WC_Subscriptions_Product::get_interval( $item_data ) == $subscription->get_billing_interval(); + $matching_billing_cycle = WC_Subscriptions_Product::get_period( $product_in_cart ) == $subscription->get_billing_period() && WC_Subscriptions_Product::get_interval( $product_in_cart ) == $subscription->get_billing_interval(); $switch_during_trial = $subscription->get_time( 'trial_end' ) > gmdate( 'U' ); // Set the days in each cycle to match if they are equal (ignoring any rounding discrepancy) or if the subscription is switched during a trial and has a matching billing cycle. @@ -1387,29 +1390,41 @@ class WC_Subscriptions_Switcher { $days_in_new_cycle = $days_in_old_cycle; } + $days_in_new_cycle = apply_filters( 'wcs_switch_proration_days_in_new_cycle', $days_in_new_cycle, $subscription, $cart_item, $days_in_old_cycle ); + // 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; + $new_price_per_day = ( WC_Subscriptions_Product::get_price( $product_in_cart ) * $cart_item['quantity'] ) / $days_in_new_cycle; add_filter( 'woocommerce_product_get_price', 'WC_Subscriptions_Cart::set_subscription_prices_for_calculation', 100, 2 ); + $new_price_per_day = apply_filters( 'wcs_switch_proration_new_price_per_day', $new_price_per_day, $subscription, $cart_item, $days_in_new_cycle ); + if ( $old_price_per_day < $new_price_per_day ) { - - WC()->cart->cart_contents[ $cart_item_key ]['subscription_switch']['upgraded_or_downgraded'] = 'upgraded'; - + $switch_type = 'upgrade'; } elseif ( $old_price_per_day > $new_price_per_day && $new_price_per_day >= 0 ) { - - WC()->cart->cart_contents[ $cart_item_key ]['subscription_switch']['upgraded_or_downgraded'] = 'downgraded'; - + $switch_type = 'downgrade'; + } else { + $switch_type = 'crossgrade'; } + $switch_type = apply_filters( 'wcs_switch_proration_switch_type', $switch_type, $subscription, $cart_item, $old_price_per_day, $new_price_per_day ); + + if ( ! in_array( $switch_type, array( 'upgrade', 'downgrade', 'crossgrade' ) ) ) { + throw new UnexpectedValueException( sprintf( __( 'Invalid switch type "%s". Switch must be one of: "upgrade", "downgrade" or "crossgrade".', 'woocommerce-subscriptions' ), $switch_type ) ); + } + + WC()->cart->cart_contents[ $cart_item_key ]['subscription_switch']['upgraded_or_downgraded'] = sprintf( '%sd', $switch_type ); // preserve past tense for backward compatibility (luckily past tense for all allowed switch types end with a d) + // Now lets see if we should add a prorated amount to the sign-up fee (for upgrades) or extend the next payment date (for downgrades) if ( in_array( $apportion_recurring_price, array( 'yes', 'yes-upgrade' ) ) || ( in_array( $apportion_recurring_price, array( 'virtual', 'virtual-upgrade' ) ) && $is_virtual_product ) ) { // If the customer is upgrading, we may need to add a gap payment to the sign-up fee or to reduce the pre-paid period (or both) - if ( $old_price_per_day < $new_price_per_day ) { + if ( 'upgrade' === $switch_type ) { - // The new subscription may be more expensive, but it's also on a shorter billing cycle, so reduce the next pre-paid term - if ( $days_in_old_cycle > $days_in_new_cycle ) { + // The new subscription may be more expensive, but it's also on a shorter billing cycle, so reduce the next pre-paid term by default, but also allow this to be customised + $reduce_pre_paid_term = apply_filters( 'wcs_switch_proration_reduce_pre_paid_term', $days_in_old_cycle > $days_in_new_cycle, $subscription, $cart_item, $days_in_old_cycle, $days_in_new_cycle, $old_price_per_day, $new_price_per_day ); + + if ( $reduce_pre_paid_term ) { // Find out how many days at the new price per day the customer would receive for the total amount already paid // (e.g. if the customer paid $10 / month previously, and was switching to a $5 / week subscription, she has pre-paid 14 days at the new price) @@ -1437,7 +1452,7 @@ class WC_Subscriptions_Switcher { $extra_to_pay = $days_until_next_payment * ( $new_price_per_day - $old_price_per_day ); // when calculating a subscription with one length (no more next payment date and the end date may have been pushed back) we need to pay for those extra days at the new price per day between the old next payment date and new end date - if ( 1 == WC_Subscriptions_Product::get_length( $item_data ) ) { + if ( 1 == WC_Subscriptions_Product::get_length( $product_in_cart ) ) { $days_to_new_end = floor( ( $end_timestamp - $next_payment_timestamp ) / ( 60 * 60 * 24 ) ); if ( $days_to_new_end > 0 ) { @@ -1447,6 +1462,7 @@ class WC_Subscriptions_Switcher { // We need to find the per item extra to pay so we can set it as the sign-up fee (WC will then multiply it by the quantity) $extra_to_pay = $extra_to_pay / $cart_item['quantity']; + $extra_to_pay = apply_filters( 'wcs_switch_proration_extra_to_pay', $extra_to_pay, $subscription, $cart_item, $days_in_old_cycle ); // Keep a record of the two separate amounts so we store these and calculate future switch amounts correctly $existing_sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( WC()->cart->cart_contents[ $cart_item_key ]['data'] ); @@ -1456,7 +1472,7 @@ class WC_Subscriptions_Switcher { } // If the customer is downgrading, set the next payment date and maybe extend it if downgrades are prorated - } elseif ( $old_price_per_day > $new_price_per_day && $new_price_per_day > 0 ) { + } elseif ( 'downgrade' === $switch_type ) { $old_total_paid = $old_price_per_day * $days_until_next_payment; @@ -1875,43 +1891,40 @@ class WC_Subscriptions_Switcher { } if ( ! empty( $switch_data['switches'] ) && is_array( $switch_data['switches'] ) ) { + foreach ( $switch_data['switches'] as $order_item_id => $switched_item_data ) { - // If the switch data is in the old format - if ( ! array_key_exists( 'remove_line_item', reset( $switch_data['switches'] ) ) ) { - self::switch_line_items_pre_2_1_2( $switch_data['switches'], $order, $subscription ); - } else { - foreach ( $switch_data['switches'] as $order_item_id => $switched_item_data ) { + // If we are adding a line item to an existing subscription + if ( isset( $switched_item_data['add_line_item'] ) ) { + wcs_update_order_item_type( $switched_item_data['add_line_item'], 'line_item', $subscription->get_id() ); + do_action( 'woocommerce_subscription_item_switched', $order, $subscription, $switched_item_data['add_line_item'], $switched_item_data['remove_line_item'] ); + } - // If we are adding a line item to an existing subscription - if ( isset( $switched_item_data['add_line_item'] ) ) { - wcs_update_order_item_type( $switched_item_data['add_line_item'], 'line_item', $subscription->get_id() ); - do_action( 'woocommerce_subscription_item_switched', $order, $subscription, $switched_item_data['add_line_item'], $switched_item_data['remove_line_item'] ); - } + // remove the existing subscription item + $old_subscription_item = wcs_get_order_item( $switched_item_data['remove_line_item'], $subscription ); + $switch_order_item = wcs_get_order_item( $order_item_id, $order ); - // remove the existing subscription item - $old_subscription_item = wcs_get_order_item( $switched_item_data['remove_line_item'], $subscription ); - $switch_order_item = wcs_get_order_item( $order_item_id, $order ); + if ( empty( $old_subscription_item ) ) { + throw new Exception( __( 'The original subscription item being switched cannot be found.', 'woocommerce-subscriptions' ) ); + } elseif ( empty( $switch_order_item ) ) { + throw new Exception( __( 'The item on the switch order cannot be found.', 'woocommerce-subscriptions' ) ); + } else { + // We don't want to include switch item meta in order item name + add_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_true' ); + $old_item_name = wcs_get_order_item_name( $old_subscription_item, array( 'attributes' => true ) ); + $new_item_name = wcs_get_order_item_name( $switch_order_item, array( 'attributes' => true ) ); + remove_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_true' ); - if ( empty( $old_subscription_item ) ) { - throw new Exception( __( 'The original subscription item being switched cannot be found.', 'woocommerce-subscriptions' ) ); - } elseif ( empty( $switch_order_item ) ) { - throw new Exception( __( 'The item on the switch order cannot be found.', 'woocommerce-subscriptions' ) ); - } else { - // We don't want to include switch item meta in order item name - add_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_true' ); - $old_item_name = wcs_get_order_item_name( $old_subscription_item, array( 'attributes' => true ) ); - $new_item_name = wcs_get_order_item_name( $switch_order_item, array( 'attributes' => true ) ); - remove_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_true' ); + wcs_update_order_item_type( $switched_item_data['remove_line_item'], 'line_item_switched', $subscription->get_id() ); - wcs_update_order_item_type( $switched_item_data['remove_line_item'], 'line_item_switched', $subscription->get_id() ); - - // translators: 1$: old item, 2$: new item when switching - $add_note = sprintf( _x( 'Customer switched from: %1$s to %2$s.', 'used in order notes', 'woocommerce-subscriptions' ), $old_item_name, $new_item_name ); - } + // translators: 1$: old item, 2$: new item when switching + $add_note = sprintf( _x( 'Customer switched from: %1$s to %2$s.', 'used in order notes', 'woocommerce-subscriptions' ), $old_item_name, $new_item_name ); } } } + // Subscription objects hold an internal cache of line items so we need to get an updated subscription object after changing the line item types directly in the database. + $subscription = wcs_get_subscription( $subscription_id ); + if ( ! empty( $add_note ) ) { $subscription->add_order_note( $add_note ); } @@ -1942,11 +1955,7 @@ class WC_Subscriptions_Switcher { } } - // If the shipping data is in the old format - if ( ! empty( $switch_data['shipping_methods'] ) ) { - self::switch_shipping_line_items_pre_2_1_2( $subscription, $switch_data['shipping_methods'] ); - } else if ( ! empty( $switch_data['shipping_line_items'] ) && is_array( $switch_data['shipping_line_items'] ) ) { - + if ( ! empty( $switch_data['shipping_line_items'] ) && is_array( $switch_data['shipping_line_items'] ) ) { // Archive the old subscription shipping methods foreach ( $subscription->get_shipping_methods() as $shipping_line_item_id => $item ) { wcs_update_order_item_type( $shipping_line_item_id, 'shipping_switched', $subscription->get_id() ); @@ -2199,98 +2208,6 @@ class WC_Subscriptions_Switcher { return $total; } - /** - * Switch subscription line items provided line item data in the 2.1 switch order meta format. - * - * @param array $switches an array of switch items and its meta - * @param WC_Order $order the switch order - * @param WC_Subscription $subscription the subscription being switched - * @since 2.1.2 - */ - protected static function switch_line_items_pre_2_1_2( $switches, $order, $subscription ) { - - foreach ( $switches as $order_item_id => $switch_item_data ) { - - $order_item = wcs_get_order_item( $order_item_id, $order ); - - // if we are simply adding this product to an existing subscription - if ( isset( $switch_item_data['add_order_item_data'] ) ) { - $product = WC_Subscriptions::get_product( wcs_get_canonical_product_id( $order_item ) ); - $line_tax_data = wc_get_order_item_meta( $order_item_id, '_line_tax_data', true ); - $variation_attributes = ( method_exists( $product, 'get_variation_attributes' ) ) ? $product->get_variation_attributes() : array(); - - $item_id = $subscription->add_product( $product, $order_item['qty'], array( - 'variation' => $variation_attributes, - 'totals' => $switch_item_data['add_order_item_data']['totals'], - ) ); - - foreach ( $switch_item_data['add_order_item_data']['meta'] as $key => $value ) { - if ( ! array_key_exists( 'attribute_' . $key, $variation_attributes ) ) { - wc_add_order_item_meta( $item_id, $key, reset( $value ), true ); - } - } - - do_action( 'woocommerce_subscription_item_switched', $order, $subscription, $order_item_id, $switch_item_data['subscription_item_id'] ); - } - - // remove the existing subscription item - $old_order_item = wcs_get_order_item( $switch_item_data['subscription_item_id'], $subscription ); - - if ( empty( $old_order_item ) ) { - throw new Exception( __( 'The original subscription item being switched cannot be found.', 'woocommerce-subscriptions' ) ); - } else { - // We don't want to include switch item meta in order item name - add_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_true' ); - $new_order_item_name = wcs_get_order_item_name( $order_item, array( 'attributes' => true ) ); - $old_subscription_item_name = wcs_get_order_item_name( $old_order_item, array( 'attributes' => true ) ); - remove_filter( 'woocommerce_subscriptions_hide_switch_itemmeta', '__return_true' ); - - wcs_update_order_item_type( $switch_item_data['subscription_item_id'], 'line_item_switched', $subscription->get_id() ); - - // translators: 1$: old item, 2$: new item when switching - $subscription->add_order_note( sprintf( _x( 'Customer switched from: %1$s to %2$s.', 'used in order notes', 'woocommerce-subscriptions' ), $old_subscription_item_name, $new_order_item_name ) ); - } - } - } - - /** - * Switch subscription shipping line items provided shipping line item data in the 2.1 switch order meta format. - * - * @param WC_Subscription $subscription the subscription being switched - * @param array $shipping_methods an array of shipping line items and meta - * @since 2.1.2 - */ - protected static function switch_shipping_line_items_pre_2_1_2( $subscription, $shipping_methods ) { - // Archive the old subscription shipping methods - foreach ( $subscription->get_shipping_methods() as $shipping_line_item_id => $item ) { - wcs_update_order_item_type( $shipping_line_item_id, 'shipping_switched', $subscription->get_id() ); - } - - // Add the new shipping line item - foreach ( $shipping_methods as $shipping_line_item ) { - $item_id = wc_add_order_item( $subscription->get_id(), array( - 'order_item_name' => $shipping_line_item['name'], - 'order_item_type' => 'shipping', - ) ); - - if ( ! $item_id || empty( $shipping_line_item['method_id'] ) || empty( $shipping_line_item['cost'] ) || empty( $shipping_line_item['taxes'] ) ) { - throw new Exception( __( 'Failed to update the subscription shipping method.', 'woocommerce-subscriptions' ) ); - } - - // Add shipping order item meta - wc_add_order_item_meta( $item_id, 'method_id', $shipping_line_item['method_id'] ); - wc_add_order_item_meta( $item_id, 'cost', wc_format_decimal( $shipping_line_item['cost'] ) ); - - $taxes = array_map( 'wc_format_decimal', maybe_unserialize( $shipping_line_item['taxes'] ) ); - wc_add_order_item_meta( $item_id, 'taxes', $taxes ); - - // Add custom shipping order item meta added by third-party plugins - foreach ( $shipping_line_item['item_meta'] as $key => $value ) { - wc_add_order_item_meta( $item_id, $key, $value ); - } - } - } - /** * Check if a cart item has a different billing schedule (period and interval) to the subscription being switched. * @@ -2699,4 +2616,3 @@ class WC_Subscriptions_Switcher { return $related_orders; } } -WC_Subscriptions_Switcher::init(); diff --git a/includes/class-wc-subscriptions-synchroniser.php b/includes/class-wc-subscriptions-synchroniser.php old mode 100755 new mode 100644 index 44bb735..6d6b668 --- a/includes/class-wc-subscriptions-synchroniser.php +++ b/includes/class-wc-subscriptions-synchroniser.php @@ -590,7 +590,7 @@ class WC_Subscriptions_Synchroniser { public static function calculate_first_payment_date( $product, $type = 'mysql', $from_date = '' ) { if ( ! is_object( $product ) ) { - $product = WC_Subscriptions::get_product( $product ); + $product = wc_get_product( $product ); } if ( ! self::is_product_synced( $product ) ) { @@ -690,7 +690,7 @@ class WC_Subscriptions_Synchroniser { if ( 'year' == $period || 'month' == $period ) { // First make sure the day is in the past so that we don't end up jumping a month or year because of a few hours difference between now and the billing date - if ( gmdate( 'Ymd', $first_payment_timestamp ) < gmdate( 'Ymd', $from_timestamp ) || gmdate( 'Ymd', $first_payment_timestamp ) < gmdate( 'Ymd' ) ) { + if ( gmdate( 'Ymd', $first_payment_timestamp ) < gmdate( 'Ymd', $from_timestamp ) || gmdate( 'Ymd', $first_payment_timestamp ) < gmdate( 'Ymd', current_time( 'timestamp' ) ) ) { $i = 1; // Then make sure the date and time of the payment is in the future while ( ( $first_payment_timestamp < gmdate( 'U' ) || $first_payment_timestamp < $from_timestamp ) && $i < 30 ) { @@ -1515,5 +1515,3 @@ class WC_Subscriptions_Synchroniser { } } -add_action( 'init', 'WC_Subscriptions_Synchroniser::init' ); - diff --git a/includes/class-wcs-action-scheduler.php b/includes/class-wcs-action-scheduler.php old mode 100755 new mode 100644 index bef25b9..9ebd1da --- a/includes/class-wcs-action-scheduler.php +++ b/includes/class-wcs-action-scheduler.php @@ -35,7 +35,7 @@ class WCS_Action_Scheduler extends WCS_Scheduler { $action_args = $this->get_action_args( $date_type, $subscription ); $timestamp = wcs_date_to_time( $datetime ); - $next_scheduled = wc_next_scheduled_action( $action_hook, $action_args ); + $next_scheduled = as_next_scheduled_action( $action_hook, $action_args ); if ( $next_scheduled !== $timestamp ) { @@ -44,7 +44,7 @@ class WCS_Action_Scheduler extends WCS_Scheduler { // Only reschedule if it's in the future if ( $timestamp > current_time( 'timestamp', true ) && ( 'payment_retry' == $date_type || 'active' == $subscription->get_status() ) ) { - wc_schedule_single_action( $timestamp, $action_hook, $action_args ); + as_schedule_single_action( $timestamp, $action_hook, $action_args ); } } } @@ -83,7 +83,7 @@ class WCS_Action_Scheduler extends WCS_Scheduler { } $action_args = $this->get_action_args( $date_type, $subscription ); - $next_scheduled = wc_next_scheduled_action( $action_hook, $action_args ); + $next_scheduled = as_next_scheduled_action( $action_hook, $action_args ); // Maybe clear the existing schedule for this hook if ( false !== $next_scheduled && $next_scheduled != $event_time ) { @@ -91,7 +91,7 @@ class WCS_Action_Scheduler extends WCS_Scheduler { } if ( 0 != $event_time && $event_time > current_time( 'timestamp', true ) && $next_scheduled != $event_time ) { - wc_schedule_single_action( $event_time, $action_hook, $action_args ); + as_schedule_single_action( $event_time, $action_hook, $action_args ); } } break; @@ -104,7 +104,7 @@ class WCS_Action_Scheduler extends WCS_Scheduler { $end_time = $subscription->get_time( 'end' ); // This will have been set to the correct date already $action_args = $this->get_action_args( 'end', $subscription ); - $next_scheduled = wc_next_scheduled_action( 'woocommerce_scheduled_subscription_end_of_prepaid_term', $action_args ); + $next_scheduled = as_next_scheduled_action( 'woocommerce_scheduled_subscription_end_of_prepaid_term', $action_args ); if ( false !== $next_scheduled && $next_scheduled != $end_time ) { $this->unschedule_actions( 'woocommerce_scheduled_subscription_end_of_prepaid_term', $action_args ); @@ -112,7 +112,7 @@ class WCS_Action_Scheduler extends WCS_Scheduler { // The end date was set in WC_Subscriptions::update_dates() to the appropriate value, so we can schedule our action for that time if ( $end_time > current_time( 'timestamp', true ) && $next_scheduled != $end_time ) { - wc_schedule_single_action( $end_time, 'woocommerce_scheduled_subscription_end_of_prepaid_term', $action_args ); + as_schedule_single_action( $end_time, 'woocommerce_scheduled_subscription_end_of_prepaid_term', $action_args ); } break; case 'on-hold' : @@ -189,9 +189,6 @@ class WCS_Action_Scheduler extends WCS_Scheduler { * @param array $action_args Array of name => value pairs stored against the scheduled action. */ protected function unschedule_actions( $action_hook, $action_args ) { - do { - wc_unschedule_action( $action_hook, $action_args ); - $next_scheduled = wc_next_scheduled_action( $action_hook, $action_args ); - } while ( false !== $next_scheduled ); + as_unschedule_all_actions( $action_hook, $action_args ); } } diff --git a/includes/class-wcs-api.php b/includes/class-wcs-api.php old mode 100755 new mode 100644 index 1e1ce13..5aab599 --- a/includes/class-wcs-api.php +++ b/includes/class-wcs-api.php @@ -31,10 +31,6 @@ class WCS_API { public static function includes( $wc_api_classes ) { if ( ! defined( 'WC_API_REQUEST_VERSION' ) || 3 == WC_API_REQUEST_VERSION ) { - - require_once( 'api/legacy/class-wc-api-subscriptions.php' ); - require_once( 'api/legacy/class-wc-api-subscriptions-customers.php' ); - array_push( $wc_api_classes, 'WC_API_Subscriptions' ); array_push( $wc_api_classes, 'WC_API_Subscriptions_Customers' ); } @@ -54,14 +50,6 @@ class WCS_API { return; } - if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) { - require_once( 'api/legacy/class-wc-rest-subscriptions-controller.php' ); - require_once( 'api/legacy/class-wc-rest-subscription-notes-controller.php' ); - } else { - require_once( 'api/class-wc-rest-subscriptions-controller.php' ); - require_once( 'api/class-wc-rest-subscription-notes-controller.php' ); - } - foreach ( array( 'WC_REST_Subscriptions_Controller', 'WC_REST_Subscription_Notes_Controller' ) as $api_class ) { $controller = new $api_class(); $controller->register_routes(); @@ -69,5 +57,3 @@ class WCS_API { } } - -WCS_API::init(); diff --git a/includes/class-wcs-auth.php b/includes/class-wcs-auth.php old mode 100755 new mode 100644 index 6ff2740..add1336 --- a/includes/class-wcs-auth.php +++ b/includes/class-wcs-auth.php @@ -49,5 +49,3 @@ class WCS_Auth { return $permissions; } } - -return new WCS_Auth(); diff --git a/includes/class-wcs-autoloader.php b/includes/class-wcs-autoloader.php new file mode 100644 index 0000000..3205373 --- /dev/null +++ b/includes/class-wcs-autoloader.php @@ -0,0 +1,285 @@ +base_path = untrailingslashit( $base_path ); + } + + /** + * Destructor. + */ + public function __destruct() { + $this->unregister(); + } + + /** + * Register the autoloader. + * + * @author Jeremy Pry + */ + public function register() { + spl_autoload_register( array( $this, 'autoload' ) ); + } + + /** + * Unregister the autoloader. + */ + public function unregister() { + spl_autoload_unregister( array( $this, 'autoload' ) ); + } + + /** + * Autoload a class. + * + * @author Jeremy Pry + * + * @param string $class The class name to autoload. + */ + public function autoload( $class ) { + $class = strtolower( $class ); + + if ( ! $this->should_autoload( $class ) ) { + return; + } + + $full_path = $this->base_path . $this->get_relative_class_path( $class ) . $this->get_file_name( $class ); + if ( is_readable( $full_path ) ) { + require_once( $full_path ); + } + } + + /** + * Determine whether we should autoload a given class. + * + * @author Jeremy Pry + * + * @param string $class The class name. + * + * @return bool + */ + protected function should_autoload( $class ) { + // We're not using namespaces, so if the class has namespace separators, skip. + if ( false !== strpos( $class, '\\' ) ) { + return false; + } + + // There are some legacy classes without WCS or Subscription in the name. + static $legacy = array( + 'wc_order_item_pending_switch' => 1, + 'wc_report_retention_rate' => 1, + 'wc_report_upcoming_recurring_revenue' => 1, + ); + if ( isset( $legacy[ $class ] ) ) { + return true; + } + + return false !== strpos( $class, 'wcs_' ) || + 0 === strpos( $class, 'wc_subscription' ) || + ( false !== strpos( $class, 'wc_' ) && false !== strpos( $class, 'subscription' ) ); + } + + /** + * Convert the class name into an appropriate file name. + * + * @author Jeremy Pry + * + * @param string $class The class name. + * + * @return string The file name. + */ + protected function get_file_name( $class ) { + $file_prefix = 'class-'; + + if ( $this->is_class_abstract( $class ) ) { + $file_prefix = 'abstract-'; + } elseif ( $this->is_class_interface( $class ) ) { + $file_prefix = 'interface-'; + } + + return $file_prefix . str_replace( '_', '-', $class ) . '.php'; + } + + /** + * Determine if the class is one of our abstract classes. + * + * @author Jeremy Pry + * + * @param string $class The class name. + * + * @return bool + */ + protected function is_class_abstract( $class ) { + static $abstracts = array( + 'wcs_background_updater' => true, + 'wcs_background_upgrader' => true, + 'wcs_cache_manager' => true, + 'wcs_debug_tool' => true, + 'wcs_debug_tool_cache_updater' => true, + 'wcs_dynamic_hook_deprecator' => true, + 'wcs_hook_deprecator' => true, + 'wcs_retry_store' => true, + 'wcs_scheduler' => true, + 'wcs_sv_api_base' => true, + 'wcs_customer_store' => true, + 'wcs_related_order_store' => true, + 'wcs_migrator' => true, + 'wcs_table_maker' => true, + ); + + return isset( $abstracts[ $class ] ); + } + + /** + * Determine if the class is one of our class interfaces. + * + * @param string $class The class name. + + * @return bool + */ + protected function is_class_interface( $class ) { + static $interfaces = array( + 'wcs_cache_updater' => true, + ); + + return isset( $interfaces[ $class ] ); + } + + /** + * Determine if the class is one of our data stores. + * + * @param string $class The class name. + + * @return bool + */ + protected function is_class_data_store( $class ) { + static $data_stores = array( + 'wcs_related_order_store_cached_cpt' => true, + 'wcs_related_order_store_cpt' => true, + 'wcs_customer_store_cached_cpt' => true, + 'wcs_customer_store_cpt' => true, + 'wcs_product_variable_data_store_cpt' => true, + 'wcs_subscription_data_store_cpt' => true, + ); + + return isset( $data_stores[ $class ] ); + } + + /** + * Get the relative path for the class location. + * + * This handles all of the special class locations and exceptions. + * + * @author Jeremy Pry + * + * @param string $class The class name. + * + * @return string The relative path (from the plugin root) to the class file. + */ + protected function get_relative_class_path( $class ) { + $path = '/includes'; + $is_admin = ( false !== strpos( $class, 'admin' ) ); + + if ( $this->is_class_abstract( $class ) ) { + if ( 'wcs_sv_api_base' === $class ) { + $path .= '/gateways/paypal/includes/abstracts'; + } else { + $path .= '/abstracts'; + } + } elseif ( $this->is_class_interface( $class ) ) { + $path .= '/interfaces'; + } elseif ( false !== strpos( $class, 'paypal' ) ) { + $path .= '/gateways/paypal'; + if ( 'wcs_paypal' === $class ) { + $path .= ''; + } elseif ( 'wcs_repair_suspended_paypal_subscriptions' === $class ) { + // Deliberately avoid concatenation for this class, using the base path. + $path = '/includes/upgrades'; + } elseif ( $is_admin ) { + $path .= '/includes/admin'; + } elseif ( 'wc_paypal_standard_subscriptions' === $class ) { + $path .= '/includes/deprecated'; + } else { + $path .= '/includes'; + } + } elseif ( 0 === strpos( $class, 'wcs_retry' ) && 'wcs_retry_manager' !== $class ) { + $path .= '/payment-retry'; + } elseif ( $is_admin && 'wcs_change_payment_method_admin' !== $class ) { + $path .= '/admin'; + } elseif ( false !== strpos( $class, 'meta_box' ) ) { + $path .= '/admin/meta-boxes'; + } elseif ( false !== strpos( $class, 'wc_report' ) ) { + $path .= '/admin/reports/deprecated'; + } elseif ( false !== strpos( $class, 'report' ) ) { + $path .= '/admin/reports'; + } elseif ( false !== strpos( $class, 'debug_tool' ) ) { + $path .= '/admin/debug-tools'; + } elseif ( false !== strpos( $class, 'rest' ) ) { + $path .= $this->legacy_api ? '/api/legacy' : '/api'; + } elseif ( false !== strpos( $class, 'api' ) && 'wcs_api' !== $class ) { + $path .= '/api/legacy'; + } elseif ( $this->is_class_data_store( $class ) ) { + $path .= '/data-stores'; + } elseif ( false !== strpos( $class, 'deprecat' ) ) { + $path .= '/deprecated'; + } elseif ( false !== strpos( $class, 'email' ) && 'wc_subscriptions_email' !== $class ) { + $path .= '/emails'; + } elseif ( false !== strpos( $class, 'gateway' ) && 'wc_subscriptions_change_payment_gateway' !== $class ) { + $path .= '/gateways'; + } elseif ( false !== strpos( $class, 'legacy' ) || 'wcs_array_property_post_meta_black_magic' === $class ) { + $path .= '/legacy'; + } elseif ( false !== strpos( $class, 'privacy' ) ) { + $path .= '/privacy'; + } elseif ( false !== strpos( $class, 'upgrade' ) || false !== strpos( $class, 'repair' ) ) { + $path .= '/upgrades'; + } elseif ( false !== strpos( $class, 'early' ) ) { + $path .= '/early-renewal'; + } + + return trailingslashit( $path ); + } + + /** + * Set whether the legacy API should be used. + * + * @author Jeremy Pry + * + * @param bool $use_legacy_api Whether to use the legacy API classes. + * + * @return $this + */ + public function use_legacy_api( $use_legacy_api ) { + $this->legacy_api = (bool) $use_legacy_api; + + return $this; + } +} diff --git a/includes/class-wcs-cached-data-manager.php b/includes/class-wcs-cached-data-manager.php old mode 100755 new mode 100644 index 98acf45..cdc8c20 --- a/includes/class-wcs-cached-data-manager.php +++ b/includes/class-wcs-cached-data-manager.php @@ -11,6 +11,9 @@ */ class WCS_Cached_Data_Manager extends WCS_Cache_Manager { + /** + * @var WC_Logger_Interface|null + */ public $logger = null; public function __construct() { diff --git a/includes/class-wcs-cart-initial-payment.php b/includes/class-wcs-cart-initial-payment.php old mode 100755 new mode 100644 index 6376f27..06f6bab --- a/includes/class-wcs-cart-initial-payment.php +++ b/includes/class-wcs-cart-initial-payment.php @@ -20,11 +20,12 @@ class WCS_Cart_Initial_Payment extends WCS_Cart_Renewal { * @since 2.0 */ public function __construct() { - $this->setup_hooks(); // When an order is paid for via checkout, ensure a new order isn't created due to mismatched cart hashes add_filter( 'woocommerce_create_order', array( &$this, 'update_cart_hash' ), 10, 1 ); + // Apply initial discounts when there is a pending initial order + add_action( 'woocommerce_setup_cart_for_subscription_initial_payment', array( $this, 'setup_discounts' ) ); } /** @@ -35,48 +36,51 @@ class WCS_Cart_Initial_Payment extends WCS_Cart_Renewal { public function maybe_setup_cart() { global $wp; - if ( isset( $_GET['pay_for_order'] ) && isset( $_GET['key'] ) && isset( $wp->query_vars['order-pay'] ) ) { + if ( ! isset( $_GET['pay_for_order'] ) || ! isset( $_GET['key'] ) || ! isset( $wp->query_vars['order-pay'] ) ) { + return; + } - // Pay for existing order - $order_key = $_GET['key']; - $order_id = ( isset( $wp->query_vars['order-pay'] ) ) ? $wp->query_vars['order-pay'] : absint( $_GET['order_id'] ); - $order = wc_get_order( $wp->query_vars['order-pay'] ); + // Pay for existing order + $order_key = $_GET['key']; + $order_id = absint( $wp->query_vars['order-pay'] ); + $order = wc_get_order( $order_id ); - if ( wcs_get_objects_property( $order, 'order_key' ) == $order_key && $order->has_status( array( 'pending', 'failed' ) ) && wcs_order_contains_subscription( $order, 'parent' ) && ! wcs_order_contains_subscription( $order, 'resubscribe' ) ) { + if ( wcs_get_objects_property( $order, 'order_key' ) !== $order_key || ! $order->has_status( array( 'pending', 'failed' ) ) || ! wcs_order_contains_subscription( $order, 'parent' ) || wcs_order_contains_subscription( $order, 'resubscribe' ) ) { + return; + } - if ( ! is_user_logged_in() ) { + if ( ! is_user_logged_in() ) { + // Allow the customer to login first and then redirect them back. + $redirect = add_query_arg( array( + 'wcs_redirect' => 'pay_for_order', + 'wcs_redirect_id' => $order_id, + ), get_permalink( wc_get_page_id( 'myaccount' ) ) ); + } elseif ( ! current_user_can( 'pay_for_order', $order_id ) ) { + wc_add_notice( __( 'That doesn\'t appear to be your order.', 'woocommerce-subscriptions' ), 'error' ); - $redirect = add_query_arg( array( - 'wcs_redirect' => 'pay_for_order', - 'wcs_redirect_id' => $order_id, - ), get_permalink( wc_get_page_id( 'myaccount' ) ) ); + $redirect = get_permalink( wc_get_page_id( 'myaccount' ) ); + } else { + $subscriptions = wcs_get_subscriptions_for_order( $order ); + do_action( 'wcs_before_parent_order_setup_cart', $subscriptions, $order ); - wp_safe_redirect( $redirect ); - exit; + // Add the existing order items to the cart + $this->setup_cart( $order, array( + 'order_id' => $order_id, + ) ); - } elseif ( ! current_user_can( 'pay_for_order', $order_id ) ) { + do_action( 'wcs_after_parent_order_setup_cart', $subscriptions, $order ); - wc_add_notice( __( 'That doesn\'t appear to be your order.', 'woocommerce-subscriptions' ), 'error' ); + // Store order's ID in the session so it can be re-used after payment + WC()->session->set( 'order_awaiting_payment', $order_id ); - wp_safe_redirect( get_permalink( wc_get_page_id( 'myaccount' ) ) ); - exit; + $this->set_cart_hash( $order_id ); - } else { + $redirect = wc_get_checkout_url(); + } - // Setup cart with all the original order's line items - $this->setup_cart( $order, array( - 'order_id' => $order_id, - ) ); - - WC()->session->set( 'order_awaiting_payment', $order_id ); - - // Set cart hash for orders paid in WC >= 2.6 - $this->set_cart_hash( $order_id ); - - wp_safe_redirect( wc_get_checkout_url() ); - exit; - } - } + if ( ! empty( $redirect ) ) { + wp_safe_redirect( $redirect ); + exit; } } @@ -124,4 +128,3 @@ class WCS_Cart_Initial_Payment extends WCS_Cart_Renewal { } } -new WCS_Cart_Initial_Payment(); diff --git a/includes/class-wcs-cart-renewal.php b/includes/class-wcs-cart-renewal.php old mode 100755 new mode 100644 index 4f252ae..6af40bb --- a/includes/class-wcs-cart-renewal.php +++ b/includes/class-wcs-cart-renewal.php @@ -54,6 +54,15 @@ class WCS_Cart_Renewal { // Remove subscription products with "one time shipping" from shipping packages. add_filter( 'woocommerce_cart_shipping_packages', array( $this, 'maybe_update_shipping_packages' ), 0, 1 ); + + add_action( 'wcs_before_renewal_setup_cart_subscriptions', array( &$this, 'clear_coupons' ), 10 ); + + // Handles renew of password-protected products. + add_action( 'wcs_before_renewal_setup_cart_subscriptions', 'wcs_allow_protected_products_to_renew' ); + add_action( 'wcs_after_renewal_setup_cart_subscriptions', 'wcs_disallow_protected_product_add_to_cart_validation' ); + + // Apply renewal discounts as pseudo coupons + add_action( 'woocommerce_setup_cart_for_subscription_renewal', array( $this, 'setup_discounts' ) ); } /** @@ -103,10 +112,7 @@ class WCS_Cart_Renewal { // Check if a user is requesting to create a renewal order for a subscription, needs to happen after $wp->query_vars are set 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, 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 ); add_action( 'woocommerce_remove_cart_item', array( &$this, 'maybe_remove_items' ), 10, 1 ); add_action( 'woocommerce_before_cart_item_quantity_zero', array( &$this, 'maybe_remove_items' ), 10, 1 ); @@ -334,100 +340,6 @@ class WCS_Cart_Renewal { do_action( 'woocommerce_setup_cart_for_' . $this->cart_item_key, $subscription, $cart_item_data ); } - /** - * Check if a renewal order subscription has any coupons applied and if so add pseudo renewal coupon equivalents to ensure the discount is still applied - * - * @param WC_Subscription $subscription subscription - * @param WC_Order $order - * - * @since 2.0.10 - */ - 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( $order ) || wcs_order_contains_renewal( $order ) ) { - - $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 = $order->get_items( 'coupon' ); - - foreach ( $coupon_items as $coupon_item ) { - - $coupon = new WC_Coupon( $coupon_item['name'] ); - $coupon_type = wcs_get_coupon_property( $coupon, 'discount_type' ); - $coupon_code = ''; - - // 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 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 - if ( 'recurring_percent' == $coupon_type ) { - wcs_set_coupon_property( $coupon, 'discount_type', 'renewal_percent' ); - } elseif ( 'recurring_fee' == $coupon_type ) { - wcs_set_coupon_property( $coupon, 'discount_type', 'renewal_fee' ); - } - - // Adjust coupon code to reflect that it is being applied to a 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' ); - - // Adjust coupon code to reflect that it is being applied to a renewal - $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 - if ( ! empty( $coupon_code ) ) { - - // Set renewal order products as the product ids on the coupon - wcs_set_coupon_property( $coupon, 'product_ids', $this->get_products( $order ) ); - - // Store the coupon info for later - $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 ) ) { - WC()->cart->add_discount( $coupon_code ); - } - } - } - // 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( $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', $order_discount ); - - // Set renewal order products as the product ids on the coupon - wcs_set_coupon_property( $coupon, 'product_ids', $this->get_products( $order ) ); - - // Store the coupon info for later - $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' ) ) { - WC()->cart->add_discount( 'discount_renewal' ); - } - } - } - } - /** * Does some housekeeping. Fires after the items have been passed through the get items from session filter. Because * that filter is not good for removing cart items, we need to work around that by doing it later, in the cart @@ -1335,6 +1247,132 @@ class WCS_Cart_Renewal { return $packages; } + /** + * Check if the order has any discounts applied and if so reapply them to the cart + * or add pseudo coupon equivalents if the coupons no longer exist. + * + * @param WC_Order $order The order to copy coupons and discounts from. + * @since 2.4.3 + */ + public function setup_discounts( $order ) { + $order_discount = $order->get_total_discount(); + $coupon_items = $order->get_items( 'coupon' ); + + if ( empty( $order_discount ) && empty( $coupon_items ) ) { + return; + } + + $total_coupon_discount = floatval( array_sum( wc_list_pluck( $coupon_items, 'get_discount' ) ) ); + $coupons = array(); + + // If the order total discount is different from the discount applied from coupons we have a manually applied discount. + $order_has_manual_discount = $order_discount !== $total_coupon_discount; + + // Get all coupon line items as coupon objects. + if ( ! empty( $coupon_items ) ) { + $coupons = $this->get_line_item_coupons( $coupon_items ); + } + + if ( $order_has_manual_discount ) { + // Remove any coupon line items which don't grant free shipping. + foreach ( $coupons as $index => $coupon ) { + if ( ! $coupon->get_free_shipping() ) { + unset( $coupons[ $index ] ); + } + + // We're going to apply a coupon for the full order discount so make sure free shipping coupons don't apply any discount. + $coupon->set_amount( 0 ); + } + + $coupons[] = $this->get_pseudo_coupon( $order_discount ); + } + + foreach ( $coupons as $coupon ) { + $this->apply_order_coupon( $order, $coupon ); + } + } + + /** + * Create coupon objects from coupon line items. + * + * @param array $coupon_line_items The coupon line items to apply to the cart. + * @return array $coupons + */ + protected function get_line_item_coupons( $coupon_line_items ) { + $coupons = array(); + + foreach ( $coupon_line_items as $coupon_item ) { + $coupon = new WC_Coupon( $coupon_item['name'] ); + + // If the coupon no longer exists, get a pseudo coupon for the discounting amount. + if ( ! $coupon->get_id() > 0 ) { + // We shouldn't apply coupons which no longer exists it to initial payment carts. + if ( 'subscription_initial_payment' === $this->cart_item_key ) { + continue; + } + + $coupon = $this->get_pseudo_coupon( $coupon_item->get_discount() ); + $coupon->set_code( $coupon_item['code'] ); + } elseif ( 'subscription_renewal' === $this->cart_item_key ) { + $coupon_type = $coupon->get_discount_type(); + + // Change recurring coupons into renewal coupons so we can handle validation while paying for a renewal order manually. + if ( in_array( $coupon_type, array( 'recurring_percent', 'recurring_fee' ) ) ) { + $coupon->set_discount_type( str_replace( 'recurring', 'renewal', $coupon_type ) ); + } + } + + $coupons[] = $coupon; + } + + return $coupons; + } + + /** + * Apply a pseudo coupon to the cart for a specific discount amount. + * + * @param float $discount The discount amount. + * @return WC_Coupon + */ + protected function get_pseudo_coupon( $discount ) { + $cart_types = array( + 'subscription_initial_payment' => 'initial', + 'subscription_renewal' => 'renewal', + ); + + $cart_type = $cart_types[ $this->cart_item_key ]; + + // Generate a unique coupon code from the cart type. + $coupon = new WC_Coupon( "discount_{$cart_type}" ); + + // Apply our cart style pseudo coupon type and the set the amount. + $coupon->set_discount_type( "{$cart_type}_cart" ); + $coupon->set_amount( $discount ); + + return $coupon; + } + + /** + * Apply an order coupon to the cart. + * + * @param WC_Order $order The order the discount should apply to. + * @param WC_Coupon $coupon The coupon to add to the cart. + */ + protected function apply_order_coupon( $order, $coupon ) { + $coupon_code = $coupon->get_code(); + + // Set order products as the product ids on the coupon + $coupon->set_product_ids( $this->get_products( $order ) ); + + // Store the coupon info for later + $this->store_coupon( $order->get_id(), $coupon ); + + // Add the coupon to the cart + if ( WC()->cart && ! WC()->cart->has_discount( $coupon_code ) ) { + WC()->cart->add_discount( $coupon_code ); + } + } + /* Deprecated */ /** @@ -1413,5 +1451,101 @@ class WCS_Cart_Renewal { $this->update_cart_hash( $order ); return $order; } + + /** + * Check if a renewal order subscription has any coupons applied and if so add pseudo renewal coupon equivalents to ensure the discount is still applied + * + * @param WC_Subscription $subscription subscription + * @param WC_Order $order + * + * @since 2.0.10 + * @deprecated 2.4.3 + */ + public function maybe_setup_discounts( $subscription, $order = null ) { + wcs_deprecated_function( __METHOD__, '2.4.3' ); + + 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( $order ) || wcs_order_contains_renewal( $order ) ) { + + $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 = $order->get_items( 'coupon' ); + + foreach ( $coupon_items as $coupon_item ) { + + $coupon = new WC_Coupon( $coupon_item['name'] ); + $coupon_type = wcs_get_coupon_property( $coupon, 'discount_type' ); + $coupon_code = ''; + + // 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 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 + if ( 'recurring_percent' == $coupon_type ) { + wcs_set_coupon_property( $coupon, 'discount_type', 'renewal_percent' ); + } elseif ( 'recurring_fee' == $coupon_type ) { + wcs_set_coupon_property( $coupon, 'discount_type', 'renewal_fee' ); + } + + // Adjust coupon code to reflect that it is being applied to a 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' ); + + // Adjust coupon code to reflect that it is being applied to a renewal + $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 + if ( ! empty( $coupon_code ) ) { + + // Set renewal order products as the product ids on the coupon + wcs_set_coupon_property( $coupon, 'product_ids', $this->get_products( $order ) ); + + // Store the coupon info for later + $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 ) ) { + WC()->cart->add_discount( $coupon_code ); + } + } + } + // 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( $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', $order_discount ); + + // Set renewal order products as the product ids on the coupon + wcs_set_coupon_property( $coupon, 'product_ids', $this->get_products( $order ) ); + + // Store the coupon info for later + $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' ) ) { + WC()->cart->add_discount( 'discount_renewal' ); + } + } + } + } } -new WCS_Cart_Renewal(); diff --git a/includes/class-wcs-cart-resubscribe.php b/includes/class-wcs-cart-resubscribe.php old mode 100755 new mode 100644 index 03eb393..00cc053 --- a/includes/class-wcs-cart-resubscribe.php +++ b/includes/class-wcs-cart-resubscribe.php @@ -323,4 +323,3 @@ class WCS_Cart_Resubscribe extends WCS_Cart_Renewal { } } } -new WCS_Cart_Resubscribe(); diff --git a/includes/class-wcs-cart-switch.php b/includes/class-wcs-cart-switch.php old mode 100755 new mode 100644 index 66bef80..78e257a --- a/includes/class-wcs-cart-switch.php +++ b/includes/class-wcs-cart-switch.php @@ -104,7 +104,7 @@ class WCS_Cart_Switch extends WCS_Cart_Renewal { } $order_item = wcs_get_order_item( $item_id, $order ); - $product = WC_Subscriptions::get_product( wcs_get_canonical_product_id( $order_item ) ); + $product = wc_get_product( wcs_get_canonical_product_id( $order_item ) ); $product_id = $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(); $order_product_data = array( @@ -160,4 +160,3 @@ class WCS_Cart_Switch extends WCS_Cart_Renewal { } } } -new WCS_Cart_Switch(); diff --git a/includes/class-wcs-change-payment-method-admin.php b/includes/class-wcs-change-payment-method-admin.php old mode 100755 new mode 100644 diff --git a/includes/class-wcs-download-handler.php b/includes/class-wcs-download-handler.php old mode 100755 new mode 100644 index 1a51fae..8cd078b --- a/includes/class-wcs-download-handler.php +++ b/includes/class-wcs-download-handler.php @@ -32,6 +32,8 @@ class WCS_Download_Handler { add_action( 'woocommerce_process_shop_order_meta', __CLASS__ . '::repair_permission_data', 60, 1 ); + add_action( 'woocommerce_admin_created_subscription', array( __CLASS__, 'grant_download_permissions' ) ); + add_action( 'deleted_post', __CLASS__ . '::delete_subscription_permissions' ); add_action( 'woocommerce_process_product_file_download_paths', __CLASS__ . '::grant_new_file_product_permissions', 11, 3 ); @@ -184,6 +186,17 @@ class WCS_Download_Handler { ", $post_id, '0000-00-00 00:00:00' ) ); } + /** + * Gives customers access to downloadable products in a subscription. + * Hooked into 'woocommerce_admin_created_subscription' to grant permissions to admin created subscriptions. + * + * @param WC_Subscription $subscription + * @since 2.4.2 + */ + public static function grant_download_permissions( $subscription ) { + wc_downloadable_product_permissions( $subscription->get_id() ); + } + /** * Remove download permissions attached to a subscription when it is permenantly deleted. * @@ -247,4 +260,3 @@ class WCS_Download_Handler { } } } -WCS_Download_Handler::init(); diff --git a/includes/class-wcs-failed-scheduled-action-manager.php b/includes/class-wcs-failed-scheduled-action-manager.php old mode 100755 new mode 100644 index ea04239..d6748ef --- a/includes/class-wcs-failed-scheduled-action-manager.php +++ b/includes/class-wcs-failed-scheduled-action-manager.php @@ -37,10 +37,11 @@ class WCS_Failed_Scheduled_Action_Manager { /** * Constructor. * - * @param WC_Logger $logger The WC Logger instance. + * @param WC_Logger_Interface $logger The WC Logger instance. + * * @since 2.2.19 */ - public function __construct( WC_Logger $logger ) { + public function __construct( WC_Logger_Interface $logger ) { $this->logger = $logger; } @@ -121,24 +122,26 @@ class WCS_Failed_Scheduled_Action_Manager { $affected_subscription_events .= $separator . $action['type'] . ' for ' . $subject; $separator = "\n"; - }?> -
', - '', - '
', - '', - '
', - '' . wp_kses( $affected_subscription_events, array( 'a' => array( 'href' => array() ) ) ) . '
',
- 'failed-scheduled-actions
',
- '',
- ''
- );?>
-
- ', + '', + '»' + ), + ); + } - if ( $valid_for_use && 'yes' == WCS_PayPal::get_option( 'enabled' ) && ! has_action( 'admin_notices', 'WC_Subscriptions_Admin::admin_installed_notice' ) && current_user_can( 'manage_options' ) ) { + if ( isset( $_GET['wcs_paypal'] ) && 'rt_enabled' === $_GET['wcs_paypal'] ) { + $notices[] = array( + 'type' => 'confirmation', + // translators: placeholders are opening and closing strong tags. + 'text' => sprintf( esc_html__( '%1$sPayPal Reference Transactions are enabled on your account%2$s. All subscription management features are now enabled. Happy selling!', 'woocommerce-subscriptions' ), + '', + '' + ), + ); + } - if ( ! WCS_PayPal::are_credentials_set() ) { + if ( false !== get_option( 'wcs_paypal_credentials_error' ) ) { + $notices[] = array( + 'type' => 'error', + // translators: placeholders are link opening and closing tags. 1$-2$: to gateway settings, 3$-4$: support docs on woocommerce.com + 'text' => sprintf( esc_html__( 'There is a problem with PayPal. Your API credentials may be incorrect. Please update your %1$sAPI credentials%2$s. %3$sLearn more%4$s.', 'woocommerce-subscriptions' ), + '', + '', + '', + '' + ), + ); + } - $notices[] = array( - 'type' => 'warning', - // translators: placeholders are opening and closing link tags. 1$-2$: to docs on woocommerce, 3$-4$ to gateway settings on the site - 'text' => sprintf( esc_html__( 'PayPal is inactive for subscription transactions. Please %1$sset up the PayPal IPN%2$s and %3$senter your API credentials%4$s to enable PayPal for Subscriptions.', 'woocommerce-subscriptions' ), - '', - '', - '', - '' - ), - ); + if ( 'yes' == get_option( 'wcs_paypal_invalid_profile_id' ) ) { + $notices[] = array( + 'type' => 'error', + // translators: placeholders are opening and closing link tags. 1$-2$: docs on woocommerce, 3$-4$: dismiss link + 'text' => sprintf( esc_html__( 'There is a problem with PayPal. Your PayPal account is issuing out-of-date subscription IDs. %1$sLearn more%2$s. %3$sDismiss%4$s.', 'woocommerce-subscriptions' ), + '', + '', + '', + '' + ), + ); + } - } elseif ( 'woocommerce_page_wc-settings' === get_current_screen()->base && isset( $_GET['tab'] ) && in_array( $_GET['tab'], array( 'subscriptions', 'checkout' ) ) && ! WCS_PayPal::are_reference_transactions_enabled() ) { + $last_ipn_error = get_option( 'wcs_fatal_error_handling_ipn', '' ); + $failed_ipn_log_handle = 'wcs-ipn-failures'; - $notices[] = array( - 'type' => 'warning', - // translators: placeholders are opening and closing strong and link tags. 1$-2$: strong tags, 3$-8$ link to docs on woocommerce - 'text' => sprintf( esc_html__( '%1$sPayPal Reference Transactions are not enabled on your account%2$s, some subscription management features are not enabled. Please contact PayPal and request they %3$senable PayPal Reference Transactions%4$s on your account. %5$sCheck PayPal Account%6$s %3$sLearn more %7$s', 'woocommerce-subscriptions' ), - '', - '', - '', - '', - '
', - '', - '»' - ), - ); + if ( ! empty( $last_ipn_error ) && ( false == get_option( 'wcs_fatal_error_handling_ipn_ignored', false ) || isset( $_GET['wcs_reveal_your_ipn_secrets'] ) ) ) { + $notice = new WCS_Admin_Notice( 'error' ); + $notice->set_content_template( 'html-ipn-failure-notice.php', dirname( __FILE__ ) . '/../templates/', array( + 'failed_ipn_log_handle' => $failed_ipn_log_handle, + 'last_ipn_error' => $last_ipn_error, + 'log_file_url' => admin_url( sprintf( 'admin.php?page=wc-status&tab=logs&log_file=%s-%s-log', $failed_ipn_log_handle, sanitize_file_name( wp_hash( $failed_ipn_log_handle ) ) ) ), + ) ); - } + $notice->set_actions( array( + array( + 'name' => __( 'Ignore this error (not recommended)', 'woocommerce-subscriptions' ), + 'url' => wp_nonce_url( add_query_arg( 'wcs_ipn_error_notice', 'ignore' ), 'wcs_ipn_error_notice', '_wcsnonce' ), + 'class' => 'button', + ), + array( + 'name' => __( 'Open a ticket', 'woocommerce-subscriptions' ), + 'url' => 'https://woocommerce.com/my-account/marketplace-ticket-form/', + 'class' => 'button button-primary', + ), + ) ); - if ( isset( $_GET['wcs_paypal'] ) && 'rt_enabled' === $_GET['wcs_paypal'] ) { - $notices[] = array( - 'type' => 'confirmation', - // translators: placeholders are opening and closing strong tags. - 'text' => sprintf( esc_html__( '%1$sPayPal Reference Transactions are enabled on your account%2$s. All subscription management features are now enabled. Happy selling!', 'woocommerce-subscriptions' ), - '', - '' - ), - ); - } - - if ( false !== get_option( 'wcs_paypal_credentials_error' ) ) { - $notices[] = array( - 'type' => 'error', - // translators: placeholders are link opening and closing tags. 1$-2$: to gateway settings, 3$-4$: support docs on woocommerce.com - 'text' => sprintf( esc_html__( 'There is a problem with PayPal. Your API credentials may be incorrect. Please update your %1$sAPI credentials%2$s. %3$sLearn more%4$s.', 'woocommerce-subscriptions' ), - '', - '', - '', - '' - ), - ); - } - - if ( 'yes' == get_option( 'wcs_paypal_invalid_profile_id' ) ) { - $notices[] = array( - 'type' => 'error', - // translators: placeholders are opening and closing link tags. 1$-2$: docs on woocommerce, 3$-4$: dismiss link - 'text' => sprintf( esc_html__( 'There is a problem with PayPal. Your PayPal account is issuing out-of-date subscription IDs. %1$sLearn more%2$s. %3$sDismiss%4$s.', 'woocommerce-subscriptions' ), - '', - '', - '', - '' - ), - ); - } - - $last_ipn_error = get_option( 'wcs_fatal_error_handling_ipn', '' ); - $failed_ipn_log_handle = 'wcs-ipn-failures'; - - if ( ! empty( $last_ipn_error ) && ( false == get_option( 'wcs_fatal_error_handling_ipn_ignored', false ) || isset( $_GET['wcs_reveal_your_ipn_secrets'] ) ) ) { - $notices[] = array( - 'type' => 'error', - 'text' => sprintf( esc_html__( '%sA fatal error has occurred when processing a recent subscription payment with PayPal. Please %sopen a new ticket at WooCommerce Support%s immediately to get this resolved.%sIn order to get the quickest possible response please attach a %sTemporary Admin Login%s and a copy of your PHP error logs to your support ticket.%sLast recorded error: %sTo see the full error, view the %s log file from the %sWooCommerce logs screen.%s', 'woocommerce-subscriptions' ), - '
',
- '',
- '',
- '
',
- '',
- '',
- '
' . esc_html( $last_ipn_error ) . '
',
- '' . $failed_ipn_log_handle . '
',
- '',
- '
', + '', + '' + );?> +
+', + '' + );?> +
+ +
+
+' . esc_html( $failed_ipn_log_handle ) . '', + '', + '' + );?> +
+ + diff --git a/includes/gateways/paypal/includes/wcs-paypal-functions.php b/includes/gateways/paypal/includes/wcs-paypal-functions.php old mode 100755 new mode 100644 diff --git a/includes/interfaces/interface-wcs-cache-updater.php b/includes/interfaces/interface-wcs-cache-updater.php old mode 100755 new mode 100644 diff --git a/includes/legacy/class-wc-product-subscription-legacy.php b/includes/legacy/class-wc-product-subscription-legacy.php old mode 100755 new mode 100644 diff --git a/includes/legacy/class-wc-product-subscription-variation-legacy.php b/includes/legacy/class-wc-product-subscription-variation-legacy.php old mode 100755 new mode 100644 diff --git a/includes/legacy/class-wc-product-variable-subscription-legacy.php b/includes/legacy/class-wc-product-variable-subscription-legacy.php old mode 100755 new mode 100644 diff --git a/includes/legacy/class-wc-subscription-legacy.php b/includes/legacy/class-wc-subscription-legacy.php old mode 100755 new mode 100644 index 5392c61..9a4e4f7 --- a/includes/legacy/class-wc-subscription-legacy.php +++ b/includes/legacy/class-wc-subscription-legacy.php @@ -425,23 +425,26 @@ class WC_Subscription_Legacy extends WC_Subscription { $sign_up_fee = $original_order_item['line_total'] / $original_order_item['qty']; } elseif ( isset( $original_order_item['item_meta']['_synced_sign_up_fee'] ) ) { $sign_up_fee = $original_order_item['item_meta']['_synced_sign_up_fee'] / $original_order_item['qty']; + + // The synced sign up fee meta contains the raw product sign up fee, if the subscription totals are inclusive of tax, we need to adjust the synced sign up fee to match tax inclusivity. + if ( $this->get_prices_include_tax() ) { + $line_item_total = $original_order_item['line_total'] + $original_order_item['line_tax']; + $signup_fee_portion = $sign_up_fee / $line_item_total; + $sign_up_fee = $original_order_item['line_total'] * $signup_fee_portion; + } } else { // Sign-up fee is any amount on top of recurring amount $sign_up_fee = max( $original_order_item['line_total'] / $original_order_item['qty'] - $line_item['line_total'] / $line_item['qty'], 0 ); } - if ( ! empty( $original_order_item ) && ! empty( $sign_up_fee ) ) { + // If prices don't inc tax, ensure that the sign up fee amount includes the tax. + if ( 'inclusive_of_tax' === $tax_inclusive_or_exclusive && ! empty( $original_order_item ) && ! empty( $sign_up_fee ) ) { $sign_up_fee_proportion = $sign_up_fee / ( $original_order_item['line_total'] / $original_order_item['qty'] ); - $sign_up_fee_tax = wc_round_tax_total( $original_order_item['line_tax'] * $sign_up_fee_proportion ); + $sign_up_fee_tax = $original_order_item['line_tax'] * $sign_up_fee_proportion; - // If prices don't inc tax, ensure that the sign up fee amount includes the tax. - if ( 'inclusive_of_tax' === $tax_inclusive_or_exclusive && ! $this->get_prices_include_tax() ) { - $sign_up_fee += $sign_up_fee_tax; - // If prices inc tax and the request is for prices exclusive of tax, remove the taxes. - } elseif ( 'inclusive_of_tax' !== $tax_inclusive_or_exclusive && $this->get_prices_include_tax() ) { - $sign_up_fee -= $sign_up_fee_tax; - } + $sign_up_fee += $sign_up_fee_tax; + $sign_up_fee = wc_format_decimal( $sign_up_fee, wc_get_price_decimals() ); } } diff --git a/includes/legacy/class-wcs-array-property-post-meta-black-magic.php b/includes/legacy/class-wcs-array-property-post-meta-black-magic.php old mode 100755 new mode 100644 diff --git a/includes/legacy/class-wcs-product-legacy.php b/includes/legacy/class-wcs-product-legacy.php old mode 100755 new mode 100644 index 80ff38f..d762201 --- a/includes/legacy/class-wcs-product-legacy.php +++ b/includes/legacy/class-wcs-product-legacy.php @@ -39,4 +39,3 @@ class WCS_Product_Legacy { } } -WCS_Product_Legacy::init(); diff --git a/includes/libraries/action-scheduler/.github/release-drafter.yml b/includes/libraries/action-scheduler/.github/release-drafter.yml new file mode 100644 index 0000000..702a690 --- /dev/null +++ b/includes/libraries/action-scheduler/.github/release-drafter.yml @@ -0,0 +1,15 @@ +template: | + ## next release – date + + + + $CHANGES + + **Added** + **Changed** + **Deprecated** + **Removed** + **Fixed** + **Security** + +change-template: '* $TITLE (PR #$NUMBER)' diff --git a/includes/libraries/action-scheduler/action-scheduler.php b/includes/libraries/action-scheduler/action-scheduler.php old mode 100755 new mode 100644 index 96595cd..28b37f1 --- a/includes/libraries/action-scheduler/action-scheduler.php +++ b/includes/libraries/action-scheduler/action-scheduler.php @@ -5,7 +5,7 @@ * Description: A robust scheduling library for use in WordPress plugins. * Author: Prospress * Author URI: http://prospress.com/ - * Version: 2.0.0 + * Version: 2.1.1 * License: GPLv3 * * Copyright 2018 Prospress, Inc. (email : freedoms@prospress.com) @@ -25,21 +25,21 @@ * */ -if ( ! function_exists( 'action_scheduler_register_2_dot_0_dot_0' ) ) { +if ( ! function_exists( 'action_scheduler_register_2_dot_1_dot_1' ) ) { if ( ! class_exists( 'ActionScheduler_Versions' ) ) { require_once( 'classes/ActionScheduler_Versions.php' ); add_action( 'plugins_loaded', array( 'ActionScheduler_Versions', 'initialize_latest_version' ), 1, 0 ); } - add_action( 'plugins_loaded', 'action_scheduler_register_2_dot_0_dot_0', 0, 0 ); + add_action( 'plugins_loaded', 'action_scheduler_register_2_dot_1_dot_1', 0, 0 ); - function action_scheduler_register_2_dot_0_dot_0() { + function action_scheduler_register_2_dot_1_dot_1() { $versions = ActionScheduler_Versions::instance(); - $versions->register( '2.0.0', 'action_scheduler_initialize_2_dot_0_dot_0' ); + $versions->register( '2.1.1', 'action_scheduler_initialize_2_dot_1_dot_1' ); } - function action_scheduler_initialize_2_dot_0_dot_0() { + function action_scheduler_initialize_2_dot_1_dot_1() { require_once( 'classes/ActionScheduler.php' ); ActionScheduler::init( __FILE__ ); } diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler.php b/includes/libraries/action-scheduler/classes/ActionScheduler.php old mode 100755 new mode 100644 index 5e1ad36..51af2b5 --- a/includes/libraries/action-scheduler/classes/ActionScheduler.php +++ b/includes/libraries/action-scheduler/classes/ActionScheduler.php @@ -59,7 +59,9 @@ abstract class ActionScheduler { public static function autoload( $class ) { $d = DIRECTORY_SEPARATOR; - if ( strpos( $class, 'ActionScheduler' ) === 0 ) { + if ( 'Deprecated' === substr( $class, -10 ) ) { + $dir = self::plugin_path('deprecated'.$d); + } elseif ( strpos( $class, 'ActionScheduler' ) === 0 ) { $dir = self::plugin_path('classes'.$d); } elseif ( strpos( $class, 'CronExpression' ) === 0 ) { $dir = self::plugin_path('lib'.$d.'cron-expression'.$d); @@ -67,8 +69,8 @@ abstract class ActionScheduler { return; } - if ( file_exists( $dir.$class.'.php' ) ) { - include( $dir.$class.'.php' ); + if ( file_exists( "{$dir}{$class}.php" ) ) { + include( "{$dir}{$class}.php" ); return; } } @@ -97,6 +99,10 @@ abstract class ActionScheduler { require_once( self::plugin_path('functions.php') ); + if ( apply_filters( 'action_scheduler_load_deprecated_functions', true ) ) { + require_once( self::plugin_path('deprecated/functions.php') ); + } + if ( defined( 'WP_CLI' ) && WP_CLI ) { WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Scheduler_command' ); } diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_Abstract_ListTable.php b/includes/libraries/action-scheduler/classes/ActionScheduler_Abstract_ListTable.php old mode 100755 new mode 100644 index de47067..6af17b9 --- a/includes/libraries/action-scheduler/classes/ActionScheduler_Abstract_ListTable.php +++ b/includes/libraries/action-scheduler/classes/ActionScheduler_Abstract_ListTable.php @@ -275,7 +275,7 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table { $valid_sortable_columns = array_values( $this->sort_by ); if ( ! empty( $_GET['orderby'] ) && in_array( $_GET['orderby'], $valid_sortable_columns ) ) { - $orderby = wc_clean( $_GET['orderby'] ); + $orderby = sanitize_text_field( $_GET['orderby'] ); } else { $orderby = $valid_sortable_columns[0]; } diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_Abstract_QueueRunner.php b/includes/libraries/action-scheduler/classes/ActionScheduler_Abstract_QueueRunner.php old mode 100755 new mode 100644 index 21f9ee0..5259797 --- a/includes/libraries/action-scheduler/classes/ActionScheduler_Abstract_QueueRunner.php +++ b/includes/libraries/action-scheduler/classes/ActionScheduler_Abstract_QueueRunner.php @@ -3,7 +3,7 @@ /** * Abstract class with common Queue Cleaner functionality. */ -abstract class ActionScheduler_Abstract_QueueRunner { +abstract class ActionScheduler_Abstract_QueueRunner extends ActionScheduler_Abstract_QueueRunner_Deprecated { /** @var ActionScheduler_QueueCleaner */ protected $cleaner; @@ -14,6 +14,16 @@ abstract class ActionScheduler_Abstract_QueueRunner { /** @var ActionScheduler_Store */ protected $store; + /** + * The created time. + * + * Represents when the queue runner was constructed and used when calculating how long a PHP request has been running. + * For this reason it should be as close as possible to the PHP request start time. + * + * @var int + */ + private $created_time; + /** * ActionScheduler_Abstract_QueueRunner constructor. * @@ -22,6 +32,9 @@ abstract class ActionScheduler_Abstract_QueueRunner { * @param ActionScheduler_QueueCleaner $cleaner */ public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null ) { + + $this->created_time = microtime( true ); + $this->store = $store ? $store : ActionScheduler_Store::instance(); $this->monitor = $monitor ? $monitor : new ActionScheduler_FatalErrorMonitor( $this->store ); $this->cleaner = $cleaner ? $cleaner : new ActionScheduler_QueueCleaner( $this->store ); @@ -35,6 +48,12 @@ abstract class ActionScheduler_Abstract_QueueRunner { public function process_action( $action_id ) { try { do_action( 'action_scheduler_before_execute', $action_id ); + + if ( ActionScheduler_Store::STATUS_PENDING !== $this->store->get_status( $action_id ) ) { + do_action( 'action_scheduler_execution_ignored', $action_id ); + return; + } + $action = $this->store->fetch_action( $action_id ); $this->store->log_execution( $action_id ); $action->execute(); @@ -79,6 +98,114 @@ abstract class ActionScheduler_Abstract_QueueRunner { return apply_filters( 'action_scheduler_queue_runner_concurrent_batches', 5 ); } + /** + * Get the maximum number of seconds a batch can run for. + * + * @return int The number of seconds. + */ + protected function get_time_limit() { + + $time_limit = 30; + + // Apply deprecated filter from deprecated get_maximum_execution_time() method + if ( has_filter( 'action_scheduler_maximum_execution_time' ) ) { + _deprecated_function( 'action_scheduler_maximum_execution_time', '2.1.1', 'action_scheduler_queue_runner_time_limit' ); + $time_limit = apply_filters( 'action_scheduler_maximum_execution_time', $time_limit ); + } + + return absint( apply_filters( 'action_scheduler_queue_runner_time_limit', $time_limit ) ); + } + + /** + * Get the number of seconds the process has been running. + * + * @return int The number of seconds. + */ + protected function get_execution_time() { + $execution_time = microtime( true ) - $this->created_time; + + // Get the CPU time if the hosting environment uses it rather than wall-clock time to calculate a process's execution time. + if ( function_exists( 'getrusage' ) && apply_filters( 'action_scheduler_use_cpu_execution_time', defined( 'PANTHEON_ENVIRONMENT' ) ) ) { + $resource_usages = getrusage(); + + if ( isset( $resource_usages['ru_stime.tv_usec'], $resource_usages['ru_stime.tv_usec'] ) ) { + $execution_time = $resource_usages['ru_stime.tv_sec'] + ( $resource_usages['ru_stime.tv_usec'] / 1000000 ); + } + } + + return $execution_time; + } + + /** + * Check if the host's max execution time is (likely) to be exceeded if processing more actions. + * + * @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action + * @return bool + */ + protected function time_likely_to_be_exceeded( $processed_actions ) { + + $execution_time = $this->get_execution_time(); + $max_execution_time = $this->get_time_limit(); + $time_per_action = $execution_time / $processed_actions; + $estimated_time = $execution_time + ( $time_per_action * 3 ); + $likely_to_be_exceeded = $estimated_time > $max_execution_time; + + return apply_filters( 'action_scheduler_maximum_execution_time_likely_to_be_exceeded', $likely_to_be_exceeded, $this, $processed_actions, $execution_time, $max_execution_time ); + } + + /** + * Get memory limit + * + * Based on WP_Background_Process::get_memory_limit() + * + * @return int + */ + protected function get_memory_limit() { + if ( function_exists( 'ini_get' ) ) { + $memory_limit = ini_get( 'memory_limit' ); + } else { + $memory_limit = '128M'; // Sensible default, and minimum required by WooCommerce + } + + if ( ! $memory_limit || -1 === $memory_limit || '-1' === $memory_limit ) { + // Unlimited, set to 32GB. + $memory_limit = '32G'; + } + + return ActionScheduler_Compatibility::convert_hr_to_bytes( $memory_limit ); + } + + /** + * Memory exceeded + * + * Ensures the batch process never exceeds 90% of the maximum WordPress memory. + * + * Based on WP_Background_Process::memory_exceeded() + * + * @return bool + */ + protected function memory_exceeded() { + + $memory_limit = $this->get_memory_limit() * 0.90; + $current_memory = memory_get_usage( true ); + $memory_exceeded = $current_memory >= $memory_limit; + + return apply_filters( 'action_scheduler_memory_exceeded', $memory_exceeded, $this ); + } + + /** + * See if the batch limits have been exceeded, which is when memory usage is almost at + * the maximum limit, or the time to process more actions will exceed the max time limit. + * + * Based on WC_Background_Process::batch_limits_exceeded() + * + * @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action + * @return bool + */ + protected function batch_limits_exceeded( $processed_actions ) { + return $this->memory_exceeded() || $this->time_likely_to_be_exceeded( $processed_actions ); + } + /** * Process actions in the queue. * diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_Action.php b/includes/libraries/action-scheduler/classes/ActionScheduler_Action.php old mode 100755 new mode 100644 diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_ActionClaim.php b/includes/libraries/action-scheduler/classes/ActionScheduler_ActionClaim.php old mode 100755 new mode 100644 diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_ActionFactory.php b/includes/libraries/action-scheduler/classes/ActionScheduler_ActionFactory.php old mode 100755 new mode 100644 diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_AdminView.php b/includes/libraries/action-scheduler/classes/ActionScheduler_AdminView.php old mode 100755 new mode 100644 diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_CanceledAction.php b/includes/libraries/action-scheduler/classes/ActionScheduler_CanceledAction.php old mode 100755 new mode 100644 diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_Compatibility.php b/includes/libraries/action-scheduler/classes/ActionScheduler_Compatibility.php new file mode 100644 index 0000000..c06e5a4 --- /dev/null +++ b/includes/libraries/action-scheduler/classes/ActionScheduler_Compatibility.php @@ -0,0 +1,99 @@ + $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) { + if ( false !== @ini_set( 'memory_limit', $filtered_limit ) ) { + return $filtered_limit; + } else { + return false; + } + } elseif ( -1 === $wp_max_limit_int || $wp_max_limit_int > $current_limit_int ) { + if ( false !== @ini_set( 'memory_limit', $wp_max_limit ) ) { + return $wp_max_limit; + } else { + return false; + } + } + return false; + } + + /** + * Attempts to raise the PHP timeout for time intensive processes. + * + * Only allows raising the existing limit and prevents lowering it. Wrapper for wc_set_time_limit(), when available. + * + * @param int The time limit in seconds. + */ + public static function raise_time_limit( $limit = 0 ) { + if ( $limit < ini_get( 'max_execution_time' ) ) { + return; + } + + if ( function_exists( 'wc_set_time_limit' ) ) { + wc_set_time_limit( $limit ); + } elseif ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) { + @set_time_limit( $limit ); + } + } +} diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_CronSchedule.php b/includes/libraries/action-scheduler/classes/ActionScheduler_CronSchedule.php old mode 100755 new mode 100644 diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_DateTime.php b/includes/libraries/action-scheduler/classes/ActionScheduler_DateTime.php old mode 100755 new mode 100644 index 13d8c27..5e8743c --- a/includes/libraries/action-scheduler/classes/ActionScheduler_DateTime.php +++ b/includes/libraries/action-scheduler/classes/ActionScheduler_DateTime.php @@ -7,6 +7,16 @@ */ class ActionScheduler_DateTime extends DateTime { + /** + * UTC offset. + * + * Only used when a timezone is not set. When a timezone string is + * used, this will be set to 0. + * + * @var int + */ + protected $utcOffset = 0; + /** * Get the unix timestamp of the current object. * @@ -17,4 +27,50 @@ class ActionScheduler_DateTime extends DateTime { public function getTimestamp() { return method_exists( 'DateTime', 'getTimestamp' ) ? parent::getTimestamp() : $this->format( 'U' ); } + + /** + * Set the UTC offset. + * + * This represents a fixed offset instead of a timezone setting. + * + * @param $offset + */ + public function setUtcOffset( $offset ) { + $this->utcOffset = intval( $offset ); + } + + /** + * Returns the timezone offset. + * + * @return int + * @link http://php.net/manual/en/datetime.getoffset.php + */ + public function getOffset() { + return $this->utcOffset ? $this->utcOffset : parent::getOffset(); + } + + /** + * Set the TimeZone associated with the DateTime + * + * @param DateTimeZone $timezone + * + * @return static + * @link http://php.net/manual/en/datetime.settimezone.php + */ + public function setTimezone( $timezone ) { + $this->utcOffset = 0; + parent::setTimezone( $timezone ); + + return $this; + } + + /** + * Get the timestamp with the WordPress timezone offset added or subtracted. + * + * @since 3.0.0 + * @return int + */ + public function getOffsetTimestamp() { + return $this->getTimestamp() + $this->getOffset(); + } } diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_Exception.php b/includes/libraries/action-scheduler/classes/ActionScheduler_Exception.php new file mode 100644 index 0000000..9ec713f --- /dev/null +++ b/includes/libraries/action-scheduler/classes/ActionScheduler_Exception.php @@ -0,0 +1,11 @@ +claim = NULL; $this->untrack_action(); remove_action( 'shutdown', array( $this, 'handle_unexpected_shutdown' ) ); - remove_action( 'action_scheduler_before_execute', array( $this, 'track_current_action' ), 0, 1 ); - remove_action( 'action_scheduler_after_execute', array( $this, 'untrack_action' ), 0, 0 ); - remove_action( 'action_scheduler_failed_execution', array( $this, 'untrack_action' ), 0, 0 ); + remove_action( 'action_scheduler_before_execute', array( $this, 'track_current_action' ), 0 ); + remove_action( 'action_scheduler_after_execute', array( $this, 'untrack_action' ), 0 ); + remove_action( 'action_scheduler_execution_ignored', array( $this, 'untrack_action' ), 0 ); + remove_action( 'action_scheduler_failed_execution', array( $this, 'untrack_action' ), 0 ); } public function track_current_action( $action_id ) { @@ -51,4 +53,3 @@ class ActionScheduler_FatalErrorMonitor { } } } - \ No newline at end of file diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_FinishedAction.php b/includes/libraries/action-scheduler/classes/ActionScheduler_FinishedAction.php old mode 100755 new mode 100644 diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_IntervalSchedule.php b/includes/libraries/action-scheduler/classes/ActionScheduler_IntervalSchedule.php old mode 100755 new mode 100644 diff --git a/includes/libraries/action-scheduler/classes/ActionScheduler_InvalidActionException.php b/includes/libraries/action-scheduler/classes/ActionScheduler_InvalidActionException.php new file mode 100644 index 0000000..a7decdc --- /dev/null +++ b/includes/libraries/action-scheduler/classes/ActionScheduler_InvalidActionException.php @@ -0,0 +1,28 @@ +'; foreach ( $row['args'] as $key => $value ) { - $row_html .= sprintf( '%s => %s
%s => %s
' . self::$active_version . '
',
'' . WC_Subscriptions::$version . '
',
- '', ''
+ '', '',
+ '', ''
) );
$admin_notice->display();
@@ -834,19 +826,35 @@ class WC_Subscriptions_Upgrader {
}
}
+ /**
+ * Handles the WC 3.5.0 upgrade routine that moves customer IDs from post metadata to the 'post_author' column.
+ *
+ * @since 2.4.0
+ */
+ public static function maybe_update_subscription_post_author() {
+ if ( version_compare( WC()->version, '3.5.0', '<' ) ) {
+ return;
+ }
+
+ // If WC hasn't run the update routine yet we can hook into theirs to update subscriptions, otherwise we'll need to schedule our own update.
+ if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '<' ) ) {
+ self::$background_updaters['2.4']['subscription_post_author']->hook_into_wc_350_update();
+ } else if ( version_compare( self::$active_version, '2.4.0', '<' ) ) {
+ self::$background_updaters['2.4']['subscription_post_author']->schedule_repair();
+ }
+ }
+
/**
* Load and initialise the background updaters.
*
- * @since 2.3.0
+ * @since 2.4.0
*/
public static function initialise_background_updaters() {
$logger = new WC_logger();
-
- include_once( dirname( __FILE__ ) . '/class-wcs-repair-suspended-paypal-subscriptions.php' );
- include_once( dirname( __FILE__ ) . '/class-wcs-repair-subscription-address-indexes.php' );
-
self::$background_updaters['2.3']['suspended_paypal_repair'] = new WCS_Repair_Suspended_PayPal_Subscriptions( $logger );
self::$background_updaters['2.3']['address_indexes_repair'] = new WCS_Repair_Subscription_Address_Indexes( $logger );
+ self::$background_updaters['2.4']['start_date_metadata'] = new WCS_Repair_Start_Date_Metadata( $logger );
+ self::$background_updaters['2.4']['subscription_post_author'] = new WCS_Upgrade_Subscription_Post_Author( $logger );
// Init the updaters
foreach ( self::$background_updaters as $version => $updaters ) {
@@ -908,4 +916,3 @@ class WC_Subscriptions_Upgrader {
return WCS_Upgrade_1_4::is_user_upgraded( $user_id );
}
}
-add_action( 'after_setup_theme', 'WC_Subscriptions_Upgrader::init', 11 );
diff --git a/includes/upgrades/class-wcs-repair-2-0-2.php b/includes/upgrades/class-wcs-repair-2-0-2.php
old mode 100755
new mode 100644
diff --git a/includes/upgrades/class-wcs-repair-2-0.php b/includes/upgrades/class-wcs-repair-2-0.php
old mode 100755
new mode 100644
index e236296..3892a08
--- a/includes/upgrades/class-wcs-repair-2-0.php
+++ b/includes/upgrades/class-wcs-repair-2-0.php
@@ -595,7 +595,7 @@ class WCS_Repair_2_0 {
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: Repairing date type "%s" from action scheduler...', $subscription['order_id'], $type ) );
WCS_Upgrade_Logger::add( '-- This is the arguments: ' . PHP_EOL . print_r( array( $action_args, 'hook' => $type ), true ) . PHP_EOL );
- $next_date_timestamp = wc_next_scheduled_action( $type, $action_args );
+ $next_date_timestamp = as_next_scheduled_action( $type, $action_args );
if ( false === $next_date_timestamp ) {
// set it to 0 as default
diff --git a/includes/upgrades/class-wcs-repair-start-date-metadata.php b/includes/upgrades/class-wcs-repair-start-date-metadata.php
new file mode 100644
index 0000000..42af3d0
--- /dev/null
+++ b/includes/upgrades/class-wcs-repair-start-date-metadata.php
@@ -0,0 +1,70 @@
+scheduled_hook = 'wcs_add_start_date_metadata';
+ $this->log_handle = 'wcs-add-start-date-metadata';
+ $this->logger = $logger;
+ }
+
+ /**
+ * Update a subscription, saving its start date as metadata.
+ *
+ * @since 2.4.0
+ */
+ protected function update_item( $subscription_id ) {
+ try {
+ $subscription = wcs_get_subscription( $subscription_id );
+
+ if ( false === $subscription ) {
+ throw new Exception( 'Failed to instantiate subscription object' );
+ }
+
+ // Saving the subscription is enough to save the start date.
+ $subscription->save();
+
+ $this->log( sprintf( 'Subscription ID %d start date metadata added.', $subscription_id ) );
+ } catch ( Exception $e ) {
+ $this->log( sprintf( '--- Exception caught adding start date metadata to subscription %d - exception message: %s ---', $subscription_id, $e->getMessage() ) );
+ }
+ }
+
+ /**
+ * Get a batch of subscriptions to repair.
+ *
+ * @since 2.4.0
+ * @return array A list of subscription ids which may need to be repaired.
+ */
+ protected function get_items_to_update() {
+ global $wpdb;
+
+ return $wpdb->get_col(
+ "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'shop_subscription'
+ AND post_status NOT IN ( 'trash', 'auto-draft' )
+ AND ID NOT IN ( SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_schedule_start' )
+ LIMIT 20"
+ );
+ }
+
+}
diff --git a/includes/upgrades/class-wcs-repair-subscription-address-indexes.php b/includes/upgrades/class-wcs-repair-subscription-address-indexes.php
old mode 100755
new mode 100644
index 8c21059..5527026
--- a/includes/upgrades/class-wcs-repair-subscription-address-indexes.php
+++ b/includes/upgrades/class-wcs-repair-subscription-address-indexes.php
@@ -21,10 +21,11 @@ class WCS_Repair_Subscription_Address_Indexes extends WCS_Background_Upgrader {
/**
* Constructor
*
- * @param WC_Logger $logger The WC_Logger instance.
+ * @param WC_Logger_Interface $logger The WC_Logger instance.
+ *
* @since 2.3.0
*/
- public function __construct( WC_Logger $logger ) {
+ public function __construct( WC_Logger_Interface $logger ) {
$this->scheduled_hook = 'wcs_add_missing_subscription_address_indexes';
$this->log_handle = 'wcs-add-subscription-address-indexes';
$this->logger = $logger;
diff --git a/includes/upgrades/class-wcs-repair-suspended-paypal-subscriptions.php b/includes/upgrades/class-wcs-repair-suspended-paypal-subscriptions.php
old mode 100755
new mode 100644
index e9b1631..a3f6255
--- a/includes/upgrades/class-wcs-repair-suspended-paypal-subscriptions.php
+++ b/includes/upgrades/class-wcs-repair-suspended-paypal-subscriptions.php
@@ -24,11 +24,11 @@ class WCS_Repair_Suspended_PayPal_Subscriptions extends WCS_Background_Upgrader
/**
* Constructor.
*
- * @param WC_Logger $logger The WC Logger instance.
+ * @param WC_Logger_Interface $logger The WC Logger instance.
*
* @since 2.3.0
*/
- public function __construct( WC_Logger $logger ) {
+ public function __construct( WC_Logger_Interface $logger ) {
$this->scheduled_hook = 'wcs_repair_subscriptions_suspended_paypal_not_woocommerce';
$this->log_handle = 'wcs-upgrade-subscriptions-paypal-suspended';
$this->logger = $logger;
diff --git a/includes/upgrades/class-wcs-upgrade-1-2.php b/includes/upgrades/class-wcs-upgrade-1-2.php
old mode 100755
new mode 100644
index 84829fd..643d8fc
--- a/includes/upgrades/class-wcs-upgrade-1-2.php
+++ b/includes/upgrades/class-wcs-upgrade-1-2.php
@@ -22,7 +22,7 @@ if ( ! defined( 'ABSPATH' ) ) {
class WCS_Upgrade_1_2 {
- public function init() {
+ public static function init() {
global $wpdb;
// Get IDs only and use a direct DB query for efficiency
@@ -276,4 +276,3 @@ class WCS_Upgrade_1_2 {
}
}
}
-WCS_Upgrade_1_2::init();
diff --git a/includes/upgrades/class-wcs-upgrade-1-3.php b/includes/upgrades/class-wcs-upgrade-1-3.php
old mode 100755
new mode 100644
index b3036dd..7c25bc6
--- a/includes/upgrades/class-wcs-upgrade-1-3.php
+++ b/includes/upgrades/class-wcs-upgrade-1-3.php
@@ -17,7 +17,7 @@ if ( ! defined( 'ABSPATH' ) ) {
class WCS_Upgrade_1_3 {
- public function init() {
+ public static function init() {
global $wpdb;
// Change transient timeout entries to be a vanilla option
@@ -36,4 +36,3 @@ class WCS_Upgrade_1_3 {
OR option_name LIKE '_transient_block_scheduled_subscription_payments_%'" );
}
}
-WCS_Upgrade_1_3::init();
diff --git a/includes/upgrades/class-wcs-upgrade-1-4.php b/includes/upgrades/class-wcs-upgrade-1-4.php
old mode 100755
new mode 100644
index df0ee2f..679fd1a
--- a/includes/upgrades/class-wcs-upgrade-1-4.php
+++ b/includes/upgrades/class-wcs-upgrade-1-4.php
@@ -18,7 +18,7 @@ class WCS_Upgrade_1_4 {
private static $last_upgraded_user_id = false;
- public function init() {
+ public static function init() {
global $wpdb;
@@ -177,4 +177,3 @@ class WCS_Upgrade_1_4 {
return $user_id > self::$last_upgraded_user_id;
}
}
-WCS_Upgrade_1_4::init();
diff --git a/includes/upgrades/class-wcs-upgrade-1-5.php b/includes/upgrades/class-wcs-upgrade-1-5.php
old mode 100755
new mode 100644
index d855b57..5eb8aec
--- a/includes/upgrades/class-wcs-upgrade-1-5.php
+++ b/includes/upgrades/class-wcs-upgrade-1-5.php
@@ -67,8 +67,8 @@ class WCS_Upgrade_1_5 {
if ( 'scheduled_subscription_payment' == $hook || 'scheduled_subscription_expiration' == $hook || 'scheduled_subscription_end_of_prepaid_term' == $hook || 'scheduled_subscription_trial_end' == $hook || 'paypal_check_subscription_payment' == $hook ) {
foreach ( $details as $hook_key => $values ) {
- if ( ! wc_next_scheduled_action( $hook, $values['args'] ) ) {
- wc_schedule_single_action( $timestamp, $hook, $values['args'] );
+ if ( ! as_next_scheduled_action( $hook, $values['args'] ) ) {
+ as_schedule_single_action( $timestamp, $hook, $values['args'] );
unset( $cron[ $timestamp ][ $hook ][ $hook_key ] );
$counter++;
}
diff --git a/includes/upgrades/class-wcs-upgrade-2-0.php b/includes/upgrades/class-wcs-upgrade-2-0.php
old mode 100755
new mode 100644
index e865410..a23fdcd
--- a/includes/upgrades/class-wcs-upgrade-2-0.php
+++ b/includes/upgrades/class-wcs-upgrade-2-0.php
@@ -68,7 +68,7 @@ class WCS_Upgrade_2_0 {
$original_order = wc_get_order( $old_subscription['order_id'] );
// If we're still in a prepaid term, the new subscription has the new pending cancellation status
- if ( 'cancelled' == $old_subscription['status'] && false != wc_next_scheduled_action( 'scheduled_subscription_end_of_prepaid_term', array( 'user_id' => $old_subscription['user_id'], 'subscription_key' => $old_subscription['subscription_key'] ) ) ) {
+ if ( 'cancelled' == $old_subscription['status'] && false != as_next_scheduled_action( 'scheduled_subscription_end_of_prepaid_term', array( 'user_id' => $old_subscription['user_id'], 'subscription_key' => $old_subscription['subscription_key'] ) ) ) {
$subscription_status = 'pending-cancel';
} elseif ( 'trash' == $old_subscription['status'] ) {
$subscription_status = 'cancelled'; // we'll trash it properly after migrating it
@@ -501,12 +501,12 @@ class WCS_Upgrade_2_0 {
} elseif ( ! empty( $old_keys['old_scheduled_hook'] ) ) {
// Now check if there is a scheduled date, this is for next payment and end of prepaid term dates
- $next_scheduled = wc_next_scheduled_action( $old_keys['old_scheduled_hook'], $old_hook_args );
+ $next_scheduled = as_next_scheduled_action( $old_keys['old_scheduled_hook'], $old_hook_args );
if ( $next_scheduled > 0 ) {
if ( 'end_of_prepaid_term' == $new_key ) {
- wc_schedule_single_action( $next_scheduled, 'woocommerce_scheduled_subscription_end_of_prepaid_term', array( 'subscription_id' => $new_subscription->get_id() ) );
+ as_schedule_single_action( $next_scheduled, 'woocommerce_scheduled_subscription_end_of_prepaid_term', array( 'subscription_id' => $new_subscription->get_id() ) );
} else {
$dates_to_update[ $new_key ] = gmdate( 'Y-m-d H:i:s', $next_scheduled );
}
diff --git a/includes/upgrades/class-wcs-upgrade-2-1.php b/includes/upgrades/class-wcs-upgrade-2-1.php
old mode 100755
new mode 100644
diff --git a/includes/upgrades/class-wcs-upgrade-2-2-7.php b/includes/upgrades/class-wcs-upgrade-2-2-7.php
old mode 100755
new mode 100644
index 0d808e7..0761806
--- a/includes/upgrades/class-wcs-upgrade-2-2-7.php
+++ b/includes/upgrades/class-wcs-upgrade-2-2-7.php
@@ -60,12 +60,12 @@ class WCS_Upgrade_2_2_7 {
$subscription->update_status( 'cancelled', __( 'Subscription end date in the past', 'woocommerce-subscriptions' ) );
} else {
$action_args = array( 'subscription_id' => $subscription_id );
- $scheduled_action = wc_next_scheduled_action( $end_of_prepaid_term_hook, $action_args );
+ $scheduled_action = as_next_scheduled_action( $end_of_prepaid_term_hook, $action_args );
// If there isn't a scheduled end of prepaid term, schedule one now.
if ( false == $scheduled_action ) {
self::log( sprintf( 'Subscription %d missing scheduled end of prepaid term action - scheduled new action (end timestamp: %d)', $subscription_id, $end_time ) );
- wc_schedule_single_action( $end_time, $end_of_prepaid_term_hook, $action_args );
+ as_schedule_single_action( $end_time, $end_of_prepaid_term_hook, $action_args );
} else {
self::log( sprintf( 'Subscription %d has a scheduled end of prepaid term action - there\'s nothing to do here', $subscription_id ) );
}
diff --git a/includes/upgrades/class-wcs-upgrade-2-2-9.php b/includes/upgrades/class-wcs-upgrade-2-2-9.php
old mode 100755
new mode 100644
diff --git a/includes/upgrades/class-wcs-upgrade-logger.php b/includes/upgrades/class-wcs-upgrade-logger.php
old mode 100755
new mode 100644
index 872bb17..61132fd
--- a/includes/upgrades/class-wcs-upgrade-logger.php
+++ b/includes/upgrades/class-wcs-upgrade-logger.php
@@ -14,7 +14,7 @@ if ( ! defined( 'ABSPATH' ) ) {
class WCS_Upgrade_Logger {
- /** @var WC_Logger instance */
+ /** @var WC_Logger_Interface instance */
protected static $log = false;
/** @var string File handle */
@@ -71,4 +71,3 @@ class WCS_Upgrade_Logger {
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/upgrades/class-wcs-upgrade-notice-manager.php b/includes/upgrades/class-wcs-upgrade-notice-manager.php
old mode 100755
new mode 100644
diff --git a/includes/upgrades/class-wcs-upgrade-subscription-post-author.php b/includes/upgrades/class-wcs-upgrade-subscription-post-author.php
new file mode 100644
index 0000000..89c3a9c
--- /dev/null
+++ b/includes/upgrades/class-wcs-upgrade-subscription-post-author.php
@@ -0,0 +1,165 @@
+scheduled_hook = 'wcs_upgrade_subscription_post_author';
+ $this->log_handle = 'wcs-upgrade-subscription-post-author';
+ $this->logger = $logger;
+ }
+
+ /**
+ * Update a subscription, setting its post_author to its customer ID.
+ *
+ * @since 2.4.0
+ */
+ protected function update_item( $subscription_id ) {
+ global $wpdb;
+
+ try {
+ $wpdb->query(
+ $wpdb->prepare(
+ "UPDATE {$wpdb->posts} p
+ INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
+ SET p.post_author = pm.meta_value WHERE p.ID = %d AND pm.meta_key = '_customer_user'",
+ $subscription_id
+ )
+ );
+
+ if ( 0 === $wpdb->rows_affected ) {
+ if ( '1' === get_post_meta( $subscription_id, '_customer_user', true ) && is_a( WCS_Customer_Store::instance(), 'WCS_Customer_Store_Cached_CPT' ) ) {
+ // Admin's subscription cache seems to be corrupt, force a refresh.
+ WCS_Customer_Store::instance()->delete_cache_for_user( 1 );
+ }
+
+ throw new Exception( 'post_author wasn\'t updated, it was already set to 1' );
+ }
+
+ $this->log( sprintf( 'Subscription ID %d post_author updated.', $subscription_id ) );
+ } catch ( Exception $e ) {
+ $this->log( sprintf( '--- Exception caught repairing subscription %d - exception message: %s ---', $subscription_id, $e->getMessage() ) );
+
+ // Ignore this subscription the next time around.
+ $this->add_subscription_to_ignore_list( $subscription_id );
+ }
+ }
+
+
+ /**
+ * Get a batch of subscriptions which need to be updated.
+ *
+ * @since 2.4.0
+ * @return array A list of subscription ids which need to be updated.
+ */
+ protected function get_items_to_update() {
+ return get_posts( array(
+ 'post_type' => 'shop_subscription',
+ 'posts_per_page' => 20,
+ 'author' => '1',
+ 'post_status' => 'any',
+ 'post__not_in' => $this->get_subscriptions_to_ignore(),
+ 'fields' => 'ids',
+ ) );
+ }
+
+ /**
+ * Schedule the instance's hook to run in $this->time_limit seconds, if it's not already scheduled.
+ */
+ protected function schedule_background_update() {
+ parent::schedule_background_update();
+
+ update_option( 'wcs_subscription_post_author_upgrade_is_scheduled', true );
+ }
+
+ /**
+ * Unschedule the instance's hook in Action Scheduler
+ */
+ protected function unschedule_background_updates() {
+ parent::unschedule_background_updates();
+
+ delete_option( 'wcs_subscription_post_author_upgrade_is_scheduled' );
+ delete_option( 'wcs_post_author_upgrade_other_subscriptions_to_ignore' );
+ }
+
+ /**
+ * Returns the list of admin subscription IDs to ignore during this upgrade routine.
+ *
+ * @return array
+ */
+ private function get_subscriptions_to_ignore() {
+ $subscriptions_to_ignore = WCS_Customer_Store::instance()->get_users_subscription_ids( 1 );
+ $other_subscriptions_to_ignore = get_option( 'wcs_post_author_upgrade_other_subscriptions_to_ignore', array() );
+
+ if ( is_array( $other_subscriptions_to_ignore ) && ! empty( $other_subscriptions_to_ignore ) ) {
+ $subscriptions_to_ignore = array_unique( array_merge( $subscriptions_to_ignore, $other_subscriptions_to_ignore ) );
+ }
+
+ return $subscriptions_to_ignore;
+ }
+
+ /**
+ * Adds a subscription ID to the ignore list for this upgrade routine.
+ *
+ * @param int $subscription_id
+ */
+ private function add_subscription_to_ignore_list( $subscription_id ) {
+ $subscription_id = absint( $subscription_id );
+ if ( ! $subscription_id ) {
+ return;
+ }
+
+ $subscriptions_to_ignore = get_option( 'wcs_post_author_upgrade_other_subscriptions_to_ignore', array() );
+ $subscriptions_to_ignore = is_array( $subscriptions_to_ignore ) ? $subscriptions_to_ignore : array();
+
+ if ( ! in_array( $subscription_id, $subscriptions_to_ignore ) ) {
+ $subscriptions_to_ignore[] = $subscription_id;
+ }
+
+ update_option( 'wcs_post_author_upgrade_other_subscriptions_to_ignore', $subscriptions_to_ignore );
+ }
+
+ /**
+ * Hooks into WC's 3.5 update routine to add the subscription post type to the list of post types affected by this update.
+ *
+ * @since 2.4.0
+ */
+ public static function hook_into_wc_350_update() {
+ add_filter( 'woocommerce_update_350_order_customer_id_post_types', array( __CLASS__, 'add_post_type_to_wc_350_update' ) );
+ }
+
+ /**
+ * Callback for the `woocommerce_update_350_order_customer_id_post_types` hook. Makes sure `shop_subscription` is
+ * included in the post types array.
+ *
+ * @param array $post_types
+ * @return array
+ * @since 2.4.0
+ */
+ public static function add_post_type_to_wc_350_update( $post_types = array() ) {
+ if ( ! in_array( 'shop_subscription', $post_types ) ) {
+ $post_types[] = 'shop_subscription';
+ }
+
+ return $post_types;
+ }
+
+}
+
diff --git a/includes/upgrades/templates/update-welcome-notice.php b/includes/upgrades/templates/update-welcome-notice.php
old mode 100755
new mode 100644
diff --git a/includes/upgrades/templates/wcs-about-2-0.php b/includes/upgrades/templates/wcs-about-2-0.php
old mode 100755
new mode 100644
diff --git a/includes/upgrades/templates/wcs-about.php b/includes/upgrades/templates/wcs-about.php
old mode 100755
new mode 100644
diff --git a/includes/upgrades/templates/wcs-upgrade-in-progress.php b/includes/upgrades/templates/wcs-upgrade-in-progress.php
old mode 100755
new mode 100644
diff --git a/includes/upgrades/templates/wcs-upgrade.php b/includes/upgrades/templates/wcs-upgrade.php
old mode 100755
new mode 100644
diff --git a/includes/wcs-cart-functions.php b/includes/wcs-cart-functions.php
old mode 100755
new mode 100644
index 279494b..f263017
--- a/includes/wcs-cart-functions.php
+++ b/includes/wcs-cart-functions.php
@@ -382,3 +382,21 @@ function wcs_get_cart_item_name( $cart_item, $include = array() ) {
return $cart_item_name;
}
+
+/**
+ * Allows protected products to be renewed.
+ *
+ * @since 2.4.0
+ */
+function wcs_allow_protected_products_to_renew() {
+ remove_filter( 'woocommerce_add_to_cart_validation', 'wc_protected_product_add_to_cart' );
+}
+
+/**
+ * Restores protected products from being added to the cart.
+ * @see wcs_allow_protected_products_to_renew
+ * @since 2.4.0
+ */
+function wcs_disallow_protected_product_add_to_cart_validation() {
+ add_filter( 'woocommerce_add_to_cart_validation', 'wc_protected_product_add_to_cart', 10, 2 );
+}
diff --git a/includes/wcs-compatibility-functions.php b/includes/wcs-compatibility-functions.php
old mode 100755
new mode 100644
diff --git a/includes/wcs-conditional-functions.php b/includes/wcs-conditional-functions.php
old mode 100755
new mode 100644
diff --git a/includes/wcs-deprecated-functions.php b/includes/wcs-deprecated-functions.php
old mode 100755
new mode 100644
index 9dc2dc2..6a0a7ec
--- a/includes/wcs-deprecated-functions.php
+++ b/includes/wcs-deprecated-functions.php
@@ -237,7 +237,7 @@ function wcs_get_subscription_in_deprecated_structure( WC_Subscription $subscrip
'length' => wcs_estimate_periods_between( ( 0 == $subscription->get_time( 'trial_end' ) ) ? $subscription->get_time( 'date_created' ) : $subscription->get_time( 'trial_end' ), $subscription->get_time( 'end' ) + 120, $subscription->get_billing_period(), 'floor' ) / $subscription->get_billing_interval(), // Since subscriptions no longer have a length, we need to calculate the length given the start and end dates and the period.
// Subscription dates
- 'start_date' => $subscription->get_date( 'date_created' ),
+ 'start_date' => $subscription->get_date( 'start' ),
'expiry_date' => $subscription->get_date( 'end' ),
'end_date' => $subscription->has_status( wcs_get_subscription_ended_statuses() ) ? $subscription->get_date( 'end' ) : 0,
'trial_expiry_date' => $subscription->get_date( 'trial_end' ),
diff --git a/includes/wcs-formatting-functions.php b/includes/wcs-formatting-functions.php
old mode 100755
new mode 100644
diff --git a/includes/wcs-helper-functions.php b/includes/wcs-helper-functions.php
old mode 100755
new mode 100644
diff --git a/includes/wcs-limit-functions.php b/includes/wcs-limit-functions.php
old mode 100755
new mode 100644
diff --git a/includes/wcs-order-functions.php b/includes/wcs-order-functions.php
old mode 100755
new mode 100644
index 105cfef..e4c4259
--- a/includes/wcs-order-functions.php
+++ b/includes/wcs-order-functions.php
@@ -540,13 +540,16 @@ function wcs_get_subscription_orders( $return_fields = 'ids', $order_type = 'par
}
/**
- * A wrapper for getting a specific item from a subscription.
+ * A wrapper for getting a specific item from an order or subscription.
*
* WooCommerce has a wc_add_order_item() function, wc_update_order_item() function and wc_delete_order_item() function,
* but no `wc_get_order_item()` function, so we need to add our own (for now).
*
- * @param int $item_id The ID of an order item
- * @return WC_Subscription Subscription details in post_id => WC_Subscription form.
+ * @param int $item_id The ID of an order item
+ * @param WC_Order|WC_Subscription $order The order or order object the item belongs to.
+ *
+ * @return WC_Order_Item|array The order item object or an empty array if the item doesn't exist.
+ *
* @since 2.0
*/
function wcs_get_order_item( $item_id, $order ) {
@@ -818,3 +821,48 @@ function wcs_copy_order_item( $from_item, &$to_item ) {
break;
}
}
+
+/**
+ * Checks an order to see if it contains a manual subscription.
+ *
+ * @since 2.4.3
+ * @param WC_Order|int $order The WC_Order object or ID to get related subscriptions from.
+ * @param string|array $order_type The order relationship type(s). Can be single string or an array of order types. Optional. Default is 'any'.
+ * @return bool
+ */
+function wcs_order_contains_manual_subscription( $order, $order_type = 'any' ) {
+ $subscriptions = wcs_get_subscriptions_for_order( $order, array( 'order_type' => $order_type ) );
+ $contains_manual_subscription = false;
+
+ foreach ( $subscriptions as $subscription ) {
+ if ( $subscription->is_manual() ) {
+ $contains_manual_subscription = true;
+ break;
+ }
+ }
+
+ return $contains_manual_subscription;
+}
+
+/**
+ * Copy payment method from a subscription to an order.
+ *
+ * @since 2.4.3
+ * @param WC_Subscription $subscription
+ * @param WC_Order $order
+ */
+function wcs_copy_payment_method_to_order( $subscription, $order ) {
+ // Set the order's payment method to match the subscription.
+ if ( $order->get_payment_method() !== $subscription->get_payment_method() ) {
+ $order->set_payment_method( $subscription->get_payment_method() );
+ }
+
+ // We only need to copy the subscription's post meta to the order. All other payment related meta should already exist.
+ // Both post_meta and postmeta keys are supported by wcs_set_payment_meta().
+ $payment_meta = array_intersect_key( $subscription->get_payment_method_meta(), array_flip( array( 'post_meta', 'postmeta' ) ) );
+ $payment_meta = (array) apply_filters( 'wcs_copy_payment_meta_to_order', $payment_meta, $order, $subscription );
+
+ if ( ! empty( $payment_meta ) ) {
+ wcs_set_payment_meta( $order, $payment_meta );
+ }
+}
diff --git a/includes/wcs-product-functions.php b/includes/wcs-product-functions.php
old mode 100755
new mode 100644
index 7e12038..b132128
--- a/includes/wcs-product-functions.php
+++ b/includes/wcs-product-functions.php
@@ -169,7 +169,7 @@ function wcs_calculate_min_max_variations( $variations_data ) {
$is_max = $is_min = false;
- if ( '' === $variation_data['price'] && '' === $variation_data['subscription']['sign_up_fee'] ) {
+ if ( '' === $variation_data['price'] && empty( $variation_data['subscription']['sign_up_fee'] ) ) {
continue;
}
@@ -194,7 +194,7 @@ function wcs_calculate_min_max_variations( $variations_data ) {
$initial_period = $variation_data['subscription']['trial_period'];
$initial_interval = $variation_data['subscription']['trial_length'];
} else {
- $initial_amount = $variation_data['price'] + $variation_data['subscription']['sign_up_fee'];
+ $initial_amount = (float) $variation_data['price'] + (float) $variation_data['subscription']['sign_up_fee'];
$initial_period = $variation_data['subscription']['period'];
$initial_interval = $variation_data['subscription']['interval'];
}
diff --git a/includes/wcs-renewal-functions.php b/includes/wcs-renewal-functions.php
old mode 100755
new mode 100644
index 4692926..8c0ea0c
--- a/includes/wcs-renewal-functions.php
+++ b/includes/wcs-renewal-functions.php
@@ -87,8 +87,7 @@ function wcs_cart_contains_renewal() {
/**
* Checks the cart to see if it contains a subscription product renewal for a failed renewal payment.
*
- * @param bool | Array The cart item containing the renewal, else false.
- * @return string
+ * @return bool|array The cart item containing the renewal, else false.
* @since 2.0
*/
function wcs_cart_contains_failed_renewal_order_payment() {
diff --git a/includes/wcs-resubscribe-functions.php b/includes/wcs-resubscribe-functions.php
old mode 100755
new mode 100644
diff --git a/includes/wcs-switch-functions.php b/includes/wcs-switch-functions.php
old mode 100755
new mode 100644
diff --git a/includes/wcs-time-functions.php b/includes/wcs-time-functions.php
old mode 100755
new mode 100644
index 883d45f..fb6e42b
--- a/includes/wcs-time-functions.php
+++ b/includes/wcs-time-functions.php
@@ -705,63 +705,76 @@ function wcs_get_days_in_cycle( $period, $interval ) {
}
/**
- * Get an instance of the site's timezone.
+ * Set a DateTime's timezone to the WordPress site's timezone, or a UTC offset
+ * if no timezone string is available.
*
- * @return DateTimeZone Timezone object for the timezone the site is using.
+ * @since 2.4.2
+ * @param WC_DateTime $date
+ * @return WC_DateTime
*/
-function wcs_get_sites_timezone() {
-
- if ( class_exists( 'ActionScheduler_TimezoneHelper' ) ) {
-
- // Use Action Scheduler's version when possible as it caches the data
- $local_timezone = ActionScheduler_TimezoneHelper::get_local_timezone();
+function wcs_set_local_timezone( WC_DateTime $date ) {
+ if ( get_option( 'timezone_string' ) ) {
+ $date->setTimezone( new DateTimeZone( wc_timezone_string() ) );
} else {
-
- $tzstring = get_option( 'timezone_string' );
-
- if ( empty( $tzstring ) ) {
-
- $gmt_offset = get_option( 'gmt_offset' );
-
- if ( 0 == $gmt_offset ) {
-
- $tzstring = 'UTC';
-
- } else {
-
- $gmt_offset *= HOUR_IN_SECONDS;
- $tzstring = timezone_name_from_abbr( '', $gmt_offset );
-
- if ( false === $tzstring ) {
-
- $is_dst = date( 'I' );
-
- foreach ( timezone_abbreviations_list() as $abbr ) {
-
- foreach ( $abbr as $city ) {
- if ( $city['dst'] == $is_dst && $city['offset'] == $gmt_offset ) {
- $tzstring = $city['timezone_id'];
- break 2;
- }
- }
- }
- }
-
- if ( false === $tzstring ) {
- $tzstring = 'UTC';
- }
- }
- }
-
- $local_timezone = new DateTimeZone( $tzstring );
+ $date->set_utc_offset( wc_timezone_offset() );
}
- return $local_timezone;
+ return $date;
}
/* Deprecated Functions */
+/**
+ * Get an instance of the site's timezone.
+ *
+ * @return DateTimeZone Timezone object for the timezone the site is using.
+ * @deprecated 2.4.2
+ */
+function wcs_get_sites_timezone() {
+ _deprecated_function( __FUNCTION__, '2.4.2' );
+
+ $tzstring = get_option( 'timezone_string' );
+
+ if ( empty( $tzstring ) ) {
+
+ $gmt_offset = get_option( 'gmt_offset' );
+
+ if ( 0 == $gmt_offset ) {
+
+ $tzstring = 'UTC';
+
+ } else {
+
+ $gmt_offset *= HOUR_IN_SECONDS;
+ $tzstring = timezone_name_from_abbr( '', $gmt_offset );
+
+ if ( false === $tzstring ) {
+
+ $is_dst = date( 'I' );
+
+ foreach ( timezone_abbreviations_list() as $abbr ) {
+
+ foreach ( $abbr as $city ) {
+ if ( $city['dst'] == $is_dst && $city['offset'] == $gmt_offset ) {
+ $tzstring = $city['timezone_id'];
+ break 2;
+ }
+ }
+ }
+ }
+
+ if ( false === $tzstring ) {
+ $tzstring = 'UTC';
+ }
+ }
+ }
+
+ $local_timezone = new DateTimeZone( $tzstring );
+
+ return $local_timezone;
+}
+
/**
* Returns an array of subscription lengths.
*
diff --git a/includes/wcs-user-functions.php b/includes/wcs-user-functions.php
old mode 100755
new mode 100644
diff --git a/languages/woocommerce-subscriptions.pot b/languages/woocommerce-subscriptions.pot
old mode 100755
new mode 100644
index 0f25b84..1de3422
--- 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.3.7\n"
+"Project-Id-Version: WooCommerce Subscriptions 2.4.5\n"
"Report-Msgid-Bugs-To: "
"https://github.com/Prospress/woocommerce-subscriptions/issues\n"
-"POT-Creation-Date: 2018-09-25 00:57:34+00:00\n"
+"POT-Creation-Date: 2018-11-28 06:13:23+00:00\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -19,74 +19,74 @@ msgstr ""
msgid "Invalid relation type: %s. Order relationship type must be one of: %s."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:181
+#: includes/admin/class-wc-subscriptions-admin.php:185
msgid "Simple subscription"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:182
+#: includes/admin/class-wc-subscriptions-admin.php:186
msgid "Variable subscription"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:200
+#: includes/admin/class-wc-subscriptions-admin.php:204
msgid "Choose the subscription price, billing interval and period."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:213
+#: includes/admin/class-wc-subscriptions-admin.php:217
#: templates/admin/html-variation-price.php:44
#. translators: placeholder is a currency symbol / code
msgid "Subscription price (%s)"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:216
+#: includes/admin/class-wc-subscriptions-admin.php:220
msgid "Subscription interval"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:222
-#: includes/admin/class-wc-subscriptions-admin.php:358
+#: includes/admin/class-wc-subscriptions-admin.php:226
+#: includes/admin/class-wc-subscriptions-admin.php:362
msgid "Subscription period"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:236
-#: includes/admin/class-wc-subscriptions-admin.php:359
+#: includes/admin/class-wc-subscriptions-admin.php:240
+#: includes/admin/class-wc-subscriptions-admin.php:363
#: templates/admin/html-variation-price.php:66
msgid "Expire after"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:239
+#: includes/admin/class-wc-subscriptions-admin.php:243
msgid ""
"Automatically expire the subscription after this length of time. This "
"length is in addition to any free trial or amount of time provided before a "
"synchronised first renewal date."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:248
+#: includes/admin/class-wc-subscriptions-admin.php:252
#: templates/admin/html-variation-price.php:20
#. translators: %s is a currency symbol / code
msgid "Sign-up fee (%s)"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:250
+#: includes/admin/class-wc-subscriptions-admin.php:254
msgid ""
"Optionally include an amount to be charged at the outset of the "
"subscription. The sign-up fee will be charged immediately, even if the "
"product has a free trial or the payment dates are synced."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:262
+#: includes/admin/class-wc-subscriptions-admin.php:266
#: templates/admin/html-variation-price.php:25
msgid "Free trial"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:265
+#: includes/admin/class-wc-subscriptions-admin.php:269
#: templates/admin/deprecated/html-variation-price.php:115
msgid "Subscription Trial Period"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:297
+#: includes/admin/class-wc-subscriptions-admin.php:301
msgid "One time shipping"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:298
+#: includes/admin/class-wc-subscriptions-admin.php:302
msgid ""
"Shipping for subscription products is normally charged on the initial order "
"and all renewal orders. Enable this to only charge shipping once on the "
@@ -94,63 +94,63 @@ msgid ""
"not have a free trial or a synced renewal date."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:355
+#: includes/admin/class-wc-subscriptions-admin.php:359
msgid "Subscription pricing"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:356
+#: includes/admin/class-wc-subscriptions-admin.php:360
msgid "Subscription sign-up fee"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:357
+#: includes/admin/class-wc-subscriptions-admin.php:361
msgid "Subscription billing interval"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:360
+#: includes/admin/class-wc-subscriptions-admin.php:364
msgid "Free trial length"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:361
+#: includes/admin/class-wc-subscriptions-admin.php:365
msgid "Free trial period"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:682
+#: includes/admin/class-wc-subscriptions-admin.php:686
msgid ""
"Unable to change subscription status to \"%s\". Please assign a customer to "
"the subscription to activate it."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:724
+#: includes/admin/class-wc-subscriptions-admin.php:728
msgid ""
"Trashing this order will also trash the subscriptions purchased with the "
"order."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:737
+#: includes/admin/class-wc-subscriptions-admin.php:741
msgid "Enter the new period, either day, week, month or year:"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:738
+#: includes/admin/class-wc-subscriptions-admin.php:742
msgid "Enter a new length (e.g. 5):"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:739
+#: includes/admin/class-wc-subscriptions-admin.php:743
msgid ""
"Enter a new interval as a single number (e.g. to charge every 2nd month, "
"enter 2):"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:740
+#: includes/admin/class-wc-subscriptions-admin.php:744
msgid "Delete all variations without a subscription"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:743
+#: includes/admin/class-wc-subscriptions-admin.php:747
msgid ""
"Product type can not be changed because this product is associated with "
"active subscriptions"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:747
+#: includes/admin/class-wc-subscriptions-admin.php:751
msgid ""
"You are about to trash one or more orders which contain a subscription.\n"
"\n"
@@ -158,7 +158,7 @@ msgid ""
"orders."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:760
+#: includes/admin/class-wc-subscriptions-admin.php:764
msgid ""
"WARNING: Bad things are about to happen!\n"
"\n"
@@ -170,13 +170,13 @@ msgid ""
"gateway."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:761
+#: includes/admin/class-wc-subscriptions-admin.php:765
msgid ""
"You are deleting a subscription item. You will also need to manually cancel "
"and trash the subscription on the Manage Subscriptions screen."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:768
+#: includes/admin/class-wc-subscriptions-admin.php:772
msgid ""
"Warning: Deleting a user will also delete the user's subscriptions. The "
"user's orders will remain but be reassigned to the 'Guest' user.\n"
@@ -185,63 +185,63 @@ msgid ""
"subscriptions?"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:831
+#: includes/admin/class-wc-subscriptions-admin.php:835
msgid "Active subscriber?"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:874
+#: includes/admin/class-wc-subscriptions-admin.php:878
msgid "Manage Subscriptions"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:878
-#: woocommerce-subscriptions.php:314
+#: includes/admin/class-wc-subscriptions-admin.php:882
+#: woocommerce-subscriptions.php:262
msgid "Search Subscriptions"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:898
-#: includes/admin/class-wc-subscriptions-admin.php:1018
-#: includes/admin/class-wcs-admin-reports.php:51
-#: includes/admin/class-wcs-admin-system-status.php:55
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:687
+#: includes/admin/class-wc-subscriptions-admin.php:902
+#: includes/admin/class-wc-subscriptions-admin.php:1017
+#: includes/admin/class-wcs-admin-reports.php:46
+#: includes/admin/class-wcs-admin-system-status.php:56
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:688
#: includes/class-wcs-query.php:108 includes/class-wcs-query.php:129
#: includes/class-wcs-query.php:248
#: includes/privacy/class-wcs-privacy-exporters.php:51
-#: woocommerce-subscriptions.php:305 woocommerce-subscriptions.php:318
+#: woocommerce-subscriptions.php:253 woocommerce-subscriptions.php:266
msgid "Subscriptions"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1058
+#: includes/admin/class-wc-subscriptions-admin.php:1057
msgid "Button Text"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1065
+#: includes/admin/class-wc-subscriptions-admin.php:1064
msgid "Add to Cart Button Text"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1066
+#: includes/admin/class-wc-subscriptions-admin.php:1065
msgid ""
"A product displays a button with the text \"Add to Cart\". By default, a "
"subscription changes this to \"Sign Up Now\". You can customise the button "
"text for subscriptions here."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1070
-#: includes/admin/class-wc-subscriptions-admin.php:1073
-#: includes/admin/class-wc-subscriptions-admin.php:1082
-#: includes/admin/class-wc-subscriptions-admin.php:1085
+#: includes/admin/class-wc-subscriptions-admin.php:1069
+#: includes/admin/class-wc-subscriptions-admin.php:1072
+#: includes/admin/class-wc-subscriptions-admin.php:1081
+#: includes/admin/class-wc-subscriptions-admin.php:1084
#: includes/class-wc-product-subscription-variation.php:98
#: includes/class-wc-product-subscription.php:72
#: includes/class-wc-product-variable-subscription.php:73
#: includes/class-wc-subscriptions-product.php:99
-#: woocommerce-subscriptions.php:610
+#: woocommerce-subscriptions.php:560
msgid "Sign Up Now"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1077
+#: includes/admin/class-wc-subscriptions-admin.php:1076
msgid "Place Order Button Text"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1078
+#: includes/admin/class-wc-subscriptions-admin.php:1077
msgid ""
"Use this field to customise the text displayed on the checkout button when "
"an order contains a subscription. Normally the checkout submission button "
@@ -249,11 +249,11 @@ msgid ""
"changed to \"Sign Up Now\"."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1091
+#: includes/admin/class-wc-subscriptions-admin.php:1090
msgid "Roles"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1094
+#: includes/admin/class-wc-subscriptions-admin.php:1093
#. translators: placeholders are tags
msgid ""
"Choose the default roles to assign to active and inactive subscribers. For "
@@ -262,46 +262,46 @@ msgid ""
"allocated these roles to prevent locking out administrators."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1099
+#: includes/admin/class-wc-subscriptions-admin.php:1098
msgid "Subscriber Default Role"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1100
+#: includes/admin/class-wc-subscriptions-admin.php:1099
msgid ""
"When a subscription is activated, either manually or after a successful "
"purchase, new users will be assigned this role."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1111
+#: includes/admin/class-wc-subscriptions-admin.php:1110
msgid "Inactive Subscriber Role"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1112
+#: includes/admin/class-wc-subscriptions-admin.php:1111
msgid ""
"If a subscriber's subscription is manually cancelled or expires, she will "
"be assigned this role."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1132
+#: includes/admin/class-wc-subscriptions-admin.php:1131
msgid "Manual Renewal Payments"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1133
+#: includes/admin/class-wc-subscriptions-admin.php:1132
msgid "Accept Manual Renewals"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1138
+#: includes/admin/class-wc-subscriptions-admin.php:1137
#. translators: placeholders are opening and closing link tags
msgid ""
"With manual renewals, a customer's subscription is put on-hold until they "
"login and pay to renew it. %sLearn more%s."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1144
+#: includes/admin/class-wc-subscriptions-admin.php:1143
msgid "Turn off Automatic Payments"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1149
+#: includes/admin/class-wc-subscriptions-admin.php:1148
#. translators: placeholders are opening and closing link tags
msgid ""
"If you don't want new subscription purchases to automatically charge "
@@ -310,11 +310,11 @@ msgid ""
"more%s."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1164
+#: includes/admin/class-wc-subscriptions-admin.php:1163
msgid "Customer Suspensions"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1171
+#: includes/admin/class-wc-subscriptions-admin.php:1170
msgid ""
"Set a maximum number of times a customer can suspend their account for each "
"billing period. For example, for a value of 3 and a subscription billed "
@@ -324,30 +324,30 @@ msgid ""
"this to 0 to turn off the customer suspension feature completely."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1175
+#: includes/admin/class-wc-subscriptions-admin.php:1174
msgid "Mixed Checkout"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1176
+#: includes/admin/class-wc-subscriptions-admin.php:1175
msgid "Allow multiple subscriptions and products to be purchased simultaneously."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1180
+#: includes/admin/class-wc-subscriptions-admin.php:1179
msgid ""
"Allow a subscription product to be purchased with other products and "
"subscriptions in the same transaction."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1184
+#: includes/admin/class-wc-subscriptions-admin.php:1183
#: includes/upgrades/templates/wcs-about-2-0.php:108
msgid "Drip Downloadable Content"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1185
+#: includes/admin/class-wc-subscriptions-admin.php:1184
msgid "Enable dripping for downloadable content on subscription products."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1189
+#: includes/admin/class-wc-subscriptions-admin.php:1188
msgid ""
"Enabling this grants access to new downloadable files added to a product "
"only after the next renewal is processed.%sBy default, access to new "
@@ -355,7 +355,7 @@ msgid ""
"customer that has an active subscription with that product."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1225
+#: includes/admin/class-wc-subscriptions-admin.php:1224
#. translators: $1-$2: opening and closing tags, $3-$4: opening and
#. closing tags
msgid ""
@@ -363,73 +363,73 @@ msgid ""
"start selling subscriptions!%4$s"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1230
+#: includes/admin/class-wc-subscriptions-admin.php:1229
msgid "Add a Subscription Product"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1231
+#: includes/admin/class-wc-subscriptions-admin.php:1230
#: includes/upgrades/templates/wcs-about-2-0.php:35
#: includes/upgrades/templates/wcs-about.php:34
-#: woocommerce-subscriptions.php:1196
+#: woocommerce-subscriptions.php:1091
msgid "Settings"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1317
+#: includes/admin/class-wc-subscriptions-admin.php:1316
#. translators: placeholder is a number
msgid "We can't find a subscription with ID #%d. Perhaps it was deleted?"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1350
-#: includes/admin/class-wc-subscriptions-admin.php:1355
+#: includes/admin/class-wc-subscriptions-admin.php:1349
+#: includes/admin/class-wc-subscriptions-admin.php:1354
#. translators: placeholders are opening link tag, ID of sub, and closing link
#. tag
msgid "Showing orders for %sSubscription %s%s"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1379
+#: includes/admin/class-wc-subscriptions-admin.php:1378
#. translators: number of 1$: days, 2$: weeks, 3$: months, 4$: years
msgid "The trial period can not exceed: %1s, %2s, %3s or %4s."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1384
+#: includes/admin/class-wc-subscriptions-admin.php:1383
#. translators: placeholder is a time period (e.g. "4 weeks")
msgid "The trial period can not exceed %s."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1447
-#: includes/admin/class-wc-subscriptions-admin.php:1500
-#: includes/admin/class-wcs-admin-system-status.php:94
-#: includes/admin/reports/class-wcs-report-cache-manager.php:341
+#: includes/admin/class-wc-subscriptions-admin.php:1446
+#: includes/admin/class-wc-subscriptions-admin.php:1499
+#: includes/admin/class-wcs-admin-system-status.php:95
+#: includes/admin/reports/class-wcs-report-cache-manager.php:328
msgid "Yes"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1447
-#: includes/admin/class-wcs-admin-system-status.php:94
-#: includes/admin/reports/class-wcs-report-cache-manager.php:341
+#: includes/admin/class-wc-subscriptions-admin.php:1446
+#: includes/admin/class-wcs-admin-system-status.php:95
+#: includes/admin/reports/class-wcs-report-cache-manager.php:328
msgid "No"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1483
+#: includes/admin/class-wc-subscriptions-admin.php:1482
msgid "Automatic Recurring Payments"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1500
+#: includes/admin/class-wc-subscriptions-admin.php:1499
msgid ""
"Supports automatic renewal payments with the WooCommerce Subscriptions "
"extension."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1580
+#: includes/admin/class-wc-subscriptions-admin.php:1579
msgid "Subscription items can no longer be edited."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1584
+#: includes/admin/class-wc-subscriptions-admin.php:1583
msgid ""
"This subscription is no longer editable because the payment gateway does "
"not allow modification of recurring amounts."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1603
+#: includes/admin/class-wc-subscriptions-admin.php:1602
#. translators: $1-2: opening and closing tags of a link that takes to Woo
#. marketplace / Stripe product page
msgid ""
@@ -438,18 +438,18 @@ msgid ""
"the %1$sfree Stripe extension%2$s."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1608
+#: includes/admin/class-wc-subscriptions-admin.php:1607
msgid "Recurring Payments"
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1616
+#: includes/admin/class-wc-subscriptions-admin.php:1615
#. translators: placeholders are opening and closing link tags
msgid ""
"Payment gateways which don't support automatic recurring payments can be "
"used to process %smanual subscription renewal payments%s."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1623
+#: includes/admin/class-wc-subscriptions-admin.php:1622
#. translators: $1-$2: opening and closing tags. Link to documents->payment
#. gateways, 3$-4$: opening and closing tags. Link to WooCommerce extensions
#. shop page
@@ -458,7 +458,7 @@ msgid ""
"the official %3$sWooCommerce Marketplace%4$s."
msgstr ""
-#: includes/admin/class-wc-subscriptions-admin.php:1725
+#: includes/admin/class-wc-subscriptions-admin.php:1724
msgid "Note that purchasing a subscription still requires an account."
msgstr ""
@@ -576,7 +576,7 @@ msgstr ""
#: templates/emails/subscription-info.php:18
#: templates/myaccount/my-subscriptions.php:25
#: templates/myaccount/related-subscriptions.php:20
-#: woocommerce-subscriptions.php:306
+#: woocommerce-subscriptions.php:254
msgid "Subscription"
msgstr ""
@@ -687,117 +687,117 @@ msgid ""
"this subscription controls when payments are processed."
msgstr ""
-#: includes/admin/class-wcs-admin-post-types.php:873
-#: includes/admin/class-wcs-admin-post-types.php:876
-#: includes/admin/class-wcs-admin-post-types.php:879
+#: includes/admin/class-wcs-admin-post-types.php:884
+#: includes/admin/class-wcs-admin-post-types.php:887
+#: includes/admin/class-wcs-admin-post-types.php:890
msgid "Subscription updated."
msgstr ""
-#: includes/admin/class-wcs-admin-post-types.php:874
+#: includes/admin/class-wcs-admin-post-types.php:885
msgid "Custom field updated."
msgstr ""
-#: includes/admin/class-wcs-admin-post-types.php:875
+#: includes/admin/class-wcs-admin-post-types.php:886
msgid "Custom field deleted."
msgstr ""
-#: includes/admin/class-wcs-admin-post-types.php:880
+#: includes/admin/class-wcs-admin-post-types.php:891
msgid "Subscription saved."
msgstr ""
-#: includes/admin/class-wcs-admin-post-types.php:881
+#: includes/admin/class-wcs-admin-post-types.php:892
msgid "Subscription submitted."
msgstr ""
-#: includes/admin/class-wcs-admin-post-types.php:883
+#: includes/admin/class-wcs-admin-post-types.php:894
#. translators: php date string
msgid "Subscription scheduled for: %1$s."
msgstr ""
-#: includes/admin/class-wcs-admin-post-types.php:884
+#: includes/admin/class-wcs-admin-post-types.php:895
msgid "Subscription draft updated."
msgstr ""
-#: includes/admin/class-wcs-admin-post-types.php:920
+#: includes/admin/class-wcs-admin-post-types.php:931
msgid "Any Payment Method"
msgstr ""
-#: includes/admin/class-wcs-admin-post-types.php:921
+#: includes/admin/class-wcs-admin-post-types.php:932
msgid "None"
msgstr ""
-#: includes/admin/class-wcs-admin-post-types.php:1114
+#: includes/admin/class-wcs-admin-post-types.php:1125
#. translators: 1: user display name 2: user ID 3: user email
msgid "%1$s (#%2$s – %3$s)"
msgstr ""
-#: includes/admin/class-wcs-admin-post-types.php:1121
+#: includes/admin/class-wcs-admin-post-types.php:1132
#: includes/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:84
msgid "Search for a customer…"
msgstr ""
-#: includes/admin/class-wcs-admin-reports.php:54
+#: includes/admin/class-wcs-admin-reports.php:49
msgid "Subscription Events by Date"
msgstr ""
-#: includes/admin/class-wcs-admin-reports.php:60
+#: includes/admin/class-wcs-admin-reports.php:55
msgid "Upcoming Recurring Revenue"
msgstr ""
-#: includes/admin/class-wcs-admin-reports.php:66
+#: includes/admin/class-wcs-admin-reports.php:61
msgid "Retention Rate"
msgstr ""
-#: includes/admin/class-wcs-admin-reports.php:72
+#: includes/admin/class-wcs-admin-reports.php:67
msgid "Subscriptions by Product"
msgstr ""
-#: includes/admin/class-wcs-admin-reports.php:78
+#: includes/admin/class-wcs-admin-reports.php:73
msgid "Subscriptions by Customer"
msgstr ""
-#: includes/admin/class-wcs-admin-reports.php:88
+#: includes/admin/class-wcs-admin-reports.php:83
msgid "Failed Payment Retries"
msgstr ""
-#: includes/admin/class-wcs-admin-reports.php:128
-#: includes/admin/reports/class-wcs-report-cache-manager.php:287
+#: includes/admin/class-wcs-admin-reports.php:104
+#: includes/admin/reports/class-wcs-report-cache-manager.php:274
msgid "WooCommerce"
msgstr ""
-#: includes/admin/class-wcs-admin-system-status.php:56
+#: includes/admin/class-wcs-admin-system-status.php:57
msgid "This section shows any information about Subscriptions."
msgstr ""
-#: includes/admin/class-wcs-admin-system-status.php:60
+#: includes/admin/class-wcs-admin-system-status.php:61
msgid "Store Setup"
msgstr ""
-#: includes/admin/class-wcs-admin-system-status.php:61
+#: includes/admin/class-wcs-admin-system-status.php:62
msgid "This section shows general information about the store."
msgstr ""
-#: includes/admin/class-wcs-admin-system-status.php:65
+#: includes/admin/class-wcs-admin-system-status.php:66
msgid "Subscriptions by Payment Gateway"
msgstr ""
-#: includes/admin/class-wcs-admin-system-status.php:66
+#: includes/admin/class-wcs-admin-system-status.php:67
msgid "This section shows information about Subscription payment methods."
msgstr ""
-#: includes/admin/class-wcs-admin-system-status.php:70
+#: includes/admin/class-wcs-admin-system-status.php:71
msgid "Payment Gateway Support"
msgstr ""
-#: includes/admin/class-wcs-admin-system-status.php:71
+#: includes/admin/class-wcs-admin-system-status.php:72
msgid "This section shows information about payment gateway feature support."
msgstr ""
-#: includes/admin/class-wcs-admin-system-status.php:128
+#: includes/admin/class-wcs-admin-system-status.php:144
msgid "%sLearn how to update%s"
msgstr ""
-#: includes/admin/class-wcs-admin-system-status.php:174
+#: includes/admin/class-wcs-admin-system-status.php:190
#. translators: %1$s is the file version, %2$s is the core version
msgid "version %1$s is out of date. The core version is %2$s"
msgstr ""
@@ -911,9 +911,9 @@ msgid "Relationship"
msgstr ""
#: includes/admin/meta-boxes/views/html-related-orders-table.php:19
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:548
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:549
#: includes/admin/reports/class-wcs-report-subscription-payment-retry.php:173
-#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:205
+#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:204
#: templates/myaccount/related-orders.php:23
#: templates/myaccount/related-orders.php:42
msgid "Date"
@@ -986,18 +986,18 @@ msgstr ""
msgid "Error: unable to find timezone of your browser."
msgstr ""
-#: includes/admin/reports/class-wcs-report-cache-manager.php:290
+#: includes/admin/reports/class-wcs-report-cache-manager.php:277
msgid ""
"Please note: data for this report is cached. The data displayed may be out "
"of date by up to 24 hours. The cache is updated each morning at 4am in your "
"site's timezone."
msgstr ""
-#: includes/admin/reports/class-wcs-report-cache-manager.php:345
+#: includes/admin/reports/class-wcs-report-cache-manager.php:332
msgid "Cache Update Failures"
msgstr ""
-#: includes/admin/reports/class-wcs-report-cache-manager.php:348
+#: includes/admin/reports/class-wcs-report-cache-manager.php:335
#. translators: %d refers to the number of times we have detected cache update
#. failures
msgid "%d failures"
@@ -1143,185 +1143,185 @@ msgstr ""
msgid "subscriptions"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:397
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:398
msgid "%s signup revenue in this period"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:398
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:399
msgid ""
"The sum of all subscription parent orders, including other items, fees, tax "
"and shipping."
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:404
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:405
msgid "%s renewal revenue in this period"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:405
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:406
msgid "The sum of all renewal orders including tax and shipping."
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:411
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:412
msgid "%s resubscribe revenue in this period"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:412
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:413
msgid "The sum of all resubscribe orders including tax and shipping."
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:418
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:419
msgid "%s new subscriptions"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:419
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:420
msgid ""
"The number of subscriptions created during this period, either by being "
"manually created, imported or a customer placing an order."
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:425
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:426
msgid "%s subscription signups"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:426
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:427
msgid ""
"The number of subscription parent orders created during this period. This "
"represents the new subscriptions created by customers placing an order via "
"checkout."
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:432
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:433
msgid "%s subscription resubscribes"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:433
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:434
msgid "The number of resubscribe orders processed during this period."
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:439
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:440
msgid "%s subscription renewals"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:440
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:441
msgid "The number of renewal orders processed during this period."
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:446
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:447
msgid "%s subscription switches"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:447
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:448
msgid ""
"The number of subscriptions upgraded, downgraded or cross-graded during "
"this period."
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:453
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:454
msgid "%s subscription cancellations"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:454
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:455
msgid ""
"The number of subscriptions cancelled by the customer or store manager "
"during this period. The pre-paid term may not yet have ended during this "
"period."
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:460
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:461
msgid "%s subscriptions ended"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:461
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:462
msgid ""
"The number of subscriptions which have either expired or reached the end of "
"the prepaid term if it was previously cancelled."
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:467
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:468
msgid "%s current subscriptions"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:468
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:469
msgid ""
"The number of subscriptions during this period with an end date in the "
"future and a status other than pending."
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:484
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:485
msgid "%s net subscription gain"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:486
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:487
msgid "%s net subscription loss"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:491
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:492
msgid "Change in subscriptions between the start and end of the period."
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:505
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:506
#: includes/admin/reports/class-wcs-report-subscription-payment-retry.php:137
msgid "Year"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:506
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:507
#: includes/admin/reports/class-wcs-report-subscription-payment-retry.php:138
msgid "Last Month"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:507
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:508
#: includes/admin/reports/class-wcs-report-subscription-payment-retry.php:139
msgid "This Month"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:508
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:509
#: includes/admin/reports/class-wcs-report-subscription-payment-retry.php:140
msgid "Last 7 Days"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:552
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:553
#: includes/admin/reports/class-wcs-report-subscription-payment-retry.php:177
-#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:209
+#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:208
msgid "Export CSV"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:609
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:610
msgid "Switched subscriptions"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:625
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:626
msgid "New Subscriptions"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:641
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:642
msgid "Subscriptions signups"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:656
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:657
msgid "Number of resubscribes"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:671
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:672
msgid "Number of renewals"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:703
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:704
msgid "Subscriptions Ended"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:719
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:720
msgid "Cancellations"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:734
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:735
msgid "Signup Totals"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:754
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:755
msgid "Resubscribe Totals"
msgstr ""
-#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:774
+#: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:775
msgid "Renewal Totals"
msgstr ""
@@ -1398,40 +1398,40 @@ msgstr ""
msgid "%s average renewal amount"
msgstr ""
-#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:175
+#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:174
msgid "Next 12 Months"
msgstr ""
-#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:176
+#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:175
msgid "Next 30 Days"
msgstr ""
-#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:177
+#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:176
msgid "Next Month"
msgstr ""
-#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:178
+#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:177
msgid "Next 7 Days"
msgstr ""
-#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:243
+#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:242
msgid "Renewals count"
msgstr ""
-#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:252
+#: includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php:251
msgid "Renewals amount"
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:118
+#: includes/api/class-wc-rest-subscriptions-controller.php:116
msgid "Customer ID is invalid."
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:223
+#: includes/api/class-wc-rest-subscriptions-controller.php:221
#: includes/api/legacy/class-wc-rest-subscriptions-controller.php:171
msgid "Invalid subscription id."
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:294
+#: includes/api/class-wc-rest-subscriptions-controller.php:292
#: includes/api/legacy/class-wc-api-subscriptions.php:307
#: includes/api/legacy/class-wc-rest-subscriptions-controller.php:303
msgid ""
@@ -1439,7 +1439,7 @@ msgid ""
"Subscription."
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:325
+#: includes/api/class-wc-rest-subscriptions-controller.php:330
#: includes/api/legacy/class-wc-rest-subscriptions-controller.php:333
#. translators: 1$: gateway id, 2$: error message
msgid ""
@@ -1447,71 +1447,71 @@ msgid ""
"%2$s"
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:392
-#: includes/api/class-wc-rest-subscriptions-controller.php:558
+#: includes/api/class-wc-rest-subscriptions-controller.php:396
+#: includes/api/class-wc-rest-subscriptions-controller.php:562
#: includes/api/legacy/class-wc-rest-subscriptions-controller.php:284
msgid "Updating subscription dates errored with message: %s"
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:417
+#: includes/api/class-wc-rest-subscriptions-controller.php:421
#: includes/api/legacy/class-wc-rest-subscriptions-controller.php:347
msgid "The number of billing periods between subscription renewals."
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:422
+#: includes/api/class-wc-rest-subscriptions-controller.php:426
#: includes/api/legacy/class-wc-rest-subscriptions-controller.php:352
msgid "Billing period for the subscription."
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:428
+#: includes/api/class-wc-rest-subscriptions-controller.php:432
#: includes/api/legacy/class-wc-rest-subscriptions-controller.php:358
msgid "Subscription payment details."
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:433
+#: includes/api/class-wc-rest-subscriptions-controller.php:437
#: includes/api/legacy/class-wc-rest-subscriptions-controller.php:363
msgid "Payment gateway ID."
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:440
+#: includes/api/class-wc-rest-subscriptions-controller.php:444
#: includes/api/legacy/class-wc-rest-subscriptions-controller.php:370
msgid "The subscription's start date."
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:445
+#: includes/api/class-wc-rest-subscriptions-controller.php:449
#: includes/api/legacy/class-wc-rest-subscriptions-controller.php:375
msgid "The subscription's trial date"
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:450
+#: includes/api/class-wc-rest-subscriptions-controller.php:454
#: includes/api/legacy/class-wc-rest-subscriptions-controller.php:380
msgid "The subscription's next payment date."
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:455
+#: includes/api/class-wc-rest-subscriptions-controller.php:459
#: includes/api/legacy/class-wc-rest-subscriptions-controller.php:385
msgid "The subscription's end date."
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:460
+#: includes/api/class-wc-rest-subscriptions-controller.php:464
msgid ""
"The subscription's original subscription ID if this is a resubscribed "
"subscription."
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:466
+#: includes/api/class-wc-rest-subscriptions-controller.php:470
msgid "The subscription's resubscribed subscription ID."
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:472
+#: includes/api/class-wc-rest-subscriptions-controller.php:476
msgid "The date the subscription's latest order was completed, in GMT."
msgstr ""
-#: includes/api/class-wc-rest-subscriptions-controller.php:478
+#: includes/api/class-wc-rest-subscriptions-controller.php:482
msgid "The date the subscription's latest order was paid, in GMT."
msgstr ""
-#: includes/api/legacy/class-wc-api-subscriptions.php:102 wcs-functions.php:172
+#: includes/api/legacy/class-wc-api-subscriptions.php:102 wcs-functions.php:178
msgid "Invalid subscription status given."
msgstr ""
@@ -1534,13 +1534,13 @@ msgid ""
"manual with error message: %2$s"
msgstr ""
-#: includes/api/legacy/class-wc-api-subscriptions.php:380 wcs-functions.php:146
+#: includes/api/legacy/class-wc-api-subscriptions.php:380 wcs-functions.php:152
msgid ""
"Invalid subscription billing interval given. Must be an integer greater "
"than 0."
msgstr ""
-#: includes/api/legacy/class-wc-api-subscriptions.php:391 wcs-functions.php:141
+#: includes/api/legacy/class-wc-api-subscriptions.php:391 wcs-functions.php:147
msgid "Invalid subscription billing period given."
msgstr ""
@@ -1548,71 +1548,75 @@ msgstr ""
msgid "Cannot create subscription: %s."
msgstr ""
-#: includes/class-wc-subscription.php:398
+#: includes/class-wc-subscription.php:413
msgid "Unable to change subscription status to \"%s\"."
msgstr ""
-#: includes/class-wc-subscription.php:500
+#: includes/class-wc-subscription.php:515
msgid "Unable to change subscription status to \"%s\". Exception: %s"
msgstr ""
-#: includes/class-wc-subscription.php:522
+#: includes/class-wc-subscription.php:537
#. translators: 1: old subscription status 2: new subscription status
msgid "Status changed from %1$s to %2$s."
msgstr ""
-#: includes/class-wc-subscription.php:534
+#: includes/class-wc-subscription.php:549
#. translators: %s: new order status
msgid "Status set to %s."
msgstr ""
-#: includes/class-wc-subscription.php:1085
+#: includes/class-wc-subscription.php:1100
#: includes/class-wc-subscriptions-manager.php:2279
#: 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:1088
+#: includes/class-wc-subscription.php:1103
#: 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:1095
+#: includes/class-wc-subscription.php:1110
msgid "Not yet ended"
msgstr ""
-#: includes/class-wc-subscription.php:1098
+#: includes/class-wc-subscription.php:1113
msgid "Not cancelled"
msgstr ""
-#: includes/class-wc-subscription.php:1213
+#: includes/class-wc-subscription.php:1228
+msgid "The creation date of a subscription can not be deleted, only updated."
+msgstr ""
+
+#: includes/class-wc-subscription.php:1231
msgid "The start date of a subscription can not be deleted, only updated."
msgstr ""
-#: includes/class-wc-subscription.php:1217
+#: includes/class-wc-subscription.php:1235
msgid "The %s date of a subscription can not be deleted. You must delete the order."
msgstr ""
-#: includes/class-wc-subscription.php:1225
-#: includes/class-wc-subscription.php:2301
+#: includes/class-wc-subscription.php:1243
+#: includes/class-wc-subscription.php:2302
msgid "Subscription #%d: "
msgstr ""
-#: includes/class-wc-subscription.php:1631
+#: includes/class-wc-subscription.php:1650
msgid "Payment status marked complete."
msgstr ""
-#: includes/class-wc-subscription.php:1659
+#: includes/class-wc-subscription.php:1678
msgid "Payment failed."
msgstr ""
-#: includes/class-wc-subscription.php:1664
+#: includes/class-wc-subscription.php:1683
msgid "Subscription Cancelled: maximum number of failed payments reached."
msgstr ""
-#: includes/class-wc-subscription.php:1774
+#: includes/class-wc-subscription.php:1793
msgid ""
"The \"all\" value for $order_type parameter is deprecated. It was a "
"misnomer, as it did not return resubscribe orders. It was also inconsistent "
@@ -1622,155 +1626,155 @@ msgid ""
"resubscribe."
msgstr ""
-#: includes/class-wc-subscription.php:1876
+#: includes/class-wc-subscription.php:1895
#: includes/class-wcs-change-payment-method-admin.php:155
msgid "Manual Renewal"
msgstr ""
-#: includes/class-wc-subscription.php:1955
+#: includes/class-wc-subscription.php:1974 wcs-functions.php:794
msgid "Payment method meta must be an array."
msgstr ""
-#: includes/class-wc-subscription.php:2199
+#: includes/class-wc-subscription.php:2200
msgid "Invalid format. First parameter needs to be an array."
msgstr ""
-#: includes/class-wc-subscription.php:2203
+#: includes/class-wc-subscription.php:2204
msgid "Invalid data. First parameter was empty when passed to update_dates()."
msgstr ""
-#: includes/class-wc-subscription.php:2210
+#: includes/class-wc-subscription.php:2211
msgid ""
"Invalid data. First parameter has a date that is not in the registered date "
"types."
msgstr ""
-#: includes/class-wc-subscription.php:2274
+#: includes/class-wc-subscription.php:2275
msgid "The %s date must occur after the cancellation date."
msgstr ""
-#: includes/class-wc-subscription.php:2279
+#: includes/class-wc-subscription.php:2280
msgid "The %s date must occur after the last payment date."
msgstr ""
-#: includes/class-wc-subscription.php:2283
+#: includes/class-wc-subscription.php:2284
msgid "The %s date must occur after the next payment date."
msgstr ""
-#: includes/class-wc-subscription.php:2288
+#: includes/class-wc-subscription.php:2289
msgid "The %s date must occur after the trial end date."
msgstr ""
-#: includes/class-wc-subscription.php:2292
+#: includes/class-wc-subscription.php:2293
msgid "The %s date must occur after the start date."
msgstr ""
-#: includes/class-wc-subscription.php:2321
+#: includes/class-wc-subscription.php:2322
#: includes/class-wc-subscriptions-checkout.php:325
#: includes/wcs-order-functions.php:305
msgid "Backordered"
msgstr ""
-#: includes/class-wc-subscriptions-addresses.php:46
+#: includes/class-wc-subscriptions-addresses.php:47
msgid "Change Address"
msgstr ""
-#: includes/class-wc-subscriptions-addresses.php:70
+#: includes/class-wc-subscriptions-addresses.php:71
msgid ""
"Both the shipping address used for the subscription and your default "
"shipping address for future purchases will be updated."
msgstr ""
-#: includes/class-wc-subscriptions-addresses.php:83
+#: includes/class-wc-subscriptions-addresses.php:84
#. translators: $1: address type (Shipping Address / Billing Address), $2:
#. opening tag, $3: closing tag
msgid "Update the %1$s used for %2$sall%3$s of my active subscriptions"
msgstr ""
-#: includes/class-wc-subscriptions-cart.php:902
+#: includes/class-wc-subscriptions-cart.php:912
msgid "Please enter a valid postcode/ZIP."
msgstr ""
-#: includes/class-wc-subscriptions-cart.php:1073
+#: includes/class-wc-subscriptions-cart.php:1083
msgid ""
"That subscription product can not be added to your cart as it already "
"contains a subscription renewal."
msgstr ""
-#: includes/class-wc-subscriptions-cart.php:1161
+#: includes/class-wc-subscriptions-cart.php:1171
msgid "Invalid recurring shipping method."
msgstr ""
-#: includes/class-wc-subscriptions-cart.php:1984
+#: includes/class-wc-subscriptions-cart.php:1994
msgid "now"
msgstr ""
-#: includes/class-wc-subscriptions-change-payment-gateway.php:160
+#: includes/class-wc-subscriptions-change-payment-gateway.php:175
#: templates/emails/plain/email-order-details.php:19
#. translators: placeholder is the subscription order number wrapped in
#. tags
msgid "Subscription Number: %s"
msgstr ""
-#: includes/class-wc-subscriptions-change-payment-gateway.php:166
+#: includes/class-wc-subscriptions-change-payment-gateway.php:181
#. translators: placeholder is the subscription's next payment date (either
#. human readable or normal date) wrapped in tags
msgid "Next Payment Date: %s"
msgstr ""
-#: includes/class-wc-subscriptions-change-payment-gateway.php:172
+#: includes/class-wc-subscriptions-change-payment-gateway.php:187
#. translators: placeholder is the formatted total to be paid for the
#. subscription wrapped in tags
msgid "Total: %s"
msgstr ""
-#: includes/class-wc-subscriptions-change-payment-gateway.php:179
+#: includes/class-wc-subscriptions-change-payment-gateway.php:194
#. translators: placeholder is the display name of the payment method
msgid "Payment Method: %s"
msgstr ""
-#: includes/class-wc-subscriptions-change-payment-gateway.php:191
+#: includes/class-wc-subscriptions-change-payment-gateway.php:206
msgid ""
"Sorry, this subscription change payment method request is invalid and "
"cannot be processed."
msgstr ""
-#: includes/class-wc-subscriptions-change-payment-gateway.php:223
+#: includes/class-wc-subscriptions-change-payment-gateway.php:238
msgid "There was an error with your request. Please try again."
msgstr ""
-#: includes/class-wc-subscriptions-change-payment-gateway.php:227
+#: includes/class-wc-subscriptions-change-payment-gateway.php:242
#: templates/myaccount/view-subscription.php:20
msgid "Invalid Subscription."
msgstr ""
-#: includes/class-wc-subscriptions-change-payment-gateway.php:231
+#: includes/class-wc-subscriptions-change-payment-gateway.php:246
#: includes/class-wcs-cart-resubscribe.php:78
#: includes/class-wcs-cart-resubscribe.php:129
#: includes/class-wcs-user-change-status-handler.php:103
-#: includes/early-renewal/class-wcs-cart-early-renewal.php:96
+#: includes/early-renewal/class-wcs-cart-early-renewal.php:94
msgid "That doesn't appear to be one of your subscriptions."
msgstr ""
-#: includes/class-wc-subscriptions-change-payment-gateway.php:235
+#: includes/class-wc-subscriptions-change-payment-gateway.php:250
msgid "The payment method can not be changed for that subscription."
msgstr ""
-#: includes/class-wc-subscriptions-change-payment-gateway.php:241
+#: includes/class-wc-subscriptions-change-payment-gateway.php:256
#. translators: placeholder is next payment's date
msgid " Next payment is due %s."
msgstr ""
-#: includes/class-wc-subscriptions-change-payment-gateway.php:247
+#: includes/class-wc-subscriptions-change-payment-gateway.php:262
#. translators: placeholder is either empty or "Next payment is due..."
msgid "Choose a new payment method.%s"
msgstr ""
-#: includes/class-wc-subscriptions-change-payment-gateway.php:276
+#: includes/class-wc-subscriptions-change-payment-gateway.php:291
msgid "Invalid order."
msgstr ""
-#: includes/class-wc-subscriptions-change-payment-gateway.php:378
+#: includes/class-wc-subscriptions-change-payment-gateway.php:393
msgid "Payment method updated."
msgstr ""
@@ -1845,7 +1849,7 @@ msgid ""
msgstr ""
#: includes/class-wc-subscriptions-coupon.php:499
-msgid "Sorry, only recurring coupons can only be applied to subscriptions."
+msgid "Sorry, only recurring coupons can be applied to subscriptions."
msgstr ""
#: includes/class-wc-subscriptions-coupon.php:681
@@ -1860,40 +1864,48 @@ msgstr ""
msgid "Renewal cart discount"
msgstr ""
-#: includes/class-wc-subscriptions-coupon.php:700
+#: includes/class-wc-subscriptions-coupon.php:684
+msgid "Initial payment discount"
+msgstr ""
+
+#: includes/class-wc-subscriptions-coupon.php:701
msgid "Renewal Discount"
msgstr ""
-#: includes/class-wc-subscriptions-coupon.php:915
+#: includes/class-wc-subscriptions-coupon.php:704
+msgid "Discount"
+msgstr ""
+
+#: includes/class-wc-subscriptions-coupon.php:920
msgid ""
"Sorry, it seems there are no available payment methods which support the "
"recurring coupon you are using. Please contact us if you require assistance "
"or wish to make alternate arrangements."
msgstr ""
-#: includes/class-wc-subscriptions-coupon.php:930
+#: includes/class-wc-subscriptions-coupon.php:935
msgid "Active for x payments"
msgstr ""
-#: includes/class-wc-subscriptions-coupon.php:931
+#: includes/class-wc-subscriptions-coupon.php:936
msgid "Unlimited payments"
msgstr ""
-#: includes/class-wc-subscriptions-coupon.php:932
+#: includes/class-wc-subscriptions-coupon.php:937
msgid ""
"Coupon will be limited to the given number of payments. It will then be "
"automatically removed from the subscription. \"Payments\" also includes the "
"initial subscription payment."
msgstr ""
-#: includes/class-wc-subscriptions-coupon.php:1064
+#: includes/class-wc-subscriptions-coupon.php:1069
#. translators: %d refers to the number of payments the coupon can be used for.
msgid "Active for %d payment"
msgid_plural "Active for %d payments"
msgstr[0] ""
msgstr[1] ""
-#: includes/class-wc-subscriptions-coupon.php:1068
+#: includes/class-wc-subscriptions-coupon.php:1073
msgid "Active for unlimited payments"
msgstr ""
@@ -1902,7 +1914,7 @@ msgid "Error: Unable to create renewal order with note \"%s\""
msgstr ""
#: includes/class-wc-subscriptions-manager.php:168
-#: includes/gateways/class-wc-subscriptions-payment-gateways.php:210
+#: includes/gateways/class-wc-subscriptions-payment-gateways.php:209
msgid "Subscription doesn't exist in scheduled action: %d"
msgstr ""
@@ -2094,7 +2106,7 @@ msgstr ""
#. translators: 1$: recurring amount, 2$: subscription period (e.g. "month" or
#. "3 months") (e.g. "$15 / month" or "$15 every 2nd month")
msgid "%1$s / %2$s"
-msgid_plural " %1$s every %2$s"
+msgid_plural "%1$s every %2$s"
msgstr[0] ""
msgstr[1] ""
@@ -2242,7 +2254,7 @@ msgstr ""
#: includes/class-wc-subscriptions-switcher.php:421
#: includes/class-wc-subscriptions-switcher.php:447
-#: includes/class-wc-subscriptions-switcher.php:2446
+#: includes/class-wc-subscriptions-switcher.php:2363
msgid "Upgrade or Downgrade"
msgstr ""
@@ -2280,19 +2292,20 @@ msgstr ""
msgid "There was an error locating the switch details."
msgstr ""
-#: includes/class-wc-subscriptions-switcher.php:1896
-#: includes/class-wc-subscriptions-switcher.php:2240
+#: includes/class-wc-subscriptions-switcher.php:1413
+msgid ""
+"Invalid switch type \"%s\". Switch must be one of: \"upgrade\", "
+"\"downgrade\" or \"crossgrade\"."
+msgstr ""
+
+#: includes/class-wc-subscriptions-switcher.php:1907
msgid "The original subscription item being switched cannot be found."
msgstr ""
-#: includes/class-wc-subscriptions-switcher.php:1898
+#: includes/class-wc-subscriptions-switcher.php:1909
msgid "The item on the switch order cannot be found."
msgstr ""
-#: includes/class-wc-subscriptions-switcher.php:2277
-msgid "Failed to update the subscription shipping method."
-msgstr ""
-
#: includes/class-wc-subscriptions-synchroniser.php:48
msgid "Synchronise renewals"
msgstr ""
@@ -2381,55 +2394,55 @@ msgstr ""
msgid "View and manage subscriptions"
msgstr ""
-#: includes/class-wcs-cached-data-manager.php:76
+#: includes/class-wcs-cached-data-manager.php:79
msgid "Related order caching is now handled by %1$s."
msgstr ""
-#: includes/class-wcs-cached-data-manager.php:83
+#: includes/class-wcs-cached-data-manager.php:86
msgid "Customer subscription caching is now handled by %1$s."
msgstr ""
-#: includes/class-wcs-cached-data-manager.php:107
-#: includes/class-wcs-cached-data-manager.php:237
+#: includes/class-wcs-cached-data-manager.php:110
+#: includes/class-wcs-cached-data-manager.php:240
msgid "Customer subscription caching is now handled by %1$s and %2$s."
msgstr ""
-#: includes/class-wcs-cached-data-manager.php:124
+#: includes/class-wcs-cached-data-manager.php:127
msgid "new related order methods in WCS_Related_Order_Store"
msgstr ""
-#: includes/class-wcs-cached-data-manager.php:222
+#: includes/class-wcs-cached-data-manager.php:225
msgid "Weekly"
msgstr ""
#: includes/class-wcs-cart-initial-payment.php:59
-#: includes/class-wcs-cart-renewal.php:182
+#: includes/class-wcs-cart-renewal.php:188
msgid "That doesn't appear to be your order."
msgstr ""
-#: includes/class-wcs-cart-renewal.php:198
+#: includes/class-wcs-cart-renewal.php:204
msgid ""
"This order can no longer be paid because the corresponding subscription "
"does not require payment at this time."
msgstr ""
-#: includes/class-wcs-cart-renewal.php:215
+#: includes/class-wcs-cart-renewal.php:221
msgid "Complete checkout to renew your subscription."
msgstr ""
-#: includes/class-wcs-cart-renewal.php:296
+#: includes/class-wcs-cart-renewal.php:302
#. translators: placeholder is an item name
msgid ""
"The %s product has been deleted and can no longer be renewed. Please choose "
"a new product or contact us for assistance."
msgstr ""
-#: includes/class-wcs-cart-renewal.php:330
+#: includes/class-wcs-cart-renewal.php:336
#. translators: %s is subscription's number
msgid "Subscription #%s has not been added to the cart."
msgstr ""
-#: includes/class-wcs-cart-renewal.php:459
+#: includes/class-wcs-cart-renewal.php:371
msgid ""
"We couldn't find the original subscription for an item in your cart. The "
"item was removed."
@@ -2439,7 +2452,7 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: includes/class-wcs-cart-renewal.php:466
+#: includes/class-wcs-cart-renewal.php:378
msgid ""
"We couldn't find the original renewal order for an item in your cart. The "
"item was removed."
@@ -2449,7 +2462,7 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: includes/class-wcs-cart-renewal.php:741
+#: includes/class-wcs-cart-renewal.php:653
msgid "All linked subscription items have been removed from the cart."
msgstr ""
@@ -2458,7 +2471,7 @@ msgid "There was an error with your request to resubscribe. Please try again."
msgstr ""
#: includes/class-wcs-cart-resubscribe.php:74
-#: includes/early-renewal/class-wcs-cart-early-renewal.php:92
+#: includes/early-renewal/class-wcs-cart-early-renewal.php:90
msgid "That subscription does not exist. Has it been deleted?"
msgstr ""
@@ -2481,33 +2494,14 @@ msgstr ""
msgid "Please choose a valid payment gateway to change to."
msgstr ""
-#: includes/class-wcs-failed-scheduled-action-manager.php:128
-#. translators: $1: Opening previously translated sentence $2,$5,$9 opening
-#. link tags $3 closing link tag $4 opening paragraph tag $6 closing paragraph
-#. tag $7 list of affected actions wrapped in code tags $8 the log file name
-#. $10 div containing a group of buttons/links
-msgid ""
-"%1$s Please %2$sopen a new ticket at WooCommerce Support%3$s immediately to "
-"get this resolved.%4$sTo resolve this error a quickly as possible, please "
-"include login details for a %5$stemporary administrator "
-"account%3$s.%6$sAffected events: %7$s%4$sTo see further details, view the "
-"%8$s log file from the %9$sWooCommerce logs screen.%3$s%6$s%10$s"
+#: includes/class-wcs-failed-scheduled-action-manager.php:134
+#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:178
+msgid "Ignore this error (not recommended)"
msgstr ""
-#: includes/class-wcs-failed-scheduled-action-manager.php:129
-msgid "An error has occurred while processing a recent subscription related event."
-msgid_plural "An error has occurred while processing recent subscription related events."
-msgstr[0] ""
-msgstr[1] ""
-
-#: includes/class-wcs-failed-scheduled-action-manager.php:138
-#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:190
-msgid "Ignore this error (not recommended!)"
-msgstr ""
-
-#: includes/class-wcs-failed-scheduled-action-manager.php:138
-#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:190
-msgid "Open up a ticket now!"
+#: includes/class-wcs-failed-scheduled-action-manager.php:139
+#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:183
+msgid "Open a ticket"
msgstr ""
#: includes/class-wcs-limiter.php:45
@@ -2616,6 +2610,12 @@ msgid ""
"not support removing an item."
msgstr ""
+#: includes/class-wcs-retry-manager.php:316
+msgid ""
+"Payment retry attempted on renewal order with multiple related "
+"subscriptions with no payment method in common."
+msgstr ""
+
#: includes/class-wcs-staging.php:37
msgid ""
"Payment processing skipped - renewal order created on %sstaging site%s "
@@ -2713,15 +2713,11 @@ msgid ""
"related order queries are run."
msgstr ""
-#: includes/early-renewal/class-wcs-cart-early-renewal.php:66
+#: includes/early-renewal/class-wcs-cart-early-renewal.php:70
msgid "Renew Now"
msgstr ""
-#: includes/early-renewal/class-wcs-cart-early-renewal.php:88
-msgid "There was an error with your request to renew. Please try again."
-msgstr ""
-
-#: includes/early-renewal/class-wcs-cart-early-renewal.php:100
+#: includes/early-renewal/class-wcs-cart-early-renewal.php:98
msgid ""
"You can not renew this subscription early. Please contact us if you need "
"assistance."
@@ -2731,23 +2727,23 @@ msgstr ""
msgid "Complete checkout to renew now."
msgstr ""
-#: includes/early-renewal/class-wcs-cart-early-renewal.php:224
+#: includes/early-renewal/class-wcs-cart-early-renewal.php:226
#. translators: placeholder contains a link to the order's edit screen.
msgid "Customer successfully renewed early with order %s."
msgstr ""
-#: includes/early-renewal/class-wcs-cart-early-renewal.php:227
+#: includes/early-renewal/class-wcs-cart-early-renewal.php:229
#. translators: placeholder contains a link to the order's edit screen.
msgid ""
"Failed to update subscription dates after customer renewed early with order "
"%s."
msgstr ""
-#: includes/early-renewal/class-wcs-cart-early-renewal.php:315
+#: includes/early-renewal/class-wcs-cart-early-renewal.php:317
msgid "Order %s created to record early renewal."
msgstr ""
-#: includes/early-renewal/class-wcs-cart-early-renewal.php:370
+#: includes/early-renewal/class-wcs-cart-early-renewal.php:372
msgid "Cancel"
msgstr ""
@@ -2766,92 +2762,92 @@ msgid ""
"the next payment date."
msgstr ""
-#: includes/emails/class-wcs-email-cancelled-subscription.php:27
+#: includes/emails/class-wcs-email-cancelled-subscription.php:26
msgid "Cancelled Subscription"
msgstr ""
-#: includes/emails/class-wcs-email-cancelled-subscription.php:28
+#: includes/emails/class-wcs-email-cancelled-subscription.php:27
msgid ""
"Cancelled Subscription emails are sent when a customer's subscription is "
"cancelled (either by a store manager, or the customer)."
msgstr ""
-#: includes/emails/class-wcs-email-cancelled-subscription.php:30
+#: includes/emails/class-wcs-email-cancelled-subscription.php:29
msgid "Subscription Cancelled"
msgstr ""
-#: includes/emails/class-wcs-email-cancelled-subscription.php:128
+#: includes/emails/class-wcs-email-cancelled-subscription.php:127
#: includes/emails/class-wcs-email-customer-renewal-invoice.php:192
-#: includes/emails/class-wcs-email-expired-subscription.php:126
-#: includes/emails/class-wcs-email-on-hold-subscription.php:126
+#: includes/emails/class-wcs-email-expired-subscription.php:125
+#: includes/emails/class-wcs-email-on-hold-subscription.php:125
msgid "Enable this email notification"
msgstr ""
-#: includes/emails/class-wcs-email-cancelled-subscription.php:135
-#: includes/emails/class-wcs-email-expired-subscription.php:133
-#: includes/emails/class-wcs-email-on-hold-subscription.php:133
+#: includes/emails/class-wcs-email-cancelled-subscription.php:134
+#: includes/emails/class-wcs-email-expired-subscription.php:132
+#: includes/emails/class-wcs-email-on-hold-subscription.php:132
#. translators: placeholder is admin email
msgid ""
"Enter recipients (comma separated) for this email. Defaults to "
"%s
."
msgstr ""
-#: includes/emails/class-wcs-email-cancelled-subscription.php:142
-#: includes/emails/class-wcs-email-expired-subscription.php:140
-#: includes/emails/class-wcs-email-on-hold-subscription.php:140
+#: includes/emails/class-wcs-email-cancelled-subscription.php:141
+#: includes/emails/class-wcs-email-expired-subscription.php:139
+#: includes/emails/class-wcs-email-on-hold-subscription.php:139
msgid ""
"This controls the email subject line. Leave blank to use the default "
"subject: %s
."
msgstr ""
-#: includes/emails/class-wcs-email-cancelled-subscription.php:149
-#: includes/emails/class-wcs-email-expired-subscription.php:147
-#: includes/emails/class-wcs-email-on-hold-subscription.php:147
+#: includes/emails/class-wcs-email-cancelled-subscription.php:148
+#: includes/emails/class-wcs-email-expired-subscription.php:146
+#: includes/emails/class-wcs-email-on-hold-subscription.php:146
msgid ""
"This controls the main heading contained within the email notification. "
"Leave blank to use the default heading: %s
."
msgstr ""
-#: includes/emails/class-wcs-email-cancelled-subscription.php:156
-#: includes/emails/class-wcs-email-expired-subscription.php:154
-#: includes/emails/class-wcs-email-on-hold-subscription.php:154
+#: includes/emails/class-wcs-email-cancelled-subscription.php:155
+#: includes/emails/class-wcs-email-expired-subscription.php:153
+#: includes/emails/class-wcs-email-on-hold-subscription.php:153
msgid "Choose which format of email to send."
msgstr ""
-#: includes/emails/class-wcs-email-customer-completed-renewal-order.php:25
+#: includes/emails/class-wcs-email-completed-renewal-order.php:25
msgid "Completed Renewal Order"
msgstr ""
-#: includes/emails/class-wcs-email-customer-completed-renewal-order.php:26
+#: includes/emails/class-wcs-email-completed-renewal-order.php:26
msgid ""
"Renewal order complete emails are sent to the customer when a subscription "
"renewal order is marked complete and usually indicates that the item for "
"that renewal period has been shipped."
msgstr ""
-#: includes/emails/class-wcs-email-customer-completed-switch-order.php:26
+#: includes/emails/class-wcs-email-completed-switch-order.php:26
msgid "Subscription Switch Complete"
msgstr ""
-#: includes/emails/class-wcs-email-customer-completed-switch-order.php:27
+#: includes/emails/class-wcs-email-completed-switch-order.php:27
msgid ""
"Subscription switch complete emails are sent to the customer when a "
"subscription is switched successfully."
msgstr ""
-#: includes/emails/class-wcs-email-customer-completed-switch-order.php:30
+#: includes/emails/class-wcs-email-completed-switch-order.php:30
msgid "Your subscription change is complete"
msgstr ""
-#: includes/emails/class-wcs-email-customer-completed-switch-order.php:31
+#: includes/emails/class-wcs-email-completed-switch-order.php:31
msgid "Your {blogname} subscription change from {order_date} is complete"
msgstr ""
-#: includes/emails/class-wcs-email-customer-completed-switch-order.php:38
+#: includes/emails/class-wcs-email-completed-switch-order.php:38
msgid "Your subscription change is complete - download your files"
msgstr ""
-#: includes/emails/class-wcs-email-customer-completed-switch-order.php:39
+#: includes/emails/class-wcs-email-completed-switch-order.php:39
msgid ""
"Your {blogname} subscription change from {order_date} is complete - "
"download your files"
@@ -2878,25 +2874,6 @@ msgstr ""
msgid "Automatic payment failed for order {order_number}"
msgstr ""
-#: includes/emails/class-wcs-email-customer-processing-renewal-order.php:24
-msgid "Processing Renewal order"
-msgstr ""
-
-#: includes/emails/class-wcs-email-customer-processing-renewal-order.php:25
-msgid ""
-"This is an order notification sent to the customer after payment for a "
-"subscription renewal order is completed. It contains the renewal order "
-"details."
-msgstr ""
-
-#: includes/emails/class-wcs-email-customer-processing-renewal-order.php:28
-msgid "Thank you for your order"
-msgstr ""
-
-#: includes/emails/class-wcs-email-customer-processing-renewal-order.php:29
-msgid "Your {blogname} renewal order receipt from {order_date}"
-msgstr ""
-
#: includes/emails/class-wcs-email-customer-renewal-invoice.php:40
msgid "Customer Renewal Invoice"
msgstr ""
@@ -2918,20 +2895,20 @@ msgstr ""
msgid "Invoice for renewal order {order_number}"
msgstr ""
-#: includes/emails/class-wcs-email-expired-subscription.php:27
+#: includes/emails/class-wcs-email-expired-subscription.php:26
msgid "Expired Subscription"
msgstr ""
-#: includes/emails/class-wcs-email-expired-subscription.php:28
+#: includes/emails/class-wcs-email-expired-subscription.php:27
msgid "Expired Subscription emails are sent when a customer's subscription expires."
msgstr ""
-#: includes/emails/class-wcs-email-expired-subscription.php:30
+#: includes/emails/class-wcs-email-expired-subscription.php:29
msgid "Subscription Expired"
msgstr ""
-#: includes/emails/class-wcs-email-expired-subscription.php:59
-#: includes/emails/class-wcs-email-on-hold-subscription.php:59
+#: includes/emails/class-wcs-email-expired-subscription.php:58
+#: includes/emails/class-wcs-email-on-hold-subscription.php:58
msgid "Subscription argument passed in is not an object."
msgstr ""
@@ -2968,17 +2945,17 @@ msgstr ""
msgid "[{blogname}] Subscription Switched ({order_number}) - {order_date}"
msgstr ""
-#: includes/emails/class-wcs-email-on-hold-subscription.php:27
+#: includes/emails/class-wcs-email-on-hold-subscription.php:26
msgid "Suspended Subscription"
msgstr ""
-#: includes/emails/class-wcs-email-on-hold-subscription.php:28
+#: includes/emails/class-wcs-email-on-hold-subscription.php:27
msgid ""
"Suspended Subscription emails are sent when a customer manually suspends "
"their subscription."
msgstr ""
-#: includes/emails/class-wcs-email-on-hold-subscription.php:30
+#: includes/emails/class-wcs-email-on-hold-subscription.php:29
msgid "Subscription Suspended"
msgstr ""
@@ -3003,42 +2980,61 @@ msgid ""
"to run {retry_time}"
msgstr ""
-#: includes/gateways/class-wc-subscriptions-payment-gateways.php:132
+#: includes/emails/class-wcs-email-processing-renewal-order.php:24
+msgid "Processing Renewal order"
+msgstr ""
+
+#: includes/emails/class-wcs-email-processing-renewal-order.php:25
+msgid ""
+"This is an order notification sent to the customer after payment for a "
+"subscription renewal order is completed. It contains the renewal order "
+"details."
+msgstr ""
+
+#: includes/emails/class-wcs-email-processing-renewal-order.php:28
+msgid "Thank you for your order"
+msgstr ""
+
+#: includes/emails/class-wcs-email-processing-renewal-order.php:29
+msgid "Your {blogname} renewal order receipt from {order_date}"
+msgstr ""
+
+#: includes/gateways/class-wc-subscriptions-payment-gateways.php:131
msgid ""
"Sorry, it seems there are no available payment methods which support "
"subscriptions. Please contact us if you require assistance or wish to make "
"alternate arrangements."
msgstr ""
-#: includes/gateways/paypal/class-wcs-paypal.php:210
+#: includes/gateways/paypal/class-wcs-paypal.php:199
msgid "Unable to find order for PayPal billing agreement."
msgstr ""
-#: includes/gateways/paypal/class-wcs-paypal.php:267
+#: includes/gateways/paypal/class-wcs-paypal.php:256
msgid "An error occurred, please try again or try an alternate form of payment."
msgstr ""
-#: includes/gateways/paypal/class-wcs-paypal.php:374
+#: includes/gateways/paypal/class-wcs-paypal.php:360
#. translators: placeholders are PayPal API error code and PayPal API error
#. message
msgid "PayPal API error: (%d) %s"
msgstr ""
-#: includes/gateways/paypal/class-wcs-paypal.php:379
+#: includes/gateways/paypal/class-wcs-paypal.php:365
#. translators: placeholder is PayPal transaction status message
msgid "PayPal Transaction Held: %s"
msgstr ""
-#: includes/gateways/paypal/class-wcs-paypal.php:391
+#: includes/gateways/paypal/class-wcs-paypal.php:377
#. translators: placeholder is PayPal transaction status message
msgid "PayPal payment declined: %s"
msgstr ""
-#: includes/gateways/paypal/class-wcs-paypal.php:395
+#: includes/gateways/paypal/class-wcs-paypal.php:381
msgid "PayPal payment approved (ID: %s)"
msgstr ""
-#: includes/gateways/paypal/class-wcs-paypal.php:448
+#: includes/gateways/paypal/class-wcs-paypal.php:434
msgid ""
"Are you sure you want to change the payment method from PayPal standard?\n"
"\n"
@@ -3052,7 +3048,7 @@ msgid ""
"break existing subscriptions."
msgstr ""
-#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:110
+#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:105
#. translators: placeholders are opening and closing link tags. 1$-2$: to docs
#. on woocommerce, 3$-4$ to gateway settings on the site
msgid ""
@@ -3061,7 +3057,7 @@ msgid ""
"Subscriptions."
msgstr ""
-#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:123
+#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:116
#. translators: placeholders are opening and closing strong and link tags.
#. 1$-2$: strong tags, 3$-8$ link to docs on woocommerce
msgid ""
@@ -3071,14 +3067,14 @@ msgid ""
"%5$sCheck PayPal Account%6$s %3$sLearn more %7$s"
msgstr ""
-#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:140
+#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:132
#. translators: placeholders are opening and closing strong tags.
msgid ""
"%1$sPayPal Reference Transactions are enabled on your account%2$s. All "
"subscription management features are now enabled. Happy selling!"
msgstr ""
-#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:151
+#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:143
#. translators: placeholders are link opening and closing tags. 1$-2$: to
#. gateway settings, 3$-4$: support docs on woocommerce.com
msgid ""
@@ -3086,7 +3082,7 @@ msgid ""
"Please update your %1$sAPI credentials%2$s. %3$sLearn more%4$s."
msgstr ""
-#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:164
+#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:156
#. translators: placeholders are opening and closing link tags. 1$-2$: docs on
#. woocommerce, 3$-4$: dismiss link
msgid ""
@@ -3094,17 +3090,7 @@ msgid ""
"subscription IDs. %1$sLearn more%2$s. %3$sDismiss%4$s."
msgstr ""
-#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:179
-msgid ""
-"%sA fatal error has occurred when processing a recent subscription payment "
-"with PayPal. Please %sopen a new ticket at WooCommerce Support%s "
-"immediately to get this resolved.%sIn order to get the quickest possible "
-"response please attach a %sTemporary Admin Login%s and a copy of your PHP "
-"error logs to your support ticket.%sLast recorded error: %sTo see the full "
-"error, view the %s log file from the %sWooCommerce logs screen.%s"
-msgstr ""
-
-#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:269
+#: includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php:265
msgid "PayPal Subscription ID:"
msgstr ""
@@ -3134,28 +3120,28 @@ msgstr ""
msgid "IPN subscription sign up completed."
msgstr ""
-#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:321
-#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:398
+#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:332
+#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:415
msgid "IPN subscription payment completed."
msgstr ""
-#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:360
+#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:377
msgid "IPN subscription failing payment method changed."
msgstr ""
-#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:450
+#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:467
msgid "IPN subscription suspended."
msgstr ""
-#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:473
+#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:490
msgid "IPN subscription cancelled."
msgstr ""
-#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:489
+#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:506
msgid "IPN subscription payment failure."
msgstr ""
-#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:627
+#: includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:644
msgid "Invalid PayPal IPN Payload: unable to find matching subscription."
msgstr ""
@@ -3175,7 +3161,34 @@ msgstr ""
msgid "PayPal API error - credentials are incorrect."
msgstr ""
-#: includes/payment-retry/class-wcs-retry-admin.php:48
+#: includes/gateways/paypal/includes/templates/html-ipn-failure-notice.php:15
+#. translators: $1 and $2 are opening link tags, $3 is a closing link tag.
+msgid ""
+"A fatal error has occurred while processing a recent subscription payment "
+"with PayPal. Please %1$sopen a new ticket at WooCommerce Support%3$s "
+"immediately to get this resolved. %2$sLearn more »%3$s"
+msgstr ""
+
+#: includes/gateways/paypal/includes/templates/html-ipn-failure-notice.php:24
+#. translators: $1 and $2 are opening and closing link tags, respectively.
+msgid ""
+"To resolve this as quickly as possible, please create a %1$stemporary "
+"administrator account%2$s with the user email support@prospress.com."
+msgstr ""
+
+#: includes/gateways/paypal/includes/templates/html-ipn-failure-notice.php:29
+msgid "Last recorded error:"
+msgstr ""
+
+#: includes/gateways/paypal/includes/templates/html-ipn-failure-notice.php:36
+#. translators: $1 is the log file name. $2 and $3 are opening and closing link
+#. tags, respectively.
+msgid ""
+"To see the full error, view the %1$s log file from the %2$sWooCommerce logs "
+"screen.%3$s."
+msgstr ""
+
+#: includes/payment-retry/class-wcs-retry-admin.php:46
msgid "Automatic Failed Payment Retries"
msgstr ""
@@ -3209,15 +3222,15 @@ msgid_plural "%d Cancelled Payment Retries"
msgstr[0] ""
msgstr[1] ""
-#: includes/payment-retry/class-wcs-retry-admin.php:135
+#: includes/payment-retry/class-wcs-retry-admin.php:139
msgid "Retry Failed Payments"
msgstr ""
-#: includes/payment-retry/class-wcs-retry-admin.php:136
+#: includes/payment-retry/class-wcs-retry-admin.php:140
msgid "Enable automatic retry of failed recurring payments"
msgstr ""
-#: includes/payment-retry/class-wcs-retry-admin.php:140
+#: includes/payment-retry/class-wcs-retry-admin.php:144
msgid ""
"Attempt to recover recurring revenue that would otherwise be lost due to "
"payment methods being declined only temporarily. %sLearn more%s."
@@ -3306,11 +3319,11 @@ msgstr ""
msgid "Browser User Agent"
msgstr ""
-#: includes/privacy/class-wcs-privacy-exporters.php:83 wcs-functions.php:274
+#: includes/privacy/class-wcs-privacy-exporters.php:83 wcs-functions.php:282
msgid "Billing Address"
msgstr ""
-#: includes/privacy/class-wcs-privacy-exporters.php:84 wcs-functions.php:273
+#: includes/privacy/class-wcs-privacy-exporters.php:84 wcs-functions.php:281
msgid "Shipping Address"
msgstr ""
@@ -3326,12 +3339,12 @@ msgstr ""
msgid "WooCommerce Subscriptions"
msgstr ""
-#: includes/privacy/class-wcs-privacy.php:46
-#: includes/privacy/class-wcs-privacy.php:47
+#: includes/privacy/class-wcs-privacy.php:43
+#: includes/privacy/class-wcs-privacy.php:44
msgid "Subscriptions Data"
msgstr ""
-#: includes/privacy/class-wcs-privacy.php:95
+#: includes/privacy/class-wcs-privacy.php:92
msgid ""
"By using WooCommerce Subscriptions, you may be storing personal data and "
"depending on which third-party payment processors you’re using to take "
@@ -3339,24 +3352,24 @@ msgid ""
"sources."
msgstr ""
-#: includes/privacy/class-wcs-privacy.php:97
+#: includes/privacy/class-wcs-privacy.php:94
#. translators: placeholders are opening and closing link tags, linking to
#. additional privacy policy documentation.
msgid "What we collect and store"
msgstr ""
-#: includes/privacy/class-wcs-privacy.php:98
+#: includes/privacy/class-wcs-privacy.php:95
msgid ""
"For the purposes of processing recurring subscription payments, we store "
"the customer's name, billing address, shipping address, email address, "
"phone number and credit card/payment details."
msgstr ""
-#: includes/privacy/class-wcs-privacy.php:99
+#: includes/privacy/class-wcs-privacy.php:96
msgid "What we share with others"
msgstr ""
-#: includes/privacy/class-wcs-privacy.php:100
+#: includes/privacy/class-wcs-privacy.php:97
msgid ""
"What personal information your store shares with external sources depends "
"on which third-party payment processor plugins you are using to collect "
@@ -3364,7 +3377,7 @@ msgid ""
"policies to inform this section of your privacy policy."
msgstr ""
-#: includes/privacy/class-wcs-privacy.php:102
+#: includes/privacy/class-wcs-privacy.php:99
#. translators: placeholders are opening and closing link tags, linking to
#. additional privacy policy documentation.
msgid ""
@@ -3372,60 +3385,63 @@ msgid ""
"see the %sPayPal Privacy Policy%s for more details."
msgstr ""
-#: includes/privacy/class-wcs-privacy.php:112
+#: includes/privacy/class-wcs-privacy.php:109
msgid "Cancel and remove personal data"
msgstr ""
-#: includes/privacy/class-wcs-privacy.php:178
+#: includes/privacy/class-wcs-privacy.php:175
msgid "Removed personal data from %d subscription."
msgid_plural "Removed personal data from %d subscriptions."
msgstr[0] ""
msgstr[1] ""
-#: includes/privacy/class-wcs-privacy.php:197
+#: includes/privacy/class-wcs-privacy.php:194
#. translators: placeholders are opening and closing tags.
msgid ""
"%sNote:%s Orders which are related to subscriptions will not be included in "
"the orders affected by these settings."
msgstr ""
-#: includes/privacy/class-wcs-privacy.php:218
-msgid "Remove personal data from subscriptions"
+#: includes/privacy/class-wcs-privacy.php:214
+msgid "account erasure request"
msgstr ""
#: includes/privacy/class-wcs-privacy.php:220
-#. Translators: placeholders are opening and closing link tags linking to the
-#. erasure request screen.
-msgid ""
-"When handling an %saccount erasure request%s, should personal data within "
-"subscriptions be retained or removed?"
+msgid "Remove personal data from subscriptions"
msgstr ""
-#: includes/privacy/class-wcs-privacy.php:229
+#: includes/privacy/class-wcs-privacy.php:222
+#. Translators: %s URL to erasure request screen.
+msgid ""
+"When handling an %s, should personal data within subscriptions be retained "
+"or removed?"
+msgstr ""
+
+#: includes/privacy/class-wcs-privacy.php:231
msgid "Retain ended subscriptions"
msgstr ""
-#: includes/privacy/class-wcs-privacy.php:230
+#: includes/privacy/class-wcs-privacy.php:232
msgid ""
"Retain ended subscriptions and their related orders for a specified "
"duration before anonymizing the personal data within them."
msgstr ""
-#: includes/privacy/class-wcs-privacy.php:233
+#: includes/privacy/class-wcs-privacy.php:235
msgid "N/A"
msgstr ""
-#: includes/privacy/class-wcs-privacy.php:274
+#: includes/privacy/class-wcs-privacy.php:276
msgid "Customers with a subscription are excluded from this setting."
msgstr ""
-#: includes/upgrades/class-wc-subscriptions-upgrader.php:323
+#: includes/upgrades/class-wc-subscriptions-upgrader.php:330
#. 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:346
+#: includes/upgrades/class-wc-subscriptions-upgrader.php:347
#. translators: 1$: number of action scheduler hooks upgraded, 2$:
#. "{execution_time}", will be replaced on front end with actual time
msgid ""
@@ -3433,36 +3449,36 @@ msgid ""
"seconds)."
msgstr ""
-#: includes/upgrades/class-wc-subscriptions-upgrader.php:362
+#: includes/upgrades/class-wc-subscriptions-upgrader.php:359
#. 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:375
+#: includes/upgrades/class-wc-subscriptions-upgrader.php:372
#. translators: 1$: error message, 2$: opening link tag, 3$: closing link tag
msgid ""
"Unable to upgrade subscriptions.", 4$: "", 5$: "", 6$: "", 7$: "", 8$: #. "
" @@ -4867,7 +4919,7 @@ msgid "" "%6$sVariable subscription%7$s.%8$s" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:788 +#: includes/admin/class-wc-subscriptions-admin.php:792 #. translators: placeholders are for HTML tags. They are 1$: "", 4$: "
" msgctxt "used in admin pointer script params in javascript as price pointer content" @@ -4877,17 +4929,17 @@ msgid "" "sign-up fee and free trial.%4$s" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1125 +#: includes/admin/class-wc-subscriptions-admin.php:1124 msgctxt "option section heading" msgid "Renewals" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1157 +#: includes/admin/class-wc-subscriptions-admin.php:1156 msgctxt "options section heading" msgid "Miscellaneous" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1165 +#: includes/admin/class-wc-subscriptions-admin.php:1164 msgctxt "there's a number immediately in front of this text" msgid "suspensions per billing period." msgstr "" @@ -4897,26 +4949,26 @@ msgctxt "there's a number immediately in front of this text" msgid "days prior to Renewal Day" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1446 -#: includes/admin/class-wcs-admin-system-status.php:92 +#: includes/admin/class-wc-subscriptions-admin.php:1445 +#: includes/admin/class-wcs-admin-system-status.php:93 msgctxt "label that indicates whether debugging is turned on for the plugin" msgid "WCS_DEBUG" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1452 -#: includes/admin/class-wcs-admin-system-status.php:104 +#: includes/admin/class-wc-subscriptions-admin.php:1451 +#: includes/admin/class-wcs-admin-system-status.php:107 msgctxt "Live or Staging, Label on WooCommerce -> System Status page" msgid "Subscriptions Mode" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1453 -#: includes/admin/class-wcs-admin-system-status.php:106 +#: includes/admin/class-wc-subscriptions-admin.php:1452 +#: includes/admin/class-wcs-admin-system-status.php:109 msgctxt "refers to staging site" msgid "Staging" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1453 -#: includes/admin/class-wcs-admin-system-status.php:106 +#: includes/admin/class-wc-subscriptions-admin.php:1452 +#: includes/admin/class-wcs-admin-system-status.php:109 msgctxt "refers to live site" msgid "Live" msgstr "" @@ -4972,72 +5024,82 @@ 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:878 +#: includes/admin/class-wcs-admin-post-types.php:889 #. 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:883 +#: includes/admin/class-wcs-admin-post-types.php:894 msgctxt "used in \"Subscription scheduled for', + '' + );?> +
+', + '' + );?> +
+ + array( 'href' => array() ) ) ); ?>
+
+failed-scheduled-actions', + '', + '' + );?> +
+ diff --git a/templates/admin/html-variation-price.php b/templates/admin/html-variation-price.php old mode 100755 new mode 100644 index a860283..02f8268 --- a/templates/admin/html-variation-price.php +++ b/templates/admin/html-variation-price.php @@ -28,7 +28,7 @@ if ( ! defined( 'ABSPATH' ) ) { - +