diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-query/inspector-controls.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-query/inspector-controls.tsx index 222c2dfcbb3..75374d17d59 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-query/inspector-controls.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-query/inspector-controls.tsx @@ -143,6 +143,18 @@ export const TOOLS_PANEL_CONTROLS = { ); }, + wooInherit: ( props: ProductQueryBlock ) => ( + { + setQueryAttribute( props, { __woocommerceInherit } ); + } } + /> + ), }; export const withProductQueryControls = diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-query/types.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-query/types.ts index 30f95d1abe9..901ccbbada1 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-query/types.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-query/types.ts @@ -40,6 +40,7 @@ export interface ProductQueryArguments { * ``` */ __woocommerceOnSale?: boolean; + __woocommerceInherit?: boolean; /** * Filter products by their stock status. * diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-query/variations/product-query.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-query/variations/product-query.tsx index a5b0f5a1a5e..ba70eceadb0 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-query/variations/product-query.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-query/variations/product-query.tsx @@ -19,6 +19,12 @@ import { const VARIATION_NAME = 'woocommerce/product-query'; +// 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. +// It is also necessary to enable this feature flag on the PHP side: `src/BlockTypes/ProductQuery.php:49`. +// https://github.com/woocommerce/woocommerce-blocks/pull/7382 +const isCustomInheritGlobalQueryImplementationEnabled = false; + if ( isExperimentalBuild() ) { registerBlockVariation( QUERY_LOOP_ID, { name: VARIATION_NAME, @@ -41,7 +47,9 @@ if ( isExperimentalBuild() ) { // https://github.com/WordPress/gutenberg/pull/43632 // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - allowedControls: DEFAULT_ALLOWED_CONTROLS, + allowedControls: isCustomInheritGlobalQueryImplementationEnabled + ? [ ...DEFAULT_ALLOWED_CONTROLS, 'wooInherit' ] + : DEFAULT_ALLOWED_CONTROLS, innerBlocks: INNER_BLOCKS_TEMPLATE, scope: [ 'block', 'inserter' ], } ); diff --git a/plugins/woocommerce-blocks/checkstyle.xml b/plugins/woocommerce-blocks/checkstyle.xml index 80283d8dbd9..cd55c0c7270 100644 --- a/plugins/woocommerce-blocks/checkstyle.xml +++ b/plugins/woocommerce-blocks/checkstyle.xml @@ -3420,7 +3420,7 @@ - diff --git a/plugins/woocommerce-blocks/src/BlockTypes/ProductQuery.php b/plugins/woocommerce-blocks/src/BlockTypes/ProductQuery.php index a3622b51e29..f7dff2df89d 100644 --- a/plugins/woocommerce-blocks/src/BlockTypes/ProductQuery.php +++ b/plugins/woocommerce-blocks/src/BlockTypes/ProductQuery.php @@ -1,6 +1,8 @@ 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(), @@ -174,7 +168,9 @@ class ProductQuery extends AbstractBlock { '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 +182,68 @@ class ProductQuery extends AbstractBlock { /** * 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', + ) + ); - return $a; + $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 $merged_query; } /** @@ -258,9 +306,11 @@ class ProductQuery extends AbstractBlock { 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 +543,42 @@ class ProductQuery extends AbstractBlock { } /** - * 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; + + $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; } }