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

Performance improvements: rendering the Subscription Relationship column and checking if order is a renewal, switch etc. #732

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
dd1299a
Introduce new function to check if an order is a subscription parent …
mattallan Nov 20, 2024
44321ec
Refactor our wcs_order_contains_x() functions to be more performant
mattallan Nov 20, 2024
976c961
Update render_contains_subscription_column_content() to accept order …
mattallan Nov 20, 2024
74a9e5a
Add changelog entry
mattallan Nov 20, 2024
fc91135
Replace count(wcs_get_subscriptions_for_order()) with new is_parent_o…
mattallan Nov 20, 2024
b8f94ab
Merge branch 'trunk' into for/731-improve-performance-rendering-order…
mattallan Feb 20, 2025
adeaeca
Dont render the inefficient parent order distinction on orders list t…
mattallan Feb 20, 2025
1122373
Move checking for parent order as the final elseif as it's the most r…
mattallan Feb 20, 2025
ec44e56
Call wcs_is_order() before passing the order to get_related_subscript…
mattallan Feb 25, 2025
de03e43
Introduce new wrapper for WCS_Related_Order_Store get_related_subscri…
mattallan Feb 25, 2025
ad40418
Use new wcs_get_subscription_ids_for_order() in order contains functions
mattallan Feb 25, 2025
7598c36
Update wcs_get_subscriptions_for_order() to use new get_related_subsc…
mattallan Feb 25, 2025
c6b4d21
Add support for fetching parent order types as well
mattallan Feb 25, 2025
ecf3bdf
Add unit tests
mattallan Feb 25, 2025
6621e10
Fix unit tests to match expected array returned (ids in DESC order)
mattallan Feb 25, 2025
71baf7c
Order by IDs
mattallan Feb 25, 2025
5f237ab
Always query for parent order relations if parent is passed - don't m…
mattallan Feb 26, 2025
cd7ac49
Add support for fetching subscription IDs for any order type
mattallan Feb 26, 2025
4051a4c
add default behaviour details to docblock
mattallan Feb 26, 2025
9dd21cf
Add unit tests for a realistic resubscribe case
mattallan Feb 26, 2025
b4bcb76
Sort the returned array of subscription IDs
mattallan Feb 26, 2025
d492796
Ensure backwards compatibility with filter hooks passing WC order obj…
mattallan Feb 26, 2025
1de39ec
Merge branch 'trunk' into for/731-improve-performance-rendering-order…
mattallan Feb 26, 2025
05813de
Update changelog
mattallan Feb 26, 2025
d1ea790
Rename wcs_is_parent_order() to wcs_order_contains_parent() for consi…
mattallan Feb 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

