Revert "Fix filter by attribute widget now working for "Any..." attributes"

This commit is contained in:
Vedanshu Jain 2020-09-08 22:19:51 +05:30 committed by GitHub
parent 6c1d7b889e
commit 8b2b6e1443
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 12 additions and 188 deletions

View File

@ -1693,9 +1693,7 @@ function wc_nocache_headers() {
* @return int
*/
function wc_product_attribute_uasort_comparison( $a, $b ) {
$a_position = is_null( $a ) ? null : $a['position'];
$b_position = is_null( $b ) ? null : $b['position'];
return wc_uasort_comparison( $a_position, $b_position );
return wc_uasort_comparison( $a['position'], $b['position'] );
}
/**

View File

@ -365,10 +365,9 @@ class WC_Widget_Layered_Nav extends WC_Widget {
$main_tax_query_sql = $this->convert_tax_query_to_sql( $main_tax_query );
$term_ids_sql = '(' . implode( ',', array_map( 'absint', $term_ids ) ) . ')';
// Generate the first part of the query.
// This one will return non-variable products and variable products with concrete values for the attributes.
// Generate query.
$query = array();
$query['select'] = "SELECT IF({$wpdb->posts}.post_type='variable_product', {$wpdb->posts}.post_parent, {$wpdb->posts}.ID) AS product_id, terms.term_id AS term_count_id";
$query['select'] = "SELECT COUNT( DISTINCT {$wpdb->posts}.ID ) as term_count, 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,28 +409,12 @@ class WC_Widget_Layered_Nav extends WC_Widget {
$query['where'] .= ' AND ' . $search;
}
$query = apply_filters( 'woocommerce_get_filtered_term_product_counts_query', $query );
$main_query_sql = implode( ' ', $query );
$query['group_by'] = 'GROUP BY terms.term_id';
$query = apply_filters( 'woocommerce_get_filtered_term_product_counts_query', $query );
$query_sql = implode( ' ', $query );
// Generate the second part of the query.
// This one will return products having "Any..." as the value of the attribute.
$query_sql_for_attributes_with_any_value = "
SELECT {$wpdb->posts}.post_parent AS product_id, {$wpdb->term_relationships}.term_taxonomy_id as term_count_id FROM {$wpdb->posts}
LEFT JOIN {$wpdb->postmeta} ON {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id AND {$wpdb->postmeta}.meta_key = 'attribute_$taxonomy'
JOIN {$wpdb->term_relationships} ON {$wpdb->term_relationships}.object_id = {$wpdb->posts}.post_parent
WHERE ( {$wpdb->postmeta}.meta_key IS NULL OR {$wpdb->postmeta}.meta_value = '')
AND {$wpdb->posts}.post_type = 'product_variation'
AND {$wpdb->posts}.post_status = 'publish'
AND {$wpdb->term_relationships}.term_taxonomy_id in $term_ids_sql
AND NOT EXISTS (
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']}";
// We have two queries - let's see if cached results of this query already exist.
$query_hash = md5( $main_query_sql . $query_sql_for_attributes_with_any_value );
// We have a query - let's see if cached results of this query already exist.
$query_hash = md5( $query_sql );
// Maybe store a transient of the count values.
$cache = apply_filters( 'woocommerce_layered_nav_count_maybe_cache', true );
@ -442,53 +425,19 @@ class WC_Widget_Layered_Nav extends WC_Widget {
}
if ( ! isset( $cached_counts[ $query_hash ] ) ) {
$counts = $this->get_term_product_counts_from_queries( $main_query_sql, $query_sql_for_attributes_with_any_value );
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$results = $wpdb->get_results( $query_sql, ARRAY_A );
$counts = array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) );
$cached_counts[ $query_hash ] = $counts;
if ( true === $cache ) {
set_transient( 'wc_layered_nav_counts_' . sanitize_title( $taxonomy ), $cached_counts, DAY_IN_SECONDS );
}
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
}
return array_map( 'absint', (array) $cached_counts[ $query_hash ] );
}
/**
* Get the count of terms for products, using a set of SQL queries that are return pairs of product id - term id.
*
* @param string ...$queries SQL queries to use, each must return a "product_id" column and a "terms_count_id" column.
*
* @return array An array where the keys are term ids, and the values are term counts.
*/
private function get_term_product_counts_from_queries( ...$queries ) {
global $wpdb;
$total_counts = null;
foreach ( $queries as $query ) {
$query = "
SELECT COUNT(DISTINCT(product_id)) AS term_count, term_count_id FROM (
{$query}
) AS x GROUP BY term_count_id";
$results = $wpdb->get_results( $query, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$counts = array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) );
if ( is_null( $total_counts ) ) {
$total_counts = $counts;
} else {
foreach ( $counts as $term_id => $term_count ) {
if ( array_key_exists( $term_id, $total_counts ) ) {
$total_counts[ $term_id ] += $term_count;
} else {
$total_counts[ $term_id ] = $term_count;
}
}
}
}
return $total_counts;
}
/**
* Wrapper for WC_Query::get_main_tax_query() to ease unit testing.
*

View File

@ -476,127 +476,4 @@ class WC_Tests_Widget_Layered_Nav extends WC_Unit_Test_Case {
);
$this->assertEquals( $expected, $actual );
}
/**
* Create a product that has two variations for two styles, and each variation
* has a value of "Any" for the color.
*
* @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_as_out_of_stock, $set_as_unpublished, $set_main_as_unpublished ) {
$main_product = new WC_Product_Variable();
$main_product->set_props(
array(
'name' => 'Some shoes',
'sku' => 'SKU for Some shoes',
)
);
$existing_colors = array( 'black', 'brown', 'blue', 'green', 'pink', 'yellow' );
$existing_styles = array( 'classic', 'sport' );
$attributes = array(
WC_Helper_Product::create_product_attribute_object( 'color', array_values( $existing_colors ) ),
WC_Helper_Product::create_product_attribute_object( 'style', array_values( $existing_styles ) ),
);
$main_product->set_attributes( $attributes );
$main_product->save();
if ( $set_main_as_unpublished ) {
wp_update_post(
array(
'ID' => $main_product->get_id(),
'post_status' => 'draft',
)
);
}
$variation_objects = array();
foreach ( $existing_styles as $style ) {
$variation_attributes = array(
'pa_color' => '',
'pa_style' => $style,
);
$variation_object = WC_Helper_Product::create_product_variation_object(
$main_product->get_id(),
"SKU for $style Some shoes",
10,
$variation_attributes
);
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 ( 'all' === $set_as_unpublished || ( 'one' === $set_as_unpublished && $style === $existing_styles[0] ) ) {
wp_update_post(
array(
'ID' => $variation_object->get_id(),
'post_status' => 'draft',
)
);
}
array_push( $variation_objects, $variation_object->get_id() );
}
$main_product->set_children( $variation_objects );
return $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.
*
* @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( $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' ) );
$this->create_colored_product( 'Medium shoes 2', array( 'black', 'brown', 'blue', 'pink' ) );
$actual = $this->run_get_filtered_term_product_counts( 'or', array() );
$expected = array(
'black' => 3,
'brown' => 3,
'blue' => 3,
'pink' => 2,
'green' => 1,
'yellow' => 1,
);
$this->assertEquals( $expected, $actual );
}
/**
* @testdox When variations have a value of "Any" for an attribute BUT the main product is unpublished, none of them 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_when_main_is_unpublished() {
$this->create_colored_product( 'Medium shoes', array( 'black', 'brown', 'blue' ) );
$this->create_product_with_all_styles_and_any_color( null, null, true );
$actual = $this->run_get_filtered_term_product_counts( 'or', array() );
$expected = array(
'black' => 1,
'brown' => 1,
'blue' => 1,
);
$this->assertEquals( $expected, $actual );
}
}