-
Notifications
You must be signed in to change notification settings - Fork 219
Product Query - add support for the global query #7382
Changes from 60 commits
16ea028
f19fd99
816ffe9
6e93dd0
b243528
e55a41d
341d47b
1fd15a8
926f62c
3176679
1d56a2e
c9839f2
9065ab3
0be510e
6f326eb
5622639
c22d149
80a0dbf
5be22b9
b8bf17e
1bef270
c7e9773
b1b05f6
f20d7da
0ce3e42
3d18a8e
c34cae7
327919a
93a9df7
fd187b0
1d6993f
b5d401d
202d587
8718ab5
322e3f8
2367c4c
da7578b
d8e43b7
6abaaaf
6886d1b
8982a87
2c72ca0
c650f59
b722cb1
3cdba16
91f8a65
780e5c0
a73a130
7c6a9a6
679aa5d
ba47bf6
cd908d5
0f37868
c492ce6
c8e315c
6905099
dac0895
a1a6eb2
bc3abe7
939faac
90e224c
a5e4ea2
b538fa9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
<?php | ||
namespace Automattic\WooCommerce\Blocks\BlockTypes; | ||
|
||
use WP_Query; | ||
|
||
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_tax_query | ||
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query | ||
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key | ||
|
@@ -37,6 +39,14 @@ class ProductQuery extends AbstractBlock { | |
*/ | ||
protected $attributes_filter_query_args = array(); | ||
|
||
/** This is a feature flag to enable the custom inherit Global Query implementation. | ||
* This is not intended to be a permanent feature flag, but rather a temporary. | ||
* https://github.com/woocommerce/woocommerce-blocks/pull/7382 | ||
* | ||
* @var boolean | ||
*/ | ||
protected $is_custom_inherit_global_query_implementation_enabled = false; | ||
|
||
/** | ||
* Initialize this block type. | ||
* | ||
|
@@ -132,49 +142,34 @@ public function build_query( $query ) { | |
'tax_query' => array(), | ||
); | ||
|
||
$queries_by_attributes = $this->get_queries_by_attributes( $parsed_block ); | ||
$queries_by_filters = $this->get_queries_by_applied_filters(); | ||
$orderby_query = $this->get_custom_orderby_query( $query['orderby'] ); | ||
|
||
$base_query = array_merge( | ||
return $this->merge_queries( | ||
$common_query_values, | ||
$orderby_query | ||
); | ||
|
||
return array_reduce( | ||
array_merge( | ||
$queries_by_attributes, | ||
$queries_by_filters | ||
), | ||
function( $acc, $query ) { | ||
return $this->merge_queries( $acc, $query ); | ||
}, | ||
$base_query | ||
$this->get_global_query( $parsed_block ), | ||
$this->get_custom_orderby_query( $query['orderby'] ), | ||
$this->get_queries_by_attributes( $parsed_block ), | ||
$this->get_queries_by_applied_filters() | ||
); | ||
} | ||
|
||
/** | ||
* Return the product ids based on the attributes. | ||
* Return the product ids based on the attributes and global query. | ||
* This is used to allow the filter blocks to render data that matches with variations. More details here: https://github.com/woocommerce/woocommerce-blocks/issues/7245 | ||
* | ||
* @param array $parsed_block The block being rendered. | ||
* @return array | ||
*/ | ||
private function get_products_ids_by_attributes( $parsed_block ) { | ||
$queries_by_attributes = $this->get_queries_by_attributes( $parsed_block ); | ||
|
||
$query = array_reduce( | ||
$queries_by_attributes, | ||
function( $acc, $query ) { | ||
return $this->merge_queries( $acc, $query ); | ||
}, | ||
$query = $this->merge_queries( | ||
array( | ||
'post_type' => 'product', | ||
'post__in' => array(), | ||
'post_status' => 'publish', | ||
'posts_per_page' => -1, | ||
'meta_query' => array(), | ||
'tax_query' => array(), | ||
) | ||
), | ||
$this->get_queries_by_attributes( $parsed_block ), | ||
$this->get_global_query( $parsed_block ) | ||
); | ||
|
||
$products = new \WP_Query( $query ); | ||
|
@@ -186,16 +181,68 @@ function( $acc, $query ) { | |
/** | ||
* Merge in the first parameter the keys "post_in", "meta_query" and "tax_query" of the second parameter. | ||
* | ||
* @param array $a The first query. | ||
* @param array $b The second query. | ||
* @param array[] ...$queries Query arrays to be merged. | ||
* @return array | ||
*/ | ||
private function merge_queries( $a, $b ) { | ||
$a['post__in'] = ( isset( $b['post__in'] ) && ! empty( $b['post__in'] ) ) ? $this->intersect_arrays_when_not_empty( $a['post__in'], $b['post__in'] ) : $a['post__in']; | ||
$a['meta_query'] = ( isset( $b['meta_query'] ) && ! empty( $b['meta_query'] ) ) ? array_merge( $a['meta_query'], array( $b['meta_query'] ) ) : $a['meta_query']; | ||
$a['tax_query'] = ( isset( $b['tax_query'] ) && ! empty( $b['tax_query'] ) ) ? array_merge( $a['tax_query'], array( $b['tax_query'] ) ) : $a['tax_query']; | ||
private function merge_queries( ...$queries ) { | ||
$valid_query_vars = array_keys( ( new WP_Query() )->fill_query_vars( array() ) ); | ||
$valid_query_vars = array_merge( | ||
$valid_query_vars, | ||
// fill_query_vars doesn't include these vars so we need to add them manually. | ||
array( | ||
'date_query', | ||
'exact', | ||
'ignore_sticky_posts', | ||
'lazy_load_term_meta', | ||
'meta_compare_key', | ||
'meta_compare', | ||
'meta_query', | ||
'meta_type_key', | ||
'meta_type', | ||
'nopaging', | ||
'offset', | ||
'order', | ||
'orderby', | ||
'page', | ||
'post_type', | ||
'posts_per_page', | ||
'suppress_filters', | ||
'tax_query', | ||
) | ||
); | ||
|
||
$merged_query = array_reduce( | ||
$queries, | ||
function( $acc, $query ) use ( $valid_query_vars ) { | ||
if ( ! is_array( $query ) ) { | ||
return $acc; | ||
} | ||
if ( empty( array_intersect( $valid_query_vars, array_keys( $query ) ) ) ) { | ||
return $this->merge_queries( $acc, ...array_values( $query ) ); | ||
} | ||
return array_merge_recursive( $acc, $query ); | ||
}, | ||
array() | ||
); | ||
|
||
/** | ||
* If there are duplicated items in post__in, it means that we need to | ||
* use the intersection of the results, which in this case, are the | ||
* duplicated items. | ||
*/ | ||
if ( | ||
! empty( $merged_query['post__in'] ) && | ||
count( $merged_query['post__in'] ) > count( array_unique( $merged_query['post__in'] ) ) | ||
) { | ||
$merged_query['post__in'] = array_unique( | ||
array_diff( | ||
$merged_query['post__in'], | ||
array_unique( $merged_query['post__in'] ) | ||
) | ||
); | ||
} | ||
|
||
return $a; | ||
return $merged_query; | ||
} | ||
|
||
/** | ||
|
@@ -258,9 +305,11 @@ private function get_custom_orderby_query( $orderby ) { | |
private function get_stock_status_query( $stock_statii ) { | ||
return array( | ||
'meta_query' => array( | ||
'key' => '_stock_status', | ||
'value' => (array) $stock_statii, | ||
'compare' => 'IN', | ||
array( | ||
'key' => '_stock_status', | ||
'value' => (array) $stock_statii, | ||
'compare' => 'IN', | ||
), | ||
), | ||
); | ||
} | ||
|
@@ -493,22 +542,42 @@ function( $stock_status ) { | |
} | ||
|
||
/** | ||
* Intersect arrays neither of them are empty, otherwise merge them. | ||
* Get product-related query variables from the global query. | ||
* | ||
* @param array $parsed_block The Product Query that being rendered. | ||
* | ||
* @param array ...$arrays Arrays. | ||
* @return array | ||
*/ | ||
private function intersect_arrays_when_not_empty( ...$arrays ) { | ||
return array_reduce( | ||
$arrays, | ||
function( $acc, $array ) { | ||
if ( ! empty( $array ) && ! empty( $acc ) ) { | ||
return array_intersect( $acc, $array ); | ||
} | ||
return array_merge( $acc, $array ); | ||
}, | ||
array() | ||
); | ||
private function get_global_query( $parsed_block ) { | ||
if ( ! $this->is_custom_inherit_global_query_implementation_enabled ) { | ||
return array(); | ||
} | ||
|
||
global $wp_query; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should also check if we're on a WooCommerce page before processing. For Product Query, we care about the Product Catalog ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mmm, not sure about this check. We want to use the Product Query for the Product Search template too. In any case, I would say not to add any particular check here. In this way, 3rd developers could use the Product Query everywhere. The important thing is that they update the global |
||
|
||
$inherit_enabled = isset( $parsed_block['attrs']['query']['__woocommerceInherit'] ) && true === $parsed_block['attrs']['query']['__woocommerceInherit']; | ||
|
||
if ( ! $inherit_enabled ) { | ||
return array(); | ||
} | ||
|
||
$query = array(); | ||
|
||
if ( isset( $wp_query->query_vars['taxonomy'] ) && isset( $wp_query->query_vars['term'] ) ) { | ||
$query['tax_query'] = array( | ||
array( | ||
'taxonomy' => $wp_query->query_vars['taxonomy'], | ||
'field' => 'slug', | ||
'terms' => $wp_query->query_vars['term'], | ||
), | ||
); | ||
} | ||
|
||
if ( isset( $wp_query->query_vars['s'] ) ) { | ||
$query['s'] = $wp_query->query_vars['s']; | ||
} | ||
|
||
return $query; | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about using an experimental flag here instead? I think we just need to limit the global query support to experimental/development build only. 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least for now, we want to show the option inherited query from the template from Gutenberg. The Product Query is a foundation for other blocks, too (for example,
Buy it again
), and havingtwo inherit query from template
options can be confusing for the developers that check the block for the first time.Also, this is a super WIP code because there is still a discussion about how we should approach the problem: this is why I used this approach.
For me, It is fine to change it, but I think it could not be very clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make senses!
Can you explain how can we use this flag for development later? I mean what do we need to do to enable the flag? At glance, we have to temporarily change the flag in the source code, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! Just set
isCustomInheritGlobalQueryImplementationEnabled
andis_custom_inherit_global_query_implementation_enabled
to true.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe adding a note here to update the PHP flag too? (and vice-versa)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sense! I updated the comments!