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';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -144,22 +153,32 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
|||
);
|
||||
}
|
||||
|
||||
// Filter by sku.
|
||||
if ( ! empty( $request['sku'] ) ) {
|
||||
$skus = explode( ',', $request['sku'] );
|
||||
// Include the current string as a SKU too.
|
||||
if ( 1 < count( $skus ) ) {
|
||||
$skus[] = $request['sku'];
|
||||
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'] );
|
||||
}
|
||||
|
||||
$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
|
||||
$args,
|
||||
array(
|
||||
'key' => '_sku',
|
||||
'value' => $skus,
|
||||
'compare' => 'IN',
|
||||
)
|
||||
);
|
||||
// Filter by sku.
|
||||
if ( ! empty( $request['sku'] ) ) {
|
||||
$skus = explode( ',', $request['sku'] );
|
||||
// Include the current string as a SKU too.
|
||||
if ( 1 < count( $skus ) ) {
|
||||
$skus[] = $request['sku'];
|
||||
}
|
||||
|
||||
$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
|
||||
$args,
|
||||
array(
|
||||
'key' => '_sku',
|
||||
'value' => $skus,
|
||||
'compare' => 'IN',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by 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.
|
||||
if ( ! empty( $request['sku'] ) ) {
|
||||
if ( ! empty( $request['sku'] ) || ! empty( $request['search_sku'] ) ) {
|
||||
$args['post_type'] = array( 'product', 'product_variation' );
|
||||
} else {
|
||||
$args['post_type'] = $this->post_type;
|
||||
|
@ -217,6 +236,61 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
|||
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.
|
||||
*
|
||||
|
@ -1368,6 +1442,13 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
|||
'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;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,57 @@
|
|||
* Product Controller tests for V3 REST API.
|
||||
*/
|
||||
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.
|
||||
|
@ -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." );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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