<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[55720] trunk/src: Upgrade/Install: Create a temporary backup of plugins and themes before updating.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/55720">55720</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/55720","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>SergeyBiryukov</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2023-05-04 02:34:58 +0000 (Thu, 04 May 2023)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Upgrade/Install: Create a temporary backup of plugins and themes before updating.

This aims to make the update process more reliable and ensures that if a plugin or theme update fails, the previous version can be safely restored.

* When updating a plugin or theme, the old version is moved to a temporary backup directory:
 * `wp-content/upgrade-temp-backup/plugins/[plugin-slug]` for plugins
 * `wp-content/upgrade-temp-backup/themes/[theme-slug]` for themes.

* If the update fails, then the backup kept in the temporary backup directory is restored to its original location.
* If the update succeeds, the temporary backup is deleted.

To further help troubleshoot plugin and theme updates, two new checks were added to the Site Health screen:
* A check to make sure that the `upgrade-temp-backup` directory is writable.
* A check that there is enough disk space available to safely perform updates.

To avoid confusion: The temporary backup directory will NOT be used to {U+201C}roll back{U+201D} a plugin to a previous version after a completed update. This directory will simply contain a transient backup of the previous version of a plugin or theme being updated, and as soon as the update process finishes, the directory will be empty.

Follow-up to <a href="https://core.trac.wordpress.org/changeset/55204">[55204]</a>, <a href="https://core.trac.wordpress.org/changeset/55220">[55220]</a>.

