} return $this->array_merge_recursive_replace_non_array_properties( $acc, $query ); }, array() ); // Perform any necessary special merges. $merged_query['post__in'] = $this->merge_post__in( ...$special_query_vars['post__in'] ); return $merged_query; } /** * Return query params to support custom sort values * * @param string $orderby Sort order option. * * @return array */ private function get_custom_orderby_query( $orderby ) { if ( ! in_array( $orderby, $this->custom_order_opts, true ) || 'post__in' === $orderby ) { return array( 'orderby' => $orderby ); } if ( 'price' === $orderby ) { add_filter( 'posts_clauses', array( $this, 'add_price_sorting_posts_clauses' ), 10, 2 ); return array( 'isProductCollection' => true, 'orderby' => $orderby, ); } // The popularity orderby value here is for backwards compatibility as we have since removed the filter option. if ( 'sales' === $orderby || 'popularity' === $orderby ) { add_filter( 'posts_clauses', array( $this, 'add_sales_sorting_posts_clauses' ), 10, 2 ); return array( 'isProductCollection' => true, 'orderby' => $orderby, ); } if ( 'menu_order' === $orderby ) { return array( 'orderby' => 'menu_order', 'order' => 'ASC', ); } if ( 'random' === $orderby ) { return array( 'orderby' => 'rand', ); } $meta_keys = array( 'rating' => '_wc_average_rating', ); return array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_key' => $meta_keys[ $orderby ], 'orderby' => 'meta_value_num', ); } /** * Add the `posts_clauses` filter to add price-based sorting * * @param array $clauses The list of clauses for the query. * @param WP_Query $query The WP_Query instance. * @return array Modified list of clauses. */ public function add_price_sorting_posts_clauses( $clauses, $query ) { $query_vars = $query->query_vars; $is_product_collection_block = $query_vars['isProductCollection'] ?? false; if ( ! $is_product_collection_block ) { return $clauses; } $orderby = $query_vars['orderby'] ?? null; if ( 'price' !== $orderby ) { return $clauses; } $clauses['join'] = $this->append_product_sorting_table_join( $clauses['join'] ); $is_ascending_order = 'asc' === strtolower( $query_vars['order'] ?? 'desc' ); $clauses['orderby'] = $is_ascending_order ? 'wc_product_meta_lookup.min_price ASC, wc_product_meta_lookup.product_id ASC' : 'wc_product_meta_lookup.max_price DESC, wc_product_meta_lookup.product_id DESC'; return $clauses; } /** * Add the `posts_clauses` filter to add sales-based sorting * * @param array $clauses The list of clauses for the query. * @param WP_Query $query The WP_Query instance. * @return array Modified list of clauses. */ public function add_sales_sorting_posts_clauses( $clauses, $query ) { $query_vars = $query->query_vars; $is_product_collection_block = $query_vars['isProductCollection'] ?? false; if ( ! $is_product_collection_block ) { return $clauses; } $orderby = $query_vars['orderby'] ?? null; // The popularity orderby value here is for backwards compatibility as we have since removed the filter option. if ( 'sales' !== $orderby && 'popularity' !== $orderby ) { return $clauses; } $clauses['join'] = $this->append_product_sorting_table_join( $clauses['join'] ); $is_ascending_order = 'asc' === strtolower( $query_vars['order'] ?? 'desc' ); $clauses['orderby'] = $is_ascending_order ? 'wc_product_meta_lookup.total_sales ASC, wc_product_meta_lookup.product_id ASC' : 'wc_product_meta_lookup.total_sales DESC, wc_product_meta_lookup.product_id DESC'; return $clauses; } /** * Join wc_product_meta_lookup to posts if not already joined. * * @param string $sql SQL join. * @return string */ protected function append_product_sorting_table_join( $sql ) { global $wpdb; if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) { $sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id "; } return $sql; } /** * Merge all of the 'post__in' values and return an array containing only values that are present in all arrays. * * @param int[][] ...$post__in The 'post__in' values to be merged. * * @return int[] The merged 'post__in' values. */ private function merge_post__in( ...$post__in ) { if ( empty( $post__in ) ) { return array(); } // Since we're using array_intersect, any array that is empty will result // in an empty output array. To avoid this we need to make sure every // argument is a non-empty array. $post__in = array_filter( $post__in, function ( $val ) { return is_array( $val ) && ! empty( $val ); } ); if ( empty( $post__in ) ) { return array(); } // Since the 'post__in' filter is exclusionary we need to use an intersection of // all of the arrays. This ensures one query doesn't add options that another // has otherwise excluded from the results. if ( count( $post__in ) > 1 ) { $post__in = array_intersect( ...$post__in ); // An empty array means that there was no overlap between the filters and so // the query should return no results. if ( empty( $post__in ) ) { return array( -1 ); } } else { $post__in = reset( $post__in ); } return array_values( array_unique( $post__in, SORT_NUMERIC ) ); } }