diff --git a/assets/js/base/context/hooks/collections/use-collection-data.ts b/assets/js/base/context/hooks/collections/use-collection-data.ts index 98c16047f9e..49ad266bf2a 100644 --- a/assets/js/base/context/hooks/collections/use-collection-data.ts +++ b/assets/js/base/context/hooks/collections/use-collection-data.ts @@ -3,7 +3,7 @@ */ import { useState, useEffect, useMemo } from '@wordpress/element'; import { useDebounce } from 'use-debounce'; -import { sortBy } from 'lodash'; +import { isEmpty, sortBy } from 'lodash'; import { useShallowEqual } from '@woocommerce/base-hooks'; import { objectHasProp } from '@woocommerce/types'; @@ -47,6 +47,7 @@ interface UseCollectionDataProps { queryStock?: boolean; queryRating?: boolean; queryState: Record< string, unknown >; + productIds?: number[]; } export const useCollectionData = ( { @@ -55,6 +56,7 @@ export const useCollectionData = ( { queryStock, queryRating, queryState, + productIds, }: UseCollectionDataProps ) => { let context = useQueryStateContext(); context = `${ context }-collection-data`; @@ -163,6 +165,7 @@ export const useCollectionData = ( { per_page: undefined, orderby: undefined, order: undefined, + ...( ! isEmpty( productIds ) && { include: productIds } ), ...collectionDataQueryVars, }, shouldSelect: debouncedShouldSelect, diff --git a/assets/js/blocks/attribute-filter/block.tsx b/assets/js/blocks/attribute-filter/block.tsx index c43cc4a02be..cf55f22e6e3 100644 --- a/assets/js/blocks/attribute-filter/block.tsx +++ b/assets/js/blocks/attribute-filter/block.tsx @@ -101,6 +101,10 @@ const AttributeFilterBlock = ( { isString ); + const productIds = isEditor + ? [] + : getSettingWithCoercion( 'product_ids', [], Array.isArray ); + const [ hasSetFilterDefaultsFromUrl, setHasSetFilterDefaultsFromUrl ] = useState( false ); @@ -158,6 +162,7 @@ const AttributeFilterBlock = ( { ...queryState, attributes: filterAvailableTerms ? queryState.attributes : null, }, + productIds, } ); /** diff --git a/assets/js/blocks/price-filter/block.tsx b/assets/js/blocks/price-filter/block.tsx index b954d35ec7e..6b90e8580ee 100644 --- a/assets/js/blocks/price-filter/block.tsx +++ b/assets/js/blocks/price-filter/block.tsx @@ -99,6 +99,10 @@ const PriceFilterBlock = ( { isBoolean ); + const productIds = isEditor + ? [] + : getSettingWithCoercion( 'product_ids', [], Array.isArray ); + const [ hasSetFilterDefaultsFromUrl, setHasSetFilterDefaultsFromUrl ] = useState( false ); @@ -108,6 +112,7 @@ const PriceFilterBlock = ( { const { results, isLoading } = useCollectionData( { queryPrices: true, queryState, + productIds, } ); const currency = getCurrencyFromPriceResponse( diff --git a/assets/js/blocks/stock-filter/block.tsx b/assets/js/blocks/stock-filter/block.tsx index 2b347935493..fc3b2eafa91 100644 --- a/assets/js/blocks/stock-filter/block.tsx +++ b/assets/js/blocks/stock-filter/block.tsx @@ -70,6 +70,10 @@ const StockStatusFilterBlock = ( { {} ); + const productIds = isEditor + ? [] + : getSettingWithCoercion( 'product_ids', [], Array.isArray ); + const STOCK_STATUS_OPTIONS = useRef( getSetting( 'hideOutOfStockItems', false ) ? otherStockStatusOptions @@ -101,6 +105,7 @@ const StockStatusFilterBlock = ( { useCollectionData( { queryStock: true, queryState, + productIds, } ); /** diff --git a/src/BlockTypes/ProductQuery.php b/src/BlockTypes/ProductQuery.php index 90e2ecb6380..cf1f7b5f7f6 100644 --- a/src/BlockTypes/ProductQuery.php +++ b/src/BlockTypes/ProductQuery.php @@ -1,6 +1,9 @@ asset_data_registry->add( 'has_filterable_products', true, true ); $this->asset_data_registry->add( 'is_rendering_php_template', true, true ); + $this->asset_data_registry->add( 'product_ids', $this->get_products_ids_by_attributes( $parsed_block ), true ); add_filter( 'query_loop_block_query_vars', array( $this, 'build_query' ), @@ -114,10 +118,7 @@ public function build_query( $query ) { 'orderby' => $query['orderby'], 'order' => $query['order'], 'offset' => $query['offset'], - // Ignoring the warning of not using meta queries. - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array(), - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query 'tax_query' => array(), ); @@ -130,19 +131,57 @@ public function build_query( $query ) { $queries_by_filters ), function( $acc, $query ) { - $acc['post__in'] = isset( $query['post__in'] ) ? $this->intersect_arrays_when_not_empty( $acc['post__in'], $query['post__in'] ) : $acc['post__in']; - - // Ignoring the warning of not using meta queries. - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query - $acc['meta_query'] = isset( $query['meta_query'] ) ? array_merge( $acc['meta_query'], array( $query['meta_query'] ) ) : $acc['meta_query']; - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query - $acc['tax_query'] = isset( $query['tax_query'] ) ? array_merge( $acc['tax_query'], array( $query['tax_query'] ) ) : $acc['tax_query']; - return $acc; + return $this->merge_queries( $acc, $query ); }, $common_query_values ); } + /** + * Return the product ids based on the attributes. + * + * @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 ); + }, + array( + 'post_type' => 'product', + 'post__in' => array(), + 'post_status' => 'publish', + 'posts_per_page' => -1, + 'meta_query' => array(), + 'tax_query' => array(), + ) + ); + + $products = new \WP_Query( $query ); + $post_ids = wp_list_pluck( $products->posts, 'ID' ); + + return $post_ids; + } + + /** + * 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. + * @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']; + + return $a; + } + /** * Return a query for on sale products. * @@ -162,7 +201,6 @@ private function get_on_sale_products_query() { */ private function get_stock_status_query( $stock_statii ) { return array( - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( 'key' => '_stock_status', 'value' => (array) $stock_statii, @@ -305,8 +343,6 @@ private function get_filter_by_price_query() { } return array( - // Ignoring the warning of not using meta queries. - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( 'relation' => 'AND', $max_price_query, @@ -356,8 +392,6 @@ function( $acc, $query_args ) { } return array( - // Ignoring the warning of not using meta queries. - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query 'tax_query' => array( 'relation' => 'AND', $queries,