Commit fbf347ca795 for woocommerce
commit fbf347ca795714edbe4b3d62e63ad762aa8bdf6d
Author: Luigi Teschio <gigitux@gmail.com>
Date: Thu Oct 29 10:08:11 2026 +0100
Improve Product Filters cache consistency (#70354)
* Improve Product Filters cache consistency
* Add changelog entry for Product Filters consistency
diff --git a/plugins/woocommerce/changelog/fix-product-filter-visibility b/plugins/woocommerce/changelog/fix-product-filter-visibility
new file mode 100644
index 00000000000..afd8e54d027
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-product-filter-visibility
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Improve the consistency of Product Filters prices and counts across user roles.
diff --git a/plugins/woocommerce/src/Internal/ProductFilters/FilterData.php b/plugins/woocommerce/src/Internal/ProductFilters/FilterData.php
index ab83992b1f8..3a51f1f9dbb 100644
--- a/plugins/woocommerce/src/Internal/ProductFilters/FilterData.php
+++ b/plugins/woocommerce/src/Internal/ProductFilters/FilterData.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Automattic\WooCommerce\Internal\ProductFilters;
+use Automattic\WooCommerce\Enums\ProductStatus;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;
use Automattic\WooCommerce\Internal\ProductFilters\Interfaces\QueryClausesGenerator;
use Automattic\WooCommerce\Internal\ProductFilters\TaxonomyHierarchyData;
@@ -58,6 +59,8 @@ class FilterData {
* @return object
*/
public function get_filtered_price( array $query_vars ) {
+ $query_vars = $this->get_query_vars_with_default_post_status( $query_vars );
+
/**
* Allows offloading the filter data to external services like Elasticsearch.
*
@@ -133,6 +136,8 @@ class FilterData {
* @return array status=>count pairs.
*/
public function get_stock_status_counts( array $query_vars, array $statuses ) {
+ $query_vars = $this->get_query_vars_with_default_post_status( $query_vars );
+
/**
* Filter the data. @see get_filtered_price() for full documentation.
*/
@@ -198,6 +203,8 @@ class FilterData {
* @return array rating=>count pairs.
*/
public function get_rating_counts( array $query_vars ) {
+ $query_vars = $this->get_query_vars_with_default_post_status( $query_vars );
+
/**
* Filter the data. @see get_filtered_price() for full documentation.
*/
@@ -258,6 +265,8 @@ class FilterData {
* @return array termId=>count pairs.
*/
public function get_attribute_counts( array $query_vars, string $attribute_to_count ) {
+ $query_vars = $this->get_query_vars_with_default_post_status( $query_vars );
+
/**
* Filter the data. @see get_filtered_price() for full documentation.
*/
@@ -345,6 +354,8 @@ class FilterData {
* @return array termId=>count pairs.
*/
public function get_taxonomy_counts( array $query_vars, string $taxonomy_to_count ) {
+ $query_vars = $this->get_query_vars_with_default_post_status( $query_vars );
+
/**
* Filter the data. @see get_filtered_price() for full documentation.
*
@@ -502,6 +513,24 @@ class FilterData {
return $final_counts;
}
+ /**
+ * Make default visibility explicit before generating SQL or shared cache keys.
+ *
+ * @param array $query_vars The WP_Query arguments.
+ * @return array Query arguments with an explicit post status.
+ */
+ private function get_query_vars_with_default_post_status( array $query_vars ): array {
+ if ( empty( $query_vars['post_status'] ) ) {
+ $post_statuses = get_post_stati( array( 'public' => true ) );
+ if ( current_user_can( 'read_private_products' ) ) {
+ $post_statuses = array_merge( $post_statuses, get_post_stati( array( 'private' => true ) ) );
+ }
+ $query_vars['post_status'] = array_values( $post_statuses );
+ }
+
+ return $query_vars;
+ }
+
/**
* Get filter data transient key.
*
diff --git a/plugins/woocommerce/tests/php/src/Internal/ProductFilters/FilterDataTest.php b/plugins/woocommerce/tests/php/src/Internal/ProductFilters/FilterDataTest.php
index b5fd9fdd9f7..8170f597281 100644
--- a/plugins/woocommerce/tests/php/src/Internal/ProductFilters/FilterDataTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/ProductFilters/FilterDataTest.php
@@ -2,6 +2,8 @@
namespace Automattic\WooCommerce\Tests\Internal\ProductFilters;
+use Automattic\WooCommerce\Enums\ProductStatus;
+use Automattic\WooCommerce\Enums\ProductStockStatus;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;
use Automattic\WooCommerce\Internal\ProductFilters\FilterDataProvider;
use Automattic\WooCommerce\Internal\ProductFilters\QueryClauses;
@@ -91,6 +93,36 @@ class FilterDataTest extends AbstractProductFiltersTest {
$this->test_get_filtered_price_with( $wp_query );
}
+ /**
+ * @testdox Product filter caches reflect the current user's product visibility.
+ */
+ public function test_filter_cache_separates_staff_and_visitors(): void {
+ $private = \WC_Helper_Product::create_simple_product(
+ true,
+ array(
+ 'status' => ProductStatus::PRIVATE,
+ 'regular_price' => 4321,
+ 'price' => 4321,
+ )
+ );
+ $query_vars = array(
+ 'post_type' => 'product',
+ 'post__in' => array( $this->products[1]->get_id(), $private->get_id() ),
+ );
+ $manager = self::factory()->user->create( array( 'role' => 'shop_manager' ) );
+ wp_set_current_user( $manager );
+ $price = $this->sut->get_filtered_price( $query_vars );
+ $this->assertSame( 4321.0, (float) $price['max_price'] );
+
+ wp_set_current_user( 0 );
+ $price = $this->sut->get_filtered_price( $query_vars );
+ $this->assertSame( 20.0, (float) $price['max_price'] );
+
+ // Stock has no cached counts yet, so this also checks the shared product-ID cache.
+ $stock = $this->sut->get_stock_status_counts( $query_vars, array( ProductStockStatus::IN_STOCK ) );
+ $this->assertSame( 1, $stock[ ProductStockStatus::IN_STOCK ] );
+ }
+
/**
* @testdox Changing the taxonomy URL map cannot reuse filter-data or product-ID cache entries.
*/