echo ' <p><a target="_blank" href="?action=downloadlog&page=updraftplus&updraftplus_backup_nonce='.esc_attr($updraftplus->nonce).'">'.__('Follow this link to download the log file for this restoration (needed for any support requests).', 'updraftplus').'</a></p>';
echo '</div>'; // end .updraft_restore_main--components
echo '<div class="updraft_restore_main--activity">';
echo ' <h2 class="updraft_restore_main--activity-title">'.__('Activity log', 'updraftplus').' <i id="activity-full-log" title="'.__('Full-screen', 'updraftplus').'" class="dashicons dashicons-fullscreen-alt" style="float: right; cursor: pointer; margin-left: 7px;"></i> <span id="updraftplus_ajax_restore_last_activity"></span></h2>';
echo ' <div id="updraftplus_ajax_restore_output"></div>';
echo '</div>'; // end .updraft_restore_main--activity
<div class="updraft-restore--footer">
<ul class="updraft-restore--stages">
<li><span>'.__('1. Component selection', 'updraftplus').'</span></li>
<li><span>'.__('2. Verifications', 'updraftplus').'</span></li>
<li class="active"><span>'.__('3. Restoration', 'updraftplus').'</span></li>
echo '</div>'; // end .updraft_restore_main
echo '</div>'; // end .updraft_restore_container
* Processes the jobdata to build an array of entities to restore.
* @param Array $backup_set - information on the backup to restore
* @return Array - the entities to restore built from the restore jobdata
private function get_entities_to_restore_from_jobdata($backup_set) {
$updraft_restore = $updraftplus->jobdata_get('updraft_restore');
if (empty($updraft_restore) || (!is_array($updraft_restore))) $updraft_restore = array();
$entities_to_restore = array();
$foreign_known = apply_filters('updraftplus_accept_archivename', array());
foreach ($updraft_restore as $entity) {
if (empty($backup_set['meta_foreign'])) {
$entities_to_restore[$entity] = $entity;
if ('db' == $entity && !empty($foreign_known[$backup_set['meta_foreign']]) && !empty($foreign_known[$backup_set['meta_foreign']]['separatedb'])) {
$entities_to_restore[$entity] = 'db';
$entities_to_restore[$entity] = 'wpcore';
return $entities_to_restore;
* Processes the jobdata to build an array of restoration options
* @return Array - the restore options built from the restore jobdata
private function get_restore_options_from_jobdata() {
$restore_options = $updraftplus->jobdata_get('updraft_restorer_restore_options');
$updraft_encryptionphrase = $updraftplus->jobdata_get('updraft_encryptionphrase');
$include_wpconfig = $updraftplus->jobdata_get('updraft_restorer_wpcore_includewpconfig');
$restore_options['updraft_encryptionphrase'] = empty($updraft_encryptionphrase) ? '' : $updraft_encryptionphrase;
$restore_options['updraft_restorer_wpcore_includewpconfig'] = !empty($include_wpconfig);
$restore_options['updraft_incremental_restore_point'] = empty($restore_options['updraft_incremental_restore_point']) ? -1 : (int) $restore_options['updraft_incremental_restore_point'];
* Carry out the restore process within the WP admin dashboard, using data from $_POST
* @param Integer $timestamp Identifying the backup to be restored
* @param Array|null $continuation_data For continuing a multi-stage restore; this is the saved jobdata for the job; in this method the keys used are second_loop_entities, restore_options; but it is also passed on to Updraft_Restorer::perform_restore()
* @return Boolean|WP_Error - a WP_Error indicates a terminal failure; false indicates not-yet complete (not necessarily terminal); true indicates complete.
private function restore_backup($timestamp, $continuation_data = null) {
global $updraftplus, $updraftplus_restorer;
$second_loop_entities = empty($continuation_data['second_loop_entities']) ? array() : $continuation_data['second_loop_entities'];
// If this is a resumption and we still need to restore the database we should rebuild the backup history to ensure the database is in there.
if (!empty($second_loop_entities['db'])) UpdraftPlus_Backup_History::rebuild();
$backup_set = UpdraftPlus_Backup_History::get_history($timestamp);
if (empty($backup_set)) {
echo '<p>'.__('This backup does not exist in the backup history - restoration aborted. Timestamp:', 'updraftplus').' '.htmlspecialchars($timestamp).'</p><br>';
return new WP_Error('does_not_exist', __('Backup does not exist in the backup history', 'updraftplus')." ($timestamp)");
$backup_set['timestamp'] = $timestamp;
'backup_timestamp' => $timestamp,
'job_id' => $updraftplus->nonce
if (!empty($continuation_data['updraftplus_ajax_restore'])) {
$url_parameters['updraftplus_ajax_restore'] = 'continue_ajax_restore';
$updraftplus->output_to_browser(''); // Start timer
// Force output buffering off so that we get log lines sent to the browser as they come not all at once at the end of the ajax restore
// zlib creates an output buffer, and waits for the entire page to be generated before it can send it to the client try to turn it off
@ini_set("zlib.output_compression", 0);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
// Turn off PHP output buffering for NGINX
header('X-Accel-Buffering: no');
header('Content-Encoding: none');
$updraftplus->log("Ensuring WP_Filesystem is setup for a restore");
// This will print HTML and die() if necessary
UpdraftPlus_Filesystem_Functions::ensure_wp_filesystem_set_up_for_restore($url_parameters);
$updraftplus->log("WP_Filesystem is setup and ready for a restore");
$entities_to_restore = $this->get_entities_to_restore_from_jobdata($backup_set);
if (empty($entities_to_restore)) {
$restore_jobdata = $updraftplus->jobdata_getarray($updraftplus->nonce);
echo '<p>'.__('ABORT: Could not find the information on which entities to restore.', 'updraftplus').'</p><p>'.__('If making a request for support, please include this information:', 'updraftplus').' '.count($restore_jobdata).' : '.htmlspecialchars(serialize($restore_jobdata)).'</p>';
return new WP_Error('missing_info', 'Backup information not found');
// This is used in painting the admin page after a successful restore
$this->entities_to_restore = $entities_to_restore;
// This will be removed by Updraft_Restorer::post_restore_clean_up()
set_error_handler(array($updraftplus, 'php_error'), E_ALL & ~E_STRICT);
// Set $restore_options, either from the continuation data, or from $_POST
if (!empty($continuation_data['restore_options'])) {
$restore_options = $continuation_data['restore_options'];
// Gather the restore options into one place - code after here should read the options
$restore_options = $this->get_restore_options_from_jobdata();
$updraftplus->jobdata_set('restore_options', $restore_options);
add_action('updraftplus_restoration_title', array($this, 'restoration_title'));
$updraftplus->log_restore_update(array('type' => 'state', 'stage' => 'started', 'data' => array()));
// We use a single object for each entity, because we want to store information about the backup set
$updraftplus_restorer = new Updraft_Restorer(new Updraft_Restorer_Skin, $backup_set, false, $restore_options, $continuation_data);
$restore_result = $updraftplus_restorer->perform_restore($entities_to_restore, $restore_options);
$updraftplus_restorer->post_restore_clean_up($restore_result);
$pval = $updraftplus->have_addons ? 1 : 0;
$sval = (true === $restore_result) ? 1 : 0;
$pages = get_pages(array('number' => 2));
'home' => get_home_url(),
foreach ($pages as $page_info) {
$page_urls[$page_info->post_name] = get_page_link($page_info->ID);
$updraftplus->log_restore_update(
__('Return to UpdraftPlus configuration', 'updraftplus') => UpdraftPlus_Options::admin_page_url() . '?page=updraftplus&updraft_restore_success=' . $sval . '&pval=' . $pval
* Called when the restore process wants to print a title
* @param String $title - title
public function restoration_title($title) {
echo '<h2>'.$title.'</h2>';
* Logs a line from the restore process, being called from UpdraftPlus::log().
* Hooks the WordPress filter updraftplus_logline
* In future, this can get more sophisticated. For now, things are funnelled through here, giving the future possibility.
* @param String $line - the line to be logged
* @param String $nonce - the job ID of the restore job
* @param String $level - the level of the log notice
* @param String|Boolean $uniq_id - a unique ID for the log if it should only be logged once; or false otherwise
* @param String $destination - the type of job ongoing. If it is not 'restore', then we will skip the logging.
* @return String|Boolean - the filtered value. If set to false, then UpdraftPlus::log() will stop processing the log line.
public function updraftplus_logline($line, $nonce, $level, $uniq_id, $destination) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
if ('progress' != $destination || (defined('WP_CLI') && WP_CLI) || false === $line || false === strpos($line, 'RINFO:')) return $line;
$updraftplus->output_to_browser($line);
// Indicate that we have completely handled all logging needed
* Ensure that what is returned is an array. Used as a WP options filter.
* @param Array $input - input
public function return_array($input) {
return is_array($input) ? $input : array();
* Called upon the WP action wp_ajax_updraft_savesettings. Will die().
public function updraft_ajax_savesettings() {
if (empty($_POST) || empty($_POST['subaction']) || 'savesettings' != $_POST['subaction'] || !isset($_POST['nonce']) || !is_user_logged_in() || !UpdraftPlus_Options::user_can_manage() || !wp_verify_nonce($_POST['nonce'], 'updraftplus-settings-nonce')) die('Security check');
if (empty($_POST['settings']) || !is_string($_POST['settings'])) die('Invalid data');
parse_str(stripslashes($_POST['settings']), $posted_settings);
// We now have $posted_settings as an array
if (!empty($_POST['updraftplus_version'])) $posted_settings['updraftplus_version'] = $_POST['updraftplus_version'];
echo json_encode($this->save_settings($posted_settings));
$log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during save settings. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
'fatal_error_message' => $log_message
// @codingStandardsIgnoreLine
$log_message = 'PHP Fatal error ('.get_class($e).') has occurred during save settings. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
'fatal_error_message' => $log_message
public function updraft_ajax_importsettings() {
if (empty($_POST) || empty($_POST['subaction']) || 'importsettings' != $_POST['subaction'] || !isset($_POST['nonce']) || !is_user_logged_in() || !UpdraftPlus_Options::user_can_manage() || !wp_verify_nonce($_POST['nonce'], 'updraftplus-settings-nonce')) die('Security check');
if (empty($_POST['settings']) || !is_string($_POST['settings'])) die('Invalid data');
$this->import_settings($_POST);
$log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during import settings. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
'fatal_error_message' => $log_message
// @codingStandardsIgnoreLine
$log_message = 'PHP Fatal error ('.get_class($e).') has occurred during import settings. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
'fatal_error_message' => $log_message
* This method handles the imported json settings it will convert them into a readable format for the existing save settings function, it will also update some of the options to match the new remote storage options format (Apr 2017)
* @param Array $settings - The settings from the imported json file
public function import_settings($settings) {
// A bug in UD releases around 1.12.40 - 1.13.3 meant that it was saved in URL-string format, instead of JSON
$perhaps_not_yet_parsed = json_decode(stripslashes($settings['settings']), true);
if (!is_array($perhaps_not_yet_parsed)) {
parse_str($perhaps_not_yet_parsed, $posted_settings);
$posted_settings = $perhaps_not_yet_parsed;
if (!empty($settings['updraftplus_version'])) $posted_settings['updraftplus_version'] = $settings['updraftplus_version'];
// Handle the settings name change of WebDAV and SFTP (Apr 2017) if someone tries to import an old settings to this version
if (isset($posted_settings['updraft_webdav_settings'])) {
$posted_settings['updraft_webdav'] = $posted_settings['updraft_webdav_settings'];
unset($posted_settings['updraft_webdav_settings']);
if (isset($posted_settings['updraft_sftp_settings'])) {
$posted_settings['updraft_sftp'] = $posted_settings['updraft_sftp_settings'];
unset($posted_settings['updraft_sftp_settings']);
// We also need to wrap some of the options in the new style settings array otherwise later on we will lose the settings if this information is missing
if (empty($posted_settings['updraft_webdav']['settings'])) $posted_settings['updraft_webdav'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_webdav']);
if (empty($posted_settings['updraft_googledrive']['settings'])) $posted_settings['updraft_googledrive'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_googledrive']);
if (empty($posted_settings['updraft_googlecloud']['settings'])) $posted_settings['updraft_googlecloud'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_googlecloud']);
if (empty($posted_settings['updraft_onedrive']['settings'])) $posted_settings['updraft_onedrive'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_onedrive']);
if (empty($posted_settings['updraft_azure']['settings'])) $posted_settings['updraft_azure'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_azure']);
if (empty($posted_settings['updraft_dropbox']['settings'])) $posted_settings['updraft_dropbox'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_dropbox']);
echo json_encode($this->save_settings($posted_settings));
* This function will get a list of remote storage methods with valid connection details and create a HTML list of checkboxes
* @return String - HTML checkbox list of remote storage methods with valid connection details
private function backup_now_remote_message() {
$services = (array) $updraftplus->just_one($updraftplus->get_canonical_service_list());
$all_services = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids($services);
$active_remote_storage_list = '';
foreach ($all_services as $method => $sinfo) {
if ('email' == $method) {
$possible_emails = $updraftplus->just_one_email(UpdraftPlus_Options::get_updraft_option('updraft_email'));
if (!empty($possible_emails)) {
$active_remote_storage_list .= '<input class="updraft_remote_service_entity" id="'.$method.'updraft_service" checked="checked" type="checkbox" name="updraft_include_remote_service_'. $method . '" value=""> <label for="'.$method.'updraft_service">'.$updraftplus->backup_methods[$method].'</label><br>';
} elseif (empty($sinfo['object']) || empty($sinfo['instance_settings']) || !is_callable(array($sinfo['object'], 'options_exist'))) {
foreach ($sinfo['instance_settings'] as $instance => $opt) {
if ($sinfo['object']->options_exist($opt)) {
$instance_count_label = (1 == $instance_count) ? '' : ' ('.$instance_count.')';
$label = empty($opt['instance_label']) ? $sinfo['object']->get_description() . $instance_count_label : $opt['instance_label'];
if (!isset($opt['instance_enabled'])) $opt['instance_enabled'] = 1;
$checked = empty($opt['instance_enabled']) ? '' : 'checked="checked"';
$active_remote_storage_list .= '<input class="updraft_remote_service_entity" id="'.$method.'updraft_service_'.$instance.'" ' . $checked . ' type="checkbox" name="updraft_include_remote_service_'. $method . '" value="'.$instance.'"> <label for="'.$method.'updraft_service_'.$instance.'">'.$label.'</label><br>';
$service = $updraftplus->just_one(UpdraftPlus_Options::get_updraft_option('updraft_service'));
if (is_string($service)) $service = array($service);
if (!is_array($service)) $service = array();
$no_remote_configured = (empty($service) || array('none') === $service || array('') === $service) ? true : false;
if ($no_remote_configured && empty($active_remote_storage_list)) {
return '<input type="checkbox" disabled="disabled" id="backupnow_includecloud"> <em>'.sprintf(__("Backup won't be sent to any remote storage - none has been saved in the %s", 'updraftplus'), '<a href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&tab=settings" id="updraft_backupnow_gotosettings">'.__('settings', 'updraftplus')).'</a>. '.__('Not got any remote storage?', 'updraftplus').' <a href="'.apply_filters('updraftplus_com_link', "https://updraftplus.com/landing/vault/").'" target="_blank">'.__("Check out UpdraftPlus Vault.", 'updraftplus').'</a></em>';
if (empty($active_remote_storage_list)) {
$active_remote_storage_list = '<p>'.__('No remote storage locations with valid options found.', 'updraftplus').'</p>';
return '<input type="checkbox" id="backupnow_includecloud" checked="checked"> <label for="backupnow_includecloud">'.__("Send this backup to remote storage", 'updraftplus').'</label> (<a href="'.$updraftplus->get_current_clean_url().'" id="backupnow_includecloud_showmoreoptions">...</a>)<br><div id="backupnow_includecloud_moreoptions" class="updraft-hidden" style="display:none;"><em>'. __('The following remote storage options are configured.', 'updraftplus').'</em><br>'.$active_remote_storage_list.'</div>';
* This method works through the passed in settings array and saves the settings to the database clearing old data and setting up a return array with content to update the page via ajax
* @param array $settings An array of settings taking from the admin page ready to be saved to the database
* @return array An array response containing the status of the update along with content to be used to update the admin page.
public function save_settings($settings) {
// Make sure that settings filters are registered
UpdraftPlus_Options::admin_init();
$more_files_path_updated = false;
if (isset($settings['updraftplus_version']) && $updraftplus->version == $settings['updraftplus_version']) {
$return_array = array('saved' => true);
$add_to_post_keys = array('updraft_interval', 'updraft_interval_database', 'updraft_interval_increments', 'updraft_starttime_files', 'updraft_starttime_db', 'updraft_startday_files', 'updraft_startday_db');
// If database and files are on same schedule, override the db day/time settings
if (isset($settings['updraft_interval_database']) && isset($settings['updraft_interval_database']) && $settings['updraft_interval_database'] == $settings['updraft_interval'] && isset($settings['updraft_starttime_files'])) {
$settings['updraft_starttime_db'] = $settings['updraft_starttime_files'];
$settings['updraft_startday_db'] = $settings['updraft_startday_files'];
foreach ($add_to_post_keys as $key) {
// For add-ons that look at $_POST to find saved settings, add the relevant keys to $_POST so that they find them there
if (isset($settings[$key])) {
$_POST[$key] = $settings[$key];
// Check if updraft_include_more_path is set, if it is then we need to update the page, if it's not set but there's content already in the database that is cleared down below so again we should update the page.
$more_files_path_updated = false;
// i.e. If an option has been set, or if it was currently active in the settings
if (isset($settings['updraft_include_more_path']) || UpdraftPlus_Options::get_updraft_option('updraft_include_more_path')) {
$more_files_path_updated = true;
// Wipe the extra retention rules, as they are not saved correctly if the last one is deleted
UpdraftPlus_Options::update_updraft_option('updraft_retain_extrarules', array());
UpdraftPlus_Options::update_updraft_option('updraft_email', array());
UpdraftPlus_Options::update_updraft_option('updraft_report_warningsonly', array());
UpdraftPlus_Options::update_updraft_option('updraft_report_wholebackup', array());
UpdraftPlus_Options::update_updraft_option('updraft_extradbs', array());
UpdraftPlus_Options::update_updraft_option('updraft_include_more_path', array());
$relevant_keys = $updraftplus->get_settings_keys();
if (isset($settings['updraft_auto_updates']) && in_array('updraft_auto_updates', $relevant_keys)) {
$updraftplus->set_automatic_updates($settings['updraft_auto_updates']);
unset($settings['updraft_auto_updates']); // unset the key and its value to prevent being processed the second time
if (method_exists('UpdraftPlus_Options', 'mass_options_update')) {
$original_settings = $settings;
$settings = UpdraftPlus_Options::mass_options_update($settings);
foreach ($settings as $key => $value) {
if (in_array($key, $relevant_keys)) {
if ('updraft_service' == $key && is_array($value)) {
foreach ($value as $subkey => $subvalue) {
if ('0' == $subvalue) unset($value[$subkey]);
// This flag indicates that either the stored database option was changed, or that the supplied option was changed before being stored. It isn't comprehensive - it's only used to update some UI elements with invalid input.
$updated = empty($mass_updated) ? (is_string($value) && UpdraftPlus_Options::get_updraft_option($key) != $value) : (is_string($value) && (!isset($original_settings[$key]) || $original_settings[$key] != $value));
if (empty($mass_updated)) UpdraftPlus_Options::update_updraft_option($key, $value);
// Add information on what has changed to array to loop through to update links etc.
// Restricting to strings for now, to prevent any unintended leakage (since this is just used for UI updating)
$value = UpdraftPlus_Options::get_updraft_option($key);
if (is_string($value)) $return_array['changed'][$key] = $value;
// @codingStandardsIgnoreLine
// This section is ignored by CI otherwise it will complain the ELSE is empty.
// When last active, it was catching: option_page, action, _wpnonce, _wp_http_referer, updraft_s3_endpoint, updraft_dreamobjects_endpoint. The latter two are empty; probably don't need to be in the page at all.
// error_log("Non-UD key when saving from POSTed data: ".$key);
$return_array = array('saved' => false, 'error_message' => sprintf(__('UpdraftPlus seems to have been updated to version (%s), which is different to the version running when this settings page was loaded. Please reload the settings page before trying to save settings.', 'updraftplus'), $updraftplus->version));
// Checking for various possible messages
$updraft_dir = $updraftplus->backups_dir_location(false);
$really_is_writable = UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir);
$dir_info = $this->really_writable_message($really_is_writable, $updraft_dir);
$button_title = esc_attr(__('This button is disabled because your backup directory is not writable (see the settings).', 'updraftplus'));
$return_array['backup_now_message'] = $this->backup_now_remote_message();
$return_array['backup_dir'] = array('writable' => $really_is_writable, 'message' => $dir_info, 'button_title' => $button_title);
// Check if $more_files_path_updated is true, is so then there's a change and we should update the backup modal
if ($more_files_path_updated) {
$return_array['updraft_include_more_path'] = $this->files_selector_widgetry('backupnow_files_', false, 'sometimes');
// Because of the single AJAX call, we need to remove the existing UD messages from the 'all_admin_notices' action
remove_all_actions('all_admin_notices');
// Moving from 2 to 1 ajax call
$service = UpdraftPlus_Options::get_updraft_option('updraft_service');