Skip to content

Commit 98dfba7

Browse files
authored
Performance improvements: rendering the Subscription Relationship column and checking if order is a renewal, switch etc. (#732)
* Introduce new function to check if an order is a subscription parent order * Refactor our wcs_order_contains_x() functions to be more performant * Update render_contains_subscription_column_content() to accept order object and call more performant contains functions * Add changelog entry * Replace count(wcs_get_subscriptions_for_order()) with new is_parent_order() function * Dont render the inefficient parent order distinction on orders list table * Move checking for parent order as the final elseif as it's the most resource intensive query * Call wcs_is_order() before passing the order to get_related_subscription_ids() * Introduce new wrapper for WCS_Related_Order_Store get_related_subscription_ids() * Use new wcs_get_subscription_ids_for_order() in order contains functions * Update wcs_get_subscriptions_for_order() to use new get_related_subscription_ids() wrapper * Add support for fetching parent order types as well * Add unit tests * Fix unit tests to match expected array returned (ids in DESC order) * Order by IDs * Always query for parent order relations if parent is passed - don't make false assumptions * Add support for fetching subscription IDs for any order type * add default behaviour details to docblock * Add unit tests for a realistic resubscribe case * Sort the returned array of subscription IDs * Ensure backwards compatibility with filter hooks passing WC order objects * Update changelog * Rename wcs_is_parent_order() to wcs_order_contains_parent() for consistency
1 parent c6441e9 commit 98dfba7

7 files changed

+166
-43
lines changed

changelog.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
= 8.1.0 - 2025-xx-xx =
44
* Update - Improved subscription search performance for WP Post stores by removing unnecessary _order_key and _billing_email meta queries.
5+
* Update - Improved performance on the Orders list table when rendering the Subscription Relationship column.
56
* Dev - Fix Node version mismatch between package.json and .nvmrc (both are now set to v16.17.1).
67

78
= 8.0.1 - 2025-02-13 =

includes/class-wc-subscriptions-order.php

+12-6
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ public static function add_contains_subscription_column_content( $column ) {
451451
*/
452452
public static function add_contains_subscription_column_content_orders_table( string $column_name, WC_Order $order ) {
453453
if ( 'subscription_relationship' === $column_name ) {
454-
self::render_contains_subscription_column_content( $order->get_id() );
454+
self::render_contains_subscription_column_content( $order );
455455
}
456456
}
457457

@@ -2442,14 +2442,20 @@ private static function render_restrict_manage_subscriptions_dropdown() {
24422442
*
24432443
* @since 6.3.0
24442444
*
2445-
* @param integer $order_id The ID of the order in the current row.
2445+
* @param WC_Order $order The order in the current row.
24462446
*/
2447-
private static function render_contains_subscription_column_content( int $order_id ) {
2448-
if ( wcs_order_contains_subscription( $order_id, 'renewal' ) ) {
2447+
private static function render_contains_subscription_column_content( $order ) {
2448+
$order = ! is_object( $order ) ? wc_get_order( $order ) : $order;
2449+
2450+
if ( ! $order ) {
2451+
return;
2452+
}
2453+
2454+
if ( wcs_order_contains_renewal( $order ) ) {
24492455
echo '<span class="subscription_renewal_order tips" data-tip="' . esc_attr__( 'Renewal Order', 'woocommerce-subscriptions' ) . '"></span>';
2450-
} elseif ( wcs_order_contains_subscription( $order_id, 'resubscribe' ) ) {
2456+
} elseif ( wcs_order_contains_resubscribe( $order ) ) {
24512457
echo '<span class="subscription_resubscribe_order tips" data-tip="' . esc_attr__( 'Resubscribe Order', 'woocommerce-subscriptions' ) . '"></span>';
2452-
} elseif ( wcs_order_contains_subscription( $order_id, 'parent' ) ) {
2458+
} elseif ( apply_filters( 'woocommerce_subscriptions_orders_list_render_parent_order_relation', false, $order ) && wcs_order_contains_parent( $order ) ) {
24532459
echo '<span class="subscription_parent_order tips" data-tip="' . esc_attr__( 'Parent Order', 'woocommerce-subscriptions' ) . '"></span>';
24542460
} else {
24552461
echo '<span class="normal_order">&ndash;</span>';

includes/wcs-order-functions.php

+96-11
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,67 @@ function wcs_get_subscriptions_for_order( $order, $args = array() ) {
6868

6969
$all_relation_types = WCS_Related_Order_Store::instance()->get_relation_types();
7070
$relation_types = $get_all ? $all_relation_types : array_intersect( $all_relation_types, $args['order_type'] );
71+
$subscription_ids = wcs_get_subscription_ids_for_order( $order, $relation_types );
7172

72-
foreach ( $relation_types as $relation_type ) {
73+
foreach ( $subscription_ids as $subscription_id ) {
74+
if ( wcs_is_subscription( $subscription_id ) ) {
75+
$subscriptions[ $subscription_id ] = wcs_get_subscription( $subscription_id );
76+
}
77+
}
7378

74-
$subscription_ids = WCS_Related_Order_Store::instance()->get_related_subscription_ids( $order, $relation_type );
79+
return $subscriptions;
80+
}
7581

76-
foreach ( $subscription_ids as $subscription_id ) {
77-
if ( wcs_is_subscription( $subscription_id ) ) {
78-
$subscriptions[ $subscription_id ] = wcs_get_subscription( $subscription_id );
79-
}
82+
/**
83+
* Get the subscription IDs for an order.
84+
*
85+
* @param WC_Order $order The order to get the subscription IDs for.
86+
* @param string|array $order_types The order types to get the subscription IDs for. Defaults to 'any' which will return all subscription IDs linked to the order.
87+
*
88+
* @return array The subscription IDs.
89+
*/
90+
function wcs_get_subscription_ids_for_order( $order, $order_types = [ 'any' ] ) {
91+
$subscription_ids = [];
92+
93+
if ( ! is_a( $order, 'WC_Abstract_Order' ) ) {
94+
$order = wc_get_order( $order );
95+
}
96+
97+
if ( ! wcs_is_order( $order ) ) {
98+
return $subscription_ids;
99+
}
100+
101+
if ( ! is_array( $order_types ) ) {
102+
$order_types = [ $order_types ];
103+
}
104+
105+
$get_all = in_array( 'any', $order_types, true );
106+
$relation_types = WCS_Related_Order_Store::instance()->get_relation_types();
107+
$valid_order_types = $get_all ? $relation_types : array_intersect( $relation_types, $order_types );
108+
109+
foreach ( $valid_order_types as $order_type ) {
110+
$subscription_ids = array_merge( $subscription_ids, WCS_Related_Order_Store::instance()->get_related_subscription_ids( $order, $order_type ) );
111+
}
112+
113+
if ( $get_all || in_array( 'parent', $order_types, true ) ) {
114+
$subscription_ids_for_parent_order = wc_get_orders(
115+
[
116+
'parent' => $order->get_id(),
117+
'type' => 'shop_subscription',
118+
'status' => 'any',
119+
'limit' => -1,
120+
'return' => 'ids',
121+
]
122+
);
123+
124+
if ( is_array( $subscription_ids_for_parent_order ) ) {
125+
$subscription_ids = array_merge( $subscription_ids, $subscription_ids_for_parent_order );
80126
}
81127
}
82128

83-
return $subscriptions;
129+
rsort( $subscription_ids );
130+
131+
return $subscription_ids;
84132
}
85133

86134
/**
@@ -365,10 +413,7 @@ function wcs_order_contains_subscription( $order, $order_type = array( 'parent',
365413
$contains_subscription = false;
366414
$get_all = in_array( 'any', $order_type, true );
367415

368-
if ( ( in_array( 'parent', $order_type, true ) || $get_all ) && count( wcs_get_subscriptions_for_order( $order->get_id(), array( 'order_type' => 'parent' ) ) ) > 0 ) {
369-
$contains_subscription = true;
370-
371-
} elseif ( ( in_array( 'renewal', $order_type, true ) || $get_all ) && wcs_order_contains_renewal( $order ) ) {
416+
if ( ( in_array( 'renewal', $order_type, true ) || $get_all ) && wcs_order_contains_renewal( $order ) ) {
372417
$contains_subscription = true;
373418

374419
} elseif ( ( in_array( 'resubscribe', $order_type, true ) || $get_all ) && wcs_order_contains_resubscribe( $order ) ) {
@@ -377,6 +422,8 @@ function wcs_order_contains_subscription( $order, $order_type = array( 'parent',
377422
} elseif ( ( in_array( 'switch', $order_type, true ) || $get_all ) && wcs_order_contains_switch( $order ) ) {
378423
$contains_subscription = true;
379424

425+
} elseif ( ( in_array( 'parent', $order_type, true ) || $get_all ) && wcs_order_contains_parent( $order ) ) {
426+
$contains_subscription = true;
380427
}
381428

382429
return $contains_subscription;
@@ -1055,3 +1102,41 @@ function wcs_set_recurring_item_total( &$item ) {
10551102
]
10561103
);
10571104
}
1105+
1106+
/**
1107+
* Checks if an order is a Subscriptions parent/initial order.
1108+
*
1109+
* @param WC_Order|int $order The WC_Order object or ID of a WC_Order order.
1110+
*
1111+
* @return bool Whether the order contains a parent.
1112+
*/
1113+
function wcs_order_contains_parent( $order ) {
1114+
$order = ! is_object( $order ) ? wc_get_order( $order ) : $order;
1115+
1116+
if ( ! $order || ! wcs_is_order( $order ) ) {
1117+
return false;
1118+
}
1119+
1120+
// Check if the order ID is the parent of a subscription.
1121+
$is_parent_order = wc_get_orders(
1122+
[
1123+
'parent' => $order->get_id(),
1124+
'type' => 'shop_subscription',
1125+
'status' => 'any',
1126+
'limit' => 1,
1127+
'return' => 'ids',
1128+
]
1129+
);
1130+
1131+
/**
1132+
* Allow third-parties to filter whether this order should be considered a parent order.
1133+
*
1134+
* @since 7.3.0
1135+
*
1136+
* @param bool $is_parent_order True if parent meta was found on the order, otherwise false.
1137+
* @param WC_Order $order The WC_Order object.
1138+
*
1139+
* @return bool True if the order contains a parent, otherwise false.
1140+
*/
1141+
return apply_filters( 'woocommerce_subscriptions_is_parent_order', ! empty( $is_parent_order ), $order );
1142+
}

includes/wcs-renewal-functions.php

+5-7
Original file line numberDiff line numberDiff line change
@@ -56,20 +56,18 @@ function wcs_create_renewal_order( $subscription ) {
5656
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
5757
*/
5858
function wcs_order_contains_renewal( $order ) {
59+
$is_renewal_order = false;
5960

6061
if ( ! is_a( $order, 'WC_Abstract_Order' ) ) {
6162
$order = wc_get_order( $order );
6263
}
6364

64-
$related_subscriptions = wcs_get_subscriptions_for_renewal_order( $order );
65-
66-
if ( wcs_is_order( $order ) && ! empty( $related_subscriptions ) ) {
67-
$is_renewal = true;
68-
} else {
69-
$is_renewal = false;
65+
if ( $order ) {
66+
$related_subscription_ids = wcs_get_subscription_ids_for_order( $order, 'renewal' );
67+
$is_renewal_order = ! empty( $related_subscription_ids );
7068
}
7169

72-
return apply_filters( 'woocommerce_subscriptions_is_renewal_order', $is_renewal, $order );
70+
return apply_filters( 'woocommerce_subscriptions_is_renewal_order', $is_renewal_order, $order );
7371
}
7472

7573
/**

includes/wcs-resubscribe-functions.php

+4-6
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,15 @@
2121
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
2222
*/
2323
function wcs_order_contains_resubscribe( $order ) {
24+
$is_resubscribe_order = false;
2425

2526
if ( ! is_a( $order, 'WC_Abstract_Order' ) ) {
2627
$order = wc_get_order( $order );
2728
}
2829

29-
$related_subscriptions = wcs_get_subscriptions_for_resubscribe_order( $order );
30-
31-
if ( wcs_is_order( $order ) && ! empty( $related_subscriptions ) ) {
32-
$is_resubscribe_order = true;
33-
} else {
34-
$is_resubscribe_order = false;
30+
if ( $order ) {
31+
$related_subscription_ids = wcs_get_subscription_ids_for_order( $order, 'resubscribe' );
32+
$is_resubscribe_order = ! empty( $related_subscription_ids );
3533
}
3634

3735
return apply_filters( 'woocommerce_subscriptions_is_resubscribe_order', $is_resubscribe_order, $order );

includes/wcs-switch-functions.php

+4-13
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,15 @@
1919
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
2020
*/
2121
function wcs_order_contains_switch( $order ) {
22+
$is_switch_order = false;
2223

2324
if ( ! is_a( $order, 'WC_Abstract_Order' ) ) {
2425
$order = wc_get_order( $order );
2526
}
2627

27-
if ( ! wcs_is_order( $order ) || wcs_order_contains_renewal( $order ) ) {
28-
29-
$is_switch_order = false;
30-
31-
} else {
32-
33-
$switched_subscriptions = wcs_get_subscriptions_for_switch_order( $order );
34-
35-
if ( ! empty( $switched_subscriptions ) ) {
36-
$is_switch_order = true;
37-
} else {
38-
$is_switch_order = false;
39-
}
28+
if ( $order ) {
29+
$related_subscription_ids = wcs_get_subscription_ids_for_order( $order, 'switch' );
30+
$is_switch_order = ! empty( $related_subscription_ids );
4031
}
4132

4233
return apply_filters( 'woocommerce_subscriptions_is_switch_order', $is_switch_order, $order );

tests/unit/test-wcs-order-functions.php

+44
Original file line numberDiff line numberDiff line change
@@ -422,4 +422,48 @@ public function test_wcs_set_recurring_item_total() {
422422
$this->assertEquals( 80, $line_item->get_total() );
423423
$this->assertEquals( 80, $line_item->get_subtotal() );
424424
}
425+
426+
/**
427+
* Tests for wcs_get_subscription_ids_for_order()
428+
*/
429+
public function test_wcs_get_subscription_ids_for_order() {
430+
$subscription = WCS_Helper_Subscription::create_subscription();
431+
$parent_order = WCS_Helper_Subscription::create_order( [ 'customer_id' => $subscription->get_customer_id() ] );
432+
433+
$this->assertEquals( [], wcs_get_subscription_ids_for_order( $parent_order, 'parent' ) );
434+
435+
$subscription->set_parent_id( $parent_order->get_id() );
436+
$subscription->save();
437+
438+
$this->assertEquals( [ $subscription->get_id() ], wcs_get_subscription_ids_for_order( $parent_order, 'parent' ) );
439+
440+
$this->assertEquals( [], wcs_get_subscription_ids_for_order( $parent_order, 'renewal' ) );
441+
$this->assertEquals( [], wcs_get_subscription_ids_for_order( $parent_order, 'switch' ) );
442+
$this->assertEquals( [], wcs_get_subscription_ids_for_order( $parent_order, 'resubscribe' ) );
443+
444+
$renewal_order = WCS_Helper_Subscription::create_renewal_order( $subscription );
445+
$switch_order = WCS_Helper_Subscription::create_switch_order( $subscription );
446+
447+
// The resubscribe order is also a parent order to the new subscription.
448+
$resubscribe_order = WCS_Helper_Subscription::create_related_order( $subscription, 'resubscribe' );
449+
$subscription_2 = WCS_Helper_Subscription::create_subscription();
450+
451+
$subscription_2->set_parent_id( $resubscribe_order->get_id() );
452+
$subscription_2->save();
453+
454+
$this->assertEquals( [ $subscription_2->get_id(), $subscription->get_id() ], wcs_get_subscription_ids_for_order( $resubscribe_order, [ 'any' ] ) );
455+
456+
$this->assertEquals( [ $subscription->get_id() ], wcs_get_subscription_ids_for_order( $renewal_order, 'renewal' ) );
457+
$this->assertEquals( [ $subscription->get_id() ], wcs_get_subscription_ids_for_order( $switch_order, 'switch' ) );
458+
$this->assertEquals( [ $subscription->get_id() ], wcs_get_subscription_ids_for_order( $resubscribe_order, 'resubscribe' ) );
459+
460+
$this->assertEquals( [ $subscription->get_id() ], wcs_get_subscription_ids_for_order( $renewal_order, [ 'renewal', 'parent' ] ) );
461+
$this->assertEquals( [ $subscription->get_id() ], wcs_get_subscription_ids_for_order( $parent_order, [ 'switch', 'parent' ] ) );
462+
463+
$subscription_3 = WCS_Helper_Subscription::create_subscription();
464+
$subscription_3->set_parent_id( $parent_order->get_id() );
465+
$subscription_3->save();
466+
467+
$this->assertEquals( [ $subscription_3->get_id(), $subscription->get_id() ], wcs_get_subscription_ids_for_order( $parent_order, 'parent' ) );
468+
}
425469
}

0 commit comments

Comments
 (0)