This commit is contained in:
Prospress Inc
2018-12-12 14:48:11 +01:00
committed by Remco Tolsma
parent 37367ac369
commit 721dda6e5c
340 changed files with 5114 additions and 2125 deletions

0
assets/css/about.css Executable file → Normal file
View File

0
assets/css/admin.css Executable file → Normal file
View File

0
assets/css/checkout.css Executable file → Normal file
View File

0
assets/css/dashboard.css Executable file → Normal file
View File

0
assets/css/view-subscription.css Executable file → Normal file
View File

0
assets/css/wcs-upgrade.css Executable file → Normal file
View File

0
assets/images/add-edit-subscription-screen.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

0
assets/images/admin-change-payment-method.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

0
assets/images/ajax-loader.gif Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 885 B

After

Width:  |  Height:  |  Size: 885 B

0
assets/images/ajax-loader@2x.gif Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

0
assets/images/billing-schedules-meta-box.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

0
assets/images/checkout-recurring-totals.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

0
assets/images/drip-downloadable-content.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

0
assets/images/gift-subscription.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

0
assets/images/renewal-retry-settings.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

0
assets/images/subscribe-all-the-things.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

0
assets/images/subscription-reports.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

0
assets/images/subscription-suspended-email.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

0
assets/images/subscriptions-importer-exporter.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

0
assets/images/view-subscription.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 250 KiB

0
assets/images/woocommerce_subscriptions_logo.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

0
assets/js/admin/admin-pointers.js Executable file → Normal file
View File

2
assets/js/admin/admin.js Executable file → Normal file
View File

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

0
assets/js/admin/jquery.flot.axislabels.js Executable file → Normal file
View File

0
assets/js/admin/jquery.flot.axislabels.min.js vendored Executable file → Normal file
View File

0
assets/js/admin/jquery.flot.orderBars.js Executable file → Normal file
View File

0
assets/js/admin/jquery.flot.orderBars.min.js vendored Executable file → Normal file
View File

0
assets/js/admin/jstz.js Executable file → Normal file
View File

0
assets/js/admin/jstz.min.js vendored Executable file → Normal file
View File

0
assets/js/admin/meta-boxes-coupon.js Executable file → Normal file
View File

15
assets/js/admin/meta-boxes-subscription.js Executable file → Normal file
View File

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

0
assets/js/admin/moment.js Executable file → Normal file
View File

0
assets/js/admin/moment.min.js vendored Executable file → Normal file
View File

0
assets/js/admin/reports.js Executable file → Normal file
View File

0
assets/js/admin/wcs-meta-boxes-order.js Executable file → Normal file
View File

View File

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

0
assets/js/wcs-upgrade.js Executable file → Normal file
View File

65
changelog.txt Executable file → Normal file
View File

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

0
composer.lock generated Executable file → Normal file
View File

6
includes/abstracts/abstract-wcs-background-updater.php Executable file → Normal file
View File

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

View File

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

0
includes/abstracts/abstract-wcs-cache-manager.php Executable file → Normal file
View File

0
includes/abstracts/abstract-wcs-customer-store.php Executable file → Normal file
View File

View File

0
includes/abstracts/abstract-wcs-debug-tool.php Executable file → Normal file
View File

View File

0
includes/abstracts/abstract-wcs-hook-deprecator.php Executable file → Normal file
View File

View File

@@ -0,0 +1,133 @@
<?php
/**
* Entry migration abstract class.
*
* @author Prospress
* @category Class
* @package WooCommerce Subscriptions
* @since 2.4
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
abstract class WCS_Migrator {
/**
* @var mixed
*/
protected $source_store;
/**
* @var mixed
*/
protected $destination_store;
/**
* @var WC_Logger_Interface
*/
protected $logger;
/**
* @var string
*/
protected $log_handle;
/**
* WCS_Migrator constructor.
*
* @param mixed $source_store Source store.
* @param mixed $destination_store $destination store.
* @param WC_Logger $logger Logger component.
*
* @since 2.4
*/
public function __construct( $source_store, $destination_store, $logger ) {
$this->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 );
}
}

