Make WordPress Core

Changeset 55720


Ignore:
Timestamp:
05/04/2023 02:34:58 AM (13 months ago)
Author:
SergeyBiryukov
Message:

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 “roll back” 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 [55204], [55220].

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 #51857.

Location:
trunk/src
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/class-plugin-upgrader.php

    r53426 r55720  
    227227                'clear_working'     => true,
    228228                'hook_extra'        => array(
    229                     'plugin' => $plugin,
    230                     'type'   => 'plugin',
    231                     'action' => 'update',
     229                    'plugin'      => $plugin,
     230                    'type'        => 'plugin',
     231                    'action'      => 'update',
     232                    'temp_backup' => array(
     233                        'slug' => dirname( $plugin ),
     234                        'src'  => WP_PLUGIN_DIR,
     235                        'dir'  => 'plugins',
     236                    ),
    232237                ),
    233238            )
     
    343348                    'is_multi'          => true,
    344349                    'hook_extra'        => array(
    345                         'plugin' => $plugin,
     350                        'plugin'      => $plugin,
     351                        'temp_backup' => array(
     352                            'slug' => dirname( $plugin ),
     353                            'src'  => WP_PLUGIN_DIR,
     354                            'dir'  => 'plugins',
     355                        ),
    346356                    ),
    347357                )
  • trunk/src/wp-admin/includes/class-theme-upgrader.php

    r53426 r55720  
    329329                'clear_working'     => true,
    330330                'hook_extra'        => array(
    331                     'theme'  => $theme,
    332                     'type'   => 'theme',
    333                     'action' => 'update',
     331                    'theme'       => $theme,
     332                    'type'        => 'theme',
     333                    'action'      => 'update',
     334                    'temp_backup' => array(
     335                        'slug' => $theme,
     336                        'src'  => get_theme_root( $theme ),
     337                        'dir'  => 'themes',
     338                    ),
    334339                ),
    335340            )
     
    444449                    'is_multi'          => true,
    445450                    'hook_extra'        => array(
    446                         'theme' => $theme,
     451                        'theme'       => $theme,
     452                        'temp_backup' => array(
     453                            'slug' => $theme,
     454                            'src'  => get_theme_root( $theme ),
     455                            'dir'  => 'themes',
     456                        ),
    447457                    ),
    448458                )
  • trunk/src/wp-admin/includes/class-wp-site-health.php

    r55703 r55720  
    19271927
    19281928    /**
     1929     * Tests available disk space for updates.
     1930     *
     1931     * @since 6.3.0
     1932     *
     1933     * @return array The test results.
     1934     */
     1935    public function get_test_available_updates_disk_space() {
     1936        $available_space = function_exists( 'disk_free_space' ) ? @disk_free_space( WP_CONTENT_DIR . '/upgrade/' ) : false;
     1937
     1938        $available_space = false !== $available_space
     1939            ? (int) $available_space
     1940            : 0;
     1941
     1942        $result = array(
     1943            'label'       => __( 'Disk space available to safely perform updates' ),
     1944            'status'      => 'good',
     1945            'badge'       => array(
     1946                'label' => __( 'Security' ),
     1947                'color' => 'blue',
     1948            ),
     1949            'description' => sprintf(
     1950                /* translators: %s: Available disk space in MB or GB. */
     1951                '<p>' . __( '%s available disk space was detected, update routines can be performed safely.' ) . '</p>',
     1952                size_format( $available_space )
     1953            ),
     1954            'actions'     => '',
     1955            'test'        => 'available_updates_disk_space',
     1956        );
     1957
     1958        if ( $available_space < 100 * MB_IN_BYTES ) {
     1959            $result['description'] = __( 'Available disk space is low, less than 100 MB available.' );
     1960            $result['status']      = 'recommended';
     1961        }
     1962
     1963        if ( $available_space < 20 * MB_IN_BYTES ) {
     1964            $result['description'] = __( 'Available disk space is critically low, less than 20 MB available. Proceed with caution, updates may fail.' );
     1965            $result['status']      = 'critical';
     1966        }
     1967
     1968        if ( ! $available_space ) {
     1969            $result['description'] = __( 'Could not determine available disk space for updates.' );
     1970            $result['status']      = 'recommended';
     1971        }
     1972
     1973        return $result;
     1974    }
     1975
     1976    /**
     1977     * Tests if plugin and theme temporary backup directories are writable or can be created.
     1978     *
     1979     * @since 6.3.0
     1980     *
     1981     * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
     1982     *
     1983     * @return array The test results.
     1984     */
     1985    public function get_test_update_temp_backup_writable() {
     1986        global $wp_filesystem;
     1987
     1988        $result = array(
     1989            'label'       => __( 'Plugin and theme temporary backup directory is writable' ),
     1990            'status'      => 'good',
     1991            'badge'       => array(
     1992                'label' => __( 'Security' ),
     1993                'color' => 'blue',
     1994            ),
     1995            'description' => sprintf(
     1996                /* translators: %s: wp-content/upgrade-temp-backup */
     1997                '<p>' . __( 'The %s directory used to improve the stability of plugin and theme updates is writable.' ) . '</p>',
     1998                '<code>wp-content/upgrade-temp-backup</code>'
     1999            ),
     2000            'actions'     => '',
     2001            'test'        => 'update_temp_backup_writable',
     2002        );
     2003
     2004        if ( ! $wp_filesystem ) {
     2005            require_once ABSPATH . '/wp-admin/includes/file.php';
     2006            WP_Filesystem();
     2007        }
     2008
     2009        $wp_content = $wp_filesystem->wp_content_dir();
     2010
     2011        if ( ! $wp_content ) {
     2012            $result['status'] = 'critical';
     2013            $result['label']  = sprintf(
     2014                /* translators: %s: wp-content */
     2015                __( 'Unable to locate WordPress content directory (%s)' ),
     2016                '<code>wp-content</code>'
     2017            );
     2018            $result['description'] = sprintf(
     2019                /* translators: %s: wp-content */
     2020                '<p>' . __( 'The %s directory cannot be located.' ) . '</p>',
     2021                '<code>wp-content</code>'
     2022            );
     2023            return $result;
     2024        }
     2025
     2026        $upgrade_dir_exists      = $wp_filesystem->is_dir( "$wp_content/upgrade" );
     2027        $upgrade_dir_is_writable = $wp_filesystem->is_writable( "$wp_content/upgrade" );
     2028        $backup_dir_exists       = $wp_filesystem->is_dir( "$wp_content/upgrade-temp-backup" );
     2029        $backup_dir_is_writable  = $wp_filesystem->is_writable( "$wp_content/upgrade-temp-backup" );
     2030
     2031        $plugins_dir_exists      = $wp_filesystem->is_dir( "$wp_content/upgrade-temp-backup/plugins" );
     2032        $plugins_dir_is_writable = $wp_filesystem->is_writable( "$wp_content/upgrade-temp-backup/plugins" );
     2033        $themes_dir_exists       = $wp_filesystem->is_dir( "$wp_content/upgrade-temp-backup/themes" );
     2034        $themes_dir_is_writable  = $wp_filesystem->is_writable( "$wp_content/upgrade-temp-backup/themes" );
     2035
     2036        if ( $plugins_dir_exists && ! $plugins_dir_is_writable && $themes_dir_exists && ! $themes_dir_is_writable ) {
     2037            $result['status']      = 'critical';
     2038            $result['label']       = __( 'Plugin and theme temporary backup directories exist but are not writable' );
     2039            $result['description'] = sprintf(
     2040                /* translators: 1: wp-content/upgrade-temp-backup/plugins, 2: wp-content/upgrade-temp-backup/themes. */
     2041                '<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>',
     2042                '<code>wp-content/upgrade-temp-backup/plugins</code>',
     2043                '<code>wp-content/upgrade-temp-backup/themes</code>'
     2044            );
     2045            return $result;
     2046        }
     2047
     2048        if ( $plugins_dir_exists && ! $plugins_dir_is_writable ) {
     2049            $result['status']      = 'critical';
     2050            $result['label']       = __( 'Plugin temporary backup directory exists but is not writable' );
     2051            $result['description'] = sprintf(
     2052                /* translators: %s: wp-content/upgrade-temp-backup/plugins */
     2053                '<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>',
     2054                '<code>wp-content/upgrade-temp-backup/plugins</code>'
     2055            );
     2056            return $result;
     2057        }
     2058
     2059        if ( $themes_dir_exists && ! $themes_dir_is_writable ) {
     2060            $result['status']      = 'critical';
     2061            $result['label']       = __( 'Theme temporary backup directory exists but is not writable' );
     2062            $result['description'] = sprintf(
     2063                /* translators: %s: wp-content/upgrade-temp-backup/themes */
     2064                '<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>',
     2065                '<code>wp-content/upgrade-temp-backup/themes</code>'
     2066            );
     2067            return $result;
     2068        }
     2069
     2070        if ( ( ! $plugins_dir_exists || ! $themes_dir_exists ) && $backup_dir_exists && ! $backup_dir_is_writable ) {
     2071            $result['status']      = 'critical';
     2072            $result['label']       = __( 'The temporary backup directory exists but is not writable' );
     2073            $result['description'] = sprintf(
     2074                /* translators: %s: wp-content/upgrade-temp-backup */
     2075                '<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>',
     2076                '<code>wp-content/upgrade-temp-backup</code>'
     2077            );
     2078            return $result;
     2079        }
     2080
     2081        if ( ! $backup_dir_exists && $upgrade_dir_exists && ! $upgrade_dir_is_writable ) {
     2082            $result['status'] = 'critical';
     2083            $result['label']  = sprintf(
     2084                /* translators: %s: upgrade */
     2085                __( 'The %s directory exists but is not writable' ),
     2086                'upgrade'
     2087            );
     2088            $result['description'] = sprintf(
     2089                /* translators: %s: wp-content/upgrade */
     2090                '<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>',
     2091                '<code>wp-content/upgrade</code>'
     2092            );
     2093            return $result;
     2094        }
     2095
     2096        if ( ! $upgrade_dir_exists && ! $wp_filesystem->is_writable( $wp_content ) ) {
     2097            $result['status'] = 'critical';
     2098            $result['label']  = sprintf(
     2099                /* translators: %s: upgrade */
     2100                __( 'The %s directory cannot be created' ),
     2101                'upgrade'
     2102            );
     2103            $result['description'] = sprintf(
     2104                /* translators: 1: wp-content/upgrade, 2: wp-content. */
     2105                '<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>',
     2106                '<code>wp-content/upgrade</code>',
     2107                '<code>wp-content</code>'
     2108            );
     2109            return $result;
     2110        }
     2111
     2112        return $result;
     2113    }
     2114
     2115    /**
    19292116     * Tests if loopbacks work as expected.
    19302117     *
     
    25332720        $tests = array(
    25342721            'direct' => array(
    2535                 'wordpress_version'         => array(
     2722                'wordpress_version'            => array(
    25362723                    'label' => __( 'WordPress Version' ),
    25372724                    'test'  => 'wordpress_version',
    25382725                ),
    2539                 'plugin_version'            => array(
     2726                'plugin_version'               => array(
    25402727                    'label' => __( 'Plugin Versions' ),
    25412728                    'test'  => 'plugin_version',
    25422729                ),
    2543                 'theme_version'             => array(
     2730                'theme_version'                => array(
    25442731                    'label' => __( 'Theme Versions' ),
    25452732                    'test'  => 'theme_version',
    25462733                ),
    2547                 'php_version'               => array(
     2734                'php_version'                  => array(
    25482735                    'label' => __( 'PHP Version' ),
    25492736                    'test'  => 'php_version',
    25502737                ),
    2551                 'php_extensions'            => array(
     2738                'php_extensions'               => array(
    25522739                    'label' => __( 'PHP Extensions' ),
    25532740                    'test'  => 'php_extensions',
    25542741                ),
    2555                 'php_default_timezone'      => array(
     2742                'php_default_timezone'         => array(
    25562743                    'label' => __( 'PHP Default Timezone' ),
    25572744                    'test'  => 'php_default_timezone',
    25582745                ),
    2559                 'php_sessions'              => array(
     2746                'php_sessions'                 => array(
    25602747                    'label' => __( 'PHP Sessions' ),
    25612748                    'test'  => 'php_sessions',
    25622749                ),
    2563                 'sql_server'                => array(
     2750                'sql_server'                   => array(
    25642751                    'label' => __( 'Database Server version' ),
    25652752                    'test'  => 'sql_server',
    25662753                ),
    2567                 'utf8mb4_support'           => array(
     2754                'utf8mb4_support'              => array(
    25682755                    'label' => __( 'MySQL utf8mb4 support' ),
    25692756                    'test'  => 'utf8mb4_support',
    25702757                ),
    2571                 'ssl_support'               => array(
     2758                'ssl_support'                  => array(
    25722759                    'label' => __( 'Secure communication' ),
    25732760                    'test'  => 'ssl_support',
    25742761                ),
    2575                 'scheduled_events'          => array(
     2762                'scheduled_events'             => array(
    25762763                    'label' => __( 'Scheduled events' ),
    25772764                    'test'  => 'scheduled_events',
    25782765                ),
    2579                 'http_requests'             => array(
     2766                'http_requests'                => array(
    25802767                    'label' => __( 'HTTP Requests' ),
    25812768                    'test'  => 'http_requests',
    25822769                ),
    2583                 'rest_availability'         => array(
     2770                'rest_availability'            => array(
    25842771                    'label'     => __( 'REST API availability' ),
    25852772                    'test'      => 'rest_availability',
    25862773                    'skip_cron' => true,
    25872774                ),
    2588                 'debug_enabled'             => array(
     2775                'debug_enabled'                => array(
    25892776                    'label' => __( 'Debugging enabled' ),
    25902777                    'test'  => 'is_in_debug_mode',
    25912778                ),
    2592                 'file_uploads'              => array(
     2779                'file_uploads'                 => array(
    25932780                    'label' => __( 'File uploads' ),
    25942781                    'test'  => 'file_uploads',
    25952782                ),
    2596                 'plugin_theme_auto_updates' => array(
     2783                'plugin_theme_auto_updates'    => array(
    25972784                    'label' => __( 'Plugin and theme auto-updates' ),
    25982785                    'test'  => 'plugin_theme_auto_updates',
     2786                ),
     2787                'update_temp_backup_writable'  => array(
     2788                    'label' => __( 'Plugin and theme temporary backup directory access' ),
     2789                    'test'  => 'update_temp_backup_writable',
     2790                ),
     2791                'available_updates_disk_space' => array(
     2792                    'label' => __( 'Available disk space' ),
     2793                    'test'  => 'available_updates_disk_space',
    25992794                ),
    26002795            ),
  • trunk/src/wp-admin/includes/class-wp-upgrader.php

    r55258 r55720  
    114114
    115115    /**
     116     * Stores the list of plugins or themes added to temporary backup directory.
     117     *
     118     * Used by the rollback functions.
     119     *
     120     * @since 6.3.0
     121     * @var array
     122     */
     123    private $temp_backups = array();
     124
     125    /**
     126     * Stores the list of plugins or themes to be restored from temporary backup directory.
     127     *
     128     * Used by the rollback functions.
     129     *
     130     * @since 6.3.0
     131     * @var array
     132     */
     133    private $temp_restores = array();
     134
     135    /**
    116136     * Construct the upgrader with a skin.
    117137     *
     
    135155     * and also add the generic strings to `WP_Upgrader::$strings`.
    136156     *
     157     * Additionally, it will schedule a weekly task to clean up the temporary backup directory.
     158     *
    137159     * @since 2.8.0
     160     * @since 6.3.0 Added the `schedule_temp_backup_cleanup()` task.
    138161     */
    139162    public function init() {
    140163        $this->skin->set_upgrader( $this );
    141164        $this->generic_strings();
     165
     166        if ( ! wp_installing() ) {
     167            $this->schedule_temp_backup_cleanup();
     168        }
     169    }
     170
     171    /**
     172     * Schedules the cleanup of the temporary backup directory.
     173     *
     174     * @since 6.3.0
     175     */
     176    protected function schedule_temp_backup_cleanup() {
     177        if ( false === wp_next_scheduled( 'wp_delete_temp_updater_backups' ) ) {
     178            wp_schedule_event( time(), 'weekly', 'wp_delete_temp_updater_backups' );
     179        }
    142180    }
    143181
     
    168206        $this->strings['maintenance_start'] = __( 'Enabling Maintenance mode&#8230;' );
    169207        $this->strings['maintenance_end']   = __( 'Disabling Maintenance mode&#8230;' );
     208
     209        /* translators: %s: upgrade-temp-backup */
     210        $this->strings['temp_backup_mkdir_failed'] = sprintf( __( 'Could not create the %s directory.' ), 'upgrade-temp-backup' );
     211        /* translators: %s: upgrade-temp-backup */
     212        $this->strings['temp_backup_move_failed'] = sprintf( __( 'Could not move the old version to the %s directory.' ), 'upgrade-temp-backup' );
     213        /* translators: %s: The plugin or theme slug. */
     214        $this->strings['temp_backup_restore_failed'] = __( 'Could not restore the original version of %s.' );
     215        /* translators: %s: The plugin or theme slug. */
     216        $this->strings['temp_backup_delete_failed'] = __( 'Could not delete the temporary backup directory for %s.' );
    170217    }
    171218
     
    309356        $this->skin->feedback( 'unpack_package' );
    310357
     358        if ( ! $wp_filesystem->wp_content_dir() ) {
     359            return new WP_Error( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
     360        }
     361
    311362        $upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
    312363
     
    533584        if ( is_wp_error( $source ) ) {
    534585            return $source;
     586        }
     587
     588        if ( ! empty( $args['hook_extra']['temp_backup'] ) ) {
     589            $temp_backup = $this->move_to_temp_backup_dir( $args['hook_extra']['temp_backup'] );
     590
     591            if ( is_wp_error( $temp_backup ) ) {
     592                return $temp_backup;
     593            }
     594
     595            $this->temp_backups[] = $args['hook_extra']['temp_backup'];
    535596        }
    536597
     
    615676        }
    616677
    617         // Clear the working folder?
     678        // Clear the working directory?
    618679        if ( $args['clear_working'] ) {
    619680            $wp_filesystem->delete( $remote_source, true );
     
    828889
    829890        $this->skin->set_result( $result );
     891
    830892        if ( is_wp_error( $result ) ) {
     893            if ( ! empty( $options['hook_extra']['temp_backup'] ) ) {
     894                $this->temp_restores[] = $options['hook_extra']['temp_backup'];
     895
     896                /*
     897                 * Restore the backup on shutdown.
     898                 * Actions running on `shutdown` are immune to PHP timeouts,
     899                 * so in case the failure was due to a PHP timeout,
     900                 * it will still be able to properly restore the previous version.
     901                 */
     902                add_action( 'shutdown', array( $this, 'restore_temp_backup' ) );
     903            }
    831904            $this->skin->error( $result );
    832905
     
    840913
    841914        $this->skin->after();
     915
     916        // Clean up the backup kept in the temporary backup directory.
     917        if ( ! empty( $options['hook_extra']['temp_backup'] ) ) {
     918            // Delete the backup on `shutdown` to avoid a PHP timeout.
     919            add_action( 'shutdown', array( $this, 'delete_temp_backup' ), 100, 0 );
     920        }
    842921
    843922        if ( ! $options['is_multi'] ) {
     
    9671046        return delete_option( $lock_name . '.lock' );
    9681047    }
     1048
     1049    /**
     1050     * Moves the plugin or theme being updated into a temporary backup directory.
     1051     *
     1052     * @since 6.3.0
     1053     *
     1054     * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
     1055     *
     1056     * @param string[] $args {
     1057     *     Array of data for the temporary backup.
     1058     *
     1059     *     @type string $slug Plugin or theme slug.
     1060     *     @type string $src  Path to the root directory for plugins or themes.
     1061     *     @type string $dir  Destination subdirectory name. Accepts 'plugins' or 'themes'.
     1062     * }
     1063     *
     1064     * @return bool|WP_Error True on success, false on early exit, otherwise WP_Error.
     1065     */
     1066    public function move_to_temp_backup_dir( $args ) {
     1067        global $wp_filesystem;
     1068
     1069        if ( empty( $args['slug'] ) || empty( $args['src'] ) || empty( $args['dir'] ) ) {
     1070            return false;
     1071        }
     1072
     1073        /*
     1074         * Skip any plugin that has "." as its slug.
     1075         * A slug of "." will result in a `$src` value ending in a period.
     1076         *
     1077         * On Windows, this will cause the 'plugins' folder to be moved,
     1078         * and will cause a failure when attempting to call `mkdir()`.
     1079         */
     1080        if ( '.' === $args['slug'] ) {
     1081            return false;
     1082        }
     1083
     1084        if ( ! $wp_filesystem->wp_content_dir() ) {
     1085            return new WP_Error( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
     1086        }
     1087
     1088        $dest_dir = $wp_filesystem->wp_content_dir() . 'upgrade-temp-backup/';
     1089        $sub_dir  = $dest_dir . $args['dir'] . '/';
     1090
     1091        // Create the temporary backup directory if it does not exist.
     1092        if ( ! $wp_filesystem->is_dir( $sub_dir ) ) {
     1093            if ( ! $wp_filesystem->is_dir( $dest_dir ) ) {
     1094                $wp_filesystem->mkdir( $dest_dir, FS_CHMOD_DIR );
     1095            }
     1096
     1097            if ( ! $wp_filesystem->mkdir( $sub_dir, FS_CHMOD_DIR ) ) {
     1098                // Could not create the backup directory.
     1099                return new WP_Error( 'fs_temp_backup_mkdir', $this->strings['temp_backup_mkdir_failed'] );
     1100            }
     1101        }
     1102
     1103        $src_dir = $wp_filesystem->find_folder( $args['src'] );
     1104        $src     = trailingslashit( $src_dir ) . $args['slug'];
     1105        $dest    = $dest_dir . trailingslashit( $args['dir'] ) . $args['slug'];
     1106
     1107        // Delete the temporary backup directory if it already exists.
     1108        if ( $wp_filesystem->is_dir( $dest ) ) {
     1109            $wp_filesystem->delete( $dest, true );
     1110        }
     1111
     1112        // Move to the temporary backup directory.
     1113        $result = move_dir( $src, $dest, true );
     1114        if ( is_wp_error( $result ) ) {
     1115            return new WP_Error( 'fs_temp_backup_move', $this->strings['temp_backup_move_failed'] );
     1116        }
     1117
     1118        return true;
     1119    }
     1120
     1121    /**
     1122     * Restores the plugin or theme from temporary backup.
     1123     *
     1124     * @since 6.3.0
     1125     *
     1126     * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
     1127     *
     1128     * @return bool|WP_Error True on success, false on early exit, otherwise WP_Error.
     1129     */
     1130    public function restore_temp_backup() {
     1131        global $wp_filesystem;
     1132
     1133        $errors = new WP_Error();
     1134
     1135        foreach ( $this->temp_restores as $args ) {
     1136            if ( empty( $args['slug'] ) || empty( $args['src'] ) || empty( $args['dir'] ) ) {
     1137                return false;
     1138            }
     1139
     1140            if ( ! $wp_filesystem->wp_content_dir() ) {
     1141                $errors->add( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
     1142                return $errors;
     1143            }
     1144
     1145            $src      = $wp_filesystem->wp_content_dir() . 'upgrade-temp-backup/' . $args['dir'] . '/' . $args['slug'];
     1146            $dest_dir = $wp_filesystem->find_folder( $args['src'] );
     1147            $dest     = trailingslashit( $dest_dir ) . $args['slug'];
     1148
     1149            if ( $wp_filesystem->is_dir( $src ) ) {
     1150                // Cleanup.
     1151                if ( $wp_filesystem->is_dir( $dest ) && ! $wp_filesystem->delete( $dest, true ) ) {
     1152                    $errors->add(
     1153                        'fs_temp_backup_delete',
     1154                        sprintf( $this->strings['temp_backup_restore_failed'], $args['slug'] )
     1155                    );
     1156                    continue;
     1157                }
     1158
     1159                // Move it.
     1160                $result = move_dir( $src, $dest, true );
     1161                if ( is_wp_error( $result ) ) {
     1162                    $errors->add(
     1163                        'fs_temp_backup_delete',
     1164                        sprintf( $this->strings['temp_backup_restore_failed'], $args['slug'] )
     1165                    );
     1166                    continue;
     1167                }
     1168            }
     1169        }
     1170
     1171        return $errors->has_errors() ? $errors : true;
     1172    }
     1173
     1174    /**
     1175     * Deletes a temporary backup.
     1176     *
     1177     * @since 6.3.0
     1178     *
     1179     * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
     1180     *
     1181     * @return bool|WP_Error True on success, false on early exit, otherwise WP_Error.
     1182     */
     1183    public function delete_temp_backup() {
     1184        global $wp_filesystem;
     1185
     1186        $errors = new WP_Error();
     1187
     1188        foreach ( $this->temp_backups as $args ) {
     1189            if ( empty( $args['slug'] ) || empty( $args['dir'] ) ) {
     1190                return false;
     1191            }
     1192
     1193            if ( ! $wp_filesystem->wp_content_dir() ) {
     1194                $errors->add( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
     1195                return $errors;
     1196            }
     1197
     1198            $temp_backup_dir = $wp_filesystem->wp_content_dir() . "upgrade-temp-backup/{$args['dir']}/{$args['slug']}";
     1199
     1200            if ( ! $wp_filesystem->delete( $temp_backup_dir, true ) ) {
     1201                $errors->add(
     1202                    'temp_backup_delete_failed',
     1203                    sprintf( $this->strings['temp_backup_delete_failed'] ),
     1204                    $args['slug']
     1205                );
     1206                continue;
     1207            }
     1208        }
     1209
     1210        return $errors->has_errors() ? $errors : true;
     1211    }
    9691212}
    9701213
  • trunk/src/wp-includes/update.php

    r55641 r55720  
    10781078}
    10791079
     1080/**
     1081 * Schedules the removal of all contents in the temporary backup directory.
     1082 *
     1083 * @since 6.3.0
     1084 */
     1085function wp_delete_all_temp_backups() {
     1086    /*
     1087     * Check if there is a lock, or if currently performing an Ajax request,
     1088     * in which case there is a chance an update is running.
     1089     * Reschedule for an hour from now and exit early.
     1090     */
     1091    if ( get_option( 'core_updater.lock' ) || get_option( 'auto_updater.lock' ) || wp_doing_ajax() ) {
     1092        wp_schedule_single_event( time() + HOUR_IN_SECONDS, 'wp_delete_temp_updater_backups' );
     1093        return;
     1094    }
     1095
     1096    // This action runs on shutdown to make sure there are no plugin updates currently running.
     1097    add_action( 'shutdown', '_wp_delete_all_temp_backups' );
     1098}
     1099
     1100/**
     1101 * Deletes all contents in the temporary backup directory.
     1102 *
     1103 * @since 6.3.0
     1104 *
     1105 * @access private
     1106 *
     1107 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
     1108 *
     1109 * @return void|WP_Error Void on success, or a WP_Error object on failure.
     1110 */
     1111function _wp_delete_all_temp_backups() {
     1112    global $wp_filesystem;
     1113
     1114    if ( ! $wp_filesystem ) {
     1115        require_once ABSPATH . '/wp-admin/includes/file.php';
     1116        WP_Filesystem();
     1117    }
     1118
     1119    if ( ! $wp_filesystem->wp_content_dir() ) {
     1120        return new WP_Error( 'fs_no_content_dir', __( 'Unable to locate WordPress content directory (wp-content).' ) );
     1121    }
     1122
     1123    $temp_backup_dir = $wp_filesystem->wp_content_dir() . 'upgrade-temp-backup/';
     1124    $dirlist         = $wp_filesystem->dirlist( $temp_backup_dir );
     1125    $dirlist         = $dirlist ? $dirlist : array();
     1126
     1127    foreach ( array_keys( $dirlist ) as $dir ) {
     1128        if ( '.' === $dir || '..' === $dir ) {
     1129            continue;
     1130        }
     1131
     1132        $wp_filesystem->delete( $temp_backup_dir . $dir, true );
     1133    }
     1134}
     1135
    10801136if ( ( ! is_main_site() && ! is_network_admin() ) || wp_doing_ajax() ) {
    10811137    return;
     
    11021158
    11031159add_action( 'init', 'wp_schedule_update_checks' );
     1160
     1161add_action( 'wp_delete_temp_updater_backups', 'wp_delete_all_temp_backups' );
Note: See TracChangeset for help on using the changeset viewer.