Commit fc9dc7cafa6 for woocommerce
commit fc9dc7cafa61f9911a459fe66d456d638f1642f3
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date: Wed Jun 24 15:54:07 2026 +0200
[Performance] Harden the conditions required to invoke ProductUtil::get_counts_for_type throughout the codebase (#65885)
Further groundwork for admin performance with large product catalogs: ensure \Automattic\WooCommerce\Internal\Utilities\ProductUtil::get_counts_for_type and wp_count_posts( 'product' ) are used only as fallbacks on the admin pages' hot path, with lighter upfront verifications.
diff --git a/plugins/woocommerce/changelog/performance-isolate-wp_count_posts-in-admin-take1 b/plugins/woocommerce/changelog/performance-isolate-wp_count_posts-in-admin-take1
new file mode 100644
index 00000000000..cc2ff146f9c
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-isolate-wp_count_posts-in-admin-take1
@@ -0,0 +1,4 @@
+Significance: patch
+Type: performance
+
+Harden the conditions required to invoke \Automattic\WooCommerce\Internal\Utilities\ProductUtil::get_counts_for_type throughout the codebase.
diff --git a/plugins/woocommerce/includes/admin/list-tables/abstract-class-wc-admin-list-table.php b/plugins/woocommerce/includes/admin/list-tables/abstract-class-wc-admin-list-table.php
index 6939d4b8ce0..7a7738c213f 100644
--- a/plugins/woocommerce/includes/admin/list-tables/abstract-class-wc-admin-list-table.php
+++ b/plugins/woocommerce/includes/admin/list-tables/abstract-class-wc-admin-list-table.php
@@ -59,12 +59,17 @@ abstract class WC_Admin_List_Table {
* @param string $which String which tablenav is being shown.
*/
public function maybe_render_blank_state( $which ) {
- global $post_type;
+ global $post_type, $wp_query;
if ( $post_type === $this->list_table_type && 'bottom' === $which ) {
+ // Performance note: this is a lightweight alternative to wp_count_posts that does not access the cache or apply filters.
+ if ( $wp_query && ! empty( $wp_query->posts ) ) {
+ return;
+ }
+
+ // Performance note: the results of wp_count_posts are cached and at this point populated by status counters.
$counts = (array) wp_count_posts( $post_type );
$count = array_sum( $counts ) - ( $counts['auto-draft'] ?? 0 );
-
if ( $count > 0 ) {
return;
}
diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php
index 2a66c13f9a8..ca28da6f03c 100644
--- a/plugins/woocommerce/includes/class-wc-install.php
+++ b/plugins/woocommerce/includes/class-wc-install.php
@@ -843,17 +843,31 @@ class WC_Install {
/**
* Is this a brand new WC install?
*
- * A brand new install has no version yet. Also treat empty installs as 'new'.
+ * A brand-new installation has no version yet. Also treat empty installations as 'new'.
+ *
+ * @since 11.0.0 returns false early for stores that are already live or have completed onboarding.
+ * @since 3.2.0
*
- * @since 3.2.0
* @return boolean
*/
public static function is_new_install() {
- return is_null( get_option( 'woocommerce_version', null ) )
- || (
- -1 === wc_get_page_id( 'shop' )
- && 0 === array_sum( wc_get_container()->get( ProductUtil::class )->get_counts_for_type( 'product' ) )
- );
+ // Performance note: woocommerce_version is absent before the very first install routine completes.
+ if ( false === get_option( 'woocommerce_version' ) ) {
+ return true;
+ }
+
+ // Performance note: verify if the store is live. This option is auto-loaded, and verification is essentially free.
+ if ( 'no' === get_option( 'woocommerce_coming_soon', 'yes' ) ) {
+ return false;
+ }
+
+ // Performance note: verify if onboarding is complete. This option is auto-loaded, and verification is essentially free.
+ if ( in_array( 'setup', (array) get_option( 'woocommerce_task_list_completed_lists', array() ), true ) ) {
+ return false;
+ }
+
+ // Performance note: this is the original fallback. The store setup is incomplete, and even with a cold cache, we do not anticipate performance issues.
+ return -1 === wc_get_page_id( 'shop' ) && 0 === array_sum( wc_get_container()->get( ProductUtil::class )->get_counts_for_type( 'product' ) );
}
/**
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index ffbbe1e8be3..d746c453800 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -46863,12 +46863,6 @@ parameters:
count: 1
path: src/Admin/Features/OnboardingTasks/Tasks/Products.php
- -
- message: '#^Parameter \#1 \$task_list of method Automattic\\WooCommerce\\Admin\\Features\\OnboardingTasks\\Task\:\:__construct\(\) expects Automattic\\WooCommerce\\Admin\\Features\\OnboardingTasks\\TaskList\|null, Automattic\\WooCommerce\\Admin\\Features\\OnboardingTasks\\Tasks\\TaskList given\.$#'
- identifier: argument.type
- count: 1
- path: src/Admin/Features/OnboardingTasks/Tasks/Products.php
-
-
message: '#^Parameter \#2 \$product of method Automattic\\WooCommerce\\Admin\\Features\\OnboardingTasks\\Tasks\\Products\:\:maybe_set_has_product_transient\(\) expects Automattic\\WooCommerce\\Admin\\Features\\OnboardingTasks\\Tasks\\WC_Product, WC_Product\|false\|null given\.$#'
identifier: argument.type
@@ -46887,12 +46881,6 @@ parameters:
count: 1
path: src/Admin/Features/OnboardingTasks/Tasks/Products.php
- -
- message: '#^Parameter \$task_list of method Automattic\\WooCommerce\\Admin\\Features\\OnboardingTasks\\Tasks\\Products\:\:__construct\(\) has invalid type Automattic\\WooCommerce\\Admin\\Features\\OnboardingTasks\\Tasks\\TaskList\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/Features/OnboardingTasks/Tasks/Products.php
-
-
message: '#^Call to an undefined method WC_Data_Store\:\:get_zones\(\)\.$#'
identifier: method.notFound
diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php
index a027773a7ae..5075f4d9e8c 100644
--- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php
+++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php
@@ -3,6 +3,7 @@
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
+use Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskList;
use Automattic\WooCommerce\Enums\ProductStatus;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
@@ -34,7 +35,10 @@ class Products extends Task {
add_action( 'woocommerce_update_product', array( $this, 'maybe_set_has_product_transient' ), 10, 2 );
add_action( 'woocommerce_new_product', array( $this, 'maybe_set_has_product_transient' ), 10, 2 );
add_action( 'untrashed_post', array( $this, 'maybe_set_has_product_transient_on_untrashed_post' ) );
- add_action( 'current_screen', array( $this, 'maybe_redirect_to_add_product_tasklist' ), 30, 0 );
+
+ if ( ! $this->is_complete() ) {
+ add_action( 'current_screen', array( $this, 'maybe_redirect_to_add_product_tasklist' ), 30, 0 );
+ }
add_action( 'trashed_post', array( $this, 'on_product_trashed' ) );
add_action( 'deleted_post_product', array( $this, 'on_product_deleted' ) );
@@ -345,6 +349,7 @@ class Products extends Task {
if ( $count > 0 ) {
return;
}
+
wp_safe_redirect( admin_url( 'admin.php?page=wc-admin&task=products' ) );
exit;
}
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-install-test.php b/plugins/woocommerce/tests/php/includes/class-wc-install-test.php
index b6ece8f455c..d005bd6009f 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-install-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-install-test.php
@@ -191,11 +191,13 @@ class WC_Install_Test extends \WC_Unit_Test_Case {
* @return void
*/
public function test_is_new_install(): void {
- // Determining if we are in a new install is based on the following three factors.
- $version = null;
- $shop_id = null;
- $post_count = 0;
- $counted_posts = false;
+ // Determining if we are in a new install is based on the following factors.
+ $version = false;
+ $shop_id = null;
+ $post_count = 0;
+ $counted_posts = false;
+ $coming_soon = 'yes';
+ $completed_lists = array();
$supply_version = function () use ( &$version ) {
return $version;
@@ -205,15 +207,25 @@ class WC_Install_Test extends \WC_Unit_Test_Case {
return $shop_id;
};
- $supply_post_count = function () use ( &$post_count ) {
+ $supply_post_count = function () use ( &$post_count, &$counted_posts ) {
$counted_posts = true;
return $post_count;
};
+ $supply_coming_soon = function () use ( &$coming_soon ) {
+ return $coming_soon;
+ };
+
+ $supply_completed_lists = function () use ( &$completed_lists ) {
+ return $completed_lists;
+ };
+
// Make it straightforward to test different values for our key variables.
add_filter( 'option_woocommerce_version', $supply_version );
add_filter( 'woocommerce_get_shop_page_id', $supply_shop_id );
add_filter( 'wp_count_posts', $supply_post_count );
+ add_filter( 'pre_option_woocommerce_coming_soon', $supply_coming_soon );
+ add_filter( 'pre_option_woocommerce_task_list_completed_lists', $supply_completed_lists );
$this->assertTrue( WC_Install::is_new_install(), 'We are in a new install if the WC version is null.' );
@@ -223,7 +235,15 @@ class WC_Install_Test extends \WC_Unit_Test_Case {
$post_count = 1;
$this->assertTrue( WC_Install::is_new_install(), 'We are in a new install if the WC version is null (even if the shop ID is set and we have one or more products).' );
- $version = '9.0.0';
+ $version = '9.0.0';
+ $coming_soon = 'no';
+ $this->assertFalse( WC_Install::is_new_install(), 'We are not in a new install if the store is live (coming soon is disabled).' );
+
+ $coming_soon = 'yes';
+ $completed_lists = array( 'setup' );
+ $this->assertFalse( WC_Install::is_new_install(), 'We are not in a new install if onboarding has been completed.' );
+
+ $completed_lists = array();
$this->assertFalse( WC_Install::is_new_install(), 'We are not in a new install if the WC version is set, we have a shop ID and we have one or more products.' );
$shop_id = null;
@@ -239,9 +259,11 @@ class WC_Install_Test extends \WC_Unit_Test_Case {
$this->assertFalse( $counted_posts, 'For established stores (version and shop ID both set), we do not need to count the number of existing products.' );
// Cleanup.
- remove_filter( 'option_woocommerce_db_version', $supply_version );
+ remove_filter( 'option_woocommerce_version', $supply_version );
remove_filter( 'woocommerce_get_shop_page_id', $supply_shop_id );
remove_filter( 'wp_count_posts', $supply_post_count );
+ remove_filter( 'pre_option_woocommerce_coming_soon', $supply_coming_soon );
+ remove_filter( 'pre_option_woocommerce_task_list_completed_lists', $supply_completed_lists );
}
/**