Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache subscription last order date #819

Open
wants to merge 13 commits into
base: trunk
Choose a base branch
from
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
* Fix - For stores with HPOS + compatibility mode enabled, using the bulk delete related orders cache tool was not correctly deleting the meta from the WP Posts table.
* Fix - Prevent empty strings being saved in related orders cache ID meta when backfilling order data to the WP Posts table.
* Fix - Correctly load product names with HTML on the cart and checkout shipping rates.
* Fix - Improve performance of the subscription listing page.
* Dev - Fix Node version mismatch between package.json and .nvmrc (both are now set to v16.17.1).
* Add - Hooks to add more columns to the Related Orders table on admin

Expand Down
31 changes: 28 additions & 3 deletions includes/admin/class-wcs-admin-post-types.php
Original file line number Diff line number Diff line change
Expand Up @@ -684,9 +684,14 @@ public function render_shop_subscription_columns( $column, $subscription = null
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
*/
public static function get_date_column_content( $subscription, $column ) {
$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;
$date_timestamp = $subscription->get_time( $date_type );
$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 ( 'last_payment_date' === $column ) {
$date_timestamp = self::get_last_payment_date( $subscription );
} else {
$date_timestamp = $subscription->get_time( $date_type );
}

if ( 0 === $date_timestamp ) {
return '-';
Expand Down Expand Up @@ -1834,6 +1839,26 @@ public function orders_table_query_clauses( $pieces, $query, $args ) {
return $pieces;
}

/**
* Get the last payment date for a subscription.
*
* @param WC_Subscription $subscription The subscription object.
* @return int The last payment date timestamp.
*/
private static function get_last_payment_date( $subscription ) {
$last_order_date_created = $subscription->get_last_order_date_created();

if ( ! empty( $last_order_date_created ) ) {
return $last_order_date_created;
}

$date_timestamp = $subscription->get_time( 'last_order_date_created' );
$subscription->set_last_order_date_created( $date_timestamp );
$subscription->save();

return $date_timestamp;
}

/**
* Adds order table query clauses to sort the subscriptions list table by last payment date.
*
Expand Down
36 changes: 28 additions & 8 deletions includes/class-wc-subscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class WC_Subscription extends WC_Order {
'requires_manual_renewal' => true,
'cancelled_email_sent' => false,
'trial_period' => '',
'last_order_date_created' => null,

// Extra data that requires manual getting/setting because we don't define getters/setters for it
'schedule_trial_end' => null,
Expand Down Expand Up @@ -908,6 +909,15 @@ public function get_cancelled_email_sent( $context = 'view' ) {
return $this->get_prop( 'cancelled_email_sent', $context );
}

/**
* The subscription last order created date.
*
* @return string
*/
public function get_last_order_date_created( $context = 'view' ) {
return $this->get_prop( 'last_order_date_created', $context );
}

/*** Setters *****************************************************/

/**
Expand Down Expand Up @@ -1092,15 +1102,23 @@ public function set_cancelled_email_sent( $value ) {
$this->set_prop( 'cancelled_email_sent', $value );
}

/**
* Set the subscription last order created date.
*/
public function set_last_order_date_created( $value ) {
$this->set_prop( 'last_order_date_created', $value );
}

/*** Date methods *****************************************************/

/**
* Get the MySQL formatted date for a specific piece of the subscriptions schedule
*
* @param string $date_type 'date_created', 'trial_end', 'next_payment', 'last_order_date_created' or 'end'
* @param string $timezone The timezone of the $datetime param, either 'gmt' or 'site'. Default 'gmt'.
* @param array $exclude_statuses An array of subscription statuses to exclude from the date calculation.
*/
public function get_date( $date_type, $timezone = 'gmt' ) {
public function get_date( $date_type, $timezone = 'gmt', $exclude_statuses = array() ) {

$date_type = wcs_normalise_date_type_key( $date_type, true );

Expand All @@ -1122,13 +1140,13 @@ public function get_date( $date_type, $timezone = 'gmt' ) {
$date = $this->get_date_completed();
break;
case 'last_order_date_created':
$date = $this->get_related_orders_date( 'date_created', 'last' );
$date = $this->get_related_orders_date( 'date_created', 'last', $exclude_statuses );
break;
case 'last_order_date_paid':
$date = $this->get_related_orders_date( 'date_paid', 'last' );
$date = $this->get_related_orders_date( 'date_paid', 'last', $exclude_statuses );
break;
case 'last_order_date_completed':
$date = $this->get_related_orders_date( 'date_completed', 'last' );
$date = $this->get_related_orders_date( 'date_completed', 'last', $exclude_statuses );
break;
default:
$date = $this->get_date_prop( $date_type );
Expand Down Expand Up @@ -1246,14 +1264,15 @@ public function set_date_completed( $date = null ) {
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
* @param string $date_type Any valid WC 3.0 date property, including 'date_paid', 'date_completed', 'date_created', or 'date_modified'
* @param string $order_type The type of orders to return, can be 'last', 'parent', 'switch', 'renewal' or 'any'. Default 'any'. Use 'last' to only check the last order.
* @param array $exclude_statuses An array of subscription statuses to exclude from the date calculation.
* @return WC_DateTime|NULL object if the date is set or null if there is no date.
*/
protected function get_related_orders_date( $date_type, $order_type = 'any' ) {
protected function get_related_orders_date( $date_type, $order_type = 'any', $exclude_statuses = array() ) {

$date = null;

if ( 'last' === $order_type ) {
$last_order = $this->get_last_order( 'all' );
$last_order = $this->get_last_order( 'all', [ 'parent', 'renewal' ], $exclude_statuses );
$date = ( ! $last_order ) ? null : wcs_get_objects_property( $last_order, $date_type );
} else {
// Loop over orders until we find a valid date of this type or run out of related orders
Expand Down Expand Up @@ -1353,10 +1372,11 @@ public function format_date_to_display( $timestamp_gmt, $date_type ) {
*
* @param string $date_type 'date_created', 'trial_end', 'next_payment', 'last_order_date_created', 'end' or 'end_of_prepaid_term'
* @param string $timezone The timezone of the $datetime param. Default 'gmt'.
* @param array $exclude_statuses An array of subscription statuses to exclude from the date calculation.
*/
public function get_time( $date_type, $timezone = 'gmt' ) {
public function get_time( $date_type, $timezone = 'gmt', $exclude_statuses = array() ) {

$datetime = $this->get_date( $date_type, $timezone );
$datetime = $this->get_date( $date_type, $timezone, $exclude_statuses );
$datetime = wcs_date_to_time( $datetime );

return $datetime;
Expand Down
1 change: 1 addition & 0 deletions includes/class-wc-subscriptions-data-copier.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class WC_Subscriptions_Data_Copier {
'_suspension_count',
'_requires_manual_renewal',
'_cancelled_email_sent',
'_last_order_date_created',
'_trial_period',
'_created_via',
'_order_stock_reduced',
Expand Down
107 changes: 107 additions & 0 deletions includes/class-wc-subscriptions-order.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,22 @@ public static function init() {
add_filter( 'woocommerce_order_query_args', array( __CLASS__, 'map_order_query_args_for_subscriptions' ) );

add_filter( 'woocommerce_orders_table_query_clauses', [ __CLASS__, 'filter_orders_query_by_parent_orders' ], 10, 2 );

add_action( 'woocommerce_before_delete_order', [ __CLASS__, 'delete_order_update_order_related_subscriptions_last_order_date_created' ], 10, 2 );

$cache_manager = new WCS_Object_Data_Cache_Manager(
'subscription',
[
'parent_id',
]
);
$cache_manager->init();

add_action( 'wcs_update_post_meta_caches', [ __CLASS__, 'update_subscription_last_order_date_parent_id_changes' ], 10, 5 );

add_action( 'wcs_orders_add_relation', [ __CLASS__, 'add_relation_update_order_related_subscriptions_last_order_date_created' ], 10, 3 );

add_action( 'wcs_orders_delete_relation', [ __CLASS__, 'delete_relation_update_order_related_subscriptions_last_order_date_created' ], 10, 3 );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function name seems weird to me. Also it does not actually delete the order just update it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leonardola it deletes the order relation, not the order itself.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function name seems weird to me. Also it does not actually delete the order just updates it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leonardola the delete_ is the function names comes from WCS_Related_Order_Store::instance()->delete_relation(, it's not related to delete an order but delete a relation.

}

/*
Expand Down Expand Up @@ -2396,6 +2412,97 @@ public static function get_meta( $order, $meta_key, $default = 0 ) {
return $meta_value;
}

/**
* Update subscription cached last_order_date_created metadata when deleting a child order.
*
* @param int $id The deleted order ID.
* @param WC_Order $order The deleted order object.
*/
public static function delete_order_update_order_related_subscriptions_last_order_date_created( $id, $order ) {
if ( $order->get_created_via() !== 'subscription' ) {
return;
}

self::update_order_related_subscriptions_last_order_date_created( $order, [ 'trash' ] );
}

/**
* Update subscription cached last_order_date_created metadata when adding a child order.
*
* @param WC_Order $order The order to link with the subscription.
* @param WC_Order $subscription The order or subscription to link the order to.
* @param string $relation_type The relationship between the subscription and the order. Must be 'renewal', 'switch' or 'resubscribe' unless custom relationships are implemented.
*/
public static function add_relation_update_order_related_subscriptions_last_order_date_created( $order, $subscription, $relation_type ) {
self::update_order_related_subscriptions_last_order_date_created( $order );
}

/**
* Update subscription cached last_order_date_created metadata when deleting a child order relation.
*
* @param WC_Order $order An order that may be linked with subscriptions.
* @param WC_Order $subscription A subscription or order to unlink the order with, if a relation exists.
* @param string $relation_type The relationship between the subscription and the order. Must be 'renewal', 'switch' or 'resubscribe' unless custom relationships are implemented.
*/
public static function delete_relation_update_order_related_subscriptions_last_order_date_created( $order, $subscription, $relation_type ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is also not deleting anything is it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function name comes from WCS_Related_Order_Store::instance()->delete_relation(, would you suggest a better name?

$last_order_date_created = $subscription->get_time( 'last_order_date_created', 'gmt' );

$subscription->set_last_order_date_created( $last_order_date_created );
$subscription->save();

self::update_order_related_subscriptions_last_order_date_created( $order );
}

/**
* Update all subscription cached last_order_date_created metadata related to the order.
*
* @param WC_Order $order The order object.
* @param array $exclude_statuses The order statuses to exclude.
*/
private static function update_order_related_subscriptions_last_order_date_created( $order, $exclude_statuses = [] ) {
$subscription_ids = wcs_get_subscription_ids_for_order( $order );

foreach ( $subscription_ids as $subscription_id ) {
$subscription = wcs_get_subscription( $subscription_id );
$last_order_date_created = $subscription->get_time( 'last_order_date_created', 'gmt', $exclude_statuses );

$subscription->set_last_order_date_created( $last_order_date_created );
$subscription->save();
}
}

/**
* Update subscription cached last_order_date_created metadata when manually updating parent id.
*
* @param string $type The type of update to check. Only 'add' or 'delete' should be used.
* @param int $object_id The object the meta is being changed on.
* @param string $meta_key The object meta key being changed.
* @param mixed $meta_value The meta value.
* @param mixed $prev_value The previous value stored in the database. Optional.
*/
public static function update_subscription_last_order_date_parent_id_changes( $type, $object_id, $key, $new_value, $previous_value ) {
if ( 'parent_id' !== $key || empty( $previous_value ) || empty( $new_value ) ) {
return;
}

$previous_subscription = wcs_get_subscription( $previous_value );
$new_subscription = wcs_get_subscription( $new_value );

if ( ! $previous_subscription || ! $new_subscription ) {
return;
}

// Switch last order date on both subscriptions.
$previous_last_order_date_created = $previous_subscription->get_time( 'last_order_date_created' );
$new_last_order_date_created = $new_subscription->get_time( 'last_order_date_created' );

$new_subscription->set_last_order_date_created( $previous_last_order_date_created );
$new_subscription->save();

$previous_subscription->set_last_order_date_created( $new_last_order_date_created );
$previous_subscription->save();
}

/**
* Prints the HTML for the admin dropdown for order types to Woocommerce -> Orders screen.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class WCS_Orders_Table_Subscription_Data_Store extends \Automattic\WooCommerce\I
'_cancelled_email_sent' => 'cancelled_email_sent',
'_requires_manual_renewal' => 'requires_manual_renewal',
'_trial_period' => 'trial_period',
'_last_order_date_created' => 'last_order_date_created',

'_schedule_trial_end' => 'schedule_trial_end',
'_schedule_next_payment' => 'schedule_next_payment',
Expand Down
4 changes: 4 additions & 0 deletions includes/data-stores/class-wcs-related-order-store-cpt.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ public function add_relation( WC_Order $order, WC_Order $subscription, $relation
$order->add_meta_data( $related_order_meta_key, $subscription_id, false );
$order->save();
}

do_action( 'wcs_orders_add_relation', $order, $subscription, $relation_type );
}

/**
Expand All @@ -124,6 +126,8 @@ public function delete_relation( WC_Order $order, WC_Order $subscription, $relat
}

$order->save();

do_action( 'wcs_orders_delete_relation', $order, $subscription, $relation_type );
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class WCS_Subscription_Data_Store_CPT extends WC_Order_Data_Store_CPT implements
'_cancelled_email_sent' => 'cancelled_email_sent',
'_requires_manual_renewal' => 'requires_manual_renewal',
'_trial_period' => 'trial_period',
'_last_order_date_created' => 'last_order_date_created',

'_schedule_trial_end' => 'schedule_trial_end',
'_schedule_next_payment' => 'schedule_next_payment',
Expand Down
5 changes: 5 additions & 0 deletions includes/wcs-order-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,11 @@ function wcs_create_order_from_subscription( $subscription, $type ) {
throw new Exception( sprintf( __( 'There was an error fetching the new order (%1$s) for subscription %2$d.', 'woocommerce-subscriptions' ), $type, $subscription->get_id() ) );
}

// Update the subscription last_order_date_created on every time
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make the comment a one liner or change to /* */ style

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed on ef28be8.

// a child order is created.
$subscription->set_last_order_date_created( $new_order->get_date_created()->getTimestamp() );
$subscription->save();

/**
* Filters the new order created from the subscription.
*
Expand Down