Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Product Query: Add product visibility query support #7951

Merged
merged 7 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion src/BlockTypes/ProductQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,9 @@ public function update_rest_query( $args, $request ) {
$orderby_query = isset( $orderby ) ? $this->get_custom_orderby_query( $orderby ) : array();
$attributes_query = is_array( $woo_attributes ) ? $this->get_product_attributes_query( $woo_attributes ) : array();
$stock_query = is_array( $woo_stock_status ) ? $this->get_stock_status_query( $woo_stock_status ) : array();
$visibility_query = $this->get_product_visibility_query( $stock_query );

return array_merge( $args, $on_sale_query, $orderby_query, $attributes_query, $stock_query );
return array_merge( $args, $on_sale_query, $orderby_query, $attributes_query, $stock_query, $visibility_query );
}

/**
Expand Down Expand Up @@ -325,6 +326,34 @@ function ( $carry, $item ) {
* @return array
*/
private function get_stock_status_query( $stock_statii ) {
if ( ! is_array( $stock_statii ) ) {
return array();
}
Comment on lines +329 to +331
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big deal, but should we return even earlier? We don't even need a call to wc_get_product_stock_status_option if the argument is not an array.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely!


$stock_status_options = array_keys( wc_get_product_stock_status_options() );

/**
* If all available stock status are selected, we don't need to add the
* meta query for stock status.
*/
Comment on lines +335 to +338
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this comments! ❤️

if (
count( $stock_statii ) === count( $stock_status_options ) &&
array_diff( $stock_statii, $stock_status_options ) === array_diff( $stock_status_options, $stock_statii )
) {
return array();
}

/**
* If all stock statuses are selected except 'outofstock', we use the
* product visibility query to filter out out of stock products.
*
* @see get_product_visibility_query()
*/
$diff = array_diff( $stock_status_options, $stock_statii );
if ( count( $diff ) === 1 && in_array( 'outofstock', $diff, true ) ) {
return array();
}

return array(
'meta_query' => array(
array(
Expand All @@ -336,6 +365,34 @@ private function get_stock_status_query( $stock_statii ) {
);
}

/**
* Return a query for product visibility depending on their stock status.
*
* @param array $stock_query Stock status query.
*
* @return array Tax query for product visibility.
*/
private function get_product_visibility_query( $stock_query ) {
$product_visibility_terms = wc_get_product_visibility_term_ids();
$product_visibility_not_in = array( is_search() ? $product_visibility_terms['exclude-from-search'] : $product_visibility_terms['exclude-from-catalog'] );

// Hide out of stock products.
if ( empty( $stock_query ) && 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
$product_visibility_not_in[] = $product_visibility_terms['outofstock'];
}

return array(
'tax_query' => array(
array(
'taxonomy' => 'product_visibility',
'field' => 'term_taxonomy_id',
'terms' => $product_visibility_not_in,
'operator' => 'NOT IN',
),
),
);
}

/**
* Set the query vars that are used by filter blocks.
*
Expand Down Expand Up @@ -439,11 +496,13 @@ private function get_queries_by_attributes( $parsed_block ) {
$on_sale_enabled = isset( $query['__woocommerceOnSale'] ) && true === $query['__woocommerceOnSale'];
$attributes_query = isset( $query['__woocommerceAttributes'] ) ? $this->get_product_attributes_query( $query['__woocommerceAttributes'] ) : array();
$stock_query = isset( $query['__woocommerceStockStatus'] ) ? $this->get_stock_status_query( $query['__woocommerceStockStatus'] ) : array();
$visibility_query = $this->get_product_visibility_query( $stock_query );

return array(
'on_sale' => ( $on_sale_enabled ? $this->get_on_sale_products_query() : array() ),
'attributes' => $attributes_query,
'stock_status' => $stock_query,
'visibility' => $visibility_query,
);
}

Expand Down
112 changes: 100 additions & 12 deletions tests/php/BlockTypes/ProductQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ public function test_merging_on_sale_queries() {
foreach ( $on_sale_product_ids as $id ) {
$this->assertContainsEquals( $id, $merged_query['post__in'] );
}

$this->assertCount( 4, $merged_query['post__in'] );

delete_transient( 'wc_products_onsale' );
}

/**
Expand All @@ -112,24 +115,34 @@ public function test_merging_stock_status_queries() {
),
$merged_query['meta_query']
);
}

/**
* Test merging default stock queries that should use product visibility
* queries instead of meta query for stock status.
*/
public function test_merging_default_stock_queries() {
$parsed_block = $this->get_base_parsed_block();
$parsed_block['attrs']['query']['__woocommerceStockStatus'] = array(
'instock',
'outofstock',
'onbackorder',
);
$this->block_instance->set_parsed_block( $parsed_block );

$merged_query = $this->block_instance->build_query( $parsed_block['attrs']['query'] );
$merged_query = $this->initialize_merged_query( $parsed_block );

$this->assertContainsEquals(
array(
'compare' => 'IN',
'key' => '_stock_status',
'value' => array( 'instock', 'onbackorder' ),
),
$merged_query['meta_query']
$this->assertEmpty( $merged_query['meta_query'] );

// Test with hide out of stock items option enabled.
$parsed_block = $this->get_base_parsed_block();
$parsed_block['attrs']['query']['__woocommerceStockStatus'] = array(
'instock',
'onbackorder',
);

$merged_query = $this->initialize_merged_query( $parsed_block );

$this->assertEmpty( $merged_query['meta_query'] );
}

/**
Expand Down Expand Up @@ -201,6 +214,57 @@ public function test_merging_order_by_popularity_queries() {
$this->assertEquals( 'total_sales', $merged_query['meta_key'] );
}

/**
* Test product visibility query exist in merged query.
*/
public function test_product_visibility_query_exist_in_merged_query() {
$product_visibility_terms = wc_get_product_visibility_term_ids();
$product_visibility_not_in = array( is_search() ? $product_visibility_terms['exclude-from-search'] : $product_visibility_terms['exclude-from-catalog'] );

$parsed_block = $this->get_base_parsed_block();

$merged_query = $this->initialize_merged_query( $parsed_block );

$this->assertContainsEquals(
array(
'taxonomy' => 'product_visibility',
'field' => 'term_taxonomy_id',
'terms' => $product_visibility_not_in,
'operator' => 'NOT IN',
),
$merged_query['tax_query']
);

$fn = function() {
return 'yes';
};

// Test with hide out of stock items option enabled.
add_filter(
'pre_option_woocommerce_hide_out_of_stock_items',
$fn
);
$product_visibility_not_in[] = $product_visibility_terms['outofstock'];

$parsed_block = $this->get_base_parsed_block();

$merged_query = $this->initialize_merged_query( $parsed_block );

$this->assertContainsEquals(
array(
'taxonomy' => 'product_visibility',
'field' => 'term_taxonomy_id',
'terms' => $product_visibility_not_in,
'operator' => 'NOT IN',
),
$merged_query['tax_query']
);
remove_filter(
'pre_option_woocommerce_hide_out_of_stock_items',
$fn
);
}

/**
* Test merging multiple queries.
*/
Expand All @@ -209,7 +273,7 @@ public function test_merging_multiple_queries() {
$parsed_block['attrs']['query']['orderBy'] = 'rating';
$parsed_block['attrs']['query']['__woocommerceStockStatus'] = array(
'instock',
'onbackorder',
'outofstock',
);
$parsed_block['attrs']['query']['__woocommerceAttributes'] = array(
array(
Expand All @@ -230,7 +294,7 @@ public function test_merging_multiple_queries() {
array(
'compare' => 'IN',
'key' => '_stock_status',
'value' => array( 'instock', 'onbackorder' ),
'value' => array( 'instock', 'outofstock' ),
),
$merged_query['meta_query']
);
Expand Down Expand Up @@ -266,6 +330,7 @@ public function test_merging_filter_by_max_price_queries() {
),
$merged_query['meta_query']
);
set_query_var( 'max_price', '' );
}

/**
Expand All @@ -289,6 +354,7 @@ public function test_merging_filter_by_min_price_queries() {
),
$merged_query['meta_query']
);
set_query_var( 'min_price', '' );
}

/**
Expand Down Expand Up @@ -318,6 +384,9 @@ public function test_merging_filter_by_min_and_max_price_queries() {
),
$merged_query['meta_query']
);

set_query_var( 'max_price', '' );
set_query_var( 'min_price', '' );
}

/**
Expand All @@ -336,6 +405,8 @@ public function test_merging_filter_by_stock_status_queries() {
),
$merged_query['meta_query']
);

set_query_var( 'filter_stock_status', '' );
}

/**
Expand Down Expand Up @@ -363,8 +434,16 @@ public function test_merging_filter_by_attribute_queries() {

$merged_query = $this->initialize_merged_query();

$attribute_tax_query = $merged_query['tax_query'][0];
$attribute_tax_query = array();

foreach ( $merged_query['tax_query'] as $tax_query ) {
if ( isset( $tax_query['relation'] ) ) {
$attribute_tax_query = $tax_query;
}
}

$attribute_tax_query_queries = $attribute_tax_query[0];

$this->assertEquals( 'AND', $attribute_tax_query['relation'] );

$this->assertContainsEquals(
Expand All @@ -385,6 +464,11 @@ public function test_merging_filter_by_attribute_queries() {
),
$attribute_tax_query_queries
);

set_query_var( 'filter_color', '' );
set_query_var( 'query_type_color', '' );
set_query_var( 'filter_size', '' );
set_query_var( 'query_type_size', '' );
}

/**
Expand Down Expand Up @@ -424,6 +508,10 @@ public function test_merging_multiple_filter_queries() {
),
$merged_query['meta_query']
);

set_query_var( 'max_price', '' );
set_query_var( 'min_price', '' );
set_query_var( 'filter_stock_status', '' );
}
}