Commit e69c9a7b66f for woocommerce

commit e69c9a7b66fda406aa5a5254c1c6023b937d0384
Author: Michal Iwanow <4765119+mcliwanow@users.noreply.github.com>
Date:   Tue Oct 13 11:34:38 2026 +0200

    Stop the Extensions menu update count from running an update check (#69454)

    * Count Extensions menu updates from cached data without an update check

    get_updates_count() now reads the products in the cached update data
    even when it is stale or expired, instead of calling get_update_data(),
    which made a blocking update-check request once the cache was older than
    12 hours. A new update-check result also resets the cached count, so a
    count taken from stale data doesn't stick for 12 hours after a refresh.

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

    * Add changelog entry for the Extensions menu update count fix

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

    * Remove the changelog entry for the Extensions menu update count fix

    The fix follows up on #69160, which hasn't been released yet, so its
    changelog entry already covers it.

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

    ---------

    Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

diff --git a/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php b/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php
index f121a51637a..5db885a2189 100644
--- a/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php
+++ b/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php
@@ -1186,6 +1186,7 @@ class WC_Helper_Updater {
 		}

 		set_transient( $cache_key, $data, WEEK_IN_SECONDS );
+		delete_transient( '_woocommerce_helper_updates_count' );
 		return $data['products'];
 	}

@@ -1201,17 +1202,13 @@ class WC_Helper_Updater {
 			return $count;
 		}

-		// This runs often, so it only counts from cached data; a cache that's stale or expired by a refresh triggers one update check here.
+		// This runs on every admin page, so it counts from the cached update data, even when stale, and never runs an update check.
 		if ( ! get_transient( '_woocommerce_helper_subscriptions' ) ) {
 			return 0;
 		}

-		if ( ! get_transient( '_woocommerce_helper_updates' ) ) {
-			return 0;
-		}
-
 		$count       = 0;
-		$update_data = self::get_update_data();
+		$update_data = self::get_cached_products( get_transient( '_woocommerce_helper_updates' ), '' );

 		if ( empty( $update_data ) ) {
 			set_transient( $cache_key, $count, 12 * HOUR_IN_SECONDS );
@@ -1220,7 +1217,7 @@ class WC_Helper_Updater {

 		// Scan local plugins.
 		foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) {
-			if ( empty( $update_data[ $plugin['_product_id'] ] ) ) {
+			if ( ! isset( $update_data[ $plugin['_product_id'] ]['version'] ) ) {
 				continue;
 			}

@@ -1235,7 +1232,7 @@ class WC_Helper_Updater {

 		// Scan local themes.
 		foreach ( WC_Helper::get_local_woo_themes() as $theme ) {
-			if ( empty( $update_data[ $theme['_product_id'] ] ) ) {
+			if ( ! isset( $update_data[ $theme['_product_id'] ]['version'] ) ) {
 				continue;
 			}

diff --git a/plugins/woocommerce/tests/php/includes/admin/helper/class-wc-helper-updater-test.php b/plugins/woocommerce/tests/php/includes/admin/helper/class-wc-helper-updater-test.php
index 4f7698b4291..92145b3e24c 100644
--- a/plugins/woocommerce/tests/php/includes/admin/helper/class-wc-helper-updater-test.php
+++ b/plugins/woocommerce/tests/php/includes/admin/helper/class-wc-helper-updater-test.php
@@ -753,6 +753,77 @@ class WC_Helper_Updater_Test extends WC_Unit_Test_Case {
 		$this->assertFalse( get_transient( '_woocommerce_helper_updates_count' ), 'Count transient should be cleared after upgrade' );
 	}

+	/**
+	 * @testdox The updates count should come from cached data an update check wouldn't reuse, without making the check.
+	 */
+	public function test_updates_count_reads_stale_cache_without_a_request(): void {
+		$plugin_file = $this->mock_local_woo_plugin();
+		update_option( 'active_plugins', array( $plugin_file ) );
+		$this->set_subscriptions( array( $this->subscription() ) );
+		set_transient(
+			'_woocommerce_helper_updates',
+			array(
+				'hash'     => 'a-stale-hash',
+				'updated'  => time() - 13 * HOUR_IN_SECONDS,
+				'products' => array( 123 => array( 'version' => '2.0.0' ) ),
+				'errors'   => array(),
+			),
+			WEEK_IN_SECONDS
+		);
+
+		$requests  = 0;
+		$http_mock = static function () use ( &$requests ) {
+			++$requests;
+			return new WP_Error( 'http_request_failed', 'cURL error 28: Operation timed out' );
+		};
+		add_filter( 'pre_http_request', $http_mock );
+
+		try {
+			$count = WC_Helper_Updater::get_updates_count();
+		} finally {
+			remove_filter( 'pre_http_request', $http_mock );
+		}
+
+		$this->assertSame( 1, $count, 'The active plugin behind the cached version should be counted' );
+		$this->assertSame( 0, $requests, 'Counting updates should not call WooCommerce.com' );
+	}
+
+	/**
+	 * @testdox The updates count should be 0 when the cached update data is malformed.
+	 */
+	public function test_updates_count_is_zero_for_malformed_cached_data(): void {
+		$plugin_file = $this->mock_local_woo_plugin();
+		update_option( 'active_plugins', array( $plugin_file ) );
+		$this->set_subscriptions( array( $this->subscription() ) );
+		set_transient( '_woocommerce_helper_updates', array( 'products' => 'not-an-array' ), WEEK_IN_SECONDS );
+
+		$this->assertSame( 0, WC_Helper_Updater::get_updates_count() );
+	}
+
+	/**
+	 * @testdox A successful update check should reset the cached updates count.
+	 */
+	public function test_update_check_resets_updates_count(): void {
+		set_transient( '_woocommerce_helper_updates_count', 5, 12 * HOUR_IN_SECONDS );
+		add_filter( 'pre_http_request', array( $this, 'mock_helper_api_response' ), 10, 3 );
+
+		try {
+			$this->call_update_check(
+				array(
+					123 => array(
+						'product_id' => 123,
+						'file_id'    => 'abc123',
+					),
+				)
+			);
+		} finally {
+			remove_filter( 'pre_http_request', array( $this, 'mock_helper_api_response' ) );
+		}
+
+		$this->assertNotFalse( get_transient( '_woocommerce_helper_updates' ), 'The update check should cache its result' );
+		$this->assertFalse( get_transient( '_woocommerce_helper_updates_count' ), 'A new cache should reset the count computed from the old one' );
+	}
+
 	/**
 	 * Test should_use_cached_update_data returns false when data is not an array.
 	 */