Merge pull request #32046 from woocommerce/api/28508-search-including-sku
Enable searching for products by SKU
This commit is contained in:
commit
870ea59738
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Adds a `search_sku` parameter to the v3 products endpoint. Allows for partial match search of the product SKU field.
|
|
@ -25,6 +25,15 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
||||||
*/
|
*/
|
||||||
protected $namespace = 'wc/v3';
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string to inject into a query to do a partial match SKU search.
|
||||||
|
*
|
||||||
|
* See prepare_objects_query()
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $search_sku_in_product_lookup_table = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the images for a product or product variation.
|
* Get the images for a product or product variation.
|
||||||
*
|
*
|
||||||
|
@ -144,6 +153,15 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( wc_product_sku_enabled() ) {
|
||||||
|
// Do a partial match for a sku. Supercedes sku parameter that does exact matching.
|
||||||
|
if ( ! empty( $request['search_sku'] ) ) {
|
||||||
|
// Store this for use in the query clause filters.
|
||||||
|
$this->search_sku_in_product_lookup_table = $request['search_sku'];
|
||||||
|
|
||||||
|
unset( $request['sku'] );
|
||||||
|
}
|
||||||
|
|
||||||
// Filter by sku.
|
// Filter by sku.
|
||||||
if ( ! empty( $request['sku'] ) ) {
|
if ( ! empty( $request['sku'] ) ) {
|
||||||
$skus = explode( ',', $request['sku'] );
|
$skus = explode( ',', $request['sku'] );
|
||||||
|
@ -161,6 +179,7 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Filter by tax class.
|
// Filter by tax class.
|
||||||
if ( ! empty( $request['tax_class'] ) ) {
|
if ( ! empty( $request['tax_class'] ) ) {
|
||||||
|
@ -201,7 +220,7 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force the post_type argument, since it's not a user input variable.
|
// Force the post_type argument, since it's not a user input variable.
|
||||||
if ( ! empty( $request['sku'] ) ) {
|
if ( ! empty( $request['sku'] ) || ! empty( $request['search_sku'] ) ) {
|
||||||
$args['post_type'] = array( 'product', 'product_variation' );
|
$args['post_type'] = array( 'product', 'product_variation' );
|
||||||
} else {
|
} else {
|
||||||
$args['post_type'] = $this->post_type;
|
$args['post_type'] = $this->post_type;
|
||||||
|
@ -217,6 +236,61 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
||||||
return $args;
|
return $args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get objects.
|
||||||
|
*
|
||||||
|
* @param array $query_args Query args.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_objects( $query_args ) {
|
||||||
|
// Add filters for search criteria in product postmeta via the lookup table.
|
||||||
|
if ( ! empty( $this->search_sku_in_product_lookup_table ) ) {
|
||||||
|
add_filter( 'posts_join', array( $this, 'add_search_criteria_to_wp_query_join' ) );
|
||||||
|
add_filter( 'posts_where', array( $this, 'add_search_criteria_to_wp_query_where' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = parent::get_objects( $query_args );
|
||||||
|
|
||||||
|
// Remove filters for search criteria in product postmeta via the lookup table.
|
||||||
|
if ( ! empty( $this->search_sku_in_product_lookup_table ) ) {
|
||||||
|
remove_filter( 'posts_join', array( $this, 'add_search_criteria_to_wp_query_join' ) );
|
||||||
|
remove_filter( 'posts_where', array( $this, 'add_search_criteria_to_wp_query_where' ) );
|
||||||
|
|
||||||
|
$this->search_sku_in_product_lookup_table = '';
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join `wc_product_meta_lookup` table when SKU search query is present.
|
||||||
|
*
|
||||||
|
* @param string $join Join clause used to search posts.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function add_search_criteria_to_wp_query_join( $join ) {
|
||||||
|
global $wpdb;
|
||||||
|
if ( ! empty( $this->search_sku_in_product_lookup_table ) && ! strstr( $join, 'wc_product_meta_lookup' ) ) {
|
||||||
|
$join .= " LEFT JOIN $wpdb->wc_product_meta_lookup wc_product_meta_lookup
|
||||||
|
ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ";
|
||||||
|
}
|
||||||
|
return $join;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a where clause for matching the SKU field.
|
||||||
|
*
|
||||||
|
* @param string $where Where clause used to search posts.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function add_search_criteria_to_wp_query_where( $where ) {
|
||||||
|
global $wpdb;
|
||||||
|
if ( ! empty( $this->search_sku_in_product_lookup_table ) ) {
|
||||||
|
$like_search = '%' . $wpdb->esc_like( $this->search_sku_in_product_lookup_table ) . '%';
|
||||||
|
$where .= ' AND ' . $wpdb->prepare( '(wc_product_meta_lookup.sku LIKE %s)', $like_search );
|
||||||
|
}
|
||||||
|
return $where;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set product images.
|
* Set product images.
|
||||||
*
|
*
|
||||||
|
@ -1368,6 +1442,13 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$params['search_sku'] = array(
|
||||||
|
'description' => __( 'Limit results to those with a SKU that partial matches a string.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'sanitize_callback' => 'sanitize_text_field',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,57 @@
|
||||||
* Product Controller tests for V3 REST API.
|
* Product Controller tests for V3 REST API.
|
||||||
*/
|
*/
|
||||||
class WC_REST_Products_Controller_Tests extends WC_REST_Unit_Test_Case {
|
class WC_REST_Products_Controller_Tests extends WC_REST_Unit_Test_Case {
|
||||||
|
/**
|
||||||
|
* @var WC_Product_Simple[]
|
||||||
|
*/
|
||||||
|
protected static $products = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create products for tests.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function wpSetUpBeforeClass() {
|
||||||
|
self::$products[] = WC_Helper_Product::create_simple_product(
|
||||||
|
true,
|
||||||
|
array(
|
||||||
|
'name' => 'Pancake',
|
||||||
|
'sku' => 'pancake-1',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
self::$products[] = WC_Helper_Product::create_simple_product(
|
||||||
|
true,
|
||||||
|
array(
|
||||||
|
'name' => 'Waffle 1',
|
||||||
|
'sku' => 'pancake-2',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
self::$products[] = WC_Helper_Product::create_simple_product(
|
||||||
|
true,
|
||||||
|
array(
|
||||||
|
'name' => 'French Toast',
|
||||||
|
'sku' => 'waffle-2',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
self::$products[] = WC_Helper_Product::create_simple_product(
|
||||||
|
true,
|
||||||
|
array(
|
||||||
|
'name' => 'Waffle 3',
|
||||||
|
'sku' => 'waffle-3',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up products after tests.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function wpTearDownAfterClass() {
|
||||||
|
foreach ( self::$products as $product ) {
|
||||||
|
WC_Helper_Product::delete_product( $product->get_id() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup our test server, endpoints, and user info.
|
* Setup our test server, endpoints, and user info.
|
||||||
|
@ -147,4 +198,116 @@ class WC_REST_Products_Controller_Tests extends WC_REST_Unit_Test_Case {
|
||||||
$this->assertContains( $field, $response_fields, "Field $field was expected but not present in product API response." );
|
$this->assertContains( $field, $response_fields, "Field $field was expected but not present in product API response." );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that the `search` parameter does partial matching in the product name, but not the SKU.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function test_products_search_with_search_param_only() {
|
||||||
|
wp_set_current_user( $this->user );
|
||||||
|
|
||||||
|
$request = new WP_REST_Request( 'GET', '/wc/v3/products' );
|
||||||
|
$request->set_query_params(
|
||||||
|
array(
|
||||||
|
'search' => 'waffle',
|
||||||
|
'order' => 'asc',
|
||||||
|
'orderby' => 'id',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$response = $this->server->dispatch( $request );
|
||||||
|
$this->assertEquals( 200, $response->get_status() );
|
||||||
|
$response_products = $response->get_data();
|
||||||
|
|
||||||
|
$this->assertEquals( 2, count( $response_products ) );
|
||||||
|
$this->assertEquals( $response_products[0]['name'], 'Waffle 1' );
|
||||||
|
$this->assertEquals( $response_products[0]['sku'], 'pancake-2' );
|
||||||
|
$this->assertEquals( $response_products[1]['name'], 'Waffle 3' );
|
||||||
|
$this->assertEquals( $response_products[1]['sku'], 'waffle-3' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that the `search_sku` parameter does partial matching in the product SKU, but not the name.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function test_products_search_with_search_sku_param_only() {
|
||||||
|
wp_set_current_user( $this->user );
|
||||||
|
|
||||||
|
$request = new WP_REST_Request( 'GET', '/wc/v3/products' );
|
||||||
|
$request->set_query_params(
|
||||||
|
array(
|
||||||
|
'search_sku' => 'waffle',
|
||||||
|
'order' => 'asc',
|
||||||
|
'orderby' => 'id',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$response = $this->server->dispatch( $request );
|
||||||
|
$this->assertEquals( 200, $response->get_status() );
|
||||||
|
$response_products = $response->get_data();
|
||||||
|
|
||||||
|
$this->assertEquals( 2, count( $response_products ) );
|
||||||
|
$this->assertEquals( $response_products[0]['name'], 'French Toast' );
|
||||||
|
$this->assertEquals( $response_products[0]['sku'], 'waffle-2' );
|
||||||
|
$this->assertEquals( $response_products[1]['name'], 'Waffle 3' );
|
||||||
|
$this->assertEquals( $response_products[1]['sku'], 'waffle-3' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that using the `search` and `search_sku` parameters together only matches when both match.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function test_products_search_with_search_and_search_sku_param() {
|
||||||
|
wp_set_current_user( $this->user );
|
||||||
|
|
||||||
|
$request = new WP_REST_Request( 'GET', '/wc/v3/products' );
|
||||||
|
$request->set_query_params(
|
||||||
|
array(
|
||||||
|
'search' => 'waffle',
|
||||||
|
'search_sku' => 'waffle',
|
||||||
|
'order' => 'asc',
|
||||||
|
'orderby' => 'id',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$response = $this->server->dispatch( $request );
|
||||||
|
$this->assertEquals( 200, $response->get_status() );
|
||||||
|
$response_products = $response->get_data();
|
||||||
|
|
||||||
|
$this->assertEquals( 1, count( $response_products ) );
|
||||||
|
$this->assertEquals( $response_products[0]['name'], 'Waffle 3' );
|
||||||
|
$this->assertEquals( $response_products[0]['sku'], 'waffle-3' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that the `search_sku` parameter does nothing when product SKUs are disabled.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function test_products_search_with_search_sku_when_skus_disabled() {
|
||||||
|
wp_set_current_user( $this->user );
|
||||||
|
|
||||||
|
add_filter( 'wc_product_sku_enabled', '__return_false' );
|
||||||
|
|
||||||
|
$request = new WP_REST_Request( 'GET', '/wc/v3/products' );
|
||||||
|
$request->set_query_params(
|
||||||
|
array(
|
||||||
|
'search' => 'waffle',
|
||||||
|
'search_sku' => 'waffle',
|
||||||
|
'order' => 'asc',
|
||||||
|
'orderby' => 'id',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$response = $this->server->dispatch( $request );
|
||||||
|
$this->assertEquals( 200, $response->get_status() );
|
||||||
|
$response_products = $response->get_data();
|
||||||
|
|
||||||
|
$this->assertEquals( 2, count( $response_products ) );
|
||||||
|
$this->assertEquals( $response_products[0]['name'], 'Waffle 1' );
|
||||||
|
$this->assertEquals( $response_products[0]['sku'], 'pancake-2' );
|
||||||
|
$this->assertEquals( $response_products[1]['name'], 'Waffle 3' );
|
||||||
|
$this->assertEquals( $response_products[1]['sku'], 'waffle-3' );
|
||||||
|
|
||||||
|
remove_filter( 'wc_product_sku_enabled', '__return_false' );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue