diff --git a/plugins/woocommerce-blocks/src/BlockTypes/PriceFilter.php b/plugins/woocommerce-blocks/src/BlockTypes/PriceFilter.php index 0c9ab501c8e..bdbdcd2a889 100644 --- a/plugins/woocommerce-blocks/src/BlockTypes/PriceFilter.php +++ b/plugins/woocommerce-blocks/src/BlockTypes/PriceFilter.php @@ -11,5 +11,7 @@ class PriceFilter extends AbstractBlock { * * @var string */ - protected $block_name = 'price-filter'; + protected $block_name = 'price-filter'; + const MIN_PRICE_QUERY_VAR = 'min_price'; + const MAX_PRICE_QUERY_VAR = 'max_price'; } diff --git a/plugins/woocommerce-blocks/src/BlockTypes/ProductQuery.php b/plugins/woocommerce-blocks/src/BlockTypes/ProductQuery.php index 987da9b8b24..6a215e2ed05 100644 --- a/plugins/woocommerce-blocks/src/BlockTypes/ProductQuery.php +++ b/plugins/woocommerce-blocks/src/BlockTypes/ProductQuery.php @@ -27,6 +27,7 @@ class ProductQuery extends AbstractBlock { * - Hook into pre_render_block to update the query. */ protected function initialize() { + add_filter( 'query_vars', array( $this, 'set_query_vars' ) ); parent::initialize(); add_filter( 'pre_render_block', @@ -63,9 +64,12 @@ class ProductQuery extends AbstractBlock { $this->parsed_block = $parsed_block; if ( $this->is_woocommerce_variation( $parsed_block ) ) { + // Set this so that our product filters can detect if it's a PHP template. + $this->asset_data_registry->add( 'has_filterable_products', true, true ); + $this->asset_data_registry->add( 'is_rendering_php_template', true, true ); add_filter( 'query_loop_block_query_vars', - array( $this, 'get_query_by_attributes' ), + array( $this, 'build_query' ), 10, 1 ); @@ -73,12 +77,12 @@ class ProductQuery extends AbstractBlock { } /** - * Return a custom query based on the attributes. + * Return a custom query based on attributes, filters and global WP_Query. * * @param WP_Query $query The WordPress Query. * @return array */ - public function get_query_by_attributes( $query ) { + public function build_query( $query ) { $parsed_block = $this->parsed_block; if ( ! $this->is_woocommerce_variation( $parsed_block ) ) { return $query; @@ -90,40 +94,111 @@ class ProductQuery extends AbstractBlock { 'posts_per_page' => $query['posts_per_page'], '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(), + ); + + do_action( 'qm/debug', $parsed_block ); + + $queries_attributes = $this->get_queries_by_attributes( $parsed_block ); + $queries_filters = $this->get_queries_by_applied_filters(); + + return array_reduce( + array_merge( + $queries_attributes, + $queries_filters + ), + function( $acc, $query ) { + if ( isset( $query['post__in'] ) ) { + $acc['post__in'] = isset( $acc['post__in'] ) ? array_intersect( $acc['post__in'], $query['post__in'] ) : $query['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']; + return $acc; + }, + $common_query_values ); - $on_sale_query = $this->get_on_sale_products_query( $parsed_block['attrs']['query'] ); - return array_merge( $query, $common_query_values, $on_sale_query ); } + /** * Return a query for on sale products. * - * @param array $query_params Block query parameters. * @return array */ - private function get_on_sale_products_query( $query_params ) { - if ( ! isset( $query_params['__woocommerceOnSale'] ) || true !== $query_params['__woocommerceOnSale'] ) { - return array(); - } + private function get_on_sale_products_query() { + return array( + 'post__in' => wc_get_product_ids_on_sale(), + ); + } + + /** + * Set the query vars that are used by filter blocks. + * + * @param array $qvars Public query vars. + * @return array + */ + public function set_query_vars( $qvars ) { + $filter_query_args = array( PriceFilter::MIN_PRICE_QUERY_VAR, PriceFilter::MAX_PRICE_QUERY_VAR ); + return array_merge( $qvars, $filter_query_args ); + } + + /** + * Return queries that are generated by query args + * + * @return array + */ + private function get_queries_by_applied_filters() { + return array( 'price_filter' => $this->get_filter_by_price_query() ); + } + + /** + * Return queries that are generated by attributes + * + * @param array $parsed_block The Product Query that being rendered. + * @return array + */ + private function get_queries_by_attributes( $parsed_block ) { + $on_sale_enabled = isset( $parsed_block['attrs']['query']['__woocommerceOnSale'] ) && true === $parsed_block['attrs']['query']['__woocommerceOnSale']; + return array( + 'on_sale' => ( $on_sale_enabled ? $this->get_on_sale_products_query() : array() ), + ); + } + + /** + * Return a query that filters products by price. + * + * @return array + */ + private function get_filter_by_price_query() { + $min_price = get_query_var( PriceFilter::MIN_PRICE_QUERY_VAR ); + $max_price = get_query_var( PriceFilter::MAX_PRICE_QUERY_VAR ); + + $max_price_query = empty( $max_price ) ? array() : [ + 'key' => '_price', + 'value' => $max_price, + 'compare' => '<=', + 'type' => 'numeric', + ]; + + $min_price_query = empty( $min_price ) ? array() : [ + 'key' => '_price', + 'value' => $min_price, + 'compare' => '>=', + 'type' => 'numeric', + ]; return array( // Ignoring the warning of not using meta queries. // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( - 'relation' => 'OR', - array( - 'key' => '_sale_price', - 'value' => 0, - 'compare' => '>', - 'type' => 'numeric', - ), - array( - 'key' => '_min_variation_sale_price', - 'value' => 0, - 'compare' => '>', - 'type' => 'numeric', - ), + 'relation' => 'AND', + $max_price_query, + $min_price_query, ), ); }