Add a filter to OrdersTableQuery to allow overriding of HPOS queries (#39945)

* Add a filter to OrdersTableQuery to allow overriding of HPOS queries

* add changelog file

* address review comments

* move `woocommerce_hpos_pre_query` filter into `maybe_override_query` method

* specify and ensure that the 3-tuple param / return value can also be null

* document the $sql param

* remove debugging echo

* tweak wording

Co-authored-by: Vedanshu Jain <vedanshu.jain.2012@gmail.com>

* remove unused variable

---------

Co-authored-by: Vedanshu Jain <vedanshu.jain.2012@gmail.com>
This commit is contained in:
Leif Singer 2023-09-12 18:18:51 +02:00 committed by GitHub
parent deee539245
commit 4a45c956ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 209 additions and 1 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add a filter to OrdersTableQuery to allow overriding of HPOS queries.

View File

@ -199,7 +199,52 @@ class OrdersTableQuery {
unset( $this->args['customer_note'], $this->args['name'] );
$this->build_query();
$this->run_query();
if ( ! $this->maybe_override_query() ) {
$this->run_query();
}
}
/**
* Lets the `woocommerce_hpos_pre_query` filter override the query.
*
* @return boolean Whether the query was overridden or not.
*/
private function maybe_override_query(): bool {
/**
* Filters the orders array before the query takes place.
*
* Return a non-null value to bypass the HPOS default order queries.
*
* If the query includes limits via the `limit`, `page`, or `offset` arguments, we
* encourage the `found_orders` and `max_num_pages` properties to also be set.
*
* @since 8.2.0
*
* @param array|null $order_data {
* An array of order data.
* @type int[] $orders Return an array of order IDs data to short-circuit the HPOS query,
* or null to allow HPOS to run its normal query.
* @type int $found_orders The number of orders found.
* @type int $max_num_pages The number of pages.
* }
* @param OrdersTableQuery $query The OrdersTableQuery instance.
* @param string $sql The OrdersTableQuery instance.
*/
list( $this->orders, $this->found_orders, $this->max_num_pages ) = apply_filters( 'woocommerce_hpos_pre_query', null, $this, $this->sql );
// If the filter set the orders, make sure the others values are set as well and skip running the query.
if ( is_array( $this->orders ) ) {
if ( ! is_int( $this->found_orders ) || $this->found_orders < 1 ) {
$this->found_orders = count( $this->orders );
}
if ( ! is_int( $this->max_num_pages ) || $this->max_num_pages < 1 ) {
if ( ! $this->arg_isset( 'limit' ) || ! is_int( $this->args['limit'] ) || $this->args['limit'] < 1 ) {
$this->args['limit'] = 10;
}
$this->max_num_pages = (int) ceil( $this->found_orders / $this->args['limit'] );
}
return true;
}
return false;
}
/**

View File

@ -1,5 +1,6 @@
<?php
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableQuery;
use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper;
use Automattic\WooCommerce\RestApi\UnitTests\HPOSToggleTrait;
use Automattic\WooCommerce\Utilities\OrderUtil;
@ -235,4 +236,162 @@ class OrdersTableQueryTests extends WC_Unit_Test_Case {
remove_all_filters( 'woocommerce_orders_table_query_clauses' );
}
/**
* @testdox The pre-query escape hook allows replacing the order query. The callback does not return pagination information.
*/
public function test_pre_query_escape_hook_simple() {
$order1 = new \WC_Order();
$order1->set_date_created( time() - HOUR_IN_SECONDS );
$order1->save();
$order2 = new \WC_Order();
$order2->save();
$query = new OrdersTableQuery( array() );
$this->assertCount( 2, $query->orders );
$this->assertEquals( 2, $query->found_orders );
$this->assertEquals( 0, $query->max_num_pages );
$callback = function( $result, $query_object, $sql ) use ( $order1 ) {
$this->assertNull( $result );
$this->assertInstanceOf( OrdersTableQuery::class, $query_object );
$this->assertStringContainsString( 'SELECT ', $sql );
// Only return one of the orders to show that we are replacing the query result.
// Do not return found_orders or max_num_pages to show we're setting defaults.
$order_ids = array( $order1->get_id() );
return array( $order_ids, null, null );
};
add_filter( 'woocommerce_hpos_pre_query', $callback, 10, 3 );
$query = new OrdersTableQuery( array() );
$this->assertCount( 1, $query->orders );
$this->assertEquals( 1, $query->found_orders );
$this->assertEquals( 1, $query->max_num_pages );
$this->assertEquals( $order1->get_id(), $query->orders[0] );
$orders = wc_get_orders( array() );
$this->assertCount( 1, $orders );
$this->assertEquals( $order1->get_id(), $orders[0]->get_id() );
remove_all_filters( 'woocommerce_hpos_pre_query' );
}
/**
* @testdox The pre-query escape hook allows replacing the order query. The callback returns pagination information.
*/
public function test_pre_query_escape_hook_with_pagination() {
$order1 = new \WC_Order();
$order1->set_date_created( time() - HOUR_IN_SECONDS );
$order1->save();
$order2 = new \WC_Order();
$order2->save();
$query = new OrdersTableQuery( array() );
$this->assertCount( 2, $query->orders );
$this->assertEquals( 2, $query->found_orders );
$this->assertEquals( 0, $query->max_num_pages );
$callback = function( $result, $query_object, $sql ) use ( $order1 ) {
$this->assertNull( $result );
$this->assertInstanceOf( OrdersTableQuery::class, $query_object );
$this->assertStringContainsString( 'SELECT ', $sql );
// Only return one of the orders to show that we are replacing the query result.
$order_ids = array( $order1->get_id() );
// These are made up to show that we are actually replacing the values.
$found_orders = 17;
$max_num_pages = 23;
return array( $order_ids, $found_orders, $max_num_pages );
};
add_filter( 'woocommerce_hpos_pre_query', $callback, 10, 3 );
$query = new OrdersTableQuery( array() );
$this->assertCount( 1, $query->orders );
$this->assertEquals( 17, $query->found_orders );
$this->assertEquals( 23, $query->max_num_pages );
$this->assertEquals( $order1->get_id(), $query->orders[0] );
$orders = wc_get_orders( array() );
$this->assertCount( 1, $orders );
$this->assertEquals( $order1->get_id(), $orders[0]->get_id() );
remove_all_filters( 'woocommerce_hpos_pre_query' );
}
/**
* @testdox The pre-query escape hook uses the limit arg if it is set.
*/
public function test_pre_query_escape_hook_pass_limit() {
$order1 = new \WC_Order();
$order1->set_date_created( time() - HOUR_IN_SECONDS );
$order1->save();
$callback = function( $result, $query_object, $sql ) use ( $order1 ) {
// Do not return found_orders or max_num_pages so as to provoke a warning.
$order_ids = array( $order1->get_id() );
return array( $order_ids, 10, null );
};
add_filter( 'woocommerce_hpos_pre_query', $callback, 10, 3 );
$query = new OrdersTableQuery(
array(
'limit' => 5,
)
);
$this->assertCount( 1, $query->orders );
$this->assertEquals( 10, $query->found_orders );
$this->assertEquals( 2, $query->max_num_pages );
remove_all_filters( 'woocommerce_hpos_pre_query' );
}
/**
* @testdox A regular query will still work even if the pre-query escape hook returns null for the whole 3-tuple.
*/
public function test_pre_query_escape_hook_return_null() {
$order1 = new \WC_Order();
$order1->set_date_created( time() - HOUR_IN_SECONDS );
$order1->save();
$callback = function( $result, $query_object, $sql ) use ( $order1 ) {
// Just return null.
return null;
};
add_filter( 'woocommerce_hpos_pre_query', $callback, 10, 3 );
$query = new OrdersTableQuery();
$this->assertCount( 1, $query->orders );
$this->assertEquals( 1, $query->found_orders );
$this->assertEquals( null, $query->max_num_pages );
remove_all_filters( 'woocommerce_hpos_pre_query' );
}
/**
* @testdox A regular query with a limit will still work even if the pre-query escape hook returns null for the whole 3-tuple.
*/
public function test_pre_query_escape_hook_return_null_limit() {
$order1 = new \WC_Order();
$order1->set_date_created( time() - HOUR_IN_SECONDS );
$order1->save();
$callback = function( $result, $query_object, $sql ) use ( $order1 ) {
// Just return null.
return null;
};
add_filter( 'woocommerce_hpos_pre_query', $callback, 10, 3 );
$query = new OrdersTableQuery(
array(
'limit' => 5,
)
);
$this->assertCount( 1, $query->orders );
$this->assertEquals( 1, $query->found_orders );
$this->assertEquals( 1, $query->max_num_pages );
remove_all_filters( 'woocommerce_hpos_pre_query' );
}
}