Skip to content
This repository was archived by the owner on May 21, 2025. It is now read-only.

Commit d48fb25

Browse files
authored
Cache subscription last order date (#819)
* Cache subscription last order date * Add changelog entry * Fix linting errors * Handle parent id changes * Handle parent id null value * Refactoring with Copilot * Fix date update on subscription deletion * Handle manual relation changes * Update comment * Fix last order updated on parent id changes * Update comment to be single liner * Fix order delete date update
1 parent 48c3fd9 commit d48fb25

9 files changed

+170
-11
lines changed

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
* 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.
2727
* Fix - Prevent empty strings being saved in related orders cache ID meta when backfilling order data to the WP Posts table.
2828
* Fix - Correctly load product names with HTML on the cart and checkout shipping rates.
29+
* Fix - Improve performance of the subscription listing page.
2930
* Dev - Fix Node version mismatch between package.json and .nvmrc (both are now set to v16.17.1).
3031
* Add - Hooks to add more columns to the Related Orders table on admin
3132

includes/admin/class-wcs-admin-post-types.php

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -684,9 +684,14 @@ public function render_shop_subscription_columns( $column, $subscription = null
684684
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
685685
*/
686686
public static function get_date_column_content( $subscription, $column ) {
687-
$date_type_map = array( 'last_payment_date' => 'last_order_date_created' );
688-
$date_type = array_key_exists( $column, $date_type_map ) ? $date_type_map[ $column ] : $column;
689-
$date_timestamp = $subscription->get_time( $date_type );
687+
$date_type_map = array( 'last_payment_date' => 'last_order_date_created' );
688+
$date_type = array_key_exists( $column, $date_type_map ) ? $date_type_map[ $column ] : $column;
689+
690+
if ( 'last_payment_date' === $column ) {
691+
$date_timestamp = self::get_last_payment_date( $subscription );
692+
} else {
693+
$date_timestamp = $subscription->get_time( $date_type );
694+
}
690695

691696
if ( 0 === $date_timestamp ) {
692697
return '-';
@@ -1834,6 +1839,26 @@ public function orders_table_query_clauses( $pieces, $query, $args ) {
18341839
return $pieces;
18351840
}
18361841

1842+
/**
1843+
* Get the last payment date for a subscription.
1844+
*
1845+
* @param WC_Subscription $subscription The subscription object.
1846+
* @return int The last payment date timestamp.
1847+
*/
1848+
private static function get_last_payment_date( $subscription ) {
1849+
$last_order_date_created = $subscription->get_last_order_date_created();
1850+
1851+
if ( ! empty( $last_order_date_created ) ) {
1852+
return $last_order_date_created;
1853+
}
1854+
1855+
$date_timestamp = $subscription->get_time( 'last_order_date_created' );
1856+
$subscription->set_last_order_date_created( $date_timestamp );
1857+
$subscription->save();
1858+
1859+
return $date_timestamp;
1860+
}
1861+
18371862
/**
18381863
* Adds order table query clauses to sort the subscriptions list table by last payment date.
18391864
*

includes/class-wc-subscription.php

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class WC_Subscription extends WC_Order {
6060
'requires_manual_renewal' => true,
6161
'cancelled_email_sent' => false,
6262
'trial_period' => '',
63+
'last_order_date_created' => null,
6364

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

912+
/**
913+
* The subscription last order created date.
914+
*
915+
* @return string
916+
*/
917+
public function get_last_order_date_created( $context = 'view' ) {
918+
return $this->get_prop( 'last_order_date_created', $context );
919+
}
920+
911921
/*** Setters *****************************************************/
912922

913923
/**
@@ -1092,15 +1102,23 @@ public function set_cancelled_email_sent( $value ) {
10921102
$this->set_prop( 'cancelled_email_sent', $value );
10931103
}
10941104

1105+
/**
1106+
* Set the subscription last order created date.
1107+
*/
1108+
public function set_last_order_date_created( $value ) {
1109+
$this->set_prop( 'last_order_date_created', $value );
1110+
}
1111+
10951112
/*** Date methods *****************************************************/
10961113

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

11051123
$date_type = wcs_normalise_date_type_key( $date_type, true );
11061124

@@ -1122,13 +1140,13 @@ public function get_date( $date_type, $timezone = 'gmt' ) {
11221140
$date = $this->get_date_completed();
11231141
break;
11241142
case 'last_order_date_created':
1125-
$date = $this->get_related_orders_date( 'date_created', 'last' );
1143+
$date = $this->get_related_orders_date( 'date_created', 'last', $exclude_statuses );
11261144
break;
11271145
case 'last_order_date_paid':
1128-
$date = $this->get_related_orders_date( 'date_paid', 'last' );
1146+
$date = $this->get_related_orders_date( 'date_paid', 'last', $exclude_statuses );
11291147
break;
11301148
case 'last_order_date_completed':
1131-
$date = $this->get_related_orders_date( 'date_completed', 'last' );
1149+
$date = $this->get_related_orders_date( 'date_completed', 'last', $exclude_statuses );
11321150
break;
11331151
default:
11341152
$date = $this->get_date_prop( $date_type );
@@ -1246,14 +1264,15 @@ public function set_date_completed( $date = null ) {
12461264
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
12471265
* @param string $date_type Any valid WC 3.0 date property, including 'date_paid', 'date_completed', 'date_created', or 'date_modified'
12481266
* @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.
1267+
* @param array $exclude_statuses An array of subscription statuses to exclude from the date calculation.
12491268
* @return WC_DateTime|NULL object if the date is set or null if there is no date.
12501269
*/
1251-
protected function get_related_orders_date( $date_type, $order_type = 'any' ) {
1270+
protected function get_related_orders_date( $date_type, $order_type = 'any', $exclude_statuses = array() ) {
12521271

12531272
$date = null;
12541273

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

1359-
$datetime = $this->get_date( $date_type, $timezone );
1379+
$datetime = $this->get_date( $date_type, $timezone, $exclude_statuses );
13601380
$datetime = wcs_date_to_time( $datetime );
13611381

13621382
return $datetime;

includes/class-wc-subscriptions-data-copier.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class WC_Subscriptions_Data_Copier {
3838
'_suspension_count',
3939
'_requires_manual_renewal',
4040
'_cancelled_email_sent',
41+
'_last_order_date_created',
4142
'_trial_period',
4243
'_created_via',
4344
'_order_stock_reduced',

includes/class-wc-subscriptions-order.php

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,22 @@ public static function init() {
7777
add_filter( 'woocommerce_order_query_args', array( __CLASS__, 'map_order_query_args_for_subscriptions' ) );
7878

7979
add_filter( 'woocommerce_orders_table_query_clauses', [ __CLASS__, 'filter_orders_query_by_parent_orders' ], 10, 2 );
80+
81+
add_action( 'woocommerce_before_delete_order', [ __CLASS__, 'delete_order_update_order_related_subscriptions_last_order_date_created' ], 10, 2 );
82+
83+
$cache_manager = new WCS_Object_Data_Cache_Manager(
84+
'subscription',
85+
[
86+
'parent_id',
87+
]
88+
);
89+
$cache_manager->init();
90+
91+
add_action( 'wcs_update_post_meta_caches', [ __CLASS__, 'update_subscription_last_order_date_parent_id_changes' ], 10, 5 );
92+
93+
add_action( 'wcs_orders_add_relation', [ __CLASS__, 'add_relation_update_order_related_subscriptions_last_order_date_created' ], 10, 3 );
94+
95+
add_action( 'wcs_orders_delete_relation', [ __CLASS__, 'delete_relation_update_order_related_subscriptions_last_order_date_created' ], 10, 3 );
8096
}
8197

8298
/*
@@ -2396,6 +2412,92 @@ public static function get_meta( $order, $meta_key, $default = 0 ) {
23962412
return $meta_value;
23972413
}
23982414

2415+
/**
2416+
* Update subscription cached last_order_date_created metadata when deleting a child order.
2417+
*
2418+
* @param int $id The deleted order ID.
2419+
* @param WC_Order $order The deleted order object.
2420+
*/
2421+
public static function delete_order_update_order_related_subscriptions_last_order_date_created( $id, $order ) {
2422+
if ( $order->get_created_via() !== 'subscription' ) {
2423+
return;
2424+
}
2425+
2426+
self::update_order_related_subscriptions_last_order_date_created( $order, [ 'trash' ] );
2427+
}
2428+
2429+
/**
2430+
* Update subscription cached last_order_date_created metadata when adding a child order.
2431+
*
2432+
* @param WC_Order $order The order to link with the subscription.
2433+
* @param WC_Order $subscription The order or subscription to link the order to.
2434+
* @param string $relation_type The relationship between the subscription and the order. Must be 'renewal', 'switch' or 'resubscribe' unless custom relationships are implemented.
2435+
*/
2436+
public static function add_relation_update_order_related_subscriptions_last_order_date_created( $order, $subscription, $relation_type ) {
2437+
self::update_order_related_subscriptions_last_order_date_created( $order );
2438+
}
2439+
2440+
/**
2441+
* Update subscription cached last_order_date_created metadata when deleting a child order relation.
2442+
*
2443+
* @param WC_Order $order An order that may be linked with subscriptions.
2444+
* @param WC_Order $subscription A subscription or order to unlink the order with, if a relation exists.
2445+
* @param string $relation_type The relationship between the subscription and the order. Must be 'renewal', 'switch' or 'resubscribe' unless custom relationships are implemented.
2446+
*/
2447+
public static function delete_relation_update_order_related_subscriptions_last_order_date_created( $order, $subscription, $relation_type ) {
2448+
self::update_subscription_last_order_date_created( $subscription );
2449+
2450+
self::update_order_related_subscriptions_last_order_date_created( $order );
2451+
}
2452+
2453+
/**
2454+
* Update all subscription cached last_order_date_created metadata related to the order.
2455+
*
2456+
* @param WC_Order $order The order object.
2457+
* @param array $exclude_statuses The order statuses to exclude.
2458+
*/
2459+
private static function update_order_related_subscriptions_last_order_date_created( $order, $exclude_statuses = [] ) {
2460+
$subscription_ids = wcs_get_subscription_ids_for_order( $order );
2461+
2462+
foreach ( $subscription_ids as $subscription_id ) {
2463+
$subscription = wcs_get_subscription( $subscription_id );
2464+
2465+
self::update_subscription_last_order_date_created( $subscription, $exclude_statuses );
2466+
}
2467+
}
2468+
2469+
/**
2470+
* Update subscription cached last_order_date_created metadata when manually updating parent id.
2471+
*
2472+
* @param string $type The type of update to check. Only 'add' or 'delete' should be used.
2473+
* @param int $object_id The object the meta is being changed on.
2474+
* @param string $key The object meta key being changed.
2475+
* @param mixed $new_value The meta value.
2476+
* @param mixed $previous_value The previous value stored in the database. Optional.
2477+
*/
2478+
public static function update_subscription_last_order_date_parent_id_changes( $type, $object_id, $key, $new_value, $previous_value ) {
2479+
if ( 'parent_id' !== $key || empty( $new_value ) ) {
2480+
return;
2481+
}
2482+
2483+
$subscription = wcs_get_subscription( $object_id );
2484+
2485+
self::update_subscription_last_order_date_created( $subscription );
2486+
}
2487+
2488+
/**
2489+
* Update subscription cached last_order_date_created metadata.
2490+
*
2491+
* @param WC_Subscription $subscription The subscription object.
2492+
* @param array $exclude_statuses The order statuses to exclude.
2493+
*/
2494+
private static function update_subscription_last_order_date_created( $subscription, $exclude_statuses = [] ) {
2495+
$last_order_date_created = $subscription->get_time( 'last_order_date_created', 'gmt', $exclude_statuses );
2496+
2497+
$subscription->set_last_order_date_created( $last_order_date_created );
2498+
$subscription->save();
2499+
}
2500+
23992501
/**
24002502
* Prints the HTML for the admin dropdown for order types to Woocommerce -> Orders screen.
24012503
*

includes/data-stores/class-wcs-orders-table-subscription-data-store.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class WCS_Orders_Table_Subscription_Data_Store extends \Automattic\WooCommerce\I
4141
'_cancelled_email_sent' => 'cancelled_email_sent',
4242
'_requires_manual_renewal' => 'requires_manual_renewal',
4343
'_trial_period' => 'trial_period',
44+
'_last_order_date_created' => 'last_order_date_created',
4445

4546
'_schedule_trial_end' => 'schedule_trial_end',
4647
'_schedule_next_payment' => 'schedule_next_payment',

includes/data-stores/class-wcs-related-order-store-cpt.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ public function add_relation( WC_Order $order, WC_Order $subscription, $relation
103103
$order->add_meta_data( $related_order_meta_key, $subscription_id, false );
104104
$order->save();
105105
}
106+
107+
do_action( 'wcs_orders_add_relation', $order, $subscription, $relation_type );
106108
}
107109

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

126128
$order->save();
129+
130+
do_action( 'wcs_orders_delete_relation', $order, $subscription, $relation_type );
127131
}
128132

129133
/**

includes/data-stores/class-wcs-subscription-data-store-cpt.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class WCS_Subscription_Data_Store_CPT extends WC_Order_Data_Store_CPT implements
4949
'_cancelled_email_sent' => 'cancelled_email_sent',
5050
'_requires_manual_renewal' => 'requires_manual_renewal',
5151
'_trial_period' => 'trial_period',
52+
'_last_order_date_created' => 'last_order_date_created',
5253

5354
'_schedule_trial_end' => 'schedule_trial_end',
5455
'_schedule_next_payment' => 'schedule_next_payment',

includes/wcs-order-functions.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,10 @@ function wcs_create_order_from_subscription( $subscription, $type ) {
277277
throw new Exception( sprintf( __( 'There was an error fetching the new order (%1$s) for subscription %2$d.', 'woocommerce-subscriptions' ), $type, $subscription->get_id() ) );
278278
}
279279

280+
// Update the subscription last_order_date_created on every time a child order is created.
281+
$subscription->set_last_order_date_created( $new_order->get_date_created()->getTimestamp() );
282+
$subscription->save();
283+
280284
/**
281285
* Filters the new order created from the subscription.
282286
*

0 commit comments

Comments
 (0)