Props afragen, costdev, pbiron, azaozz, hellofromTonya, aristath, peterwilsoncc, TJNowell, bronsonquick, Clorith, dd32, poena, TimothyBlynJacobs, audrasjb, mikeschroder, a2hosting, KZeni, galbaras, richards1052, Boniu91, mai21, francina, TobiasBg, desrosj, noisysocks, johnbillion, dlh, chaion07, davidbaumwald, jrf, thisisyeasin, ignatggeorgiev, SergeyBiryukov.
Fixes <a href="https://core.trac.wordpress.org/ticket/51857">#51857</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpadminincludesclasspluginupgraderphp">trunk/src/wp-admin/includes/class-plugin-upgrader.php</a></li>
<li><a href="#trunksrcwpadminincludesclassthemeupgraderphp">trunk/src/wp-admin/includes/class-theme-upgrader.php</a></li>
<li><a href="#trunksrcwpadminincludesclasswpsitehealthphp">trunk/src/wp-admin/includes/class-wp-site-health.php</a></li>
<li><a href="#trunksrcwpadminincludesclasswpupgraderphp">trunk/src/wp-admin/includes/class-wp-upgrader.php</a></li>
<li><a href="#trunksrcwpincludesupdatephp">trunk/src/wp-includes/update.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpadminincludesclasspluginupgraderphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/class-plugin-upgrader.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/class-plugin-upgrader.php     2023-05-03 23:55:50 UTC (rev 55719)
+++ trunk/src/wp-admin/includes/class-plugin-upgrader.php       2023-05-04 02:34:58 UTC (rev 55720)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -226,9 +226,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                'clear_destination' => true,
</span><span class="cx" style="display: block; padding: 0 10px">                                'clear_working'     => true,
</span><span class="cx" style="display: block; padding: 0 10px">                                'hook_extra'        => array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        'plugin' => $plugin,
-                                       'type'   => 'plugin',
-                                       'action' => 'update',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 'plugin'      => $plugin,
+                                       'type'        => 'plugin',
+                                       'action'      => 'update',
+                                       'temp_backup' => array(
+                                               'slug' => dirname( $plugin ),
+                                               'src'  => WP_PLUGIN_DIR,
+                                               'dir'  => 'plugins',
+                                       ),
</ins><span class="cx" style="display: block; padding: 0 10px">                                 ),
</span><span class="cx" style="display: block; padding: 0 10px">                        )
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -342,7 +347,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        'clear_working'     => true,
</span><span class="cx" style="display: block; padding: 0 10px">                                        'is_multi'          => true,
</span><span class="cx" style="display: block; padding: 0 10px">                                        'hook_extra'        => array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                                'plugin' => $plugin,
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                         'plugin'      => $plugin,
+                                               'temp_backup' => array(
+                                                       'slug' => dirname( $plugin ),
+                                                       'src'  => WP_PLUGIN_DIR,
+                                                       'dir'  => 'plugins',
+                                               ),
</ins><span class="cx" style="display: block; padding: 0 10px">                                         ),
</span><span class="cx" style="display: block; padding: 0 10px">                                )
</span><span class="cx" style="display: block; padding: 0 10px">                        );
</span></span></pre></div>
<a id="trunksrcwpadminincludesclassthemeupgraderphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/class-theme-upgrader.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/class-theme-upgrader.php      2023-05-03 23:55:50 UTC (rev 55719)
+++ trunk/src/wp-admin/includes/class-theme-upgrader.php        2023-05-04 02:34:58 UTC (rev 55720)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -328,9 +328,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                'clear_destination' => true,
</span><span class="cx" style="display: block; padding: 0 10px">                                'clear_working'     => true,
</span><span class="cx" style="display: block; padding: 0 10px">                                'hook_extra'        => array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        'theme'  => $theme,
-                                       'type'   => 'theme',
-                                       'action' => 'update',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 'theme'       => $theme,
+                                       'type'        => 'theme',
+                                       'action'      => 'update',
+                                       'temp_backup' => array(
+                                               'slug' => $theme,
+                                               'src'  => get_theme_root( $theme ),
+                                               'dir'  => 'themes',
+                                       ),
</ins><span class="cx" style="display: block; padding: 0 10px">                                 ),
</span><span class="cx" style="display: block; padding: 0 10px">                        )
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -443,7 +448,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        'clear_working'     => true,
</span><span class="cx" style="display: block; padding: 0 10px">                                        'is_multi'          => true,
</span><span class="cx" style="display: block; padding: 0 10px">                                        'hook_extra'        => array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                                'theme' => $theme,
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                         'theme'       => $theme,
+                                               'temp_backup' => array(
+                                                       'slug' => $theme,
+                                                       'src'  => get_theme_root( $theme ),
+                                                       'dir'  => 'themes',
+                                               ),
</ins><span class="cx" style="display: block; padding: 0 10px">                                         ),
</span><span class="cx" style="display: block; padding: 0 10px">                                )
</span><span class="cx" style="display: block; padding: 0 10px">                        );
</span></span></pre></div>
<a id="trunksrcwpadminincludesclasswpsitehealthphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/class-wp-site-health.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/class-wp-site-health.php      2023-05-03 23:55:50 UTC (rev 55719)
+++ trunk/src/wp-admin/includes/class-wp-site-health.php        2023-05-04 02:34:58 UTC (rev 55720)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1926,6 +1926,193 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * 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. */
+                               '<p>' . __( '%s available disk space was detected, update routines can be performed safely.' ) . '</p>',
+                               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 */
+                               '<p>' . __( 'The %s directory used to improve the stability of plugin and theme updates is writable.' ) . '</p>',
+                               '<code>wp-content/upgrade-temp-backup</code>'
+                       ),
+                       '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)' ),
+                               '<code>wp-content</code>'
+                       );
+                       $result['description'] = sprintf(
+                               /* translators: %s: wp-content */
+                               '<p>' . __( 'The %s directory cannot be located.' ) . '</p>',
+                               '<code>wp-content</code>'
+                       );
+                       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. */
+                               '<p>' . __( '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.' ) . '</p>',
+                               '<code>wp-content/upgrade-temp-backup/plugins</code>',
+                               '<code>wp-content/upgrade-temp-backup/themes</code>'
+                       );
+                       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 */
+                               '<p>' . __( '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.' ) . '</p>',
+                               '<code>wp-content/upgrade-temp-backup/plugins</code>'
+                       );
+                       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 */
+                               '<p>' . __( '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.' ) . '</p>',
+                               '<code>wp-content/upgrade-temp-backup/themes</code>'
+                       );
+                       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 */
+                               '<p>' . __( '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.' ) . '</p>',
+                               '<code>wp-content/upgrade-temp-backup</code>'
+                       );
+                       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 */
+                               '<p>' . __( '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.' ) . '</p>',
+                               '<code>wp-content/upgrade</code>'
+                       );
+                       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. */
+                               '<p>' . __( '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.' ) . '</p>',
+                               '<code>wp-content/upgrade</code>',
+                               '<code>wp-content</code>'
+                       );
+                       return $result;
+               }
+
+               return $result;
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Tests if loopbacks work as expected.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * A loopback is when WordPress queries itself, for example to start a new WP_Cron instance,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2532,71 +2719,79 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public static function get_tests() {
</span><span class="cx" style="display: block; padding: 0 10px">                $tests = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'direct' => array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'wordpress_version'         => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'wordpress_version'            => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'WordPress Version' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'wordpress_version',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'plugin_version'            => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'plugin_version'               => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'Plugin Versions' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'plugin_version',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'theme_version'             => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'theme_version'                => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'Theme Versions' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'theme_version',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'php_version'               => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'php_version'                  => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'PHP Version' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'php_version',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'php_extensions'            => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'php_extensions'               => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'PHP Extensions' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'php_extensions',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'php_default_timezone'      => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'php_default_timezone'         => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'PHP Default Timezone' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'php_default_timezone',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'php_sessions'              => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'php_sessions'                 => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'PHP Sessions' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'php_sessions',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'sql_server'                => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'sql_server'                   => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'Database Server version' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'sql_server',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'utf8mb4_support'           => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'utf8mb4_support'              => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'MySQL utf8mb4 support' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'utf8mb4_support',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'ssl_support'               => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'ssl_support'                  => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'Secure communication' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'ssl_support',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'scheduled_events'          => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'scheduled_events'             => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'Scheduled events' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'scheduled_events',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'http_requests'             => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'http_requests'                => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'HTTP Requests' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'http_requests',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'rest_availability'         => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'rest_availability'            => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label'     => __( 'REST API availability' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'      => 'rest_availability',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'skip_cron' => true,
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'debug_enabled'             => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'debug_enabled'                => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'Debugging enabled' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'is_in_debug_mode',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'file_uploads'              => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'file_uploads'                 => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'File uploads' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'file_uploads',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'plugin_theme_auto_updates' => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'plugin_theme_auto_updates'    => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'label' => __( 'Plugin and theme auto-updates' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'test'  => 'plugin_theme_auto_updates',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                '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',
+                               ),
</ins><span class="cx" style="display: block; padding: 0 10px">                         ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'async'  => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'dotorg_communication' => array(
</span></span></pre></div>
<a id="trunksrcwpadminincludesclasswpupgraderphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/class-wp-upgrader.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/class-wp-upgrader.php 2023-05-03 23:55:50 UTC (rev 55719)
+++ trunk/src/wp-admin/includes/class-wp-upgrader.php   2023-05-04 02:34:58 UTC (rev 55720)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -113,6 +113,26 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public $update_current = 0;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * 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();
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Construct the upgrader with a skin.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 2.8.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -134,14 +154,32 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * This will set the relationship between the skin being used and this upgrader,
</span><span class="cx" style="display: block; padding: 0 10px">         * and also add the generic strings to `WP_Upgrader::$strings`.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Additionally, it will schedule a weekly task to clean up the temporary backup directory.
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @since 2.8.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @since 6.3.0 Added the `schedule_temp_backup_cleanup()` task.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function init() {
</span><span class="cx" style="display: block; padding: 0 10px">                $this->skin->set_upgrader( $this );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->generic_strings();
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               if ( ! wp_installing() ) {
+                       $this->schedule_temp_backup_cleanup();
+               }
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * 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' );
+               }
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Add the generic strings to WP_Upgrader::$strings.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 2.8.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -167,6 +205,15 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->strings['maintenance_start'] = __( 'Enabling Maintenance mode&#8230;' );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->strings['maintenance_end']   = __( 'Disabling Maintenance mode&#8230;' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               /* 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.' );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -308,6 +355,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->skin->feedback( 'unpack_package' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( ! $wp_filesystem->wp_content_dir() ) {
+                       return new WP_Error( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Clean up contents of upgrade directory beforehand.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -534,6 +585,16 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        return $source;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                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'];
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 // Has the source location changed? If so, we need a new source_files list.
</span><span class="cx" style="display: block; padding: 0 10px">                if ( $source !== $remote_source ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $source_files = array_keys( $wp_filesystem->dirlist( $source ) );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -614,7 +675,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $result = copy_dir( $source, $remote_destination );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // Clear the working folder?
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Clear the working directory?
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( $args['clear_working'] ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $wp_filesystem->delete( $remote_source, true );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -827,7 +888,19 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $result = apply_filters( 'upgrader_install_package_result', $result, $options['hook_extra'] );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->skin->set_result( $result );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( is_wp_error( $result ) ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        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' ) );
+                       }
</ins><span class="cx" style="display: block; padding: 0 10px">                         $this->skin->error( $result );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( ! method_exists( $this->skin, 'hide_process_failed' ) || ! $this->skin->hide_process_failed( $result ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -840,6 +913,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->skin->after();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // 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 );
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( ! $options['is_multi'] ) {
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -966,6 +1045,170 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public static function release_lock( $lock_name ) {
</span><span class="cx" style="display: block; padding: 0 10px">                return delete_option( $lock_name . '.lock' );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * 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;
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /** Plugin_Upgrader class */
</span></span></pre></div>
<a id="trunksrcwpincludesupdatephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/update.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/update.php  2023-05-03 23:55:50 UTC (rev 55719)
+++ trunk/src/wp-includes/update.php    2023-05-04 02:34:58 UTC (rev 55720)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1077,6 +1077,62 @@
</span><span class="cx" style="display: block; padding: 0 10px">        delete_site_transient( 'update_core' );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * 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 );
+       }
+}
+
</ins><span class="cx" style="display: block; padding: 0 10px"> if ( ( ! is_main_site() && ! is_network_admin() ) || wp_doing_ajax() ) {
</span><span class="cx" style="display: block; padding: 0 10px">        return;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1101,3 +1157,5 @@
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'wp_maybe_auto_update', 'wp_maybe_auto_update' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'init', 'wp_schedule_update_checks' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+add_action( 'wp_delete_temp_updater_backups', 'wp_delete_all_temp_backups' );
</ins></span></pre>
</div>
</div>

</body>
</html>