Merge pull request #27625 from woocommerce/revert-improved-filtering-for-variations
Revert improved filtering for variations
This commit is contained in:
commit
2bcdbacc98
|
@ -593,94 +593,6 @@ class WC_Product_Variable extends WC_Product {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the product is visible in the catalog (doesn't trigger filters).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_visible_core() {
|
||||
if ( ! $this->parent_is_visible_core() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$query_filters = $this->get_layered_nav_chosen_attributes();
|
||||
if ( empty( $query_filters ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there are attribute filters in the request, a variable product will be visible
|
||||
* if at least one of the available variations matches the filters.
|
||||
*/
|
||||
|
||||
$attributes_with_terms = array();
|
||||
array_walk(
|
||||
$query_filters,
|
||||
function( $value, $key ) use ( &$attributes_with_terms ) {
|
||||
$attributes_with_terms[ $key ] = $value['terms'];
|
||||
}
|
||||
);
|
||||
|
||||
$variations = $this->get_available_variations( 'objects' );
|
||||
foreach ( $variations as $variation ) {
|
||||
if ( $this->variation_matches_filters( $variation, $attributes_with_terms ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given variation matches the active attribute filters.
|
||||
*
|
||||
* @param WC_Product_Variation $variation The variation to check.
|
||||
* @param array $query_filters The active filters as an array of attribute_name => [term1, term2...].
|
||||
*
|
||||
* @return bool True if the variation matches the active attribute filters.
|
||||
*/
|
||||
private function variation_matches_filters( WC_Product_Variation $variation, array $query_filters ) {
|
||||
// Get the variation attributes as an array of attribute_name => attribute_value.
|
||||
// The array_filter will filter out attributes having a value of '', these correspond
|
||||
// to "Any..." variations that don't participate in filtering.
|
||||
$variation_attributes = array_filter( $variation->get_variation_attributes( false ) );
|
||||
|
||||
$variation_attribute_names_in_filters = array_intersect( array_keys( $query_filters ), array_keys( $variation_attributes ) );
|
||||
if ( empty( $variation_attribute_names_in_filters ) ) {
|
||||
// The variation doesn't have any attribute that participates in filtering so we consider it a match.
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ( $variation_attribute_names_in_filters as $attribute_name ) {
|
||||
if ( ! in_array( $variation_attributes[ $attribute_name ], $query_filters[ $attribute_name ], true ) ) {
|
||||
// Multiple filters interact with AND logic, so as soon as one of them
|
||||
// doesn't match then the variation doesn't match.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* What does is_visible_core in the parent class say?
|
||||
* This method exists to ease unit testing.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function parent_is_visible_core() {
|
||||
return parent::is_visible_core();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of attributes and terms selected with the layered nav widget.
|
||||
* This method exists to ease unit testing.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_layered_nav_chosen_attributes() {
|
||||
return WC()->query::get_layered_nav_chosen_attributes();
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
@ -362,23 +362,10 @@ class WC_Query {
|
|||
if ( 'product_query' !== $query->get( 'wc_query' ) ) {
|
||||
return $posts;
|
||||
}
|
||||
$this->adjust_total_pages();
|
||||
$this->remove_product_query_filters( $posts );
|
||||
return $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* The 'adjust_posts_count' method that handles the 'found_posts' filter indirectly initializes
|
||||
* the loop properties with a call to 'wc_setup_loop'. This includes setting 'total_pages' to
|
||||
* '$GLOBALS['wp_query']->max_num_pages', which at that point has a value of zero.
|
||||
* Thus we need to set the real value from the 'the_posts' filter, where $GLOBALS['wp_query']->max_num_pages'
|
||||
* will aready have been initialized.
|
||||
*/
|
||||
private function adjust_total_pages() {
|
||||
if ( 0 === wc_get_loop_prop( 'total_pages' ) ) {
|
||||
wc_set_loop_prop( 'total_pages', $GLOBALS['wp_query']->max_num_pages );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre_get_posts above may adjust the main query to add WooCommerce logic. When this query is done, we need to ensure
|
||||
|
@ -396,12 +383,9 @@ class WC_Query {
|
|||
}
|
||||
|
||||
/**
|
||||
* When we are listing products and the request is filtering by attributes via layered nav plugin
|
||||
* we need to adjust the total posts count to account for variable products having stock
|
||||
* in some variations but not in others.
|
||||
* We do that by just checking if each product is visible.
|
||||
*
|
||||
* We also cache the post visibility so that it isn't checked again when displaying the posts list.
|
||||
* This function used to be hooked to found_posts and adjust the posts count when the filtering by attribute
|
||||
* widget was used and variable products were present. Now it isn't hooked anymore and does nothing but return
|
||||
* the input unchanged, since the pull request in which it was introduced has been reverted.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @param int $count Original posts count, as supplied by the found_posts filter.
|
||||
|
@ -410,35 +394,6 @@ class WC_Query {
|
|||
* @return int Adjusted posts count.
|
||||
*/
|
||||
public function adjust_posts_count( $count, $query ) {
|
||||
if ( ! $query->get( 'wc_query' ) ) {
|
||||
return $count;
|
||||
}
|
||||
|
||||
$posts = $this->get_current_posts();
|
||||
if ( is_null( $posts ) ) {
|
||||
return $count;
|
||||
}
|
||||
|
||||
foreach ( $posts as $post ) {
|
||||
if ( is_object( $post ) && 'product' !== $post->post_type ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$product_id = is_object( $post ) ? $post->ID : $post;
|
||||
$product = wc_get_product( $product_id );
|
||||
if ( ! is_object( $product ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $product->is_visible() ) {
|
||||
wc_set_loop_product_visibility( $product_id, true );
|
||||
} else {
|
||||
wc_set_loop_product_visibility( $product_id, false );
|
||||
$count--;
|
||||
}
|
||||
}
|
||||
|
||||
wc_set_loop_prop( 'total', $count );
|
||||
return $count;
|
||||
}
|
||||
|
||||
|
@ -514,7 +469,7 @@ class WC_Query {
|
|||
// Additonal hooks to change WP Query.
|
||||
add_filter( 'posts_clauses', array( $this, 'price_filter_post_clauses' ), 10, 2 );
|
||||
add_filter( 'the_posts', array( $this, 'handle_get_posts' ), 10, 2 );
|
||||
add_filter( 'found_posts', array( $this, 'adjust_posts_count' ), 10, 2 );
|
||||
|
||||
do_action( 'woocommerce_product_query', $q, $this );
|
||||
}
|
||||
|
||||
|
|
|
@ -344,93 +344,50 @@ class WC_Widget_Layered_Nav extends WC_Widget {
|
|||
protected function get_filtered_term_product_counts( $term_ids, $taxonomy, $query_type ) {
|
||||
global $wpdb;
|
||||
|
||||
$main_tax_query = $this->get_main_tax_query();
|
||||
$meta_query = $this->get_main_meta_query();
|
||||
$tax_query = $this->get_main_tax_query();
|
||||
$meta_query = $this->get_main_meta_query();
|
||||
|
||||
$non_variable_tax_query_sql = array( 'where' => '' );
|
||||
$is_and_query = 'and' === $query_type;
|
||||
|
||||
foreach ( $main_tax_query as $key => $query ) {
|
||||
if ( is_array( $query ) && $taxonomy === $query['taxonomy'] ) {
|
||||
if ( $is_and_query ) {
|
||||
$non_variable_tax_query_sql = $this->convert_tax_query_to_sql( array( $query ) );
|
||||
if ( 'or' === $query_type ) {
|
||||
foreach ( $tax_query as $key => $query ) {
|
||||
if ( is_array( $query ) && $taxonomy === $query['taxonomy'] ) {
|
||||
unset( $tax_query[ $key ] );
|
||||
}
|
||||
unset( $main_tax_query[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
$exclude_variable_products_tax_query_sql = $this->get_extra_tax_query_sql( 'product_type', array( 'variable' ), 'NOT IN' );
|
||||
$meta_query = new WP_Meta_Query( $meta_query );
|
||||
$tax_query = new WP_Tax_Query( $tax_query );
|
||||
$meta_query_sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' );
|
||||
$tax_query_sql = $tax_query->get_sql( $wpdb->posts, 'ID' );
|
||||
$term_ids_sql = '(' . implode( ',', array_map( 'absint', $term_ids ) ) . ')';
|
||||
|
||||
$meta_query_sql = ( new WP_Meta_Query( $meta_query ) )->get_sql( 'post', $wpdb->posts, 'ID' );
|
||||
$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='product_variation', {$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
|
||||
INNER JOIN {$wpdb->term_relationships} AS term_relationships ON {$wpdb->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 )
|
||||
{$main_tax_query_sql['join']} {$meta_query_sql['join']}"; // Not an omission, really no more JOINs required.
|
||||
|
||||
$variable_where_part = "
|
||||
OR ({$wpdb->posts}.post_type = 'product_variation'
|
||||
AND NOT EXISTS (
|
||||
SELECT ID FROM {$wpdb->posts} AS parent
|
||||
WHERE parent.ID = {$wpdb->posts}.post_parent AND parent.post_status NOT IN ('publish')
|
||||
))
|
||||
";
|
||||
|
||||
$search_sql = '';
|
||||
$search = $this->get_main_search_query_sql();
|
||||
if ( $search ) {
|
||||
$search_sql = ' AND ' . $search;
|
||||
}
|
||||
" . $tax_query_sql['join'] . $meta_query_sql['join'];
|
||||
|
||||
$query['where'] = "
|
||||
WHERE
|
||||
{$wpdb->posts}.post_status = 'publish'
|
||||
{$main_tax_query_sql['where']} {$meta_query_sql['where']}
|
||||
AND (
|
||||
(
|
||||
{$wpdb->posts}.post_type = 'product'
|
||||
{$exclude_variable_products_tax_query_sql['where']}
|
||||
{$non_variable_tax_query_sql['where']}
|
||||
)
|
||||
{$variable_where_part}
|
||||
)
|
||||
AND terms.term_id IN {$term_ids_sql}
|
||||
{$search_sql}";
|
||||
WHERE {$wpdb->posts}.post_type IN ( 'product' )
|
||||
AND {$wpdb->posts}.post_status = 'publish'
|
||||
{$tax_query_sql['where']} {$meta_query_sql['where']}
|
||||
AND terms.term_id IN $term_ids_sql";
|
||||
|
||||
$search = $this->get_main_search_query_sql();
|
||||
if ( $search ) {
|
||||
$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}.ID AS product_id, {$wpdb->term_relationships}.term_taxonomy_id as term_count_id FROM {$wpdb->posts}
|
||||
JOIN {$wpdb->posts} variations ON variations.post_parent = {$wpdb->posts}.ID
|
||||
LEFT JOIN {$wpdb->postmeta} ON variations.ID = {$wpdb->postmeta}.post_id AND {$wpdb->postmeta}.meta_key = 'attribute_$taxonomy'
|
||||
JOIN {$wpdb->term_relationships} ON {$wpdb->term_relationships}.object_id = {$wpdb->posts}.ID
|
||||
WHERE ( {$wpdb->postmeta}.meta_key IS NULL OR {$wpdb->postmeta}.meta_value = '')
|
||||
AND {$wpdb->posts}.post_type = 'product'
|
||||
AND {$wpdb->posts}.post_status = 'publish'
|
||||
AND variations.post_status = 'publish'
|
||||
AND variations.post_type = 'product_variation'
|
||||
AND {$wpdb->term_relationships}.term_taxonomy_id in $term_ids_sql
|
||||
{$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 );
|
||||
|
@ -441,7 +398,9 @@ 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:ignore 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 );
|
||||
|
@ -451,30 +410,6 @@ class WC_Widget_Layered_Nav extends WC_Widget {
|
|||
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 $main_query_sql The SQL query to use in order to count products with concrete values for attributes, must return a "product_id" column and a "terms_count_id" column.
|
||||
* @param string $query_sql_for_attributes_with_any_value The SQL query to use in order to count products with "Any" values for attributes, 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( $main_query_sql, $query_sql_for_attributes_with_any_value ) {
|
||||
global $wpdb;
|
||||
|
||||
$total_counts = null;
|
||||
|
||||
$query = "
|
||||
SELECT COUNT(DISTINCT(product_id)) AS term_count, term_count_id FROM (
|
||||
{$main_query_sql}
|
||||
UNION ALL
|
||||
{$query_sql_for_attributes_with_any_value}
|
||||
) AS x GROUP BY term_count_id";
|
||||
|
||||
$results = $wpdb->get_results( $query, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
return array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for WC_Query::get_main_tax_query() to ease unit testing.
|
||||
*
|
||||
|
@ -505,45 +440,6 @@ class WC_Widget_Layered_Nav extends WC_Widget {
|
|||
return WC_Query::get_main_meta_query();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a tax query SQL for a given set of taxonomy, terms and operator.
|
||||
* Uses an intermediate WP_Tax_Query object.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @param string $taxonomy Taxonomy name.
|
||||
* @param array $terms Terms to include in the query.
|
||||
* @param string $operator Query operator, as supported by WP_Tax_Query; e.g. "NOT IN".
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_extra_tax_query_sql( $taxonomy, $terms, $operator ) {
|
||||
$query = array(
|
||||
array(
|
||||
'taxonomy' => $taxonomy,
|
||||
'field' => 'slug',
|
||||
'terms' => $terms,
|
||||
'operator' => $operator,
|
||||
'include_children' => false,
|
||||
),
|
||||
);
|
||||
|
||||
return $this->convert_tax_query_to_sql( $query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a tax query array to SQL using an intermediate WP_Tax_Query object.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @param array $query Query array in the same format accepted by WP_Tax_Query constructor.
|
||||
*
|
||||
* @return array Query SQL as returned by WP_Tax_Query->get_sql.
|
||||
*/
|
||||
private function convert_tax_query_to_sql( $query ) {
|
||||
global $wpdb;
|
||||
|
||||
return ( new WP_Tax_Query( $query ) )->get_sql( $wpdb->posts, 'ID' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Show list based layered nav.
|
||||
*
|
||||
|
|
|
@ -20,7 +20,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
global $product;
|
||||
|
||||
// Ensure visibility.
|
||||
if ( empty( $product ) || false === wc_get_loop_product_visibility( $product->get_id() ) || ! $product->is_visible() ) {
|
||||
if ( empty( $product ) || ! $product->is_visible() ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
|
|
|
@ -176,199 +176,4 @@ class WC_Tests_Product_Variable extends WC_Unit_Test_Case {
|
|||
|
||||
$this->assertEquals( $expected_stock_status, $product->get_stock_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup for a test for is_visible.
|
||||
*
|
||||
* @param array $filtering_attributes Simulated filtering attributes as an array of attribute_name => [term1, term2...].
|
||||
* @param bool $hide_out_of_stock_products Should the woocommerce_hide_out_of_stock_items option be set?.
|
||||
* @param bool $is_visible_from_parent Return value of is_visible from base class.
|
||||
*
|
||||
* @return WC_Product_Variable A properly configured instance of WC_Product_Variable to test.
|
||||
*/
|
||||
private function prepare_visibility_test( $filtering_attributes, $hide_out_of_stock_products = true, $is_visible_from_parent = true ) {
|
||||
foreach ( $filtering_attributes as $attribute_name => $terms ) {
|
||||
$filtering_attributes[ $attribute_name ]['query_type'] = 'ANY_QUERY_TYPE';
|
||||
$filtering_attributes[ $attribute_name ]['terms'] = $terms;
|
||||
}
|
||||
|
||||
update_option( 'woocommerce_hide_out_of_stock_items', $hide_out_of_stock_products ? 'yes' : 'no' );
|
||||
|
||||
$sut = $this
|
||||
->getMockBuilder( WC_Product_Variable::class )
|
||||
->setMethods( array( 'parent_is_visible_core', 'get_layered_nav_chosen_attributes' ) )
|
||||
->getMock();
|
||||
|
||||
$sut = WC_Helper_Product::create_variation_product( $sut, true );
|
||||
$sut->save();
|
||||
|
||||
$sut->method( 'parent_is_visible_core' )->willReturn( $is_visible_from_parent );
|
||||
$sut->method( 'get_layered_nav_chosen_attributes' )->willReturn( $filtering_attributes );
|
||||
|
||||
return $sut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the stock status for the attribute-based variations of a product.
|
||||
*
|
||||
* @param WC_Product_Variable $product Product with the variations to configure.
|
||||
* @param array $attributes An array of attribute_name => [attribute_values], only the matching variations will have stock.
|
||||
*/
|
||||
private function set_variations_with_stock( $product, $attributes ) {
|
||||
$variation_ids = $product->get_children();
|
||||
foreach ( $variation_ids as $id ) {
|
||||
$variation = wc_get_product( $id );
|
||||
$attribute_matches = true;
|
||||
foreach ( $attributes as $name => $values ) {
|
||||
if ( ! in_array( $variation->get_attribute( $name ), $values, true ) ) {
|
||||
$attribute_matches = false;
|
||||
}
|
||||
}
|
||||
$variation->set_stock_status( $attribute_matches ? 'instock' : 'outofstock' );
|
||||
$variation->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox The product should be invisible when the parent 'is_visible' method returns false.
|
||||
*/
|
||||
public function test_is_invisible_when_parent_is_visible_returns_false() {
|
||||
$sut = $this->prepare_visibility_test( array(), '', false, false );
|
||||
|
||||
$this->assertFalse( $sut->is_visible() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox The product should be visible when no nav filtering is supplied if at least one variation has stock.
|
||||
*
|
||||
* Note that if no variations have stock the base is_visible will already return false.
|
||||
*/
|
||||
public function test_is_visible_when_no_filtering_supplied_and_at_least_one_variation_has_stock() {
|
||||
$sut = $this->prepare_visibility_test( array(), '' );
|
||||
|
||||
$this->set_variations_with_stock( $sut, array( 'pa_size' => array( 'small' ) ) );
|
||||
|
||||
$this->assertTrue( $sut->is_visible() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Test product visibility when the variation requested in nav filtering has no stock, result depends on woocommerce_hide_out_of_stock_items option.
|
||||
*
|
||||
* @param bool $hide_out_of_stock Value for woocommerce_hide_out_of_stock_items.
|
||||
* @param bool $expected_visibility Expected value of is_visible for the tested product.
|
||||
*
|
||||
* @testWith [true, false]
|
||||
* [false, true]
|
||||
*/
|
||||
public function test_visibility_when_supplied_filter_has_no_stock( $hide_out_of_stock, $expected_visibility ) {
|
||||
$sut = $this->prepare_visibility_test( array( 'pa_size' => array( 'large' ) ), $hide_out_of_stock );
|
||||
|
||||
$this->set_variations_with_stock( $sut, array( 'pa_size' => array( 'small' ) ) );
|
||||
|
||||
$this->assertEquals( $expected_visibility, $sut->is_visible() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Product should always be visible when only one of the variations requested in nav filtering has stock.
|
||||
*
|
||||
* @param bool $hide_out_of_stock Value for woocommerce_hide_out_of_stock_items.
|
||||
*
|
||||
* @testWith [true]
|
||||
* [false]
|
||||
*/
|
||||
public function test_visibility_when_multiple_filter_values_supplied_and_only_one_has_stock( $hide_out_of_stock ) {
|
||||
$sut = $this->prepare_visibility_test( array( 'pa_size' => array( 'small', 'large' ) ), $hide_out_of_stock );
|
||||
|
||||
$this->set_variations_with_stock( $sut, array( 'pa_size' => array( 'small' ) ) );
|
||||
|
||||
$this->assertTrue( $sut->is_visible() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Product should be visible when all of the variations requested in nav filtering have stock.
|
||||
*
|
||||
* @param bool $hide_out_of_stock Value for woocommerce_hide_out_of_stock_items.
|
||||
*
|
||||
* @testWith [true]
|
||||
* [false]
|
||||
*/
|
||||
public function test_visibility_when_multiple_filter_values_supplied_and_all_of_them_have_stock( $hide_out_of_stock ) {
|
||||
$sut = $this->prepare_visibility_test( array( 'pa_size' => array( 'small', 'large' ) ), $hide_out_of_stock );
|
||||
|
||||
$this->set_variations_with_stock( $sut, array( 'pa_size' => array( 'small', 'large' ) ) );
|
||||
|
||||
$this->assertTrue( $sut->is_visible() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Product should be visible when multiple filters are present, and there's a variation matching all of them.
|
||||
*/
|
||||
public function test_visibility_when_multiple_filters_are_used_and_all_of_them_match() {
|
||||
$sut = $this->prepare_visibility_test(
|
||||
array(
|
||||
'pa_size' => array( 'huge' ),
|
||||
'pa_colour' => array( 'blue' ),
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
$this->set_variations_with_stock(
|
||||
$sut,
|
||||
array(
|
||||
'pa_size' => array( 'huge' ),
|
||||
'pa_colour' => array( 'blue' ),
|
||||
'pa_number' => array( '2' ),
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertTrue( $sut->is_visible() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Product should not be visible when multiple filters are present, and there are no variations matching all of them.
|
||||
*/
|
||||
public function test_visibility_when_multiple_filters_are_used_and_one_of_them_does_not_match() {
|
||||
$sut = $this->prepare_visibility_test(
|
||||
array(
|
||||
'pa_size' => array( 'small', 'huge' ),
|
||||
'pa_colour' => array( 'red' ),
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
$this->set_variations_with_stock(
|
||||
$sut,
|
||||
array(
|
||||
'pa_size' => array( 'huge' ),
|
||||
'pa_colour' => array( 'blue' ),
|
||||
'pa_number' => array( '2' ),
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertFalse( $sut->is_visible() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Attributes having "Any..." as value should not count when searching for matching attributes.
|
||||
*/
|
||||
public function test_visibility_when_multiple_filters_are_used_and_an_attribute_has_any_value() {
|
||||
$sut = $this->prepare_visibility_test(
|
||||
array(
|
||||
'pa_size' => array( 'huge' ),
|
||||
'pa_number' => array( '34' ),
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
$this->set_variations_with_stock(
|
||||
$sut,
|
||||
array(
|
||||
'pa_size' => array( 'huge' ),
|
||||
'pa_colour' => array( 'blue' ),
|
||||
'pa_number' => array( '' ),
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertTrue( $sut->is_visible() );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -437,124 +437,4 @@ class WC_Tests_WC_Query extends WC_Unit_Test_Case {
|
|||
|
||||
WC()->query->remove_ordering_args();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup for a test for adjust_posts.
|
||||
*
|
||||
* @param bool $with_nav_filtering_data Should WC_Query::get_layered_nav_chosen_attributes return filtering data?.
|
||||
* @param bool $use_objects If true, get_current_posts will return objects with an ID property; if false, it will returns the ids.
|
||||
* @param string $post_type The value of the 'post_type' property for the objects generated when $use_objects is true.
|
||||
*
|
||||
* @return array An array where the first element is the instance of WC_Query, and the second is an array of sample products created.
|
||||
*/
|
||||
private function setup_adjust_posts_test( $with_nav_filtering_data, $use_objects, $post_type = 'product' ) {
|
||||
update_option( 'woocommerce_hide_out_of_stock_items', 'yes' );
|
||||
|
||||
if ( $with_nav_filtering_data ) {
|
||||
$nav_filtering_data = array( 'pa_something' => array( 'terms' => array( 'foo', 'bar' ) ) );
|
||||
} else {
|
||||
$nav_filtering_data = array();
|
||||
}
|
||||
|
||||
$products = array();
|
||||
$posts = array();
|
||||
for ( $i = 0; $i < 5; $i++ ) {
|
||||
$product = WC_Helper_Product::create_simple_product();
|
||||
array_push( $products, $product );
|
||||
$post = $use_objects ? (object) array(
|
||||
'ID' => $product->get_id(),
|
||||
'post_type' => $post_type,
|
||||
) : $product->get_id();
|
||||
array_push( $posts, $post );
|
||||
}
|
||||
|
||||
$products[0]->set_stock_status( 'outofstock' );
|
||||
|
||||
$sut = $this
|
||||
->getMockBuilder( WC_Query::class )
|
||||
->setMethods( array( 'get_current_posts', 'get_layered_nav_chosen_attributes_inst' ) )
|
||||
->getMock();
|
||||
|
||||
$sut->method( 'get_current_posts' )->willReturn( $posts );
|
||||
$sut->method( 'get_layered_nav_chosen_attributes_inst' )->willReturn( $nav_filtering_data );
|
||||
|
||||
return array( $sut, $products );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $with_nav_filtering_data Should WC_Query::get_layered_nav_chosen_attributes return filtering data?.
|
||||
* @param bool $use_objects If true, get_current_posts will return objects with an ID property; if false, it will returns the ids.
|
||||
*
|
||||
* @testdox adjust_posts should return the number of visible products and create product visibility loop variables
|
||||
* @testWith [true, true]
|
||||
* [false, false]
|
||||
* [true, false]
|
||||
* [false, true]
|
||||
*/
|
||||
public function test_adjust_posts_count_with_nav_filtering_attributes( $with_nav_filtering_data, $use_objects ) {
|
||||
global $wp_query;
|
||||
|
||||
list($sut, $products) = $this->setup_adjust_posts_test( $with_nav_filtering_data, $use_objects );
|
||||
|
||||
$products[0]->set_stock_status( 'outofstock' );
|
||||
$products[0]->save();
|
||||
$products[1]->set_stock_status( 'outofstock' );
|
||||
$products[1]->save();
|
||||
|
||||
$wp_query->set( 'wc_query', 'product_query' );
|
||||
$this->assertEquals( 32, $sut->adjust_posts_count( 34, $wp_query ) );
|
||||
$this->assertEquals( 32, wc_get_loop_prop( 'total' ) );
|
||||
$this->assertEquals( false, wc_get_loop_product_visibility( $products[0]->get_id() ) );
|
||||
$this->assertEquals( false, wc_get_loop_product_visibility( $products[1]->get_id() ) );
|
||||
foreach ( array_slice( $products, 2 ) as $product ) {
|
||||
$this->assertEquals( true, wc_get_loop_product_visibility( $product->get_id() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox adjust_posts should return the input unmodified if get_current_posts returns null.
|
||||
*/
|
||||
public function test_adjust_posts_count_when_there_are_no_posts() {
|
||||
global $wp_query;
|
||||
|
||||
$sut = $this
|
||||
->getMockBuilder( WC_Query::class )
|
||||
->setMethods( array( 'get_current_posts', 'get_layered_nav_chosen_attributes_inst' ) )
|
||||
->getMock();
|
||||
|
||||
$sut->method( 'get_current_posts' )->willReturn( null );
|
||||
|
||||
$wp_query->set( 'wc_query', 'product_query' );
|
||||
$this->assertEquals( 34, $sut->adjust_posts_count( 34, $wp_query ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox adjust_posts should return the input unmodified if the posts do not represent products.
|
||||
*/
|
||||
public function test_adjust_posts_count_when_the_posts_are_not_products() {
|
||||
global $wp_query;
|
||||
|
||||
list( $sut, $products ) = $this->setup_adjust_posts_test( true, true, 'page' );
|
||||
|
||||
$products[0]->set_stock_status( 'outofstock' );
|
||||
$products[0]->save();
|
||||
|
||||
$wp_query->set( 'wc_query', 'product_query' );
|
||||
$this->assertEquals( 34, $sut->adjust_posts_count( 34, $wp_query ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox adjust_posts should return the input unmodified if not in the main product query.
|
||||
*/
|
||||
public function test_adjust_posts_count_when_not_in_the_main_product_query() {
|
||||
global $wp_query;
|
||||
|
||||
list( $sut, $products ) = $this->setup_adjust_posts_test( true, true );
|
||||
|
||||
$products[0]->set_stock_status( 'outofstock' );
|
||||
$products[0]->save();
|
||||
|
||||
$wp_query->set( 'wc_query', null );
|
||||
$this->assertEquals( 34, $sut->adjust_posts_count( 34, $wp_query ) );
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue