diff --git a/includes/data/connection/class-product-connection-resolver.php b/includes/data/connection/class-product-connection-resolver.php index 95e1aa27..8c892897 100644 --- a/includes/data/connection/class-product-connection-resolver.php +++ b/includes/data/connection/class-product-connection-resolver.php @@ -239,6 +239,13 @@ public function get_query_args() { public function get_query() { add_filter( 'posts_clauses', [ $this->products_query, 'add_query_clauses' ], 10, 2 ); + // Temporary fix for the search query. + if ( ! empty( $this->query_args['search'] ) ) { + $this->query_args['fulltext_search'] = $this->query_args['search']; + unset( $this->query_args['search'] ); + add_filter( 'posts_clauses', [ $this, 'add_search_query_clause' ], 10, 2 ); + } + return new \WP_Query(); } @@ -249,6 +256,10 @@ public function get_ids_from_query() { // Run query and get IDs. $ids = $this->query->query( $this->query_args ); + if ( ! empty( $this->query_args['fulltext_search'] ) ) { + remove_filter( 'posts_clauses', [ $this, 'add_search_query_clause' ], 10 ); + } + remove_filter( 'posts_clauses', [ $this->products_query, 'add_query_clauses' ], 10 ); // If we're going backwards, we need to reverse the array. @@ -284,6 +295,30 @@ public function ordering_meta( $is_numeric = true ) { ); } + /** + * This function replaces the default product query search query clause with a clause searching the product's description, short description and slug. + * + * @param array $args The query arguments. + * @param \WP_Query $wp_query The WP_Query object. + * @return array + */ + public function add_search_query_clause( $args, $wp_query ) { + global $wpdb; + if ( empty( $wp_query->get( 'fulltext_search' ) ) ) { + return $args; + } + + $search = '%' . $wpdb->esc_like( $wp_query->get( 'fulltext_search' ) ) . '%'; + $search_query = $wpdb->prepare( " AND ( $wpdb->posts.post_title LIKE %s OR $wpdb->posts.post_name LIKE %s OR wc_product_meta_lookup.sku LIKE %s OR $wpdb->posts.post_content LIKE %s OR $wpdb->posts.post_excerpt LIKE %s ) ", $search, $search, $search, $search, $search ); + $args['where'] .= $search_query; + + if ( ! strstr( $args['join'], 'wc_product_meta_lookup' ) ) { + $args['join'] .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id "; + } + + return $args; + } + /** * This sets up the "allowed" args, and translates the GraphQL-friendly keys to WP_Query * friendly keys. There's probably a cleaner/more dynamic way to approach this, but @@ -309,6 +344,7 @@ public function sanitize_input_fields( array $where_args ) { 'parentIn' => 'post_parent__in', 'parentNotIn' => 'post_parent__not_in', 'search' => 'search', + ] ); diff --git a/tests/wpunit/ProductsQueriesTest.php b/tests/wpunit/ProductsQueriesTest.php index 235e726f..5c6cddb7 100644 --- a/tests/wpunit/ProductsQueriesTest.php +++ b/tests/wpunit/ProductsQueriesTest.php @@ -5,6 +5,7 @@ private function createProducts() { $products = [ $this->factory->product->createSimple([ 'name' => 'Product Blue', + 'slug' => 'product-blue', 'description' => 'A peach description', 'price' => 100, 'regular_price' => 100, @@ -16,6 +17,7 @@ private function createProducts() { ]), $this->factory->product->createSimple([ 'name' => 'Product Green', + 'slug' => 'product-green', 'description' => 'A turquoise description', 'sku' => 'green-sku', 'price' => 200, @@ -28,6 +30,7 @@ private function createProducts() { ]), $this->factory->product->createSimple([ 'name' => 'Product Red', + 'slug' => 'product-red', 'description' => 'A maroon description', 'price' => 300, 'regular_price' => 300, @@ -39,6 +42,7 @@ private function createProducts() { ]), $this->factory->product->createSimple([ 'name' => 'Product Yellow', + 'slug' => 'product-yellow', 'description' => 'A teal description', 'price' => 400, 'regular_price' => 400, @@ -50,6 +54,7 @@ private function createProducts() { ]), $this->factory->product->createSimple([ 'name' => 'Product Purple', + 'slug' => 'product-purple', 'description' => 'A magenta description', 'price' => 500, 'regular_price' => 500, @@ -1024,6 +1029,8 @@ public function testProductsSearchArg() { nodes { id name + description + sku ... on ProductWithPricing { databaseId price @@ -1056,9 +1063,7 @@ public function testProductsSearchArg() { /** * Assert search by product sku. */ - $variables = [ - 'search' => 'green-sku', - ]; + $variables = [ 'search' => 'green-sku' ]; $response = $this->graphql( compact( 'query', 'variables' ) ); $this->assertQuerySuccessful( $response, @@ -1071,7 +1076,41 @@ public function testProductsSearchArg() { 0 ), ], + 'Failed to search products by product sku.' + ); + + // Search by product description. + $variables = [ 'search' => 'magenta' ]; + $response = $this->graphql( compact( 'query', 'variables' ) ); + $this->assertQuerySuccessful( + $response, + [ + $this->expectedNode( + 'products.nodes', + [ + $this->expectedField( 'id', $this->toRelayId( 'post', $products[4] ) ) + ], + 0 + ), + ], 'Failed to search products by product description content.' ); + + // Search by slug. + $variables = [ 'search' => 'product-red' ]; + $response = $this->graphql( compact( 'query', 'variables' ) ); + $this->assertQuerySuccessful( + $response, + [ + $this->expectedNode( + 'products.nodes', + [ + $this->expectedField( 'id', $this->toRelayId( 'post', $products[2] ) ) + ], + 0 + ), + ], + 'Failed to search products by product slug.' + ); } }