ass ) { $tax_display = get_option( 'woocommerce_tax_display_shop' ); $tax_rates = WC_Tax::get_rates( $tax_class ); $base_tax_rates = WC_Tax::get_base_tax_rates( $tax_class ); // If prices are shown incl. tax, we want to remove the taxes from the filter amount to match prices stored excl. tax. if ( 'incl' === $tax_display ) { /** * Filters if taxes should be removed from locations outside the store base location. * * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing * with out of base locations. e.g. If a product costs 10 including tax, all users will pay 10 * regardless of location and taxes. * * @since 2.6.0 * * @internal Matches filter name in WooCommerce core. * * @param boolean $adjust_non_base_location_prices True by default. * @return boolean */ $taxes = apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ? WC_Tax::calc_tax( $price_filter, $base_tax_rates, true ) : WC_Tax::calc_tax( $price_filter, $tax_rates, true ); return $price_filter - array_sum( $taxes ); } // If prices are shown excl. tax, add taxes to match the prices stored in the DB. $taxes = WC_Tax::calc_tax( $price_filter, $tax_rates, false ); return $price_filter + array_sum( $taxes ); } /** * Get attribute lookup table name. * * @return string */ private function get_lookup_table_name() { return wc_get_container()->get( LookupDataStore::class )->get_lookup_table_name(); } /** * Add query clauses for attribute filter. * * @param array $args Query args. * @param \WP_Query $wp_query WP_Query object. * @return array */ private function attribute_filter_clauses( $args, $wp_query ) { $chosen_attributes = $this->get_chosen_attributes( $wp_query->query_vars ); if ( empty( $chosen_attributes ) ) { return $args; } global $wpdb; // The extra derived table ("SELECT product_or_parent_id FROM") is needed for performance // (causes the filtering subquery to be executed only once). $clause_root = " {$wpdb->posts}.ID IN ( SELECT product_or_parent_id FROM ("; if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) { $in_stock_clause = ' AND in_stock = 1'; } else { $in_stock_clause = ''; } $attribute_ids_for_and_filtering = array(); foreach ( $chosen_attributes as $taxonomy => $data ) { $all_terms = get_terms( $taxonomy, array( 'hide_empty' => false ) ); $term_ids_by_slug = wp_list_pluck( $all_terms, 'term_id', 'slug' ); $term_ids_to_filter_by = array_values( array_intersect_key( $term_ids_by_slug, array_flip( $data['terms'] ) ) ); $term_ids_to_filter_by = array_map( 'absint', $term_ids_to_filter_by ); $term_ids_to_filter_by_list = '(' . join( ',', $term_ids_to_filter_by ) . ')'; $is_and_query = 'and' === $data['query_type']; $count = count( $term_ids_to_filter_by ); if ( 0 !== $count ) { if ( $is_and_query && $count > 1 ) { $attribute_ids_for_and_filtering = array_merge( $attribute_ids_for_and_filtering, $term_ids_to_filter_by ); } else { $clauses[] = " {$clause_root} SELECT product_or_parent_id FROM {$this->get_lookup_table_name()} lt WHERE term_id in {$term_ids_to_filter_by_list} {$in_stock_clause} )"; } } } if ( ! empty( $attribute_ids_for_and_filtering ) ) { $count = count( $attribute_ids_for_and_filtering ); $term_ids_to_filter_by_list = '(' . join( ',', $attribute_ids_for_and_filtering ) . ')'; $clauses[] = " {$clause_root} SELECT product_or_parent_id FROM {$this->get_lookup_table_name()} lt WHERE is_variation_attribute=0 {$in_stock_clause} AND term_id in {$term_ids_to_filter_by_list} GROUP BY product_id HAVING COUNT(product_id)={$count} UNION SELECT product_or_parent_id FROM {$this->get_lookup_table_name()} lt WHERE is_variation_attribute=1 {$in_stock_clause} AND term_id in {$term_ids_to_filter_by_list} )"; } if ( ! empty( $clauses ) ) { // "temp" is needed because the extra derived tables require an alias. $args['where'] .= ' AND (' . join( ' temp ) AND ', $clauses ) . ' temp ))'; } elseif ( ! empty( $chosen_attributes ) ) { $args['where'] .= ' AND 1=0'; } return $args; } /** * Get an array of attributes and terms selected from query arguments. * * @param array $query_vars The WP_Query arguments. * @return array */ private function get_chosen_attributes( $query_vars ) { $chosen_attributes = array(); if ( empty( $query_vars ) ) { return $chosen_attributes; } foreach ( $query_vars as $key => $value ) { if ( 0 === strpos( $key, 'filter_' ) ) { $attribute = wc_sanitize_taxonomy_name( str_replace( 'filter_', '', $key ) ); $taxonomy = wc_attribute_taxonomy_name( $attribute ); $filter_terms = ! empty( $value ) ? explode( ',', wc_clean( wp_unslash( $value ) ) ) : array(); if ( empty( $filter_terms ) || ! taxonomy_exists( $taxonomy ) || ! wc_attribute_taxonomy_id_by_name( $attribute ) ) { continue; } $query_type = ! empty( $query_vars[ 'query_type_' . $attribute ] ) && in_array( $query_vars[ 'query_type_' . $attribute ], array( 'and', 'or' ), true ) ? wc_clean( wp_unslash( $query_vars[ 'query_type_' . $attribute ] ) ) : ''; $chosen_attributes[ $taxonomy ]['terms'] = array_map( 'sanitize_title', $filter_terms ); // Ensures correct encoding. $chosen_attributes[ $taxonomy ]['query_type'] = $query_type ? $query_type : 'and'; } } return $chosen_attributes; } }