View File

53
includes/abstracts/abstract-wcs-retry-store.php Executable file → Normal file
View File

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

0
includes/abstracts/abstract-wcs-scheduler.php Executable file → Normal file
View File

View File

@@ -0,0 +1,128 @@
<?php
/**
* WCS_Table_Maker Class
*
* Provide APIs for create custom tables.
*
* @author Prospress
* @category Abstract Class
* @package WooCommerce Subscriptions/Abstracts
* @since 2.4
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
} // Exit if accessed directly
abstract class WCS_Table_Maker {
/**
* @var int Increment this value to trigger a schema update
*/
protected $schema_version = 1;
/**
* @var array Names of tables that will be registered by this class
*/
protected $tables = array();
/**
* Register tables with WordPress, and create them if needed
*/
public function register_tables() {
global $wpdb;
// make WP aware of our tables
foreach ( $this->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;
}
}

15
includes/admin/class-wc-subscriptions-admin.php Executable file → Normal file
View File

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

2
includes/admin/class-wcs-admin-meta-boxes.php Executable file → Normal file
View File

@@ -296,5 +296,3 @@ class WCS_Admin_Meta_Boxes {
return $can_be_retried;
}
}
new WCS_Admin_Meta_Boxes();

0
includes/admin/class-wcs-admin-notice.php Executable file → Normal file
View File

25
includes/admin/class-wcs-admin-post-types.php Executable file → Normal file
View File

@@ -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 {
<?php
}
}
new WCS_Admin_Post_Types();

85
includes/admin/class-wcs-admin-reports.php Executable file → Normal file
View File

@@ -23,19 +23,14 @@ class WCS_Admin_Reports {
* Constructor
*/
public function __construct() {
// Add the reports layout to the WooCommerce -> 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' );
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;
}
}

16
includes/admin/class-wcs-admin-system-status.php Executable file → Normal file
View File

@@ -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 );
@@ -98,6 +99,8 @@ class WCS_Admin_System_Status {
/**
* 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(
@@ -108,6 +111,19 @@ class WCS_Admin_System_Status {
);
}
/**
* @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' => '<a href="' . esc_url( WC_Subscriptions::get_site_url_from_source( 'subscriptions_install' ) ) . '">' . esc_html( WC_Subscriptions::get_site_url_from_source( 'subscriptions_install' ) ) . '</a>',
'mark' => '',
'mark_icon' => '',
);
}
/**
* List any Subscriptions template overrides.
*/

View File

View File

View File

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

View File

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

View File

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

View File

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

View File

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

View File

View File

0
includes/admin/meta-boxes/views/html-retries-table.php Executable file → Normal file
View File

View File

View File

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

2
includes/admin/reports/class-wcs-report-dashboard.php Executable file → Normal file
View File

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

View File

@@ -11,7 +11,7 @@
* @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();

View File

@@ -10,7 +10,7 @@
* @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;

View File

@@ -10,7 +10,7 @@
* @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.

View File

@@ -10,7 +10,7 @@
* @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;
}

View File

@@ -10,7 +10,7 @@
* @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();

View File

@@ -11,7 +11,7 @@
* @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 ) )

View File

