if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed');
if (!class_exists('Updraft_Restorer_Skin')) require_once(UPDRAFTPLUS_DIR.'/includes/updraft-restorer-skin.php');
if (!class_exists('UpdraftPlus_Search_Replace')) require_once(UPDRAFTPLUS_DIR.'/includes/class-search-replace.php');
// This just stores the result of is_multisite()
// This is just used so far for detecting whether we're on the second run for an entity or not.
public $been_restored = array();
private $tables_been_dropped = array();
// Public: it is manipulated by the caller after the caller gets the object
private $created_by_version = false;
// This one can be set externally, if the information is available
public $ud_backup_is_multisite = -1;
// store restored table names
public $restored_table_names = array();
public $is_dummy_db_restore = false;
// The default of false means "use the global $wpdb"
private $wpdb_obj = false;
private $line_last_logged = 0;
private $configuration_bundle;
private $ajax_restore_auth_code;
private $restore_options;
private $restore_this_site = array();
private $restore_this_table = array();
private $restoring_table = '';
private $statements_run = 0;
private $use_wpdb = null;
private $import_table_prefix = null;
private $final_import_table_prefix = null;
private $disable_atomic_on_current_table = false;
private $table_engine = '';
private $table_name = '';
private $continuation_data;
private $current_index = 0;
private $current_type = '';
private $previous_table_name = '';
private $include_unspecified_tables = false;
private $tables_to_restore = array();
private $stored_routine_supported = null;
private $tables_to_skip = array();
public $search_replace_obj = null;
// Constants for use with the move_backup_in method
// These can't be arbitrarily changed; there is legacy code doing bitwise operations and numerical comparisons, and possibly legacy code still using the values directly.
const MOVEIN_OVERWRITE_NO_BACKUP = 0;
const MOVEIN_MAKE_BACKUP_OF_EXISTING = 1;
const MOVEIN_DO_NOTHING_IF_EXISTING = 2;
const MOVEIN_COPY_IN_CONTENTS = 3;
public $strings = array();
private $generated_columns = array();
private $supported_generated_column_engines = array();
private $generated_columns_exist_in_the_statement = array();
private $printed_new_table_prefix = false;
private $old_table_prefix = null;
private $old_abspath = '';
* @param WP_Upgrader_Skin|Null $skin - an upgrader skin
* @param Array|Null $backup_set - the backup set to restore
* @param Boolean $short_init - whether just to do a minimal initialisation
* @param Array $restore_options - options to guide the restoration
* @param Array|Null $continuation_data - continuation data; the jobdata of the job thus far (but only a few properties are used - including second_loop_entities; $restore_options will have come from there too if relevant, but that is passed in here separately); the 'last_index_*' entries also indicate unzipping progress
public function __construct($skin = null, $backup_set = null, $short_init = false, $restore_options = array(), $continuation_data = null) {
$this->our_siteurl = untrailingslashit(site_url());
$this->continuation_data = $continuation_data;
$this->setup_database_objects();
$this->search_replace_obj = new UpdraftPlus_Search_Replace();
// If updraft_incremental_restore_point is equal to -1 then this is either not a incremental restore or we are going to restore up to the latest increment, so there is no need to prune the backup set of any unwanted backup archives.
if (isset($restore_options['updraft_incremental_restore_point']) && $restore_options['updraft_incremental_restore_point'] > 0) {
$restore_point = $restore_options['updraft_incremental_restore_point'];
foreach ($backup_set['incremental_sets'] as $increment_timestamp => $entities) {
if ($increment_timestamp > $restore_point) {
foreach ($entities as $entity => $backups) {
foreach ($backups as $key => $value) {
unset($backup_set[$entity][$key]);
// if updraft_include_more_path is set then, the user has chosen where they want these backup files to be restored as either UD did not know where to restore them or the original location is not found on disk any more
if (isset($restore_options['updraft_include_more_path'])) {
if (!isset($backup_set['morefiles_linked_indexes']) || !isset($backup_set['morefiles_more_locations'])) {
$backup_set['morefiles_more_locations'] = $restore_options['updraft_include_more_path'];
foreach ($restore_options['updraft_include_more_path'] as $key => $path) {
$backup_set['morefiles_linked_indexes'][] = $key;
foreach ($restore_options['updraft_include_more_path'] as $key => $path) {
$backup_set['morefiles_more_locations'][$key] = $path;
if (isset($restore_options['updraft_include_more_index'])) {
// unset any backups the user has chosen not to restore
foreach (array_keys($backup_set['more']) as $key) {
if (!in_array($key, $restore_options['updraft_include_more_index'])) unset($backup_set['more'][$key]);
if (isset($restore_options['include_unspecified_tables'])) $this->include_unspecified_tables = $restore_options['include_unspecified_tables'];
if (isset($restore_options['tables_to_restore'])) $this->tables_to_restore = $restore_options['tables_to_restore'];
if (isset($restore_options['tables_to_skip'])) $this->tables_to_skip = $restore_options['tables_to_skip'];
// Restore in the most helpful order
uksort($backup_set, array('UpdraftPlus_Manipulation_Functions', 'sort_restoration_entities'));
$this->ud_backup_set = $backup_set;
add_filter('updraftplus_logline', array($this, 'updraftplus_logline'), 10, 5);
add_action('updraftplus_restored_db_table', array($this, 'updraftplus_restored_db_table'), 10, 3);
do_action('updraftplus_restorer_restore_options', $restore_options);
$this->ud_multisite_selective_restore = (is_array($restore_options) && !empty($restore_options['updraft_restore_ms_whichsites']) && $restore_options['updraft_restore_ms_whichsites'] > 0) ? $restore_options['updraft_restore_ms_whichsites'] : false;
$this->restore_options = $restore_options;
$this->ud_foreign = empty($backup_set['meta_foreign']) ? false : $backup_set['meta_foreign'];
if (isset($backup_set['is_multisite'])) $this->ud_backup_is_multisite = $backup_set['is_multisite'];
if (isset($backup_set['created_by_version'])) $this->created_by_version = $backup_set['created_by_version'];
$this->is_multisite = is_multisite();
require_once(UPDRAFTPLUS_DIR.'/includes/class-database-utility.php');
if (!class_exists('WP_Upgrader')) include_once(ABSPATH.'wp-admin/includes/class-wp-upgrader.php');
$this->wp_upgrader = new WP_Upgrader($skin);
$this->wp_upgrader->init();
* This function will check if we are using wpdb, if we are not then it will setup our wpdb-like objects
* @param boolean $reconnect_wpdb - if we should include and create a new instance of wpdb
private function setup_database_objects($reconnect_wpdb = false) {
// Line up a wpdb-like object
if (!$this->use_wpdb()) {
// We have our own extension which drops lots of the overhead on the query
$wpdb_obj = new UpdraftPlus_WPDB(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);
if (!$wpdb_obj->is_mysql || !$wpdb_obj->ready) {
$this->wpdb_obj = $wpdb_obj;
$this->mysql_dbh = $wpdb_obj->updraftplus_get_database_handle();
$this->use_mysqli = $wpdb_obj->updraftplus_use_mysqli();
* This function will try to restore the database connection, if it succeeds then it returns true otherwise false
* @return boolean - true if the connection is restored, otherwise false
private function restore_database_connection() {
global $updraftplus, $wpdb;
$wpdb_connected = $updraftplus->check_db_connection($wpdb, false, false, true);
if (false === $wpdb_connected || -1 === $wpdb_connected) {
$this->setup_database_objects(true);
* Get the wpdb-like object that we are using, if we are using one
* @return UpdraftPlus_WPDB|Boolean
public function get_db_object() {
* Restore has been completed - clean some things up
* @param Boolean|WP_Error $successful - if the restore was successful (true) or not (false or WP_Error). If not, then only a minimum of necessary clean-up things is done.
* @param Boolean $browser_context - if true, then extra messages will be echo-ed
* @uses UpdraftPlus::log()
public function post_restore_clean_up($successful = true, $browser_context = true) {
global $updraftplus, $updraftplus_admin;
$updraftplus->log_restore_update(array('type' => 'state', 'stage' => 'cleaning', 'data' => array()));
if (is_wp_error($successful)) {
foreach ($successful->get_error_codes() as $code) {
if ('already_exists' == $code) {
global $updraftplus_admin;
$updraftplus_admin->print_delete_old_dirs_form(false);
$updraftplus->log(__('Your WordPress install has old directories from its state before you restored/migrated (technical information: these are suffixed with -old).', 'updraftplus'));
$data = $successful->get_error_data($code);
$pdata = is_string($data) ? $data : serialize($data);
$updraftplus->log(__('Error data:', 'updraftplus').' '.$pdata, 'warning-restore');
if (false !== strpos($pdata, 'PCLZIP_ERR_BAD_FORMAT (-10)')) {
$url = apply_filters('updraftplus_com_link', 'https://updraftplus.com/faqs/error-message-pclzip_err_bad_format-10-invalid-archive-structure-mean/');
echo '<a href="'.$url.'" target="_blank"><strong>'.__('Follow this link for more information', 'updraftplus').'</strong></a><br>';
$updraftplus->log(__('Follow this link for more information', 'updraftplus').': '.$url);
// From this point on, $successful is a boolean
// All done - remove the intermediate marker
delete_site_option('updraft_restore_in_progress');
foreach (array('template', 'stylesheet', 'template_root', 'stylesheet_root') as $opt) {
add_filter('pre_option_'.$opt, array($this, 'option_filter_'.$opt));
// Clear any cached pages after the restore
// Have seen a case where the current theme in the DB began with a capital, but not on disk - and this breaks migrating from Windows to a case-sensitive system
$template = get_option('template');
if (!empty($template) && WP_DEFAULT_THEME != $template && strtolower($template) != $template) {
$theme_root = get_theme_root($template);
if (!file_exists("$theme_root/$template/style.css") && file_exists("$theme_root/".strtolower($template)."/style.css")) {
$updraftplus->log_e("Theme directory (%s) not found, but lower-case version exists; updating database option accordingly", $template);
update_option('template', strtolower($template));
if (!function_exists('validate_current_theme')) include_once(ABSPATH.WPINC.'/themes');
if (!validate_current_theme()) {
if ($browser_context) echo '<strong>';
$updraftplus->log_e("The current theme was not found; to prevent this stopping the site from loading, your theme has been reverted to the default theme");
if ($browser_context) echo '</strong>';
do_action('updraftplus_restore_completed');
if ($browser_context) echo '</div>'; // Close the updraft_restore_progress div
* Whether or not we must use the global $wpdb object for database queries.
* That is to say: we *can* always use it. But we prefer to avoid the overhead since we are potentially doing very many queries.
* This is the getter. We have no use-case for a setter outside of this class, so we just set it directly.
public function use_wpdb() {
if (!is_bool($this->use_wpdb)) {
if (defined('UPDRAFTPLUS_USE_WPDB')) {
$this->use_wpdb = (bool) UPDRAFTPLUS_USE_WPDB;
$this->use_wpdb = ((!function_exists('mysql_query') && !function_exists('mysqli_query')) || !$wpdb->is_mysql || !$wpdb->ready) ? true : false;
* @return WP_Upgrader_Skin
public function ud_get_skin() {
* Ensure that needed files are present locally, and return data for the next step (plus do some internal configuration)
* @param Array $entities_to_restore - as returned by self::get_entities_to_restore()
* @param Array $backupable_entities - list of entities that can be backed u
* @param Array $services - list of services that the backup can be found at
* @uses self::pre_restore_backup() (and some other internal properties)
* @uses UpdraftPlus::log()
* @return Boolean|Array|WP_Error - a sorted array (of entity types and files for each entity type) or false or a WP_Error if there was an error
private function ensure_restore_files_present($entities_to_restore, $backupable_entities, $services) {
$entities_to_download = $this->get_entities_to_download($entities_to_restore);
$backup_set = $this->ud_backup_set;
$timestamp = $backup_set['timestamp'];
$updraft_dir = $updraftplus->backups_dir_location();
$foreign_known = apply_filters('updraftplus_accept_archivename', array());
// First loop: make sure that files are present + readable; and populate array for second loop
foreach ($backup_set as $type => $files) {
// All restorable entities must be given explicitly, as we can store other arbitrary data in the history array
if (!isset($backupable_entities[$type]) && 'db' != $type) continue;
if (isset($backupable_entities[$type]['restorable']) && false == $backupable_entities[$type]['restorable']) continue;
if (!isset($entities_to_download[$type])) continue;
if ('wpcore' == $type && is_multisite() && 0 === $this->ud_backup_is_multisite) {
$updraftplus->log('wpcore: '.__('Skipping restoration of WordPress core when importing a single site into a multisite installation. If you had anything necessary in your WordPress directory then you will need to re-add it manually from the zip file.', 'updraftplus'), 'notice-restore');
// $updraftplus->log_e('Skipping restoration of WordPress core when importing a single site into a multisite installation. If you had anything necessary in your WordPress directory then you will need to re-add it manually from the zip file.');
if (is_string($files)) $files = array($files);
foreach ($files as $ind => $file) {
$fullpath = $updraft_dir.'/'.$file;
$updraftplus->log(sprintf(__("Looking for %s archive: file name: %s", 'updraftplus'), $type, $file), 'notice-restore');
if (is_array($this->continuation_data) && isset($this->continuation_data['second_loop_entities'][$type]) && !in_array($file, $this->continuation_data['second_loop_entities'][$type])) {
$updraftplus->log(__('Skipping: this archive was already restored.', 'updraftplus'), 'notice-restore');
// Set the marker so that the existing directory isn't moved out of the way
$this->been_restored[$type] = true;
if (!is_readable($fullpath) || 0 == filesize($fullpath)) UpdraftPlus_Storage_Methods_Interface::get_remote_file($services, $file, $timestamp, true);
$index = (0 == $ind) ? '' : $ind;
// If a file size is stored in the backup data, then verify correctness of the local file
if (isset($backup_set[$type.$index.'-size'])) {
$fs = $backup_set[$type.$index.'-size'];
$print_message = __("Archive is expected to be size:", 'updraftplus')." ".round($fs/1024, 1)." KB: ";
$as = @filesize($fullpath);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
$updraftplus->log($print_message.__('OK', 'updraftplus'), 'notice-restore');
$updraftplus->log($print_message.__('Error:', 'updraftplus')." ".__('file is size:', 'updraftplus')." ".round($as/1024)." ($fs, $as)", 'warning-restore');
$updraftplus->log(__("The backup records do not contain information about the proper size of this file.", 'updraftplus'), 'notice-restore');
if (!is_readable($fullpath)) {
$updraftplus->log(__('Could not read one of the files for restoration', 'updraftplus')." ($file)", 'warning-restore');
$updraftplus->log("$file: ".__('Could not read one of the files for restoration', 'updraftplus'), 'error');
if (empty($this->ud_foreign)) {
if ('db' != $type || empty($foreign_known[$this->ud_foreign]['separatedb'])) {
$types = array('wpcore');
foreach ($types as $check_type) {
$info = isset($backupable_entities[$check_type]) ? $backupable_entities[$check_type] : array();
$val = $this->pre_restore_backup($files, $check_type, $info);
$updraftplus->log_wp_error($val);
foreach ($val->get_error_messages() as $msg) {
$updraftplus->log(__('Error:', 'updraftplus').' '.$msg, 'warning-restore');
} elseif (false === $val) {
foreach ($entities_to_restore as $entity => $via) {
if ('wpcore' == $via && 'db' == $entity && count($files) > 1) {
$second_loop[$entity] = apply_filters('updraftplus_select_wpcore_file_with_db', $files, $this->ud_foreign);
$second_loop[$entity] = $files;
$this->delete = UpdraftPlus_Options::get_updraft_option('updraft_delete_local', 1) ? true : false;
if (empty($services) || array('email') === $services || !empty($this->ud_foreign)) {
if ($this->delete) $updraftplus->log_e('Will not delete any archives after unpacking them, because there was no cloud storage for this backup');
if (!empty($this->ud_foreign)) $updraftplus->log("Foreign backup; created by: ".$this->ud_foreign);
// Second loop: now actually do the restoration
uksort($second_loop, array('UpdraftPlus_Manipulation_Functions', 'sort_restoration_entities'));
// If continuing, then prune those already done
if (is_array($this->continuation_data) && isset($this->continuation_data['second_loop_entities'])) {
foreach ($second_loop as $type => $files) {
if (isset($this->continuation_data['second_loop_entities'][$type])) {