Commit 78da1cc988 for wordpress.org

commit 78da1cc9887f660d01593b401e381278f4b00560
Author: SergeyBiryukov <sergeybiryukov@git.wordpress.org>
Date:   Mon Oct 19 23:41:54 2026 +0000

    Upgrade/Install: Explain when automatic updates cannot run.

    When automatic updates can't run, the Plugins, Themes, and Updates screens still say “Automatic update scheduled in 6 hours”. The automatic updater skips the update entirely when the site is under version control or when writing files needs FTP credentials, so that scheduled update never happens.

    `wp_get_auto_update_message()` now runs the same two checks as `WP_Automatic_Updater::should_update()`, against the same directory, and says why the update won't run instead. Users who can view Site Health get a link to it, since Site Health already explains both cases. On the Updates screen, the core section drops the schedule and the “kept up to date” line in either case.

    The checks walk the filesystem, so the plugins and network themes list tables determine the message once per table instead of once per row.

    Developed in https://github.com/WordPress/wordpress-develop/pull/14506.

    Follow-up to r47835.

    Props kraftbj, eidolonnight, jgehrcke, nacin, johnbillion, chriscct7, SergeyBiryukov.
    Fixes #27814.
    Built from https://develop.svn.wordpress.org/trunk@64586


    git-svn-id: http://core.svn.wordpress.org/trunk@63736 1a063a9b-81f0-0310-95a4-ce76da25c4cd

diff --git a/wp-admin/includes/class-wp-ms-themes-list-table.php b/wp-admin/includes/class-wp-ms-themes-list-table.php
index 0ecacb8f27..be0039b5ea 100644
--- a/wp-admin/includes/class-wp-ms-themes-list-table.php
+++ b/wp-admin/includes/class-wp-ms-themes-list-table.php
@@ -30,6 +30,17 @@ class WP_MS_Themes_List_Table extends WP_List_Table {
 	 */
 	protected $show_autoupdates = true;

+	/**
+	 * The auto-update message shown for themes with an update available.
+	 *
+	 * Determined once per list table, as it is the same for every theme.
+	 *
+	 * @since 7.2.0
+	 *
+	 * @var string|null Null until first needed.
+	 */
+	private $auto_update_message = null;
+
 	/**
 	 * Constructor.
 	 *
@@ -867,10 +878,14 @@ class WP_MS_Themes_List_Table extends WP_List_Table {
 		}

 		if ( isset( $available_updates->response[ $stylesheet ] ) ) {
+			if ( null === $this->auto_update_message ) {
+				$this->auto_update_message = wp_get_auto_update_message( 'theme' );
+			}
+
 			$html[] = sprintf(
 				'<div class="auto-update-time%s">%s</div>',
 				$time_class,
-				wp_get_auto_update_message()
+				$this->auto_update_message
 			);
 		}

diff --git a/wp-admin/includes/class-wp-plugins-list-table.php b/wp-admin/includes/class-wp-plugins-list-table.php
index c63a37992b..7778f21b46 100644
--- a/wp-admin/includes/class-wp-plugins-list-table.php
+++ b/wp-admin/includes/class-wp-plugins-list-table.php
@@ -24,6 +24,17 @@ class WP_Plugins_List_Table extends WP_List_Table {
 	 */
 	protected $show_autoupdates = true;

+	/**
+	 * The auto-update message shown for plugins with an update available.
+	 *
+	 * Determined once per list table, as it is the same for every plugin.
+	 *
+	 * @since 7.2.0
+	 *
+	 * @var string|null Null until first needed.
+	 */
+	private $auto_update_message = null;
+
 	/**
 	 * Constructor.
 	 *
@@ -1434,10 +1445,14 @@ class WP_Plugins_List_Table extends WP_List_Table {
 					}

 					if ( ! empty( $plugin_data['update'] ) ) {
+						if ( null === $this->auto_update_message ) {
+							$this->auto_update_message = wp_get_auto_update_message( 'plugin' );
+						}
+
 						$html[] = sprintf(
 							'<div class="auto-update-time%s">%s</div>',
 							$time_class,
-							wp_get_auto_update_message()
+							$this->auto_update_message
 						);
 					}

diff --git a/wp-admin/includes/update.php b/wp-admin/includes/update.php
index b0e998264f..c3c15c8fe3 100644
--- a/wp-admin/includes/update.php
+++ b/wp-admin/includes/update.php
@@ -1091,10 +1091,59 @@ function wp_is_auto_update_forced_for_item( $type, $update, $item ) {
  * Determines the appropriate auto-update message to be displayed.
  *
  * @since 5.5.0
+ * @since 7.2.0 Added the `$type` parameter. A message is now returned when
+ *              automatic updates cannot run because the filesystem requires
+ *              credentials or the site appears to be under version control.
  *
+ * @param string $type Optional. The type of update the message is for: 'plugin' or 'theme'.
+ *                     Determines which directory is checked. Default empty, which checks ABSPATH.
  * @return string The update message to be shown.
  */
-function wp_get_auto_update_message() {
+function wp_get_auto_update_message( $type = '' ) {
+	// Loads Automatic_Upgrader_Skin and WP_Automatic_Updater.
+	require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
+
+	if ( ! function_exists( 'request_filesystem_credentials' ) ) {
+		require_once ABSPATH . 'wp-admin/includes/file.php';
+	}
+
+	switch ( $type ) {
+		case 'plugin':
+			$context = WP_PLUGIN_DIR;
+			break;
+		case 'theme':
+			$context = get_theme_root();
+			break;
+		default:
+			$context = ABSPATH;
+	}
+
+	/*
+	 * These mirror the checks in WP_Automatic_Updater::should_update(), which skips
+	 * the update regardless of the schedule if either one fails.
+	 */
+	$skin    = new Automatic_Upgrader_Skin();
+	$updater = new WP_Automatic_Updater();
+
+	$message = '';
+	if ( ! $skin->request_filesystem_credentials( false, $context ) ) {
+		$message = __( 'Automatic update will not run because WordPress needs FTP credentials to update files.' );
+	} elseif ( $updater->is_vcs_checkout( $context ) ) {
+		$message = __( 'Automatic update will not run because this site appears to be under version control.' );
+	}
+
+	if ( $message ) {
+		if ( current_user_can( 'view_site_health_checks' ) ) {
+			$message .= ' ' . sprintf(
+				/* translators: %s: URL to the Site Health screen. */
+				__( '<a href="%s">Learn more in Site Health</a>.' ),
+				esc_url( admin_url( 'site-health.php' ) )
+			);
+		}
+
+		return $message;
+	}
+
 	$next_update_time = wp_next_scheduled( 'wp_version_check' );

 	// Check if the event exists.
diff --git a/wp-admin/themes.php b/wp-admin/themes.php
index a0686c6457..9e17386594 100644
--- a/wp-admin/themes.php
+++ b/wp-admin/themes.php
@@ -811,7 +811,7 @@ function wp_theme_auto_update_setting_template() {
 				<# } else { #>
 					<span class="auto-update-time hidden">
 				<# } #>
-				<br />' . wp_get_auto_update_message() . '</span>
+				<br />' . wp_get_auto_update_message( 'theme' ) . '</span>
 			<# } #>
 			' . $notice . '
 		</div>
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 0741962c27..40481716ef 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
  *
  * @global string $wp_version
  */
-$wp_version = '7.2-alpha-64585';
+$wp_version = '7.2-alpha-64586';

 /**
  * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.