function restore_backup($timestamp) { @set_time_limit(900); global $wp_filesystem, $updraftplus; $backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history'); if (!is_array($backup_history[$timestamp])) { echo '<p>' . __('This backup does not exist in the backup history - restoration aborted. Timestamp:', 'updraftplus') . " {$timestamp}</p><br/>"; return new WP_Error('does_not_exist', __('Backup does not exist in the backup history', 'updraftplus')); } // request_filesystem_credentials passes on fields just via hidden name/value pairs. // Build array of parameters to be passed via this $extra_fields = array(); if (isset($_POST['updraft_restore']) && is_array($_POST['updraft_restore'])) { foreach ($_POST['updraft_restore'] as $entity) { $_POST['updraft_restore_' . $entity] = 1; $extra_fields[] = 'updraft_restore_' . $entity; } } // Now make sure that updraft_restorer_ option fields get passed along to request_filesystem_credentials foreach ($_POST as $key => $value) { if (0 === strpos($key, 'updraft_restorer_')) { $extra_fields[] = $key; } } $credentials = request_filesystem_credentials(UpdraftPlus_Options::admin_page() . "?page=updraftplus&action=updraft_restore&backup_timestamp={$timestamp}", '', false, false, $extra_fields); WP_Filesystem($credentials); if ($wp_filesystem->errors->get_error_code()) { foreach ($wp_filesystem->errors->get_error_messages() as $message) { show_message($message); } exit; } # Set up logging $updraftplus->backup_time_nonce(); $updraftplus->jobdata_set('job_type', 'restore'); $updraftplus->jobdata_set('job_time_ms', $updraftplus->job_time_ms); $updraftplus->logfile_open($updraftplus->nonce); # Provide download link for the log file #echo '<p><a target="_new" href="?action=downloadlog&page=updraftplus&updraftplus_backup_nonce='.htmlspecialchars($updraftplus->nonce).'">'.__('Follow this link to download the log file for this restoration.', 'updraftplus').'</a></p>'; # TODO: Automatic purging of old log files # TODO: Provide option to auto-email the log file //if we make it this far then WP_Filesystem has been instantiated and is functional (tested with ftpext, what about suPHP and other situations where direct may work?) echo '<h1>' . __('UpdraftPlus Restoration: Progress', 'updraftplus') . '</h1><div id="updraft-restore-progress">'; $this->show_admin_warning('<a target="_new" href="?action=downloadlog&page=updraftplus&updraftplus_backup_nonce=' . htmlspecialchars($updraftplus->nonce) . '">' . __('Follow this link to download the log file for this restoration (needed for any support requests).', 'updraftplus') . '</a>'); $updraft_dir = trailingslashit($updraftplus->backups_dir_location()); $service = isset($backup_history[$timestamp]['service']) ? $backup_history[$timestamp]['service'] : false; if (!is_array($service)) { $service = array($service); } // Now, need to turn any updraft_restore_<entity> fields (that came from a potential WP_Filesystem form) back into parts of the _POST array (which we want to use) if (empty($_POST['updraft_restore']) || !is_array($_POST['updraft_restore'])) { $_POST['updraft_restore'] = array(); } $entities_to_restore = array_flip($_POST['updraft_restore']); $entities_log = ''; foreach ($_POST as $key => $value) { if (strpos($key, 'updraft_restore_') === 0) { $nkey = substr($key, 16); if (!isset($entities_to_restore[$nkey])) { $_POST['updraft_restore'][] = $nkey; $entities_to_restore[$nkey] = 1; $entities_log .= '' == $entities_log ? $nkey : ",{$nkey}"; } } } $updraftplus->log("Restore job started. Entities to restore: {$entities_log}"); if (0 == count($_POST['updraft_restore'])) { echo '<p>' . __('ABORT: Could not find the information on which entities to restore.', 'updraftplus') . '</p>'; echo '<p>' . __('If making a request for support, please include this information:', 'updraftplus') . ' ' . count($_POST) . ' : ' . htmlspecialchars(serialize($_POST)) . '</p>'; return new WP_Error('missing_info', 'Backup information not found'); } set_error_handler(array($updraftplus, 'php_error'), E_ALL & ~E_STRICT); /* $_POST['updraft_restore'] is typically something like: array( 0=>'db', 1=>'plugins', 2=>'themes'), etc. i.e. array ( 'db', 'plugins', themes') */ $backupable_entities = $updraftplus->get_backupable_file_entities(true, true); $backup_set = $backup_history[$timestamp]; uksort($backup_set, array($this, 'sort_restoration_entities')); // We use a single object for each entity, because we want to store information about the backup set require_once UPDRAFTPLUS_DIR . '/restorer.php'; global $updraftplus_restorer; $updraftplus_restorer = new Updraft_Restorer(new Updraft_Restorer_Skin()); $second_loop = array(); echo "<h2>" . __('Final checks', 'updraftplus') . '</h2>'; // 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']) && $backupable_entities[$type]['restorable'] == false) { continue; } if (!isset($entities_to_restore[$type])) { continue; } if ($type == 'wpcore' && is_multisite() && 0 === $updraftplus_restorer->ud_backup_is_multisite) { echo "<p>{$type}: <strong>"; echo __('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'); #TODO #$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.'); echo "</strong></p>"; continue; } if (is_string($files)) { $files = array($files); } foreach ($files as $ind => $file) { $fullpath = $updraft_dir . $file; echo sprintf(__("Looking for %s archive: file name: %s", 'updraftplus'), $type, htmlspecialchars($file)) . "<br>"; foreach ($service as $serv) { if (!is_readable($fullpath)) { $sd = empty($updraftplus->backup_methods[$serv]) ? $serv : $updraftplus->backup_methods[$serv]; echo __("File is not locally present - needs retrieving from remote storage", 'updraftplus') . " ({$sd})"; $this->download_file($file, $serv); echo ": "; if (!is_readable($fullpath)) { echo __("Error", 'updraftplus'); } else { echo __("OK", 'updraftplus'); } echo '<br>'; } } $index = $ind == 0 ? '' : $ind; // If a file size is stored in the backup data, then verify correctness of the local file if (isset($backup_history[$timestamp][$type . $index . '-size'])) { $fs = $backup_history[$timestamp][$type . $index . '-size']; echo __("Archive is expected to be size:", 'updraftplus') . " " . round($fs / 1024, 1) . " Kb: "; $as = @filesize($fullpath); if ($as == $fs) { echo __('OK', 'updraftplus') . '<br>'; } else { echo "<strong>" . __('Error:', 'updraftplus') . "</strong> " . __('file is size:', 'updraftplus') . " " . round($as / 1024) . " ({$fs}, {$as})<br>"; } } else { echo __("The backup records do not contain information about the proper size of this file.", 'updraftplus') . "<br>"; } if (!is_readable($fullpath)) { echo __('Could not find one of the files for restoration', 'updraftplus') . " ({$file})<br>"; $updraftplus->log("{$file}: " . __('Could not find one of the files for restoration', 'updraftplus'), 'error'); echo '</div>'; restore_error_handler(); return false; } } $info = isset($backupable_entities[$type]) ? $backupable_entities[$type] : array(); $val = $updraftplus_restorer->pre_restore_backup($files, $type, $info); if (is_wp_error($val)) { $updraftplus->log_wp_error($val); foreach ($val->get_error_messages() as $msg) { echo '<strong>' . __('Error:', 'updraftplus') . '</strong> ' . htmlspecialchars($msg) . '<br>'; } foreach ($val->get_error_codes() as $code) { if ('already_exists' == $code) { $this->print_delete_old_dirs_form(false); } } echo '</div>'; //close the updraft_restore_progress div even if we error restore_error_handler(); return $val; } elseif (false === $val) { echo '</div>'; //close the updraft_restore_progress div even if we error restore_error_handler(); return false; } $second_loop[$type] = $files; } $updraftplus_restorer->delete = UpdraftPlus_Options::get_updraft_option('updraft_delete_local') ? true : false; if ('none' === $service || 'email' === $service || empty($service) || is_array($service) && 1 == count($service) && (in_array('none', $service) || in_array('', $service) || in_array('email', $service))) { if ($updraftplus_restorer->delete) { $updraftplus->log_e('Will not delete any archives after unpacking them, because there was no cloud storage for this backup'); } $updraftplus_restorer->delete = false; } // Second loop: now actually do the restoration uksort($second_loop, array($this, 'sort_restoration_entities')); foreach ($second_loop as $type => $files) { # Types: uploads, themes, plugins, others, db $info = isset($backupable_entities[$type]) ? $backupable_entities[$type] : array(); echo 'db' == $type ? "<h2>" . __('Database', 'updraftplus') . "</h2>" : "<h2>" . $info['description'] . "</h2>"; $updraftplus->log("Entity: " . $type); if (is_string($files)) { $files = array($files); } foreach ($files as $file) { $val = $updraftplus_restorer->restore_backup($file, $type, $info); if (is_wp_error($val)) { $updraftplus->log_e($val); foreach ($val->get_error_messages() as $msg) { echo '<strong>' . __('Error message', 'updraftplus') . ':</strong> ' . htmlspecialchars($msg) . '<br>'; } $codes = $val->get_error_codes(); if (is_array($codes)) { foreach ($codes as $code) { $data = $val->get_error_data($code); if (!empty($data)) { $pdata = is_string($data) ? $data : serialize($data); echo '<strong>' . __('Error data:', 'updraftplus') . '</strong> ' . htmlspecialchars($pdata) . '<br>'; } } } echo '</div>'; //close the updraft_restore_progress div even if we error restore_error_handler(); return $val; } elseif (false === $val) { echo '</div>'; //close the updraft_restore_progress div even if we error restore_error_handler(); return false; } } } foreach (array('template', 'stylesheet', 'template_root', 'stylesheet_root') as $opt) { add_filter('pre_option_' . $opt, array($this, 'option_filter_' . $opt)); } if (!function_exists('validate_current_theme')) { require_once ABSPATH . 'wp-includes/themes'; } if (!validate_current_theme()) { global $updraftplus; 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"); echo '</strong>'; } #foreach (array('template', 'stylesheet', 'template_root', 'stylesheet_root') as $opt) { # remove_filter('pre_option_'.$opt, array($this, 'option_filter_'.$opt)); #} echo '</div>'; //close the updraft_restore_progress div restore_error_handler(); return true; }
private function restore_backup($timestamp, $continuation_data = null) { @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT); global $wp_filesystem, $updraftplus; $backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history'); if (!isset($backup_history[$timestamp]) || !is_array($backup_history[$timestamp])) { echo '<p>' . __('This backup does not exist in the backup history - restoration aborted. Timestamp:', 'updraftplus') . " {$timestamp}</p><br/>"; return new WP_Error('does_not_exist', __('Backup does not exist in the backup history', 'updraftplus')); } // request_filesystem_credentials passes on fields just via hidden name/value pairs. // Build array of parameters to be passed via this $extra_fields = array(); if (isset($_POST['updraft_restore']) && is_array($_POST['updraft_restore'])) { foreach ($_POST['updraft_restore'] as $entity) { $_POST['updraft_restore_' . $entity] = 1; $extra_fields[] = 'updraft_restore_' . $entity; } } if (is_array($continuation_data)) { foreach ($continuation_data['second_loop_entities'] as $type => $files) { $_POST['updraft_restore_' . $type] = 1; if (!in_array('updraft_restore_' . $type, $extra_fields)) { $extra_fields[] = 'updraft_restore_' . $type; } } if (!empty($continuation_data['restore_options'])) { $restore_options = $continuation_data['restore_options']; } } // Now make sure that updraft_restorer_ option fields get passed along to request_filesystem_credentials foreach ($_POST as $key => $value) { if (0 === strpos($key, 'updraft_restorer_')) { $extra_fields[] = $key; } } $credentials = request_filesystem_credentials(UpdraftPlus_Options::admin_page() . "?page=updraftplus&action=updraft_restore&backup_timestamp={$timestamp}", '', false, false, $extra_fields); WP_Filesystem($credentials); if ($wp_filesystem->errors->get_error_code()) { echo '<p><em><a href="https://updraftplus.com/faqs/asked-ftp-details-upon-restorationmigration-updates/">' . __('Why am I seeing this?', 'updraftplus') . '</a></em></p>'; foreach ($wp_filesystem->errors->get_error_messages() as $message) { show_message($message); } exit; } // If we make it this far then WP_Filesystem has been instantiated and is functional # Set up logging $updraftplus->backup_time_nonce(); $updraftplus->jobdata_set('job_type', 'restore'); $updraftplus->jobdata_set('job_time_ms', $updraftplus->job_time_ms); $updraftplus->logfile_open($updraftplus->nonce); # Provide download link for the log file # TODO: Automatic purging of old log files # TODO: Provide option to auto-email the log file echo '<h1>' . __('UpdraftPlus Restoration: Progress', 'updraftplus') . '</h1><div id="updraft-restore-progress">'; $this->show_admin_warning('<a target="_blank" href="?action=downloadlog&page=updraftplus&updraftplus_backup_nonce=' . htmlspecialchars($updraftplus->nonce) . '">' . __('Follow this link to download the log file for this restoration (needed for any support requests).', 'updraftplus') . '</a>'); $updraft_dir = trailingslashit($updraftplus->backups_dir_location()); $foreign_known = apply_filters('updraftplus_accept_archivename', array()); $service = isset($backup_history[$timestamp]['service']) ? $backup_history[$timestamp]['service'] : false; if (!is_array($service)) { $service = array($service); } // Now, need to turn any updraft_restore_<entity> fields (that came from a potential WP_Filesystem form) back into parts of the _POST array (which we want to use) if (empty($_POST['updraft_restore']) || !is_array($_POST['updraft_restore'])) { $_POST['updraft_restore'] = array(); } $backup_set = $backup_history[$timestamp]; $entities_to_restore = array(); foreach ($_POST['updraft_restore'] as $entity) { if (empty($backup_set['meta_foreign'])) { $entities_to_restore[$entity] = $entity; } else { if ('db' == $entity && !empty($foreign_known[$backup_set['meta_foreign']]) && !empty($foreign_known[$backup_set['meta_foreign']]['separatedb'])) { $entities_to_restore[$entity] = 'db'; } else { $entities_to_restore[$entity] = 'wpcore'; } } } foreach ($_POST as $key => $value) { if (0 === strpos($key, 'updraft_restore_')) { $nkey = substr($key, 16); if (!isset($entities_to_restore[$nkey])) { $_POST['updraft_restore'][] = $nkey; if (empty($backup_set['meta_foreign'])) { $entities_to_restore[$nkey] = $nkey; } else { if ('db' == $entity && !empty($foreign_known[$backup_set['meta_foreign']]['separatedb'])) { $entities_to_restore[$nkey] = 'db'; } else { $entities_to_restore[$nkey] = 'wpcore'; } } } } } if (0 == count($_POST['updraft_restore'])) { echo '<p>' . __('ABORT: Could not find the information on which entities to restore.', 'updraftplus') . '</p>'; echo '<p>' . __('If making a request for support, please include this information:', 'updraftplus') . ' ' . count($_POST) . ' : ' . htmlspecialchars(serialize($_POST)) . '</p>'; return new WP_Error('missing_info', 'Backup information not found'); } $this->entities_to_restore = $entities_to_restore; set_error_handler(array($updraftplus, 'php_error'), E_ALL & ~E_STRICT); /* $_POST['updraft_restore'] is typically something like: array( 0=>'db', 1=>'plugins', 2=>'themes'), etc. i.e. array ( 'db', 'plugins', themes') */ if (empty($restore_options)) { // Gather the restore optons into one place - code after here should read the options, and not the HTTP layer $restore_options = array(); if (!empty($_POST['updraft_restorer_restore_options'])) { parse_str($_POST['updraft_restorer_restore_options'], $restore_options); } $restore_options['updraft_restorer_replacesiteurl'] = empty($_POST['updraft_restorer_replacesiteurl']) ? false : true; $restore_options['updraft_encryptionphrase'] = empty($_POST['updraft_encryptionphrase']) ? '' : (string) $_POST['updraft_encryptionphrase']; $restore_options['updraft_restorer_wpcore_includewpconfig'] = empty($_POST['updraft_restorer_wpcore_includewpconfig']) ? false : true; $updraftplus->jobdata_set('restore_options', $restore_options); } // Restore in the most helpful order uksort($backup_set, array($this, 'sort_restoration_entities')); // Now log $copy_restore_options = $restore_options; if (!empty($copy_restore_options['updraft_encryptionphrase'])) { $copy_restore_options['updraft_encryptionphrase'] = '***'; } $updraftplus->log("Restore job started. Entities to restore: " . implode(', ', array_flip($entities_to_restore)) . '. Restore options: ' . json_encode($copy_restore_options)); $backup_set['timestamp'] = $timestamp; $backupable_entities = $updraftplus->get_backupable_file_entities(true, true); // Allow add-ons to adjust the restore directory (but only in the case of restore - otherwise, they could just use the filter built into UpdraftPlus::get_backupable_file_entities) $backupable_entities = apply_filters('updraft_backupable_file_entities_on_restore', $backupable_entities, $restore_options, $backup_set); // We use a single object for each entity, because we want to store information about the backup set require_once UPDRAFTPLUS_DIR . '/restorer.php'; global $updraftplus_restorer; $updraftplus_restorer = new Updraft_Restorer(new Updraft_Restorer_Skin(), $backup_set, false, $restore_options); $second_loop = array(); echo "<h2>" . __('Final checks', 'updraftplus') . '</h2>'; if (empty($backup_set['meta_foreign'])) { $entities_to_download = $entities_to_restore; } else { if (!empty($foreign_known[$backup_set['meta_foreign']]['separatedb'])) { $entities_to_download = array(); if (in_array('db', $entities_to_restore)) { $entities_to_download['db'] = 1; } if (count($entities_to_restore) > 1 || !in_array('db', $entities_to_restore)) { $entities_to_download['wpcore'] = 1; } } else { $entities_to_download = array('wpcore' => 1); } } // 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']) && $backupable_entities[$type]['restorable'] == false) { continue; } if (!isset($entities_to_download[$type])) { continue; } if ('wpcore' == $type && is_multisite() && 0 === $updraftplus_restorer->ud_backup_is_multisite) { echo "<p>{$type}: <strong>"; echo __('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'); #TODO #$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.'); echo "</strong></p>"; continue; } if (is_string($files)) { $files = array($files); } foreach ($files as $ind => $file) { $fullpath = $updraft_dir . $file; echo sprintf(__("Looking for %s archive: file name: %s", 'updraftplus'), $type, htmlspecialchars($file)) . "<br>"; if (is_array($continuation_data) && isset($continuation_data['second_loop_entities'][$type]) && !in_array($file, $continuation_data['second_loop_entities'][$type])) { echo __('Skipping: this archive was already restored.', 'updraftplus') . "<br>"; // Set the marker so that the existing directory isn't moved out of the way $updraftplus_restorer->been_restored[$type] = true; continue; } add_action('http_request_args', array($updraftplus, 'modify_http_options')); foreach ($service as $serv) { if (!is_readable($fullpath)) { $sd = empty($updraftplus->backup_methods[$serv]) ? $serv : $updraftplus->backup_methods[$serv]; echo __("File is not locally present - needs retrieving from remote storage", 'updraftplus') . " ({$sd})"; $this->download_file($file, $serv); echo ": "; if (!is_readable($fullpath)) { echo __("Error", 'updraftplus'); } else { echo __("OK", 'updraftplus'); } echo '<br>'; } } remove_action('http_request_args', array($updraftplus, 'modify_http_options')); $index = $ind == 0 ? '' : $ind; // If a file size is stored in the backup data, then verify correctness of the local file if (isset($backup_history[$timestamp][$type . $index . '-size'])) { $fs = $backup_history[$timestamp][$type . $index . '-size']; echo __("Archive is expected to be size:", 'updraftplus') . " " . round($fs / 1024, 1) . " KB: "; $as = @filesize($fullpath); if ($as == $fs) { echo __('OK', 'updraftplus') . '<br>'; } else { echo "<strong>" . __('Error:', 'updraftplus') . "</strong> " . __('file is size:', 'updraftplus') . " " . round($as / 1024) . " ({$fs}, {$as})<br>"; } } else { echo __("The backup records do not contain information about the proper size of this file.", 'updraftplus') . "<br>"; } if (!is_readable($fullpath)) { echo __('Could not find one of the files for restoration', 'updraftplus') . " ({$file})<br>"; $updraftplus->log("{$file}: " . __('Could not find one of the files for restoration', 'updraftplus'), 'error'); echo '</div>'; restore_error_handler(); return false; } } if (empty($updraftplus_restorer->ud_foreign)) { $types = array($type); } else { if ('db' != $type || empty($foreign_known[$updraftplus_restorer->ud_foreign]['separatedb'])) { $types = array('wpcore'); } else { $types = array('db'); } } foreach ($types as $check_type) { $info = isset($backupable_entities[$check_type]) ? $backupable_entities[$check_type] : array(); $val = $updraftplus_restorer->pre_restore_backup($files, $check_type, $info, $continuation_data); if (is_wp_error($val)) { $updraftplus->log_wp_error($val); foreach ($val->get_error_messages() as $msg) { echo '<strong>' . __('Error:', 'updraftplus') . '</strong> ' . htmlspecialchars($msg) . '<br>'; } foreach ($val->get_error_codes() as $code) { if ('already_exists' == $code) { $this->print_delete_old_dirs_form(false); } } echo '</div>'; //close the updraft_restore_progress div even if we error restore_error_handler(); return $val; } elseif (false === $val) { echo '</div>'; //close the updraft_restore_progress div even if we error restore_error_handler(); return false; } } foreach ($entities_to_restore as $entity => $via) { if ($via == $type) { if ('wpcore' == $via && 'db' == $entity && count($files) > 1) { $second_loop[$entity] = apply_filters('updraftplus_select_wpcore_file_with_db', $files, $updraftplus_restorer->ud_foreign); } else { $second_loop[$entity] = $files; } } } } $updraftplus_restorer->delete = UpdraftPlus_Options::get_updraft_option('updraft_delete_local') ? true : false; if ('none' === $service || 'email' === $service || empty($service) || is_array($service) && 1 == count($service) && (in_array('none', $service) || in_array('', $service) || in_array('email', $service)) || !empty($updraftplus_restorer->ud_foreign)) { if ($updraftplus_restorer->delete) { $updraftplus->log_e('Will not delete any archives after unpacking them, because there was no cloud storage for this backup'); } $updraftplus_restorer->delete = false; } if (!empty($updraftplus_restorer->ud_foreign)) { $updraftplus->log("Foreign backup; created by: " . $updraftplus_restorer->ud_foreign); } // Second loop: now actually do the restoration uksort($second_loop, array($this, 'sort_restoration_entities')); // If continuing, then prune those already done if (is_array($continuation_data)) { foreach ($second_loop as $type => $files) { if (isset($continuation_data['second_loop_entities'][$type])) { $second_loop[$type] = $continuation_data['second_loop_entities'][$type]; } } } $updraftplus->jobdata_set('second_loop_entities', $second_loop); $updraftplus->jobdata_set('backup_timestamp', $timestamp); // use a site option, as otherwise on multisite when all the array of options is updated via UpdraftPlus_Options::update_site_option(), it will over-write any restored UD options from the backup update_site_option('updraft_restore_in_progress', $updraftplus->nonce); foreach ($second_loop as $type => $files) { # Types: uploads, themes, plugins, others, db $info = isset($backupable_entities[$type]) ? $backupable_entities[$type] : array(); echo 'db' == $type ? "<h2>" . __('Database', 'updraftplus') . "</h2>" : "<h2>" . $info['description'] . "</h2>"; $updraftplus->log("Entity: " . $type); if (is_string($files)) { $files = array($files); } foreach ($files as $fkey => $file) { $last_one = 1 == count($second_loop) && 1 == count($files); $val = $updraftplus_restorer->restore_backup($file, $type, $info, $last_one); if (is_wp_error($val)) { $codes = $val->get_error_codes(); if (is_array($codes) && in_array('not_found', $codes) && !empty($updraftplus_restorer->ud_foreign) && apply_filters('updraftplus_foreign_allow_missing_entity', false, $type, $updraftplus_restorer->ud_foreign)) { $updraftplus->log("Entity to move not found in this zip - but this is possible with this foreign backup type"); } else { $updraftplus->log_e($val); foreach ($val->get_error_messages() as $msg) { echo '<strong>' . __('Error message', 'updraftplus') . ':</strong> ' . htmlspecialchars($msg) . '<br>'; } $codes = $val->get_error_codes(); if (is_array($codes)) { foreach ($codes as $code) { $data = $val->get_error_data($code); if (!empty($data)) { $pdata = is_string($data) ? $data : serialize($data); echo '<strong>' . __('Error data:', 'updraftplus') . '</strong> ' . htmlspecialchars($pdata) . '<br>'; if (false !== strpos($pdata, 'PCLZIP_ERR_BAD_FORMAT (-10)')) { echo '<a href="https://updraftplus.com/faqs/error-message-pclzip_err_bad_format-10-invalid-archive-structure-mean/"><strong>' . __('Please consult this FAQ for help on what to do about it.', 'updraftplus') . '</strong></a><br>'; } } } } echo '</div>'; //close the updraft_restore_progress div even if we error restore_error_handler(); return $val; } } elseif (false === $val) { echo '</div>'; //close the updraft_restore_progress div even if we error restore_error_handler(); return false; } unset($files[$fkey]); $second_loop[$type] = $files; $updraftplus->jobdata_set('second_loop_entities', $second_loop); $updraftplus->jobdata_set('backup_timestamp', $timestamp); do_action('updraft_restored_archive', $file, $type, $val, $fkey, $timestamp); } unset($second_loop[$type]); update_site_option('updraft_restore_in_progress', $updraftplus->nonce); $updraftplus->jobdata_set('second_loop_entities', $second_loop); $updraftplus->jobdata_set('backup_timestamp', $timestamp); } // All done - remove 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 $updraftplus_restorer->clear_cache(); if (!function_exists('validate_current_theme')) { require_once ABSPATH . WPINC . '/themes'; } # 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) && $template != WP_DEFAULT_THEME && $template != strtolower($template)) { $theme_root = get_theme_root($template); $theme_root2 = get_theme_root(strtolower($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 (!validate_current_theme()) { 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"); echo '</strong>'; } echo '</div>'; //close the updraft_restore_progress div restore_error_handler(); return true; }
function restore_backup($timestamp) { @set_time_limit(900); global $wp_filesystem, $updraftplus; $backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history'); if (!is_array($backup_history[$timestamp])) { echo '<p>' . __('This backup does not exist in the backup history - restoration aborted. Timestamp:', 'updraftplus') . " {$timestamp}</p><br/>"; return new WP_Error('does_not_exist', 'Backup does not exist in the backup history'); } // request_filesystem_credentials passes on fields just via hidden name/value pairs. // Build array of parameters to be passed via this $extra_fields = array(); if (isset($_POST['updraft_restore']) && is_array($_POST['updraft_restore'])) { foreach ($_POST['updraft_restore'] as $entity) { $_POST['updraft_restore_' . $entity] = 1; $extra_fields[] = 'updraft_restore_' . $entity; } } // Now make sure that updraft_restorer_ option fields get passed along to request_filesystem_credentials foreach ($_POST as $key => $value) { if (strpos($key, 'updraft_restorer_') === 0) { $extra_fields[] = $key; } } $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_restore&backup_timestamp={$timestamp}", '', false, false, $extra_fields); WP_Filesystem($credentials); if ($wp_filesystem->errors->get_error_code()) { foreach ($wp_filesystem->errors->get_error_messages() as $message) { show_message($message); } exit; } //if we make it this far then WP_Filesystem has been instantiated and is functional (tested with ftpext, what about suPHP and other situations where direct may work?) echo '<h1>' . __('UpdraftPlus Restoration: Progress', 'updraftplus') . '</h1><div id="updraft-restore-progress">'; $updraft_dir = $updraftplus->backups_dir_location() . '/'; $service = isset($backup_history[$timestamp]['service']) ? $backup_history[$timestamp]['service'] : false; // Now, need to turn any updraft_restore_<entity> fields (that came from a potential WP_Filesystem form) back into parts of the _POST array (which we want to use) if (empty($_POST['updraft_restore']) || !is_array($_POST['updraft_restore'])) { $_POST['updraft_restore'] = array(); } $entities_to_restore = array_flip($_POST['updraft_restore']); foreach ($_POST as $key => $value) { if (strpos($key, 'updraft_restore_') === 0) { $nkey = substr($key, 16); if (!isset($entities_to_restore[$nkey])) { $_POST['updraft_restore'][] = $nkey; $entities_to_restore[$nkey] = 1; } } } if (count($_POST['updraft_restore']) == 0) { echo '<p>' . __('ABORT: Could not find the information on which entities to restore.', 'updraftplus') . '</p>'; echo '<p>' . __('If making a request for support, please include this information:', 'updraftplus') . ' ' . count($_POST) . ' : ' . htmlspecialchars(serialize($_POST)) . '</p>'; return new WP_Error('missing_info', 'Backup information not found'); } /* $_POST['updraft_restore'] is typically something like: array( 0=>'db', 1=>'plugins', 2=>'themes'), etc. i.e. array ( 'db', 'plugins', themes') */ $backupable_entities = $updraftplus->get_backupable_file_entities(true, true); $backup_set = $backup_history[$timestamp]; uksort($backup_set, array($this, 'sort_restoration_entities')); // We use a single object for each entity, because we want to store information about the backup set if (!class_exists('WP_Upgrader')) { require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } require_once UPDRAFTPLUS_DIR . '/restorer.php'; $restorer = new Updraft_Restorer(); $second_loop = array(); echo "<h2>" . __('Final checks', 'updraftplus') . '</h2>'; // 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']) && $backupable_entities[$type]['restorable'] == false) { continue; } if (!isset($entities_to_restore[$type])) { continue; } if ($type == 'wpcore' && is_multisite() && 0 === $restorer->ud_backup_is_multisite) { echo "<p>{$type}: <strong>" . __('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') . "</strong></p>"; continue; } if (is_string($files)) { $files = array($files); } foreach ($files as $ind => $file) { $fullpath = $updraft_dir . $file; echo sprintf(__("Looking for %s archive: file name: %s", 'updraftplus'), $type, htmlspecialchars($file)) . "<br>"; if (!is_readable($fullpath)) { echo __("File is not locally present - needs retrieving from remote storage", 'updraftplus') . "<br>"; $this->download_file($file, $service); } $index = $ind == 0 ? '' : $ind; // If a file size is stored in the backup data, then verify correctness of the local file if (isset($backup_history[$timestamp][$type . $index . '-size'])) { $fs = $backup_history[$timestamp][$type . $index . '-size']; echo __("Archive is expected to be size:", 'updraftplus') . " " . round($fs / 1024) . " Kb: "; $as = @filesize($fullpath); if ($as == $fs) { echo __('OK', 'updraftplus') . '<br>'; } else { echo "<strong>" . __('Error:', 'updraftplus') . "</strong> " . __('file is size:', 'updraftplus') . " " . round($as / 1024) . " ({$fs}, {$as})<br>"; } } else { echo __("The backup records do not contain information about the proper size of this file.", 'updraftplus') . "<br>"; } if (!is_readable($fullpath)) { echo __('Could not find one of the files for restoration', 'updraftplus') . " ({$file})<br>"; $updraftplus->log("{$file}: " . __('Could not find one of the files for restoration', 'updraftplus'), 'error'); echo '</div>'; return false; } } $info = isset($backupable_entities[$type]) ? $backupable_entities[$type] : array(); $val = $restorer->pre_restore_backup($files, $type, $info); if (is_wp_error($val)) { foreach ($val->get_error_messages() as $msg) { echo '<strong>' . __('Error message', 'updraftplus') . ':</strong> ' . htmlspecialchars($msg) . '<br>'; } echo '</div>'; //close the updraft_restore_progress div even if we error return $val; } elseif (false === $val) { echo '</div>'; //close the updraft_restore_progress div even if we error return false; } $second_loop[$type] = $files; } // Second loop: now actually do the restoration uksort($second_loop, array($this, 'sort_restoration_entities')); foreach ($second_loop as $type => $files) { # Types: uploads, themes, plugins, others, db $info = isset($backupable_entities[$type]) ? $backupable_entities[$type] : array(); echo 'db' == $type ? "<h2>" . __('Database', 'updraftplus') . "</h2>" : "<h2>" . $info['description'] . "</h2>"; $val = $restorer->restore_backup($files, $type, $service, $info); if (is_wp_error($val)) { foreach ($val->get_error_messages() as $msg) { echo '<strong>' . __('Error message', 'updraftplus') . ':</strong> ' . htmlspecialchars($msg) . '<br>'; } echo '</div>'; //close the updraft_restore_progress div even if we error return $val; } elseif (false === $val) { echo '</div>'; //close the updraft_restore_progress div even if we error return false; } } echo '</div>'; //close the updraft_restore_progress div return true; }