diff --git a/wp-admin/includes/class-plugin-upgrader.php b/wp-admin/includes/class-plugin-upgrader.php index 812a5691a8..67d04ccc75 100644 --- a/wp-admin/includes/class-plugin-upgrader.php +++ b/wp-admin/includes/class-plugin-upgrader.php @@ -226,9 +226,14 @@ class Plugin_Upgrader extends WP_Upgrader { 'clear_destination' => true, 'clear_working' => true, 'hook_extra' => array( - 'plugin' => $plugin, - 'type' => 'plugin', - 'action' => 'update', + 'plugin' => $plugin, + 'type' => 'plugin', + 'action' => 'update', + 'temp_backup' => array( + 'slug' => dirname( $plugin ), + 'src' => WP_PLUGIN_DIR, + 'dir' => 'plugins', + ), ), ) ); @@ -342,7 +347,12 @@ class Plugin_Upgrader extends WP_Upgrader { 'clear_working' => true, 'is_multi' => true, 'hook_extra' => array( - 'plugin' => $plugin, + 'plugin' => $plugin, + 'temp_backup' => array( + 'slug' => dirname( $plugin ), + 'src' => WP_PLUGIN_DIR, + 'dir' => 'plugins', + ), ), ) ); diff --git a/wp-admin/includes/class-theme-upgrader.php b/wp-admin/includes/class-theme-upgrader.php index 9907c44feb..418888b262 100644 --- a/wp-admin/includes/class-theme-upgrader.php +++ b/wp-admin/includes/class-theme-upgrader.php @@ -328,9 +328,14 @@ class Theme_Upgrader extends WP_Upgrader { 'clear_destination' => true, 'clear_working' => true, 'hook_extra' => array( - 'theme' => $theme, - 'type' => 'theme', - 'action' => 'update', + 'theme' => $theme, + 'type' => 'theme', + 'action' => 'update', + 'temp_backup' => array( + 'slug' => $theme, + 'src' => get_theme_root( $theme ), + 'dir' => 'themes', + ), ), ) ); @@ -443,7 +448,12 @@ class Theme_Upgrader extends WP_Upgrader { 'clear_working' => true, 'is_multi' => true, 'hook_extra' => array( - 'theme' => $theme, + 'theme' => $theme, + 'temp_backup' => array( + 'slug' => $theme, + 'src' => get_theme_root( $theme ), + 'dir' => 'themes', + ), ), ) ); diff --git a/wp-admin/includes/class-wp-site-health.php b/wp-admin/includes/class-wp-site-health.php index b375633050..2a91be3f60 100644 --- a/wp-admin/includes/class-wp-site-health.php +++ b/wp-admin/includes/class-wp-site-health.php @@ -1925,6 +1925,193 @@ class WP_Site_Health { return $result; } + /** + * Tests available disk space for updates. + * + * @since 6.3.0 + * + * @return array The test results. + */ + public function get_test_available_updates_disk_space() { + $available_space = function_exists( 'disk_free_space' ) ? @disk_free_space( WP_CONTENT_DIR . '/upgrade/' ) : false; + + $available_space = false !== $available_space + ? (int) $available_space + : 0; + + $result = array( + 'label' => __( 'Disk space available to safely perform updates' ), + 'status' => 'good', + 'badge' => array( + 'label' => __( 'Security' ), + 'color' => 'blue', + ), + 'description' => sprintf( + /* translators: %s: Available disk space in MB or GB. */ + '
' . __( '%s available disk space was detected, update routines can be performed safely.' ) . '
', + size_format( $available_space ) + ), + 'actions' => '', + 'test' => 'available_updates_disk_space', + ); + + if ( $available_space < 100 * MB_IN_BYTES ) { + $result['description'] = __( 'Available disk space is low, less than 100 MB available.' ); + $result['status'] = 'recommended'; + } + + if ( $available_space < 20 * MB_IN_BYTES ) { + $result['description'] = __( 'Available disk space is critically low, less than 20 MB available. Proceed with caution, updates may fail.' ); + $result['status'] = 'critical'; + } + + if ( ! $available_space ) { + $result['description'] = __( 'Could not determine available disk space for updates.' ); + $result['status'] = 'recommended'; + } + + return $result; + } + + /** + * Tests if plugin and theme temporary backup directories are writable or can be created. + * + * @since 6.3.0 + * + * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. + * + * @return array The test results. + */ + public function get_test_update_temp_backup_writable() { + global $wp_filesystem; + + $result = array( + 'label' => __( 'Plugin and theme temporary backup directory is writable' ), + 'status' => 'good', + 'badge' => array( + 'label' => __( 'Security' ), + 'color' => 'blue', + ), + 'description' => sprintf( + /* translators: %s: wp-content/upgrade-temp-backup */ + '' . __( 'The %s directory used to improve the stability of plugin and theme updates is writable.' ) . '
', + 'wp-content/upgrade-temp-backup
'
+ ),
+ 'actions' => '',
+ 'test' => 'update_temp_backup_writable',
+ );
+
+ if ( ! $wp_filesystem ) {
+ require_once ABSPATH . '/wp-admin/includes/file.php';
+ WP_Filesystem();
+ }
+
+ $wp_content = $wp_filesystem->wp_content_dir();
+
+ if ( ! $wp_content ) {
+ $result['status'] = 'critical';
+ $result['label'] = sprintf(
+ /* translators: %s: wp-content */
+ __( 'Unable to locate WordPress content directory (%s)' ),
+ 'wp-content
'
+ );
+ $result['description'] = sprintf(
+ /* translators: %s: wp-content */
+ '' . __( 'The %s directory cannot be located.' ) . '
', + 'wp-content
'
+ );
+ return $result;
+ }
+
+ $upgrade_dir_exists = $wp_filesystem->is_dir( "$wp_content/upgrade" );
+ $upgrade_dir_is_writable = $wp_filesystem->is_writable( "$wp_content/upgrade" );
+ $backup_dir_exists = $wp_filesystem->is_dir( "$wp_content/upgrade-temp-backup" );
+ $backup_dir_is_writable = $wp_filesystem->is_writable( "$wp_content/upgrade-temp-backup" );
+
+ $plugins_dir_exists = $wp_filesystem->is_dir( "$wp_content/upgrade-temp-backup/plugins" );
+ $plugins_dir_is_writable = $wp_filesystem->is_writable( "$wp_content/upgrade-temp-backup/plugins" );
+ $themes_dir_exists = $wp_filesystem->is_dir( "$wp_content/upgrade-temp-backup/themes" );
+ $themes_dir_is_writable = $wp_filesystem->is_writable( "$wp_content/upgrade-temp-backup/themes" );
+
+ if ( $plugins_dir_exists && ! $plugins_dir_is_writable && $themes_dir_exists && ! $themes_dir_is_writable ) {
+ $result['status'] = 'critical';
+ $result['label'] = __( 'Plugin and theme temporary backup directories exist but are not writable' );
+ $result['description'] = sprintf(
+ /* translators: 1: wp-content/upgrade-temp-backup/plugins, 2: wp-content/upgrade-temp-backup/themes. */
+ '' . __( 'The %1$s and %2$s directories exist but are not writable. These directories are used to improve the stability of plugin updates. Please make sure the server has write permissions to these directories.' ) . '
', + 'wp-content/upgrade-temp-backup/plugins
',
+ 'wp-content/upgrade-temp-backup/themes
'
+ );
+ return $result;
+ }
+
+ if ( $plugins_dir_exists && ! $plugins_dir_is_writable ) {
+ $result['status'] = 'critical';
+ $result['label'] = __( 'Plugin temporary backup directory exists but is not writable' );
+ $result['description'] = sprintf(
+ /* translators: %s: wp-content/upgrade-temp-backup/plugins */
+ '' . __( 'The %s directory exists but is not writable. This directory is used to improve the stability of plugin updates. Please make sure the server has write permissions to this directory.' ) . '
', + 'wp-content/upgrade-temp-backup/plugins
'
+ );
+ return $result;
+ }
+
+ if ( $themes_dir_exists && ! $themes_dir_is_writable ) {
+ $result['status'] = 'critical';
+ $result['label'] = __( 'Theme temporary backup directory exists but is not writable' );
+ $result['description'] = sprintf(
+ /* translators: %s: wp-content/upgrade-temp-backup/themes */
+ '' . __( 'The %s directory exists but is not writable. This directory is used to improve the stability of theme updates. Please make sure the server has write permissions to this directory.' ) . '
', + 'wp-content/upgrade-temp-backup/themes
'
+ );
+ return $result;
+ }
+
+ if ( ( ! $plugins_dir_exists || ! $themes_dir_exists ) && $backup_dir_exists && ! $backup_dir_is_writable ) {
+ $result['status'] = 'critical';
+ $result['label'] = __( 'The temporary backup directory exists but is not writable' );
+ $result['description'] = sprintf(
+ /* translators: %s: wp-content/upgrade-temp-backup */
+ '' . __( 'The %s directory exists but is not writable. This directory is used to improve the stability of plugin and theme updates. Please make sure the server has write permissions to this directory.' ) . '
', + 'wp-content/upgrade-temp-backup
'
+ );
+ return $result;
+ }
+
+ if ( ! $backup_dir_exists && $upgrade_dir_exists && ! $upgrade_dir_is_writable ) {
+ $result['status'] = 'critical';
+ $result['label'] = sprintf(
+ /* translators: %s: upgrade */
+ __( 'The %s directory exists but is not writable' ),
+ 'upgrade'
+ );
+ $result['description'] = sprintf(
+ /* translators: %s: wp-content/upgrade */
+ '' . __( 'The %s directory exists but is not writable. This directory is used for plugin and theme updates. Please make sure the server has write permissions to this directory.' ) . '
', + 'wp-content/upgrade
'
+ );
+ return $result;
+ }
+
+ if ( ! $upgrade_dir_exists && ! $wp_filesystem->is_writable( $wp_content ) ) {
+ $result['status'] = 'critical';
+ $result['label'] = sprintf(
+ /* translators: %s: upgrade */
+ __( 'The %s directory cannot be created' ),
+ 'upgrade'
+ );
+ $result['description'] = sprintf(
+ /* translators: 1: wp-content/upgrade, 2: wp-content. */
+ '' . __( 'The %1$s directory does not exist, and the server does not have write permissions in %2$s to create it. This directory is used for plugin and theme updates. Please make sure the server has write permissions in %2$s.' ) . '
', + 'wp-content/upgrade
',
+ 'wp-content
'
+ );
+ return $result;
+ }
+
+ return $result;
+ }
+
/**
* Tests if loopbacks work as expected.
*
@@ -2532,71 +2719,79 @@ class WP_Site_Health {
public static function get_tests() {
$tests = array(
'direct' => array(
- 'wordpress_version' => array(
+ 'wordpress_version' => array(
'label' => __( 'WordPress Version' ),
'test' => 'wordpress_version',
),
- 'plugin_version' => array(
+ 'plugin_version' => array(
'label' => __( 'Plugin Versions' ),
'test' => 'plugin_version',
),
- 'theme_version' => array(
+ 'theme_version' => array(
'label' => __( 'Theme Versions' ),
'test' => 'theme_version',
),
- 'php_version' => array(
+ 'php_version' => array(
'label' => __( 'PHP Version' ),
'test' => 'php_version',
),
- 'php_extensions' => array(
+ 'php_extensions' => array(
'label' => __( 'PHP Extensions' ),
'test' => 'php_extensions',
),
- 'php_default_timezone' => array(
+ 'php_default_timezone' => array(
'label' => __( 'PHP Default Timezone' ),
'test' => 'php_default_timezone',
),
- 'php_sessions' => array(
+ 'php_sessions' => array(
'label' => __( 'PHP Sessions' ),
'test' => 'php_sessions',
),
- 'sql_server' => array(
+ 'sql_server' => array(
'label' => __( 'Database Server version' ),
'test' => 'sql_server',
),
- 'utf8mb4_support' => array(
+ 'utf8mb4_support' => array(
'label' => __( 'MySQL utf8mb4 support' ),
'test' => 'utf8mb4_support',
),
- 'ssl_support' => array(
+ 'ssl_support' => array(
'label' => __( 'Secure communication' ),
'test' => 'ssl_support',
),
- 'scheduled_events' => array(
+ 'scheduled_events' => array(
'label' => __( 'Scheduled events' ),
'test' => 'scheduled_events',
),
- 'http_requests' => array(
+ 'http_requests' => array(
'label' => __( 'HTTP Requests' ),
'test' => 'http_requests',
),
- 'rest_availability' => array(
+ 'rest_availability' => array(
'label' => __( 'REST API availability' ),
'test' => 'rest_availability',
'skip_cron' => true,
),
- 'debug_enabled' => array(
+ 'debug_enabled' => array(
'label' => __( 'Debugging enabled' ),
'test' => 'is_in_debug_mode',
),
- 'file_uploads' => array(
+ 'file_uploads' => array(
'label' => __( 'File uploads' ),
'test' => 'file_uploads',
),
- 'plugin_theme_auto_updates' => array(
+ 'plugin_theme_auto_updates' => array(
'label' => __( 'Plugin and theme auto-updates' ),
'test' => 'plugin_theme_auto_updates',
),
+ 'update_temp_backup_writable' => array(
+ 'label' => __( 'Plugin and theme temporary backup directory access' ),
+ 'test' => 'update_temp_backup_writable',
+ ),
+ 'available_updates_disk_space' => array(
+ 'label' => __( 'Available disk space' ),
+ 'test' => 'available_updates_disk_space',
+ ),
),
'async' => array(
'dotorg_communication' => array(
diff --git a/wp-admin/includes/class-wp-upgrader.php b/wp-admin/includes/class-wp-upgrader.php
index f6653af742..99a28b61ba 100644
--- a/wp-admin/includes/class-wp-upgrader.php
+++ b/wp-admin/includes/class-wp-upgrader.php
@@ -112,6 +112,26 @@ class WP_Upgrader {
*/
public $update_current = 0;
+ /**
+ * Stores the list of plugins or themes added to temporary backup directory.
+ *
+ * Used by the rollback functions.
+ *
+ * @since 6.3.0
+ * @var array
+ */
+ private $temp_backups = array();
+
+ /**
+ * Stores the list of plugins or themes to be restored from temporary backup directory.
+ *
+ * Used by the rollback functions.
+ *
+ * @since 6.3.0
+ * @var array
+ */
+ private $temp_restores = array();
+
/**
* Construct the upgrader with a skin.
*
@@ -134,11 +154,29 @@ class WP_Upgrader {
* This will set the relationship between the skin being used and this upgrader,
* and also add the generic strings to `WP_Upgrader::$strings`.
*
+ * Additionally, it will schedule a weekly task to clean up the temporary backup directory.
+ *
* @since 2.8.0
+ * @since 6.3.0 Added the `schedule_temp_backup_cleanup()` task.
*/
public function init() {
$this->skin->set_upgrader( $this );
$this->generic_strings();
+
+ if ( ! wp_installing() ) {
+ $this->schedule_temp_backup_cleanup();
+ }
+ }
+
+ /**
+ * Schedules the cleanup of the temporary backup directory.
+ *
+ * @since 6.3.0
+ */
+ protected function schedule_temp_backup_cleanup() {
+ if ( false === wp_next_scheduled( 'wp_delete_temp_updater_backups' ) ) {
+ wp_schedule_event( time(), 'weekly', 'wp_delete_temp_updater_backups' );
+ }
}
/**
@@ -167,6 +205,15 @@ class WP_Upgrader {
$this->strings['maintenance_start'] = __( 'Enabling Maintenance mode…' );
$this->strings['maintenance_end'] = __( 'Disabling Maintenance mode…' );
+
+ /* translators: %s: upgrade-temp-backup */
+ $this->strings['temp_backup_mkdir_failed'] = sprintf( __( 'Could not create the %s directory.' ), 'upgrade-temp-backup' );
+ /* translators: %s: upgrade-temp-backup */
+ $this->strings['temp_backup_move_failed'] = sprintf( __( 'Could not move the old version to the %s directory.' ), 'upgrade-temp-backup' );
+ /* translators: %s: The plugin or theme slug. */
+ $this->strings['temp_backup_restore_failed'] = __( 'Could not restore the original version of %s.' );
+ /* translators: %s: The plugin or theme slug. */
+ $this->strings['temp_backup_delete_failed'] = __( 'Could not delete the temporary backup directory for %s.' );
}
/**
@@ -308,6 +355,10 @@ class WP_Upgrader {
$this->skin->feedback( 'unpack_package' );
+ if ( ! $wp_filesystem->wp_content_dir() ) {
+ return new WP_Error( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
+ }
+
$upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
// Clean up contents of upgrade directory beforehand.
@@ -534,6 +585,16 @@ class WP_Upgrader {
return $source;
}
+ if ( ! empty( $args['hook_extra']['temp_backup'] ) ) {
+ $temp_backup = $this->move_to_temp_backup_dir( $args['hook_extra']['temp_backup'] );
+
+ if ( is_wp_error( $temp_backup ) ) {
+ return $temp_backup;
+ }
+
+ $this->temp_backups[] = $args['hook_extra']['temp_backup'];
+ }
+
// Has the source location changed? If so, we need a new source_files list.
if ( $source !== $remote_source ) {
$source_files = array_keys( $wp_filesystem->dirlist( $source ) );
@@ -614,7 +675,7 @@ class WP_Upgrader {
$result = copy_dir( $source, $remote_destination );
}
- // Clear the working folder?
+ // Clear the working directory?
if ( $args['clear_working'] ) {
$wp_filesystem->delete( $remote_source, true );
}
@@ -827,7 +888,19 @@ class WP_Upgrader {
$result = apply_filters( 'upgrader_install_package_result', $result, $options['hook_extra'] );
$this->skin->set_result( $result );
+
if ( is_wp_error( $result ) ) {
+ if ( ! empty( $options['hook_extra']['temp_backup'] ) ) {
+ $this->temp_restores[] = $options['hook_extra']['temp_backup'];
+
+ /*
+ * Restore the backup on shutdown.
+ * Actions running on `shutdown` are immune to PHP timeouts,
+ * so in case the failure was due to a PHP timeout,
+ * it will still be able to properly restore the previous version.
+ */
+ add_action( 'shutdown', array( $this, 'restore_temp_backup' ) );
+ }
$this->skin->error( $result );
if ( ! method_exists( $this->skin, 'hide_process_failed' ) || ! $this->skin->hide_process_failed( $result ) ) {
@@ -840,6 +913,12 @@ class WP_Upgrader {
$this->skin->after();
+ // Clean up the backup kept in the temporary backup directory.
+ if ( ! empty( $options['hook_extra']['temp_backup'] ) ) {
+ // Delete the backup on `shutdown` to avoid a PHP timeout.
+ add_action( 'shutdown', array( $this, 'delete_temp_backup' ), 100, 0 );
+ }
+
if ( ! $options['is_multi'] ) {
/**
@@ -966,6 +1045,170 @@ class WP_Upgrader {
public static function release_lock( $lock_name ) {
return delete_option( $lock_name . '.lock' );
}
+
+ /**
+ * Moves the plugin or theme being updated into a temporary backup directory.
+ *
+ * @since 6.3.0
+ *
+ * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
+ *
+ * @param string[] $args {
+ * Array of data for the temporary backup.
+ *
+ * @type string $slug Plugin or theme slug.
+ * @type string $src Path to the root directory for plugins or themes.
+ * @type string $dir Destination subdirectory name. Accepts 'plugins' or 'themes'.
+ * }
+ *
+ * @return bool|WP_Error True on success, false on early exit, otherwise WP_Error.
+ */
+ public function move_to_temp_backup_dir( $args ) {
+ global $wp_filesystem;
+
+ if ( empty( $args['slug'] ) || empty( $args['src'] ) || empty( $args['dir'] ) ) {
+ return false;
+ }
+
+ /*
+ * Skip any plugin that has "." as its slug.
+ * A slug of "." will result in a `$src` value ending in a period.
+ *
+ * On Windows, this will cause the 'plugins' folder to be moved,
+ * and will cause a failure when attempting to call `mkdir()`.
+ */
+ if ( '.' === $args['slug'] ) {
+ return false;
+ }
+
+ if ( ! $wp_filesystem->wp_content_dir() ) {
+ return new WP_Error( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
+ }
+
+ $dest_dir = $wp_filesystem->wp_content_dir() . 'upgrade-temp-backup/';
+ $sub_dir = $dest_dir . $args['dir'] . '/';
+
+ // Create the temporary backup directory if it does not exist.
+ if ( ! $wp_filesystem->is_dir( $sub_dir ) ) {
+ if ( ! $wp_filesystem->is_dir( $dest_dir ) ) {
+ $wp_filesystem->mkdir( $dest_dir, FS_CHMOD_DIR );
+ }
+
+ if ( ! $wp_filesystem->mkdir( $sub_dir, FS_CHMOD_DIR ) ) {
+ // Could not create the backup directory.
+ return new WP_Error( 'fs_temp_backup_mkdir', $this->strings['temp_backup_mkdir_failed'] );
+ }
+ }
+
+ $src_dir = $wp_filesystem->find_folder( $args['src'] );
+ $src = trailingslashit( $src_dir ) . $args['slug'];
+ $dest = $dest_dir . trailingslashit( $args['dir'] ) . $args['slug'];
+
+ // Delete the temporary backup directory if it already exists.
+ if ( $wp_filesystem->is_dir( $dest ) ) {
+ $wp_filesystem->delete( $dest, true );
+ }
+
+ // Move to the temporary backup directory.
+ $result = move_dir( $src, $dest, true );
+ if ( is_wp_error( $result ) ) {
+ return new WP_Error( 'fs_temp_backup_move', $this->strings['temp_backup_move_failed'] );
+ }
+
+ return true;
+ }
+
+ /**
+ * Restores the plugin or theme from temporary backup.
+ *
+ * @since 6.3.0
+ *
+ * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
+ *
+ * @return bool|WP_Error True on success, false on early exit, otherwise WP_Error.
+ */
+ public function restore_temp_backup() {
+ global $wp_filesystem;
+
+ $errors = new WP_Error();
+
+ foreach ( $this->temp_restores as $args ) {
+ if ( empty( $args['slug'] ) || empty( $args['src'] ) || empty( $args['dir'] ) ) {
+ return false;
+ }
+
+ if ( ! $wp_filesystem->wp_content_dir() ) {
+ $errors->add( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
+ return $errors;
+ }
+
+ $src = $wp_filesystem->wp_content_dir() . 'upgrade-temp-backup/' . $args['dir'] . '/' . $args['slug'];
+ $dest_dir = $wp_filesystem->find_folder( $args['src'] );
+ $dest = trailingslashit( $dest_dir ) . $args['slug'];
+
+ if ( $wp_filesystem->is_dir( $src ) ) {
+ // Cleanup.
+ if ( $wp_filesystem->is_dir( $dest ) && ! $wp_filesystem->delete( $dest, true ) ) {
+ $errors->add(
+ 'fs_temp_backup_delete',
+ sprintf( $this->strings['temp_backup_restore_failed'], $args['slug'] )
+ );
+ continue;
+ }
+
+ // Move it.
+ $result = move_dir( $src, $dest, true );
+ if ( is_wp_error( $result ) ) {
+ $errors->add(
+ 'fs_temp_backup_delete',
+ sprintf( $this->strings['temp_backup_restore_failed'], $args['slug'] )
+ );
+ continue;
+ }
+ }
+ }
+
+ return $errors->has_errors() ? $errors : true;
+ }
+
+ /**
+ * Deletes a temporary backup.
+ *
+ * @since 6.3.0
+ *
+ * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
+ *
+ * @return bool|WP_Error True on success, false on early exit, otherwise WP_Error.
+ */
+ public function delete_temp_backup() {
+ global $wp_filesystem;
+
+ $errors = new WP_Error();
+
+ foreach ( $this->temp_backups as $args ) {
+ if ( empty( $args['slug'] ) || empty( $args['dir'] ) ) {
+ return false;
+ }
+
+ if ( ! $wp_filesystem->wp_content_dir() ) {
+ $errors->add( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
+ return $errors;
+ }
+
+ $temp_backup_dir = $wp_filesystem->wp_content_dir() . "upgrade-temp-backup/{$args['dir']}/{$args['slug']}";
+
+ if ( ! $wp_filesystem->delete( $temp_backup_dir, true ) ) {
+ $errors->add(
+ 'temp_backup_delete_failed',
+ sprintf( $this->strings['temp_backup_delete_failed'] ),
+ $args['slug']
+ );
+ continue;
+ }
+ }
+
+ return $errors->has_errors() ? $errors : true;
+ }
}
/** Plugin_Upgrader class */
diff --git a/wp-includes/update.php b/wp-includes/update.php
index c640e816fe..8e5302afab 100644
--- a/wp-includes/update.php
+++ b/wp-includes/update.php
@@ -1077,6 +1077,62 @@ function wp_clean_update_cache() {
delete_site_transient( 'update_core' );
}
+/**
+ * Schedules the removal of all contents in the temporary backup directory.
+ *
+ * @since 6.3.0
+ */
+function wp_delete_all_temp_backups() {
+ /*
+ * Check if there is a lock, or if currently performing an Ajax request,
+ * in which case there is a chance an update is running.
+ * Reschedule for an hour from now and exit early.
+ */
+ if ( get_option( 'core_updater.lock' ) || get_option( 'auto_updater.lock' ) || wp_doing_ajax() ) {
+ wp_schedule_single_event( time() + HOUR_IN_SECONDS, 'wp_delete_temp_updater_backups' );
+ return;
+ }
+
+ // This action runs on shutdown to make sure there are no plugin updates currently running.
+ add_action( 'shutdown', '_wp_delete_all_temp_backups' );
+}
+
+/**
+ * Deletes all contents in the temporary backup directory.
+ *
+ * @since 6.3.0
+ *
+ * @access private
+ *
+ * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
+ *
+ * @return void|WP_Error Void on success, or a WP_Error object on failure.
+ */
+function _wp_delete_all_temp_backups() {
+ global $wp_filesystem;
+
+ if ( ! $wp_filesystem ) {
+ require_once ABSPATH . '/wp-admin/includes/file.php';
+ WP_Filesystem();
+ }
+
+ if ( ! $wp_filesystem->wp_content_dir() ) {
+ return new WP_Error( 'fs_no_content_dir', __( 'Unable to locate WordPress content directory (wp-content).' ) );
+ }
+
+ $temp_backup_dir = $wp_filesystem->wp_content_dir() . 'upgrade-temp-backup/';
+ $dirlist = $wp_filesystem->dirlist( $temp_backup_dir );
+ $dirlist = $dirlist ? $dirlist : array();
+
+ foreach ( array_keys( $dirlist ) as $dir ) {
+ if ( '.' === $dir || '..' === $dir ) {
+ continue;
+ }
+
+ $wp_filesystem->delete( $temp_backup_dir . $dir, true );
+ }
+}
+
if ( ( ! is_main_site() && ! is_network_admin() ) || wp_doing_ajax() ) {
return;
}
@@ -1101,3 +1157,5 @@ add_action( 'update_option_WPLANG', 'wp_clean_update_cache', 10, 0 );
add_action( 'wp_maybe_auto_update', 'wp_maybe_auto_update' );
add_action( 'init', 'wp_schedule_update_checks' );
+
+add_action( 'wp_delete_temp_updater_backups', 'wp_delete_all_temp_backups' );
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 4ad7cada78..3e50eb6f93 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '6.3-alpha-55719';
+$wp_version = '6.3-alpha-55720';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.