Change filter by attributes widget to count products, not variations.

Right now the filter by attributes widget counts available variations
(for variation products). This is confusing since the counter shows
numbers that are higher than the actual count of products displayed.

This commit changes the query used by the widget so that instead
of counting variations it returns the parent product ids, and then
counts the distinct values. This also covers the case of products
where some of the variations have concrete values and some have
"Any..." values.
This commit is contained in:
Nestor Soriano 2020-08-31 12:22:33 +02:00
parent 602f58a7a9
commit 3a583feab1
2 changed files with 24 additions and 63 deletions

View File

@ -366,9 +366,9 @@ class WC_Widget_Layered_Nav extends WC_Widget {
$term_ids_sql = '(' . implode( ',', array_map( 'absint', $term_ids ) ) . ')';
// Generate the first part of the query.
// This one will count non-variable products and variable products with concrete values for the attributes.
// This one will return non-variable products and variable products with concrete values for the attributes.
$query = array();
$query['select'] = "SELECT COUNT( DISTINCT {$wpdb->posts}.ID ) as term_count, terms.term_id as term_count_id";
$query['select'] = "SELECT {$wpdb->posts}.post_parent as product_id, terms.term_id as term_count_id";
$query['from'] = "FROM {$wpdb->posts}";
$query['join'] = "
INNER JOIN {$wpdb->term_relationships} AS tr ON {$wpdb->posts}.ID = tr.object_id
@ -410,15 +410,14 @@ class WC_Widget_Layered_Nav extends WC_Widget {
$query['where'] .= ' AND ' . $search;
}
$query['group_by'] = 'GROUP BY terms.term_id';
$query = apply_filters( 'woocommerce_get_filtered_term_product_counts_query', $query );
$main_query_sql = implode( ' ', $query );
$query = apply_filters( 'woocommerce_get_filtered_term_product_counts_query', $query );
$main_query_sql = implode( ' ', $query );
// Generate the second part of the query.
// This one will count products having "Any..." as the value of the attribute.
// This one will return products having "Any..." as the value of the attribute.
$query_sql_for_attributes_with_any_value = "
SELECT COUNT( {$wpdb->posts}.post_parent ) AS term_count, {$wpdb->term_relationships}.term_taxonomy_id as term_count_id FROM {$wpdb->postmeta}
SELECT {$wpdb->posts}.post_parent AS product_id, {$wpdb->term_relationships}.term_taxonomy_id as term_count_id FROM {$wpdb->postmeta}
JOIN {$wpdb->posts} ON {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id
JOIN {$wpdb->term_relationships} ON {$wpdb->term_relationships}.object_id = {$wpdb->posts}.post_parent
WHERE {$wpdb->postmeta}.meta_key = 'attribute_$taxonomy'
@ -430,12 +429,11 @@ class WC_Widget_Layered_Nav extends WC_Widget {
SELECT ID FROM {$wpdb->posts} AS parent
WHERE parent.ID = {$wpdb->posts}.post_parent AND parent.post_status NOT IN ('publish')
)
{$main_tax_query_sql['where']}
GROUP BY {$wpdb->term_relationships}.term_taxonomy_id";
{$main_tax_query_sql['where']}";
// Generate the final query as the union+sum of both.
// Generate the final query as the union+count of both.
$query_sql = "
SELECT SUM(term_count) AS term_count, term_count_id FROM (
SELECT COUNT(DISTINCT(product_id)) AS term_count, term_count_id FROM (
{$main_query_sql}
UNION ALL
{$query_sql_for_attributes_with_any_value}

View File

@ -481,13 +481,13 @@ class WC_Tests_Widget_Layered_Nav extends WC_Unit_Test_Case {
* Create a product that has two variations for two styles, and each variation
* has a value of "Any" for the color.
*
* @param bool $set_one_as_out_of_stock If true, set one of the variations as "Out of stock".
* @param bool $set_one_as_unpublished If true, set one of the variations as "draft".
* @param bool $set_main_as_unpublished If true, set the main product as "draft".
* @param string $set_as_out_of_stock 'one': set one of the variations as "Out of stock", 'all': set all the variations as "Out of stock".
* @param string $set_as_unpublished 'one': set one of the variations as "draft", 'all': set all the variations as "draft".
* @param bool $set_main_as_unpublished If true, set the main product as "draft".
*
* @return WC_Product_Variable
*/
private function create_product_with_all_styles_and_any_color( $set_one_as_out_of_stock, $set_one_as_unpublished, $set_main_as_unpublished ) {
private function create_product_with_all_styles_and_any_color( $set_as_out_of_stock, $set_as_unpublished, $set_main_as_unpublished ) {
$main_product = new WC_Product_Variable();
$main_product->set_props(
@ -528,13 +528,13 @@ class WC_Tests_Widget_Layered_Nav extends WC_Unit_Test_Case {
10,
$variation_attributes
);
if ( $set_one_as_out_of_stock && $style === $existing_styles[0] ) {
if ( 'all' === $set_as_out_of_stock || ( 'one' === $set_as_out_of_stock && $style === $existing_styles[0] ) ) {
$variation_object->set_stock_status( 'outofstock' );
}
$variation_object->save();
if ( $set_one_as_unpublished && $style === $existing_styles[0] ) {
if ( 'all' === $set_as_unpublished || ( 'one' === $set_as_unpublished && $style === $existing_styles[0] ) ) {
wp_update_post(
array(
'ID' => $variation_object->get_id(),
@ -554,54 +554,17 @@ class WC_Tests_Widget_Layered_Nav extends WC_Unit_Test_Case {
/**
* @testdox When a variation has a value of "Any" for an attribute it should be included in the count of all the attribute values used by the main product.
*
* @throws ReflectionException Error when dealing with reflection to invoke the tested method.
*/
public function test_product_count_per_attribute_with_any_valued_variations() {
$this->create_product_with_all_styles_and_any_color( false, false, false );
$this->create_colored_product( 'Medium shoes', array( 'black', 'brown', 'blue' ) );
$actual = $this->run_get_filtered_term_product_counts( 'or', array() );
$expected = array(
'black' => 3,
'brown' => 3,
'blue' => 3,
'pink' => 2,
'green' => 2,
'yellow' => 2,
);
$this->assertEquals( $expected, $actual );
}
/**
* @testdox When a variation has a value of "Any" for an attribute BUT it's out of stock, it should NOT be included in the count of all the attribute values used by the main product.
* @testWith [null, null]
* [null, "one"]
* ["one", null]
*
* @param string $set_as_out_of_stock "one" to set one of the variations as out of stock.
* @param string $set_as_unpublished "one" to set one of the variations as a draft.
*
* @throws ReflectionException Error when dealing with reflection to invoke the tested method.
*/
public function test_product_count_per_attribute_with_any_valued_variations_when_one_is_out_of_stock() {
$this->create_product_with_all_styles_and_any_color( true, false, false );
$this->create_colored_product( 'Medium shoes', array( 'black', 'brown', 'blue' ) );
$actual = $this->run_get_filtered_term_product_counts( 'or', array() );
$expected = array(
'black' => 2,
'brown' => 2,
'blue' => 2,
'pink' => 1,
'green' => 1,
'yellow' => 1,
);
$this->assertEquals( $expected, $actual );
}
/**
* @testdox When a variation has a value of "Any" for an attribute BUT it's unpublished, it should NOT be included in the count of all the attribute values used by the main product.
*
* @throws ReflectionException Error when dealing with reflection to invoke the tested method.
*/
public function test_product_count_per_attribute_with_any_valued_variations_when_one_is_unpublished() {
$this->create_product_with_all_styles_and_any_color( false, true, false );
public function test_product_count_per_attribute_with_any_valued_variations( $set_as_out_of_stock, $set_as_unpublished ) {
$this->create_product_with_all_styles_and_any_color( $set_as_out_of_stock, $set_as_unpublished, false );
$this->create_colored_product( 'Medium shoes', array( 'black', 'brown', 'blue' ) );
$actual = $this->run_get_filtered_term_product_counts( 'or', array() );
@ -624,7 +587,7 @@ class WC_Tests_Widget_Layered_Nav extends WC_Unit_Test_Case {
*/
public function test_product_count_per_attribute_with_any_valued_variations_when_main_is_unpublished() {
$this->create_colored_product( 'Medium shoes', array( 'black', 'brown', 'blue' ) );
$this->create_product_with_all_styles_and_any_color( false, false, true );
$this->create_product_with_all_styles_and_any_color( null, null, true );
$actual = $this->run_get_filtered_term_product_counts( 'or', array() );