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 );
 	}

 	/**