From 46d1c87f424af5c3dc54377399cb8e7039fae775 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Thu, 2 Mar 2023 01:48:26 +0100 Subject: [PATCH 01/47] Introduce the new get_attribute_and_meta_counts method. --- .../Utilities/ProductQueryFilters.php | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 63205b6f110..742a564f2c0 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -110,6 +110,83 @@ private function generate_stock_status_count_query( $status, $product_query_sql, "; } + public function get_attribute_and_meta_counts( $request, $attributes = [] ) { + global $wpdb; + + $attributes = array_map( 'esc_sql', $attributes ); + $taxonomy = $attributes[0]; + + $query_type = 'or'; + foreach ( $request['calculate_attribute_counts'] as $attributes_to_count ) { + if ( ! empty( $attributes_to_count['query_type'] ) ) { + $query_type = $attributes_to_count['query_type']; + } + } + + $term_ids = []; + foreach ( $_GET['attributes'] as $attribute ) { + if ( empty( $attribute['term_id'] ) && empty( $attribute['slug'] ) ) { + continue; + } + + if ( in_array( $attribute['attribute'], wc_get_attribute_taxonomy_names(), true ) ) { + if ( ! empty( $attribute['term_id'] ) ) { + $term_ids[] = $attribute['term_id']; + } elseif ( ! empty( $attribute['slug'] ) ) { + $term = get_term_by( 'slug', $attribute['slug'][0], $attribute['attribute'] ); + if ( is_object( $term ) ) { + $term_ids[] = $term->term_id; + } + } + } + } + + $term_ids_count = count( $term_ids ); + $term_ids = implode( ',', array_map( 'intval', $term_ids ) ); + + if ( 'and' === $query_type ) { + $condition_query = "SELECT product_or_parent_id + FROM wp_wc_product_attributes_lookup + WHERE taxonomy = '{$taxonomy}' + AND term_id IN ({$term_ids}) + GROUP BY product_or_parent_id + HAVING count(DISTINCT term_id) >= {$term_ids_count})"; + } else { + $condition_query = "SELECT product_or_parent_id + FROM wp_wc_product_attributes_lookup + WHERE taxonomy = '{$taxonomy}' + AND term_id IN ({$term_ids})"; + } + + $products = $wpdb->get_col( $condition_query ); + $products = implode( ',', array_map( 'intval', $products ) ); + + $query = "SELECT attributes.term_id as term_count_id, coalesce(term_count, 0) as term_count +FROM ( + SELECT DISTINCT term_id + FROM wp_wc_product_attributes_lookup + WHERE taxonomy = '{$taxonomy}') as attributes + LEFT JOIN ( + SELECT COUNT(product_attribute_lookup.product_id) as term_count, product_attribute_lookup.term_id + FROM wp_wc_product_attributes_lookup product_attribute_lookup + INNER JOIN wp_posts posts + ON posts.ID = product_attribute_lookup.product_id + WHERE posts.post_type IN ('product', 'product_variation') + AND posts.post_status = 'publish' + AND product_attribute_lookup.product_or_parent_id IN ({$products}) + AND product_attribute_lookup.product_id IN ( + SELECT product_meta_lookup.product_id + FROM wp_wc_product_meta_lookup product_meta_lookup + WHERE product_meta_lookup.max_price <= 16.000000) + GROUP BY product_attribute_lookup.term_id +) summarize ON attributes.term_id = summarize.term_id"; + + $counts = $wpdb->get_results( $query ); + + return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); + } + + /** * Get attribute counts for the current products. * From 0fb83b208447897f2978c01133e22a1439e115d3 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Fri, 3 Mar 2023 00:36:16 +0100 Subject: [PATCH 02/47] Ensure that if no term_slug or term_id is found for counting, the default list with all terms (with count equal zero) is returned instead. --- src/StoreApi/Utilities/ProductQueryFilters.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 742a564f2c0..b98717e8857 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -123,8 +123,12 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { } } + if ( ! isset( $_REQUEST['attributes'] ) ) { + return []; + } + $term_ids = []; - foreach ( $_GET['attributes'] as $attribute ) { + foreach ( $_REQUEST['attributes'] as $attribute ) { if ( empty( $attribute['term_id'] ) && empty( $attribute['slug'] ) ) { continue; } @@ -141,6 +145,15 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { } } + if ( empty( $term_ids ) ) { + $empty_query = "SELECT DISTINCT term_id as term_count_id, 0 as term_tount + FROM wp_wc_product_attributes_lookup + WHERE taxonomy = '{$taxonomy}'"; + $counts = $wpdb->get_results( $empty_query ); + + return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); + } + $term_ids_count = count( $term_ids ); $term_ids = implode( ',', array_map( 'intval', $term_ids ) ); From ab5ad746fb326a9eeb4d285d7fedc7627e8bd134 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Thu, 9 Mar 2023 22:49:03 -0300 Subject: [PATCH 03/47] update conditional for the slug, the empty state for requests without filter attributes and the condition query for 'and' --- .../Utilities/ProductQueryFilters.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index b98717e8857..f8e76c58287 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -124,7 +124,12 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { } if ( ! isset( $_REQUEST['attributes'] ) ) { - return []; + $empty_query = "SELECT DISTINCT term_id as term_count_id, 0 as term_tount + FROM wp_wc_product_attributes_lookup + WHERE taxonomy = '{$taxonomy}'"; + $counts = $wpdb->get_results( $empty_query ); + + return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } $term_ids = []; @@ -137,9 +142,11 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { if ( ! empty( $attribute['term_id'] ) ) { $term_ids[] = $attribute['term_id']; } elseif ( ! empty( $attribute['slug'] ) ) { - $term = get_term_by( 'slug', $attribute['slug'][0], $attribute['attribute'] ); - if ( is_object( $term ) ) { - $term_ids[] = $term->term_id; + foreach ( $attribute['slug'] as $slug ) { + $term = get_term_by( 'slug', $slug, $attribute['attribute'] ); + if ( is_object( $term ) ) { + $term_ids[] = $term->term_id; + } } } } @@ -163,7 +170,7 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { WHERE taxonomy = '{$taxonomy}' AND term_id IN ({$term_ids}) GROUP BY product_or_parent_id - HAVING count(DISTINCT term_id) >= {$term_ids_count})"; + HAVING count(DISTINCT term_id) >= {$term_ids_count}"; } else { $condition_query = "SELECT product_or_parent_id FROM wp_wc_product_attributes_lookup @@ -190,7 +197,7 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { AND product_attribute_lookup.product_id IN ( SELECT product_meta_lookup.product_id FROM wp_wc_product_meta_lookup product_meta_lookup - WHERE product_meta_lookup.max_price <= 16.000000) + WHERE product_meta_lookup.max_price <= 200.000000) GROUP BY product_attribute_lookup.term_id ) summarize ON attributes.term_id = summarize.term_id"; From e55655c2ac50a45bc99cda50a01b6f9042c1174a Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Fri, 10 Mar 2023 10:55:07 -0300 Subject: [PATCH 04/47] Introduce the get_terms_list method. --- .../Utilities/ProductQueryFilters.php | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index f8e76c58287..e92f4d2936e 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -110,6 +110,30 @@ private function generate_stock_status_count_query( $status, $product_query_sql, "; } + /** + * Get terms list for a given taxonomy. + * + * @param string $taxonomy Taxonomy name. + * + * @return array + */ + public function get_terms_list( string $taxonomy ) { + global $wpdb; + + $terms_list = "SELECT DISTINCT term_id as term_count_id, 0 as term_count + FROM {$wpdb->prefix}wc_product_attributes_lookup + WHERE taxonomy = '{$taxonomy}'"; + + return $wpdb->get_results( $wpdb->prepare( $terms_list ) ); + } + + /** + * Get attribute and meta counts. + * + * @param WP_REST_Request $request Request data. + * @param array $attributes Attributes to count. + * @return array + */ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { global $wpdb; @@ -124,10 +148,7 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { } if ( ! isset( $_REQUEST['attributes'] ) ) { - $empty_query = "SELECT DISTINCT term_id as term_count_id, 0 as term_tount - FROM wp_wc_product_attributes_lookup - WHERE taxonomy = '{$taxonomy}'"; - $counts = $wpdb->get_results( $empty_query ); + $counts = $this->get_terms_list( $taxonomy ); return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } @@ -153,10 +174,7 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { } if ( empty( $term_ids ) ) { - $empty_query = "SELECT DISTINCT term_id as term_count_id, 0 as term_tount - FROM wp_wc_product_attributes_lookup - WHERE taxonomy = '{$taxonomy}'"; - $counts = $wpdb->get_results( $empty_query ); + $counts = $this->get_terms_list( $taxonomy ); return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } From 602bf81f35eea31d7e697f176c527c0ba6abaf5e Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Fri, 10 Mar 2023 10:59:21 -0300 Subject: [PATCH 05/47] Remove the legacy get_attribute_counts method and update its calls to rely on the new get_attribute_and_meta_counts method instead. --- .../Routes/V1/ProductCollectionData.php | 4 +- .../Utilities/ProductQueryFilters.php | 61 ------------------- 2 files changed, 2 insertions(+), 63 deletions(-) diff --git a/src/StoreApi/Routes/V1/ProductCollectionData.php b/src/StoreApi/Routes/V1/ProductCollectionData.php index 88fbc99db70..d49af5d4e70 100644 --- a/src/StoreApi/Routes/V1/ProductCollectionData.php +++ b/src/StoreApi/Routes/V1/ProductCollectionData.php @@ -121,7 +121,7 @@ function( $query ) use ( $taxonomy ) { } $filter_request->set_param( 'attributes', $filter_attributes ); - $counts = $filters->get_attribute_counts( $filter_request, [ $taxonomy ] ); + $counts = $filters->get_attribute_and_meta_counts( $filter_request, [ $taxonomy ] ); foreach ( $counts as $key => $value ) { $data['attribute_counts'][] = (object) [ @@ -133,7 +133,7 @@ function( $query ) use ( $taxonomy ) { } if ( $taxonomy__and_queries ) { - $counts = $filters->get_attribute_counts( $request, $taxonomy__and_queries ); + $counts = $filters->get_attribute_and_meta_counts( $request, $taxonomy__and_queries ); foreach ( $counts as $key => $value ) { $data['attribute_counts'][] = (object) [ diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index e92f4d2936e..22c10b6b03c 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -224,67 +224,6 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } - - /** - * Get attribute counts for the current products. - * - * @param \WP_REST_Request $request The request object. - * @param array $attributes Attributes to count, either names or ids. - * @return array termId=>count pairs. - */ - public function get_attribute_counts( $request, $attributes = [] ) { - global $wpdb; - - // Remove paging and sorting params from the request. - $request->set_param( 'page', null ); - $request->set_param( 'per_page', null ); - $request->set_param( 'order', null ); - $request->set_param( 'orderby', null ); - - // Grab the request from the WP Query object, and remove SQL_CALC_FOUND_ROWS and Limits so we get a list of all products. - $product_query = new ProductQuery(); - - add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); - add_filter( 'posts_pre_query', '__return_empty_array' ); - - $query_args = $product_query->prepare_objects_query( $request ); - $query_args['no_found_rows'] = true; - $query_args['posts_per_page'] = -1; - $query = new \WP_Query(); - $result = $query->query( $query_args ); - $product_query_sql = $query->request; - - remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); - remove_filter( 'posts_pre_query', '__return_empty_array' ); - - if ( count( $attributes ) === count( array_filter( $attributes, 'is_numeric' ) ) ) { - $attributes = array_map( 'wc_attribute_taxonomy_name_by_id', wp_parse_id_list( $attributes ) ); - } - - $attributes_to_count = array_map( - function( $attribute ) { - $attribute = wc_sanitize_taxonomy_name( $attribute ); - return esc_sql( $attribute ); - }, - $attributes - ); - $attributes_to_count_sql = 'AND term_taxonomy.taxonomy IN ("' . implode( '","', $attributes_to_count ) . '")'; - $attribute_count_sql = " - SELECT COUNT( DISTINCT posts.ID ) as term_count, terms.term_id as term_count_id - FROM {$wpdb->posts} AS posts - INNER JOIN {$wpdb->term_relationships} AS term_relationships ON posts.ID = term_relationships.object_id - INNER JOIN {$wpdb->term_taxonomy} AS term_taxonomy USING( term_taxonomy_id ) - INNER JOIN {$wpdb->terms} AS terms USING( term_id ) - WHERE posts.ID IN ( {$product_query_sql} ) - {$attributes_to_count_sql} - GROUP BY terms.term_id - "; - - $results = $wpdb->get_results( $attribute_count_sql ); // phpcs:ignore - - return array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) ); - } - /** * Get rating counts for the current products. * From 42f5063901f62aaae76c7231f4ed55a6646c36bd Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Fri, 10 Mar 2023 16:15:59 -0300 Subject: [PATCH 06/47] Update the query to ensure that if a parent product has multiple identical attributes, they are counted once. --- src/StoreApi/Utilities/ProductQueryFilters.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 22c10b6b03c..c08ee9e11fc 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -205,7 +205,7 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { FROM wp_wc_product_attributes_lookup WHERE taxonomy = '{$taxonomy}') as attributes LEFT JOIN ( - SELECT COUNT(product_attribute_lookup.product_id) as term_count, product_attribute_lookup.term_id + SELECT COUNT(DISTINCT product_attribute_lookup.product_or_parent_id) as term_count, product_attribute_lookup.term_id FROM wp_wc_product_attributes_lookup product_attribute_lookup INNER JOIN wp_posts posts ON posts.ID = product_attribute_lookup.product_id From ef9a4861f11edc8d853b72ff80cd5a18c1a30c61 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Fri, 10 Mar 2023 19:46:04 -0300 Subject: [PATCH 07/47] Update to start relying on the get_product_by_metas method for counting product metas --- src/StoreApi/Utilities/ProductQueryFilters.php | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index c08ee9e11fc..adb9880b9ae 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -147,7 +147,18 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { } } - if ( ! isset( $_REQUEST['attributes'] ) ) { + $product_metas = []; + if ( isset( $_REQUEST['min_price'] ) ) { + $product_metas['min_price'] = intval( $_REQUEST['min_price'] ) / 100; + } + + if ( isset( $_REQUEST['max_price'] ) ) { + $product_metas['max_price'] = intval( $_REQUEST['max_price'] ) / 100; + } + + $product_metas = $this->get_product_by_metas( $product_metas ); + + if ( ! isset( $_REQUEST['attributes'] ) || ( ! isset( $_REQUEST['min_price'] ) && ! isset( $_REQUEST['max_price'] ) ) ) { $counts = $this->get_terms_list( $taxonomy ); return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); @@ -212,10 +223,7 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { WHERE posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish' AND product_attribute_lookup.product_or_parent_id IN ({$products}) - AND product_attribute_lookup.product_id IN ( - SELECT product_meta_lookup.product_id - FROM wp_wc_product_meta_lookup product_meta_lookup - WHERE product_meta_lookup.max_price <= 200.000000) + AND product_attribute_lookup.product_id IN ({$product_metas}) GROUP BY product_attribute_lookup.term_id ) summarize ON attributes.term_id = summarize.term_id"; From 9ee08f38911317bb91f866596841598669de97ef Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Mon, 13 Mar 2023 12:06:51 -0300 Subject: [PATCH 08/47] Add a new where_clause to only include product metas and attributes in the macro query if they are not empty. --- src/StoreApi/Utilities/ProductQueryFilters.php | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index adb9880b9ae..c0cf97bcdc1 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -158,7 +158,7 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { $product_metas = $this->get_product_by_metas( $product_metas ); - if ( ! isset( $_REQUEST['attributes'] ) || ( ! isset( $_REQUEST['min_price'] ) && ! isset( $_REQUEST['max_price'] ) ) ) { + if ( ! isset( $_REQUEST['attributes'] ) ) { $counts = $this->get_terms_list( $taxonomy ); return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); @@ -210,6 +210,17 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { $products = $wpdb->get_col( $condition_query ); $products = implode( ',', array_map( 'intval', $products ) ); + + $where_clause = "posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'"; + + if ( ! empty( $products ) ) { + $where_clause .= " AND product_attribute_lookup.product_or_parent_id IN ({$products})"; + } + + if ( ! empty( $product_metas ) ) { + $where_clause .= " AND product_attribute_lookup.product_id IN ({$product_metas})"; + } + $query = "SELECT attributes.term_id as term_count_id, coalesce(term_count, 0) as term_count FROM ( SELECT DISTINCT term_id @@ -220,10 +231,7 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { FROM wp_wc_product_attributes_lookup product_attribute_lookup INNER JOIN wp_posts posts ON posts.ID = product_attribute_lookup.product_id - WHERE posts.post_type IN ('product', 'product_variation') - AND posts.post_status = 'publish' - AND product_attribute_lookup.product_or_parent_id IN ({$products}) - AND product_attribute_lookup.product_id IN ({$product_metas}) + WHERE {$where_clause} GROUP BY product_attribute_lookup.term_id ) summarize ON attributes.term_id = summarize.term_id"; From 82446d36158100b4d7c334bb47ad7ea238752f0e Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Mon, 13 Mar 2023 12:32:14 -0300 Subject: [PATCH 09/47] Add wpdb->prepare to the macro query and the get_terms_list method. --- .../Utilities/ProductQueryFilters.php | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index c0cf97bcdc1..182c36bb583 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -113,18 +113,21 @@ private function generate_stock_status_count_query( $status, $product_query_sql, /** * Get terms list for a given taxonomy. * - * @param string $taxonomy Taxonomy name. + * @param string $taxonomy Taxonomy name. * * @return array */ public function get_terms_list( string $taxonomy ) { global $wpdb; - $terms_list = "SELECT DISTINCT term_id as term_count_id, 0 as term_count - FROM {$wpdb->prefix}wc_product_attributes_lookup - WHERE taxonomy = '{$taxonomy}'"; - - return $wpdb->get_results( $wpdb->prepare( $terms_list ) ); + return $wpdb->get_results( + $wpdb->prepare( + "SELECT DISTINCT term_id as term_count_id, 0 as term_count + FROM {$wpdb->prefix}wc_product_attributes_lookup + WHERE taxonomy = %s", + $taxonomy + ) + ); } /** @@ -207,33 +210,33 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { AND term_id IN ({$term_ids})"; } - $products = $wpdb->get_col( $condition_query ); - $products = implode( ',', array_map( 'intval', $products ) ); + $product_attributes = $wpdb->get_col( $condition_query ); + $product_attributes = implode( ',', array_map( 'intval', $product_attributes ) ); $where_clause = "posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'"; - if ( ! empty( $products ) ) { - $where_clause .= " AND product_attribute_lookup.product_or_parent_id IN ({$products})"; + if ( ! empty( $product_attributes ) ) { + $where_clause .= " AND product_attribute_lookup.product_or_parent_id IN ({$product_attributes})"; } if ( ! empty( $product_metas ) ) { $where_clause .= " AND product_attribute_lookup.product_id IN ({$product_metas})"; } - $query = "SELECT attributes.term_id as term_count_id, coalesce(term_count, 0) as term_count + $query = $wpdb->prepare( "SELECT attributes.term_id as term_count_id, coalesce(term_count, 0) as term_count FROM ( SELECT DISTINCT term_id - FROM wp_wc_product_attributes_lookup - WHERE taxonomy = '{$taxonomy}') as attributes + FROM {$wpdb->prefix}wc_product_attributes_lookup + WHERE taxonomy = '%s') as attributes LEFT JOIN ( SELECT COUNT(DISTINCT product_attribute_lookup.product_or_parent_id) as term_count, product_attribute_lookup.term_id - FROM wp_wc_product_attributes_lookup product_attribute_lookup - INNER JOIN wp_posts posts + FROM {$wpdb->prefix}wc_product_attributes_lookup product_attribute_lookup + INNER JOIN {$wpdb->posts} posts ON posts.ID = product_attribute_lookup.product_id WHERE {$where_clause} GROUP BY product_attribute_lookup.term_id -) summarize ON attributes.term_id = summarize.term_id"; +) summarize ON attributes.term_id = summarize.term_id", $taxonomy ); $counts = $wpdb->get_results( $query ); From c473b60b6ef87c0e1789148656fe215577a30011 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Mon, 13 Mar 2023 15:23:17 -0300 Subject: [PATCH 10/47] Replace the raw atomic query for fetching the filtered terms with the new get_product_by_filtered_terms method. --- .../Utilities/ProductQueryFilters.php | 54 +++++++------------ 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 182c36bb583..a7bab078d16 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -193,26 +193,7 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } - $term_ids_count = count( $term_ids ); - $term_ids = implode( ',', array_map( 'intval', $term_ids ) ); - - if ( 'and' === $query_type ) { - $condition_query = "SELECT product_or_parent_id - FROM wp_wc_product_attributes_lookup - WHERE taxonomy = '{$taxonomy}' - AND term_id IN ({$term_ids}) - GROUP BY product_or_parent_id - HAVING count(DISTINCT term_id) >= {$term_ids_count}"; - } else { - $condition_query = "SELECT product_or_parent_id - FROM wp_wc_product_attributes_lookup - WHERE taxonomy = '{$taxonomy}' - AND term_id IN ({$term_ids})"; - } - - $product_attributes = $wpdb->get_col( $condition_query ); - $product_attributes = implode( ',', array_map( 'intval', $product_attributes ) ); - + $product_attributes = $this->get_product_by_filtered_terms( $taxonomy, $term_ids, $query_type ); $where_clause = "posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'"; @@ -224,21 +205,24 @@ public function get_attribute_and_meta_counts( $request, $attributes = [] ) { $where_clause .= " AND product_attribute_lookup.product_id IN ({$product_metas})"; } - $query = $wpdb->prepare( "SELECT attributes.term_id as term_count_id, coalesce(term_count, 0) as term_count -FROM ( - SELECT DISTINCT term_id - FROM {$wpdb->prefix}wc_product_attributes_lookup - WHERE taxonomy = '%s') as attributes - LEFT JOIN ( - SELECT COUNT(DISTINCT product_attribute_lookup.product_or_parent_id) as term_count, product_attribute_lookup.term_id - FROM {$wpdb->prefix}wc_product_attributes_lookup product_attribute_lookup - INNER JOIN {$wpdb->posts} posts - ON posts.ID = product_attribute_lookup.product_id - WHERE {$where_clause} - GROUP BY product_attribute_lookup.term_id -) summarize ON attributes.term_id = summarize.term_id", $taxonomy ); - - $counts = $wpdb->get_results( $query ); + $counts = $wpdb->get_results( + $wpdb->prepare( + "SELECT attributes.term_id as term_count_id, coalesce(term_count, 0) as term_count + FROM ( + SELECT DISTINCT term_id + FROM {$wpdb->prefix}wc_product_attributes_lookup + WHERE taxonomy = %s) as attributes + LEFT JOIN ( + SELECT COUNT(DISTINCT product_attribute_lookup.product_or_parent_id) as term_count, product_attribute_lookup.term_id + FROM {$wpdb->prefix}wc_product_attributes_lookup product_attribute_lookup + INNER JOIN {$wpdb->posts} posts + ON posts.ID = product_attribute_lookup.product_id + WHERE {$where_clause} + GROUP BY product_attribute_lookup.term_id + ) summarize ON attributes.term_id = summarize.term_id", + $taxonomy + ) + ); return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } From a41c687a998e5b543d7156ff87cc1b1074464a5e Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Tue, 14 Mar 2023 17:08:36 -0300 Subject: [PATCH 11/47] Update the request params for the get_attribute_and_meta_counts method. --- .../Routes/V1/ProductCollectionData.php | 15 +--- .../Utilities/ProductQueryFilters.php | 79 +++++++++---------- 2 files changed, 39 insertions(+), 55 deletions(-) diff --git a/src/StoreApi/Routes/V1/ProductCollectionData.php b/src/StoreApi/Routes/V1/ProductCollectionData.php index d49af5d4e70..b7eb3a788f6 100644 --- a/src/StoreApi/Routes/V1/ProductCollectionData.php +++ b/src/StoreApi/Routes/V1/ProductCollectionData.php @@ -108,20 +108,7 @@ protected function get_route_response( \WP_REST_Request $request ) { // Or type queries need special handling because the attribute, if set, needs removing from the query first otherwise counts would not be correct. if ( $taxonomy__or_queries ) { foreach ( $taxonomy__or_queries as $taxonomy ) { - $filter_request = clone $request; - $filter_attributes = $filter_request->get_param( 'attributes' ); - - if ( ! empty( $filter_attributes ) ) { - $filter_attributes = array_filter( - $filter_attributes, - function( $query ) use ( $taxonomy ) { - return $query['attribute'] !== $taxonomy; - } - ); - } - - $filter_request->set_param( 'attributes', $filter_attributes ); - $counts = $filters->get_attribute_and_meta_counts( $filter_request, [ $taxonomy ] ); + $counts = $filters->get_attribute_and_meta_counts( $request, [ $taxonomy ] ); foreach ( $counts as $key => $value ) { $data['attribute_counts'][] = (object) [ diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index a7bab078d16..4f1f10a4328 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -2,6 +2,7 @@ namespace Automattic\WooCommerce\StoreApi\Utilities; use Automattic\WooCommerce\StoreApi\Utilities\ProductQuery; +use Exception; /** * Product Query filters class. @@ -137,68 +138,64 @@ public function get_terms_list( string $taxonomy ) { * @param array $attributes Attributes to count. * @return array */ - public function get_attribute_and_meta_counts( $request, $attributes = [] ) { + public function get_attribute_and_meta_counts( $request, $attributes ) { global $wpdb; - $attributes = array_map( 'esc_sql', $attributes ); - $taxonomy = $attributes[0]; + $attributes_data = $request->get_param( 'attributes' ); + $calculate_attribute_counts = $request->get_param( 'calculate_attribute_counts' ); + $query_type = $calculate_attribute_counts[0]['query_type'] ?? 'or'; - $query_type = 'or'; - foreach ( $request['calculate_attribute_counts'] as $attributes_to_count ) { - if ( ! empty( $attributes_to_count['query_type'] ) ) { - $query_type = $attributes_to_count['query_type']; - } - } - - $product_metas = []; - if ( isset( $_REQUEST['min_price'] ) ) { - $product_metas['min_price'] = intval( $_REQUEST['min_price'] ) / 100; - } - - if ( isset( $_REQUEST['max_price'] ) ) { - $product_metas['max_price'] = intval( $_REQUEST['max_price'] ) / 100; - } - - $product_metas = $this->get_product_by_metas( $product_metas ); - - if ( ! isset( $_REQUEST['attributes'] ) ) { - $counts = $this->get_terms_list( $taxonomy ); + if ( empty( $attributes_data ) ) { + $taxonomy = $attributes[0] ?? ''; + $counts = $this->get_terms_list( $taxonomy ); return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } - $term_ids = []; - foreach ( $_REQUEST['attributes'] as $attribute ) { - if ( empty( $attribute['term_id'] ) && empty( $attribute['slug'] ) ) { + $filtered_products_by_terms = []; + foreach ( $attributes_data as $attribute ) { + $taxonomy = $attribute['attribute'] ?? ''; + $filtered_terms = $attribute['slug'] ?? ''; + + if ( empty( $filtered_terms ) ) { continue; } - if ( in_array( $attribute['attribute'], wc_get_attribute_taxonomy_names(), true ) ) { - if ( ! empty( $attribute['term_id'] ) ) { - $term_ids[] = $attribute['term_id']; - } elseif ( ! empty( $attribute['slug'] ) ) { - foreach ( $attribute['slug'] as $slug ) { - $term = get_term_by( 'slug', $slug, $attribute['attribute'] ); - if ( is_object( $term ) ) { - $term_ids[] = $term->term_id; - } + $term_ids = []; + if ( in_array( $taxonomy, wc_get_attribute_taxonomy_names(), true ) ) { + foreach ( $filtered_terms as $filtered_term ) { + $term = get_term_by( 'slug', $filtered_term, $taxonomy ); + if ( is_object( $term ) ) { + $term_ids[] = $term->term_id; } } } + + if ( empty( $term_ids ) ) { + continue; + } + + $filtered_products_by_terms = array_merge( $filtered_products_by_terms, $this->get_product_by_filtered_terms( $taxonomy, $term_ids, $query_type ) ); } - if ( empty( $term_ids ) ) { - $counts = $this->get_terms_list( $taxonomy ); + $formatted_filtered_products_by_terms = implode( ',', array_map( 'intval', $filtered_products_by_terms ) ); - return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); + $product_metas = []; + if ( isset( $_REQUEST['min_price'] ) ) { + $product_metas['min_price'] = intval( $_REQUEST['min_price'] ) / 100; } - $product_attributes = $this->get_product_by_filtered_terms( $taxonomy, $term_ids, $query_type ); + if ( isset( $_REQUEST['max_price'] ) ) { + $product_metas['max_price'] = intval( $_REQUEST['max_price'] ) / 100; + } + + $product_metas = $this->get_product_by_metas( $product_metas ); + $product_metas = implode( ',', array_map( 'intval', $product_metas ) ); $where_clause = "posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'"; - if ( ! empty( $product_attributes ) ) { - $where_clause .= " AND product_attribute_lookup.product_or_parent_id IN ({$product_attributes})"; + if ( ! empty( $formatted_filtered_products_by_terms ) ) { + $where_clause .= " AND product_attribute_lookup.product_or_parent_id IN ({$formatted_filtered_products_by_terms})"; } if ( ! empty( $product_metas ) ) { From 754f4ed459bdf329b39bdea3e24472820a5a1c5e Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Tue, 14 Mar 2023 17:29:44 -0300 Subject: [PATCH 12/47] Update the request params for the product metas (min and max price). --- .../Utilities/ProductQueryFilters.php | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 4f1f10a4328..e453b5cb45d 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -144,10 +144,10 @@ public function get_attribute_and_meta_counts( $request, $attributes ) { $attributes_data = $request->get_param( 'attributes' ); $calculate_attribute_counts = $request->get_param( 'calculate_attribute_counts' ); $query_type = $calculate_attribute_counts[0]['query_type'] ?? 'or'; + $taxonomy = $attributes[0] ?? ''; if ( empty( $attributes_data ) ) { - $taxonomy = $attributes[0] ?? ''; - $counts = $this->get_terms_list( $taxonomy ); + $counts = $this->get_terms_list( $taxonomy ); return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } @@ -180,17 +180,13 @@ public function get_attribute_and_meta_counts( $request, $attributes ) { $formatted_filtered_products_by_terms = implode( ',', array_map( 'intval', $filtered_products_by_terms ) ); - $product_metas = []; - if ( isset( $_REQUEST['min_price'] ) ) { - $product_metas['min_price'] = intval( $_REQUEST['min_price'] ) / 100; - } - - if ( isset( $_REQUEST['max_price'] ) ) { - $product_metas['max_price'] = intval( $_REQUEST['max_price'] ) / 100; - } + $product_metas = [ + 'min_price' => $request->get_param( 'min_price' ), + 'max_price' => $request->get_param( 'max_price' ), + ]; - $product_metas = $this->get_product_by_metas( $product_metas ); - $product_metas = implode( ',', array_map( 'intval', $product_metas ) ); + $filtered_products_by_metas = $this->get_product_by_metas( $product_metas ); + $formatted_filtered_products_by_metas = implode( ',', array_map( 'intval', $filtered_products_by_metas ) ); $where_clause = "posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'"; @@ -198,8 +194,8 @@ public function get_attribute_and_meta_counts( $request, $attributes ) { $where_clause .= " AND product_attribute_lookup.product_or_parent_id IN ({$formatted_filtered_products_by_terms})"; } - if ( ! empty( $product_metas ) ) { - $where_clause .= " AND product_attribute_lookup.product_id IN ({$product_metas})"; + if ( ! empty( $formatted_filtered_products_by_metas ) ) { + $where_clause .= " AND product_attribute_lookup.product_id IN ({$formatted_filtered_products_by_metas})"; } $counts = $wpdb->get_results( From ce07db75d665f2dc89a74c29fa4bab2a2c418797 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Tue, 14 Mar 2023 19:25:59 -0300 Subject: [PATCH 13/47] Update the query and returned value on get_terms_list. --- src/StoreApi/Utilities/ProductQueryFilters.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index e453b5cb45d..f56068fb92b 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -123,9 +123,11 @@ public function get_terms_list( string $taxonomy ) { return $wpdb->get_results( $wpdb->prepare( - "SELECT DISTINCT term_id as term_count_id, 0 as term_count - FROM {$wpdb->prefix}wc_product_attributes_lookup - WHERE taxonomy = %s", + 'SELECT term_id as term_count_id, + count(DISTINCT product_or_parent_id) as term_count + FROM wp_wc_product_attributes_lookup + WHERE taxonomy = %s + GROUP BY term_id', $taxonomy ) ); @@ -143,7 +145,6 @@ public function get_attribute_and_meta_counts( $request, $attributes ) { $attributes_data = $request->get_param( 'attributes' ); $calculate_attribute_counts = $request->get_param( 'calculate_attribute_counts' ); - $query_type = $calculate_attribute_counts[0]['query_type'] ?? 'or'; $taxonomy = $attributes[0] ?? ''; if ( empty( $attributes_data ) ) { @@ -175,6 +176,13 @@ public function get_attribute_and_meta_counts( $request, $attributes ) { continue; } + $query_type = 'or'; + foreach ( $calculate_attribute_counts as $calculate_attribute_count ) { + if ( isset( $calculate_attribute_count['taxonomy'] ) && $calculate_attribute_count['taxonomy'] === $taxonomy ) { + $query_type = $calculate_attribute_count['query_type']; + } + } + $filtered_products_by_terms = array_merge( $filtered_products_by_terms, $this->get_product_by_filtered_terms( $taxonomy, $term_ids, $query_type ) ); } From 551a81d569dbfcb1f7a3ac419cffc2816f2c2a09 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Tue, 14 Mar 2023 21:51:25 -0300 Subject: [PATCH 14/47] Update the validation for returning the default counts when no values are filtered. --- src/StoreApi/Utilities/ProductQueryFilters.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index f56068fb92b..a0050693f06 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -145,9 +145,11 @@ public function get_attribute_and_meta_counts( $request, $attributes ) { $attributes_data = $request->get_param( 'attributes' ); $calculate_attribute_counts = $request->get_param( 'calculate_attribute_counts' ); + $min_price = $request->get_param( 'min_price' ); + $max_price = $request->get_param( 'max_price' ); $taxonomy = $attributes[0] ?? ''; - if ( empty( $attributes_data ) ) { + if ( empty( $attributes_data ) && empty( $min_price ) && empty( $max_price ) ) { $counts = $this->get_terms_list( $taxonomy ); return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); @@ -189,8 +191,8 @@ public function get_attribute_and_meta_counts( $request, $attributes ) { $formatted_filtered_products_by_terms = implode( ',', array_map( 'intval', $filtered_products_by_terms ) ); $product_metas = [ - 'min_price' => $request->get_param( 'min_price' ), - 'max_price' => $request->get_param( 'max_price' ), + 'min_price' => $min_price, + 'max_price' => $max_price, ]; $filtered_products_by_metas = $this->get_product_by_metas( $product_metas ); From 1cb4780003a65e08acb553f4a91b334ad4ae7fb7 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Tue, 14 Mar 2023 21:59:53 -0300 Subject: [PATCH 15/47] Update the query on get_terms_list to use ->prefix --- src/StoreApi/Utilities/ProductQueryFilters.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index a0050693f06..afa82398043 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -123,11 +123,11 @@ public function get_terms_list( string $taxonomy ) { return $wpdb->get_results( $wpdb->prepare( - 'SELECT term_id as term_count_id, + "SELECT term_id as term_count_id, count(DISTINCT product_or_parent_id) as term_count - FROM wp_wc_product_attributes_lookup + FROM {$wpdb->prefix}wc_product_attributes_lookup WHERE taxonomy = %s - GROUP BY term_id', + GROUP BY term_id", $taxonomy ) ); From 90d37c9cb931595b3b3af98e45547570a21ab443 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Tue, 21 Mar 2023 18:53:29 -0300 Subject: [PATCH 16/47] Update the variable for the query to rely on the filtered one. Update the min_price and max_price format on get_product_by_metas. --- src/StoreApi/Utilities/ProductQueryFilters.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index b24bbd6c20b..14d4cfa7bbf 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -147,10 +147,10 @@ public function get_attribute_and_meta_counts( $request, $attributes ) { $calculate_attribute_counts = $request->get_param( 'calculate_attribute_counts' ); $min_price = $request->get_param( 'min_price' ); $max_price = $request->get_param( 'max_price' ); - $taxonomy = $attributes[0] ?? ''; + $filtered_taxonomy = $attributes[0] ?? ''; if ( empty( $attributes_data ) && empty( $min_price ) && empty( $max_price ) ) { - $counts = $this->get_terms_list( $taxonomy ); + $counts = $this->get_terms_list( $filtered_taxonomy ); return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } @@ -223,7 +223,7 @@ public function get_attribute_and_meta_counts( $request, $attributes ) { WHERE {$where_clause} GROUP BY product_attribute_lookup.term_id ) summarize ON attributes.term_id = summarize.term_id", - $taxonomy + $filtered_taxonomy ) ); @@ -292,14 +292,14 @@ public function get_product_by_metas( $metas = array() ) { foreach ( $metas as $column => $value ) { if ( 'min_price' === $column ) { - $where[] = "{$column} >= %f"; - $params[] = (float) $value; + $where[] = "{$column} >= %d"; + $params[] = intval( $value ) / 100; continue; } if ( 'max_price' === $column ) { - $where[] = "{$column} <= %f"; - $params[] = (float) $value; + $where[] = "{$column} <= %d"; + $params[] = intval( $value ) / 100; continue; } From fb8895f956cbdd0af0546704acd517ec47ca4a63 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 22 Mar 2023 14:29:07 -0300 Subject: [PATCH 17/47] Ensure the get_product_by_filtered_terms method is triggered for each one of the filtered terms and update the macro query to include those term ids on the WHERE clause. --- .../Utilities/ProductQueryFilters.php | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 14d4cfa7bbf..3b4b1f71609 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -155,7 +155,7 @@ public function get_attribute_and_meta_counts( $request, $attributes ) { return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } - $filtered_products_by_terms = []; + $where_clause = "posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'"; foreach ( $attributes_data as $attribute ) { $taxonomy = $attribute['attribute'] ?? ''; $filtered_terms = $attribute['slug'] ?? ''; @@ -178,17 +178,20 @@ public function get_attribute_and_meta_counts( $request, $attributes ) { continue; } - $query_type = 'or'; foreach ( $calculate_attribute_counts as $calculate_attribute_count ) { + $query_type = 'or'; if ( isset( $calculate_attribute_count['taxonomy'] ) && $calculate_attribute_count['taxonomy'] === $taxonomy ) { $query_type = $calculate_attribute_count['query_type']; } - } - $filtered_products_by_terms = array_merge( $filtered_products_by_terms, $this->get_product_by_filtered_terms( $taxonomy, $term_ids, $query_type ) ); - } + $filtered_products_by_terms = $this->get_product_by_filtered_terms( $taxonomy, $term_ids, $query_type ); + $formatted_filtered_products_by_terms = implode( ',', array_map( 'intval', $filtered_products_by_terms ) ); - $formatted_filtered_products_by_terms = implode( ',', array_map( 'intval', $filtered_products_by_terms ) ); + if ( ! empty( $formatted_filtered_products_by_terms ) ) { + $where_clause .= " AND product_attribute_lookup.product_or_parent_id IN ({$formatted_filtered_products_by_terms})"; + } + } + } $product_metas = [ 'min_price' => $min_price, @@ -198,12 +201,6 @@ public function get_attribute_and_meta_counts( $request, $attributes ) { $filtered_products_by_metas = $this->get_product_by_metas( $product_metas ); $formatted_filtered_products_by_metas = implode( ',', array_map( 'intval', $filtered_products_by_metas ) ); - $where_clause = "posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'"; - - if ( ! empty( $formatted_filtered_products_by_terms ) ) { - $where_clause .= " AND product_attribute_lookup.product_or_parent_id IN ({$formatted_filtered_products_by_terms})"; - } - if ( ! empty( $formatted_filtered_products_by_metas ) ) { $where_clause .= " AND product_attribute_lookup.product_id IN ({$formatted_filtered_products_by_metas})"; } From 074ac6cd77bac2fa8a28d84c3a65ea48f671dc88 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 22 Mar 2023 17:36:44 -0300 Subject: [PATCH 18/47] Make adjustments for the 'and' condition to work as expected. --- .../Routes/V1/ProductCollectionData.php | 30 ++----------------- .../Utilities/ProductQueryFilters.php | 19 ++++++------ 2 files changed, 13 insertions(+), 36 deletions(-) diff --git a/src/StoreApi/Routes/V1/ProductCollectionData.php b/src/StoreApi/Routes/V1/ProductCollectionData.php index b7eb3a788f6..69edc834031 100644 --- a/src/StoreApi/Routes/V1/ProductCollectionData.php +++ b/src/StoreApi/Routes/V1/ProductCollectionData.php @@ -91,36 +91,12 @@ protected function get_route_response( \WP_REST_Request $request ) { } if ( ! empty( $request['calculate_attribute_counts'] ) ) { - $taxonomy__or_queries = []; - $taxonomy__and_queries = []; - foreach ( $request['calculate_attribute_counts'] as $attributes_to_count ) { - if ( ! empty( $attributes_to_count['taxonomy'] ) ) { - if ( empty( $attributes_to_count['query_type'] ) || 'or' === $attributes_to_count['query_type'] ) { - $taxonomy__or_queries[] = $attributes_to_count['taxonomy']; - } else { - $taxonomy__and_queries[] = $attributes_to_count['taxonomy']; - } + if ( ! isset( $attributes_to_count['taxonomy'] ) ) { + continue; } - } - - $data['attribute_counts'] = []; - // Or type queries need special handling because the attribute, if set, needs removing from the query first otherwise counts would not be correct. - if ( $taxonomy__or_queries ) { - foreach ( $taxonomy__or_queries as $taxonomy ) { - $counts = $filters->get_attribute_and_meta_counts( $request, [ $taxonomy ] ); - - foreach ( $counts as $key => $value ) { - $data['attribute_counts'][] = (object) [ - 'term' => $key, - 'count' => $value, - ]; - } - } - } - if ( $taxonomy__and_queries ) { - $counts = $filters->get_attribute_and_meta_counts( $request, $taxonomy__and_queries ); + $counts = $filters->get_attribute_and_meta_counts( $request, $attributes_to_count['taxonomy'] ); foreach ( $counts as $key => $value ) { $data['attribute_counts'][] = (object) [ diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 3b4b1f71609..4a97687c3dd 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -3,6 +3,7 @@ use Automattic\WooCommerce\StoreApi\Utilities\ProductQuery; use Exception; +use WP_REST_Request; /** * Product Query filters class. @@ -137,20 +138,20 @@ public function get_terms_list( string $taxonomy ) { * Get attribute and meta counts. * * @param WP_REST_Request $request Request data. - * @param array $attributes Attributes to count. + * @param string $filtered_attribute The attribute to count. + * * @return array */ - public function get_attribute_and_meta_counts( $request, $attributes ) { + public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { global $wpdb; $attributes_data = $request->get_param( 'attributes' ); $calculate_attribute_counts = $request->get_param( 'calculate_attribute_counts' ); $min_price = $request->get_param( 'min_price' ); $max_price = $request->get_param( 'max_price' ); - $filtered_taxonomy = $attributes[0] ?? ''; if ( empty( $attributes_data ) && empty( $min_price ) && empty( $max_price ) ) { - $counts = $this->get_terms_list( $filtered_taxonomy ); + $counts = $this->get_terms_list( $filtered_attribute ); return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } @@ -179,12 +180,12 @@ public function get_attribute_and_meta_counts( $request, $attributes ) { } foreach ( $calculate_attribute_counts as $calculate_attribute_count ) { - $query_type = 'or'; - if ( isset( $calculate_attribute_count['taxonomy'] ) && $calculate_attribute_count['taxonomy'] === $taxonomy ) { - $query_type = $calculate_attribute_count['query_type']; + if ( ! isset( $calculate_attribute_count['taxonomy'] ) && ! isset( $calculate_attribute_count['query_type'] ) ) { + continue; } - $filtered_products_by_terms = $this->get_product_by_filtered_terms( $taxonomy, $term_ids, $query_type ); + $query_type = $calculate_attribute_count['query_type']; + $filtered_products_by_terms = $this->get_product_by_filtered_terms( $calculate_attribute_count['taxonomy'], $term_ids, $query_type ); $formatted_filtered_products_by_terms = implode( ',', array_map( 'intval', $filtered_products_by_terms ) ); if ( ! empty( $formatted_filtered_products_by_terms ) ) { @@ -220,7 +221,7 @@ public function get_attribute_and_meta_counts( $request, $attributes ) { WHERE {$where_clause} GROUP BY product_attribute_lookup.term_id ) summarize ON attributes.term_id = summarize.term_id", - $filtered_taxonomy + $filtered_attribute ) ); From 88490af85c215baf5421ef5c705153909d174819 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Mon, 13 Mar 2023 16:41:16 -0300 Subject: [PATCH 19/47] Ensure the queryState.attributes is properly added as a param to the API request to correctly fetch the attribute count data. --- assets/js/blocks/attribute-filter/block.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/assets/js/blocks/attribute-filter/block.tsx b/assets/js/blocks/attribute-filter/block.tsx index 04007431803..b3e72e472f8 100644 --- a/assets/js/blocks/attribute-filter/block.tsx +++ b/assets/js/blocks/attribute-filter/block.tsx @@ -142,9 +142,6 @@ const AttributeFilterBlock = ( { shouldSelect: blockAttributes.attributeId > 0, } ); - const filterAvailableTerms = - blockAttributes.displayStyle !== 'dropdown' && - blockAttributes.queryType === 'and'; const { results: filteredCounts, isLoading: filteredCountsLoading } = useCollectionData( { queryAttribute: { @@ -153,7 +150,6 @@ const AttributeFilterBlock = ( { }, queryState: { ...queryState, - attributes: filterAvailableTerms ? queryState.attributes : null, }, productIds, isEditor, From e750d473981022291693d5a9b8288320500ca64d Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Sun, 26 Mar 2023 18:31:13 -0300 Subject: [PATCH 20/47] Ensure the get_product_by_metas method is only triggered when at least one of the metas in the request is not empty. --- src/StoreApi/Utilities/ProductQueryFilters.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 4a97687c3dd..bdd1f73c7c9 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -194,16 +194,18 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { } } - $product_metas = [ - 'min_price' => $min_price, - 'max_price' => $max_price, - ]; + if ( ! empty( $min_price ) || ! empty( $max_price ) ) { + $product_metas = [ + 'min_price' => $min_price, + 'max_price' => $max_price, + ]; - $filtered_products_by_metas = $this->get_product_by_metas( $product_metas ); - $formatted_filtered_products_by_metas = implode( ',', array_map( 'intval', $filtered_products_by_metas ) ); + $filtered_products_by_metas = $this->get_product_by_metas( $product_metas ); + $formatted_filtered_products_by_metas = implode( ',', array_map( 'intval', $filtered_products_by_metas ) ); - if ( ! empty( $formatted_filtered_products_by_metas ) ) { - $where_clause .= " AND product_attribute_lookup.product_id IN ({$formatted_filtered_products_by_metas})"; + if ( ! empty( $formatted_filtered_products_by_metas ) ) { + $where_clause .= " AND product_attribute_lookup.product_id IN ({$formatted_filtered_products_by_metas})"; + } } $counts = $wpdb->get_results( From 77e2c117ffbfb709d6687f408411daaa7d86c412 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 29 Mar 2023 08:56:10 -0300 Subject: [PATCH 21/47] Join type update: for the 'and' (all) filter condition, items with the count zero are not displayed. --- .../Utilities/ProductQueryFilters.php | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index bdd1f73c7c9..7c5e4cc03a2 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -157,14 +157,29 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { } $where_clause = "posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'"; + if ( ! empty( $min_price ) || ! empty( $max_price ) ) { + $product_metas = [ + 'min_price' => $min_price, + 'max_price' => $max_price, + ]; + + $filtered_products_by_metas = $this->get_product_by_metas( $product_metas ); + $formatted_filtered_products_by_metas = implode( ',', array_map( 'intval', $filtered_products_by_metas ) ); + + if ( ! empty( $formatted_filtered_products_by_metas ) ) { + $where_clause .= " AND product_attribute_lookup.product_id IN ({$formatted_filtered_products_by_metas})"; + } + } + + $join_type = 'LEFT'; foreach ( $attributes_data as $attribute ) { - $taxonomy = $attribute['attribute'] ?? ''; $filtered_terms = $attribute['slug'] ?? ''; if ( empty( $filtered_terms ) ) { continue; } + $taxonomy = $attribute['attribute'] ?? ''; $term_ids = []; if ( in_array( $taxonomy, wc_get_attribute_taxonomy_names(), true ) ) { foreach ( $filtered_terms as $filtered_term ) { @@ -191,20 +206,10 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { if ( ! empty( $formatted_filtered_products_by_terms ) ) { $where_clause .= " AND product_attribute_lookup.product_or_parent_id IN ({$formatted_filtered_products_by_terms})"; } - } - } - - if ( ! empty( $min_price ) || ! empty( $max_price ) ) { - $product_metas = [ - 'min_price' => $min_price, - 'max_price' => $max_price, - ]; - - $filtered_products_by_metas = $this->get_product_by_metas( $product_metas ); - $formatted_filtered_products_by_metas = implode( ',', array_map( 'intval', $filtered_products_by_metas ) ); - if ( ! empty( $formatted_filtered_products_by_metas ) ) { - $where_clause .= " AND product_attribute_lookup.product_id IN ({$formatted_filtered_products_by_metas})"; + if ( $calculate_attribute_count['taxonomy'] === $filtered_attribute ) { + $join_type = 'or' === $query_type ? 'LEFT' : 'INNER'; + } } } @@ -215,7 +220,7 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { SELECT DISTINCT term_id FROM {$wpdb->prefix}wc_product_attributes_lookup WHERE taxonomy = %s) as attributes - LEFT JOIN ( + %1s JOIN ( SELECT COUNT(DISTINCT product_attribute_lookup.product_or_parent_id) as term_count, product_attribute_lookup.term_id FROM {$wpdb->prefix}wc_product_attributes_lookup product_attribute_lookup INNER JOIN {$wpdb->posts} posts @@ -223,7 +228,8 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { WHERE {$where_clause} GROUP BY product_attribute_lookup.term_id ) summarize ON attributes.term_id = summarize.term_id", - $filtered_attribute + $filtered_attribute, + $join_type ) ); From d8c6ec70377c8bd15502329ad222d2666b5dc4ba Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 29 Mar 2023 12:20:26 -0300 Subject: [PATCH 22/47] wpdb prepare the where clauses --- src/StoreApi/Utilities/ProductQueryFilters.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 7c5e4cc03a2..224cfe09e8e 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -167,7 +167,7 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { $formatted_filtered_products_by_metas = implode( ',', array_map( 'intval', $filtered_products_by_metas ) ); if ( ! empty( $formatted_filtered_products_by_metas ) ) { - $where_clause .= " AND product_attribute_lookup.product_id IN ({$formatted_filtered_products_by_metas})"; + $where_clause .= $wpdb->prepare( ' AND product_attribute_lookup.product_id IN (%1s)', $formatted_filtered_products_by_metas ); } } @@ -204,7 +204,7 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { $formatted_filtered_products_by_terms = implode( ',', array_map( 'intval', $filtered_products_by_terms ) ); if ( ! empty( $formatted_filtered_products_by_terms ) ) { - $where_clause .= " AND product_attribute_lookup.product_or_parent_id IN ({$formatted_filtered_products_by_terms})"; + $where_clause .= $wpdb->prepare( ' AND product_attribute_lookup.product_or_parent_id IN (%1s)', $formatted_filtered_products_by_terms ); } if ( $calculate_attribute_count['taxonomy'] === $filtered_attribute ) { From 1276e523ae922633f4d11951875d55215f516c03 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 29 Mar 2023 13:46:01 -0300 Subject: [PATCH 23/47] Update the get_product_by_filtered_terms query wpdb prepare params --- src/StoreApi/Utilities/ProductQueryFilters.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 224cfe09e8e..a874fd2c072 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -343,38 +343,36 @@ public function get_product_by_filtered_terms( $taxonomy = '', $term_ids = array $term_ids = implode( ',', array_map( 'intval', $term_ids ) ); if ( 'or' === $query_type ) { - // phpcs:disable $results = $wpdb->get_col( $wpdb->prepare( " SELECT DISTINCT `product_or_parent_id` FROM {$wpdb->prefix}wc_product_attributes_lookup WHERE `taxonomy` = %s - AND `term_id` IN ({$term_ids}) + AND `term_id` IN (%1s) ", - $taxonomy + $taxonomy, + $term_ids ) ); - // phpcs:enable } if ( 'and' === $query_type ) { - // phpcs:disable $results = $wpdb->get_col( $wpdb->prepare( " SELECT DISTINCT `product_or_parent_id` FROM {$wpdb->prefix}wc_product_attributes_lookup WHERE `taxonomy` = %s - AND `term_id` IN ({$term_ids}) + AND `term_id` IN (%1s) GROUP BY `product_or_parent_id` HAVING COUNT( DISTINCT `term_id` ) >= %d ", $taxonomy, + $term_ids, $term_count ) ); - // phpcs:enable } return $results; From 6408941a41e6aeb7bc53de64144695b2e067869e Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 29 Mar 2023 14:15:59 -0300 Subject: [PATCH 24/47] update the get_product_by_metas method's where clause preparation. --- src/StoreApi/Utilities/ProductQueryFilters.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index a874fd2c072..1656b331ce5 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -315,10 +315,11 @@ public function get_product_by_metas( $metas = array() ) { if ( ! empty( $where ) ) { $where_clause = implode( ' AND ', $where ); - // Use a parameterized query. + $where_clause = sprintf( $where_clause, ...$params ); $results = $wpdb->get_col( - $wpdb->prepare( "SELECT DISTINCT product_id FROM {$wpdb->prefix}wc_product_meta_lookup WHERE {$where_clause}", // phpcs:ignore - $params + $wpdb->prepare( + "SELECT DISTINCT product_id FROM {$wpdb->prefix}wc_product_meta_lookup WHERE %1s", + $where_clause ) ); } From 62f9910d9564c5005b79269217ad16a2a9de6080 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 29 Mar 2023 14:33:51 -0300 Subject: [PATCH 25/47] Update the where clause preparation for get_attribute_and_meta_counts so we don't rely on interpolated variables anymore. --- src/StoreApi/Utilities/ProductQueryFilters.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 1656b331ce5..323a8b913df 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -156,7 +156,7 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } - $where_clause = "posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'"; + $where_clause = ''; if ( ! empty( $min_price ) || ! empty( $max_price ) ) { $product_metas = [ 'min_price' => $min_price, @@ -167,7 +167,7 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { $formatted_filtered_products_by_metas = implode( ',', array_map( 'intval', $filtered_products_by_metas ) ); if ( ! empty( $formatted_filtered_products_by_metas ) ) { - $where_clause .= $wpdb->prepare( ' AND product_attribute_lookup.product_id IN (%1s)', $formatted_filtered_products_by_metas ); + $where_clause .= sprintf( ' AND product_attribute_lookup.product_id IN (%1s)', $formatted_filtered_products_by_metas ); } } @@ -204,7 +204,7 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { $formatted_filtered_products_by_terms = implode( ',', array_map( 'intval', $filtered_products_by_terms ) ); if ( ! empty( $formatted_filtered_products_by_terms ) ) { - $where_clause .= $wpdb->prepare( ' AND product_attribute_lookup.product_or_parent_id IN (%1s)', $formatted_filtered_products_by_terms ); + $where_clause .= sprintf( ' AND product_attribute_lookup.product_or_parent_id IN (%1s)', $formatted_filtered_products_by_terms ); } if ( $calculate_attribute_count['taxonomy'] === $filtered_attribute ) { @@ -225,11 +225,12 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { FROM {$wpdb->prefix}wc_product_attributes_lookup product_attribute_lookup INNER JOIN {$wpdb->posts} posts ON posts.ID = product_attribute_lookup.product_id - WHERE {$where_clause} + WHERE posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'%1s GROUP BY product_attribute_lookup.term_id ) summarize ON attributes.term_id = summarize.term_id", $filtered_attribute, - $join_type + $join_type, + $where_clause ) ); @@ -316,7 +317,7 @@ public function get_product_by_metas( $metas = array() ) { if ( ! empty( $where ) ) { $where_clause = implode( ' AND ', $where ); $where_clause = sprintf( $where_clause, ...$params ); - $results = $wpdb->get_col( + $results = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT product_id FROM {$wpdb->prefix}wc_product_meta_lookup WHERE %1s", $where_clause From a2861c942f3793ba79c849d17fcb79af2e9ccf02 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 29 Mar 2023 15:53:46 -0300 Subject: [PATCH 26/47] Adjust the get_attribute_and_meta_counts method for usage alongside the rating filter. --- .../Utilities/ProductQueryFilters.php | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 323a8b913df..66448399d16 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -134,6 +134,29 @@ public function get_terms_list( string $taxonomy ) { ); } + /** + * Get the empty terms list for a given taxonomy. + * + * @param string $taxonomy Taxonomy name. + * + * @return array + */ + public function get_empty_terms_list( string $taxonomy ) { + global $wpdb; + + return $wpdb->get_results( + $wpdb->prepare( + "SELECT DISTINCT term_id as term_count_id, + 0 as term_count + FROM {$wpdb->prefix}wc_product_attributes_lookup + WHERE taxonomy = %s", + $taxonomy + ) + ); + } + + + /** * Get attribute and meta counts. * @@ -149,18 +172,20 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { $calculate_attribute_counts = $request->get_param( 'calculate_attribute_counts' ); $min_price = $request->get_param( 'min_price' ); $max_price = $request->get_param( 'max_price' ); + $rating = $request->get_param( 'rating' ); - if ( empty( $attributes_data ) && empty( $min_price ) && empty( $max_price ) ) { + if ( empty( $attributes_data ) && empty( $min_price ) && empty( $max_price ) && empty( $rating ) ) { $counts = $this->get_terms_list( $filtered_attribute ); return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } $where_clause = ''; - if ( ! empty( $min_price ) || ! empty( $max_price ) ) { + if ( ! empty( $min_price ) || ! empty( $max_price ) || ! empty( $rating ) ) { $product_metas = [ 'min_price' => $min_price, 'max_price' => $max_price, + 'rating' => $rating, ]; $filtered_products_by_metas = $this->get_product_by_metas( $product_metas ); @@ -168,6 +193,10 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { if ( ! empty( $formatted_filtered_products_by_metas ) ) { $where_clause .= sprintf( ' AND product_attribute_lookup.product_id IN (%1s)', $formatted_filtered_products_by_metas ); + } else { + $counts = $this->get_empty_terms_list( $filtered_attribute ); + + return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } } @@ -298,6 +327,10 @@ public function get_product_by_metas( $metas = array() ) { $params = array(); foreach ( $metas as $column => $value ) { + if ( empty( $value ) ) { + continue; + } + if ( 'min_price' === $column ) { $where[] = "{$column} >= %d"; $params[] = intval( $value ) / 100; @@ -310,13 +343,20 @@ public function get_product_by_metas( $metas = array() ) { continue; } + if ( 'rating' === $column ) { + $where[] = 'average_rating >= %s - 0.5 AND average_rating <= %s + 0.5'; + $params[] = is_array( $value ) ? implode( ',', $value ) : $value; + continue; + } + $where[] = "{$column} = %s"; - $params[] = $value; + $params[] = is_array( $value ) ? implode( ',', $value ) : $value; } if ( ! empty( $where ) ) { $where_clause = implode( ' AND ', $where ); $where_clause = sprintf( $where_clause, ...$params ); + $results = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT product_id FROM {$wpdb->prefix}wc_product_meta_lookup WHERE %1s", From b715ceff90a10ded789e1c9bdf42513749c48fe4 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 29 Mar 2023 16:23:06 -0300 Subject: [PATCH 27/47] Adjust the query for fetching the attribute counts for filtered ratings. --- src/StoreApi/Utilities/ProductQueryFilters.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 66448399d16..1363e5296be 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -344,8 +344,8 @@ public function get_product_by_metas( $metas = array() ) { } if ( 'rating' === $column ) { - $where[] = 'average_rating >= %s - 0.5 AND average_rating <= %s + 0.5'; - $params[] = is_array( $value ) ? implode( ',', $value ) : $value; + $value = is_array( $value ) ? implode( ',', $value ) : $value; + $where[] = sprintf( 'average_rating BETWEEN %s - 0.5 AND %s + 0.5', $value, $value ); continue; } From 601c5a95ec6ca9d6983a2e575a01106adf4f41ea Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 29 Mar 2023 20:49:32 -0300 Subject: [PATCH 28/47] Add support for the filter by stock. --- .../Utilities/ProductQueryFilters.php | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 1363e5296be..8c3d513bf71 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -155,8 +155,6 @@ public function get_empty_terms_list( string $taxonomy ) { ); } - - /** * Get attribute and meta counts. * @@ -173,19 +171,21 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { $min_price = $request->get_param( 'min_price' ); $max_price = $request->get_param( 'max_price' ); $rating = $request->get_param( 'rating' ); + $stock_status = $request->get_param( 'stock_status' ); - if ( empty( $attributes_data ) && empty( $min_price ) && empty( $max_price ) && empty( $rating ) ) { + if ( empty( $attributes_data ) && empty( $min_price ) && empty( $max_price ) && empty( $rating ) && empty( $stock_status ) ) { $counts = $this->get_terms_list( $filtered_attribute ); return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); } $where_clause = ''; - if ( ! empty( $min_price ) || ! empty( $max_price ) || ! empty( $rating ) ) { + if ( ! empty( $min_price ) || ! empty( $max_price ) || ! empty( $rating ) || ! empty( $stock_status ) ) { $product_metas = [ - 'min_price' => $min_price, - 'max_price' => $max_price, - 'rating' => $rating, + 'min_price' => $min_price, + 'max_price' => $max_price, + 'average_rating' => $rating, + 'stock_status' => $stock_status, ]; $filtered_products_by_metas = $this->get_product_by_metas( $product_metas ); @@ -325,12 +325,23 @@ public function get_product_by_metas( $metas = array() ) { $where = array(); $results = array(); $params = array(); - foreach ( $metas as $column => $value ) { if ( empty( $value ) ) { continue; } + $value = is_array( $value ) ? implode( ',', $value ) : $value; + + if ( 'stock_status' === $column ) { + $stock_product_ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT DISTINCT product_id FROM {$wpdb->prefix}wc_product_meta_lookup WHERE stock_status = %s", + $value + ) + ); + continue; + } + if ( 'min_price' === $column ) { $where[] = "{$column} >= %d"; $params[] = intval( $value ) / 100; @@ -343,20 +354,23 @@ public function get_product_by_metas( $metas = array() ) { continue; } - if ( 'rating' === $column ) { - $value = is_array( $value ) ? implode( ',', $value ) : $value; - $where[] = sprintf( 'average_rating BETWEEN %s - 0.5 AND %s + 0.5', $value, $value ); + if ( 'average_rating' === $column ) { + $value = is_array( $value ) ? implode( ',', $value ) : $value; + $where[] = sprintf( 'average_rating BETWEEN %s - 0.5 AND %s + 0.5', $value, $value ); continue; } - $where[] = "{$column} = %s"; - $params[] = is_array( $value ) ? implode( ',', $value ) : $value; + $where[] = sprintf( "%1s = '%s'", $column, $value ); + $params[] = $value; + } + + if ( isset( $stock_product_ids ) && ! empty( $stock_product_ids ) ) { + $where[] = 'product_id IN (' . implode( ',', $stock_product_ids ) . ')'; } if ( ! empty( $where ) ) { $where_clause = implode( ' AND ', $where ); $where_clause = sprintf( $where_clause, ...$params ); - $results = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT product_id FROM {$wpdb->prefix}wc_product_meta_lookup WHERE %1s", From f8ba66fef9ba23fce19920c40f5759569df573ed Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Fri, 31 Mar 2023 11:18:31 -0300 Subject: [PATCH 29/47] Ensure the product attribute counts are correct if the parent product receives a rating. --- src/StoreApi/Utilities/ProductQueryFilters.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 8c3d513bf71..f89ea25e1fe 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -192,7 +192,7 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { $formatted_filtered_products_by_metas = implode( ',', array_map( 'intval', $filtered_products_by_metas ) ); if ( ! empty( $formatted_filtered_products_by_metas ) ) { - $where_clause .= sprintf( ' AND product_attribute_lookup.product_id IN (%1s)', $formatted_filtered_products_by_metas ); + $where_clause .= sprintf( ' AND product_attribute_lookup.product_or_parent_id IN (%1s)', $formatted_filtered_products_by_metas ); } else { $counts = $this->get_empty_terms_list( $filtered_attribute ); From f77b6f8dc2e952dfcf027adee34d7cecf9d1de56 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Fri, 31 Mar 2023 11:40:59 -0300 Subject: [PATCH 30/47] Ensure product_or_parent_id is used only when the filter by rating is used, not affecting price or stock filters. --- src/StoreApi/Utilities/ProductQueryFilters.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index f89ea25e1fe..39fcd67d1a8 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -192,7 +192,11 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { $formatted_filtered_products_by_metas = implode( ',', array_map( 'intval', $filtered_products_by_metas ) ); if ( ! empty( $formatted_filtered_products_by_metas ) ) { - $where_clause .= sprintf( ' AND product_attribute_lookup.product_or_parent_id IN (%1s)', $formatted_filtered_products_by_metas ); + if ( ! empty( $rating ) ) { + $where_clause .= sprintf( ' AND product_attribute_lookup.product_or_parent_id IN (%1s)', $formatted_filtered_products_by_metas ); + } + + $where_clause .= sprintf( ' AND product_attribute_lookup.product_id IN (%1s)', $formatted_filtered_products_by_metas ); } else { $counts = $this->get_empty_terms_list( $filtered_attribute ); From 5df4e9b8082d08b51380b63b355b7b0741179303 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Fri, 31 Mar 2023 11:57:00 -0300 Subject: [PATCH 31/47] Add the missing else condition. --- src/StoreApi/Utilities/ProductQueryFilters.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 39fcd67d1a8..75628f8cd39 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -194,9 +194,9 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { if ( ! empty( $formatted_filtered_products_by_metas ) ) { if ( ! empty( $rating ) ) { $where_clause .= sprintf( ' AND product_attribute_lookup.product_or_parent_id IN (%1s)', $formatted_filtered_products_by_metas ); + } else { + $where_clause .= sprintf( ' AND product_attribute_lookup.product_id IN (%1s)', $formatted_filtered_products_by_metas ); } - - $where_clause .= sprintf( ' AND product_attribute_lookup.product_id IN (%1s)', $formatted_filtered_products_by_metas ); } else { $counts = $this->get_empty_terms_list( $filtered_attribute ); From a186e856dc5b71d1052ff1128cbb23954409e1c1 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Tue, 4 Apr 2023 16:28:29 -0300 Subject: [PATCH 32/47] Enable caching. --- .../Utilities/ProductQueryFilters.php | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 75628f8cd39..3ba7aacf3b5 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -164,8 +164,6 @@ public function get_empty_terms_list( string $taxonomy ) { * @return array */ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { - global $wpdb; - $attributes_data = $request->get_param( 'attributes' ); $calculate_attribute_counts = $request->get_param( 'calculate_attribute_counts' ); $min_price = $request->get_param( 'min_price' ); @@ -173,6 +171,25 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { $rating = $request->get_param( 'rating' ); $stock_status = $request->get_param( 'stock_status' ); + $transient_key = 'wc_get_attribute_and_meta_counts_' . md5( + wp_json_encode( + array( + 'attributes_data' => $attributes_data, + 'calculate_attribute_counts' => $calculate_attribute_counts, + 'min_price' => $min_price, + 'max_price' => $max_price, + 'rating' => $rating, + 'stock_status' => $stock_status, + ) + ) + ); + + $cached_results = get_transient( $transient_key ); + + if ( ! empty( $cached_results ) ) { + return $cached_results; + } + if ( empty( $attributes_data ) && empty( $min_price ) && empty( $max_price ) && empty( $rating ) && empty( $stock_status ) ) { $counts = $this->get_terms_list( $filtered_attribute ); @@ -246,6 +263,7 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { } } + global $wpdb; $counts = $wpdb->get_results( $wpdb->prepare( "SELECT attributes.term_id as term_count_id, coalesce(term_count, 0) as term_count @@ -267,7 +285,11 @@ public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { ) ); - return array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); + $results = array_map( 'absint', wp_list_pluck( $counts, 'term_count', 'term_count_id' ) ); + + set_transient( $transient_key, $results, 24 * HOUR_IN_SECONDS ); + + return $results; } /** From 42d6d2323510aca48b4c4b1bc11dabcf2a0b58a5 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Tue, 4 Apr 2023 16:41:03 -0300 Subject: [PATCH 33/47] Address CR --- src/StoreApi/Routes/V1/ProductCollectionData.php | 2 +- src/StoreApi/Utilities/ProductQueryFilters.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/StoreApi/Routes/V1/ProductCollectionData.php b/src/StoreApi/Routes/V1/ProductCollectionData.php index 69edc834031..94d73233069 100644 --- a/src/StoreApi/Routes/V1/ProductCollectionData.php +++ b/src/StoreApi/Routes/V1/ProductCollectionData.php @@ -96,7 +96,7 @@ protected function get_route_response( \WP_REST_Request $request ) { continue; } - $counts = $filters->get_attribute_and_meta_counts( $request, $attributes_to_count['taxonomy'] ); + $counts = $filters->get_attribute_counts( $request, $attributes_to_count['taxonomy'] ); foreach ( $counts as $key => $value ) { $data['attribute_counts'][] = (object) [ diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 3ba7aacf3b5..07f7eba981d 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -163,7 +163,7 @@ public function get_empty_terms_list( string $taxonomy ) { * * @return array */ - public function get_attribute_and_meta_counts( $request, $filtered_attribute ) { + public function get_attribute_counts( $request, $filtered_attribute ) { $attributes_data = $request->get_param( 'attributes' ); $calculate_attribute_counts = $request->get_param( 'calculate_attribute_counts' ); $min_price = $request->get_param( 'min_price' ); @@ -390,7 +390,7 @@ public function get_product_by_metas( $metas = array() ) { $params[] = $value; } - if ( isset( $stock_product_ids ) && ! empty( $stock_product_ids ) ) { + if ( ! empty( $stock_product_ids ) ) { $where[] = 'product_id IN (' . implode( ',', $stock_product_ids ) . ')'; } From c98d1ff2e8962cab8c36a9cb50efa1af26ffae26 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Tue, 4 Apr 2023 16:53:42 -0300 Subject: [PATCH 34/47] Update query for average rating. --- src/StoreApi/Utilities/ProductQueryFilters.php | 2 +- tests/php/StoreApi/Routes/error.txt | 0 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tests/php/StoreApi/Routes/error.txt diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 07f7eba981d..48c573d2b02 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -382,7 +382,7 @@ public function get_product_by_metas( $metas = array() ) { if ( 'average_rating' === $column ) { $value = is_array( $value ) ? implode( ',', $value ) : $value; - $where[] = sprintf( 'average_rating BETWEEN %s - 0.5 AND %s + 0.5', $value, $value ); + $where[] = sprintf( 'average_rating >= %s - 0.5 AND average_rating < %s + 0.5', $value, $value ); continue; } diff --git a/tests/php/StoreApi/Routes/error.txt b/tests/php/StoreApi/Routes/error.txt new file mode 100644 index 00000000000..e69de29bb2d From c216e08bdc235b109a727f4f1ad0e27a0295616d Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Tue, 4 Apr 2023 16:54:53 -0300 Subject: [PATCH 35/47] remove file accidentally commited. --- tests/php/StoreApi/Routes/error.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/php/StoreApi/Routes/error.txt diff --git a/tests/php/StoreApi/Routes/error.txt b/tests/php/StoreApi/Routes/error.txt deleted file mode 100644 index e69de29bb2d..00000000000 From 97199212987c937ce0301734cac207a02b656f46 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 5 Apr 2023 12:01:41 -0300 Subject: [PATCH 36/47] When multiple ratings are selected, make sure the where clause is updated accordingly for each one of them. --- src/StoreApi/Utilities/ProductQueryFilters.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 48c573d2b02..daa0ee4d957 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -356,8 +356,6 @@ public function get_product_by_metas( $metas = array() ) { continue; } - $value = is_array( $value ) ? implode( ',', $value ) : $value; - if ( 'stock_status' === $column ) { $stock_product_ids = $wpdb->get_col( $wpdb->prepare( @@ -381,8 +379,16 @@ public function get_product_by_metas( $metas = array() ) { } if ( 'average_rating' === $column ) { - $value = is_array( $value ) ? implode( ',', $value ) : $value; - $where[] = sprintf( 'average_rating >= %s - 0.5 AND average_rating < %s + 0.5', $value, $value ); + if ( is_array( $value ) ) { + $where_rating = array(); + foreach ( $value as $rating ) { + $where_rating[] = sprintf( '(average_rating >= %f - 0.5 AND average_rating < %f + 0.5)', $rating, $rating ); + } + $where[] = '(' . implode( ' OR ', $where_rating ) . ')'; + } else { + $where[] = sprintf( 'average_rating >= %f - 0.5 AND average_rating < %f + 0.5', $value, $value ); + } + continue; } From 3ee28ee951bfac916070fd1bb746b1059a79be77 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 5 Apr 2023 12:38:33 -0300 Subject: [PATCH 37/47] Start updating the stock_status logic to account for when multiple options are selected by the user. --- src/StoreApi/Utilities/ProductQueryFilters.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index daa0ee4d957..db95cded4c8 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -357,10 +357,20 @@ public function get_product_by_metas( $metas = array() ) { } if ( 'stock_status' === $column ) { + if ( is_array( $value ) ) { + $where_stock = array(); + foreach ( $value as $stock_status ) { + $where_stock[] = sprintf( 'stock_status = "%s"', $stock_status ); + } + $where_stock_part = '(' . implode( ' OR ', $where_stock ) . ')'; + } else { + $where_stock_part = sprintf( 'stock_status = "%s"', $value ); + } + $stock_product_ids = $wpdb->get_col( $wpdb->prepare( - "SELECT DISTINCT product_id FROM {$wpdb->prefix}wc_product_meta_lookup WHERE stock_status = %s", - $value + "SELECT DISTINCT product_id FROM {$wpdb->prefix}wc_product_meta_lookup WHERE %s", + $where_stock_part ) ); continue; @@ -382,7 +392,7 @@ public function get_product_by_metas( $metas = array() ) { if ( is_array( $value ) ) { $where_rating = array(); foreach ( $value as $rating ) { - $where_rating[] = sprintf( '(average_rating >= %f - 0.5 AND average_rating < %f + 0.5)', $rating, $rating ); + $where_rating[] = sprintf( '(average_rating >= %f - 0.5 AND average_rating < %f + 0.5)', $rating, $rating ); } $where[] = '(' . implode( ' OR ', $where_rating ) . ')'; } else { From f947d2202dab6613a33678ae46c3351f71b3586d Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 5 Apr 2023 14:04:52 -0300 Subject: [PATCH 38/47] Ensure the counts are properly updated when more than one stock status is selected. --- .../Utilities/ProductQueryFilters.php | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index db95cded4c8..382e62df2d4 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -357,22 +357,17 @@ public function get_product_by_metas( $metas = array() ) { } if ( 'stock_status' === $column ) { - if ( is_array( $value ) ) { - $where_stock = array(); - foreach ( $value as $stock_status ) { - $where_stock[] = sprintf( 'stock_status = "%s"', $stock_status ); - } - $where_stock_part = '(' . implode( ' OR ', $where_stock ) . ')'; - } else { - $where_stock_part = sprintf( 'stock_status = "%s"', $value ); + $stock_product_ids = array(); + foreach ( $value as $stock_status ) { + $stock_product_ids[] = $wpdb->get_col( + $wpdb->prepare( + "SELECT DISTINCT product_id FROM {$wpdb->prefix}wc_product_meta_lookup WHERE stock_status = %s", + $stock_status + ) + ); } - $stock_product_ids = $wpdb->get_col( - $wpdb->prepare( - "SELECT DISTINCT product_id FROM {$wpdb->prefix}wc_product_meta_lookup WHERE %s", - $where_stock_part - ) - ); + $where[] = 'product_id IN (' . implode( ',', array_merge( ...$stock_product_ids ) ) . ')'; continue; } @@ -406,14 +401,11 @@ public function get_product_by_metas( $metas = array() ) { $params[] = $value; } - if ( ! empty( $stock_product_ids ) ) { - $where[] = 'product_id IN (' . implode( ',', $stock_product_ids ) . ')'; - } - if ( ! empty( $where ) ) { $where_clause = implode( ' AND ', $where ); $where_clause = sprintf( $where_clause, ...$params ); - $results = $wpdb->get_col( + + $results = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT product_id FROM {$wpdb->prefix}wc_product_meta_lookup WHERE %1s", $where_clause From 45372aedb773daec7448946869452a272ae73c05 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 5 Apr 2023 16:05:48 -0300 Subject: [PATCH 39/47] Ditch the is_array condition for the average_rating counts as is always an array. --- src/StoreApi/Utilities/ProductQueryFilters.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 382e62df2d4..d4bcf6c6c4d 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -384,16 +384,11 @@ public function get_product_by_metas( $metas = array() ) { } if ( 'average_rating' === $column ) { - if ( is_array( $value ) ) { - $where_rating = array(); - foreach ( $value as $rating ) { - $where_rating[] = sprintf( '(average_rating >= %f - 0.5 AND average_rating < %f + 0.5)', $rating, $rating ); - } - $where[] = '(' . implode( ' OR ', $where_rating ) . ')'; - } else { - $where[] = sprintf( 'average_rating >= %f - 0.5 AND average_rating < %f + 0.5', $value, $value ); + $where_rating = array(); + foreach ( $value as $rating ) { + $where_rating[] = sprintf( '(average_rating >= %f - 0.5 AND average_rating < %f + 0.5)', $rating, $rating ); } - + $where[] = '(' . implode( ' OR ', $where_rating ) . ')'; continue; } From 640f66fd4828b559afe36694d0bc092fe3250b7e Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 5 Apr 2023 17:34:54 -0300 Subject: [PATCH 40/47] Deprecate the second param attributes for the get_attribute_counts method. --- src/StoreApi/Utilities/ProductQueryFilters.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index d4bcf6c6c4d..82f6daca71e 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -164,6 +164,16 @@ public function get_empty_terms_list( string $taxonomy ) { * @return array */ public function get_attribute_counts( $request, $filtered_attribute ) { + if ( is_array( $filtered_attribute ) ) { + wc_deprecated_argument( 'attributes', 'TBD', 'get_attribute_counts does not require an array of attributes as the second parameter anymore. Provide the filtered attribute as a string instead.' ); + + $filtered_attribute = ! empty( $filtered_attribute[0] ) ? $filtered_attribute[0] : ''; + + if ( empty( $filtered_attribute ) ) { + return array(); + } + } + $attributes_data = $request->get_param( 'attributes' ); $calculate_attribute_counts = $request->get_param( 'calculate_attribute_counts' ); $min_price = $request->get_param( 'min_price' ); From e00ce15f3d536227aa348d29c96fb682f0ab4d1b Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Wed, 5 Apr 2023 17:59:58 -0300 Subject: [PATCH 41/47] Add the filtered_attribute to the transient_key --- src/StoreApi/Utilities/ProductQueryFilters.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 82f6daca71e..cb0248a32ae 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -190,6 +190,7 @@ public function get_attribute_counts( $request, $filtered_attribute ) { 'max_price' => $max_price, 'rating' => $rating, 'stock_status' => $stock_status, + 'filtered_attribute' => $filtered_attribute, ) ) ); From 497e101a5f58a7bfc82cf3415b33ab05af925a31 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Thu, 6 Apr 2023 09:22:31 -0300 Subject: [PATCH 42/47] Bypass cache if WP_DEBUG is enabled. --- src/StoreApi/Utilities/ProductQueryFilters.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index cb0248a32ae..be6c1e7b663 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -196,8 +196,7 @@ public function get_attribute_counts( $request, $filtered_attribute ) { ); $cached_results = get_transient( $transient_key ); - - if ( ! empty( $cached_results ) ) { + if ( ! empty( $cached_results ) && defined( 'WP_DEBUG' ) && ! WP_DEBUG ) { return $cached_results; } From cb40f042651b931875c652dc5136c6e4797a3bd8 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Thu, 6 Apr 2023 12:16:30 -0300 Subject: [PATCH 43/47] Update formatting for macro query. --- .../Utilities/ProductQueryFilters.php | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index be6c1e7b663..4aeca6bee52 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -276,19 +276,20 @@ public function get_attribute_counts( $request, $filtered_attribute ) { global $wpdb; $counts = $wpdb->get_results( $wpdb->prepare( - "SELECT attributes.term_id as term_count_id, coalesce(term_count, 0) as term_count - FROM ( - SELECT DISTINCT term_id - FROM {$wpdb->prefix}wc_product_attributes_lookup - WHERE taxonomy = %s) as attributes - %1s JOIN ( - SELECT COUNT(DISTINCT product_attribute_lookup.product_or_parent_id) as term_count, product_attribute_lookup.term_id - FROM {$wpdb->prefix}wc_product_attributes_lookup product_attribute_lookup - INNER JOIN {$wpdb->posts} posts - ON posts.ID = product_attribute_lookup.product_id - WHERE posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'%1s - GROUP BY product_attribute_lookup.term_id - ) summarize ON attributes.term_id = summarize.term_id", + " + SELECT attributes.term_id as term_count_id, coalesce(term_count, 0) as term_count + FROM (SELECT DISTINCT term_id + FROM {$wpdb->prefix}wc_product_attributes_lookup + WHERE taxonomy = %s) as attributes %1s JOIN ( + SELECT COUNT(DISTINCT product_attribute_lookup.product_or_parent_id) as term_count, product_attribute_lookup.term_id + FROM {$wpdb->prefix}wc_product_attributes_lookup product_attribute_lookup + INNER JOIN {$wpdb->posts} posts + ON posts.ID = product_attribute_lookup.product_id + WHERE posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'%1s + GROUP BY product_attribute_lookup.term_id + ) summarize + ON attributes.term_id = summarize.term_id + ", $filtered_attribute, $join_type, $where_clause From 83388ce81811149d150bcf334c789579a7ec3e24 Mon Sep 17 00:00:00 2001 From: roykho Date: Thu, 6 Apr 2023 08:26:11 -0700 Subject: [PATCH 44/47] Fix mixed tabs spaces on query --- src/StoreApi/Utilities/ProductQueryFilters.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 4aeca6bee52..b73a775bbd2 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -279,14 +279,14 @@ public function get_attribute_counts( $request, $filtered_attribute ) { " SELECT attributes.term_id as term_count_id, coalesce(term_count, 0) as term_count FROM (SELECT DISTINCT term_id - FROM {$wpdb->prefix}wc_product_attributes_lookup - WHERE taxonomy = %s) as attributes %1s JOIN ( - SELECT COUNT(DISTINCT product_attribute_lookup.product_or_parent_id) as term_count, product_attribute_lookup.term_id - FROM {$wpdb->prefix}wc_product_attributes_lookup product_attribute_lookup - INNER JOIN {$wpdb->posts} posts - ON posts.ID = product_attribute_lookup.product_id - WHERE posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'%1s - GROUP BY product_attribute_lookup.term_id + FROM {$wpdb->prefix}wc_product_attributes_lookup + WHERE taxonomy = %s) as attributes %1s JOIN ( + SELECT COUNT(DISTINCT product_attribute_lookup.product_or_parent_id) as term_count, product_attribute_lookup.term_id + FROM {$wpdb->prefix}wc_product_attributes_lookup product_attribute_lookup + INNER JOIN {$wpdb->posts} posts + ON posts.ID = product_attribute_lookup.product_id + WHERE posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'%1s + GROUP BY product_attribute_lookup.term_id ) summarize ON attributes.term_id = summarize.term_id ", From 51f344531c2b6ce8bb202859f7959e001ef675d6 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Fri, 7 Apr 2023 12:55:35 -0300 Subject: [PATCH 45/47] Fix PHP unit tests for the new attribute counts. --- tests/php/Helpers/FixtureData.php | 47 +++++++++++++++++++ .../StoreApi/Routes/ProductCollectionData.php | 18 ++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/tests/php/Helpers/FixtureData.php b/tests/php/Helpers/FixtureData.php index 9976e04fc2e..df4d7a7d6b3 100644 --- a/tests/php/Helpers/FixtureData.php +++ b/tests/php/Helpers/FixtureData.php @@ -208,6 +208,53 @@ public function get_coupon( $props ) { return new \WC_Coupon( $coupon->get_id() ); } + /** + * Create a new product taxonomy and term. + * + * @param \WC_Product $product The product to add the term to. + * @param string $taxonomy_name The name of the taxonomy. + * @param string $term_name The name of the term. + * @param string $term_slug The slug of the term. + * @param int $term_parent The parent of the term. + * @param string $term_description The description of the term. + * + * @return array|int[]|\WP_Error|\WP_Taxonomy + */ + public function get_taxonomy_and_term( \WC_Product $product, $taxonomy_name, $term_name, $term_slug = '', $term_parent = 0, $term_description = '' ) { + $taxonomy = register_taxonomy( $taxonomy_name, array( 'product' ), array( 'hierarchical' => true ) ); + + if ( is_wp_error( $taxonomy ) ) { + return $taxonomy; + } + + $term = wp_insert_term( + $term_name, + $taxonomy_name, + array( + 'slug' => $term_slug, + 'parent' => $term_parent, + 'description' => $term_description, + ) + ); + + if ( ! is_wp_error( $term ) && ! empty( $term['term_id'] ) ) { + global $wpdb; + $wpdb->insert( + $wpdb->prefix . 'wc_product_attributes_lookup', + array( + 'product_id' => $product->get_id(), + 'product_or_parent_id' => $product->get_parent_id(), + 'taxonomy' => $taxonomy_name, + 'term_id' => $term['term_id'], + 'is_variation_attribute' => true, + ), + array( '%d', '%d', '%s', '%d', '%d' ) + ); + } + + return $term; + } + /** * Upload a sample image and return it's ID. * diff --git a/tests/php/StoreApi/Routes/ProductCollectionData.php b/tests/php/StoreApi/Routes/ProductCollectionData.php index 255042787fb..80dd569e9a2 100644 --- a/tests/php/StoreApi/Routes/ProductCollectionData.php +++ b/tests/php/StoreApi/Routes/ProductCollectionData.php @@ -82,6 +82,7 @@ public function test_calculate_attribute_counts() { $fixtures->get_product_attribute( 'size', array( 'small', 'medium', 'large' ) ), ) ); + $fixtures->get_taxonomy_and_term( $product, 'pa_size', 'large', 'large' ); $request = new \WP_REST_Request( 'GET', '/wc/store/v1/products/collection-data' ); $request->set_param( @@ -91,8 +92,20 @@ public function test_calculate_attribute_counts() { 'taxonomy' => 'pa_size', 'query_type' => 'and', ), + ), + ); + + $request->set_param( + 'attributes', + array( + array( + 'attribute' => 'pa_size', + 'operator' => 'in', + 'slug' => array( 'large' ), + ), ) ); + $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); @@ -100,8 +113,9 @@ public function test_calculate_attribute_counts() { $this->assertEquals( null, $data['price_range'] ); $this->assertEquals( null, $data['rating_counts'] ); - $this->assertObjectHasAttribute( 'term', $data['attribute_counts'][0] ); - $this->assertObjectHasAttribute( 'count', $data['attribute_counts'][0] ); + $this->assertIsArray( $data ); + $this->assertTrue( property_exists( $data['attribute_counts'][0], 'term' ) ); + $this->assertTrue( property_exists( $data['attribute_counts'][0], 'count' ) ); } /** From 760042901dbd7c4785c325fdd662085b9c68df35 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Mon, 10 Apr 2023 09:39:20 -0300 Subject: [PATCH 46/47] Update spacing/formatting for SQL queries. --- src/StoreApi/Utilities/ProductQueryFilters.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index b73a775bbd2..4aeca6bee52 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -279,14 +279,14 @@ public function get_attribute_counts( $request, $filtered_attribute ) { " SELECT attributes.term_id as term_count_id, coalesce(term_count, 0) as term_count FROM (SELECT DISTINCT term_id - FROM {$wpdb->prefix}wc_product_attributes_lookup - WHERE taxonomy = %s) as attributes %1s JOIN ( - SELECT COUNT(DISTINCT product_attribute_lookup.product_or_parent_id) as term_count, product_attribute_lookup.term_id - FROM {$wpdb->prefix}wc_product_attributes_lookup product_attribute_lookup - INNER JOIN {$wpdb->posts} posts - ON posts.ID = product_attribute_lookup.product_id - WHERE posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'%1s - GROUP BY product_attribute_lookup.term_id + FROM {$wpdb->prefix}wc_product_attributes_lookup + WHERE taxonomy = %s) as attributes %1s JOIN ( + SELECT COUNT(DISTINCT product_attribute_lookup.product_or_parent_id) as term_count, product_attribute_lookup.term_id + FROM {$wpdb->prefix}wc_product_attributes_lookup product_attribute_lookup + INNER JOIN {$wpdb->posts} posts + ON posts.ID = product_attribute_lookup.product_id + WHERE posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'%1s + GROUP BY product_attribute_lookup.term_id ) summarize ON attributes.term_id = summarize.term_id ", From 42e7b9cef308b1851c0ccf147e375406270455e8 Mon Sep 17 00:00:00 2001 From: Patricia Hillebrandt Date: Mon, 10 Apr 2023 09:53:46 -0300 Subject: [PATCH 47/47] Minor: update indentation for the main SQL query --- src/StoreApi/Utilities/ProductQueryFilters.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/StoreApi/Utilities/ProductQueryFilters.php b/src/StoreApi/Utilities/ProductQueryFilters.php index 4aeca6bee52..a0ff5e5793b 100644 --- a/src/StoreApi/Utilities/ProductQueryFilters.php +++ b/src/StoreApi/Utilities/ProductQueryFilters.php @@ -279,14 +279,14 @@ public function get_attribute_counts( $request, $filtered_attribute ) { " SELECT attributes.term_id as term_count_id, coalesce(term_count, 0) as term_count FROM (SELECT DISTINCT term_id - FROM {$wpdb->prefix}wc_product_attributes_lookup - WHERE taxonomy = %s) as attributes %1s JOIN ( - SELECT COUNT(DISTINCT product_attribute_lookup.product_or_parent_id) as term_count, product_attribute_lookup.term_id - FROM {$wpdb->prefix}wc_product_attributes_lookup product_attribute_lookup - INNER JOIN {$wpdb->posts} posts - ON posts.ID = product_attribute_lookup.product_id - WHERE posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'%1s - GROUP BY product_attribute_lookup.term_id + FROM {$wpdb->prefix}wc_product_attributes_lookup + WHERE taxonomy = %s) as attributes %1s JOIN ( + SELECT COUNT(DISTINCT product_attribute_lookup.product_or_parent_id) as term_count, product_attribute_lookup.term_id + FROM {$wpdb->prefix}wc_product_attributes_lookup product_attribute_lookup + INNER JOIN {$wpdb->posts} posts + ON posts.ID = product_attribute_lookup.product_id + WHERE posts.post_type IN ('product', 'product_variation') AND posts.post_status = 'publish'%1s + GROUP BY product_attribute_lookup.term_id ) summarize ON attributes.term_id = summarize.term_id ",