Make WordPress Core


Ignore:
Timestamp:
09/15/2021 06:39:09 PM (5 years 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 temporary backup kept in the upgrade/temp-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 temp-backup directory is writable.
  • A check that there is enough disk space available to safely perform updates.

To avoid confusion: The temp-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.

Props aristath, afragen, pbiron, dd32, poena, TimothyBlynJacobs, audrasjb, mikeschroder, a2hosting, hellofromTonya, KZeni, galbaras, richards1052, Boniu91, mai21, francina, SergeyBiryukov.
See #51857.

File:
1 edited

Legend:

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

    r51655 r51815  
    134134         * and also add the generic strings to `WP_Upgrader::$strings`.
    135135         *
     136         * Additionally, it will schedule a weekly task to clean up the temp-backup directory.
     137         *
    136138         * @since 2.8.0
     139         * @since 5.9.0 Added the `schedule_temp_backup_cleanup()` task.
    137140         */
    138141        public function init() {
    139142                $this->skin->set_upgrader( $this );
    140143                $this->generic_strings();
     144                $this->schedule_temp_backup_cleanup();
     145        }
     146
     147        /**
     148         * Schedule cleanup of the temp-backup directory.
     149         *
     150         * @since 5.9.0
     151         */
     152        protected function schedule_temp_backup_cleanup() {
     153                wp_schedule_event( time(), 'weekly', 'delete_temp_updater_backups' );
     154                add_action( 'delete_temp_updater_backups', array( $this, 'delete_all_temp_backups' ) );
    141155        }
    142156
     
    167181                $this->strings['maintenance_start'] = __( 'Enabling Maintenance mode…' );
    168182                $this->strings['maintenance_end']   = __( 'Disabling Maintenance mode…' );
     183
     184                /* translators: %s: temp-backup */
     185                $this->strings['temp_backup_mkdir_failed'] = sprintf( __( 'Could not create the %s directory.' ), 'temp-backup' );
     186                /* translators: %s: temp-backup */
     187                $this->strings['temp_backup_move_failed']    = sprintf( __( 'Could not move old version to the %s directory.' ), 'temp-backup' );
     188                $this->strings['temp_backup_restore_failed'] = __( 'Could not restore original version.' );
     189
    169190        }
    170191
     
    314335                if ( ! empty( $upgrade_files ) ) {
    315336                        foreach ( $upgrade_files as $file ) {
     337                                if ( 'temp-backup' === $file['name'] ) {
     338                                        continue;
     339                                }
    316340                                $wp_filesystem->delete( $upgrade_folder . $file['name'], true );
    317341                        }
     
    492516                if ( is_wp_error( $res ) ) {
    493517                        return $res;
     518                }
     519
     520                if ( ! empty( $args['hook_extra']['temp_backup'] ) ) {
     521                        $temp_backup = $this->move_to_temp_backup_dir( $args['hook_extra']['temp_backup'] );
     522                        if ( is_wp_error( $temp_backup ) ) {
     523                                return $temp_backup;
     524                        }
    494525                }
    495526
     
    812843                $this->skin->set_result( $result );
    813844                if ( is_wp_error( $result ) ) {
     845                        if ( ! empty( $options['hook_extra']['temp_backup'] ) ) {
     846                                $this->restore_temp_backup( $options['hook_extra']['temp_backup'] );
     847                        }
    814848                        $this->skin->error( $result );
    815849
     
    823857
    824858                $this->skin->after();
     859
     860                // Clean up the backup kept in the temp-backup directory.
     861                if ( ! empty( $options['hook_extra']['temp_backup'] ) ) {
     862                        $this->delete_temp_backup( $options['hook_extra']['temp_backup'] );
     863                }
    825864
    826865                if ( ! $options['is_multi'] ) {
     
    949988        }
    950989
     990        /**
     991         * Moves the plugin/theme being updated into a temp-backup directory.
     992         *
     993         * @since 5.9.0
     994         *
     995         * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
     996         *
     997         * @param array $args Array of data for the temp-backup. Must include a slug, the source, and directory.
     998         * @return bool|WP_Error
     999         */
     1000        public function move_to_temp_backup_dir( $args ) {
     1001                global $wp_filesystem;
     1002
     1003                if ( empty( $args['slug'] ) || empty( $args['src'] ) || empty( $args['dir'] ) ) {
     1004                        return false;
     1005                }
     1006
     1007                $dest_dir = $wp_filesystem->wp_content_dir() . 'upgrade/temp-backup/';
     1008                // Create the temp-backup directory if it doesn't exist.
     1009                if ( (
     1010                                ! $wp_filesystem->is_dir( $dest_dir )
     1011                                && ! $wp_filesystem->mkdir( $dest_dir )
     1012                        ) || (
     1013                                ! $wp_filesystem->is_dir( $dest_dir . $args['dir'] . '/' )
     1014                                && ! $wp_filesystem->mkdir( $dest_dir . $args['dir'] . '/' )
     1015                        )
     1016                ) {
     1017                        return new WP_Error( 'fs_temp_backup_mkdir', $this->strings['temp_backup_mkdir_failed'] );
     1018                }
     1019
     1020                $src  = trailingslashit( $args['src'] ) . $args['slug'];
     1021                $dest = $dest_dir . $args['dir'] . '/' . $args['slug'];
     1022
     1023                // Delete the temp-backup directory if it already exists.
     1024                if ( $wp_filesystem->is_dir( $dest ) ) {
     1025                        $wp_filesystem->delete( $dest, true );
     1026                }
     1027
     1028                // Move to the temp-backup directory.
     1029                if ( ! $wp_filesystem->move( $src, $dest, true ) ) {
     1030                        return new WP_Error( 'fs_temp_backup_move', $this->strings['temp_backup_move_failed'] );
     1031                }
     1032
     1033                return true;
     1034        }
     1035
     1036        /**
     1037         * Restores the plugin/theme from the temp-backup directory.
     1038         *
     1039         * @since 5.9.0
     1040         *
     1041         * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
     1042         *
     1043         * @param array $args Array of data for the temp-backup. Must include a slug, the source, and directory.
     1044         * @return bool|WP_Error
     1045         */
     1046        public function restore_temp_backup( $args ) {
     1047                global $wp_filesystem;
     1048
     1049                if ( empty( $args['slug'] ) || empty( $args['src'] ) || empty( $args['dir'] ) ) {
     1050                        return false;
     1051                }
     1052
     1053                $src  = $wp_filesystem->wp_content_dir() . 'upgrade/temp-backup/' . $args['dir'] . '/' . $args['slug'];
     1054                $dest = trailingslashit( $args['src'] ) . $args['slug'];
     1055
     1056                if ( $wp_filesystem->is_dir( $src ) ) {
     1057                        // Cleanup.
     1058                        if ( $wp_filesystem->is_dir( $dest ) && ! $wp_filesystem->delete( $dest, true ) ) {
     1059                                return new WP_Error( 'fs_temp_backup_delete', $this->strings['temp_backup_restore_failed'] );
     1060                        }
     1061
     1062                        // Move it.
     1063                        if ( ! $wp_filesystem->move( $src, $dest, true ) ) {
     1064                                return new WP_Error( 'fs_temp_backup_delete', $this->strings['temp_backup_restore_failed'] );
     1065                        }
     1066                }
     1067
     1068                return true;
     1069        }
     1070
     1071        /**
     1072         * Deletes a temp-backup.
     1073         *
     1074         * @since 5.9.0
     1075         *
     1076         * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
     1077         *
     1078         * @param array $args Array of data for the temp-backup. Must include a slug, the source, and directory.
     1079         * @return bool
     1080         */
     1081        public function delete_temp_backup( $args ) {
     1082                global $wp_filesystem;
     1083
     1084                if ( empty( $args['slug'] ) || empty( $args['dir'] ) ) {
     1085                        return false;
     1086                }
     1087
     1088                return $wp_filesystem->delete(
     1089                        $wp_filesystem->wp_content_dir() . "upgrade/temp-backup/{$args['dir']}/{$args['slug']}",
     1090                        true
     1091                );
     1092        }
     1093
     1094        /**
     1095         * Deletes all contents of the temp-backup directory.
     1096         *
     1097         * @since 5.9.0
     1098         *
     1099         * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
     1100         */
     1101        public function delete_all_temp_backups() {
     1102                /*
     1103                 * Check if there's a lock, or if currently performing an Ajax request,
     1104                 * in which case there's a chance we're doing an update.
     1105                 * Reschedule for an hour from now and exit early.
     1106                 */
     1107                if ( get_option( 'core_updater.lock' ) || get_option( 'auto_updater.lock' ) || wp_doing_ajax() ) {
     1108                        wp_schedule_single_event( time() + HOUR_IN_SECONDS, 'delete_temp_updater_backups' );
     1109                        return;
     1110                }
     1111
     1112                add_action(
     1113                        'shutdown',
     1114                        /*
     1115                         * This action runs on shutdown to make sure there's no plugin updates currently running.
     1116                         * Using a closure in this case is OK since the action can be removed by removing the parent hook.
     1117                         */
     1118                        function() {
     1119                                global $wp_filesystem;
     1120
     1121                                if ( ! $wp_filesystem ) {
     1122                                        include_once ABSPATH . '/wp-admin/includes/file.php';
     1123                                        WP_Filesystem();
     1124                                }
     1125
     1126                                $dirlist = $wp_filesystem->dirlist( $wp_filesystem->wp_content_dir() . 'upgrade/temp-backup/' );
     1127
     1128                                foreach ( array_keys( $dirlist ) as $dir ) {
     1129                                        if ( '.' === $dir || '..' === $dir ) {
     1130                                                continue;
     1131                                        }
     1132
     1133                                        $wp_filesystem->delete( $wp_filesystem->wp_content_dir() . 'upgrade/temp-backup/' . $dir, true );
     1134                                }
     1135                        }
     1136                );
     1137        }
    9511138}
    9521139
Note: See TracChangeset for help on using the changeset viewer.