@@ -0,0 +1,20 @@
<?php
/**
* Subscriptions Admin Report - Retention Rate
*
* 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
* @deprecated in favor of WCS_Report_Retention_Rate
*/
class WC_Report_Retention_Rate extends WCS_Report_Retention_Rate {
public function __construct() {
wcs_deprecated_function( __CLASS__, '2.4.0', get_parent_class( __CLASS__ ) );
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Subscriptions Admin Report - Subscriptions by customer
*
* Creates the subscription admin reports area.
*
* @package WooCommerce Subscriptions
* @subpackage WC_Subscriptions_Admin_Reports
* @category Class
* @author Prospress
* @since 2.1
* @deprecated in favor of WCS_Report_Subscription_By_Customer
*/
class WC_Report_Subscription_By_Customer extends WCS_Report_Subscription_By_Customer {
public function __construct() {
wcs_deprecated_function( __CLASS__, '2.4.0', get_parent_class( __CLASS__ ) );
parent::__construct();
}
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Subscriptions Admin Report - Subscriptions by product
*
* Creates the subscription admin reports area.
*
* @package WooCommerce Subscriptions
* @subpackage WC_Subscriptions_Admin_Reports
* @category Class
* @author Prospress
* @since 2.1
* @deprecated In favor of WCS_Report_Subscription_By_Product
*/
class WC_Report_Subscription_By_Product extends WCS_Report_Subscription_By_Product {
public function __construct() {
wcs_deprecated_function( __CLASS__, '2.4.0', get_parent_class( __CLASS__ ) );
parent::__construct();
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Subscriptions Admin Report - Subscription Events by Date
*
* 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
* @deprecated In favor of WCS_Report_Subscription_Events_By_Date
*/
class WC_Report_Subscription_Events_By_Date extends WCS_Report_Subscription_Events_By_Date {
public function __construct() {
wcs_deprecated_function( __CLASS__, '2.4.0', get_parent_class( __CLASS__ ) );
}
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* Subscriptions Admin Report - Subscription Events by Date
*
* Creates the subscription admin reports area.
*
* @package WooCommerce Subscriptions
* @subpackage WC_Subscriptions_Admin_Reports
* @category Class
* @author Prospress
* @since 2.1
* @deprecated In favor of WCS_Report_Subscription_Payment_Retry
*/
class WC_Report_Subscription_Payment_Retry extends WCS_Report_Subscription_Payment_Retry {
public function __construct() {
wcs_deprecated_function( __CLASS__, '2.4.0', get_parent_class( __CLASS__ ) );
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Subscriptions Admin Report - Upcoming Recurring Revenue
*
* 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
* @deprecated In favor of WCS_Report_Upcoming_Recurring_Revenue
*/
class WC_Report_Upcoming_Recurring_Revenue extends WCS_Report_Upcoming_Recurring_Revenue {
public function __construct() {
wcs_deprecated_function( __CLASS__, '2.4.0', get_parent_class( __CLASS__ ) );
}
}

0
includes/admin/views/html-report-by-period.php Executable file → Normal file
View File

0
includes/admin/wcs-admin-functions.php Executable file → Normal file
View File

View File

36
includes/api/class-wc-rest-subscriptions-controller.php Executable file → Normal file
View File

@@ -90,9 +90,7 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_V1_Controller {
$response->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 );
}

View File

6
includes/api/legacy/class-wc-api-subscriptions.php Executable file → Normal file
View File

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

View File

View File

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

0
includes/class-wc-order-item-pending-switch.php Executable file → Normal file
View File

0
includes/class-wc-product-subscription-variation.php Executable file → Normal file
View File

0
includes/class-wc-product-subscription.php Executable file → Normal file
View File

19
includes/class-wc-product-variable-subscription.php Executable file → Normal file
View File

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

99
includes/class-wc-subscription.php Executable file → Normal file
View File

@@ -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 ) ) {
$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 );
// 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() ) {
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 = $original_order_item->get_total_tax( 'edit' ) * $sign_up_fee_proportion;
$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 = 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
*

45
includes/class-wc-subscriptions-addresses.php Executable file → Normal file
View File

@@ -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 <strong> tag, $3: closing </strong> 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 ), '<strong>', '</strong>' );
$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 ), '<strong>', '</strong>' );
woocommerce_form_field( 'update_all_subscriptions_addresses', array(
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;
}
}

15
includes/class-wc-subscriptions-cart.php Executable file → Normal file
View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More