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:
parent
602f58a7a9
commit
3a583feab1
|
@ -366,9 +366,9 @@ class WC_Widget_Layered_Nav extends WC_Widget {
|
||||||
$term_ids_sql = '(' . implode( ',', array_map( 'absint', $term_ids ) ) . ')';
|
$term_ids_sql = '(' . implode( ',', array_map( 'absint', $term_ids ) ) . ')';
|
||||||
|
|
||||||
// Generate the first part of the query.
|
// 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 = 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['from'] = "FROM {$wpdb->posts}";
|
||||||
$query['join'] = "
|
$query['join'] = "
|
||||||
INNER JOIN {$wpdb->term_relationships} AS tr ON {$wpdb->posts}.ID = tr.object_id
|
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['where'] .= ' AND ' . $search;
|
||||||
}
|
}
|
||||||
|
|
||||||
$query['group_by'] = 'GROUP BY terms.term_id';
|
|
||||||
$query = apply_filters( 'woocommerce_get_filtered_term_product_counts_query', $query );
|
$query = apply_filters( 'woocommerce_get_filtered_term_product_counts_query', $query );
|
||||||
$main_query_sql = implode( ' ', $query );
|
$main_query_sql = implode( ' ', $query );
|
||||||
|
|
||||||
// Generate the second part of the 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 = "
|
$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->posts} ON {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id
|
||||||
JOIN {$wpdb->term_relationships} ON {$wpdb->term_relationships}.object_id = {$wpdb->posts}.post_parent
|
JOIN {$wpdb->term_relationships} ON {$wpdb->term_relationships}.object_id = {$wpdb->posts}.post_parent
|
||||||
WHERE {$wpdb->postmeta}.meta_key = 'attribute_$taxonomy'
|
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
|
SELECT ID FROM {$wpdb->posts} AS parent
|
||||||
WHERE parent.ID = {$wpdb->posts}.post_parent AND parent.post_status NOT IN ('publish')
|
WHERE parent.ID = {$wpdb->posts}.post_parent AND parent.post_status NOT IN ('publish')
|
||||||
)
|
)
|
||||||
{$main_tax_query_sql['where']}
|
{$main_tax_query_sql['where']}";
|
||||||
GROUP BY {$wpdb->term_relationships}.term_taxonomy_id";
|
|
||||||
|
|
||||||
// Generate the final query as the union+sum of both.
|
// Generate the final query as the union+count of both.
|
||||||
$query_sql = "
|
$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}
|
{$main_query_sql}
|
||||||
UNION ALL
|
UNION ALL
|
||||||
{$query_sql_for_attributes_with_any_value}
|
{$query_sql_for_attributes_with_any_value}
|
||||||
|
|
|
@ -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
|
* Create a product that has two variations for two styles, and each variation
|
||||||
* has a value of "Any" for the color.
|
* 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 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 bool $set_one_as_unpublished If true, set one of the variations as "draft".
|
* @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".
|
* @param bool $set_main_as_unpublished If true, set the main product as "draft".
|
||||||
*
|
*
|
||||||
* @return WC_Product_Variable
|
* @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 = new WC_Product_Variable();
|
||||||
|
|
||||||
$main_product->set_props(
|
$main_product->set_props(
|
||||||
|
@ -528,13 +528,13 @@ class WC_Tests_Widget_Layered_Nav extends WC_Unit_Test_Case {
|
||||||
10,
|
10,
|
||||||
$variation_attributes
|
$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->set_stock_status( 'outofstock' );
|
||||||
}
|
}
|
||||||
|
|
||||||
$variation_object->save();
|
$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(
|
wp_update_post(
|
||||||
array(
|
array(
|
||||||
'ID' => $variation_object->get_id(),
|
'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.
|
* @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.
|
* @testWith [null, null]
|
||||||
*/
|
* [null, "one"]
|
||||||
public function test_product_count_per_attribute_with_any_valued_variations() {
|
* ["one", null]
|
||||||
$this->create_product_with_all_styles_and_any_color( false, false, false );
|
*
|
||||||
$this->create_colored_product( 'Medium shoes', array( 'black', 'brown', 'blue' ) );
|
* @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.
|
||||||
$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.
|
|
||||||
*
|
*
|
||||||
* @throws ReflectionException Error when dealing with reflection to invoke the tested method.
|
* @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() {
|
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( true, false, false );
|
$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() );
|
|
||||||
|
|
||||||
$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 );
|
|
||||||
$this->create_colored_product( 'Medium shoes', array( 'black', 'brown', 'blue' ) );
|
$this->create_colored_product( 'Medium shoes', array( 'black', 'brown', 'blue' ) );
|
||||||
|
|
||||||
$actual = $this->run_get_filtered_term_product_counts( 'or', array() );
|
$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() {
|
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_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() );
|
$actual = $this->run_get_filtered_term_product_counts( 'or', array() );
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue