diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php index c2c03690b87..05bdd2a76f0 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php @@ -25,6 +25,9 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller { */ protected $namespace = 'wc/v3'; + private $search_param = 'search'; + private static $wp_query_search_param = 's'; + /** * Get the images for a product or product variation. * @@ -70,6 +73,66 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller { return $images; } + /** + * Get a collection of products and add other search criteria to WP_Query. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|WP_REST_Response + */ + public function get_items( $request ) { + // Add filters for search criteria in product postmeta via the lookup table. + if ( ! empty( $request[ $this->search_param ] ) ) { + add_filter( 'posts_where', array( __CLASS__, 'add_search_criteria_to_wp_query_filter' ), 10, 2 ); + add_filter( 'posts_join', array( __CLASS__, 'add_search_criteria_to_wp_query_join' ), 10, 2 ); + } + + $response = parent::get_items( $request ); + + // Remove filters for search criteria in product postmeta via the lookup table. + if ( ! empty( $request[ $this->search_param ] ) ) { + remove_filter( 'posts_where', array( __CLASS__, 'add_search_criteria_to_wp_query_filter' ), 10 ); + remove_filter( 'posts_join', array( __CLASS__, 'add_search_criteria_to_wp_query_join' ), 10 ); + } + return $response; + } + + /** + * Add in conditional search filters for products. + * + * @param string $where Where clause used to search posts. + * @param object $wp_query WP_Query object. + * @return string + */ + public static function add_search_criteria_to_wp_query_filter( $where, $wp_query ) { + global $wpdb; + $search = $wp_query->get( self::$wp_query_search_param ); + if ( ! empty( $search ) ) { + $like_search = '%' . $wpdb->esc_like( $search ) . '%'; + $conditions = array( + $wpdb->prepare( 'wc_product_meta_lookup.sku LIKE %s', $like_search ), + ); + $where .= ' OR (' . implode( ' OR ', $conditions ) . ')'; + } + return $where; + } + + /** + * Join `wc_product_meta_lookup` table when product search query is present. + * + * @param string $join Join clause used to search posts. + * @param object $wp_query WP_Query object. + * @return string + */ + public static function add_search_criteria_to_wp_query_join( $join, $wp_query ) { + global $wpdb; + $search = $wp_query->get( self::$wp_query_search_param ); + if ( ! empty( $search ) ) { + $join .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup + ON $wpdb->posts.ID = wc_product_meta_lookup.product_id "; + } + return $join; + } + /** * Make extra product orderby features supported by WooCommerce available to the WC API. * This includes 'price', 'popularity', and 'rating'. diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php index 6f220b9ff7b..eb330f45828 100644 --- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php +++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php @@ -147,4 +147,68 @@ 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 products with partial name or SKU case-insensitive match are returned given a `search` parameter. + */ + public function test_products_with_search_param_returns_products_with_sku_and_name_match() { + // Given. + wp_set_current_user( $this->user ); + WC_Helper_Product::create_simple_product( true, array( 'name' => 'WooCommerce Tests' ) ); + WC_Helper_Product::create_simple_product( true, array( 'name' => 'WordCamp' ) ); + WC_Helper_Product::create_simple_product( true, array( 'name' => 'WoO' ) ); + WC_Helper_Product::create_simple_product( true, array( 'sku' => 'Woo' ) ); + WC_Helper_Product::create_simple_product( true, array( 'sku' => 'wordpress' ) ); + WC_Helper_Product::create_simple_product( true, array( 'sku' => '*sunglasses-woo*' ) ); + + // When. + $request = new WP_REST_Request( 'GET', '/wc/v3/products' ); + $request->set_query_params( + array( + 'search' => 'woo', + 'order' => 'asc', + 'orderby' => 'id', + ) + ); + $response = $this->server->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $response_products = $response->get_data(); + + // Then. + $this->assertEquals( 4, count( $response_products ) ); + $this->assertEquals( $response_products[0]['name'], 'WooCommerce Tests' ); + $this->assertEquals( $response_products[1]['name'], 'WoO' ); + $this->assertEquals( $response_products[2]['sku'], 'Woo' ); + $this->assertEquals( $response_products[3]['sku'], '*sunglasses-woo*' ); + } + + /** + * Test that the first product with full SKU case-insensitive match are returned given a `sku` parameter. + */ + public function test_products_with_sku_param_returns_the_first_product_with_full_sku_match() { + // Given. + wp_set_current_user( $this->user ); + WC_Helper_Product::create_simple_product( true, array( 'sku' => '0oWoO' ) ); + WC_Helper_Product::create_simple_product( true, array( 'sku' => 'WoO' ) ); + WC_Helper_Product::create_simple_product( true, array( 'sku' => 'woo' ) ); + WC_Helper_Product::create_simple_product( true, array( 'sku' => 'woo-sunglasses' ) ); + + // When. + $request = new WP_REST_Request( 'GET', '/wc/v3/products' ); + $request->set_query_params( + array( + 'sku' => 'woo', + 'order' => 'asc', + 'orderby' => 'id', + ) + ); + $response = $this->server->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $response_products = $response->get_data(); + + // Then. + $this->assertEquals( 1, count( $response_products ) ); + $response_product = $response_products[0]; + $this->assertEquals( 'WoO', $response_product['sku'] ); + } }