Make WordPress Core


Ignore:
Timestamp:
09/15/2021 06:39:09 PM (3 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.