656 lines
31 KiB
PHP
656 lines
31 KiB
PHP
<?php
|
|
/**
|
|
* Repair subscriptions data to v2.0
|
|
*
|
|
* @author Prospress
|
|
* @category Admin
|
|
* @package WooCommerce Subscriptions/Admin/Upgrades
|
|
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit; // Exit if accessed directly
|
|
}
|
|
|
|
/**
|
|
* @deprecated
|
|
*/
|
|
class WCS_Repair_2_0 {
|
|
|
|
/**
|
|
* Takes care of undefine notices in the upgrade process
|
|
*
|
|
* @param array $order_item item meta
|
|
* @return array repaired item meta
|
|
*/
|
|
public static function maybe_repair_order_item( $order_item ) {
|
|
foreach ( array( 'qty', 'tax_class', 'product_id', 'variation_id', 'recurring_line_subtotal', 'recurring_line_total', 'recurring_line_subtotal_tax', 'recurring_line_tax' ) as $key ) {
|
|
if ( ! array_key_exists( $key, $order_item ) ) {
|
|
$order_item[ $key ] = '';
|
|
}
|
|
}
|
|
|
|
return $order_item;
|
|
}
|
|
|
|
/**
|
|
* Does sanity check on every subscription, and repairs them as needed
|
|
*
|
|
* @param array $subscription subscription data to be upgraded
|
|
* @param integer $item_id id of order item meta
|
|
* @return array a repaired subscription array
|
|
*/
|
|
public static function maybe_repair_subscription( $subscription, $item_id ) {
|
|
global $wpdb;
|
|
|
|
$item_meta = get_metadata( 'order_item', $item_id );
|
|
|
|
foreach ( self::integrity_check( $subscription ) as $function ) {
|
|
$subscription = call_user_func( 'WCS_Repair_2_0::repair_' . $function, $subscription, $item_id, $item_meta );
|
|
}
|
|
|
|
return $subscription;
|
|
}
|
|
|
|
/**
|
|
* Checks for missing data on a subscription
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @return array a list of repair functions to run on the subscription
|
|
*/
|
|
public static function integrity_check( $subscription ) {
|
|
$repairs_needed = array();
|
|
|
|
foreach (
|
|
array(
|
|
'order_id',
|
|
'product_id',
|
|
'variation_id',
|
|
'subscription_key',
|
|
'status',
|
|
'period',
|
|
'interval',
|
|
'length',
|
|
'start_date',
|
|
'trial_expiry_date',
|
|
'expiry_date',
|
|
'end_date',
|
|
) as $meta ) {
|
|
if ( ! array_key_exists( $meta, $subscription ) || '' === $subscription[ $meta ] ) {
|
|
$repairs_needed[] = $meta;
|
|
}
|
|
}
|
|
|
|
return $repairs_needed;
|
|
}
|
|
|
|
/**
|
|
* 'order_id': a subscription can exist without an original order in v2.0, so technically the order ID is no longer required.
|
|
* However, if some or all order item meta data that constitutes a subscription exists without a corresponding parent order,
|
|
* we can deem the issue to be that the subscription meta data was not deleted, not that the subscription should exist. Meta
|
|
* data could be orphaned in v1.n if the order row in the wp_posts table was deleted directly in the database, or the
|
|
* subscription/order were for a customer that was deleted in WordPress administration interface prior to Subscriptions v1.3.8.
|
|
* In both cases, the subscription, including meta data, should have been permanently deleted. However, deleting data is not a
|
|
* good idea during an upgrade. So I propose instead that we create a subscription without a parent order, but move it to the trash.
|
|
*
|
|
* Additional idea was to check whether the given order_id exists, but since that's another database read, it would slow down a lot of things.
|
|
*
|
|
* A subscription will not make it to this point if it doesn't have an order id, so this function will practically never be run
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_order_id( $subscription ) {
|
|
WCS_Upgrade_Logger::add( '-- Repairing order_id for subscription that is missing order id: Status changed to trash' );
|
|
WCS_Upgrade_Logger::add( '-- Shop owner: please review new trashed subscriptions. There is at least one with missing order id.' );
|
|
|
|
$subscription['status'] = 'trash';
|
|
|
|
return $subscription;
|
|
}
|
|
|
|
/**
|
|
* Combined functionality for the following functions:
|
|
* - repair_product_id
|
|
* - repair_variation_id
|
|
* - repair_recurring_line_total
|
|
* - repair_recurring_line_tax
|
|
* - repair_recurring_line_subtotal
|
|
* - repair_recurring_line_subtotal_tax
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing the id for
|
|
* @param array $item_meta meta data about the product
|
|
* @param string $item_meta_key the meta key for the data on the item meta
|
|
* @param string $subscription_meta_key the meta key for the data on the subscription
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_from_item_meta( array $subscription, $item_id, $item_meta, $subscription_meta_key = null, $item_meta_key = null, $default_value = '' ) {
|
|
if ( ! is_array( $subscription ) || ! is_numeric( $item_id ) || ! is_array( $item_meta ) || ! is_string( $subscription_meta_key ) || ! is_string( $item_meta_key ) || ( ! is_string( $default_value ) && ! is_numeric( $default_value ) ) ) {
|
|
return $subscription;
|
|
}
|
|
|
|
if ( array_key_exists( $item_meta_key, $item_meta ) && ! empty( $item_meta[ $item_meta_key ] ) ) {
|
|
// only do the copy if the value on item meta is actually different to what the subscription has
|
|
// otherwise it'd be an extra line in the log file for no actual use
|
|
if ( ! array_key_exists( $subscription_meta_key, $subscription ) || $item_meta[ $item_meta_key ][0] != $subscription[ $subscription_meta_key ] ) {
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: copying %s from item_meta to %s on subscription.', $subscription['order_id'], $item_meta_key, $subscription_meta_key ) );
|
|
$subscription[ $subscription_meta_key ] = $item_meta[ $item_meta_key ][0];
|
|
}
|
|
} elseif ( ! array_key_exists( $item_meta_key, $item_meta ) ) {
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: setting an empty %s on old subscription, item meta was not helpful.', $subscription['order_id'], $subscription_meta_key ) );
|
|
$subscription[ $subscription_meta_key ] = $default_value;
|
|
}
|
|
|
|
return $subscription;
|
|
}
|
|
|
|
/**
|
|
* '_product_id': the only way to derive a order item's product ID would be to match the order item's name to a product name/title.
|
|
* This is quite hacky, so we may be better copying the empty product ID to the new subscription. A subscription to a deleted
|
|
* produced should be able to exist.
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing the id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_product_id( $subscription, $item_id, $item_meta ) {
|
|
return self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'product_id', '_product_id' );
|
|
}
|
|
|
|
/**
|
|
* '_variation_id': the only way to derive a order item's product ID would be to match the order item's name to a product name/title.
|
|
* This is quite hacky, so we may be better copying the empty product ID to the new subscription. A subscription to a deleted produced
|
|
* should be able to exist.
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing variation id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_variation_id( $subscription, $item_id, $item_meta ) {
|
|
return self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'variation_id', '_variation_id' );
|
|
}
|
|
|
|
/**
|
|
* If the subscription does not have a subscription key for whatever reason (probably because the product_id was missing), then this one
|
|
* fills in the blank.
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing variation id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_subscription_key( $subscription, $item_id, $item_meta ) {
|
|
if ( ! is_numeric( $item_id ) ) {
|
|
// because item_id can be either product id or variation id, we can't use
|
|
// item meta to backfill this
|
|
$subscription['subscription_key'] = '';
|
|
} else {
|
|
$subscription['subscription_key'] = $subscription['order_id'] . '_' . $item_id;
|
|
}
|
|
|
|
return $subscription;
|
|
}
|
|
|
|
/**
|
|
* '_subscription_status': we could default to cancelled (and then potentially trash) if no status exists because the cancelled status
|
|
* is irreversible. But we can also take this a step further. If the subscription has a '_subscription_expiry_date' value and a
|
|
* '_subscription_end_date' value, and they are within a few minutes of each other, we can assume the subscription's status should be
|
|
* expired. If there is a '_subscription_end_date' value that is different to the '_subscription_expiry_date' value (either because the
|
|
* expiration value is 0 or some other date), then we can assume the status should be cancelled). If there is no end date value, we're
|
|
* a bit lost as technically the subscription hasn't ended, but we should make sure it is not active, so cancelled is still the best
|
|
* default.
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing variation id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_status( $subscription, $item_id, $item_meta ) {
|
|
// only reset this if we didn't repair the order_id
|
|
if ( ! array_key_exists( 'order_id', $subscription ) || empty( $subscription['order_id'] ) ) {
|
|
WCS_Upgrade_Logger::add( '-- Tried to repair status. Previously set it to trash with order_id missing, bailing.' );
|
|
return $subscription;
|
|
}
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: repairing status for subscription.', $subscription['order_id'] ) );
|
|
|
|
// if expiry_date and end_date are within 4 minutes (arbitrary), let it be expired
|
|
if ( array_key_exists( 'expiry_date', $subscription ) && ! empty( $subscription['expiry_date'] ) && array_key_exists( 'end_date', $subscription ) && ! empty( $subscription['end_date'] ) && ( 4 * MINUTE_IN_SECONDS ) >= self::time_diff( $subscription['expiry_date'], $subscription['end_date'] ) ) {
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: there are end dates and expiry dates, they are close to each other, setting status to "expired" and returning.', $subscription['order_id'] ) );
|
|
$subscription['status'] = 'expired';
|
|
} else {
|
|
// default to cancelled
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: setting the default to "cancelled".', $subscription['order_id'] ) );
|
|
$subscription['status'] = 'cancelled';
|
|
}
|
|
self::log_store_owner_review( $subscription );
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: returning the status with %s', $subscription['order_id'], $subscription['status'] ) );
|
|
return $subscription;
|
|
}
|
|
|
|
/**
|
|
* '_subscription_period': we can attempt to derive this from the time between renewal orders. For example, if there are two renewal
|
|
* orders found 3 months apart, the billing period would be month. If there are not two or more renewal orders (we can't use a single
|
|
* renewal order because that would account for the free trial) and a _product_id value , if the product still exists, we can use the
|
|
* current value set on that product. It won't always be correct, but it's the closest we can get to an accurate estimate.
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing variation id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_period( $subscription, $item_id, $item_meta ) {
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: repairing period for subscription', $subscription['order_id'] ) );
|
|
|
|
// Get info from the product
|
|
$subscription = self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'period', '_subscription_period', '' );
|
|
|
|
if ( '' !== $subscription['period'] ) {
|
|
return $subscription;
|
|
}
|
|
|
|
// let's get the renewal orders
|
|
$renewal_orders = self::get_renewal_orders( $subscription );
|
|
|
|
if ( count( $renewal_orders ) < 2 ) {
|
|
// default to month. Because we're defaulting, we also need to cancel this to avoid charging customers on a schedule they didn't
|
|
// agree to.
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: setting default subscription period to month.', $subscription['order_id'] ) );
|
|
self::log_store_owner_review( $subscription );
|
|
$subscription['period'] = 'month';
|
|
$subscription['status'] = 'cancelled';
|
|
return $subscription;
|
|
}
|
|
|
|
// let's get the last 2 renewal orders
|
|
$last_renewal_order = array_shift( $renewal_orders );
|
|
$last_renewal_date = wcs_get_datetime_utc_string( wcs_get_objects_property( $last_renewal_order, 'date_created' ) );
|
|
$last_renewal_timestamp = wcs_date_to_time( $last_renewal_date );
|
|
|
|
$second_renewal_order = array_shift( $renewal_orders );
|
|
$second_renewal_date = wcs_get_datetime_utc_string( wcs_get_objects_property( $second_renewal_order, 'date_created' ) );
|
|
$second_renewal_timestamp = wcs_date_to_time( $second_renewal_date );
|
|
|
|
$interval = 1;
|
|
|
|
// if we have an interval, let's pass this along too, because then it's a known variable
|
|
if ( array_key_exists( 'interval', $subscription ) && ! empty( $subscription['interval'] ) ) {
|
|
$interval = $subscription['interval'];
|
|
}
|
|
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: calling wcs_estimate_period_between().', $subscription['order_id'] ) );
|
|
$period = wcs_estimate_period_between( $last_renewal_date, $second_renewal_date, $interval );
|
|
|
|
// if we have 3 renewal orders, do a double check
|
|
if ( ! empty( $renewal_orders ) ) {
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: we have 3 renewal orders, trying to make sure we are right.', $subscription['order_id'] ) );
|
|
|
|
$third_renewal_order = array_shift( $renewal_orders );
|
|
$third_renewal_date = wcs_get_datetime_utc_string( wcs_get_objects_property( $third_renewal_order, 'date_created' ) );
|
|
|
|
$period2 = wcs_estimate_period_between( $second_renewal_date, $third_renewal_date, $interval );
|
|
|
|
if ( $period == $period2 ) {
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: second check confirmed, we are very confident period is %s.', $subscription['order_id'], $period ) );
|
|
$subscription['period'] = $period;
|
|
}
|
|
}
|
|
|
|
$subscription['period'] = $period;
|
|
|
|
return $subscription;
|
|
}
|
|
|
|
/**
|
|
* '_subscription_interval': we can attempt to derive this from the time between renewal orders. For example, if there are two renewal
|
|
* orders found 3 months apart, the billing period would be month. If there are not two or more renewal orders (we can't use a single
|
|
* renewal order because that would account for the free trial) and a _product_id value , if the product still exists, we can use the
|
|
* current value set on that product. It won't always be correct, but it's the closest we can get to an accurate estimate.
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing variation id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_interval( $subscription, $item_id, $item_meta ) {
|
|
|
|
// Get info from the product
|
|
if ( array_key_exists( '_subscription_interval', $item_meta ) && ! empty( $item_meta['_subscription_interval'] ) ) {
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: getting interval from item meta and returning.', $subscription['order_id'] ) );
|
|
|
|
$subscription['interval'] = $item_meta['_subscription_interval'][0];
|
|
return $subscription;
|
|
}
|
|
|
|
// by this time we already have a period on our hand
|
|
// let's get the renewal orders
|
|
$renewal_orders = self::get_renewal_orders( $subscription );
|
|
|
|
if ( count( $renewal_orders ) < 2 ) {
|
|
// default to 1
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: setting default subscription interval to 1.', $subscription['order_id'] ) );
|
|
self::log_store_owner_review( $subscription );
|
|
$subscription['interval'] = 1;
|
|
$subscription['status'] = 'cancelled';
|
|
return $subscription;
|
|
}
|
|
|
|
// let's get the last 2 renewal orders
|
|
$last_renewal_order = array_shift( $renewal_orders );
|
|
$last_renewal_date = wcs_get_datetime_utc_string( wcs_get_objects_property( $last_renewal_order, 'date_created' ) );
|
|
$last_renewal_timestamp = wcs_date_to_time( $last_renewal_date );
|
|
|
|
$second_renewal_order = array_shift( $renewal_orders );
|
|
$second_renewal_date = wcs_get_datetime_utc_string( wcs_get_objects_property( $second_renewal_order, 'date_created' ) );
|
|
$second_renewal_timestamp = wcs_date_to_time( $second_renewal_date );
|
|
|
|
$subscription['interval'] = wcs_estimate_periods_between( $second_renewal_timestamp, $last_renewal_timestamp, $subscription['period'] );
|
|
|
|
return $subscription;
|
|
}
|
|
|
|
/**
|
|
* '_subscription_length': if there are '_subscription_expiry_date' and '_subscription_start_date' values, we can use those to
|
|
* determine how many billing periods fall between them, and therefore, the length of the subscription. This data is low value however as
|
|
* it is no longer stored in v2.0 and mainly used to determine the expiration date.
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing variation id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_length( $subscription, $item_id, $item_meta ) {
|
|
// Let's see if the item meta has that
|
|
$subscription = self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'length', '_subscription_length', '' );
|
|
|
|
if ( '' !== $subscription['length'] ) {
|
|
return $subscription;
|
|
}
|
|
|
|
$effective_start_date = self::get_effective_start_date( $subscription );
|
|
|
|
// If we can calculate it from the effective date and expiry date
|
|
if ( 'expired' == $subscription['status'] && array_key_exists( 'expiry_date', $subscription ) && ! empty( $subscription['expiry_date'] ) && null !== $effective_start_date && array_key_exists( 'period', $subscription ) && ! empty( $subscription['period'] ) && array_key_exists( 'interval', $subscription ) && ! empty( $subscription['interval'] ) ) {
|
|
$intervals = wcs_estimate_periods_between( wcs_date_to_time( $effective_start_date ), wcs_date_to_time( $subscription['expiry_date'] ), $subscription['period'], 'floor' );
|
|
$subscription['length'] = $intervals;
|
|
} else {
|
|
$subscription['length'] = 0;
|
|
}
|
|
|
|
return $subscription;
|
|
}
|
|
|
|
/**
|
|
* '_subscription_start_date': the original order's '_paid_date' value (stored in post meta) can be used as the subscription's start date.
|
|
* If no '_paid_date' exists, because the order used a payment method that doesn't call $order->payment_complete(), like BACs or Cheque,
|
|
* then we can use the post_date_gmt column in the wp_posts table of the original order.
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing variation id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_start_date( $subscription, $item_id, $item_meta ) {
|
|
global $wpdb;
|
|
|
|
$start_date = get_post_meta( $subscription['order_id'], '_paid_date', true );
|
|
|
|
WCS_Upgrade_Logger::add( sprintf( 'Repairing start_date for order %d: Trying to use the _paid date for start date.', $subscription['order_id'] ) );
|
|
|
|
if ( empty( $start_date ) ) {
|
|
WCS_Upgrade_Logger::add( '-- start_date from _paid date failed. Using post_date_gmt' );
|
|
|
|
$start_date = $wpdb->get_var( $wpdb->prepare( "SELECT post_date_gmt FROM {$wpdb->posts} WHERE ID = %d", $subscription['order_id'] ) );
|
|
}
|
|
|
|
$subscription['start_date'] = $start_date;
|
|
return $subscription;
|
|
}
|
|
|
|
/**
|
|
* '_subscription_trial_expiry_date': if the subscription has at least one renewal order, we can set the trial expiration date to the date
|
|
* of the first renewal order. However, this is generally safe to default to 0 if it is not set. Especially if the subscription is
|
|
* inactive and/or has 1 or more renewals (because its no longer used and is simply for record keeping).
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing variation id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_trial_expiry_date( $subscription, $item_id, $item_meta ) {
|
|
$subscription['trial_expiry_date'] = self::maybe_get_date_from_action_scheduler( 'scheduled_subscription_trial_end', $subscription );
|
|
return $subscription;
|
|
}
|
|
|
|
/**
|
|
* '_subscription_expiry_date': if the subscription has a '_subscription_length' value, that can be used to calculate the expiration date
|
|
* (from the '_subscription_start_date' or '_subscription_trial_expiry_date' if one is set). If no length is set, but the subscription has
|
|
* an expired status, the '_subscription_end_date' can be used. In most other cases, this is generally safe to default to 0 if the
|
|
* subscription is cancelled because its no longer used and is simply for record keeping.
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing variation id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_expiry_date( $subscription, $item_id, $item_meta ) {
|
|
$subscription['expiry_date'] = self::maybe_get_date_from_action_scheduler( 'scheduled_subscription_expiration', $subscription );
|
|
return $subscription;
|
|
}
|
|
|
|
/**
|
|
* '_subscription_end_date': if the subscription has a '_subscription_length' value and status of expired, the length can be used to
|
|
* calculate the end date as it will be the same as the expiration date. If no length is set, or the subscription has a cancelled status,
|
|
* some time within 24 hours after the last renewal order's date can be used to provide a rough estimate.
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing variation id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_end_date( $subscription, $item_id, $item_meta ) {
|
|
|
|
$subscription = self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'end_date', '_subscription_end_date', '' );
|
|
|
|
if ( '' !== $subscription['end_date'] ) {
|
|
return $subscription;
|
|
}
|
|
|
|
if ( 'expired' == $subscription['status'] && array_key_exists( 'expiry_date', $subscription ) && ! empty( $subscription['expiry_date'] ) ) {
|
|
|
|
$subscription['end_date'] = $subscription['expiry_date'];
|
|
|
|
} elseif ( 'cancelled' == $subscription['status'] || ! array_key_exists( 'length', $subscription ) || empty( $subscription['length'] ) ) {
|
|
|
|
// get renewal orders
|
|
$renewal_orders = self::get_renewal_orders( $subscription );
|
|
$last_order = array_shift( $renewal_orders );
|
|
|
|
if ( empty( $last_order ) ) {
|
|
|
|
$subscription['end_date'] = 0;
|
|
|
|
} else {
|
|
|
|
$subscription['end_date'] = wcs_add_time( 5, 'hours', wcs_get_objects_property( $last_order, 'date_created' )->getTimestamp() );
|
|
|
|
}
|
|
} else {
|
|
|
|
// if everything failed, let's have an empty one
|
|
$subscription['end_date'] = 0;
|
|
|
|
}
|
|
|
|
return $subscription;
|
|
}
|
|
|
|
/**
|
|
* _recurring_line_total': if the subscription has at least one renewal order, this value can be derived from the '_line_total' value of
|
|
* that order. If no renewal orders exist, it can be derived roughly by deducting the '_subscription_sign_up_fee' value from the original
|
|
* order's total if there is no trial expiration date.
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing variation id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_recurring_line_total( $subscription, $item_id, $item_meta ) {
|
|
return self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'recurring_line_total', '_line_total', 0 );
|
|
}
|
|
|
|
/**
|
|
* _recurring_line_total': if the subscription has at least one renewal order, this value can be derived from the '_line_total' value
|
|
* of that order. If no renewal orders exist, it can be derived roughly by deducting the '_subscription_sign_up_fee' value from the
|
|
* original order's total if there is no trial expiration date.
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing variation id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_recurring_line_tax( $subscription, $item_id, $item_meta ) {
|
|
return self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'recurring_line_tax', '_line_tax', 0 );
|
|
}
|
|
|
|
/**
|
|
* _recurring_line_total': if the subscription has at least one renewal order, this value can be derived from the '_line_total' value of
|
|
* that order. If no renewal orders exist, it can be derived roughly by deducting the '_subscription_sign_up_fee' value from the original
|
|
* order's total if there is no trial expiration date
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing variation id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_recurring_line_subtotal( $subscription, $item_id, $item_meta ) {
|
|
return self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'recurring_line_subtotal', '_line_subtotal', 0 );
|
|
}
|
|
|
|
/**
|
|
* _recurring_line_total': if the subscription has at least one renewal order, this value can be derived from the '_line_total' value of
|
|
* that order. If no renewal orders exist, it can be derived roughly by deducting the '_subscription_sign_up_fee' value from the original
|
|
* order's total if there is no trial expiration date.
|
|
*
|
|
* @param array $subscription data about the subscription
|
|
* @param numeric $item_id the id of the product we're missing variation id for
|
|
* @param array $item_meta meta data about the product
|
|
* @return array repaired data about the subscription
|
|
*/
|
|
public static function repair_recurring_line_subtotal_tax( $subscription, $item_id, $item_meta ) {
|
|
return self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'recurring_line_subtotal_tax', '_line_subtotal_tax', 0 );
|
|
}
|
|
|
|
/**
|
|
* Utility function to calculate the seconds between two timestamps. Order is not important, it's just the difference.
|
|
*
|
|
* @param string $to mysql timestamp
|
|
* @param string $from mysql timestamp
|
|
* @return integer number of seconds between the two
|
|
*/
|
|
private static function time_diff( $to, $from ) {
|
|
$to = wcs_date_to_time( $to );
|
|
$from = wcs_date_to_time( $from );
|
|
|
|
return abs( $to - $from );
|
|
}
|
|
|
|
/**
|
|
* Utility function to get all renewal orders in the old structure.
|
|
*
|
|
* @param array $subscription the sub we're looking for the renewal orders
|
|
* @return array of WC_Orders
|
|
*/
|
|
private static function get_renewal_orders( $subscription ) {
|
|
$related_orders = array();
|
|
|
|
$related_post_ids = get_posts( array(
|
|
'posts_per_page' => -1,
|
|
'post_type' => 'shop_order',
|
|
'post_status' => 'any',
|
|
'fields' => 'ids',
|
|
'orderby' => 'date',
|
|
'order' => 'DESC',
|
|
'post_parent' => $subscription['order_id'],
|
|
) );
|
|
|
|
foreach ( $related_post_ids as $post_id ) {
|
|
$related_orders[ $post_id ] = wc_get_order( $post_id );
|
|
}
|
|
|
|
return $related_orders;
|
|
}
|
|
|
|
/**
|
|
* Utility method to check the action scheduler for dates
|
|
*
|
|
* @param string $type the type of scheduled action
|
|
* @param string $subscription_key key of subscription in the format of order_id_item_id
|
|
* @return string either 0 or mysql date
|
|
*/
|
|
private static function maybe_get_date_from_action_scheduler( $type, $subscription ) {
|
|
$action_args = array(
|
|
'user_id' => intval( $subscription['user_id'] ),
|
|
'subscription_key' => $subscription['subscription_key'],
|
|
);
|
|
|
|
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 ); // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
|
|
|
|
$next_date_timestamp = as_next_scheduled_action( $type, $action_args );
|
|
|
|
if ( false === $next_date_timestamp ) {
|
|
// set it to 0 as default
|
|
$formatted_date = 0;
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: Repairing date type "%s": fetch of date unsuccessful: no action present. Date is 0.', $subscription['order_id'], $type ) );
|
|
} else {
|
|
$formatted_date = gmdate( 'Y-m-d H:i:s', $next_date_timestamp );
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: Repairing date type "%s": fetch of date successful. New date is %s', $subscription['order_id'], $type, $formatted_date ) );
|
|
}
|
|
|
|
return $formatted_date;
|
|
}
|
|
|
|
/**
|
|
* Utility function to return the effective start date for interval calculations (end of trial period -> start date -> null )
|
|
*
|
|
* @param array $subscription subscription data
|
|
* @return mixed mysql formatted date, or null if none found
|
|
*/
|
|
public static function get_effective_start_date( $subscription ) {
|
|
|
|
if ( array_key_exists( 'trial_expiry_date', $subscription ) && ! empty( $subscription['trial_expiry_date'] ) ) {
|
|
|
|
$effective_date = $subscription['trial_expiry_date'];
|
|
|
|
} elseif ( array_key_exists( 'trial_period', $subscription ) && ! empty( $subscription['trial_period'] ) && array_key_exists( 'trial_length', $subscription ) && ! empty( $subscription['trial_length'] ) && array_key_exists( 'start_date', $subscription ) && ! empty( $subscription['start_date'] ) ) {
|
|
|
|
// calculate the end of trial from interval, period and start date
|
|
$effective_date = gmdate( 'Y-m-d H:i:s', wcs_add_time( $subscription['trial_length'], $subscription['trial_period'], wcs_date_to_time( $subscription['start_date'] ) ) );
|
|
|
|
} elseif ( array_key_exists( 'start_date', $subscription ) && ! empty( $subscription['start_date'] ) ) {
|
|
|
|
$effective_date = $subscription['start_date'];
|
|
|
|
} else {
|
|
|
|
$effective_date = null;
|
|
|
|
}
|
|
|
|
return $effective_date;
|
|
}
|
|
|
|
|
|
/**
|
|
* Logs an entry for the store owner to review an issue.
|
|
*
|
|
* @param array $subscription subscription data
|
|
*/
|
|
protected static function log_store_owner_review( $subscription ) {
|
|
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: shop owner please review subscription.', $subscription['order_id'] ) );
|
|
}
|
|
}
|