Commit bbf90f2b61a for woocommerce

commit bbf90f2b61a6f6ed4596a8ee6e3f61be74257f3c
Author: Mike Jolley <mike.jolley@me.com>
Date:   Fri Jun 12 16:41:49 2026 +0100

    Move global admin notices into Site Health (#64758)

    * Move admin notices to Site Health

    * Move admin notices to Site Health

    * Fix WP-Cron-dependent assertion in lookup table regeneration test

    * Remove stale PHPStan baseline entry for regenerating_notice

    * Extract side-effect-free WC_Install::get_missing_base_tables() for Site Health

    * Stop status tools polling when the refresh request fails

    * Add comments explaining notice migration leftovers

    * Bump changelog significance to minor for notice migration

    * Honour MaxMind display notices filter in the Site Health check

    * Restore thumbnail regeneration notice methods as BC stubs

    * Mention deprecated notice callbacks and dropped filters in changelog

diff --git a/plugins/woocommerce/changelog/site-health-admin-notice-migration b/plugins/woocommerce/changelog/site-health-admin-notice-migration
new file mode 100644
index 00000000000..fbb5f395c1e
--- /dev/null
+++ b/plugins/woocommerce/changelog/site-health-admin-notice-migration
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Move several WooCommerce admin status notices to Site Health and show regeneration progress in status tools; deprecate the related WC_Admin_Notices/WC_Regenerate_Images notice callbacks and stop honoring the woocommerce_hide_* notice dismissal filters
diff --git a/plugins/woocommerce/client/admin/client/marketplace/components/connect-notice/connect-notice.tsx b/plugins/woocommerce/client/admin/client/marketplace/components/connect-notice/connect-notice.tsx
index bd6fddcb762..422b5144409 100644
--- a/plugins/woocommerce/client/admin/client/marketplace/components/connect-notice/connect-notice.tsx
+++ b/plugins/woocommerce/client/admin/client/marketplace/components/connect-notice/connect-notice.tsx
@@ -26,7 +26,9 @@ export default function ConnectNotice(): React.JSX.Element | null {
 			? `<strong>${ storeName }</strong>`
 			: storeName;

-	if ( noticeType === 'none' ) {
+	// The "outdated plugins / store at risk" (long) state is now surfaced in
+	// Site Health instead of here, so only the short connect nudge renders.
+	if ( noticeType === 'none' || noticeType === 'long' ) {
 		return null;
 	}

@@ -49,14 +51,6 @@ export default function ConnectNotice(): React.JSX.Element | null {
 	}

 	const noticeText = {
-		long: sprintf(
-			/* translators: %s: store name set from the store settings, if not set, it will be "Your store" */
-			__(
-				'%s might be at risk because it’s running outdated WooCommerce plugins and is not yet connected to a WooCommerce.com account. Please complete the connection to get updates and streamlined support.',
-				'woocommerce'
-			),
-			formattedStoreName
-		),
 		short: sprintf(
 			/* translators: %s: store name set from the store settings, if not set, it will be "Your store" */
 			__(
diff --git a/plugins/woocommerce/client/legacy/css/admin.scss b/plugins/woocommerce/client/legacy/css/admin.scss
index 5b1a8e936fc..6ddce06519e 100644
--- a/plugins/woocommerce/client/legacy/css/admin.scss
+++ b/plugins/woocommerce/client/legacy/css/admin.scss
@@ -1257,11 +1257,16 @@ table.wc_status_table {
 		&.run-tool {
 			text-align: right;

+			.run-tool-actions {
+				align-items: center;
+				display: inline-flex;
+				white-space: nowrap;
+			}
+
 			.run-tool-status {
-				font-size: 90%;
-				margin-right: 0.5em;
 				display: inline-block;
-				opacity: 80%;
+				margin-left: 0.5em;
+				vertical-align: middle;

 				.dashicons.spin {
 					animation: rotation 2s infinite linear;
diff --git a/plugins/woocommerce/client/legacy/js/admin/system-status.js b/plugins/woocommerce/client/legacy/js/admin/system-status.js
index 4ef81430487..0fefe46c145 100644
--- a/plugins/woocommerce/client/legacy/js/admin/system-status.js
+++ b/plugins/woocommerce/client/legacy/js/admin/system-status.js
@@ -4,6 +4,9 @@ jQuery( function ( $ ) {
 	 * Users country and state fields
 	 */
 	var wcSystemStatus = {
+		toolsPollTimer: null,
+		toolsPollInProgress: false,
+
 		init: function () {
 			$( document.body )
 				.on(
@@ -17,6 +20,85 @@ jQuery( function ( $ ) {
 				.on( 'aftercopy', '#copy-for-support, #copy-for-github', this.copySuccess )
 				.on( 'aftercopyfailure', '#copy-for-support, #copy-for-github', this.copyFail )
 				.on( 'click', '#download-for-support', this.downloadReport );
+
+			this.maybePollTools();
+		},
+
+		/**
+		 * Start polling the tools page if a background tool is running.
+		 */
+		maybePollTools: function() {
+			if (
+				this.toolsPollTimer ||
+				! woocommerce_admin_system_status.tools_url ||
+				! this.shouldPollTools()
+			) {
+				return;
+			}
+
+			this.toolsPollTimer = window.setInterval(
+				$.proxy( this.pollTools, this ),
+				parseInt( woocommerce_admin_system_status.tools_poll_interval, 10 ) || 10000
+			);
+		},
+
+		/**
+		 * Check whether any tool rows still need polling.
+		 *
+		 * @return {Bool}
+		 */
+		shouldPollTools: function() {
+			return $( '.wc_status_table--tools tr.requires-refresh' ).is( function() {
+				var $row = $( this );
+				return $row.find( '.run-tool-status' ).length > 0 || $row.find( '.run-tool .button:disabled' ).length > 0;
+			});
+		},
+
+		/**
+		 * Refresh background tool rows from the tools page.
+		 */
+		pollTools: function() {
+			if ( this.toolsPollInProgress ) {
+				return;
+			}
+
+			this.toolsPollInProgress = true;
+
+			var self = this;
+			var toolsUrl = woocommerce_admin_system_status.tools_url;
+			var pollUrl = toolsUrl + ( toolsUrl.indexOf( '?' ) === -1 ? '?' : '&' ) + 'wc_status_tools_poll=' + Date.now();
+
+			$.get( pollUrl )
+				.done( function( response ) {
+					var $response = $( '<div>' ).append( $.parseHTML( response ) );
+
+					$( '.wc_status_table--tools tr.requires-refresh' ).each( function() {
+						var $currentRow = $( this );
+						var action      = $currentRow.data( 'tool-action' );
+
+						if ( ! action ) {
+							return;
+						}
+
+						var $updatedRow = $response.find( '.wc_status_table--tools tr[data-tool-action="' + action + '"]' );
+
+						if ( $updatedRow.length ) {
+							$currentRow.replaceWith( $updatedRow );
+						}
+					});
+
+					if ( ! self.shouldPollTools() ) {
+						window.clearInterval( self.toolsPollTimer );
+						self.toolsPollTimer = null;
+					}
+				})
+				.fail( function() {
+					window.clearInterval( self.toolsPollTimer );
+					self.toolsPollTimer = null;
+				})
+				.always( function() {
+					self.toolsPollInProgress = false;
+				});
 		},

 		/**
@@ -82,7 +164,7 @@ jQuery( function ( $ ) {
 				return false;
 			} catch ( e ) {
 				/* jshint devel: true */
-				console.log( e );
+				window.console.log( e );
 			}

 			return false;
@@ -177,7 +259,7 @@ jQuery( function ( $ ) {

 	wcSystemStatus.init();

-	$( '.wc_status_table' ).on( 'click', '.run-tool .button', function( evt ) {
+	$( '.wc_status_table' ).on( 'click', '.run-tool input.button', function( evt ) {
 		evt.stopImmediatePropagation();
 		return window.confirm( woocommerce_admin_system_status.run_tool_confirmation );
 	});
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php
index e1bdf292283..88ca40adecf 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php
@@ -808,6 +808,8 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
 					array(
 						'delete_log_confirmation' => esc_js( __( 'Are you sure you want to delete this log?', 'woocommerce' ) ),
 						'run_tool_confirmation'   => esc_js( __( 'Are you sure you want to run this tool?', 'woocommerce' ) ),
+						'tools_poll_interval'     => 10000,
+						'tools_url'               => esc_url_raw( admin_url( 'admin.php?page=wc-status&tab=tools' ) ),
 					)
 				);
 			}
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-notices.php b/plugins/woocommerce/includes/admin/class-wc-admin-notices.php
index 1e1254aaa6b..5639414df4a 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-notices.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-notices.php
@@ -7,8 +7,6 @@
  */

 use Automattic\Jetpack\Constants;
-use Automattic\WooCommerce\Enums\DefaultCustomerAddress;
-use Automattic\WooCommerce\Internal\Utilities\Users;

 defined( 'ABSPATH' ) || exit;

@@ -63,10 +61,12 @@ class WC_Admin_Notices {
 	public static function init() {
 		self::$is_multisite = is_multisite();
 		self::set_notices( get_option( 'woocommerce_admin_notices', array() ) );
+		if ( defined( 'WC_PHP_MIN_REQUIREMENTS_NOTICE' ) ) {
+			self::remove_notice( WC_PHP_MIN_REQUIREMENTS_NOTICE );
+		}

 		add_action( 'switch_theme', array( __CLASS__, 'reset_admin_notices' ) );
 		add_action( 'woocommerce_installed', array( __CLASS__, 'reset_admin_notices' ) );
-		add_action( 'update_option_woocommerce_file_download_method', array( __CLASS__, 'add_redirect_download_method_notice' ) );
 		add_action( 'admin_init', array( __CLASS__, 'hide_notices' ), 20 );

 		// @TODO: This prevents Action Scheduler async jobs from storing empty list of notices during WC installation.
@@ -165,15 +165,6 @@ class WC_Admin_Notices {
 	 * @return void
 	 */
 	public static function reset_admin_notices() {
-		if ( ! self::is_ssl() ) {
-			self::add_notice( 'no_secure_connection' );
-		}
-		if ( ! self::is_uploads_directory_protected() ) {
-			self::add_notice( 'uploads_directory_is_unprotected' );
-		}
-		self::add_notice( 'template_files' );
-		self::add_min_version_notice();
-		self::add_maxmind_missing_license_key_notice();
 	}

 	/**
@@ -389,7 +380,7 @@ class WC_Admin_Notices {
 		if ( WC_Install::needs_db_update() ) {
 			$next_scheduled_date = WC()->queue()->get_next( 'woocommerce_run_update_callback', null, 'woocommerce-db-updates' );

-            // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+			// phpcs:ignore WordPress.Security.NonceVerification.Recommended
 			if ( $next_scheduled_date || ! empty( $_GET['do_update_woocommerce'] ) ) {
 				include __DIR__ . '/views/html-notice-updating.php';
 			} else {
@@ -411,138 +402,81 @@ class WC_Admin_Notices {
 	}

 	/**
-	 * Show a notice highlighting bad template files.
+	 * Previously showed a notice highlighting bad template files.
+	 *
+	 * Template override status is now shown in Site Health.
 	 *
 	 * @return void
 	 */
 	public static function template_file_check_notice() {
-		$core_templates = WC_Admin_Status::scan_template_files( WC()->plugin_path() . '/templates' );
-		$outdated       = false;
-
-		foreach ( $core_templates as $file ) {
-
-			$theme_file = false;
-			if ( file_exists( get_stylesheet_directory() . '/' . $file ) ) {
-				$theme_file = get_stylesheet_directory() . '/' . $file;
-			} elseif ( file_exists( get_stylesheet_directory() . '/' . WC()->template_path() . $file ) ) {
-				$theme_file = get_stylesheet_directory() . '/' . WC()->template_path() . $file;
-			} elseif ( file_exists( get_template_directory() . '/' . $file ) ) {
-				$theme_file = get_template_directory() . '/' . $file;
-			} elseif ( file_exists( get_template_directory() . '/' . WC()->template_path() . $file ) ) {
-				$theme_file = get_template_directory() . '/' . WC()->template_path() . $file;
-			}
-
-			if ( false !== $theme_file ) {
-				$core_version  = WC_Admin_Status::get_file_version( WC()->plugin_path() . '/templates/' . $file );
-				$theme_version = WC_Admin_Status::get_file_version( $theme_file );
-
-				if ( $core_version && $theme_version && version_compare( $theme_version, $core_version, '<' ) ) {
-					$outdated = true;
-					break;
-				}
-			}
-		}
-
-		if ( $outdated ) {
-			include __DIR__ . '/views/html-notice-template-check.php';
-		} else {
-			self::remove_notice( 'template_files' );
-		}
+		self::remove_notice( 'template_files' );
 	}

 	/**
-	 * Show a notice asking users to convert to shipping zones.
+	 * Previously showed a notice asking users to convert to shipping zones.
+	 *
+	 * Legacy shipping status is now shown in Site Health.
 	 *
-	 * @todo remove in 4.0.0
 	 * @return void
 	 */
 	public static function legacy_shipping_notice() {
-		$maybe_load_legacy_methods = array( 'flat_rate', 'free_shipping', 'international_delivery', 'local_delivery', 'local_pickup' );
-		$enabled                   = false;
-
-		foreach ( $maybe_load_legacy_methods as $method ) {
-			$options = get_option( 'woocommerce_' . $method . '_settings' );
-			if ( $options && isset( $options['enabled'] ) && 'yes' === $options['enabled'] ) {
-				$enabled = true;
-			}
-		}
-
-		if ( $enabled ) {
-			include __DIR__ . '/views/html-notice-legacy-shipping.php';
-		} else {
-			self::remove_notice( 'legacy_shipping' );
-		}
+		self::remove_notice( 'legacy_shipping' );
 	}

 	/**
-	 * No shipping methods.
+	 * Previously showed a notice when no shipping methods were configured.
+	 *
+	 * Shipping method status is now shown in Site Health.
 	 *
 	 * @return void
 	 */
 	public static function no_shipping_methods_notice() {
-        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
-		if ( wc_shipping_enabled() && ( ! is_wc_admin_settings_page() || empty( $_GET['tab'] ) || 'shipping' !== $_GET['tab'] ) ) {
-			$product_count = wp_count_posts( 'product' );
-			$method_count  = wc_get_shipping_method_count();
-
-			if ( $product_count->publish > 0 && 0 === $method_count ) {
-				include __DIR__ . '/views/html-notice-no-shipping-methods.php';
-			}
-
-			if ( $method_count > 0 ) {
-				self::remove_notice( 'no_shipping_methods' );
-			}
-		}
+		self::remove_notice( 'no_shipping_methods' );
 	}

 	/**
-	 * Notice shown when regenerating thumbnails background process is running.
+	 * Previously showed a notice about secure connections.
+	 *
+	 * Secure connection status is now shown in Site Health.
 	 *
 	 * @return void
 	 */
-	public static function regenerating_thumbnails_notice() {
-		include __DIR__ . '/views/html-notice-regenerating-thumbnails.php';
+	public static function secure_connection_notice() {
+		self::remove_notice( 'no_secure_connection' );
 	}

 	/**
-	 * Notice about secure connection.
+	 * Previously showed a notice while thumbnails regenerated in the background.
+	 *
+	 * Thumbnail regeneration progress is now shown beside the matching status tool.
 	 *
 	 * @return void
 	 */
-	public static function secure_connection_notice() {
-		if ( self::is_ssl() || get_user_meta( get_current_user_id(), 'dismissed_no_secure_connection_notice', true ) ) {
-			return;
-		}
-
-		include __DIR__ . '/views/html-notice-secure-connection.php';
+	public static function regenerating_thumbnails_notice() {
+		self::remove_notice( 'regenerating_thumbnails' );
 	}

 	/**
-	 * Notice shown when regenerating thumbnails background process is running.
+	 * Previously showed a notice while product lookup tables regenerated.
+	 *
+	 * Product lookup table regeneration status is now shown beside the matching status tool.
 	 *
 	 * @since 3.6.0
 	 * @return void
 	 */
 	public static function regenerating_lookup_table_notice() {
-		// See if this is still relevant.
-		if ( ! wc_update_product_lookup_tables_is_running() ) {
-			self::remove_notice( 'regenerating_lookup_table' );
-			return;
-		}
-
-		include __DIR__ . '/views/html-notice-regenerating-lookup-table.php';
+		self::remove_notice( 'regenerating_lookup_table' );
 	}

 	/**
 	 * Add notice about minimum PHP and WordPress requirement.
 	 *
+	 * @deprecated 11.0.0 WordPress and PHP minimum requirements notices are no longer shown.
+	 *
 	 * @since 3.6.5
 	 * @return void
 	 */
 	public static function add_min_version_notice() {
-		if ( version_compare( phpversion(), WC_NOTICE_MIN_PHP_VERSION, '<' ) || version_compare( get_bloginfo( 'version' ), WC_NOTICE_MIN_WP_VERSION, '<' ) ) {
-			self::add_notice( WC_PHP_MIN_REQUIREMENTS_NOTICE );
-		}
 	}

 	/**
@@ -557,153 +491,96 @@ class WC_Admin_Notices {
 	}

 	/**
-	 * Add MaxMind missing license key notice.
+	 * Previously added a MaxMind missing license key notice.
+	 *
+	 * MaxMind geolocation status is now shown in Site Health.
 	 *
 	 * @since 3.9.0
 	 * @return void
 	 */
 	public static function add_maxmind_missing_license_key_notice() {
-		$default_address = get_option( 'woocommerce_default_customer_address' );
-
-		if ( ! in_array( $default_address, array( DefaultCustomerAddress::GEOLOCATION, DefaultCustomerAddress::GEOLOCATION_AJAX ), true ) ) {
-			return;
-		}
-
-		$integration_options = get_option( 'woocommerce_maxmind_geolocation_settings' );
-		if ( empty( $integration_options['license_key'] ) ) {
-			self::add_notice( 'maxmind_license_key' );
-
-		}
+		self::remove_notice( 'maxmind_license_key' );
 	}

 	/**
-	 *  Add notice about Redirect-only download method, nudging user to switch to a different method instead.
+	 * Previously added a Redirect only download method notice.
+	 *
+	 * Download method status is now shown in Site Health.
 	 *
 	 * @return void
 	 */
 	public static function add_redirect_download_method_notice() {
-		if ( 'redirect' === get_option( 'woocommerce_file_download_method' ) ) {
-			self::add_notice( 'redirect_download_method' );
-		} else {
-			self::remove_notice( 'redirect_download_method' );
-		}
+		self::remove_notice( 'redirect_download_method' );
 	}

 	/**
-	 * Notice about the completion of the product downloads sync, with further advice for the site operator.
+	 * Previously displayed the approved download directories sync completion notice.
+	 *
+	 * Approved download directory sync status is now shown in Site Health. The notice ID
+	 * remains stored until the merchant marks it reviewed in Site Health.
 	 *
 	 * @return void
 	 */
 	public static function download_directories_sync_complete() {
-		$notice_dismissed = apply_filters(
-			'woocommerce_hide_download_directories_sync_complete',
-			get_user_meta( get_current_user_id(), 'download_directories_sync_complete', true )
-		);
-
-		if ( $notice_dismissed ) {
-			self::remove_notice( 'download_directories_sync_complete' );
-		}
-
-		if ( Users::is_site_administrator() ) {
-			include __DIR__ . '/views/html-notice-download-dir-sync-complete.php';
-		}
 	}

 	/**
-	 * Display MaxMind missing license key notice.
+	 * Previously displayed a MaxMind missing license key notice.
+	 *
+	 * MaxMind geolocation status is now shown in Site Health.
 	 *
 	 * @since 3.9.0
 	 * @return void
 	 */
 	public static function maxmind_missing_license_key_notice() {
-		$user_dismissed_notice   = get_user_meta( get_current_user_id(), 'dismissed_maxmind_license_key_notice', true );
-		$filter_dismissed_notice = ! apply_filters( 'woocommerce_maxmind_geolocation_display_notices', true );
-
-		if ( $user_dismissed_notice || $filter_dismissed_notice ) {
-			self::remove_notice( 'maxmind_license_key' );
-			return;
-		}
-
-		include __DIR__ . '/views/html-notice-maxmind-license-key.php';
+		self::remove_notice( 'maxmind_license_key' );
 	}

 	/**
-	 * Notice about Redirect-Only download method.
+	 * Previously displayed a Redirect only download method notice.
+	 *
+	 * Download method status is now shown in Site Health.
 	 *
 	 * @since 4.0
 	 * @return void
 	 */
 	public static function redirect_download_method_notice() {
-		if ( apply_filters( 'woocommerce_hide_redirect_method_nag', get_user_meta( get_current_user_id(), 'dismissed_redirect_download_method_notice', true ) ) ) {
-			self::remove_notice( 'redirect_download_method' );
-			return;
-		}
-
-		include __DIR__ . '/views/html-notice-redirect-only-download.php';
+		self::remove_notice( 'redirect_download_method' );
 	}

 	/**
-	 * Notice about uploads directory begin unprotected.
+	 * Previously displayed an uploads directory protection notice.
+	 *
+	 * Uploads directory protection status is now shown in Site Health.
 	 *
 	 * @since 4.2.0
 	 * @return void
 	 */
 	public static function uploads_directory_is_unprotected_notice() {
-		if ( get_user_meta( get_current_user_id(), 'dismissed_uploads_directory_is_unprotected_notice', true ) || self::is_uploads_directory_protected() ) {
-			self::remove_notice( 'uploads_directory_is_unprotected' );
-			return;
-		}
-
-		include __DIR__ . '/views/html-notice-uploads-directory-is-unprotected.php';
+		self::remove_notice( 'uploads_directory_is_unprotected' );
 	}

 	/**
-	 * Notice about base tables missing.
+	 * Previously displayed a missing database tables notice.
+	 *
+	 * Database table status is now shown in Site Health.
 	 *
 	 * @return void
 	 */
 	public static function base_tables_missing_notice() {
-		$notice_dismissed = apply_filters(
-			'woocommerce_hide_base_tables_missing_nag',
-			get_user_meta( get_current_user_id(), 'dismissed_base_tables_missing_notice', true )
-		);
-		if ( $notice_dismissed ) {
-			self::remove_notice( 'base_tables_missing' );
-		}
-
-		include __DIR__ . '/views/html-notice-base-table-missing.php';
+		self::remove_notice( 'base_tables_missing' );
 	}

 	/**
-	 * Notice about HPOS sync-on-read being disabled by default.
+	 * Previously displayed a notice about HPOS sync-on-read being disabled by default.
+	 *
+	 * HPOS sync-on-read status is now shown in Site Health.
 	 *
 	 * @since 10.7.0
 	 * @return void
 	 */
 	public static function sync_on_read_disabled_notice() {
-		$dismiss =
-			! \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled()
-			|| ! wc_get_container()->get( \Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer::class )->data_sync_is_enabled()
-			|| get_user_meta( get_current_user_id(), 'dismissed_hpos_sync_on_read_disabled_notice', true );
-
-		if ( $dismiss ) {
-			self::remove_notice( 'hpos_sync_on_read_disabled' );
-			return;
-		}
-
-		include __DIR__ . '/views/html-notice-sync-on-read-disabled.php';
-	}
-
-	/**
-	 * Determine if the store is running SSL.
-	 *
-	 * @return bool Flag SSL enabled.
-	 * @since  3.5.1
-	 */
-	protected static function is_ssl() {
-		$shop_page = wc_get_page_permalink( 'shop' );
-
-		return ( is_ssl() && 'https' === substr( $shop_page, 0, 5 ) );
+		self::remove_notice( 'hpos_sync_on_read_disabled' );
 	}

 	/**
@@ -738,42 +615,6 @@ class WC_Admin_Notices {
 	public static function theme_check_notice() {
 		wc_deprecated_function( 'WC_Admin_Notices::theme_check_notice', '3.3.0' );
 	}
-
-	/**
-	 * Check if uploads directory is protected.
-	 *
-	 * @since 4.2.0
-	 * @return bool
-	 */
-	protected static function is_uploads_directory_protected() {
-		$cache_key = '_woocommerce_upload_directory_status';
-		$status    = get_transient( $cache_key );
-
-		// Check for cache.
-		if ( false !== $status ) {
-			return 'protected' === $status;
-		}
-
-		// Get only data from the uploads directory.
-		$uploads = wp_get_upload_dir();
-
-		// Check for the "uploads/woocommerce_uploads" directory.
-		$response         = wp_safe_remote_get(
-			esc_url_raw( $uploads['baseurl'] . '/woocommerce_uploads/' ),
-			array(
-				'redirection' => 0,
-			)
-		);
-		$response_code    = intval( wp_remote_retrieve_response_code( $response ) );
-		$response_content = wp_remote_retrieve_body( $response );
-
-		// Check if returns 200 with empty content in case can open an index.html file,
-		// and check for non-200 codes in case the directory is protected.
-		$is_protected = ( 200 === $response_code && empty( $response_content ) ) || ( 200 !== $response_code );
-		set_transient( $cache_key, $is_protected ? 'protected' : 'unprotected', 1 * DAY_IN_SECONDS );
-
-		return $is_protected;
-	}
 }

 WC_Admin_Notices::init();
diff --git a/plugins/woocommerce/includes/admin/views/html-admin-page-status-tools.php b/plugins/woocommerce/includes/admin/views/html-admin-page-status-tools.php
index d001700583c..11f78eab4f4 100644
--- a/plugins/woocommerce/includes/admin/views/html-admin-page-status-tools.php
+++ b/plugins/woocommerce/includes/admin/views/html-admin-page-status-tools.php
@@ -26,7 +26,13 @@ foreach ( $tools as $action_name => $tool ) {
 <table class="wc_status_table wc_status_table--tools widefat" cellspacing="0">
 	<tbody class="tools">
 		<?php foreach ( $tools as $action_name => $tool ) : ?>
-			<tr class="<?php echo sanitize_html_class( $action_name ); ?>">
+			<?php
+			$row_classes = array( sanitize_html_class( $action_name ) );
+			if ( ArrayUtil::is_truthy( $tool, 'requires_refresh' ) ) {
+				$row_classes[] = 'requires-refresh';
+			}
+			?>
+			<tr class="<?php echo esc_attr( implode( ' ', $row_classes ) ); ?>" data-tool-action="<?php echo esc_attr( $action_name ); ?>">
 				<th>
 					<strong class="name"><?php echo esc_html( $tool['name'] ); ?></strong>
 					<p class="description">
@@ -38,19 +44,27 @@ foreach ( $tools as $action_name => $tool ) {
 								echo '</p><p class="description">';
 								echo wp_kses_post( $selector['description'] );
 							}
-							// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
-							echo "&nbsp;&nbsp;<select style='width: 300px;' form='form_$action_name' id='selector_$action_name' data-allow_clear='true' class='{$selector['class']}' name='{$selector['name']}' data-placeholder='{$selector['placeholder']}' data-action='{$selector['search_action']}'></select>";
+							printf(
+								'&nbsp;&nbsp;<select style="width: 300px;" form="%1$s" id="%2$s" data-allow_clear="true" class="%3$s" name="%4$s" data-placeholder="%5$s" data-action="%6$s"></select>',
+								esc_attr( 'form_' . $action_name ),
+								esc_attr( 'selector_' . $action_name ),
+								esc_attr( $selector['class'] ),
+								esc_attr( $selector['name'] ),
+								esc_attr( $selector['placeholder'] ),
+								esc_attr( $selector['search_action'] )
+							);
 						}
 						?>
 					</p>
 				</th>
 				<td class="run-tool">
+					<span class="run-tool-actions">
+					<input <?php disabled( ArrayUtil::is_truthy( $tool, 'disabled' ) ); ?> type="submit" form="<?php echo esc_attr( 'form_' . $action_name ); ?>" class="button button-large" value="<?php echo esc_attr( $tool['button'] ); ?>" />
+
 					<?php if ( ! empty( $tool['status_text'] ) ) : ?>
 					<span class="run-tool-status"><?php echo wp_kses_post( $tool['status_text'] ); ?></span>
 					<?php endif; ?>
-
-					<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
-					<input <?php echo ArrayUtil::is_truthy( $tool, 'disabled' ) ? 'disabled' : ''; ?> type="submit" form="<?php echo 'form_' . $action_name; ?>" class="button button-large" value="<?php echo esc_attr( $tool['button'] ); ?>" />
+					</span>
 				</td>
 			</tr>
 		<?php endforeach; ?>
diff --git a/plugins/woocommerce/includes/admin/views/html-notice-base-table-missing.php b/plugins/woocommerce/includes/admin/views/html-notice-base-table-missing.php
deleted file mode 100644
index f2d8e687adc..00000000000
--- a/plugins/woocommerce/includes/admin/views/html-notice-base-table-missing.php
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-/**
- * Admin View: Notice - Base table missing.
- *
- * @package WooCommerce\Admin
- */
-
-defined( 'ABSPATH' ) || exit;
-
-?>
-<div class="updated woocommerce-message">
-	<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'base_tables_missing' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>">
-		<?php esc_html_e( 'Dismiss', 'woocommerce' ); ?>
-	</a>
-
-	<p>
-		<strong><?php esc_html_e( 'Database tables missing', 'woocommerce' ); ?></strong>
-	</p>
-	<p>
-		<?php
-		$verify_db_tool_available = array_key_exists( 'verify_db_tables', WC_Admin_Status::get_tools() );
-		$missing_tables           = get_option( 'woocommerce_schema_missing_tables' );
-		if ( $verify_db_tool_available ) {
-			echo wp_kses_post(
-				sprintf(
-				/* translators: %1%s: Missing tables (separated by ",") %2$s: Link to check again */
-					__( 'One or more tables required for WooCommerce to function are missing, some features may not work as expected. Missing tables: %1$s. <a href="%2$s">Check again.</a>', 'woocommerce' ),
-					esc_html( implode( ', ', $missing_tables ) ),
-					wp_nonce_url( admin_url( 'admin.php?page=wc-status&tab=tools&action=verify_db_tables' ), 'debug_action' )
-				)
-			);
-		} else {
-			echo wp_kses_post(
-				sprintf(
-				/* translators: %1%s: Missing tables (separated by ",") */
-					__( 'One or more tables required for WooCommerce to function are missing, some features may not work as expected. Missing tables: %1$s.', 'woocommerce' ),
-					esc_html( implode( ', ', $missing_tables ) )
-				)
-			);
-		}
-		?>
-	</p>
-</div>
diff --git a/plugins/woocommerce/includes/admin/views/html-notice-download-dir-sync-complete.php b/plugins/woocommerce/includes/admin/views/html-notice-download-dir-sync-complete.php
deleted file mode 100644
index 24da9e5b0af..00000000000
--- a/plugins/woocommerce/includes/admin/views/html-notice-download-dir-sync-complete.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-/**
- * Admin View: Notice - Product downloads directories sync complete.
- */
-
-defined( 'ABSPATH' ) || exit;
-
-?>
-<div id="message" class="updated woocommerce-message">
-	<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'download_directories_sync_complete' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php _e( 'Dismiss', 'woocommerce' ); ?></a>
-
-	<p>
-		<?php
-		$settings_screen_link = '<a href="' . esc_url( get_admin_url( null, 'admin.php?page=wc-settings&tab=products&section=download_urls' ) ) . '">';
-		$documentation_link   = '<a href="https://woocommerce.com/document/approved-download-directories">';
-		$closing_link         = '</a>';
-
-		printf(
-			/* translators: %1$s and %3$s are HTML (opening link tags). %2$s is also HTML (closing link tag). */
-			esc_html__( 'The %1$sApproved Product Download Directories list%2$s has been updated. To protect your site, please review the list and make any changes that might be required. For more information, please refer to %3$sthis guide%2$s.', 'woocommerce' ),
-			$settings_screen_link,
-			$closing_link,
-			$documentation_link
-		);
-		?>
-	</p>
-</div>
diff --git a/plugins/woocommerce/includes/admin/views/html-notice-install.php b/plugins/woocommerce/includes/admin/views/html-notice-install.php
deleted file mode 100644
index 5413acc0ba0..00000000000
--- a/plugins/woocommerce/includes/admin/views/html-notice-install.php
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-/**
- * Admin View: Notice - Install
- *
- * @deprecated 4.6.0
- */
-
-if ( ! defined( 'ABSPATH' ) ) {
-	exit;
-}
-
-?>
-<div id="message" class="updated woocommerce-message wc-connect">
-	<p><?php _e( '<strong>Welcome to WooCommerce</strong> &#8211; You&lsquo;re almost ready to start selling :)', 'woocommerce' ); ?></p>
-	<p class="submit"><a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-setup' ) ); ?>" class="button-primary"><?php _e( 'Run the Setup Wizard', 'woocommerce' ); ?></a> <a class="button-secondary skip" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'install' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php _e( 'Skip setup', 'woocommerce' ); ?></a></p>
-</div>
diff --git a/plugins/woocommerce/includes/admin/views/html-notice-legacy-shipping.php b/plugins/woocommerce/includes/admin/views/html-notice-legacy-shipping.php
deleted file mode 100644
index a84740994c8..00000000000
--- a/plugins/woocommerce/includes/admin/views/html-notice-legacy-shipping.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-/**
- * Admin View: Notice - Legacy Shipping.
- *
- * @package WooCommerce\Admin\Notices
- */
-
-if ( ! defined( 'ABSPATH' ) ) {
-	exit;
-}
-?>
-<div id="message" class="updated woocommerce-message">
-	<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'legacy_shipping' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>">
-		<?php esc_html_e( 'Dismiss', 'woocommerce' ); ?>
-	</a>
-
-	<p class="main">
-		<strong><?php esc_html_e( 'New:', 'woocommerce' ); ?> <?php esc_html_e( 'Shipping zones', 'woocommerce' ); ?></strong> &#8211; <?php esc_html_e( 'a group of regions that can be assigned different shipping methods and rates.', 'woocommerce' ); ?>
-	</p>
-	<p>
-		<?php esc_html_e( 'Legacy shipping methods (flat rate, international flat rate, local pickup and delivery, and free shipping) are deprecated but will continue to work as normal for now. <b><em>They will be removed in future versions of WooCommerce</em></b>. We recommend disabling these and setting up new rates within shipping zones as soon as possible.', 'woocommerce' ); ?>
-	</p>
-
-	<p class="submit">
-		<?php if ( ! is_wc_admin_settings_page() || empty( $_GET['tab'] ) || 'shipping' !== $_GET['tab'] ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
-			<a class="button-primary" href="<?php echo esc_url( admin_url( 'admin.php?page=wc-settings&tab=shipping' ) ); ?>">
-				<?php esc_html_e( 'Setup shipping zones', 'woocommerce' ); ?>
-			</a>
-		<?php endif; ?>
-		<a class="button-secondary" href="https://woocommerce.com/document/setting-up-shipping-zones/">
-			<?php esc_html_e( 'Learn more about shipping zones', 'woocommerce' ); ?>
-		</a>
-	</p>
-</div>
diff --git a/plugins/woocommerce/includes/admin/views/html-notice-maxmind-license-key.php b/plugins/woocommerce/includes/admin/views/html-notice-maxmind-license-key.php
deleted file mode 100644
index 1ae8c8132f1..00000000000
--- a/plugins/woocommerce/includes/admin/views/html-notice-maxmind-license-key.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-/**
- * Admin View: Notice - Missing MaxMind license key
- *
- * @package WooCommerce\Admin
- */
-
-defined( 'ABSPATH' ) || exit;
-
-?>
-
-<div id="message" class="updated woocommerce-message">
-	<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'maxmind_license_key' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php esc_html_e( 'Dismiss', 'woocommerce' ); ?></a>
-
-	<p>
-		<strong><?php esc_html_e( 'Geolocation has not been configured.', 'woocommerce' ); ?></strong>
-	</p>
-
-	<p>
-		<?php
-		echo wp_kses_post(
-			sprintf(
-				/* translators: %1%s: integration page %2$s: general settings page */
-				__( 'You must enter a valid license key on the <a href="%1$s">MaxMind integration settings page</a> in order to use the geolocation service. If you do not need geolocation for shipping or taxes, you should change the default customer location on the <a href="%2$s">general settings page</a>.', 'woocommerce' ),
-				admin_url( 'admin.php?page=wc-settings&tab=integration&section=maxmind_geolocation' ),
-				admin_url( 'admin.php?page=wc-settings&tab=general' )
-			)
-		);
-		?>
-	</p>
-</div>
diff --git a/plugins/woocommerce/includes/admin/views/html-notice-no-shipping-methods.php b/plugins/woocommerce/includes/admin/views/html-notice-no-shipping-methods.php
deleted file mode 100644
index 89bb34432c6..00000000000
--- a/plugins/woocommerce/includes/admin/views/html-notice-no-shipping-methods.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-/**
- * Admin View: Notice - No Shipping methods.
- *
- * @package WooCommerce\Admin\Notices
- */
-
-if ( ! defined( 'ABSPATH' ) ) {
-	exit;
-}
-?>
-<div id="message" class="updated woocommerce-message">
-	<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'no_shipping_methods' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>">
-		<?php esc_html_e( 'Dismiss', 'woocommerce' ); ?>
-	</a>
-
-	<p class="main">
-		<strong>
-			<?php esc_html_e( 'Add shipping methods &amp; zones', 'woocommerce' ); ?>
-		</strong>
-	</p>
-	<p>
-		<?php esc_html_e( 'Shipping is currently enabled, but you have not added any shipping methods to your shipping zones.', 'woocommerce' ); ?>
-	</p>
-	<p>
-		<?php esc_html_e( 'Customers will not be able to purchase physical goods from your store until a shipping method is available.', 'woocommerce' ); ?>
-	</p>
-
-	<p class="submit">
-		<a class="button-primary" href="<?php echo esc_url( admin_url( 'admin.php?page=wc-settings&tab=shipping' ) ); ?>">
-			<?php esc_html_e( 'Setup shipping zones', 'woocommerce' ); ?>
-		</a>
-		<a class="button-secondary" href="https://woocommerce.com/document/setting-up-shipping-zones/">
-			<?php esc_html_e( 'Learn more about shipping zones', 'woocommerce' ); ?>
-		</a>
-	</p>
-</div>
diff --git a/plugins/woocommerce/includes/admin/views/html-notice-redirect-only-download.php b/plugins/woocommerce/includes/admin/views/html-notice-redirect-only-download.php
deleted file mode 100644
index 23d985b0335..00000000000
--- a/plugins/woocommerce/includes/admin/views/html-notice-redirect-only-download.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-/**
- * Admin View: Notice - Redirect only download method is selected.
- *
- * @package WooCommerce\Admin\Notices
- */
-
-defined( 'ABSPATH' ) || exit;
-
-?>
-<div class="updated woocommerce-message">
-	<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'redirect_download_method' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php esc_html_e( 'Dismiss', 'woocommerce' ); ?></a>
-	<p>
-		<?php
-		echo wp_kses_post(
-			sprintf(
-				/* translators: %s: Link to settings page. */
-				__( 'Your store is configured to serve digital products using "Redirect only" method. This method is deprecated, <a href="%s">please switch to a different method instead.</a><br><em>If you use a remote server for downloadable files (such as Google Drive, Dropbox, Amazon S3), you may optionally wish to "allow using redirects as a last resort". Enabling that and/or selecting any of the other options will make this notice go away.</em>', 'woocommerce' ),
-				add_query_arg(
-					array(
-						'page'    => 'wc-settings',
-						'tab'     => 'products',
-						'section' => 'downloadable',
-					),
-					admin_url( 'admin.php' )
-				)
-			)
-		);
-		?>
-	</p>
-</div>
diff --git a/plugins/woocommerce/includes/admin/views/html-notice-regenerating-lookup-table.php b/plugins/woocommerce/includes/admin/views/html-notice-regenerating-lookup-table.php
deleted file mode 100644
index 32c50aae07a..00000000000
--- a/plugins/woocommerce/includes/admin/views/html-notice-regenerating-lookup-table.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-/**
- * Admin View: Notice - Regenerating product lookup table.
- *
- * @package WooCommerce\Admin
- */
-
-use Automattic\Jetpack\Constants;
-
-defined( 'ABSPATH' ) || exit;
-
-$pending_actions_url = admin_url( 'admin.php?page=wc-status&tab=action-scheduler&s=wc_update_product_lookup_tables&status=pending' );
-$cron_disabled       = Constants::is_true( 'DISABLE_WP_CRON' );
-$cron_cta            = $cron_disabled ? __( 'You can manually run queued updates here.', 'woocommerce' ) : __( 'View progress &rarr;', 'woocommerce' );
-?>
-<div id="message" class="updated woocommerce-message">
-	<p>
-		<strong><?php esc_html_e( 'WooCommerce is updating product data in the background', 'woocommerce' ); ?></strong><br>
-		<?php
-		esc_html_e( 'Product display, sorting, and reports may not be accurate until this finishes. It will take a few minutes and this notice will disappear when complete.', 'woocommerce' );
-
-		if ( $cron_disabled ) {
-			echo '<br>' . esc_html__( 'Note: WP CRON has been disabled on your install which may prevent this update from completing.', 'woocommerce' );
-		}
-		?>
-		&nbsp;<a href="<?php echo esc_url( $pending_actions_url ); ?>"><?php echo esc_html( $cron_cta ); ?></a>
-	</p>
-</div>
diff --git a/plugins/woocommerce/includes/admin/views/html-notice-regenerating-thumbnails.php b/plugins/woocommerce/includes/admin/views/html-notice-regenerating-thumbnails.php
deleted file mode 100644
index 52575c42b50..00000000000
--- a/plugins/woocommerce/includes/admin/views/html-notice-regenerating-thumbnails.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<?php
-/**
- * Admin View: Notice - Regenerating thumbnails.
- */
-
-defined( 'ABSPATH' ) || exit;
-
-?>
-<div id="message" class="updated woocommerce-message">
-	<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'regenerating_thumbnails' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php _e( 'Cancel thumbnail regeneration', 'woocommerce' ); ?></a>
-
-	<p><?php esc_html_e( 'Thumbnail regeneration is running in the background. Depending on the amount of images in your store this may take a while.', 'woocommerce' ); ?></p>
-</div>
diff --git a/plugins/woocommerce/includes/admin/views/html-notice-secure-connection.php b/plugins/woocommerce/includes/admin/views/html-notice-secure-connection.php
deleted file mode 100644
index 2af8d269421..00000000000
--- a/plugins/woocommerce/includes/admin/views/html-notice-secure-connection.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-/**
- * Admin View: Notice - Secure connection.
- *
- * @package WooCommerce\Admin\Notices
- */
-
-defined( 'ABSPATH' ) || exit;
-
-?>
-<div id="message" class="updated woocommerce-message">
-	<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'no_secure_connection' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php esc_html_e( 'Dismiss', 'woocommerce' ); ?></a>
-
-	<p>
-	<?php
-		echo wp_kses_post( sprintf(
-			/* translators: %s: documentation URL */
-			__( 'Your store does not appear to be using a secure connection. We highly recommend serving your entire website over an HTTPS connection to help keep customer data secure. <a href="%s">Learn more here.</a>', 'woocommerce' ),
-			'https://woocommerce.com/document/ssl-and-https/'
-		) );
-	?>
-	</p>
-</div>
diff --git a/plugins/woocommerce/includes/admin/views/html-notice-sync-on-read-disabled.php b/plugins/woocommerce/includes/admin/views/html-notice-sync-on-read-disabled.php
deleted file mode 100644
index 10e61231a34..00000000000
--- a/plugins/woocommerce/includes/admin/views/html-notice-sync-on-read-disabled.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-/**
- * Admin View: Notice - HPOS sync-on-read disabled.
- *
- * @package WooCommerce\Admin\Notices
- */
-
-defined( 'ABSPATH' ) || exit;
-
-?>
-<div id="message" class="updated woocommerce-message">
-	<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'hpos_sync_on_read_disabled' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php esc_html_e( 'Dismiss', 'woocommerce' ); ?></a>
-
-	<p>
-		<strong><?php esc_html_e( 'HPOS order "sync on read" has been disabled', 'woocommerce' ); ?></strong><br />
-		<?php
-			echo wp_kses_post(
-				sprintf(
-					/* translators: %s: URL to blog post about this change. */
-					__( 'Compatibility mode for HPOS no longer pulls order changes made to the posts database back into your orders automatically. If your site uses custom code or plugins that modify orders outside of WooCommerce, this may affect how order data is handled. <a href="%s">Learn more about this change and what to do</a>.', 'woocommerce' ),
-					'https://developer.woocommerce.com/2026/02/16/hpos-sync-on-read-to-be-disabled-by-default-in-woocommerce-10-7/'
-				)
-			);
-			?>
-	</p>
-</div>
diff --git a/plugins/woocommerce/includes/admin/views/html-notice-template-check.php b/plugins/woocommerce/includes/admin/views/html-notice-template-check.php
deleted file mode 100644
index 92ab0a72840..00000000000
--- a/plugins/woocommerce/includes/admin/views/html-notice-template-check.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-/**
- * Admin View: Notice - Template Check
- *
- * @package WooCommerce\Views
- */
-
-if ( ! defined( 'ABSPATH' ) ) {
-	exit;
-}
-
-$theme = wp_get_theme();
-?>
-<div id="message" class="updated woocommerce-message">
-	<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'template_files' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php esc_html_e( 'Dismiss', 'woocommerce' ); ?></a>
-
-	<p>
-		<?php /* translators: %s: theme name */ ?>
-		<?php printf( __( '<strong>Your theme (%s) contains outdated copies of some WooCommerce template files.</strong> These files may need updating to ensure they are compatible with the current version of WooCommerce. Suggestions to fix this:', 'woocommerce' ), esc_html( $theme['Name'] ) ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?>
-		<ol>
-			<li><?php esc_html_e( 'Update your theme to the latest version. If no update is available contact your theme author asking about compatibility with the current WooCommerce version.', 'woocommerce' ); ?></li>
-			<li><?php esc_html_e( 'If you copied over a template file to change something, then you will need to copy the new version of the template and apply your changes again.', 'woocommerce' ); ?></li>
-		</ol>
-	</p>
-	<p class="submit">
-		<a class="button-primary" href="https://woocommerce.com/document/template-structure/" target="_blank"><?php esc_html_e( 'Learn more about templates', 'woocommerce' ); ?></a>
-		<a class="button-primary" href="<?php echo esc_url( admin_url( 'admin.php?page=wc-status#status-table-templates' ) ); ?>" target="_blank"><?php esc_html_e( 'View affected templates', 'woocommerce' ); ?></a>
-	</p>
-</div>
diff --git a/plugins/woocommerce/includes/admin/views/html-notice-uploads-directory-is-unprotected.php b/plugins/woocommerce/includes/admin/views/html-notice-uploads-directory-is-unprotected.php
deleted file mode 100644
index 2f4b2763a27..00000000000
--- a/plugins/woocommerce/includes/admin/views/html-notice-uploads-directory-is-unprotected.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-/**
- * Admin View: Notice - Uploads directory is unprotected.
- *
- * @package WooCommerce\Admin\Notices
- * @since   4.2.0
- */
-
-defined( 'ABSPATH' ) || exit;
-
-$uploads = wp_get_upload_dir();
-
-?>
-<div id="message" class="error woocommerce-message">
-	<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'uploads_directory_is_unprotected' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php esc_html_e( 'Dismiss', 'woocommerce' ); ?></a>
-
-	<p>
-	<?php
-		echo wp_kses_post(
-			sprintf(
-				/* translators: 1: uploads directory URL 2: documentation URL */
-				__( 'Your store\'s uploads directory is <a href="%1$s">browsable via the web</a>. We strongly recommend <a href="%2$s">configuring your web server to prevent directory indexing</a>.', 'woocommerce' ),
-				esc_url( $uploads['baseurl'] . '/woocommerce_uploads' ),
-				'https://woocommerce.com/document/digital-downloadable-product-handling/#protecting-your-uploads-directory'
-			)
-		);
-		?>
-	</p>
-</div>
diff --git a/plugins/woocommerce/includes/admin/views/html-notice-wp-php-minimum-requirements.php b/plugins/woocommerce/includes/admin/views/html-notice-wp-php-minimum-requirements.php
deleted file mode 100644
index 48e5f6e6b23..00000000000
--- a/plugins/woocommerce/includes/admin/views/html-notice-wp-php-minimum-requirements.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-/**
- * Admin View: Notice - PHP & WP minimum requirements.
- *
- * @package WooCommerce\Admin\Notices
- */
-
-defined( 'ABSPATH' ) || exit;
-?>
-<div id="message" class="updated woocommerce-message">
-	<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', WC_PHP_MIN_REQUIREMENTS_NOTICE ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php esc_html_e( 'Dismiss', 'woocommerce' ); ?></a>
-
-	<p>
-		<?php
-		echo wp_kses_post(
-			sprintf(
-				$msg . '<p><a href="%s" class="button button-primary">' . __( 'Learn how to upgrade', 'woocommerce' ) . '</a></p>',
-				add_query_arg(
-					array(
-						'utm_source'   => 'wpphpupdatebanner',
-						'utm_medium'   => 'product',
-						'utm_campaign' => 'woocommerceplugin',
-						'utm_content'  => 'docs',
-					),
-					'https://woocommerce.com/document/update-php-wordpress/'
-				)
-			)
-		);
-		?>
-	</p>
-</div>
diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php
index 2a659c6b102..8e333bc11f4 100644
--- a/plugins/woocommerce/includes/class-wc-install.php
+++ b/plugins/woocommerce/includes/class-wc-install.php
@@ -746,18 +746,16 @@ class WC_Install {
 	}

 	/**
-	 * Check if all the base tables are present.
+	 * Get the names of any missing WooCommerce base tables.
 	 *
-	 * @param bool $modify_notice Whether to modify notice based on if all tables are present.
-	 * @param bool $execute       Whether to execute get_schema queries as well.
+	 * Unlike verify_base_tables(), this method has no side effects: it only inspects
+	 * the database and reports which required tables are missing.
+	 *
+	 * @since 11.0.0
 	 *
-	 * @return array List of queries.
+	 * @return string[] List of missing table names.
 	 */
-	public static function verify_base_tables( $modify_notice = true, $execute = false ) {
-		if ( $execute ) {
-			self::create_tables();
-		}
-
+	public static function get_missing_base_tables(): array {
 		$schema = self::get_schema();

 		$hpos_settings = filter_var_array(
@@ -776,14 +774,27 @@ class WC_Install {
 				->get_database_schema();
 		}

-		$missing_tables = wc_get_container()
+		return wc_get_container()
 			->get( DatabaseUtil::class )
 			->get_missing_tables( $schema );
+	}
+
+	/**
+	 * Check if all the base tables are present, updating the stored schema status accordingly.
+	 *
+	 * @param bool $modify_notice Whether to modify notice based on if all tables are present.
+	 * @param bool $execute       Whether to execute get_schema queries as well.
+	 *
+	 * @return string[] List of missing table names.
+	 */
+	public static function verify_base_tables( $modify_notice = true, $execute = false ) {
+		if ( $execute ) {
+			self::create_tables();
+		}
+
+		$missing_tables = self::get_missing_base_tables();

 		if ( 0 < count( $missing_tables ) ) {
-			if ( $modify_notice ) {
-				WC_Admin_Notices::add_notice( 'base_tables_missing' );
-			}
 			update_option( 'woocommerce_schema_missing_tables', $missing_tables );
 		} else {
 			if ( $modify_notice ) {
diff --git a/plugins/woocommerce/includes/class-wc-regenerate-images.php b/plugins/woocommerce/includes/class-wc-regenerate-images.php
index 7ed47a877e8..d6d8f325a1b 100644
--- a/plugins/woocommerce/includes/class-wc-regenerate-images.php
+++ b/plugins/woocommerce/includes/class-wc-regenerate-images.php
@@ -48,7 +48,6 @@ class WC_Regenerate_Images {

 			self::$background_process = new WC_Regenerate_Images_Request();

-			add_action( 'admin_init', array( __CLASS__, 'regenerating_notice' ) );
 			add_action( 'woocommerce_hide_regenerating_thumbnails_notice', array( __CLASS__, 'dismiss_regenerating_notice' ) );

 			// Regenerate thumbnails in the background after settings changes. Not ran on multisite to avoid multiple simultaneous jobs.
@@ -139,14 +138,26 @@ class WC_Regenerate_Images {
 	}

 	/**
-	 * Show notice when job is running in background.
+	 * See if thumbnail regeneration is currently in progress.
+	 *
+	 * @since 11.0.0
+	 * @return bool
+	 */
+	public static function is_regeneration_in_progress() {
+		// WC_Regenerate_Images_Request::is_running() returns whether the queue is empty.
+		return self::$background_process && ! self::$background_process->is_running();
+	}
+
+	/**
+	 * Previously kept the regenerating thumbnails admin notice in sync with the job state.
+	 *
+	 * Regeneration progress is now shown beside the regenerate thumbnails status tool.
+	 *
+	 * @deprecated 11.0.0 Use WC_Regenerate_Images::is_regeneration_in_progress() instead.
+	 * @return void
 	 */
 	public static function regenerating_notice() {
-		if ( ! self::$background_process->is_running() ) {
-			WC_Admin_Notices::add_notice( 'regenerating_thumbnails' );
-		} else {
-			WC_Admin_Notices::remove_notice( 'regenerating_thumbnails' );
-		}
+		wc_deprecated_function( 'WC_Regenerate_Images::regenerating_notice', '11.0.0', 'WC_Regenerate_Images::is_regeneration_in_progress' );
 	}

 	/**
diff --git a/plugins/woocommerce/includes/integrations/maxmind-geolocation/class-wc-integration-maxmind-geolocation.php b/plugins/woocommerce/includes/integrations/maxmind-geolocation/class-wc-integration-maxmind-geolocation.php
index 6e77dd20c06..98716a613d1 100644
--- a/plugins/woocommerce/includes/integrations/maxmind-geolocation/class-wc-integration-maxmind-geolocation.php
+++ b/plugins/woocommerce/includes/integrations/maxmind-geolocation/class-wc-integration-maxmind-geolocation.php
@@ -251,16 +251,6 @@ class WC_Integration_MaxMind_Geolocation extends WC_Integration {
 		return $prefix;
 	}

-	/**
-	 * Add missing license key notice.
-	 */
-	private function add_missing_license_key_notice() {
-		if ( ! class_exists( 'WC_Admin_Notices' ) ) {
-			include_once WC_ABSPATH . 'includes/admin/class-wc-admin-notices.php';
-		}
-		WC_Admin_Notices::add_notice( 'maxmind_license_key' );
-	}
-
 	/**
 	 * Remove missing license key notice.
 	 */
@@ -272,13 +262,14 @@ class WC_Integration_MaxMind_Geolocation extends WC_Integration {
 	}

 	/**
-	 * Display notice if license key is missing.
+	 * Remove the old missing license key notice when it no longer applies.
 	 *
 	 * @param mixed $old_value Option old value.
 	 * @param mixed $new_value Current value.
 	 */
 	public function display_missing_license_key_notice( $old_value, $new_value ) {
 		if ( ! apply_filters( 'woocommerce_maxmind_geolocation_display_notices', true ) ) {
+			$this->remove_missing_license_key_notice();
 			return;
 		}

@@ -289,9 +280,8 @@ class WC_Integration_MaxMind_Geolocation extends WC_Integration {

 		$license_key = $this->get_option( 'license_key' );
 		if ( ! empty( $license_key ) ) {
+			$this->remove_missing_license_key_notice();
 			return;
 		}
-
-		$this->add_missing_license_key_notice();
 	}
 }
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php
index 5685b31055f..18b568eaefb 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php
@@ -8,6 +8,7 @@
  * @since   3.0.0
  */

+use Automattic\Jetpack\Constants;
 use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
 use Automattic\WooCommerce\Internal\ProductFilters\CacheController;

@@ -147,9 +148,10 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
 				'desc'   => __( 'This tool will delete expired download permissions and permissions with 0 remaining downloads.', 'woocommerce' ),
 			),
 			'regenerate_product_lookup_tables'     => array(
-				'name'   => __( 'Product lookup tables', 'woocommerce' ),
-				'button' => __( 'Regenerate', 'woocommerce' ),
-				'desc'   => __( 'This tool will regenerate product lookup table data. This process may take a while.', 'woocommerce' ),
+				'name'             => __( 'Product lookup tables', 'woocommerce' ),
+				'button'           => __( 'Regenerate', 'woocommerce' ),
+				'desc'             => __( 'This tool will regenerate product lookup table data. This process may take a while.', 'woocommerce' ),
+				'requires_refresh' => true,
 			),
 			'repair_coupons_lookup_table'          => array(
 				'name'   => __( 'Coupons lookup table', 'woocommerce' ),
@@ -212,9 +214,10 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
 				),
 			),
 			'regenerate_thumbnails'                => array(
-				'name'   => __( 'Regenerate shop thumbnails', 'woocommerce' ),
-				'button' => __( 'Regenerate', 'woocommerce' ),
-				'desc'   => __( 'This will regenerate all shop thumbnails to match your theme and/or image settings.', 'woocommerce' ),
+				'name'             => __( 'Regenerate shop thumbnails', 'woocommerce' ),
+				'button'           => __( 'Regenerate', 'woocommerce' ),
+				'desc'             => __( 'This will regenerate all shop thumbnails to match your theme and/or image settings.', 'woocommerce' ),
+				'requires_refresh' => true,
 			),
 			'db_update_routine'                    => array(
 				'name'   => __( 'Update database', 'woocommerce' ),
@@ -250,9 +253,78 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
 			unset( $tools['clear_template_cache'] );
 		}

+		if ( wc_update_product_lookup_tables_is_running() ) {
+			$tools['regenerate_product_lookup_tables']['desc'] = __(
+				'Product lookup table regeneration is running in the background. Product display, sorting, and reports may not be accurate until this finishes.',
+				'woocommerce'
+			);
+			if ( Constants::is_true( 'DISABLE_WP_CRON' ) ) {
+				$tools['regenerate_product_lookup_tables']['desc'] .= ' ' . __(
+					'WP-Cron has been disabled, which may prevent this update from completing.',
+					'woocommerce'
+				);
+			}
+			$tools['regenerate_product_lookup_tables']['button']      = __( 'Regenerating in progress', 'woocommerce' );
+			$tools['regenerate_product_lookup_tables']['disabled']    = true;
+			$tools['regenerate_product_lookup_tables']['status_text'] = $this->get_regenerating_lookup_table_status_text();
+		}
+
+		if ( isset( $tools['regenerate_thumbnails'] ) && WC_Regenerate_Images::is_regeneration_in_progress() ) {
+			$tools['regenerate_thumbnails']['desc']        = __(
+				'Thumbnail regeneration is running in the background. Depending on the number of images in your store, this may take a while.',
+				'woocommerce'
+			);
+			$tools['regenerate_thumbnails']['button']      = __( 'Regenerating in progress', 'woocommerce' );
+			$tools['regenerate_thumbnails']['disabled']    = true;
+			$tools['regenerate_thumbnails']['status_text'] = $this->get_regenerating_thumbnails_status_text();
+		}
+
 		return apply_filters( 'woocommerce_debug_tools', $tools );
 	}

+	/**
+	 * Get the tools-page status text for product lookup table regeneration.
+	 *
+	 * @return string
+	 */
+	private function get_regenerating_lookup_table_status_text() {
+		$pending_actions_url = admin_url( 'admin.php?page=wc-status&tab=action-scheduler&s=wc_update_product_lookup_tables&status=pending' );
+		$cron_disabled       = Constants::is_true( 'DISABLE_WP_CRON' );
+		$progress_label      = $cron_disabled
+			? __( 'View queued updates', 'woocommerce' )
+			: __( 'View progress', 'woocommerce' );
+
+		return sprintf(
+			'<a href="%1$s">%2$s</a>',
+			esc_url( $pending_actions_url ),
+			esc_html( $progress_label )
+		);
+	}
+
+	/**
+	 * Get the tools-page status text for thumbnail regeneration.
+	 *
+	 * @return string
+	 */
+	private function get_regenerating_thumbnails_status_text() {
+		$cancel_url = wp_nonce_url(
+			add_query_arg(
+				'wc-hide-notice',
+				'regenerating_thumbnails',
+				admin_url( 'admin.php?page=wc-status&tab=tools' )
+			),
+			'woocommerce_hide_notices_nonce',
+			'_wc_notice_nonce'
+		);
+
+		return sprintf(
+			'<a class="button button-large" href="%1$s" aria-label="%2$s">%3$s</a>',
+			esc_url( $cancel_url ),
+			esc_attr__( 'Cancel thumbnail regeneration', 'woocommerce' ),
+			esc_html__( 'Cancel', 'woocommerce' )
+		);
+	}
+
 	/**
 	 * Get a list of system status tools.
 	 *
diff --git a/plugins/woocommerce/includes/wc-product-functions.php b/plugins/woocommerce/includes/wc-product-functions.php
index a02b896cd61..5e994709d17 100644
--- a/plugins/woocommerce/includes/wc-product-functions.php
+++ b/plugins/woocommerce/includes/wc-product-functions.php
@@ -1914,10 +1914,6 @@ function wc_update_product_lookup_tables() {

 	$is_cli = Constants::is_true( 'WP_CLI' );

-	if ( ! $is_cli ) {
-		WC_Admin_Notices::add_notice( 'regenerating_lookup_table' );
-	}
-
 	// Note that the table is not yet generated.
 	update_option( 'woocommerce_product_lookup_table_is_generating', true );

diff --git a/plugins/woocommerce/includes/wc-update-functions.php b/plugins/woocommerce/includes/wc-update-functions.php
index 677adb01294..e9ae31f42e5 100644
--- a/plugins/woocommerce/includes/wc-update-functions.php
+++ b/plugins/woocommerce/includes/wc-update-functions.php
@@ -1071,8 +1071,6 @@ function wc_update_260_options() {
 	if ( 'no' === get_option( 'woocommerce_calc_shipping' ) ) {
 		update_option( 'woocommerce_ship_to_countries', 'disabled' );
 	}
-
-	WC_Admin_Notices::add_notice( 'legacy_shipping' );
 }

 /**
@@ -3402,23 +3400,18 @@ function wc_update_1060_add_woo_idx_comment_approved_type_index(): void {
 }

 /**
- * Add an admin notice about HPOS sync-on-read being disabled by default for sites
- * that have both HPOS and data synchronization enabled.
+ * Previously added an admin notice about HPOS sync-on-read being disabled by default.
+ *
+ * HPOS sync-on-read status is now shown in Site Health.
  *
  * @since 10.7.0
  *
  * @return void
  */
 function wc_update_1070_disable_hpos_sync_on_read(): void {
-	if ( 'yes' !== get_option( 'woocommerce_custom_orders_table_enabled' ) ) {
-		return;
-	}
-
-	if ( 'yes' !== get_option( 'woocommerce_custom_orders_table_data_sync_enabled' ) ) {
-		return;
-	}
-
-	WC_Admin_Notices::add_notice( 'hpos_sync_on_read_disabled' );
+	// Intentionally empty. The admin notice this update function used to queue has been
+	// replaced by a Site Health check, but the function must be kept so the 10.7.0 entry
+	// in WC_Install::$db_updates remains valid for stores upgrading from older versions.
 }

 /**
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 3ca5c07a0fb..8bf4aed5745 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -8172,12 +8172,6 @@ parameters:
 			count: 1
 			path: includes/admin/views/html-notice-custom.php

-		-
-			message: '#^Variable \$msg might not be defined\.$#'
-			identifier: variable.undefined
-			count: 1
-			path: includes/admin/views/html-notice-wp-php-minimum-requirements.php
-
 		-
 			message: '#^Parameter \#1 \$value of function wc_format_localized_decimal expects string, int given\.$#'
 			identifier: argument.type
@@ -14370,12 +14364,6 @@ parameters:
 			count: 1
 			path: includes/class-wc-regenerate-images.php

-		-
-			message: '#^Method WC_Regenerate_Images\:\:regenerating_notice\(\) has no return type specified\.$#'
-			identifier: missingType.return
-			count: 1
-			path: includes/class-wc-regenerate-images.php
-
 		-
 			message: '#^Method WC_Regenerate_Images\:\:resize_and_return_image\(\) should return string but returns array\.$#'
 			identifier: return.type
@@ -20532,12 +20520,6 @@ parameters:
 			count: 1
 			path: includes/integrations/maxmind-geolocation/class-wc-integration-maxmind-geolocation.php

-		-
-			message: '#^Method WC_Integration_MaxMind_Geolocation\:\:add_missing_license_key_notice\(\) has no return type specified\.$#'
-			identifier: missingType.return
-			count: 1
-			path: includes/integrations/maxmind-geolocation/class-wc-integration-maxmind-geolocation.php
-
 		-
 			message: '#^Method WC_Integration_MaxMind_Geolocation\:\:admin_options\(\) has no return type specified\.$#'
 			identifier: missingType.return
diff --git a/plugins/woocommerce/src/Admin/PluginsHelper.php b/plugins/woocommerce/src/Admin/PluginsHelper.php
index 3a71e1cd55e..e0509dd1884 100644
--- a/plugins/woocommerce/src/Admin/PluginsHelper.php
+++ b/plugins/woocommerce/src/Admin/PluginsHelper.php
@@ -626,17 +626,11 @@ class PluginsHelper {

 		$notice_type = WC_Helper_Updater::get_woo_connect_notice_type();

-		if ( 'none' === $notice_type ) {
+		// The outdated plugin risk state is reported in Site Health.
+		if ( in_array( $notice_type, array( 'none', 'long' ), true ) ) {
 			return;
 		}

-		$notice_string = '';
-
-		if ( 'long' === $notice_type ) {
-			$notice_string .= __( 'Your store might be at risk as you are running old versions of WooCommerce plugins.', 'woocommerce' );
-			$notice_string .= ' ';
-		}
-
 		$connect_page_url = add_query_arg(
 			array(
 				'page'         => 'wc-admin',
@@ -648,7 +642,7 @@ class PluginsHelper {
 			admin_url( 'admin.php' )
 		);

-		$notice_string .= sprintf(
+		$notice_string = sprintf(
 			/* translators: %s: Connect page URL */
 			__( '<a id="woo-connect-notice-url" href="%s">Connect your store</a> to WooCommerce.com to get updates and streamlined support for your subscriptions.', 'woocommerce' ),
 			esc_url( $connect_page_url )
@@ -671,7 +665,7 @@ class PluginsHelper {

 		$notice_type = WC_Helper_Updater::get_woo_connect_notice_type();

-		if ( 'none' === $notice_type ) {
+		if ( in_array( $notice_type, array( 'none', 'long' ), true ) ) {
 			return;
 		}

diff --git a/plugins/woocommerce/src/Internal/Admin/SiteHealth.php b/plugins/woocommerce/src/Internal/Admin/SiteHealth.php
index ac3b593d417..b222a7987e0 100644
--- a/plugins/woocommerce/src/Internal/Admin/SiteHealth.php
+++ b/plugins/woocommerce/src/Internal/Admin/SiteHealth.php
@@ -3,8 +3,20 @@
  * Customize Site Health recommendations for WooCommerce.
  */

+declare( strict_types = 1 );
+
 namespace Automattic\WooCommerce\Internal\Admin;

+use Automattic\WooCommerce\Enums\DefaultCustomerAddress;
+use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer;
+use Automattic\WooCommerce\Utilities\OrderUtil;
+use WC_Admin_Notices;
+use WC_Admin_Status;
+use WC_Helper_Updater;
+use WC_Install;
+use WC_Order_Query;
+use WC_Product_Query;
+
 defined( 'ABSPATH' ) || exit;

 /**
@@ -33,6 +45,354 @@ class SiteHealth {
 	 */
 	public function __construct() {
 		add_filter( 'site_status_should_suggest_persistent_object_cache', array( $this, 'should_suggest_persistent_object_cache' ) );
+		add_filter( 'site_status_tests', array( $this, 'handle_site_status_tests' ) );
+	}
+
+	/**
+	 * Register WooCommerce Site Health status tests.
+	 *
+	 * @internal
+	 *
+	 * @param array $tests Site Health tests.
+	 * @return array
+	 */
+	public function handle_site_status_tests( $tests ) {
+		$tests['direct'] = $tests['direct'] ?? array();
+
+		foreach ( $this->get_woocommerce_site_health_tests() as $test_id => $test ) {
+			$tests['direct'][ $test_id ] = array(
+				'label'     => $test['label'],
+				'test'      => function () use ( $test_id ) {
+					return $this->run_test( $test_id );
+				},
+				'skip_cron' => true,
+			);
+		}
+
+		return $tests;
+	}
+
+	/**
+	 * Run a registered WooCommerce Site Health test by ID.
+	 *
+	 * @param string $test_id Site Health test ID.
+	 * @return array
+	 */
+	public function run_test( string $test_id ): array {
+		$tests = $this->get_woocommerce_site_health_tests();
+
+		if ( ! isset( $tests[ $test_id ] ) ) {
+			return array();
+		}
+
+		$test         = $tests[ $test_id ];
+		$check_result = ( $test['check'] )();
+
+		if ( is_array( $check_result ) ) {
+			$context = $check_result;
+			$is_good = empty( $check_result );
+		} else {
+			$context = null;
+			$is_good = (bool) $check_result;
+		}
+
+		$data        = $is_good ? $test['good'] : $test['fail'];
+		$description = is_callable( $data['description'] )
+			? ( $data['description'] )( $context )
+			: $data['description'];
+
+		$actions_html = '';
+		if ( ! $is_good && ! empty( $data['actions'] ) ) {
+			foreach ( $data['actions'] as $action ) {
+				$url           = is_callable( $action['url'] ) ? ( $action['url'] )( $context ) : $action['url'];
+				$actions_html .= $this->get_site_health_action( $url, $action['label'], ! empty( $action['new_tab'] ) );
+			}
+		}
+
+		return array(
+			'label'       => $data['label'],
+			'status'      => $is_good ? 'good' : ( $data['status'] ?? 'recommended' ),
+			'badge'       => array(
+				'label' => 'security' === $test['badge'] ? __( 'Security', 'woocommerce' ) : __( 'Performance', 'woocommerce' ),
+				'color' => 'blue',
+			),
+			'description' => '<p>' . esc_html( $description ) . '</p>',
+			'actions'     => $actions_html,
+			'test'        => $test_id,
+		);
+	}
+
+	/**
+	 * Get the registry of WooCommerce Site Health tests.
+	 *
+	 * Each entry describes one test:
+	 *   - label: visible test name in Site Health.
+	 *   - badge: 'security' or 'performance'.
+	 *   - check: callable returning bool (true = good) or array (empty = good, otherwise context for description/actions).
+	 *   - good: result data when the check passes (label, description).
+	 *   - fail: result data when the check fails (status defaults to 'recommended', label, description, optional actions).
+	 *
+	 * @return array<string, array>
+	 */
+	protected function get_woocommerce_site_health_tests(): array {
+		return array(
+			'woocommerce_secure_connection'            => array(
+				'label' => __( 'WooCommerce secure connection', 'woocommerce' ),
+				'badge' => 'security',
+				'check' => array( $this, 'is_store_using_secure_connection' ),
+				'good'  => array(
+					'label'       => __( 'WooCommerce store uses a secure connection', 'woocommerce' ),
+					'description' => __( 'Customer data is protected by HTTPS on your store pages.', 'woocommerce' ),
+				),
+				'fail'  => array(
+					'status'      => 'critical',
+					'label'       => __( 'WooCommerce store is not using a secure connection', 'woocommerce' ),
+					'description' => __( 'WooCommerce strongly recommends serving your entire store over HTTPS to help keep customer data secure.', 'woocommerce' ),
+					'actions'     => array(
+						array(
+							'url'     => 'https://woocommerce.com/document/ssl-and-https/',
+							'label'   => __( 'Learn more about HTTPS', 'woocommerce' ),
+							'new_tab' => true,
+						),
+					),
+				),
+			),
+			'woocommerce_uploads_directory_protection' => array(
+				'label' => __( 'WooCommerce uploads directory protection', 'woocommerce' ),
+				'badge' => 'security',
+				'check' => array( $this, 'is_uploads_directory_protected' ),
+				'good'  => array(
+					'label'       => __( 'WooCommerce uploads directory is protected', 'woocommerce' ),
+					'description' => __( 'The directory used for downloadable product files is not browsable from the web.', 'woocommerce' ),
+				),
+				'fail'  => array(
+					'status'      => 'critical',
+					'label'       => __( 'WooCommerce uploads directory is browsable from the web', 'woocommerce' ),
+					'description' => __( 'Directory browsing can expose downloadable product files. Configure your web server to prevent directory indexing for the WooCommerce uploads directory.', 'woocommerce' ),
+					'actions'     => array(
+						array(
+							'url'     => 'https://woocommerce.com/document/digital-downloadable-product-handling/#protecting-your-uploads-directory',
+							'label'   => __( 'Learn how to protect downloads', 'woocommerce' ),
+							'new_tab' => true,
+						),
+					),
+				),
+			),
+			'woocommerce_template_overrides'           => array(
+				'label' => __( 'WooCommerce template overrides', 'woocommerce' ),
+				'badge' => 'performance',
+				'check' => fn() => ! $this->has_outdated_template_overrides(),
+				'good'  => array(
+					'label'       => __( 'WooCommerce template overrides are up to date', 'woocommerce' ),
+					'description' => __( 'The active theme does not contain outdated WooCommerce template overrides.', 'woocommerce' ),
+				),
+				'fail'  => array(
+					'label'       => __( 'Your theme contains outdated WooCommerce template overrides', 'woocommerce' ),
+					'description' => __( 'Outdated template overrides may not be compatible with the current version of WooCommerce.', 'woocommerce' ),
+					'actions'     => array(
+						array(
+							'url'   => admin_url( 'admin.php?page=wc-status#status-table-templates' ),
+							'label' => __( 'View affected templates', 'woocommerce' ),
+						),
+						array(
+							'url'     => 'https://woocommerce.com/document/template-structure/',
+							'label'   => __( 'Learn more about templates', 'woocommerce' ),
+							'new_tab' => true,
+						),
+					),
+				),
+			),
+			'woocommerce_maxmind_geolocation'          => array(
+				'label' => __( 'WooCommerce MaxMind geolocation', 'woocommerce' ),
+				'badge' => 'performance',
+				'check' => fn() => ! $this->needs_maxmind_license_key(),
+				'good'  => array(
+					'label'       => __( 'WooCommerce geolocation is configured', 'woocommerce' ),
+					'description' => __( 'Your store does not require further MaxMind geolocation configuration.', 'woocommerce' ),
+				),
+				'fail'  => array(
+					'label'       => __( 'WooCommerce geolocation needs a MaxMind license key', 'woocommerce' ),
+					'description' => __( 'A MaxMind license key is required when the default customer location uses geolocation.', 'woocommerce' ),
+					'actions'     => array(
+						array(
+							'url'   => admin_url( 'admin.php?page=wc-settings&tab=integration&section=maxmind_geolocation' ),
+							'label' => __( 'Configure MaxMind geolocation', 'woocommerce' ),
+						),
+						array(
+							'url'   => admin_url( 'admin.php?page=wc-settings&tab=general' ),
+							'label' => __( 'Change default customer location', 'woocommerce' ),
+						),
+					),
+				),
+			),
+			'woocommerce_download_method'              => array(
+				'label' => __( 'WooCommerce download method', 'woocommerce' ),
+				'badge' => 'security',
+				'check' => fn() => 'redirect' !== get_option( 'woocommerce_file_download_method' ),
+				'good'  => array(
+					'label'       => __( 'WooCommerce uses a supported download method', 'woocommerce' ),
+					'description' => __( 'Your store is not configured to use the deprecated Redirect only download method.', 'woocommerce' ),
+				),
+				'fail'  => array(
+					'label'       => __( 'WooCommerce is using the deprecated Redirect only download method', 'woocommerce' ),
+					'description' => __( 'Redirect only is deprecated for downloadable products. Choose a different download method, or allow redirects only as a fallback.', 'woocommerce' ),
+					'actions'     => array(
+						array(
+							'url'   => admin_url( 'admin.php?page=wc-settings&tab=products&section=downloadable' ),
+							'label' => __( 'Review download settings', 'woocommerce' ),
+						),
+					),
+				),
+			),
+			'woocommerce_database_tables'              => array(
+				'label' => __( 'WooCommerce database tables', 'woocommerce' ),
+				'badge' => 'performance',
+				'check' => fn() => WC_Install::get_missing_base_tables(),
+				'good'  => array(
+					'label'       => __( 'WooCommerce database tables are present', 'woocommerce' ),
+					'description' => __( 'All required WooCommerce database tables exist.', 'woocommerce' ),
+				),
+				'fail'  => array(
+					'status'      => 'critical',
+					'label'       => __( 'WooCommerce database tables are missing', 'woocommerce' ),
+					'description' => fn( array $missing_tables ) => sprintf(
+						/* translators: %s: Comma-separated list of missing database tables. */
+						__( 'One or more tables required for WooCommerce to function are missing: %s.', 'woocommerce' ),
+						implode( ', ', $missing_tables )
+					),
+					'actions'     => array(
+						array(
+							'url'   => wp_nonce_url( admin_url( 'admin.php?page=wc-status&tab=tools&action=verify_db_tables' ), 'debug_action' ),
+							'label' => __( 'Check database tables again', 'woocommerce' ),
+						),
+					),
+				),
+			),
+			'woocommerce_hpos_sync_on_read'            => array(
+				'label' => __( 'WooCommerce HPOS sync on read', 'woocommerce' ),
+				'badge' => 'performance',
+				'check' => fn() => ! $this->should_show_hpos_sync_on_read_status(),
+				'good'  => array(
+					'label'       => __( 'WooCommerce HPOS sync on read does not require attention', 'woocommerce' ),
+					'description' => __( 'Your current order storage configuration is not affected by the HPOS sync-on-read change.', 'woocommerce' ),
+				),
+				'fail'  => array(
+					'label'       => __( 'WooCommerce HPOS sync on read is disabled', 'woocommerce' ),
+					'description' => __( 'Compatibility mode for HPOS no longer pulls order changes made to the posts database back into orders automatically. Review this if custom code modifies orders outside WooCommerce.', 'woocommerce' ),
+					'actions'     => array(
+						array(
+							'url'   => admin_url( 'admin.php?page=wc-settings&tab=advanced&section=features' ),
+							'label' => __( 'Review order storage settings', 'woocommerce' ),
+						),
+						array(
+							'url'     => 'https://developer.woocommerce.com/2026/02/16/hpos-sync-on-read-to-be-disabled-by-default-in-woocommerce-10-7/',
+							'label'   => __( 'Learn more about this change', 'woocommerce' ),
+							'new_tab' => true,
+						),
+					),
+				),
+			),
+			'woocommerce_legacy_shipping_methods'      => array(
+				'label' => __( 'WooCommerce legacy shipping methods', 'woocommerce' ),
+				'badge' => 'performance',
+				'check' => fn() => ! $this->has_legacy_shipping_methods_enabled(),
+				'good'  => array(
+					'label'       => __( 'WooCommerce legacy shipping methods are not enabled', 'woocommerce' ),
+					'description' => __( 'Your store is not using deprecated legacy shipping methods.', 'woocommerce' ),
+				),
+				'fail'  => array(
+					'label'       => __( 'WooCommerce legacy shipping methods are enabled', 'woocommerce' ),
+					'description' => __( 'Legacy shipping methods are deprecated. Set up rates with shipping zones instead.', 'woocommerce' ),
+					'actions'     => array(
+						array(
+							'url'   => admin_url( 'admin.php?page=wc-settings&tab=shipping' ),
+							'label' => __( 'Review shipping zones', 'woocommerce' ),
+						),
+						array(
+							'url'     => 'https://woocommerce.com/document/setting-up-shipping-zones/',
+							'label'   => __( 'Learn more about shipping zones', 'woocommerce' ),
+							'new_tab' => true,
+						),
+					),
+				),
+			),
+			'woocommerce_shipping_methods'             => array(
+				'label' => __( 'WooCommerce shipping methods', 'woocommerce' ),
+				'badge' => 'performance',
+				'check' => fn() => ! $this->needs_shipping_methods(),
+				'good'  => array(
+					'label'       => __( 'WooCommerce shipping methods are configured', 'woocommerce' ),
+					'description' => __( 'Your store does not need additional shipping method setup.', 'woocommerce' ),
+				),
+				'fail'  => array(
+					'status'      => 'critical',
+					'label'       => __( 'WooCommerce shipping is enabled but no shipping methods are configured', 'woocommerce' ),
+					'description' => __( 'Customers cannot purchase physical goods until at least one shipping method is available.', 'woocommerce' ),
+					'actions'     => array(
+						array(
+							'url'   => admin_url( 'admin.php?page=wc-settings&tab=shipping' ),
+							'label' => __( 'Set up shipping methods', 'woocommerce' ),
+						),
+						array(
+							'url'     => 'https://woocommerce.com/document/setting-up-shipping-zones/',
+							'label'   => __( 'Learn more about shipping zones', 'woocommerce' ),
+							'new_tab' => true,
+						),
+					),
+				),
+			),
+			'woocommerce_approved_download_directories_sync' => array(
+				'label' => __( 'WooCommerce approved download directories', 'woocommerce' ),
+				'badge' => 'security',
+				'check' => fn() => ! WC_Admin_Notices::has_notice( 'download_directories_sync_complete' ),
+				'good'  => array(
+					'label'       => __( 'WooCommerce approved download directories do not require review', 'woocommerce' ),
+					'description' => __( 'There is no completed approved download directories sync waiting for review.', 'woocommerce' ),
+				),
+				'fail'  => array(
+					'label'       => __( 'WooCommerce approved download directories need review', 'woocommerce' ),
+					'description' => __( 'The approved product download directories list was updated. Review it to confirm downloadable product files remain protected.', 'woocommerce' ),
+					'actions'     => array(
+						array(
+							'url'   => admin_url( 'admin.php?page=wc-settings&tab=products&section=download_urls' ),
+							'label' => __( 'Review approved directories', 'woocommerce' ),
+						),
+						array(
+							'url'   => wp_nonce_url(
+								add_query_arg( 'wc-hide-notice', 'download_directories_sync_complete', admin_url( 'site-health.php' ) ),
+								'woocommerce_hide_notices_nonce',
+								'_wc_notice_nonce'
+							),
+							'label' => __( 'Mark as reviewed', 'woocommerce' ),
+						),
+						array(
+							'url'     => 'https://woocommerce.com/document/approved-download-directories',
+							'label'   => __( 'Learn more about approved directories', 'woocommerce' ),
+							'new_tab' => true,
+						),
+					),
+				),
+			),
+			'woocommerce_com_extension_updates'        => array(
+				'label' => __( 'WooCommerce.com plugin updates', 'woocommerce' ),
+				'badge' => 'security',
+				'check' => fn() => ! $this->has_outdated_woocommerce_com_plugins(),
+				'good'  => array(
+					'label'       => __( 'WooCommerce.com plugin updates do not require attention', 'woocommerce' ),
+					'description' => __( 'Your store can receive WooCommerce.com plugin updates, or no outdated WooCommerce.com plugins were found.', 'woocommerce' ),
+				),
+				'fail'  => array(
+					'label'       => __( 'WooCommerce.com plugin updates require attention', 'woocommerce' ),
+					'description' => __( 'Your store might be at risk because it is running old versions of WooCommerce plugins. Connect your store to WooCommerce.com to get updates and streamlined support for your subscriptions.', 'woocommerce' ),
+					'actions'     => array(
+						array(
+							'url'   => $this->get_woocommerce_com_extensions_url(),
+							'label' => __( 'Connect your store', 'woocommerce' ),
+						),
+					),
+				),
+			),
+		);
 	}

 	/**
@@ -60,7 +420,7 @@ class SiteHealth {
 			try {
 				switch ( $key ) {
 					case 'orders':
-						$orders_query   = new \WC_Order_Query(
+						$orders_query   = new WC_Order_Query(
 							array(
 								'status'   => 'any',
 								'limit'    => 1,
@@ -75,7 +435,7 @@ class SiteHealth {
 						break;

 					case 'products':
-						$products_query   = new \WC_Product_Query(
+						$products_query   = new WC_Product_Query(
 							array(
 								'status'   => 'any',
 								'limit'    => 1,
@@ -100,4 +460,235 @@ class SiteHealth {

 		return $check;
 	}
+
+	/**
+	 * Create a Site Health action link.
+	 *
+	 * @param string $url              URL.
+	 * @param string $label            Link label.
+	 * @param bool   $opens_in_new_tab Whether the link opens in a new tab.
+	 * @return string
+	 */
+	private function get_site_health_action( $url, $label, $opens_in_new_tab = false ) {
+		if ( $opens_in_new_tab ) {
+			return sprintf(
+				'<p><a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s' .
+					'<span class="screen-reader-text"> %3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
+				esc_url( $url ),
+				esc_html( $label ),
+				esc_html__( '(opens in a new tab)', 'woocommerce' )
+			);
+		}
+
+		return sprintf(
+			'<p><a href="%1$s">%2$s</a></p>',
+			esc_url( $url ),
+			esc_html( $label )
+		);
+	}
+
+	/**
+	 * Determine whether the store is using HTTPS for WooCommerce pages.
+	 *
+	 * @return bool
+	 */
+	private function is_store_using_secure_connection() {
+		$shop_page = wc_get_page_permalink( 'shop' );
+
+		return is_ssl() && 'https' === substr( $shop_page, 0, 5 );
+	}
+
+	/**
+	 * Check if the WooCommerce uploads directory is protected from directory browsing.
+	 *
+	 * @return bool
+	 */
+	private function is_uploads_directory_protected() {
+		$cache_key = '_woocommerce_upload_directory_status';
+		$status    = get_transient( $cache_key );
+
+		if ( false !== $status ) {
+			return 'protected' === $status;
+		}
+
+		$uploads  = wp_get_upload_dir();
+		$response = wp_safe_remote_get(
+			esc_url_raw( $uploads['baseurl'] . '/woocommerce_uploads/' ),
+			array(
+				'redirection' => 0,
+			)
+		);
+
+		if ( is_wp_error( $response ) ) {
+			return false;
+		}
+
+		$response_code = intval( wp_remote_retrieve_response_code( $response ) );
+
+		if ( 0 === $response_code ) {
+			return false;
+		}
+
+		$response_content = wp_remote_retrieve_body( $response );
+		$is_protected     = ( 200 === $response_code && empty( $response_content ) ) || ( 200 !== $response_code );
+
+		set_transient( $cache_key, $is_protected ? 'protected' : 'unprotected', DAY_IN_SECONDS );
+
+		return $is_protected;
+	}
+
+	/**
+	 * Determine whether the active theme has outdated WooCommerce template overrides.
+	 *
+	 * @return bool
+	 */
+	private function has_outdated_template_overrides() {
+		$core_templates = WC_Admin_Status::scan_template_files( WC()->plugin_path() . '/templates' );
+
+		foreach ( $core_templates as $file ) {
+			$theme_file = $this->get_theme_template_override_path( $file );
+
+			if ( ! $theme_file ) {
+				continue;
+			}
+
+			$core_version  = WC_Admin_Status::get_file_version( WC()->plugin_path() . '/templates/' . $file );
+			$theme_version = WC_Admin_Status::get_file_version( $theme_file );
+
+			if ( $core_version && $theme_version && version_compare( $theme_version, $core_version, '<' ) ) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Get the path to a theme template override, if one exists.
+	 *
+	 * @param string $file Template file.
+	 * @return string|false
+	 */
+	private function get_theme_template_override_path( $file ) {
+		$paths = array(
+			get_stylesheet_directory() . '/' . $file,
+			get_stylesheet_directory() . '/' . WC()->template_path() . $file,
+			get_template_directory() . '/' . $file,
+			get_template_directory() . '/' . WC()->template_path() . $file,
+		);
+
+		foreach ( $paths as $path ) {
+			if ( file_exists( $path ) ) {
+				return $path;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Determine whether MaxMind geolocation requires a license key.
+	 *
+	 * @return bool
+	 */
+	private function needs_maxmind_license_key() {
+		/**
+		 * Filter whether MaxMind geolocation notices should be displayed.
+		 *
+		 * Previously used to suppress the MaxMind license key admin notice. Honoured
+		 * here so the equivalent Site Health warning can be suppressed the same way.
+		 *
+		 * @since 3.9.0
+		 *
+		 * @param bool $display Whether to display MaxMind geolocation notices.
+		 */
+		if ( ! apply_filters( 'woocommerce_maxmind_geolocation_display_notices', true ) ) {
+			return false;
+		}
+
+		$default_address = get_option( 'woocommerce_default_customer_address' );
+
+		if ( ! in_array( $default_address, array( DefaultCustomerAddress::GEOLOCATION, DefaultCustomerAddress::GEOLOCATION_AJAX ), true ) ) {
+			return false;
+		}
+
+		$integration_options = get_option( 'woocommerce_maxmind_geolocation_settings' );
+
+		return empty( $integration_options['license_key'] );
+	}
+
+	/**
+	 * Determine whether the HPOS sync-on-read status should be shown.
+	 *
+	 * @return bool
+	 */
+	private function should_show_hpos_sync_on_read_status() {
+		return OrderUtil::custom_orders_table_usage_is_enabled()
+			&& wc_get_container()->get( DataSynchronizer::class )->data_sync_is_enabled();
+	}
+
+	/**
+	 * Determine whether any legacy shipping methods are enabled.
+	 *
+	 * @return bool
+	 */
+	private function has_legacy_shipping_methods_enabled() {
+		$legacy_methods = array( 'flat_rate', 'free_shipping', 'international_delivery', 'local_delivery', 'local_pickup' );
+
+		foreach ( $legacy_methods as $method ) {
+			$options = get_option( 'woocommerce_' . $method . '_settings' );
+
+			if ( $options && isset( $options['enabled'] ) && 'yes' === $options['enabled'] ) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Determine whether shipping is enabled but no methods are configured.
+	 *
+	 * @return bool
+	 */
+	private function needs_shipping_methods() {
+		if ( ! wc_shipping_enabled() ) {
+			return false;
+		}
+
+		$product_count = wp_count_posts( 'product' );
+
+		return $product_count->publish > 0 && 0 === wc_get_shipping_method_count();
+	}
+
+	/**
+	 * Determine whether the store has outdated WooCommerce.com plugins that need connection to update.
+	 *
+	 * @return bool
+	 */
+	private function has_outdated_woocommerce_com_plugins() {
+		if ( ! class_exists( WC_Helper_Updater::class ) ) {
+			return false;
+		}
+
+		return 'long' === WC_Helper_Updater::get_woo_connect_notice_type();
+	}
+
+	/**
+	 * Get the WooCommerce.com extensions admin URL.
+	 *
+	 * @return string
+	 */
+	private function get_woocommerce_com_extensions_url() {
+		return add_query_arg(
+			array(
+				'page'         => 'wc-admin',
+				'tab'          => 'my-subscriptions',
+				'path'         => rawurlencode( '/extensions' ),
+				'utm_source'   => 'site-health',
+				'utm_campaign' => 'woo_extension_updates',
+			),
+			admin_url( 'admin.php' )
+		);
+	}
 }
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 c08235417ed..389fecc90da 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-install-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-install-test.php
@@ -9,9 +9,9 @@ use Automattic\WooCommerce\Admin\Notes\Note;
 class WC_Install_Test extends \WC_Unit_Test_Case {

 	/**
-	 * Test if verify base table can detect missing table and adds/remove a notice.
+	 * Test if verify base table can detect missing tables and clear the stored missing table list.
 	 */
-	public function test_verify_base_tables_adds_and_remove_notice() {
+	public function test_verify_base_tables_stores_and_removes_missing_tables() {
 		global $wpdb;

 		// Remove drop filter because we do want to drop temp table if it exists.
@@ -39,13 +39,13 @@ class WC_Install_Test extends \WC_Unit_Test_Case {
 		add_filter( 'query', array( $this, '_drop_temporary_tables' ) );

 		$this->assertContains( $original_table_name, $missing_tables );
-		$this->assertContains( 'base_tables_missing', \WC_Admin_Notices::get_notices() );
+		$this->assertContains( $original_table_name, get_option( 'woocommerce_schema_missing_tables', array() ) );

 		// Ideally, no missing table anymore because we have switched back table name.
 		$missing_tables = \WC_Install::verify_base_tables();

 		$this->assertNotContains( $original_table_name, $missing_tables );
-		$this->assertNotContains( 'base_tables_missing', \WC_Admin_Notices::get_notices() );
+		$this->assertSame( array(), get_option( 'woocommerce_schema_missing_tables', array() ) );
 	}


@@ -82,7 +82,7 @@ class WC_Install_Test extends \WC_Unit_Test_Case {

 		// Ideally, no missing table because verify base tables created the table as well.
 		$this->assertNotContains( $original_table_name, $missing_tables );
-		$this->assertNotContains( 'base_tables_missing', \WC_Admin_Notices::get_notices() );
+		$this->assertSame( array(), get_option( 'woocommerce_schema_missing_tables', array() ) );
 	}

 	/**
diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller-test.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller-test.php
index 8d2950e0349..dfeea681116 100644
--- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller-test.php
+++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller-test.php
@@ -145,4 +145,83 @@ class WC_REST_System_Status_Tools_V2_Controller_Test extends WC_REST_Unit_Test_C
 		$prefix_after = WC_Cache_Helper::get_cache_prefix( 'woocommerce-attributes' );
 		$this->assertNotEquals( $prefix_before, $prefix_after, 'Cache prefix should have changed after invalidation' );
 	}
+
+	/**
+	 * @testdox Product lookup table regeneration status is shown alongside the matching tool.
+	 */
+	public function test_get_tools_shows_product_lookup_table_regeneration_status() {
+		as_unschedule_all_actions( '', array(), 'wc_update_product_lookup_tables' );
+
+		// The status link text depends on whether WP-Cron is disabled, which it is in the
+		// test environment. Pin it so the assertions are deterministic rather than environment-dependent.
+		\Automattic\Jetpack\Constants::set_constant( 'DISABLE_WP_CRON', false );
+
+		try {
+			WC()->queue()->schedule_single(
+				time() + HOUR_IN_SECONDS,
+				'wc_update_product_lookup_tables_column',
+				array(
+					'column' => 'min_max_price',
+				),
+				'wc_update_product_lookup_tables'
+			);
+
+			$tools = $this->endpoint->get_tools();
+			$tool  = $tools['regenerate_product_lookup_tables'];
+
+			$this->assertTrue( $tool['requires_refresh'] );
+			$this->assertTrue( $tool['disabled'] );
+			$this->assertEquals( 'Regenerating in progress', $tool['button'] );
+			$this->assertStringContainsString( 'View progress', $tool['status_text'] );
+			$this->assertStringContainsString( 'wc_update_product_lookup_tables', $tool['status_text'] );
+			$this->assertStringNotContainsString( 'dashicons-update', $tool['status_text'] );
+
+			// When WP-Cron is disabled the link nudges to the queued updates instead.
+			\Automattic\Jetpack\Constants::set_constant( 'DISABLE_WP_CRON', true );
+			$tool = $this->endpoint->get_tools()['regenerate_product_lookup_tables'];
+			$this->assertStringContainsString( 'View queued updates', $tool['status_text'] );
+		} finally {
+			\Automattic\Jetpack\Constants::clear_single_constant( 'DISABLE_WP_CRON' );
+			as_unschedule_all_actions( '', array(), 'wc_update_product_lookup_tables' );
+		}
+	}
+
+	/**
+	 * @testdox Thumbnail regeneration status is shown alongside the matching tool.
+	 */
+	public function test_get_tools_shows_thumbnail_regeneration_status() {
+		$background_process_property = new ReflectionProperty( WC_Regenerate_Images::class, 'background_process' );
+		$background_process_property->setAccessible( true );
+		$original_background_process = $background_process_property->getValue();
+
+		$background_process_property->setValue(
+			null,
+			new class() {
+				/**
+				 * Simulate a non-empty thumbnail regeneration queue.
+				 *
+				 * @return bool
+				 */
+				public function is_running() {
+					return false;
+				}
+			}
+		);
+
+		try {
+			$tools = $this->endpoint->get_tools();
+			$tool  = $tools['regenerate_thumbnails'];
+
+			$this->assertTrue( $tool['requires_refresh'] );
+			$this->assertTrue( $tool['disabled'] );
+			$this->assertEquals( 'Regenerating in progress', $tool['button'] );
+			$this->assertStringContainsString( '>Cancel</a>', $tool['status_text'] );
+			$this->assertStringContainsString( 'aria-label="Cancel thumbnail regeneration"', $tool['status_text'] );
+			$this->assertStringContainsString( 'class="button button-large"', $tool['status_text'] );
+			$this->assertStringContainsString( 'wc-hide-notice=regenerating_thumbnails', $tool['status_text'] );
+			$this->assertStringNotContainsString( 'dashicons-update', $tool['status_text'] );
+		} finally {
+			$background_process_property->setValue( null, $original_background_process );
+		}
+	}
 }
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/SiteHealthTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/SiteHealthTest.php
new file mode 100644
index 00000000000..d668e10b854
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/SiteHealthTest.php
@@ -0,0 +1,90 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\Admin;
+
+use Automattic\WooCommerce\Internal\Admin\SiteHealth;
+use WC_Unit_Test_Case;
+use WP_Error;
+
+/**
+ * Tests for the SiteHealth class.
+ */
+class SiteHealthTest extends WC_Unit_Test_Case {
+	/**
+	 * The System Under Test.
+	 *
+	 * @var SiteHealth
+	 */
+	private SiteHealth $sut;
+
+	/**
+	 * Set up test fixtures.
+	 */
+	public function setUp(): void {
+		parent::setUp();
+		$this->sut = new SiteHealth();
+		delete_transient( '_woocommerce_upload_directory_status' );
+	}
+
+	/**
+	 * Tear down test fixtures.
+	 */
+	public function tearDown(): void {
+		delete_transient( '_woocommerce_upload_directory_status' );
+		parent::tearDown();
+	}
+
+	/**
+	 * @testdox Upload directory protection check fails when the HTTP request fails.
+	 */
+	public function test_uploads_directory_protection_fails_for_http_request_error(): void {
+		$filter_callback = static function ( $_preempt, $_parsed_args, $_url ) {
+			unset( $_preempt, $_parsed_args, $_url );
+
+			return new WP_Error( 'http_request_failed', 'Request failed.' );
+		};
+
+		add_filter( 'pre_http_request', $filter_callback, 10, 3 );
+
+		try {
+			$result = $this->sut->run_test( 'woocommerce_uploads_directory_protection' );
+
+			$this->assertSame( 'critical', $result['status'], 'Request failures should not be reported as protected.' );
+			$this->assertFalse( get_transient( '_woocommerce_upload_directory_status' ), 'Request failures should not be cached.' );
+		} finally {
+			remove_filter( 'pre_http_request', $filter_callback, 10 );
+		}
+	}
+
+	/**
+	 * @testdox Upload directory protection check fails when the HTTP response code is zero.
+	 */
+	public function test_uploads_directory_protection_fails_for_zero_response_code(): void {
+		$filter_callback = static function ( $_preempt, $_parsed_args, $_url ) {
+			unset( $_preempt, $_parsed_args, $_url );
+
+			return array(
+				'headers'  => array(),
+				'body'     => '',
+				'response' => array(
+					'code'    => 0,
+					'message' => '',
+				),
+				'cookies'  => array(),
+				'filename' => null,
+			);
+		};
+
+		add_filter( 'pre_http_request', $filter_callback, 10, 3 );
+
+		try {
+			$result = $this->sut->run_test( 'woocommerce_uploads_directory_protection' );
+
+			$this->assertSame( 'critical', $result['status'], 'Missing response codes should not be reported as protected.' );
+			$this->assertFalse( get_transient( '_woocommerce_upload_directory_status' ), 'Missing response codes should not be cached.' );
+		} finally {
+			remove_filter( 'pre_http_request', $filter_callback, 10 );
+		}
+	}
+}