diff --git a/plugins/woocommerce-blocks/src/BlockTypes/ProductQuery.php b/plugins/woocommerce-blocks/src/BlockTypes/ProductQuery.php index f7dff2df89d..4ba07717b56 100644 --- a/plugins/woocommerce-blocks/src/BlockTypes/ProductQuery.php +++ b/plugins/woocommerce-blocks/src/BlockTypes/ProductQuery.php @@ -48,6 +48,13 @@ class ProductQuery extends AbstractBlock { */ protected $is_custom_inherit_global_query_implementation_enabled = false; + /** + * All query args from WP_Query. + * + * @var array + */ + protected $valid_query_vars; + /** * Initialize this block type. * @@ -186,42 +193,17 @@ class ProductQuery extends AbstractBlock { * @return array */ 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 ) { + function( $acc, $query ) { if ( ! is_array( $query ) ) { return $acc; } - if ( empty( array_intersect( $valid_query_vars, array_keys( $query ) ) ) ) { + // If the $query doesn't contain any valid query keys, we unpack/spread it then merge. + if ( empty( array_intersect( $this->get_valid_query_vars(), array_keys( $query ) ) ) ) { return $this->merge_queries( $acc, ...array_values( $query ) ); } - return array_merge_recursive( $acc, $query ); + return $this->array_merge_recursive_replace_non_array_properties( $acc, $query ); }, array() ); @@ -450,9 +432,11 @@ class ProductQuery extends AbstractBlock { return array( 'meta_query' => array( - 'relation' => 'AND', - $max_price_query, - $min_price_query, + array( + 'relation' => 'AND', + $max_price_query, + $min_price_query, + ), ), ); } @@ -499,8 +483,10 @@ class ProductQuery extends AbstractBlock { return array( 'tax_query' => array( - 'relation' => 'AND', - $queries, + array( + 'relation' => 'AND', + $queries, + ), ), ); } @@ -542,6 +528,112 @@ class ProductQuery extends AbstractBlock { ); } + /** + * Return or initialize $valid_query_vars. + * + * @return array + */ + private function get_valid_query_vars() { + if ( ! empty( $this->valid_query_vars ) ) { + return $this->valid_query_vars; + } + + $valid_query_vars = array_keys( ( new WP_Query() )->fill_query_vars( array() ) ); + $this->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 $this->valid_query_vars; + } + + /** + * Merge two array recursively but replace the non-array values instead of + * merging them. The merging strategy: + * + * - If keys from merge array doesn't exist in the base array, create them. + * - For array items with numeric keys, we merge them as normal. + * - For array items with string keys: + * + * - If the value isn't array, we'll use the value comming from the merge array. + * $base = ['orderby' => 'date'] + * $new = ['orderby' => 'meta_value_num'] + * Result: ['orderby' => 'meta_value_num'] + * + * - If the value is array, we'll use recursion to merge each key. + * $base = ['meta_query' => [ + * [ + * 'key' => '_stock_status', + * 'compare' => 'IN' + * 'value' => ['instock', 'onbackorder'] + * ] + * ]] + * $new = ['meta_query' => [ + * [ + * 'relation' => 'AND', + * [...], + * [...], + * ] + * ]] + * Result: ['meta_query' => [ + * [ + * 'key' => '_stock_status', + * 'compare' => 'IN' + * 'value' => ['instock', 'onbackorder'] + * ], + * [ + * 'relation' => 'AND', + * [...], + * [...], + * ] + * ]] + * + * $base = ['post__in' => [1, 2, 3, 4, 5]] + * $new = ['post__in' => [3, 4, 5, 6, 7]] + * Result: ['post__in' => [1, 2, 3, 4, 5, 3, 4, 5, 6, 7]] + * + * @param array $base First array. + * @param array $new Second array. + */ + private function array_merge_recursive_replace_non_array_properties( $base, $new ) { + foreach ( $new as $key => $value ) { + if ( is_numeric( $key ) ) { + $base[] = $value; + } else { + if ( is_array( $value ) ) { + if ( ! isset( $base[ $key ] ) ) { + $base[ $key ] = array(); + } + $base[ $key ] = $this->array_merge_recursive_replace_non_array_properties( $base[ $key ], $value ); + } else { + $base[ $key ] = $value; + } + } + } + + return $base; + } + /** * Get product-related query variables from the global query. * @@ -582,3 +674,4 @@ class ProductQuery extends AbstractBlock { } } +