= 8.0.1 - 2025-02-13 =
Expand Down
18 changes: 12 additions & 6 deletions includes/class-wc-subscriptions-order.php
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ public static function add_contains_subscription_column_content( $column ) {
*/
public static function add_contains_subscription_column_content_orders_table( string $column_name, WC_Order $order ) {
if ( 'subscription_relationship' === $column_name ) {
self::render_contains_subscription_column_content( $order->get_id() );
self::render_contains_subscription_column_content( $order );
}
}

Expand Down Expand Up @@ -2442,14 +2442,20 @@ private static function render_restrict_manage_subscriptions_dropdown() {
*
* @since 6.3.0
*
* @param integer $order_id The ID of the order in the current row.
* @param WC_Order $order The order in the current row.
*/
private static function render_contains_subscription_column_content( int $order_id ) {
if ( wcs_order_contains_subscription( $order_id, 'renewal' ) ) {
private static function render_contains_subscription_column_content( $order ) {
$order = ! is_object( $order ) ? wc_get_order( $order ) : $order;

if ( ! $order ) {
return;
}

if ( wcs_order_contains_renewal( $order ) ) {
echo '<span class="subscription_renewal_order tips" data-tip="' . esc_attr__( 'Renewal Order', 'woocommerce-subscriptions' ) . '"></span>';
} elseif ( wcs_order_contains_subscription( $order_id, 'resubscribe' ) ) {
} elseif ( wcs_order_contains_resubscribe( $order ) ) {
echo '<span class="subscription_resubscribe_order tips" data-tip="' . esc_attr__( 'Resubscribe Order', 'woocommerce-subscriptions' ) . '"></span>';
} elseif ( wcs_order_contains_subscription( $order_id, 'parent' ) ) {
} elseif ( apply_filters( 'woocommerce_subscriptions_orders_list_render_parent_order_relation', false, $order ) && wcs_order_contains_parent( $order ) ) {
echo '<span class="subscription_parent_order tips" data-tip="' . esc_attr__( 'Parent Order', 'woocommerce-subscriptions' ) . '"></span>';
} else {
echo '<span class="normal_order">&ndash;</span>';
Expand Down
107 changes: 96 additions & 11 deletions includes/wcs-order-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,67 @@ function wcs_get_subscriptions_for_order( $order, $args = array() ) {

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

foreach ( $relation_types as $relation_type ) {
foreach ( $subscription_ids as $subscription_id ) {
if ( wcs_is_subscription( $subscription_id ) ) {
$subscriptions[ $subscription_id ] = wcs_get_subscription( $subscription_id );
}
}

$subscription_ids = WCS_Related_Order_Store::instance()->get_related_subscription_ids( $order, $relation_type );
return $subscriptions;
}

foreach ( $subscription_ids as $subscription_id ) {
if ( wcs_is_subscription( $subscription_id ) ) {
$subscriptions[ $subscription_id ] = wcs_get_subscription( $subscription_id );
}
/**
* Get the subscription IDs for an order.
*
* @param WC_Order $order The order to get the subscription IDs for.
* @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.
*
* @return array The subscription IDs.
*/
function wcs_get_subscription_ids_for_order( $order, $order_types = [ 'any' ] ) {
$subscription_ids = [];

if ( ! is_a( $order, 'WC_Abstract_Order' ) ) {
$order = wc_get_order( $order );
}

if ( ! wcs_is_order( $order ) ) {
return $subscription_ids;
}

if ( ! is_array( $order_types ) ) {
$order_types = [ $order_types ];
}

$get_all = in_array( 'any', $order_types, true );
$relation_types = WCS_Related_Order_Store::instance()->get_relation_types();
$valid_order_types = $get_all ? $relation_types : array_intersect( $relation_types, $order_types );

foreach ( $valid_order_types as $order_type ) {
$subscription_ids = array_merge( $subscription_ids, WCS_Related_Order_Store::instance()->get_related_subscription_ids( $order, $order_type ) );
}

if ( $get_all || in_array( 'parent', $order_types, true ) ) {
$subscription_ids_for_parent_order = wc_get_orders(
[
'parent' => $order->get_id(),
'type' => 'shop_subscription',
'status' => 'any',
'limit' => -1,
'return' => 'ids',
]
);

if ( is_array( $subscription_ids_for_parent_order ) ) {
$subscription_ids = array_merge( $subscription_ids, $subscription_ids_for_parent_order );
}
}

return $subscriptions;
rsort( $subscription_ids );

return $subscription_ids;
}

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

if ( ( in_array( 'parent', $order_type, true ) || $get_all ) && count( wcs_get_subscriptions_for_order( $order->get_id(), array( 'order_type' => 'parent' ) ) ) > 0 ) {
$contains_subscription = true;

} elseif ( ( in_array( 'renewal', $order_type, true ) || $get_all ) && wcs_order_contains_renewal( $order ) ) {
if ( ( in_array( 'renewal', $order_type, true ) || $get_all ) && wcs_order_contains_renewal( $order ) ) {
$contains_subscription = true;

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

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

return $contains_subscription;
Expand Down Expand Up @@ -1055,3 +1102,41 @@ function wcs_set_recurring_item_total( &$item ) {
]
);
}

/**
* Checks if an order is a Subscriptions parent/initial order.
*
* @param WC_Order|int $order The WC_Order object or ID of a WC_Order order.
*
* @return bool Whether the order contains a parent.
*/
function wcs_order_contains_parent( $order ) {
$order = ! is_object( $order ) ? wc_get_order( $order ) : $order;

if ( ! $order || ! wcs_is_order( $order ) ) {
return false;
}

// Check if the order ID is the parent of a subscription.
$is_parent_order = wc_get_orders(
[
'parent' => $order->get_id(),
'type' => 'shop_subscription',
'status' => 'any',
'limit' => 1,
'return' => 'ids',
]
);

/**
* Allow third-parties to filter whether this order should be considered a parent order.
*
* @since 7.3.0
*
* @param bool $is_parent_order True if parent meta was found on the order, otherwise false.
* @param WC_Order $order The WC_Order object.
*
* @return bool True if the order contains a parent, otherwise false.
*/
return apply_filters( 'woocommerce_subscriptions_is_parent_order', ! empty( $is_parent_order ), $order );
}
12 changes: 5 additions & 7 deletions includes/wcs-renewal-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,18 @@ function wcs_create_renewal_order( $subscription ) {
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
function wcs_order_contains_renewal( $order ) {
$is_renewal_order = false;

if ( ! is_a( $order, 'WC_Abstract_Order' ) ) {
$order = wc_get_order( $order );
}

$related_subscriptions = wcs_get_subscriptions_for_renewal_order( $order );

if ( wcs_is_order( $order ) && ! empty( $related_subscriptions ) ) {
$is_renewal = true;
} else {
$is_renewal = false;
if ( $order ) {
$related_subscription_ids = wcs_get_subscription_ids_for_order( $order, 'renewal' );
$is_renewal_order = ! empty( $related_subscription_ids );
}

return apply_filters( 'woocommerce_subscriptions_is_renewal_order', $is_renewal, $order );
return apply_filters( 'woocommerce_subscriptions_is_renewal_order', $is_renewal_order, $order );
}

/**
Expand Down
10 changes: 4 additions & 6 deletions includes/wcs-resubscribe-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,15 @@
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
function wcs_order_contains_resubscribe( $order ) {
$is_resubscribe_order = false;

if ( ! is_a( $order, 'WC_Abstract_Order' ) ) {
$order = wc_get_order( $order );
}

$related_subscriptions = wcs_get_subscriptions_for_resubscribe_order( $order );

if ( wcs_is_order( $order ) && ! empty( $related_subscriptions ) ) {
$is_resubscribe_order = true;
} else {
$is_resubscribe_order = false;
if ( $order ) {
$related_subscription_ids = wcs_get_subscription_ids_for_order( $order, 'resubscribe' );
$is_resubscribe_order = ! empty( $related_subscription_ids );
}

return apply_filters( 'woocommerce_subscriptions_is_resubscribe_order', $is_resubscribe_order, $order );
Expand Down
17 changes: 4 additions & 13 deletions includes/wcs-switch-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,15 @@
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
*/
function wcs_order_contains_switch( $order ) {
$is_switch_order = false;

if ( ! is_a( $order, 'WC_Abstract_Order' ) ) {
$order = wc_get_order( $order );
}

if ( ! wcs_is_order( $order ) || wcs_order_contains_renewal( $order ) ) {

$is_switch_order = false;

} else {

$switched_subscriptions = wcs_get_subscriptions_for_switch_order( $order );

if ( ! empty( $switched_subscriptions ) ) {
$is_switch_order = true;
} else {
$is_switch_order = false;
}
if ( $order ) {
$related_subscription_ids = wcs_get_subscription_ids_for_order( $order, 'switch' );
$is_switch_order = ! empty( $related_subscription_ids );
}

return apply_filters( 'woocommerce_subscriptions_is_switch_order', $is_switch_order, $order );
Expand Down
44 changes: 44 additions & 0 deletions tests/unit/test-wcs-order-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -422,4 +422,48 @@ public function test_wcs_set_recurring_item_total() {
$this->assertEquals( 80, $line_item->get_total() );
$this->assertEquals( 80, $line_item->get_subtotal() );
}

/**
* Tests for wcs_get_subscription_ids_for_order()
*/
public function test_wcs_get_subscription_ids_for_order() {
$subscription = WCS_Helper_Subscription::create_subscription();
$parent_order = WCS_Helper_Subscription::create_order( [ 'customer_id' => $subscription->get_customer_id() ] );

$this->assertEquals( [], wcs_get_subscription_ids_for_order( $parent_order, 'parent' ) );

$subscription->set_parent_id( $parent_order->get_id() );
$subscription->save();

$this->assertEquals( [ $subscription->get_id() ], wcs_get_subscription_ids_for_order( $parent_order, 'parent' ) );

$this->assertEquals( [], wcs_get_subscription_ids_for_order( $parent_order, 'renewal' ) );
$this->assertEquals( [], wcs_get_subscription_ids_for_order( $parent_order, 'switch' ) );
$this->assertEquals( [], wcs_get_subscription_ids_for_order( $parent_order, 'resubscribe' ) );

$renewal_order = WCS_Helper_Subscription::create_renewal_order( $subscription );
$switch_order = WCS_Helper_Subscription::create_switch_order( $subscription );

// The resubscribe order is also a parent order to the new subscription.
$resubscribe_order = WCS_Helper_Subscription::create_related_order( $subscription, 'resubscribe' );
$subscription_2 = WCS_Helper_Subscription::create_subscription();

$subscription_2->set_parent_id( $resubscribe_order->get_id() );
$subscription_2->save();

$this->assertEquals( [ $subscription_2->get_id(), $subscription->get_id() ], wcs_get_subscription_ids_for_order( $resubscribe_order, [ 'any' ] ) );

$this->assertEquals( [ $subscription->get_id() ], wcs_get_subscription_ids_for_order( $renewal_order, 'renewal' ) );
$this->assertEquals( [ $subscription->get_id() ], wcs_get_subscription_ids_for_order( $switch_order, 'switch' ) );
$this->assertEquals( [ $subscription->get_id() ], wcs_get_subscription_ids_for_order( $resubscribe_order, 'resubscribe' ) );

$this->assertEquals( [ $subscription->get_id() ], wcs_get_subscription_ids_for_order( $renewal_order, [ 'renewal', 'parent' ] ) );
$this->assertEquals( [ $subscription->get_id() ], wcs_get_subscription_ids_for_order( $parent_order, [ 'switch', 'parent' ] ) );

$subscription_3 = WCS_Helper_Subscription::create_subscription();
$subscription_3->set_parent_id( $parent_order->get_id() );
$subscription_3->save();

$this->assertEquals( [ $subscription_3->get_id(), $subscription->get_id() ], wcs_get_subscription_ids_for_order( $parent_order, 'parent' ) );
}
}
Loading