From 6cc700a49751eda373db60aad2e1431f615ad1b6 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 22 Jan 2016 15:50:23 +0000 Subject: [PATCH 01/36] [2.5] Report on partial refunds/refunds items rather than full refunds to ensure they are reported when refunded @claudiosmweb your testing + approval welcome. Closes #10138 --- .../reports/class-wc-report-sales-by-date.php | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/includes/admin/reports/class-wc-report-sales-by-date.php b/includes/admin/reports/class-wc-report-sales-by-date.php index a51f80fa562..40a6d6ff65b 100644 --- a/includes/admin/reports/class-wc-report-sales-by-date.php +++ b/includes/admin/reports/class-wc-report-sales-by-date.php @@ -266,19 +266,22 @@ class WC_Report_Sales_By_Date extends WC_Admin_Report { 'name' => 'total_sales' ), '_order_shipping' => array( - 'type' => 'meta', - 'function' => '', - 'name' => 'total_shipping' + 'type' => 'meta', + 'function' => '', + 'name' => 'total_shipping', + 'join_type' => 'LEFT' ), '_order_tax' => array( - 'type' => 'meta', - 'function' => '', - 'name' => 'total_tax' + 'type' => 'meta', + 'function' => '', + 'name' => 'total_tax', + 'join_type' => 'LEFT' ), '_order_shipping_tax' => array( - 'type' => 'meta', - 'function' => '', - 'name' => 'total_shipping_tax' + 'type' => 'meta', + 'function' => '', + 'name' => 'total_shipping_tax', + 'join_type' => 'LEFT' ), '_qty' => array( 'type' => 'order_item_meta', @@ -292,34 +295,32 @@ class WC_Report_Sales_By_Date extends WC_Admin_Report { 'query_type' => 'get_results', 'filter_range' => true, 'order_status' => false, - 'parent_order_status' => array( 'completed', 'processing', 'on-hold' ), + 'parent_order_status' => array( 'completed', 'processing', 'on-hold', 'refunded' ), ) ); /** - * Total up values by combining full refunds and partial refunds for line items. + * Total up refunds. Note: when an order is fully refunded, a refund line will be added. */ - $this->report_data->total_tax_refunded = wc_format_decimal( array_sum( wp_list_pluck( $this->report_data->full_refunds, 'total_tax' ) ), 2 ); - $this->report_data->total_shipping_refunded = wc_format_decimal( array_sum( wp_list_pluck( $this->report_data->full_refunds, 'total_shipping' ) ), 2 ); - $this->report_data->total_shipping_tax_refunded = wc_format_decimal( array_sum( wp_list_pluck( $this->report_data->full_refunds, 'total_shipping_tax' ) ), 2 ); - $this->report_data->total_refunds = wc_format_decimal( array_sum( wp_list_pluck( $this->report_data->full_refunds, 'total_refund' ) ), 2 ); + $this->report_data->total_tax_refunded = 0; + $this->report_data->total_shipping_refunded = 0; + $this->report_data->total_shipping_tax_refunded = 0; + $this->report_data->total_refunds = 0; - /** - * Loop over partial refunds and increase the above values. - */ foreach ( $this->report_data->partial_refunds as $key => $value ) { - switch ( $value->item_type ) { - case 'shipping' : - $this->report_data->total_shipping_tax_refunded += ( $value->total_shipping_tax * -1 ); - $this->report_data->total_shipping_refunded += wc_format_decimal( $value->total_refund, 2 ); - $this->report_data->total_refunds += $value->total_refund; - - - break; - case 'line_item' : - $this->report_data->total_tax_refunded += ( $value->total_tax * -1 ); - $this->report_data->refunded_order_items += absint( $value->order_item_count ); - $this->report_data->total_refunds += $value->total_refund; - break; + if ( is_null( $value->item_type ) ) { + // Null when the order was refunded, but not the line items themselves. + $this->report_data->total_tax_refunded += ( $value->total_tax * -1 ); + $this->report_data->total_refunds += $value->total_refund; + } + elseif( 'shipping' === $value->item_type ) { + $this->report_data->total_shipping_tax_refunded += ( $value->total_shipping_tax * -1 ); + $this->report_data->total_shipping_refunded += wc_format_decimal( $value->total_refund, 2 ); + $this->report_data->total_refunds += $value->total_refund; + } + elseif( 'line_item' === $value->item_type ) { + $this->report_data->total_tax_refunded += ( $value->total_tax * -1 ); + $this->report_data->refunded_order_items += absint( $value->order_item_count ); + $this->report_data->total_refunds += $value->total_refund; } } @@ -504,7 +505,7 @@ class WC_Report_Sales_By_Date extends WC_Admin_Report { $order_amounts = $this->prepare_chart_data( $this->report_data->orders, 'post_date', 'total_sales', $this->chart_interval, $this->start_date, $this->chart_groupby ); $coupon_amounts = $this->prepare_chart_data( $this->report_data->coupons, 'post_date', 'discount_amount', $this->chart_interval, $this->start_date, $this->chart_groupby ); $shipping_amounts = $this->prepare_chart_data( $this->report_data->orders, 'post_date', 'total_shipping', $this->chart_interval, $this->start_date, $this->chart_groupby ); - $refund_amounts = $this->prepare_chart_data( array_merge( $this->report_data->partial_refunds, $this->report_data->full_refunds ), 'post_date', 'total_refund', $this->chart_interval, $this->start_date, $this->chart_groupby ); + $refund_amounts = $this->prepare_chart_data( $this->report_data->partial_refunds, 'post_date', 'total_refund', $this->chart_interval, $this->start_date, $this->chart_groupby ); $shipping_tax_amounts = $this->prepare_chart_data( $this->report_data->orders, 'post_date', 'total_shipping_tax', $this->chart_interval, $this->start_date, $this->chart_groupby ); $tax_amounts = $this->prepare_chart_data( $this->report_data->orders, 'post_date', 'total_tax', $this->chart_interval, $this->start_date, $this->chart_groupby ); From 77e1aa82635b912299f0e50b9dd16e25c271a1ac Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 22 Jan 2016 16:28:29 +0000 Subject: [PATCH 02/36] Make qty for refunds negative Fixes order counts reported in #10135 May however require an upgrade routine and needs more testing. Not for 2.5. Fixes #10135 --- .../admin/meta-boxes/views/html-order-item.php | 2 +- .../reports/class-wc-report-sales-by-date.php | 4 ++-- .../class-wc-report-sales-by-product.php | 18 +++--------------- includes/wc-order-functions.php | 2 +- 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/includes/admin/meta-boxes/views/html-order-item.php b/includes/admin/meta-boxes/views/html-order-item.php index 7c26bbf403e..b359c221db8 100644 --- a/includes/admin/meta-boxes/views/html-order-item.php +++ b/includes/admin/meta-boxes/views/html-order-item.php @@ -170,7 +170,7 @@ if ( ! defined( 'ABSPATH' ) ) { echo ( isset( $item['qty'] ) ) ? esc_html( $item['qty'] ) : ''; if ( $refunded_qty = $order->get_qty_refunded_for_item( $item_id ) ) { - echo '-' . $refunded_qty . ''; + echo '' . $refunded_qty . ''; } ?> diff --git a/includes/admin/reports/class-wc-report-sales-by-date.php b/includes/admin/reports/class-wc-report-sales-by-date.php index 40a6d6ff65b..7311307d59c 100644 --- a/includes/admin/reports/class-wc-report-sales-by-date.php +++ b/includes/admin/reports/class-wc-report-sales-by-date.php @@ -191,7 +191,7 @@ class WC_Report_Sales_By_Date extends WC_Admin_Report { 'order_by' => 'post_date ASC', 'query_type' => 'get_results', 'filter_range' => true, - 'order_types' => wc_get_order_types( 'sales-reports' ), // Orders, not refunds + 'order_types' => wc_get_order_types( 'sales-reports' ), 'order_status' => array( 'completed', 'processing', 'on-hold', 'refunded' ) ) ); @@ -343,7 +343,7 @@ class WC_Report_Sales_By_Date extends WC_Admin_Report { $this->report_data->total_refunded_orders = absint( count( $this->report_data->full_refunds ) ); // Item counts - $this->report_data->total_items = absint( array_sum( wp_list_pluck( $this->report_data->order_items, 'order_item_count' ) ) ); + $this->report_data->total_items = absint( array_sum( wp_list_pluck( $this->report_data->order_items, 'order_item_count' ) ) ) - $this->report_data->refunded_order_items; } /** diff --git a/includes/admin/reports/class-wc-report-sales-by-product.php b/includes/admin/reports/class-wc-report-sales-by-product.php index 022f39fc561..597c85a32bd 100644 --- a/includes/admin/reports/class-wc-report-sales-by-product.php +++ b/includes/admin/reports/class-wc-report-sales-by-product.php @@ -99,7 +99,6 @@ class WC_Report_Sales_By_Product extends WC_Admin_Report { ) ), 'query_type' => 'get_var', - 'order_types' => wc_get_order_types( 'order-count' ), 'filter_range' => true ) ) ); @@ -110,7 +109,7 @@ class WC_Report_Sales_By_Product extends WC_Admin_Report { ); $legend[] = array( - 'title' => sprintf( __( '%s purchases for the selected items', 'woocommerce' ), '' . $total_items . '' ), + 'title' => sprintf( __( '%s purchases for the selected items', 'woocommerce' ), '' . ( $total_items ) . '' ), 'color' => $this->chart_colours['item_count'], 'highlight_series' => 0 ); @@ -230,20 +229,11 @@ class WC_Report_Sales_By_Product extends WC_Admin_Report { 'name' => 'order_item_qty' ) ), - 'where_meta' => array( - array( - 'type' => 'order_item_meta', - 'meta_key' => '_line_subtotal', - 'meta_value' => '0', - 'operator' => '>' - ) - ), 'order_by' => 'order_item_qty DESC', 'group_by' => 'product_id', 'limit' => 12, 'query_type' => 'get_results', - 'filter_range' => true, - 'order_types' => wc_get_order_types( 'order-count' ), + 'filter_range' => true ) ); if ( $top_sellers ) { @@ -291,9 +281,7 @@ class WC_Report_Sales_By_Product extends WC_Admin_Report { 'group_by' => 'product_id', 'limit' => 12, 'query_type' => 'get_results', - 'filter_range' => true, - 'order_types' => wc_get_order_types( 'order-count' ), - 'nocache' => true + 'filter_range' => true ) ); if ( $top_freebies ) { diff --git a/includes/wc-order-functions.php b/includes/wc-order-functions.php index 41d2a819121..922c15b7436 100644 --- a/includes/wc-order-functions.php +++ b/includes/wc-order-functions.php @@ -689,7 +689,7 @@ function wc_create_refund( $args = array() ) { 'tax_data' => array( 'total' => array_map( 'wc_format_refund_total', $refund_item['refund_tax'] ), 'subtotal' => array_map( 'wc_format_refund_total', $refund_item['refund_tax'] ) ) ) ); - $new_item_id = $refund->add_product( $order->get_product_from_item( $order_items[ $refund_item_id ] ), isset( $refund_item['qty'] ) ? $refund_item['qty'] : 0, $line_item_args ); + $new_item_id = $refund->add_product( $order->get_product_from_item( $order_items[ $refund_item_id ] ), isset( $refund_item['qty'] ) ? $refund_item['qty'] * -1 : 0, $line_item_args ); wc_add_order_item_meta( $new_item_id, '_refunded_item_id', $refund_item_id ); break; case 'shipping' : From 5b0abaaa1b69f47c4455479ef8f5d992a6bed2cc Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 9 Feb 2016 20:16:08 +0000 Subject: [PATCH 03/36] Sync min and max price for better filtering! --- includes/class-wc-product-variable.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-product-variable.php b/includes/class-wc-product-variable.php index c841278d512..ffa83303c5a 100644 --- a/includes/class-wc-product-variable.php +++ b/includes/class-wc-product-variable.php @@ -823,8 +823,10 @@ class WC_Product_Variable extends WC_Product { update_post_meta( $product_id, '_max_' . $price_type . '_variation_id', ${"max_{$price_type}_id"} ); } - // The VARIABLE PRODUCT price should equal the min price of any type - update_post_meta( $product_id, '_price', $min_price ); + // Sync _price meta + delete_post_meta( $product_id, '_price' ); + add_post_meta( $product_id, '_price', $min_price, false ); + add_post_meta( $product_id, '_price', $max_price, false ); delete_transient( 'wc_products_onsale' ); // Sync attributes From 1d5349bc83b874c7d4c91bd4865fc30294a317b9 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 9 Feb 2016 20:53:18 +0000 Subject: [PATCH 04/36] Keep rating meta synced for filtering --- includes/abstracts/abstract-wc-product.php | 102 ++++++++++++--------- includes/class-wc-comments.php | 1 + 2 files changed, 62 insertions(+), 41 deletions(-) diff --git a/includes/abstracts/abstract-wc-product.php b/includes/abstracts/abstract-wc-product.php index b53f2a3bacb..8317b50c808 100644 --- a/includes/abstracts/abstract-wc-product.php +++ b/includes/abstracts/abstract-wc-product.php @@ -1083,29 +1083,12 @@ class WC_Product { * @return string */ public function get_average_rating() { - global $wpdb; - - // No meta date? Do the calculation + // No meta data? Do the calculation if ( ! metadata_exists( 'post', $this->id, '_wc_average_rating' ) ) { - if ( $count = $this->get_rating_count() ) { - $ratings = $wpdb->get_var( $wpdb->prepare(" - SELECT SUM(meta_value) FROM $wpdb->commentmeta - LEFT JOIN $wpdb->comments ON $wpdb->commentmeta.comment_id = $wpdb->comments.comment_ID - WHERE meta_key = 'rating' - AND comment_post_ID = %d - AND comment_approved = '1' - AND meta_value > 0 - ", $this->id ) ); - $average = number_format( $ratings / $count, 2, '.', '' ); - } else { - $average = 0; - } - update_post_meta( $this->id, '_wc_average_rating', $average ); - } else { - $average = get_post_meta( $this->id, '_wc_average_rating', true ); + $this->sync_average_rating( $this->id ); } - return (string) floatval( $average ); + return (string) floatval( get_post_meta( $this->id, '_wc_average_rating', true ) ); } /** @@ -1114,30 +1097,13 @@ class WC_Product { * @return int */ public function get_rating_count( $value = null ) { - global $wpdb; - - // No meta date? Do the calculation + // No meta data? Do the calculation if ( ! metadata_exists( 'post', $this->id, '_wc_rating_count' ) ) { - $counts = array(); - $raw_counts = $wpdb->get_results( $wpdb->prepare(" - SELECT meta_value, COUNT( * ) as meta_value_count FROM $wpdb->commentmeta - LEFT JOIN $wpdb->comments ON $wpdb->commentmeta.comment_id = $wpdb->comments.comment_ID - WHERE meta_key = 'rating' - AND comment_post_ID = %d - AND comment_approved = '1' - AND meta_value > 0 - GROUP BY meta_value - ", $this->id ) ); - - foreach ( $raw_counts as $count ) { - $counts[ $count->meta_value ] = $count->meta_value_count; - } - - update_post_meta( $this->id, '_wc_rating_count', $counts ); - } else { - $counts = get_post_meta( $this->id, '_wc_rating_count', true ); + $this->sync_rating_count( $this->id ); } + $counts = get_post_meta( $this->id, '_wc_rating_count', true ); + if ( is_null( $value ) ) { return array_sum( $counts ); } else { @@ -1145,6 +1111,60 @@ class WC_Product { } } + /** + * Sync product rating. Can be called statically. + * @param int $post_id + */ + public static function sync_average_rating( $post_id ) { + if ( ! metadata_exists( 'post', $post_id, '_wc_rating_count' ) ) { + self::sync_rating_count( $post_id ); + } + + $count = array_sum( (array) get_post_meta( $post_id, '_wc_rating_count', true ) ); + + if ( $count ) { + global $wpdb; + + $ratings = $wpdb->get_var( $wpdb->prepare(" + SELECT SUM(meta_value) FROM $wpdb->commentmeta + LEFT JOIN $wpdb->comments ON $wpdb->commentmeta.comment_id = $wpdb->comments.comment_ID + WHERE meta_key = 'rating' + AND comment_post_ID = %d + AND comment_approved = '1' + AND meta_value > 0 + ", $post_id ) ); + $average = number_format( $ratings / $count, 2, '.', '' ); + } else { + $average = 0; + } + update_post_meta( $post_id, '_wc_average_rating', $average ); + } + + /** + * Sync product rating count. Can be called statically. + * @param int $post_id + */ + public static function sync_rating_count( $post_id ) { + global $wpdb; + + $counts = array(); + $raw_counts = $wpdb->get_results( $wpdb->prepare(" + SELECT meta_value, COUNT( * ) as meta_value_count FROM $wpdb->commentmeta + LEFT JOIN $wpdb->comments ON $wpdb->commentmeta.comment_id = $wpdb->comments.comment_ID + WHERE meta_key = 'rating' + AND comment_post_ID = %d + AND comment_approved = '1' + AND meta_value > 0 + GROUP BY meta_value + ", $post_id ) ); + + foreach ( $raw_counts as $count ) { + $counts[ $count->meta_value ] = $count->meta_value_count; + } + + update_post_meta( $post_id, '_wc_rating_count', $counts ); + } + /** * Returns the product rating in html format. * diff --git a/includes/class-wc-comments.php b/includes/class-wc-comments.php index 08ec1a73c30..8fed8984f32 100644 --- a/includes/class-wc-comments.php +++ b/includes/class-wc-comments.php @@ -235,6 +235,7 @@ class WC_Comments { delete_post_meta( $post_id, '_wc_average_rating' ); delete_post_meta( $post_id, '_wc_rating_count' ); delete_post_meta( $post_id, '_wc_review_count' ); + WC_Product::sync_average_rating( $post_id ); } /** From d23c316326864fa38caf177b33578715c7a18b48 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 9 Feb 2016 21:14:55 +0000 Subject: [PATCH 05/36] Refactor queries to use WP_QUERY only --- includes/class-wc-query.php | 544 ++++-------------- .../widgets/class-wc-widget-layered-nav.php | 64 ++- .../widgets/class-wc-widget-price-filter.php | 155 +++-- .../widgets/class-wc-widget-rating-filter.php | 6 +- 4 files changed, 272 insertions(+), 497 deletions(-) diff --git a/includes/class-wc-query.php b/includes/class-wc-query.php index 53e3de5b698..7ee5edd356f 100644 --- a/includes/class-wc-query.php +++ b/includes/class-wc-query.php @@ -3,7 +3,7 @@ * Contains the query functions for WooCommerce which alter the front-end post queries and loops * * @class WC_Query - * @version 2.3.0 + * @version 2.6.0 * @package WooCommerce/Classes * @category Class * @author WooThemes @@ -23,27 +23,6 @@ class WC_Query { /** @public array Query vars to add to wp */ public $query_vars = array(); - /** @public array Unfiltered product ids (before layered nav etc) */ - public $unfiltered_product_ids = array(); - - /** @public array Filtered product ids (after layered nav) */ - public $filtered_product_ids = array(); - - /** @public array Filtered product ids (after layered nav, per taxonomy) */ - public $filtered_product_ids_for_taxonomy = array(); - - /** @public array Product IDs that match the layered nav + price filter */ - public $post__in = array(); - - /** @public array|string The meta query for the page */ - public $meta_query = ''; - - /** @public array Post IDs matching layered nav only */ - public $layered_nav_post__in = array(); - - /** @public array Stores post IDs matching layered nav, so price filter can find max price in view */ - public $layered_nav_product_ids = array(); - /** * Constructor for the query class. Hooks in methods. * @@ -51,20 +30,15 @@ class WC_Query { */ public function __construct() { add_action( 'init', array( $this, 'add_endpoints' ) ); - add_action( 'init', array( $this, 'layered_nav_init' ) ); - add_action( 'init', array( $this, 'price_filter_init' ) ); - add_action( 'init', array( $this, 'rating_filter_init' ) ); - if ( ! is_admin() ) { + add_action( 'init', array( $this, 'layered_nav_init' ) ); add_action( 'wp_loaded', array( $this, 'get_errors' ), 20 ); add_filter( 'query_vars', array( $this, 'add_query_vars'), 0 ); add_action( 'parse_request', array( $this, 'parse_request'), 0 ); add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) ); - add_filter( 'the_posts', array( $this, 'the_posts' ), 11, 2 ); add_action( 'wp', array( $this, 'remove_product_query' ) ); add_action( 'wp', array( $this, 'remove_ordering_args' ) ); } - $this->init_query_vars(); } @@ -155,7 +129,6 @@ class WC_Query { foreach ( $this->query_vars as $key => $var ) { $vars[] = $key; } - return $vars; } @@ -301,9 +274,6 @@ class WC_Query { add_filter( 'wp', array( $this, 'remove_posts_where' ) ); } - // We're on a shop page so queue the woocommerce_get_products_in_view function - add_action( 'wp', array( $this, 'get_products_in_view' ), 2); - // And remove the pre_get_posts hook $this->remove_product_query(); } @@ -338,7 +308,7 @@ class WC_Query { * @return string */ public function wpseo_metadesc() { - return WPSEO_Meta::get_value( 'metadesc', wc_get_page_id('shop') ); + return WPSEO_Meta::get_value( 'metadesc', wc_get_page_id( 'shop' ) ); } /** @@ -350,80 +320,16 @@ class WC_Query { * @return string */ public function wpseo_metakey() { - return WPSEO_Meta::get_value( 'metakey', wc_get_page_id('shop') ); + return WPSEO_Meta::get_value( 'metakey', wc_get_page_id( 'shop' ) ); } - /** - * Hook into the_posts to do the main product query if needed - relevanssi compatibility. - * - * @access public - * @param array $posts - * @param WP_Query|bool $query (default: false) - * @return array - */ - public function the_posts( $posts, $query = false ) { - // Abort if there's no query - if ( ! $query ) - return $posts; - - // Abort if we're not filtering posts - if ( empty( $this->post__in ) ) - return $posts; - - // Abort if this query has already been done - if ( ! empty( $query->wc_query ) ) - return $posts; - - // Abort if this isn't a search query - if ( empty( $query->query_vars["s"] ) ) - return $posts; - - // Abort if we're not on a post type archive/product taxonomy - if ( ! $query->is_post_type_archive( 'product' ) && ! $query->is_tax( get_object_taxonomies( 'product' ) ) ) - return $posts; - - $filtered_posts = array(); - $queried_post_ids = array(); - - foreach ( $posts as $post ) { - if ( in_array( $post->ID, $this->post__in ) ) { - $filtered_posts[] = $post; - $queried_post_ids[] = $post->ID; - } - } - - $query->posts = $filtered_posts; - $query->post_count = count( $filtered_posts ); - - // Ensure filters are set - $this->unfiltered_product_ids = $queried_post_ids; - $this->filtered_product_ids = $queried_post_ids; - - if ( sizeof( $this->layered_nav_post__in ) > 0 ) { - $this->layered_nav_product_ids = array_intersect( $this->unfiltered_product_ids, $this->layered_nav_post__in ); - } else { - $this->layered_nav_product_ids = $this->unfiltered_product_ids; - } - - return $filtered_posts; - } - - /** * Query the products, applying sorting/ordering etc. This applies to the main wordpress loop. * * @param mixed $q */ public function product_query( $q ) { - - // Meta query - $meta_query = $this->get_meta_query( $q->get( 'meta_query' ) ); - - // Ordering - $ordering = $this->get_catalog_ordering_args(); - - // Get a list of post id's which match the current filters set (in the layered nav and price filter) - $post__in = array_unique( apply_filters( 'loop_shop_post_in', array() ) ); + $ordering = $this->get_catalog_ordering_args(); // Ordering query vars $q->set( 'orderby', $ordering['orderby'] ); @@ -433,16 +339,15 @@ class WC_Query { } // Query vars that affect posts shown - $q->set( 'meta_query', $meta_query ); - $q->set( 'post__in', $post__in ); + $q->set( 'meta_query', $this->get_meta_query( $q->get( 'meta_query' ) ) ); + $q->set( 'tax_query', $this->get_tax_query( $q->get( 'tax_query' ) ) ); $q->set( 'posts_per_page', $q->get( 'posts_per_page' ) ? $q->get( 'posts_per_page' ) : apply_filters( 'loop_shop_per_page', get_option( 'posts_per_page' ) ) ); // Set a special variable $q->set( 'wc_query', 'product_query' ); - // Store variables - $this->post__in = $post__in; - $this->meta_query = $meta_query; + // Filterable post__in + $q->set( 'post__in', array_unique( apply_filters( 'loop_shop_post_in', array() ) ) ); do_action( 'woocommerce_product_query', $q, $this ); } @@ -470,66 +375,6 @@ class WC_Query { remove_filter( 'posts_where', array( $this, 'search_post_excerpt' ) ); } - - /** - * Get an unpaginated list all product ID's (both filtered and unfiltered). Makes use of transients. - */ - public function get_products_in_view() { - global $wp_the_query; - - // Get main query - $current_wp_query = $wp_the_query->query; - - // Get WP Query for current page (without 'paged') - unset( $current_wp_query['paged'] ); - - // Generate a transient name based on current query - $transient_name = 'wc_uf_pid_' . md5( http_build_query( $current_wp_query ) . WC_Cache_Helper::get_transient_version( 'product_query' ) ); - $transient_name = ( is_search() ) ? $transient_name . '_s' : $transient_name; - - if ( false === ( $unfiltered_product_ids = get_transient( $transient_name ) ) ) { - - // Get all visible posts, regardless of filters - $unfiltered_args = array_merge( - $current_wp_query, - array( - 'post_type' => 'product', - 'numberposts' => -1, - 'post_status' => 'publish', - 'meta_query' => $this->meta_query, - 'fields' => 'ids', - 'no_found_rows' => true, - 'update_post_meta_cache' => false, - 'update_post_term_cache' => false, - 'pagename' => '', - 'wc_query' => 'get_products_in_view' - ) - ); - - $unfiltered_product_ids = apply_filters( 'woocommerce_unfiltered_product_ids', get_posts( $unfiltered_args ), $unfiltered_args ); - - set_transient( $transient_name, $unfiltered_product_ids, DAY_IN_SECONDS * 30 ); - } - - // Store the variable - $this->unfiltered_product_ids = $unfiltered_product_ids; - - // Also store filtered posts ids... - if ( sizeof( $this->post__in ) > 0 ) { - $this->filtered_product_ids = array_intersect( $this->unfiltered_product_ids, $this->post__in ); - } else { - $this->filtered_product_ids = $this->unfiltered_product_ids; - } - - // And filtered post ids which just take layered nav into consideration (to find max price in the price widget) - if ( sizeof( $this->layered_nav_post__in ) > 0 ) { - $this->layered_nav_product_ids = array_intersect( $this->unfiltered_product_ids, $this->layered_nav_post__in ); - } else { - $this->layered_nav_product_ids = $this->unfiltered_product_ids; - } - } - - /** * Returns an array of arguments for ordering products based on the selected values. * @@ -601,9 +446,7 @@ class WC_Query { */ public function order_by_popularity_post_clauses( $args ) { global $wpdb; - $args['orderby'] = "$wpdb->postmeta.meta_value+0 DESC, $wpdb->posts.post_date DESC"; - return $args; } @@ -640,13 +483,71 @@ class WC_Query { * @return array */ public function get_meta_query( $meta_query = array() ) { - if ( ! is_array( $meta_query ) ) + if ( ! is_array( $meta_query ) ) { $meta_query = array(); + } $meta_query[] = $this->visibility_meta_query(); $meta_query[] = $this->stock_status_meta_query(); + $meta_query[] = $this->price_filter_meta_query(); + $meta_query[] = $this->rating_filter_meta_query(); - return array_filter( $meta_query ); + return array_filter( apply_filters( 'woocommerce_product_query_meta_query', $meta_query, $this ) ); + } + + /** + * Return a meta query for filtering by price. + * @return array + */ + private function price_filter_meta_query() { + if ( isset( $_GET['max_price'] ) || isset( $_GET['min_price'] ) ) { + $min = isset( $_GET['min_price'] ) ? floatval( $_GET['min_price'] ) : 0; + $max = isset( $_GET['max_price'] ) ? floatval( $_GET['max_price'] ) : 9999999999; + + // If displaying prices in the shop including taxes, but prices don't include taxes.. + if ( wc_tax_enabled() && 'incl' === get_option( 'woocommerce_tax_display_shop' ) && ! wc_prices_include_tax() ) { + $tax_classes = array_merge( array( '' ), WC_Tax::get_tax_classes() ); + + foreach ( $tax_classes as $tax_class ) { + $tax_rates = WC_Tax::get_rates( $tax_class ); + $class_min = $min - WC_Tax::get_tax_total( WC_Tax::calc_inclusive_tax( $min, $tax_rates ) ); + $class_max = $max - WC_Tax::get_tax_total( WC_Tax::calc_inclusive_tax( $max, $tax_rates ) ); + if ( $class_min < $min ) { + $min = $class_min; + } + if ( $class_max > $max ) { + $max = $class_max; + } + } + } + + return array( + 'key' => '_price', + 'value' => array( $min, $max ), + 'compare' => 'BETWEEN', + 'type' => 'DECIMAL', + 'price_filter' => true + ); + } + return array(); + } + + /** + * Return a meta query for filtering by rating. + * @return array + */ + public function rating_filter_meta_query() { + if ( isset( $_GET['min_rating'] ) ) { + $min = isset( $_GET['min_rating'] ) ? floatval( $_GET['min_rating'] ) : 0; + return array( + 'key' => '_wc_average_rating', + 'value' => $min, + 'compare' => '>=', + 'type' => 'DECIMAL', + 'rating_filter' => true + ); + } + return array(); } /** @@ -657,18 +558,11 @@ class WC_Query { * @return array */ public function visibility_meta_query( $compare = 'IN' ) { - if ( is_search() ) - $in = array( 'visible', 'search' ); - else - $in = array( 'visible', 'catalog' ); - - $meta_query = array( + return array( 'key' => '_visibility', - 'value' => $in, + 'value' => is_search() ? array( 'visible', 'search' ) : array( 'visible', 'catalog' ), 'compare' => $compare ); - - return $meta_query; } /** @@ -679,15 +573,38 @@ class WC_Query { * @return array */ public function stock_status_meta_query( $status = 'instock' ) { - $meta_query = array(); - if ( get_option( 'woocommerce_hide_out_of_stock_items' ) == 'yes' ) { - $meta_query = array( - 'key' => '_stock_status', - 'value' => $status, - 'compare' => '=' - ); + return 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ? array( + 'key' => '_stock_status', + 'value' => $status, + 'compare' => '=' + ) : array(); + } + + /** + * Appends tax queries to an array. + * @param array $tax_query + * @return array + */ + public function get_tax_query( $tax_query = array() ) { + global $_chosen_attributes; + + if ( ! is_array( $tax_query ) ) { + $tax_query = array(); } - return $meta_query; + + // Layered nav filters on terms + if ( $_chosen_attributes ) { + foreach ( $_chosen_attributes as $taxonomy => $data ) { + $tax_query[] = array( + 'taxonomy' => $taxonomy, + 'field' => 'slug', + 'terms' => $data['terms'], + 'operator' => 'and' === $data['query_type'] ? 'AND' : 'IN' + ); + } + } + + return array_filter( apply_filters( 'woocommerce_product_query_tax_query', $tax_query, $this ) ); } /** @@ -695,7 +612,6 @@ class WC_Query { */ public function layered_nav_init( ) { if ( apply_filters( 'woocommerce_is_layered_nav_active', is_active_widget( false, false, 'woocommerce_layered_nav', true ) ) && ! is_admin() ) { - global $_chosen_attributes; $_chosen_attributes = array(); @@ -718,260 +634,24 @@ class WC_Query { } } } - - add_filter( 'loop_shop_post_in', array( $this, 'layered_nav_query' ) ); } } + /** + * Get an unpaginated list all product ID's (both filtered and unfiltered). Makes use of transients. + * @deprecated 2.6.0 due to performance concerns + */ + public function get_products_in_view() { + _deprecated_function( 'get_products_in_view', '2.6', '' ); + } + /** * Layered Nav post filter. - * - * @param array $filtered_posts - * @return array + * @deprecated 2.6.0 due to performance concerns */ public function layered_nav_query( $filtered_posts ) { - global $_chosen_attributes; - - if ( sizeof( $_chosen_attributes ) > 0 ) { - - $matched_products = array( - 'and' => array(), - 'or' => array() - ); - $filtered_attribute = array( - 'and' => false, - 'or' => false - ); - - foreach ( $_chosen_attributes as $attribute => $data ) { - $matched_products_from_attribute = array(); - $filtered = false; - - if ( sizeof( $data['terms'] ) > 0 ) { - foreach ( $data['terms'] as $value ) { - - $args = array( - 'post_type' => 'product', - 'numberposts' => -1, - 'post_status' => 'publish', - 'fields' => 'ids', - 'no_found_rows' => true, - 'tax_query' => array( - array( - 'taxonomy' => $attribute, - 'terms' => $value, - 'field' => 'slug' - ) - ) - ); - - $post_ids = apply_filters( 'woocommerce_layered_nav_query_post_ids', get_posts( $args ), $args, $attribute, $value ); - - if ( ! is_wp_error( $post_ids ) ) { - - if ( sizeof( $matched_products_from_attribute ) > 0 || $filtered ) { - $matched_products_from_attribute = $data['query_type'] == 'or' ? array_merge( $post_ids, $matched_products_from_attribute ) : array_intersect( $post_ids, $matched_products_from_attribute ); - } else { - $matched_products_from_attribute = $post_ids; - } - - $filtered = true; - } - } - } - - if ( sizeof( $matched_products[ $data['query_type'] ] ) > 0 || $filtered_attribute[ $data['query_type'] ] === true ) { - $matched_products[ $data['query_type'] ] = ( $data['query_type'] == 'or' ) ? array_merge( $matched_products_from_attribute, $matched_products[ $data['query_type'] ] ) : array_intersect( $matched_products_from_attribute, $matched_products[ $data['query_type'] ] ); - } else { - $matched_products[ $data['query_type'] ] = $matched_products_from_attribute; - } - - $filtered_attribute[ $data['query_type'] ] = true; - - $this->filtered_product_ids_for_taxonomy[ $attribute ] = $matched_products_from_attribute; - } - - // Combine our AND and OR result sets - if ( $filtered_attribute['and'] && $filtered_attribute['or'] ) - $results = array_intersect( $matched_products[ 'and' ], $matched_products[ 'or' ] ); - else - $results = array_merge( $matched_products[ 'and' ], $matched_products[ 'or' ] ); - - if ( $filtered ) { - - WC()->query->layered_nav_post__in = $results; - WC()->query->layered_nav_post__in[] = 0; - - if ( sizeof( $filtered_posts ) == 0 ) { - $filtered_posts = $results; - $filtered_posts[] = 0; - } else { - $filtered_posts = array_intersect( $filtered_posts, $results ); - $filtered_posts[] = 0; - } - - } - } - return (array) $filtered_posts; + _deprecated_function( 'layered_nav_query', '2.6', '' ); } - - /** - * Price filter Init. - */ - public function price_filter_init() { - if ( apply_filters( 'woocommerce_is_price_filter_active', is_active_widget( false, false, 'woocommerce_price_filter', true ) ) && ! is_admin() ) { - - $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; - - wp_register_script( 'wc-jquery-ui-touchpunch', WC()->plugin_url() . '/assets/js/jquery-ui-touch-punch/jquery-ui-touch-punch' . $suffix . '.js', array( 'jquery-ui-slider' ), WC_VERSION, true ); - wp_register_script( 'wc-price-slider', WC()->plugin_url() . '/assets/js/frontend/price-slider' . $suffix . '.js', array( 'jquery-ui-slider', 'wc-jquery-ui-touchpunch' ), WC_VERSION, true ); - - wp_localize_script( 'wc-price-slider', 'woocommerce_price_slider_params', array( - 'currency_symbol' => get_woocommerce_currency_symbol(), - 'currency_pos' => get_option( 'woocommerce_currency_pos' ), - 'min_price' => isset( $_GET['min_price'] ) ? esc_attr( $_GET['min_price'] ) : '', - 'max_price' => isset( $_GET['max_price'] ) ? esc_attr( $_GET['max_price'] ) : '' - ) ); - - add_filter( 'loop_shop_post_in', array( $this, 'price_filter' ) ); - } - } - - /** - * Price Filter post filter. - * - * @param array $filtered_posts - * @return array - */ - public function price_filter( $filtered_posts = array() ) { - global $wpdb; - - if ( isset( $_GET['max_price'] ) || isset( $_GET['min_price'] ) ) { - - $matched_products = array(); - $min = isset( $_GET['min_price'] ) ? floatval( $_GET['min_price'] ) : 0; - $max = isset( $_GET['max_price'] ) ? floatval( $_GET['max_price'] ) : 9999999999; - - // If displaying prices in the shop including taxes, but prices don't include taxes.. - if ( wc_tax_enabled() && 'incl' === get_option( 'woocommerce_tax_display_shop' ) && ! wc_prices_include_tax() ) { - $tax_classes = array_merge( array( '' ), WC_Tax::get_tax_classes() ); - - foreach ( $tax_classes as $tax_class ) { - $tax_rates = WC_Tax::get_rates( $tax_class ); - $min_class = $min - WC_Tax::get_tax_total( WC_Tax::calc_inclusive_tax( $min, $tax_rates ) ); - $max_class = $max - WC_Tax::get_tax_total( WC_Tax::calc_inclusive_tax( $max, $tax_rates ) ); - - $matched_products_query = apply_filters( 'woocommerce_price_filter_results', $wpdb->get_results( $wpdb->prepare( " - SELECT DISTINCT ID, post_parent, post_type FROM {$wpdb->posts} - INNER JOIN {$wpdb->postmeta} pm1 ON ID = pm1.post_id - INNER JOIN {$wpdb->postmeta} pm2 ON ID = pm2.post_id - WHERE post_type IN ( 'product', 'product_variation' ) - AND post_status = 'publish' - AND pm1.meta_key IN ('" . implode( "','", array_map( 'esc_sql', apply_filters( 'woocommerce_price_filter_meta_keys', array( '_price' ) ) ) ) . "') - AND pm1.meta_value BETWEEN %f AND %f - AND pm2.meta_key = '_tax_class' - AND pm2.meta_value = %s - ", $min_class, $max_class, sanitize_title( $tax_class ) ), OBJECT_K ), $min_class, $max_class ); - - if ( $matched_products_query ) { - foreach ( $matched_products_query as $product ) { - if ( $product->post_type == 'product' ) { - $matched_products[] = $product->ID; - } - if ( $product->post_parent > 0 ) { - $matched_products[] = $product->post_parent; - } - } - } - } - } else { - $matched_products_query = apply_filters( 'woocommerce_price_filter_results', $wpdb->get_results( $wpdb->prepare( " - SELECT DISTINCT ID, post_parent, post_type FROM {$wpdb->posts} - INNER JOIN {$wpdb->postmeta} pm1 ON ID = pm1.post_id - WHERE post_type IN ( 'product', 'product_variation' ) - AND post_status = 'publish' - AND pm1.meta_key IN ('" . implode( "','", array_map( 'esc_sql', apply_filters( 'woocommerce_price_filter_meta_keys', array( '_price' ) ) ) ) . "') - AND pm1.meta_value BETWEEN %f AND %f - ", $min, $max ), OBJECT_K ), $min, $max ); - - if ( $matched_products_query ) { - foreach ( $matched_products_query as $product ) { - if ( $product->post_type == 'product' ) { - $matched_products[] = $product->ID; - } - if ( $product->post_parent > 0 ) { - $matched_products[] = $product->post_parent; - } - } - } - } - - $matched_products = array_unique( $matched_products ); - - // Filter the id's - if ( 0 === sizeof( $filtered_posts ) ) { - $filtered_posts = $matched_products; - } else { - $filtered_posts = array_intersect( $filtered_posts, $matched_products ); - } - $filtered_posts[] = 0; - } - - return (array) $filtered_posts; - } - - /** - * Rating filter Init. - */ - public function rating_filter_init() { - if ( apply_filters( 'woocommerce_is_rating_filter_active', is_active_widget( false, false, 'woocommerce_rating_filter', true ) ) && ! is_admin() ) { - add_filter( 'loop_shop_post_in', array( $this, 'rating_filter' ) ); - } - } - - /** - * Rating Filter post filter. - * - * @param array $filtered_posts - * @return array - */ - public function rating_filter( $filtered_posts = array() ) { - global $wpdb; - - if( isset( $_GET['min_rating'] ) ) { - $matched_products = array(); - $min = isset( $_GET['min_rating'] ) ? floatval( $_GET['min_rating'] ) : 0; - - $matched_products_query = apply_filters( 'woocommerce_rating_filter_results', $wpdb->get_results( $wpdb->prepare( " - SELECT comment_post_ID, ROUND( AVG( meta_value ), 2 ) as average_rating FROM {$wpdb->commentmeta} - LEFT JOIN $wpdb->comments ON $wpdb->commentmeta.comment_id = $wpdb->comments.comment_ID - WHERE meta_key = 'rating' - AND comment_approved = '1' - AND meta_value > 0 - GROUP BY comment_post_ID - HAVING ROUND( AVG( meta_value ), 2 ) >= %d - ", $min ), OBJECT_K ), $min ); - - if ( $matched_products_query ) { - foreach ( $matched_products_query as $commentmeta ) { - $matched_products[] = $commentmeta->comment_post_ID; - } - } - - $matched_products = array_unique( $matched_products ); - - // Filter the id's - if ( 0 === sizeof( $filtered_posts ) ) { - $filtered_posts = $matched_products; - } else { - $filtered_posts = array_intersect( $filtered_posts, $matched_products ); - } - $filtered_posts[] = 0; - } - - return (array) $filtered_posts; - } - } endif; diff --git a/includes/widgets/class-wc-widget-layered-nav.php b/includes/widgets/class-wc-widget-layered-nav.php index 569e7bf50ab..a160d624e2d 100644 --- a/includes/widgets/class-wc-widget-layered-nav.php +++ b/includes/widgets/class-wc-widget-layered-nav.php @@ -229,7 +229,7 @@ class WC_Widget_Layered_Nav extends WC_Widget { // If this is an AND query, only show options with count > 0 if ( 'and' === $query_type ) { - $count = sizeof( array_intersect( $_products_in_term, WC()->query->filtered_product_ids ) ); + $count = $this->get_filtered_term_count( $term, $taxonomy, $query_type ); if ( 0 < $count ) { $found = true; @@ -241,7 +241,7 @@ class WC_Widget_Layered_Nav extends WC_Widget { // If this is an OR query, show all options so search can be expanded } else { - $count = sizeof( array_intersect( $_products_in_term, WC()->query->unfiltered_product_ids ) ); + $count = $this->get_filtered_term_count( $term, $taxonomy, $query_type ); if ( 0 < $count ) { $found = true; @@ -309,6 +309,55 @@ class WC_Widget_Layered_Nav extends WC_Widget { return $link; } + /** + * Count terms after other filters have occured by adjusting the main query. + * @param object $term + * @param string $taxonomy + * @param string $query_type + * @return int + */ + protected function get_filtered_term_count( $term, $taxonomy, $query_type ) { + global $wpdb, $wp_the_query; + + $args = $wp_the_query->query_vars; + $tax_query = isset( $args['tax_query'] ) ? $args['tax_query'] : array(); + $meta_query = isset( $args['meta_query'] ) ? $args['meta_query'] : array(); + + if ( ! empty( $args['taxonomy'] ) && ! empty( $args['term'] ) ) { + $tax_query[] = array( + 'taxonomy' => $args['taxonomy'], + 'terms' => array( $args['term'] ), + 'field' => 'slug', + ); + } + + if ( 'or' === $query_type ) { + foreach ( $tax_query as $key => $query ) { + if ( $taxonomy === $query['taxonomy'] ) { + unset( $tax_query[ $key ] ); + } + } + } + $tax_query[] = array( + 'taxonomy' => $taxonomy, + 'field' => 'slug', + 'terms' => $term->slug + ); + + $meta_query = new WP_Meta_Query( $meta_query ); + $tax_query = new WP_Tax_Query( $tax_query ); + + $meta_query_sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' ); + $tax_query_sql = $tax_query->get_sql( $wpdb->posts, 'ID' ); + + $sql = "SELECT COUNT( {$wpdb->posts}.ID ) FROM {$wpdb->posts} "; + $sql .= $tax_query_sql['join'] . $meta_query_sql['join']; + $sql .= " WHERE {$wpdb->posts}.post_type = 'product' AND {$wpdb->posts}.post_status = 'publish' "; + $sql .= $tax_query_sql['where'] . $meta_query_sql['where']; + + return absint( $wpdb->get_var( $sql ) ); + } + /** * Show list based layered nav. * @param array $terms @@ -322,10 +371,7 @@ class WC_Widget_Layered_Nav extends WC_Widget { // List display echo '
    '; - // flip the filtered_products_ids array so that we can use the more efficient array_intersect_key - $filtered_product_ids = array_flip( WC()->query->filtered_product_ids ); - $unfiltered_product_ids = array_flip( WC()->query->unfiltered_product_ids ); - $found = false; + $found = false; foreach ( $terms as $term ) { // Get count based on current view - uses transients @@ -341,8 +387,7 @@ class WC_Widget_Layered_Nav extends WC_Widget { // If this is an AND query, only show options with count > 0 if ( 'and' === $query_type ) { - // Intersect both arrays now they have been flipped so that we can use their keys - $count = sizeof( array_intersect_key( $_products_in_term, $filtered_product_ids ) ); + $count = $this->get_filtered_term_count( $term, $taxonomy, $query_type ); if ( 0 < $count ) { $found = true; @@ -354,8 +399,7 @@ class WC_Widget_Layered_Nav extends WC_Widget { // If this is an OR query, show all options so search can be expanded } else { - // Intersect both arrays now they have been flipped so that we can use their keys - $count = sizeof( array_intersect_key( $_products_in_term, $unfiltered_product_ids ) ); + $count = $this->get_filtered_term_count( $term, $taxonomy, $query_type ); if ( 0 < $count ) { $found = true; diff --git a/includes/widgets/class-wc-widget-price-filter.php b/includes/widgets/class-wc-widget-price-filter.php index 6734d001101..5955ea0a2a2 100644 --- a/includes/widgets/class-wc-widget-price-filter.php +++ b/includes/widgets/class-wc-widget-price-filter.php @@ -32,7 +32,15 @@ class WC_Widget_Price_Filter extends WC_Widget { 'label' => __( 'Title', 'woocommerce' ) ) ); - + $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + wp_register_script( 'wc-jquery-ui-touchpunch', WC()->plugin_url() . '/assets/js/jquery-ui-touch-punch/jquery-ui-touch-punch' . $suffix . '.js', array( 'jquery-ui-slider' ), WC_VERSION, true ); + wp_register_script( 'wc-price-slider', WC()->plugin_url() . '/assets/js/frontend/price-slider' . $suffix . '.js', array( 'jquery-ui-slider', 'wc-jquery-ui-touchpunch' ), WC_VERSION, true ); + wp_localize_script( 'wc-price-slider', 'woocommerce_price_slider_params', array( + 'currency_symbol' => get_woocommerce_currency_symbol(), + 'currency_pos' => get_option( 'woocommerce_currency_pos' ), + 'min_price' => isset( $_GET['min_price'] ) ? esc_attr( $_GET['min_price'] ) : '', + 'max_price' => isset( $_GET['max_price'] ) ? esc_attr( $_GET['max_price'] ) : '' + ) ); parent::__construct(); } @@ -45,14 +53,14 @@ class WC_Widget_Price_Filter extends WC_Widget { * @param array $instance */ public function widget( $args, $instance ) { - global $_chosen_attributes, $wpdb, $wp; + global $_chosen_attributes, $wpdb, $wp, $wp_the_query; if ( ! is_post_type_archive( 'product' ) && ! is_tax( get_object_taxonomies( 'product' ) ) ) { return; } - if ( sizeof( WC()->query->unfiltered_product_ids ) == 0 ) { - return; // None shown - return + if ( ! $wp_the_query->post_count ) { + return; } $min_price = isset( $_GET['min_price'] ) ? esc_attr( $_GET['min_price'] ) : ''; @@ -99,51 +107,11 @@ class WC_Widget_Price_Filter extends WC_Widget { } } - if ( 0 === sizeof( WC()->query->layered_nav_product_ids ) ) { - $min = floor( $wpdb->get_var( " - SELECT min(meta_value + 0) - FROM {$wpdb->posts} as posts - LEFT JOIN {$wpdb->postmeta} as postmeta ON posts.ID = postmeta.post_id - WHERE meta_key IN ('" . implode( "','", array_map( 'esc_sql', apply_filters( 'woocommerce_price_filter_meta_keys', array( '_price', '_min_variation_price' ) ) ) ) . "') - AND meta_value != '' - " ) ); - $max = ceil( $wpdb->get_var( " - SELECT max(meta_value + 0) - FROM {$wpdb->posts} as posts - LEFT JOIN {$wpdb->postmeta} as postmeta ON posts.ID = postmeta.post_id - WHERE meta_key IN ('" . implode( "','", array_map( 'esc_sql', apply_filters( 'woocommerce_price_filter_meta_keys', array( '_price' ) ) ) ) . "') - " ) ); - } else { - $min = floor( $wpdb->get_var( " - SELECT min(meta_value + 0) - FROM {$wpdb->posts} as posts - LEFT JOIN {$wpdb->postmeta} as postmeta ON posts.ID = postmeta.post_id - WHERE meta_key IN ('" . implode( "','", array_map( 'esc_sql', apply_filters( 'woocommerce_price_filter_meta_keys', array( '_price', '_min_variation_price' ) ) ) ) . "') - AND meta_value != '' - AND ( - posts.ID IN (" . implode( ',', array_map( 'absint', WC()->query->layered_nav_product_ids ) ) . ") - OR ( - posts.post_parent IN (" . implode( ',', array_map( 'absint', WC()->query->layered_nav_product_ids ) ) . ") - AND posts.post_parent != 0 - ) - ) - " ) ); - $max = ceil( $wpdb->get_var( " - SELECT max(meta_value + 0) - FROM {$wpdb->posts} as posts - LEFT JOIN {$wpdb->postmeta} as postmeta ON posts.ID = postmeta.post_id - WHERE meta_key IN ('" . implode( "','", array_map( 'esc_sql', apply_filters( 'woocommerce_price_filter_meta_keys', array( '_price' ) ) ) ) . "') - AND ( - posts.ID IN (" . implode( ',', array_map( 'absint', WC()->query->layered_nav_product_ids ) ) . ") - OR ( - posts.post_parent IN (" . implode( ',', array_map( 'absint', WC()->query->layered_nav_product_ids ) ) . ") - AND posts.post_parent != 0 - ) - ) - " ) ); - } + // Find min and max price in current result set + $min = $this->get_filtered_min_price(); + $max = $this->get_filtered_max_price(); - if ( $min == $max ) { + if ( $min === $max ) { return; } @@ -155,16 +123,15 @@ class WC_Widget_Price_Filter extends WC_Widget { $form_action = preg_replace( '%\/page/[0-9]+%', '', home_url( trailingslashit( $wp->request ) ) ); } + // Adjust min and max if the store taxes are not displayed how they are stored if ( wc_tax_enabled() && 'incl' === get_option( 'woocommerce_tax_display_shop' ) && ! wc_prices_include_tax() ) { $tax_classes = array_merge( array( '' ), WC_Tax::get_tax_classes() ); - $min = 0; foreach ( $tax_classes as $tax_class ) { $tax_rates = WC_Tax::get_rates( $tax_class ); $class_min = $min + WC_Tax::get_tax_total( WC_Tax::calc_exclusive_tax( $min, $tax_rates ) ); $class_max = $max + WC_Tax::get_tax_total( WC_Tax::calc_exclusive_tax( $max, $tax_rates ) ); - - if ( $min === 0 || $class_min < $min ) { + if ( $class_min < $min ) { $min = $class_min; } if ( $class_max > $max ) { @@ -177,7 +144,7 @@ class WC_Widget_Price_Filter extends WC_Widget {
    - +