)
* The 'base' is not for us: we just pass it straight back
*
* @param array $downloaders Array of Items to download
* @return array
*/
public function get_download_statuses($downloaders) {
global $updraftplus;
$download_status = array();
foreach ($downloaders as $downloader) {
// prefix, timestamp, entity, index
if (preg_match('/^([^,]+),(\d+),([-a-z]+|db[0-9]+),(\d+)$/', $downloader, $matches)) {
$findex = (empty($matches[4])) ? '0' : $matches[4];
$updraftplus->nonce = dechex($matches[2]).$findex.substr(md5($matches[3]), 0, 3);
$updraftplus->jobdata_reset();
$status = $this->download_status($matches[2], $matches[3], $matches[4]);
if (is_array($status)) {
$status['base'] = $matches[1];
$status['timestamp'] = $matches[2];
$status['what'] = $matches[3];
$status['findex'] = $findex;
$download_status[] = $status;
}
}
}
return $download_status;
}
/**
* Get, as HTML output, a list of active jobs
*
* @param Array $request - details on the request being made (e.g. extra info to include)
*
* @return String
*/
public function get_activejobs_list($request) {
global $updraftplus;
$download_status = empty($request['downloaders']) ? array() : $this->get_download_statuses(explode(':', $request['downloaders']));
if (!empty($request['oneshot'])) {
$job_id = get_site_option('updraft_oneshotnonce', false);
// print_active_job() for one-shot jobs that aren't in cron
$active_jobs = (false === $job_id) ? '' : $this->print_active_job($job_id, true);
} elseif (!empty($request['thisjobonly'])) {
// print_active_jobs() is for resumable jobs where we want the cron info to be included in the output
$active_jobs = $this->print_active_jobs($request['thisjobonly']);
} else {
$active_jobs = $this->print_active_jobs();
}
$logupdate_array = array();
if (!empty($request['log_fetch'])) {
if (isset($request['log_nonce'])) {
$log_nonce = $request['log_nonce'];
$log_pointer = isset($request['log_pointer']) ? absint($request['log_pointer']) : 0;
$logupdate_array = $this->fetch_log($log_nonce, $log_pointer);
}
}
$res = array(
// We allow the front-end to decide what to do if there's nothing logged - we used to (up to 1.11.29) send a pre-defined message
'l' => htmlspecialchars(UpdraftPlus_Options::get_updraft_lastmessage()),
'j' => $active_jobs,
'ds' => $download_status,
'u' => $logupdate_array,
'automatic_updates' => $updraftplus->is_automatic_updating_enabled()
);
$res['hosting_restriction'] = $updraftplus->is_hosting_backup_limit_reached();
return $res;
}
/**
* Start a new backup
*
* @param Array $request
* @param Boolean|Callable $close_connection_callable
*/
public function request_backupnow($request, $close_connection_callable = false) {
global $updraftplus;
$abort_before_booting = false;
$backupnow_nocloud = !empty($request['backupnow_nocloud']);
$request['incremental'] = !empty($request['incremental']);
$entities = !empty($request['onlythisfileentity']) ? explode(',', $request['onlythisfileentity']) : array();
$remote_storage_instances = array();
// if only_these_cloud_services is not an array then all connected remote storage locations are being backed up to and we don't need to do this
if (!empty($request['only_these_cloud_services']) && is_array($request['only_these_cloud_services'])) {
$remote_storage_locations = $request['only_these_cloud_services'];
foreach ($remote_storage_locations as $key => $value) {
/*
This name key inside the value array is the remote storage method name prefixed by 31 characters (updraft_include_remote_service_) so we need to remove them to get the actual name, then the value key inside the value array has the instance id.
*/
$remote_storage_instances[substr($value['name'], 31)][$key] = $value['value'];
}
}
$incremental = $request['incremental'] ? apply_filters('updraftplus_prepare_incremental_run', false, $entities) : false;
// The call to backup_time_nonce() allows us to know the nonce in advance, and return it
$nonce = $updraftplus->backup_time_nonce();
$msg = array(
'nonce' => $nonce,
'm' => apply_filters('updraftplus_backupnow_start_message', ''.__('Start backup', 'updraftplus').': '.htmlspecialchars(__('OK.', 'updraftplus').' '.__('You should soon see activity in the "Last log message" field below.', 'updraftplus')), $nonce)
);
if (!empty($request['backup_nonce']) && 'current' != $request['backup_nonce']) $msg['nonce'] = $request['backup_nonce'];
if (!empty($request['incremental']) && !$incremental) {
$msg = array(
'error' => __('No suitable backup set (that already contains a full backup of all the requested file component types) was found, to add increments to.', 'updraftplus').' '.__('Aborting this backup.', 'updraftplus')
);
$abort_before_booting = true;
}
if ($close_connection_callable && is_callable($close_connection_callable)) {
call_user_func($close_connection_callable, $msg);
} else {
$updraftplus->close_browser_connection(json_encode($msg));
}
if ($abort_before_booting) die;
$options = array('nocloud' => $backupnow_nocloud, 'use_nonce' => $nonce);
if (!empty($request['onlythisfileentity']) && is_string($request['onlythisfileentity'])) {
// Something to see in the 'last log' field when it first appears, before the backup actually starts
$updraftplus->log(__('Start backup', 'updraftplus'));
$options['restrict_files_to_override'] = explode(',', $request['onlythisfileentity']);
}
if ($request['incremental'] && !$incremental) {
$updraftplus->log('An incremental backup was requested but no suitable backup found to add increments to; will proceed with a new backup');
$request['incremental'] = false;
}
if (!empty($request['extradata'])) $options['extradata'] = $request['extradata'];
if (!empty($remote_storage_instances)) $options['remote_storage_instances'] = $remote_storage_instances;
$options['always_keep'] = !empty($request['always_keep']);
$event = empty($request['backupnow_nofiles']) ? (empty($request['backupnow_nodb']) ? 'updraft_backupnow_backup_all' : 'updraft_backupnow_backup') : 'updraft_backupnow_backup_database';
do_action($event, apply_filters('updraft_backupnow_options', $options, $request));
}
/**
* Get the contents of a log file
*
* @param String $backup_nonce - the backup id; or empty, for the most recently modified
* @param Integer $log_pointer - the byte count to fetch from
* @param String $output_format - the format to return in; allowed as 'html' (which will escape HTML entities in what is returned) and 'raw'
*
* @return String
*/
public function fetch_log($backup_nonce = '', $log_pointer = 0, $output_format = 'html') {
global $updraftplus;
if (empty($backup_nonce)) {
list($mod_time, $log_file, $nonce) = $updraftplus->last_modified_log();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused parameter is present because the method returns an array.
} else {
$nonce = $backup_nonce;
}
if (!preg_match('/^[0-9a-f]+$/', $nonce)) die('Security check');
$log_content = '';
$new_pointer = $log_pointer;
if (!empty($nonce)) {
$updraft_dir = $updraftplus->backups_dir_location();
$potential_log_file = $updraft_dir."/log.".$nonce.".txt";
if (is_readable($potential_log_file)) {
$templog_array = array();
$log_file = fopen($potential_log_file, "r");
if ($log_pointer > 0) fseek($log_file, $log_pointer);
while (($buffer = fgets($log_file, 4096)) !== false) {
$templog_array[] = $buffer;
}
if (!feof($log_file)) {
$templog_array[] = __('Error: unexpected file read fail', 'updraftplus');
}
$new_pointer = ftell($log_file);
$log_content = implode("", $templog_array);
} else {
$log_content .= __('The log file could not be read.', 'updraftplus');
}
} else {
$log_content .= __('The log file could not be read.', 'updraftplus');
}
if ('html' == $output_format) $log_content = htmlspecialchars($log_content);
$ret_array = array(
'log' => $log_content,
'nonce' => $nonce,
'pointer' => $new_pointer
);
return $ret_array;
}
/**
* Get a count for the number of overdue cron jobs
*
* @return Integer - how many cron jobs are overdue
*/
public function howmany_overdue_crons() {
$how_many_overdue = 0;
if (function_exists('_get_cron_array') || (is_file(ABSPATH.WPINC.'/cron.php') && include_once(ABSPATH.WPINC.'/cron.php') && function_exists('_get_cron_array'))) {
$crons = _get_cron_array();
if (is_array($crons)) {
$timenow = time();
foreach ($crons as $jt => $job) {
if ($jt < $timenow) $how_many_overdue++;
}
}
}
return $how_many_overdue;
}
public function get_php_errors($errno, $errstr, $errfile, $errline) {
global $updraftplus;
if (0 == error_reporting()) return true;
$logline = $updraftplus->php_error_to_logline($errno, $errstr, $errfile, $errline);
if (false !== $logline) $this->logged[] = $logline;
// Don't pass it up the chain (since it's going to be output to the user always)
return true;
}
private function download_status($timestamp, $type, $findex) {
global $updraftplus;
$response = array('m' => $updraftplus->jobdata_get('dlmessage_'.$timestamp.'_'.$type.'_'.$findex).'
');
if ($file = $updraftplus->jobdata_get('dlfile_'.$timestamp.'_'.$type.'_'.$findex)) {
if ('failed' == $file) {
$response['e'] = __('Download failed', 'updraftplus').'
';
$response['failed'] = true;
$errs = $updraftplus->jobdata_get('dlerrors_'.$timestamp.'_'.$type.'_'.$findex);
if (is_array($errs) && !empty($errs)) {
$response['e'] .= '';
foreach ($errs as $err) {
if (is_array($err)) {
$response['e'] .= '- '.htmlspecialchars($err['message']).'
';
} else {
$response['e'] .= '- '.htmlspecialchars($err).'
';
}
}
$response['e'] .= '
';
}
} elseif (preg_match('/^downloaded:(\d+):(.*)$/', $file, $matches) && file_exists($matches[2])) {
$response['p'] = 100;
$response['f'] = $matches[2];
$response['s'] = (int) $matches[1];
$response['t'] = (int) $matches[1];
$response['m'] = __('File ready.', 'updraftplus');
if ('db' != substr($type, 0, 2)) $response['can_show_contents'] = true;
} elseif (preg_match('/^downloading:(\d+):(.*)$/', $file, $matches) && file_exists($matches[2])) {
// Convert to bytes
$response['f'] = $matches[2];
$total_size = (int) max($matches[1], 1);
$cur_size = filesize($matches[2]);
$response['s'] = $cur_size;
$file_age = time() - filemtime($matches[2]);
if ($file_age > 20) $response['a'] = time() - filemtime($matches[2]);
$response['t'] = $total_size;
$response['m'] .= __("Download in progress", 'updraftplus').' ('.round($cur_size/1024).' / '.round(($total_size/1024)).' KB)';
$response['p'] = round(100*$cur_size/$total_size);
} else {
$response['m'] .= __('No local copy present.', 'updraftplus');
$response['p'] = 0;
$response['s'] = 0;
$response['t'] = 1;
}
}
return $response;
}
/**
* Used with the WP filter upload_dir to adjust where uploads go to when uploading a backup
*
* @param Array $uploads - pre-filter array
*
* @return Array - filtered array
*/
public function upload_dir($uploads) {
global $updraftplus;
$updraft_dir = $updraftplus->backups_dir_location();
if (is_writable($updraft_dir)) $uploads['path'] = $updraft_dir;
return $uploads;
}
/**
* We do actually want to over-write
*
* @param String $dir Directory
* @param String $name Name
* @param String $ext File extension
*
* @return String
*/
public function unique_filename_callback($dir, $name, $ext) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Filter use
return $name.$ext;
}
public function sanitize_file_name($filename) {
// WordPress 3.4.2 on multisite (at least) adds in an unwanted underscore
return preg_replace('/-db(.*)\.gz_\.crypt$/', '-db$1.gz.crypt', $filename);
}
/**
* Runs upon the WordPress action plupload_action
*/
public function plupload_action() {
global $updraftplus;
if (function_exists('set_time_limit')) @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
if (!UpdraftPlus_Options::user_can_manage()) return;
check_ajax_referer('updraft-uploader');
$updraft_dir = $updraftplus->backups_dir_location();
if (!@UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
echo json_encode(array('e' => sprintf(__("Backup directory (%s) is not writable, or does not exist.", 'updraftplus'), $updraft_dir).' '.__('You will find more information about this in the Settings section.', 'updraftplus')));
exit;
}
add_filter('upload_dir', array($this, 'upload_dir'));
add_filter('sanitize_file_name', array($this, 'sanitize_file_name'));
// handle file upload
$farray = array('test_form' => true, 'action' => 'plupload_action');
$farray['test_type'] = false;
$farray['ext'] = 'x-gzip';
$farray['type'] = 'application/octet-stream';
if (!isset($_POST['chunks'])) {
$farray['unique_filename_callback'] = array($this, 'unique_filename_callback');
}
$status = wp_handle_upload(
$_FILES['async-upload'],
$farray
);
remove_filter('upload_dir', array($this, 'upload_dir'));
remove_filter('sanitize_file_name', array($this, 'sanitize_file_name'));
if (isset($status['error'])) {
echo json_encode(array('e' => $status['error']));
exit;
}
// If this was the chunk, then we should instead be concatenating onto the final file
if (isset($_POST['chunks']) && isset($_POST['chunk']) && preg_match('/^[0-9]+$/', $_POST['chunk'])) {
$final_file = basename($_POST['name']);
if (!rename($status['file'], $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp')) {
@unlink($status['file']);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
echo json_encode(array('e' => sprintf(__('Error: %s', 'updraftplus'), __('This file could not be uploaded', 'updraftplus'))));
exit;
}
$status['file'] = $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp';
}
$response = array();
if (!isset($_POST['chunks']) || (isset($_POST['chunk']) && preg_match('/^[0-9]+$/', $_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1) && isset($final_file)) {
if (!preg_match('/^log\.[a-f0-9]{12}\.txt/i', $final_file) && !preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-([\-a-z]+)([0-9]+)?(\.(zip|gz|gz\.crypt))?$/i', $final_file, $matches)) {
$accept = apply_filters('updraftplus_accept_archivename', array());
if (is_array($accept)) {
foreach ($accept as $acc) {
if (preg_match('/'.$acc['pattern'].'/i', $final_file)) {
$response['dm'] = sprintf(__('This backup was created by %s, and can be imported.', 'updraftplus'), $acc['desc']);
}
}
}
if (empty($response['dm'])) {
if (isset($status['file'])) @unlink($status['file']);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
echo json_encode(array('e' => sprintf(__('Error: %s', 'updraftplus'), __('Bad filename format - this does not look like a file created by UpdraftPlus', 'updraftplus'))));
exit;
}
} else {
$backupable_entities = $updraftplus->get_backupable_file_entities(true);
$type = isset($matches[3]) ? $matches[3] : '';
if (!preg_match('/^log\.[a-f0-9]{12}\.txt/', $final_file) && 'db' != $type && !isset($backupable_entities[$type])) {
if (isset($status['file'])) @unlink($status['file']);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
echo json_encode(array('e' => sprintf(__('Error: %s', 'updraftplus'), sprintf(__('This looks like a file created by UpdraftPlus, but this install does not know about this type of object: %s.', 'updraftplus'), htmlspecialchars($type)).' '.__('Perhaps you need to install an add-on?', 'updraftplus'))));
exit;
}
}
// Final chunk? If so, then stich it all back together
if (isset($_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1 && !empty($final_file)) {
if ($wh = fopen($updraft_dir.'/'.$final_file, 'wb')) {
for ($i = 0; $i < $_POST['chunks']; $i++) {
$rf = $updraft_dir.'/'.$final_file.'.'.$i.'.zip.tmp';
if ($rh = fopen($rf, 'rb+')) {
// April 1st 2020 - Due to a bug during uploads to Dropbox some backups had string "null" appended to the end which caused warnings, this removes the string "null" from these backups
fseek($rh, -4, SEEK_END);
$data = fgets($rh, 5);
if ("null" === $data) {
ftruncate($rh, filesize($rf) - 4);
}
fseek($rh, 0, SEEK_SET);
while ($line = fread($rh, 524288)) {
fwrite($wh, $line);
}
fclose($rh);
@unlink($rf);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
}
}
fclose($wh);
$status['file'] = $updraft_dir.'/'.$final_file;
if ('.tar' == substr($final_file, -4, 4)) {
if (file_exists($status['file'].'.gz')) unlink($status['file'].'.gz');
if (file_exists($status['file'].'.bz2')) unlink($status['file'].'.bz2');
} elseif ('.tar.gz' == substr($final_file, -7, 7)) {
if (file_exists(substr($status['file'], 0, strlen($status['file'])-3))) unlink(substr($status['file'], 0, strlen($status['file'])-3));
if (file_exists(substr($status['file'], 0, strlen($status['file'])-3).'.bz2')) unlink(substr($status['file'], 0, strlen($status['file'])-3).'.bz2');
} elseif ('.tar.bz2' == substr($final_file, -8, 8)) {
if (file_exists(substr($status['file'], 0, strlen($status['file'])-4))) unlink(substr($status['file'], 0, strlen($status['file'])-4));
if (file_exists(substr($status['file'], 0, strlen($status['file'])-4).'.gz')) unlink(substr($status['file'], 0, strlen($status['file'])-3).'.gz');
}
}
}
}
// send the uploaded file url in response
$response['m'] = $status['url'];
echo json_encode($response);
exit;
}
/**
* Database decrypter - runs upon the WP action plupload_action2
*/
public function plupload_action2() {
if (function_exists('set_time_limit')) @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
global $updraftplus;
if (!UpdraftPlus_Options::user_can_manage()) return;
check_ajax_referer('updraft-uploader');
$updraft_dir = $updraftplus->backups_dir_location();
if (!is_writable($updraft_dir)) exit;
add_filter('upload_dir', array($this, 'upload_dir'));
add_filter('sanitize_file_name', array($this, 'sanitize_file_name'));
// handle file upload
$farray = array('test_form' => true, 'action' => 'plupload_action2');
$farray['test_type'] = false;
$farray['ext'] = 'crypt';
$farray['type'] = 'application/octet-stream';
if (isset($_POST['chunks'])) {
// $farray['ext'] = 'zip';
// $farray['type'] = 'application/zip';
} else {
$farray['unique_filename_callback'] = array($this, 'unique_filename_callback');
}
$status = wp_handle_upload(
$_FILES['async-upload'],
$farray
);
remove_filter('upload_dir', array($this, 'upload_dir'));
remove_filter('sanitize_file_name', array($this, 'sanitize_file_name'));
if (isset($status['error'])) die('ERROR: '.$status['error']);
// If this was the chunk, then we should instead be concatenating onto the final file
if (isset($_POST['chunks']) && isset($_POST['chunk']) && preg_match('/^[0-9]+$/', $_POST['chunk'])) {
$final_file = basename($_POST['name']);
rename($status['file'], $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp');
$status['file'] = $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp';
}
if (!isset($_POST['chunks']) || (isset($_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1)) {
if (!preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-db([0-9]+)?\.(gz\.crypt)$/i', $final_file)) {
@unlink($status['file']);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
echo 'ERROR:'.__('Bad filename format - this does not look like an encrypted database file created by UpdraftPlus', 'updraftplus');
exit;
}
// Final chunk? If so, then stich it all back together
if (isset($_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1 && isset($final_file)) {
if ($wh = fopen($updraft_dir.'/'.$final_file, 'wb')) {
for ($i=0; $i<$_POST['chunks']; $i++) {
$rf = $updraft_dir.'/'.$final_file.'.'.$i.'.zip.tmp';
if ($rh = fopen($rf, 'rb')) {
while ($line = fread($rh, 524288)) {
fwrite($wh, $line);
}
fclose($rh);
@unlink($rf);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
}
}
fclose($wh);
}
}
}
// send the uploaded file url in response
if (isset($final_file)) echo 'OK:'.$final_file;
exit;
}
/**
* Show footer review message and link.
*
* @return string
*/
public function display_footer_review_message() {
$message = sprintf(
__('Enjoyed %s? Please leave us a %s rating.', 'updraftplus').' '.__('We really appreciate your support!', 'updraftplus'),
'UpdraftPlus',
'★★★★★'
);
return $message;
}
/**
* Include the settings header template
*/
public function settings_header() {
$this->include_template('wp-admin/settings/header.php');
}
/**
* Include the settings footer template
*/
public function settings_footer() {
$this->include_template('wp-admin/settings/footer.php');
}
/**
* Output the settings page content. Will also run a restore if $_REQUEST so indicates.
*/
public function settings_output() {
if (false == ($render = apply_filters('updraftplus_settings_page_render', true))) {
do_action('updraftplus_settings_page_render_abort', $render);
return;
}
do_action('updraftplus_settings_page_init');
global $updraftplus;
/**
* We use request here because the initial restore is triggered by a POSTed form. we then may need to obtain credential for the WP_Filesystem. to do this WP outputs a form, but we don't pass our parameters via that. So the values are passed back in as GET parameters.
*/
if (isset($_REQUEST['action']) && (('updraft_restore' == $_REQUEST['action'] && isset($_REQUEST['backup_timestamp'])) || ('updraft_restore_continue' == $_REQUEST['action'] && !empty($_REQUEST['job_id'])))) {
$this->prepare_restore();
return;
}
if (isset($_REQUEST['action']) && 'updraft_delete_old_dirs' == $_REQUEST['action']) {
$nonce = empty($_REQUEST['updraft_delete_old_dirs_nonce']) ? '' : $_REQUEST['updraft_delete_old_dirs_nonce'];
if (!wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce')) die('Security check');
$this->delete_old_dirs_go();
return;
}
if (!empty($_REQUEST['action']) && 'updraftplus_broadcastaction' == $_REQUEST['action'] && !empty($_REQUEST['subaction'])) {
$nonce = (empty($_REQUEST['nonce'])) ? "" : $_REQUEST['nonce'];
if (!wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce')) die('Security check');
do_action($_REQUEST['subaction']);
return;
}
if (isset($_GET['error'])) {
// This is used by Microsoft OneDrive authorisation failures (May 15). I am not sure what may have been using the 'error' GET parameter otherwise - but it is harmless.
if (!empty($_GET['error_description'])) {
$this->show_admin_warning(htmlspecialchars($_GET['error_description']).' ('.htmlspecialchars($_GET['error']).')', 'error');
} else {
$this->show_admin_warning(htmlspecialchars($_GET['error']), 'error');
}
}
if (isset($_GET['message'])) $this->show_admin_warning(htmlspecialchars($_GET['message']));
if (isset($_GET['action']) && 'updraft_create_backup_dir' == $_GET['action'] && isset($_GET['nonce']) && wp_verify_nonce($_GET['nonce'], 'create_backup_dir')) {
$created = $this->create_backup_dir();
if (is_wp_error($created)) {
echo ''.__('Backup directory could not be created', 'updraftplus').'...
';
echo '
';
foreach ($created->get_error_messages() as $msg) {
echo '- '.htmlspecialchars($msg).'
';
}
echo '
';
} elseif (false !== $created) {
echo ''.__('Backup directory successfully created.', 'updraftplus').'
';
}
echo ''.__('Actions', 'updraftplus').': '.__('Return to UpdraftPlus configuration', 'updraftplus').'';
return;
}
if (substr($updraftplus->version, 0, 1) === '2') {
/**
* Add filter for display footer review message and link.
*/
add_filter('admin_footer_text', array($this, 'display_footer_review_message'));
}
echo '';
// This opens a div
$this->settings_header();
?>
show_admin_warning("" . __("OptimizePress 2.0 encodes its contents, so search/replace does not work.", "updraftplus") . ' ' . __("To fix this problem go here.", "updraftplus") . "", "notice notice-warning");
}
$success_advert = (isset($_GET['pval']) && 0 == $_GET['pval'] && !$updraftplus->have_addons) ? ''.__('For even more features and personal support, check out ', 'updraftplus').'UpdraftPlus Premium.
' : "";
echo "".__('Your backup has been restored.', 'updraftplus').'
';
// Unnecessary - will be advised of this below
// if (2 == $_GET['updraft_restore_success']) echo ' '.__('Your old (themes, uploads, plugins, whatever) directories have been retained with "-old" appended to their name. Remove them when you are satisfied that the backup worked properly.');
echo $success_advert;
$include_deleteform_div = false;
}
if ($this->scan_old_dirs(true)) $this->print_delete_old_dirs_form(true, $include_deleteform_div);
// Close the div opened by the earlier section
if (isset($_GET['updraft_restore_success'])) echo '
';
if (empty($success_advert) && empty($this->no_settings_warning)) {
if (!class_exists('UpdraftPlus_Notices')) updraft_try_include_file('includes/updraftplus-notices.php', 'include_once');
global $updraftplus_notices;
$backup_history = UpdraftPlus_Backup_History::get_history();
$review_dismiss = UpdraftPlus_Options::get_updraft_option('dismissed_review_notice', 0);
$backup_dir = $updraftplus->backups_dir_location();
// N.B. Not an exact proxy for the installed time; they may have tweaked the expert option to move the directory
$installed = @filemtime($backup_dir.'/index.html');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
$installed_for = time() - $installed;
$advert = false;
if (!empty($backup_history) && $installed && time() > $review_dismiss && $installed_for > 28*86400 && $installed_for < 84*86400) {
$advert = 'rate';
}
$updraftplus_notices->do_notice($advert);
}
if (!$updraftplus->memory_check(64)) {
// HS8390 - A case where UpdraftPlus::memory_check_current() returns -1
$memory_check_current = $updraftplus->memory_check_current();
if ($memory_check_current > 0) {
?>
memory_check_current(); ?> MB
errors)) {
echo '';
$updraftplus->list_errors();
echo '
';
}
$backup_history = UpdraftPlus_Backup_History::get_history();
if (empty($backup_history)) {
UpdraftPlus_Backup_History::rebuild();
$backup_history = UpdraftPlus_Backup_History::get_history();
}
$tabflag = 'backups';
$main_tabs = $this->get_main_tabs_array();
if (isset($_REQUEST['tab'])) {
$request_tab = sanitize_text_field($_REQUEST['tab']);
$valid_tabflags = array_keys($main_tabs);
if (in_array($request_tab, $valid_tabflags)) {
$tabflag = $request_tab;
} else {
$tabflag = 'backups';
}
}
$this->include_template('wp-admin/settings/tab-bar.php', false, array('main_tabs' => $main_tabs, 'backup_history' => $backup_history, 'tabflag' => $tabflag));
?>
include_template('wp-admin/settings/delete-and-restore-modals.php');
?>
style="">
$is_opera);
$this->include_template('wp-admin/settings/tab-backups.php', false, array('backup_history' => $backup_history, 'options' => $tmp_opts));
$this->include_template('wp-admin/settings/upload-backups-modal.php');
?>
style="">
include_template('wp-admin/settings/migrator-no-migrator.php');
}
?>
style="">
settings_formcontents(); ?>
get_option(UDADDONS2_SLUG.'_options');
if (!empty($options['email'])) {
$email = htmlspecialchars($options['email']);
}
}
}
// Check the vault's email if we fail to get the "email" from the "Premium / Extensions" tab
if (empty($email)) {
$settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('updraftvault');
if (!is_wp_error($settings)) {
if (!empty($settings['settings'])) {
foreach ($settings['settings'] as $storage_options) {
if (!empty($storage_options['email'])) {
$email = $storage_options['email'];
break;
}
}
}
}
}
// Checking any possible email we could find from the "updraft_email" option in case the
// above two checks failed.
if (empty($email)) {
$possible_emails = $updraftplus->just_one_email(UpdraftPlus_Options::get_updraft_option('updraft_email'));
if (!empty($possible_emails)) {
// If we get an array from the 'just_one_email' result then we're going
// to pull the very first entry and make use of that on the succeeding process.
if (is_array($possible_emails)) $possible_emails = array_shift($possible_emails);
if (is_string($possible_emails)) {
$emails = explode(',', $possible_emails);
$email = trim($emails[0]);
}
}
}
$this->include_template('wp-admin/settings/updraftcentral-connect.php', false, array('email' => $email));
?>
style="">
settings_advanced_tools(); ?>
style="">
include_template('wp-admin/settings/tab-addons.php', true, array('tabflag' => $tabflag));
echo apply_filters('updraftplus_addonstab_content', $tab_addons);
?>
settings_footer();
}
/**
* Get main tabs array
*
* @return Array Array which have key as a tab key and value as tab label
*/
private function get_main_tabs_array() {
return apply_filters(
'updraftplus_main_tabs',
array(
'backups' => __('Backup / Restore', 'updraftplus'),
'migrate' => __('Migrate / Clone', 'updraftplus'),
'settings' => __('Settings', 'updraftplus'),
'expert' => __('Advanced Tools', 'updraftplus'),
'addons' => __('Premium / Extensions', 'updraftplus'),
)
);
}
/**
* Potentially register an action for showing restore progress
*/
private function print_restore_in_progress_box_if_needed() {
global $updraftplus;
$check_restore_progress = $updraftplus->check_restore_progress();
// Check to see if the restore is still in progress
if (is_array($check_restore_progress) && true == $check_restore_progress['status']) {
$restore_jobdata = $check_restore_progress['restore_jobdata'];
$restore_jobdata['jobid'] = $check_restore_progress['restore_in_progress'];
$this->restore_in_progress_jobdata = $restore_jobdata;
add_action('all_admin_notices', array($this, 'show_admin_restore_in_progress_notice'));
}
}
/**
* This function is called via the command class, it will get the resume restore notice to be shown when a restore is taking place over AJAX
*
* @param string $job_id - the id of the job
*
* @return WP_Error|string - can return a string containing html or a WP_Error
*/
public function get_restore_resume_notice($job_id) {
global $updraftplus;
if (empty($job_id)) return new WP_Error('missing_parameter', 'Missing parameters.');
$restore_jobdata = $updraftplus->jobdata_getarray($job_id);
if (!is_array($restore_jobdata) && empty($restore_jobdata)) return new WP_Error('missing_jobdata', 'Job data not found.');
$restore_jobdata['jobid'] = $job_id;
$this->restore_in_progress_jobdata = $restore_jobdata;
$html = $this->show_admin_restore_in_progress_notice(true, true);
if (empty($html)) return new WP_Error('job_aborted', 'Job aborted.');
return $html;
}
/**
* If added, then runs upon the WP action all_admin_notices, or can be called via get_restore_resume_notice() for when a restore is running over AJAX
*
* @param Boolean $return_instead_of_echo - indicates if we want to add the tfa UI
* @param Boolean $exclude_js - indicates if we want to exclude the js in the returned html
*
* @return void|string - can return a string containing html or echo the html to page
*/
public function show_admin_restore_in_progress_notice($return_instead_of_echo = false, $exclude_js = false) {
if (isset($_REQUEST['action']) && 'updraft_restore_abort' === $_REQUEST['action'] && !empty($_REQUEST['job_id'])) {
delete_site_option('updraft_restore_in_progress');
return;
}
$restore_jobdata = $this->restore_in_progress_jobdata;
$seconds_ago = time() - (int) $restore_jobdata['job_time_ms'];
$minutes_ago = floor($seconds_ago/60);
$seconds_ago = $seconds_ago - $minutes_ago*60;
$time_ago = sprintf(__("%s minutes, %s seconds", 'updraftplus'), $minutes_ago, $seconds_ago);
$html = '';
$html .= '
UpdraftPlus: '.__('Unfinished restoration', 'updraftplus').'';
$html .= '
'.sprintf(__('You have an unfinished restoration operation, begun %s ago.', 'updraftplus'), $time_ago).'
';
$html .= '
';
if ($return_instead_of_echo) return $html;
echo $html;
}
/**
* This method will build the UpdraftPlus.com login form and echo it to the page.
*
* @param String $option_page - the option page this form is being output to
* @param Boolean $tfa - indicates if we want to add the tfa UI
* @param Boolean $include_form_container - indicates if we want the form container
* @param Array $further_options - other options (see below for the possibilities + defaults)
*
* @return void
*/
public function build_credentials_form($option_page, $tfa = false, $include_form_container = true, $further_options = array()) {
global $updraftplus;
$further_options = wp_parse_args($further_options, array(
'under_username' => __("Not yet got an account (it's free)? Go get one!", 'updraftplus'),
'under_username_link' => $updraftplus->get_url('my-account')
));
if ($include_form_container) {
$enter_credentials_begin = UpdraftPlus_Options::options_form_begin('', false, array(), 'updraftplus_com_login');
if (is_multisite()) $enter_credentials_begin .= '';
} else {
$enter_credentials_begin = '';
}
$interested = htmlspecialchars(__('Interested in knowing about your UpdraftPlus.Com password security? Read about it here.', 'updraftplus'));
$connect = htmlspecialchars(__('Connect', 'updraftplus'));
$enter_credentials_end = '
';
if ($include_form_container) {
$enter_credentials_end .= '';
} else {
$enter_credentials_end .= '';
}
$enter_credentials_end .= '' . __('Processing', 'updraftplus') . '...
';
$enter_credentials_end .= '
'.$interested.'
';
$enter_credentials_end .= $include_form_container ? '' : '
';
echo $enter_credentials_begin;
$options = apply_filters('updraftplus_com_login_options', array("email" => "", "password" => ""));
if ($include_form_container) {
// We have to duplicate settings_fields() in order to set our referer
// settings_fields(UDADDONS2_SLUG.'_options');
$option_group = $option_page.'_options';
echo "";
echo '';
// wp_nonce_field("$option_group-options");
// This one is used on multisite
echo '';
$name = "_wpnonce";
$action = esc_attr($option_group."-options");
$nonce_field = '';
echo $nonce_field;
$referer = esc_attr(UpdraftPlus_Manipulation_Functions::wp_unslash($_SERVER['REQUEST_URI']));
// This one is used on single site installs
if (false === strpos($referer, '?')) {
$referer .= '?tab=addons';
} else {
$referer .= '&tab=addons';
}
echo '';
// End of duplication of settings-fields()
}
?>
include_template('wp-admin/settings/backupnow-modal.php', true);
}
/**
* Also used by the auto-backups add-on
*
* @param Boolean $wide_format Whether to return data in a wide format
* @param Boolean $print_active_jobs Whether to include currently active jobs
* @return String - the HTML output
*/
public function render_active_jobs_and_log_table($wide_format = false, $print_active_jobs = true) {
global $updraftplus;
?>
print_active_jobs() : '';?>
:
most_recently_modified_log_link(); ?>
:
most_recently_modified_log_link(); ?>
get_updraftplus_rssfeed();
if (is_a($feed, 'SimplePie')) {
echo '
'.__('Latest UpdraftPlus.com news:', 'updraftplus').' | ';
echo ' |
---|
';
}
}
?>
last_modified_log();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused parameter is present because the method returns an array.
?>
class="updraft-log-link" onclick="event.preventDefault(); updraft_popuplog('');">
include_template('wp-admin/settings/downloading-and-restoring.php', $return_result, array('backup_history' => $backup_history, 'options' => $options));
}
/**
* Renders take backup content
*/
public function take_backup_content() {
global $updraftplus;
$updraft_dir = $updraftplus->backups_dir_location();
$backup_disabled = UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir) ? '' : 'disabled="disabled"';
$this->include_template('wp-admin/settings/take-backup.php', false, array('backup_disabled' => $backup_disabled));
}
/**
* Output a table row using the updraft_debugrow class
*
* @param String $head - header cell contents
* @param String $content - content cell contents
*/
public function settings_debugrow($head, $content) {
echo "$head | $content |
";
}
public function settings_advanced_tools($return_instead_of_echo = false, $pass_through = array()) {
return $this->include_template('wp-admin/advanced/advanced-tools.php', $return_instead_of_echo, $pass_through);
}
/**
* Paint the HTML for the form for deleting old directories
*
* @param Boolean $include_blurb - whether to include explanatory text
* @param Boolean $include_div - whether to wrap inside a div tag
*/
public function print_delete_old_dirs_form($include_blurb = true, $include_div = true) {
if ($include_blurb) {
if ($include_div) {
echo '';
}
echo '
'.__('Your WordPress install has old folders from its state before you restored/migrated (technical information: these are suffixed with -old).', 'updraftplus').' '.__('You should press this button to delete them as soon as you have verified that the restoration worked.', 'updraftplus').'
';
}
?>
';
}
/**
* Return cron status information about a specified in-progress job
*
* @param Boolean|String $job_id - the job to get information about; or, if not specified, all jobs
*
* @return Array|Boolean - the requested information, or false if it was not found. Format differs depending on whether info on all jobs, or a single job, was requested.
*/
public function get_cron($job_id = false) {
$cron = get_option('cron');
if (!is_array($cron)) $cron = array();
if (false === $job_id) return $cron;
foreach ($cron as $time => $job) {
if (!isset($job['updraft_backup_resume'])) continue;
foreach ($job['updraft_backup_resume'] as $info) {
if (isset($info['args'][1]) && $job_id == $info['args'][1]) {
global $updraftplus;
$jobdata = $updraftplus->jobdata_getarray($job_id);
return is_array($jobdata) ? array($time, $jobdata) : false;
}
}
}
}
/**
* Gets HTML describing the active jobs
*
* @param Boolean $this_job_only A value for $this_job_only also causes something non-empty to always be returned (to allow detection of the job having started on the front-end)
*
* @return String - the HTML
*/
private function print_active_jobs($this_job_only = false) {
$cron = $this->get_cron();
$ret = '';
foreach ($cron as $time => $job) {
if (!isset($job['updraft_backup_resume'])) continue;
foreach ($job['updraft_backup_resume'] as $info) {
if (isset($info['args'][1])) {
$job_id = $info['args'][1];
if (false === $this_job_only || $job_id == $this_job_only) {
$ret .= $this->print_active_job($job_id, false, $time, $info['args'][0]);
}
}
}
}
// A value for $this_job_only implies that output is required
if (false !== $this_job_only && !$ret) {
$ret = $this->print_active_job($this_job_only);
if ('' == $ret) {
global $updraftplus;
$log_file = $updraftplus->get_logfile_name($this_job_only);
// if the file exists, the backup was booted. Check if the information about completion is found in the log, or if it was modified at least 2 minutes ago.
if (file_exists($log_file) && ($updraftplus->found_backup_complete_in_logfile($this_job_only) || (time() - filemtime($log_file)) > 120)) {
// The presence of the exact ID matters to the front-end - indicates that the backup job has at least begun
$ret = '
';
}
}
}
return $ret;
}
/**
* Print the HTML for a particular job
*
* @param String $job_id - the job identifier/nonce
* @param Boolean $is_oneshot - whether this backup should be 'one shot', i.e. no resumptions
* @param Boolean|Integer $time
* @param Integer $next_resumption
*
* @return String
*/
private function print_active_job($job_id, $is_oneshot = false, $time = false, $next_resumption = false) {
$ret = '';
global $updraftplus;
$jobdata = $updraftplus->jobdata_getarray($job_id);
if (false == apply_filters('updraftplus_print_active_job_continue', true, $is_oneshot, $next_resumption, $jobdata)) return '';
if (!isset($jobdata['backup_time'])) return '';
$backupable_entities = $updraftplus->get_backupable_file_entities(true, true);
$began_at = isset($jobdata['backup_time']) ? get_date_from_gmt(gmdate('Y-m-d H:i:s', (int) $jobdata['backup_time']), 'D, F j, Y H:i') : '?';
$backup_label = !empty($jobdata['label']) ? $jobdata['label'] : '';
$remote_sent = (!empty($jobdata['service']) && ((is_array($jobdata['service']) && in_array('remotesend', $jobdata['service'])) || 'remotesend' === $jobdata['service'])) ? true : false;
$jobstatus = empty($jobdata['jobstatus']) ? 'unknown' : $jobdata['jobstatus'];
$stage = 0;
switch ($jobstatus) {
// Stage 0
case 'begun':
$curstage = __('Backup begun', 'updraftplus');
break;
// Stage 1
case 'filescreating':
$stage = 1;
$curstage = __('Creating file backup zips', 'updraftplus');
if (!empty($jobdata['filecreating_substatus']) && isset($backupable_entities[$jobdata['filecreating_substatus']['e']]['description'])) {
$sdescrip = preg_replace('/ \(.*\)$/', '', $backupable_entities[$jobdata['filecreating_substatus']['e']]['description']);
if (strlen($sdescrip) > 20 && isset($jobdata['filecreating_substatus']['e']) && is_array($jobdata['filecreating_substatus']['e']) && isset($backupable_entities[$jobdata['filecreating_substatus']['e']]['shortdescription'])) $sdescrip = $backupable_entities[$jobdata['filecreating_substatus']['e']]['shortdescription'];
$curstage .= ' ('.$sdescrip.')';
if (isset($jobdata['filecreating_substatus']['i']) && isset($jobdata['filecreating_substatus']['t'])) {
$stage = min(2, 1 + ($jobdata['filecreating_substatus']['i']/max($jobdata['filecreating_substatus']['t'], 1)));
}
}
break;
case 'filescreated':
$stage = 2;
$curstage = __('Created file backup zips', 'updraftplus');
break;
// Stage 4
case 'clonepolling':
$stage = 4;
$curstage = __('Clone server being provisioned and booted (can take several minutes)', 'updraftplus');
break;
case 'partialclouduploading':
case 'clouduploading':
$stage = 'clouduploading' == $jobstatus ? 4 : 2;
$curstage = __('Uploading files to remote storage', 'updraftplus');
if ($remote_sent) $curstage = __('Sending files to remote site', 'updraftplus');
if (isset($jobdata['uploading_substatus']['t']) && isset($jobdata['uploading_substatus']['i'])) {
$t = max((int) $jobdata['uploading_substatus']['t'], 1);
$i = min($jobdata['uploading_substatus']['i']/$t, 1);
$p = min($jobdata['uploading_substatus']['p'], 1);
$pd = $i + $p/$t;
$stage = 'clouduploading' == $jobstatus ? $stage + $pd : $stage;
$curstage .= ' ('.floor(100*$pd).'%, '.sprintf(__('file %d of %d', 'updraftplus'), (int) $jobdata['uploading_substatus']['i']+1, $t).')';
}
break;
case 'pruning':
$stage = 5;
$curstage = __('Pruning old backup sets', 'updraftplus');
break;
case 'resumingforerrors':
$stage = -1;
$curstage = __('Waiting until scheduled time to retry because of errors', 'updraftplus');
break;
// Stage 6
case 'finished':
$stage = 6;
$curstage = __('Backup finished', 'updraftplus');
break;
default:
// Database creation and encryption occupies the space from 2 to 4. Databases are created then encrypted, then the next database is created/encrypted, etc.
if ('dbcreated' == substr($jobstatus, 0, 9)) {
$jobstatus = 'dbcreated';
$whichdb = substr($jobstatus, 9);
if (!is_numeric($whichdb)) $whichdb = 0;
$howmanydbs = max((empty($jobdata['backup_database']) || !is_array($jobdata['backup_database'])) ? 1 : count($jobdata['backup_database']), 1);
$perdbspace = 2/$howmanydbs;
$stage = min(4, 2 + ($whichdb+2)*$perdbspace);
$curstage = __('Created database backup', 'updraftplus');
} elseif ('dbcreating' == substr($jobstatus, 0, 10)) {
$whichdb = substr($jobstatus, 10);
if (!is_numeric($whichdb)) $whichdb = 0;
$howmanydbs = (empty($jobdata['backup_database']) || !is_array($jobdata['backup_database'])) ? 1 : count($jobdata['backup_database']);
$perdbspace = 2/$howmanydbs;
$jobstatus = 'dbcreating';
$stage = min(4, 2 + $whichdb*$perdbspace);
$curstage = __('Creating database backup', 'updraftplus');
if (!empty($jobdata['dbcreating_substatus']['t'])) {
$curstage .= ' ('.sprintf(__('table: %s', 'updraftplus'), $jobdata['dbcreating_substatus']['t']).')';
if (!empty($jobdata['dbcreating_substatus']['i']) && !empty($jobdata['dbcreating_substatus']['a'])) {
$substage = max(0.001, ($jobdata['dbcreating_substatus']['i'] / max($jobdata['dbcreating_substatus']['a'], 1)));
$stage += $substage * $perdbspace * 0.5;
}
}
} elseif ('dbencrypting' == substr($jobstatus, 0, 12)) {
$whichdb = substr($jobstatus, 12);
if (!is_numeric($whichdb)) $whichdb = 0;
$howmanydbs = (empty($jobdata['backup_database']) || !is_array($jobdata['backup_database'])) ? 1 : count($jobdata['backup_database']);
$perdbspace = 2/$howmanydbs;
$stage = min(4, 2 + $whichdb*$perdbspace + $perdbspace*0.5);
$jobstatus = 'dbencrypting';
$curstage = __('Encrypting database', 'updraftplus');
} elseif ('dbencrypted' == substr($jobstatus, 0, 11)) {
$whichdb = substr($jobstatus, 11);
if (!is_numeric($whichdb)) $whichdb = 0;
$howmanydbs = (empty($jobdata['backup_database']) || !is_array($jobdata['backup_database'])) ? 1 : count($jobdata['backup_database']);
$jobstatus = 'dbencrypted';
$perdbspace = 2/$howmanydbs;
$stage = min(4, 2 + $whichdb*$perdbspace + $perdbspace);
$curstage = __('Encrypted database', 'updraftplus');
} else {
$curstage = __('Unknown', 'updraftplus');
}
}
$runs_started = empty($jobdata['runs_started']) ? array() : $jobdata['runs_started'];
$time_passed = empty($jobdata['run_times']) ? array() : $jobdata['run_times'];
$last_checkin_ago = -1;
if (is_array($time_passed)) {
foreach ($time_passed as $run => $passed) {
if (isset($runs_started[$run])) {
$time_ago = microtime(true) - ($runs_started[$run] + $time_passed[$run]);
if ($time_ago < $last_checkin_ago || -1 == $last_checkin_ago) $last_checkin_ago = $time_ago;
}
}
}
$next_res_after = (int) $time-time();
$next_res_txt = $is_oneshot ? '' : sprintf(__('next resumption: %d', 'updraftplus'), $next_resumption).($next_resumption ? ' '.sprintf(__('(after %ss)', 'updraftplus'), $next_res_after) : '').' ';
$last_activity_txt = ($last_checkin_ago >= 0) ? sprintf(__('last activity: %ss ago', 'updraftplus'), floor($last_checkin_ago)).' ' : '';
if (($last_checkin_ago < 50 && $next_res_after>30) || $is_oneshot) {
$show_inline_info = $last_activity_txt;
$title_info = $next_res_txt;
} else {
$show_inline_info = $next_res_txt;
$title_info = $last_activity_txt;
}
$ret .= '
';
$ret .= '
'.(!empty($backup_label) ? esc_html($backup_label) : $began_at).
'
';
$ret .= '
';
// Existence of the 'updraft-jobid-(id)' id is checked for in other places, so do not modify this
$ret .= '
';
if ($clone_url) $ret .= '
';
$ret .= apply_filters('updraft_printjob_beforewarnings', '', $jobdata, $job_id);
if (!empty($jobdata['warnings']) && is_array($jobdata['warnings'])) {
$ret .= '
';
foreach ($jobdata['warnings'] as $warning) {
$ret .= '- '.sprintf(__('Warning: %s', 'updraftplus'), make_clickable(htmlspecialchars($warning))).'
';
}
$ret .= '
';
}
$ret .= '
';
// $ret .= '
'.htmlspecialchars($curstage).'';
$ret .= htmlspecialchars($curstage);
// we need to add this data-progress attribute in order to be able to update the progress bar in UDC
$ret .= '
';
$ret .= '
';
$ret .= '
';
$ret .= '
';
return $ret;
}
private function delete_old_dirs_go($show_return = true) {
echo $show_return ? '
UpdraftPlus - '.__('Remove old folders', 'updraftplus').'
' : '
'.__('Remove old directories', 'updraftplus').'
';
if ($this->delete_old_dirs()) {
echo '
'.__('Old folders successfully removed.', 'updraftplus').'
';
} else {
echo '
',__('Old folder removal failed for some reason.', 'updraftplus').' '.__('You may want to do this manually.', 'updraftplus').'
';
}
if ($show_return) echo '
'.__('Actions', 'updraftplus').': '.__('Return to UpdraftPlus configuration', 'updraftplus').'';
}
/**
* Deletes the -old directories that are created when a backup is restored.
*
* @return Boolean. Can also exit (something we ought to probably review)
*/
private function delete_old_dirs() {
global $wp_filesystem, $updraftplus;
$credentials = request_filesystem_credentials(wp_nonce_url(UpdraftPlus_Options::admin_page_url()."?page=updraftplus&action=updraft_delete_old_dirs", 'updraftplus-credentialtest-nonce', 'updraft_delete_old_dirs_nonce'));
$wpfs = WP_Filesystem($credentials);
if (!empty($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
foreach ($wp_filesystem->errors->get_error_messages() as $message) show_message($message);
exit;
}
if (!$wpfs) exit;
// From WP_CONTENT_DIR - which contains 'themes'
$ret = $this->delete_old_dirs_dir($wp_filesystem->wp_content_dir());
$updraft_dir = $updraftplus->backups_dir_location();
if ($updraft_dir) {
$ret4 = $updraft_dir ? $this->delete_old_dirs_dir($updraft_dir, false) : true;
} else {
$ret4 = true;
}
$plugs = untrailingslashit($wp_filesystem->wp_plugins_dir());
if ($wp_filesystem->is_dir($plugs.'-old')) {
echo "
".__('Delete', 'updraftplus').": plugins-old: ";
if (!$wp_filesystem->delete($plugs.'-old', true)) {
$ret3 = false;
echo "
".__('Failed', 'updraftplus')."";
echo $updraftplus->log_permission_failure_message($wp_filesystem->wp_content_dir(), 'Delete '.$plugs.'-old');
} else {
$ret3 = true;
echo "
".__('OK', 'updraftplus')."";
}
} else {
$ret3 = true;
}
return $ret && $ret3 && $ret4;
}
private function delete_old_dirs_dir($dir, $wpfs = true) {
$dir = trailingslashit($dir);
global $wp_filesystem, $updraftplus;
if ($wpfs) {
$list = $wp_filesystem->dirlist($dir);
} else {
$list = scandir($dir);
}
if (!is_array($list)) return false;
$ret = true;
foreach ($list as $item) {
$name = (is_array($item)) ? $item['name'] : $item;
if ("-old" == substr($name, -4, 4)) {
// recursively delete
print "
".__('Delete', 'updraftplus').": ".htmlspecialchars(basename($dir).'/'.$name).": ";
if ($wpfs) {
if (!$wp_filesystem->delete($dir.$name, true)) {
$ret = false;
echo "
".__('Failed', 'updraftplus')."";
echo $updraftplus->log_permission_failure_message($dir, 'Delete '.$dir.$name);
} else {
echo "
".__('OK', 'updraftplus')."";
}
} else {
if (UpdraftPlus_Filesystem_Functions::remove_local_directory($dir.$name)) {
echo "
".__('OK', 'updraftplus')."";
} else {
$ret = false;
echo "
".__('Failed', 'updraftplus')."";
echo $updraftplus->log_permission_failure_message($dir, 'Delete '.$dir.$name);
}
}
}
}
return $ret;
}
/**
* The aim is to get a directory that is writable by the webserver, because that's the only way we can create zip files
*
* @return Boolean|WP_Error true if successful, otherwise false or a WP_Error
*/
private function create_backup_dir() {
global $wp_filesystem, $updraftplus;
if (false === ($credentials = request_filesystem_credentials(UpdraftPlus_Options::admin_page().'?page=updraftplus&action=updraft_create_backup_dir&nonce='.wp_create_nonce('create_backup_dir')))) {
return false;
}
if (!WP_Filesystem($credentials)) {
// our credentials were no good, ask the user for them again
request_filesystem_credentials(UpdraftPlus_Options::admin_page().'?page=updraftplus&action=updraft_create_backup_dir&nonce='.wp_create_nonce('create_backup_dir'), '', true);
return false;
}
$updraft_dir = $updraftplus->backups_dir_location();
$default_backup_dir = $wp_filesystem->find_folder(dirname($updraft_dir)).basename($updraft_dir);
$updraft_dir = ($updraft_dir) ? $wp_filesystem->find_folder(dirname($updraft_dir)).basename($updraft_dir) : $default_backup_dir;
if (!$wp_filesystem->is_dir($default_backup_dir) && !$wp_filesystem->mkdir($default_backup_dir, 0775)) {
$wperr = new WP_Error;
if ($wp_filesystem->errors->get_error_code()) {
foreach ($wp_filesystem->errors->get_error_messages() as $message) {
$wperr->add('mkdir_error', $message);
}
return $wperr;
} else {
return new WP_Error('mkdir_error', __('The request to the filesystem to create the directory failed.', 'updraftplus'));
}
}
if ($wp_filesystem->is_dir($default_backup_dir)) {
if (UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) return true;
@$wp_filesystem->chmod($default_backup_dir, 0775);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
if (UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) return true;
@$wp_filesystem->chmod($default_backup_dir, 0777);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
if (UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) {
echo '
'.__('The folder was created, but we had to change its file permissions to 777 (world-writable) to be able to write to it.', 'updraftplus').' '.__('You should check with your hosting provider that this will not cause any problems', 'updraftplus').'
';
return true;
} else {
@$wp_filesystem->chmod($default_backup_dir, 0775);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
$show_dir = (0 === strpos($default_backup_dir, ABSPATH)) ? substr($default_backup_dir, strlen(ABSPATH)) : $default_backup_dir;
return new WP_Error('writable_error', __('The folder exists, but your webserver does not have permission to write to it.', 'updraftplus').' '.__('You will need to consult with your web hosting provider to find out how to set permissions for a WordPress plugin to write to the directory.', 'updraftplus').' ('.$show_dir.')');
}
}
return true;
}
/**
* scans the content dir to see if any -old dirs are present
*
* @param Boolean $print_as_comment Echo information in an HTML comment
* @return Boolean
*/
private function scan_old_dirs($print_as_comment = false) {
global $updraftplus;
$dirs = scandir(untrailingslashit(WP_CONTENT_DIR));
if (!is_array($dirs)) $dirs = array();
$backups_dir_location = $updraftplus->backups_dir_location();
$dirs_u = @scandir($backups_dir_location);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
if (!is_array($dirs_u)) $dirs_u = array();
foreach (array_merge($dirs, $dirs_u) as $dir) {
// is_dir() should be checked because scandir() occasionally returns a cached directory list.
$dir_path = $backups_dir_location.'/'.$dir;
if (preg_match('/-old$/', $dir) && is_dir($dir_path)) {
if ($print_as_comment) echo '';
return true;
}
}
// No need to scan ABSPATH - we don't backup there
if (is_dir(untrailingslashit(WP_PLUGIN_DIR).'-old')) {
if ($print_as_comment) echo '';
return true;
}
return false;
}
/**
* Outputs html for a storage method using the parameters passed in, this version should be removed when all remote storages use the multi version
*
* @param String $method a list of methods to be used when
* @param String $header the table header content
* @param String $contents the table contents
*/
public function storagemethod_row($method, $header, $contents) {
?>
|
|
|
|
'.$header.' |
'.$contents.' |
';
}
/**
* Get HTML suitable for the admin area for the status of the last backup
*
* @return String
*/
public function last_backup_html() {
global $updraftplus;
$updraft_last_backup = UpdraftPlus_Options::get_updraft_option('updraft_last_backup');
if ($updraft_last_backup) {
// Convert to GMT, then to blog time
$backup_time = (int) $updraft_last_backup['backup_time'];
$print_time = get_date_from_gmt(gmdate('Y-m-d H:i:s', $backup_time), 'D, F j, Y H:i');
if (empty($updraft_last_backup['backup_time_incremental'])) {
$last_backup_text = "
".$print_time.'';
} else {
$inc_time = get_date_from_gmt(gmdate('Y-m-d H:i:s', $updraft_last_backup['backup_time_incremental']), 'D, F j, Y H:i');
$last_backup_text = "
$inc_time (".sprintf(__('incremental backup; base backup: %s', 'updraftplus'), $print_time).')';
}
$last_backup_text .= '
';
// Show errors + warnings
if (is_array($updraft_last_backup['errors'])) {
foreach ($updraft_last_backup['errors'] as $err) {
$level = (is_array($err)) ? $err['level'] : 'error';
$message = (is_array($err)) ? $err['message'] : $err;
$last_backup_text .= ('warning' == $level) ? "
" : "";
if ('warning' == $level) {
$message = sprintf(__("Warning: %s", 'updraftplus'), make_clickable(htmlspecialchars($message)));
} else {
$message = htmlspecialchars($message);
}
$last_backup_text .= $message;
$last_backup_text .= '
';
}
}
// Link log
if (!empty($updraft_last_backup['backup_nonce'])) {
$updraft_dir = $updraftplus->backups_dir_location();
$potential_log_file = $updraft_dir."/log.".$updraft_last_backup['backup_nonce'].".txt";
if (is_readable($potential_log_file)) $last_backup_text .= "".__('Download log file', 'updraftplus')."";
}
} else {
$last_backup_text = "".__('No backup has been completed', 'updraftplus')."";
}
return $last_backup_text;
}
/**
* Get a list of backup intervals
*
* @param String $what_for - 'files' or 'db'
*
* @return Array - keys are used as identifiers in the UI drop-down; values are user-displayed text describing the interval
*/
public function get_intervals($what_for = 'db') {
global $updraftplus;
$intervals = wp_get_schedules();
if ($updraftplus->is_restricted_hosting('only_one_backup_per_month')) {
$intervals = array_intersect_key($intervals, array('monthly' => array()));
} else {
if ('db' != $what_for) unset($intervals['everyhour']);
}
$intervals = array_intersect_key(updraftplus_list_cron_schedules(), $intervals); // update schedule descriptions for the UI of the backup schedule drop-down and rearrange schedule order
foreach ($intervals as $interval => $data) {
$intervals[$interval] = $data['display'];
}
$intervals = array('manual' => _x('Manual', 'i.e. Non-automatic', 'updraftplus')) + $intervals;
return apply_filters('updraftplus_backup_intervals', $intervals, $what_for);
}
public function really_writable_message($really_is_writable, $updraft_dir) {
if ($really_is_writable) {
$dir_info = ''.__('Backup directory specified is writable, which is good.', 'updraftplus').'';
} else {
$dir_info = '';
if (!is_dir($updraft_dir)) {
$dir_info .= __('Backup directory specified does not exist.', 'updraftplus');
} else {
$dir_info .= __('Backup directory specified exists, but is not writable.', 'updraftplus');
}
$dir_info .= ''.__('Follow this link to attempt to create the directory and set the permissions', 'updraftplus').', '.__('or, to reset this option', 'updraftplus').' '.__('press here', 'updraftplus').'. '.__('If that is unsuccessful check the permissions on your server or change it to another directory that is writable by your web server process.', 'updraftplus').'';
}
return $dir_info;
}
/**
* Directly output the settings form (suitable for the admin area)
*
* @param Array $options current options (passed on to the template)
*/
public function settings_formcontents($options = array()) {
$this->include_template('wp-admin/settings/form-contents.php', false, array(
'options' => $options
));
if (!(defined('UPDRAFTCENTRAL_COMMAND') && UPDRAFTCENTRAL_COMMAND)) {
$this->include_template('wp-admin/settings/exclude-modal.php', false);
}
}
public function get_settings_js($method_objects, $really_is_writable, $updraft_dir, $active_service) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Filter use
global $updraftplus;
ob_start();
?>
jQuery(function() {
backup_methods as $method => $description) {
// already done: updraft_try_include_file('methods/'.$method.'.php', 'require_once');
$call_method = "UpdraftPlus_BackupModule_$method";
if (method_exists($call_method, 'config_print_javascript_onready')) {
$method_objects[$method]->config_print_javascript_onready();
}
}
?>
});
get_backupable_file_entities(true, true);
// The true (default value if non-existent) here has the effect of forcing a default of on.
$include_more_paths = UpdraftPlus_Options::get_updraft_option('updraft_include_more_path');
foreach ($backupable_entities as $key => $info) {
$included = (UpdraftPlus_Options::get_updraft_option("updraft_include_$key", apply_filters("updraftplus_defaultoption_include_".$key, true))) ? 'checked="checked"' : "";
if ('others' == $key || 'uploads' == $key) {
$data_toggle_exclude_field = $show_exclusion_options ? 'data-toggle_exclude_field="'.$key.'"' : '';
$ret .= '';
if ($show_exclusion_options) {
$include_exclude = UpdraftPlus_Options::get_updraft_option('updraft_include_'.$key.'_exclude', ('others' == $key) ? UPDRAFT_DEFAULT_OTHERS_EXCLUDE : UPDRAFT_DEFAULT_UPLOADS_EXCLUDE);
$display = ($included) ? '' : 'class="updraft-hidden" style="display:none;"';
$exclude_container_class = $prefix.'updraft_include_'.$key.'_exclude';
if (!$for_updraftcentral) $exclude_container_class .= '_container';
$ret .= "";
$ret .= ' '.__('(the asterisk character matches zero or more characters)', 'updraftplus').'';
$exclude_input_type = $for_updraftcentral ? "text" : "hidden";
$exclude_input_extra_attr = $for_updraftcentral ? 'title="'.__('If entering multiple files/directories, then separate them with commas.', 'updraftplus').' '.__('For entities at the top level, you can use a * at the start or end of the entry as a wildcard.', 'updraftplus').'" size="54"' : '';
$ret .= '';
if (!$for_updraftcentral) {
global $updraftplus;
$backupable_file_entities = $updraftplus->get_backupable_file_entities();
if ('uploads' == $key) {
$path = UpdraftPlus_Manipulation_Functions::wp_normalize_path($backupable_file_entities['uploads']);
} elseif ('others' == $key) {
$path = UpdraftPlus_Manipulation_Functions::wp_normalize_path($backupable_file_entities['others']);
}
$ret .= $this->include_template('wp-admin/settings/file-backup-exclude.php', true, array(
'key' => $key,
'include_exclude' => $include_exclude,
'path' => $path,
'show_exclusion_options' => $show_exclusion_options,
));
}
$ret .= '
';
}
} else {
if ('more' != $key || true === $include_more || ('sometimes' === $include_more && !empty($include_more_paths))) {
$data_toggle_exclude_field = $show_exclusion_options ? 'data-toggle_exclude_field="'.$key.'"' : '';
$ret .= "";
$ret .= apply_filters("updraftplus_config_option_include_$key", '', $prefix, $for_updraftcentral);
}
}
}
return $ret;
}
/**
* Output or echo HTML for an error condition relating to a remote storage method
*
* @param String $text - the text of the message; this should already be escaped (no more is done)
* @param String $extraclass - a CSS class for the resulting DOM node
* @param Integer $echo - if set, then the results will be echoed as well as returned
*
* @return String - the results
*/
public function show_double_warning($text, $extraclass = '', $echo = true) {
$ret = "";
$ret .= "";
if ($echo) echo $ret;
return $ret;
}
public function optionfilter_split_every($value) {
return max(absint($value), UPDRAFTPLUS_SPLIT_MIN);
}
/**
* Check if curl exists; if not, print or return appropriate error messages
*
* @param String $service the service description (used only for user-visible messages - so, use the description)
* @param Boolean $has_fallback set as true if the lack of Curl only affects the ability to connect over SSL
* @param String $extraclass an extra CSS class for any resulting message, passed on to show_double_warning()
* @param Boolean $echo_instead_of_return whether the result should be echoed or returned
* @return String any resulting message, if $echo_instead_of_return was set
*/
public function curl_check($service, $has_fallback = false, $extraclass = '', $echo_instead_of_return = true) {
$ret = '';
// Check requirements
if (!function_exists("curl_init") || !function_exists('curl_exec')) {
$ret .= $this->show_double_warning(''.__('Warning', 'updraftplus').': '.sprintf(__("Your web server's PHP installation does not included a required (for %s) module (%s).", 'updraftplus'), $service, 'Curl').' '.__("Please contact your web hosting provider's support and ask for them to enable it.", 'updraftplus').' ', $extraclass, false);
} else {
$curl_version = curl_version();
$curl_ssl_supported= ($curl_version['features'] & CURL_VERSION_SSL);
if (!$curl_ssl_supported) {
if ($has_fallback) {
$ret .= ''.__('Warning', 'updraftplus').': '.__("Your web server's PHP/Curl installation does not support https access.", 'updraftplus').' '.sprintf(__("Communications with %s will be unencrypted.", 'updraftplus'), $service).' '.__("Ask your web host to install Curl/SSL in order to gain the ability for encryption (via an add-on).", 'updraftplus').'
';
} else {
$ret .= $this->show_double_warning(''.__('Warning', 'updraftplus').': '.__("Your web server's PHP/Curl installation does not support https access.", 'updraftplus').' '.sprintf(__("We cannot access %s without this support.", 'updraftplus'), $service).' '.__("Please contact your web hosting provider's support.", 'updraftplus').' '.sprintf(__("%s requires Curl+https.", 'updraftplus'), $service).' '.__("Please do not file any support requests; there is no alternative.", 'updraftplus').'
', $extraclass, false);
}
} else {
$ret .= ''.sprintf(__("Good news: Your site's communications with %s can be encrypted.", 'updraftplus'), $service).' '.__("If you see any errors to do with encryption, then look in the 'Expert Settings' for more help.", 'updraftplus').'
';
}
}
if ($echo_instead_of_return) {
echo $ret;
} else {
return $ret;
}
}
/**
* Get backup information in HTML format for a specific backup
*
* @param Array $backup_history all backups history
* @param String $key backup timestamp
* @param String $nonce backup nonce (job ID)
* @param Array|Null $job_data if an array, then use this as the job data (if null, then it will be fetched directly)
*
* @return string HTML-formatted backup information
*/
public function raw_backup_info($backup_history, $key, $nonce, $job_data = null) {
global $updraftplus;
$backup = $backup_history[$key];
$only_remote_sent = !empty($backup['service']) && (array('remotesend') === $backup['service'] || 'remotesend' === $backup['service']);
$pretty_date = get_date_from_gmt(gmdate('Y-m-d H:i:s', (int) $key), 'M d, Y G:i');
$rawbackup = "$pretty_date
";
if (!empty($backup['label'])) $rawbackup .= ''.$backup['label'].'';
if (null === $job_data) $job_data = empty($nonce) ? array() : $updraftplus->jobdata_getarray($nonce);
if (!$only_remote_sent) {
$rawbackup .= '
';
$rawbackup .= '';
}
$rawbackup .= '
';
$backupable_entities = $updraftplus->get_backupable_file_entities(true, true);
$checksums = $updraftplus->which_checksums();
foreach ($backupable_entities as $type => $info) {
if (!isset($backup[$type])) continue;
$rawbackup .= $updraftplus->printfile($info['description'], $backup, $type, $checksums, $job_data, true);
}
$total_size = 0;
foreach ($backup as $ekey => $files) {
if ('db' == strtolower(substr($ekey, 0, 2)) && '-size' != substr($ekey, -5, 5)) {
$rawbackup .= $updraftplus->printfile(__('Database', 'updraftplus'), $backup, $ekey, $checksums, $job_data, true);
}
if (!isset($backupable_entities[$ekey]) && ('db' != substr($ekey, 0, 2) || '-size' == substr($ekey, -5, 5))) continue;
if (is_string($files)) $files = array($files);
foreach ($files as $findex => $file) {
$size_key = (0 == $findex) ? $ekey.'-size' : $ekey.$findex.'-size';
$total_size = (false === $total_size || !isset($backup[$size_key]) || !is_numeric($backup[$size_key])) ? false : $total_size + $backup[$size_key];
}
}
$services = empty($backup['service']) ? array('none') : $backup['service'];
if (!is_array($services)) $services = array('none');
$rawbackup .= ''.__('Uploaded to:', 'updraftplus').' ';
$show_services = '';
foreach ($services as $serv) {
if ('none' == $serv || '' == $serv) {
$add_none = true;
} elseif (isset($updraftplus->backup_methods[$serv])) {
$show_services .= $show_services ? ', '.$updraftplus->backup_methods[$serv] : $updraftplus->backup_methods[$serv];
} else {
$show_services .= $show_services ? ', '.$serv : $serv;
}
}
if ('' == $show_services && $add_none) $show_services .= __('None', 'updraftplus');
$rawbackup .= $show_services;
if (false !== $total_size) {
$rawbackup .= '
'.__('Total backup size:', 'updraftplus').' '.UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($total_size).'';
}
$rawbackup .= '
'.print_r($backup, true).'
';
if (!empty($job_data) && is_array($job_data)) {
$rawbackup .= ''.htmlspecialchars(print_r($job_data, true)).'
';
}
return esc_attr($rawbackup);
}
private function download_db_button($bkey, $key, $esc_pretty_date, $backup, $accept = array()) {
if (!empty($backup['meta_foreign']) && isset($accept[$backup['meta_foreign']])) {
$desc_source = $accept[$backup['meta_foreign']]['desc'];
} else {
$desc_source = __('unknown source', 'updraftplus');
}
$ret = '';
if ('db' == $bkey) {
$dbt = empty($backup['meta_foreign']) ? esc_attr(__('Database', 'updraftplus')) : esc_attr(sprintf(__('Database (created by %s)', 'updraftplus'), $desc_source));
} else {
$dbt = __('External database', 'updraftplus').' ('.substr($bkey, 2).')';
}
$ret .= $this->download_button($bkey, $key, 0, null, '', $dbt, $esc_pretty_date, '0');
return $ret;
}
/**
* Go through each of the file entities
*
* @param Array $backup An array of meta information
* @param Integer $key Backup timestamp (epoch time)
* @param Array $accept An array of values to be accepted from values within $backup
* @param String $entities Entities to be added
* @param String $esc_pretty_date Whether the button needs to escape the pretty date format
* @return String - the resulting HTML
*/
public function download_buttons($backup, $key, $accept, &$entities, $esc_pretty_date) {
global $updraftplus;
$ret = '';
$backupable_entities = $updraftplus->get_backupable_file_entities(true, true);
$first_entity = true;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- template
foreach ($backupable_entities as $type => $info) {
if (!empty($backup['meta_foreign']) && 'wpcore' != $type) continue;
$ide = '';
if (empty($backup['meta_foreign'])) {
$sdescrip = preg_replace('/ \(.*\)$/', '', $info['description']);
if (strlen($sdescrip) > 20 && isset($info['shortdescription'])) $sdescrip = $info['shortdescription'];
} else {
$info['description'] = 'WordPress';
if (isset($accept[$backup['meta_foreign']])) {
$desc_source = $accept[$backup['meta_foreign']]['desc'];
$ide .= sprintf(__('Backup created by: %s.', 'updraftplus'), $accept[$backup['meta_foreign']]['desc']).' ';
} else {
$desc_source = __('unknown source', 'updraftplus');
$ide .= __('Backup created by unknown source (%s) - cannot be restored.', 'updraftplus').' ';
}
$sdescrip = (empty($accept[$backup['meta_foreign']]['separatedb'])) ? sprintf(__('Files and database WordPress backup (created by %s)', 'updraftplus'), $desc_source) : sprintf(__('Files backup (created by %s)', 'updraftplus'), $desc_source);
}
if (isset($backup[$type])) {
if (!is_array($backup[$type])) $backup[$type] = array($backup[$type]);
$howmanyinset = count($backup[$type]);
$expected_index = 0;
$index_missing = false;
$set_contents = '';
$entities .= "/$type=";
$whatfiles = $backup[$type];
ksort($whatfiles);
$total_file_size = 0;
foreach ($whatfiles as $findex => $bfile) {
$set_contents .= ('' == $set_contents) ? $findex : ",$findex";
if ($findex != $expected_index) $index_missing = true;
$expected_index++;
if ($howmanyinset > 0) {
if (!empty($backup[$type.(($findex > 0) ? $findex : '')."-size"]) && $findex < $howmanyinset) $total_file_size += $backup[$type.(($findex > 0) ? $findex : '')."-size"];
}
}
$ide = __('Press here to download or browse', 'updraftplus').' '.strtolower($info['description']);
$ide .= ' '.sprintf(__('(%d archive(s) in set, total %s).', 'updraftplus'), $howmanyinset, '%UP_backups_total_file_size%');
if ($index_missing) $ide .= ' '.__('You are missing one or more archives from this multi-archive set.', 'updraftplus');
$entities .= $set_contents.'/';
if (!empty($backup['meta_foreign'])) {
$entities .= '/plugins=0//themes=0//uploads=0//others=0/';
}
$ret .= $this->download_button($type, $key, 0, null, $ide, $sdescrip, $esc_pretty_date, $set_contents);
$ret = str_replace('%UP_backups_total_file_size%', UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($total_file_size), $ret);
}
}
return $ret;
}
public function date_label($pretty_date, $key, $backup, $jobdata, $nonce, $simple_format = false) {
$pretty_date = $simple_format ? $pretty_date : ''.$pretty_date.'
';
$ret = apply_filters('updraftplus_showbackup_date', $pretty_date, $backup, $jobdata, (int) $key, $simple_format);
if (is_array($jobdata) && !empty($jobdata['resume_interval']) && (empty($jobdata['jobstatus']) || 'finished' != $jobdata['jobstatus'])) {
if ($simple_format) {
$ret .= ' '.__('(Not finished)', 'updraftplus');
} else {
$ret .= apply_filters('updraftplus_msg_unfinishedbackup', "
".__('(Not finished)', 'updraftplus').'', $jobdata, $nonce);
}
}
return $ret;
}
public function download_button($type, $backup_timestamp, $findex, $info, $title, $pdescrip, $esc_pretty_date, $set_contents) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Filter use
$ret = '';
$wp_nonce = wp_create_nonce('updraftplus_download');
// updraft_downloader(base, backup_timestamp, what, whicharea, set_contents, prettydate, async)
$ret .= '';
// onclick="'."return updraft_downloader('uddlstatus_', '$backup_timestamp', '$type', '.ud_downloadstatus', '$set_contents', '$esc_pretty_date', true)".'"
return $ret;
}
public function restore_button($backup, $key, $pretty_date, $entities = '') {
$ret = '';
if ($entities) {
$show_data = $pretty_date;
if (isset($backup['native']) && false == $backup['native']) {
$show_data .= ' '.__('(backup set imported from remote location)', 'updraftplus');
}
$ret .= '';
}
$ret .= "
\n";
return $ret;
}
/**
* Get HTML for the 'Upload' button for a particular backup in the 'Existing backups' tab
*
* @param Integer $backup_time - backup timestamp (epoch time)
* @param String $nonce - backup nonce
* @param Array $backup - backup information array
* @param Null|Array $jobdata - if not null, then use as the job data instead of fetching
*
* @return String - the resulting HTML
*/
public function upload_button($backup_time, $nonce, $backup, $jobdata = null) {
global $updraftplus;
// Check the job is not still running.
if (null === $jobdata) $jobdata = $updraftplus->jobdata_getarray($nonce);
if (!empty($jobdata) && 'finished' != $jobdata['jobstatus']) return '';
// Check that the user has remote storage setup.
$services = (array) $updraftplus->just_one($updraftplus->get_canonical_service_list());
if (empty($services)) return '';
$show_upload = false;
// Check that the backup has not already been sent to remote storage before.
if (empty($backup['service']) || array('none') == $backup['service'] || array('') == $backup['service'] || 'none' == $backup['service']) {
$show_upload = true;
// If it has been uploaded then check if there are any new remote storage options that it has not yet been sent to.
} elseif (!empty($backup['service']) && array('none') != $backup['service'] && array('') != $backup['service'] && 'none' != $backup['service']) {
foreach ($services as $key => $value) {
if (is_string($backup['service'])) $backup['service'] = array($backup['service']);
if (in_array($value, $backup['service'])) unset($services[$key]);
}
if (!empty($services)) $show_upload = true;
}
if ($show_upload) {
$backup_local = $this->check_backup_is_complete($backup, false, false, true);
if ($backup_local) {
$service_list = '';
$service_list_display = '';
$is_first_service = true;
foreach ($services as $key => $service) {
if (!$is_first_service) {
$service_list .= ',';
$service_list_display .= ', ';
}
$service_list .= $service;
$service_list_display .= $updraftplus->backup_methods[$service];
$is_first_service = false;
}
return '
';
}
return '';
}
}
/**
* Get HTML for the 'Delete' button for a particular backup in the 'Existing backups' tab
*
* @param Integer $backup_time - backup timestamp (epoch time)
* @param String $nonce - backup nonce
* @param Array $backup - backup information array
*
* @return String - the resulting HTML
*/
public function delete_button($backup_time, $nonce, $backup) {
$sval = (!empty($backup['service']) && 'email' != $backup['service'] && 'none' != $backup['service'] && array('email') !== $backup['service'] && array('none') !== $backup['service'] && array('remotesend') !== $backup['service']) ? '1' : '0';
return '';
}
public function log_button($backup) {
global $updraftplus;
$updraft_dir = $updraftplus->backups_dir_location();
$ret = '';
if (isset($backup['nonce']) && preg_match("/^[0-9a-f]{12}$/", $backup['nonce']) && is_readable($updraft_dir.'/log.'.$backup['nonce'].'.txt')) {
$nval = $backup['nonce'];
$lt = __('View Log', 'updraftplus');
$url = esc_attr(UpdraftPlus_Options::admin_page()."?page=updraftplus&action=downloadlog&updraftplus_backup_nonce=$nval");
$ret .= <<
$lt
ENDHERE;
return $ret;
}
}
/**
* This function will check that a backup is complete depending on the parameters passed in.
* A backup is complete in the case of a "clone" if it contains a db, plugins, themes, uploads and others.
* A backup is complete in the case of a "full backup" when it contains everything the user has set in their options to be backed up.
* It can also check if the backup is local on the filesystem.
*
* @param array $backup - the backup array we want to check
* @param boolean $full_backup - a boolean to indicate if the backup should also be a full backup
* @param boolean $clone - a boolean to indicate if the backup is for a clone, if so it does not need to be a full backup it only needs to include everything a clone can restore
* @param boolean $local - a boolean to indicate if the backup should be present on the local file system or not
*
* @return boolean - returns true if the backup is complete and if specified is found on the local system otherwise false
*/
public function check_backup_is_complete($backup, $full_backup, $clone, $local) {
global $updraftplus;
if (empty($backup)) return false;
if ($clone) {
$entities = array('db' => '', 'plugins' => '', 'themes' => '', 'uploads' => '', 'others' => '');
} else {
$entities = $updraftplus->get_backupable_file_entities(true, true);
// Add the database to the entities array ready to loop over
$entities['db'] = '';
foreach ($entities as $key => $info) {
if (!UpdraftPlus_Options::get_updraft_option("updraft_include_$key", false)) {
unset($entities[$key]);
}
}
}
$updraft_dir = trailingslashit($updraftplus->backups_dir_location());
foreach ($entities as $type => $info) {
if ($full_backup) {
if (UpdraftPlus_Options::get_updraft_option("updraft_include_$type", false) && !isset($backup[$type])) return false;
}
if (!isset($backup[$type])) return false;
if ($local) {
// Cast this to an array so that a warning is not thrown when we encounter a Database.
foreach ((array) $backup[$type] as $value) {
if (!file_exists($updraft_dir . DIRECTORY_SEPARATOR . $value)) return false;
}
}
}
return true;
}
/**
* This function will set up the backup job data for when we are uploading a local backup to remote storage. It changes the initial jobdata so that UpdraftPlus knows about what files it's uploading and so that it skips directly to the upload stage.
*
* @param array $jobdata - the initial job data that we want to change
* @param array $options - options sent from the front end includes backup timestamp and nonce
*
* @return array - the modified jobdata
*/
public function upload_local_backup_jobdata($jobdata, $options) {
global $updraftplus;
if (!is_array($jobdata)) return $jobdata;
$backup_history = UpdraftPlus_Backup_History::get_history();
$services = !empty($options['services']) ? $options['services'] : array();
$backup = $backup_history[$options['use_timestamp']];
/*
The initial job data is not set up in a key value array instead it is set up so key "x" is the name of the key and then key "y" is the value.
e.g array[0] = 'backup_name' array[1] = 'my_backup'
*/
$jobstatus_key = array_search('jobstatus', $jobdata) + 1;
$backup_time_key = array_search('backup_time', $jobdata) + 1;
$backup_database_key = array_search('backup_database', $jobdata) + 1;
$backup_files_key = array_search('backup_files', $jobdata) + 1;
$service_key = array_search('service', $jobdata) + 1;
$db_backups = $jobdata[$backup_database_key];
$db_backup_info = $updraftplus->update_database_jobdata($db_backups, $backup);
$file_backups = $updraftplus->update_files_jobdata($backup);
// Next we need to build the services array using the remote storage destinations the user has selected to upload this backup set to
$selected_services = array();
if (is_array($services)) {
foreach ($services as $storage_info) {
$selected_services[] = $storage_info['value'];
}
} else {
$selected_services = array($services);
}
$jobdata[$jobstatus_key] = 'clouduploading';
$jobdata[$backup_time_key] = $options['use_timestamp'];
$jobdata[$backup_files_key] = 'finished';
$jobdata[] = 'backup_files_array';
$jobdata[] = $file_backups;
$jobdata[] = 'blog_name';
$jobdata[] = $db_backup_info['blog_name'];
$jobdata[$backup_database_key] = $db_backup_info['db_backups'];
$jobdata[] = 'local_upload';
$jobdata[] = true;
if (!empty($selected_services)) $jobdata[$service_key] = $selected_services;
return $jobdata;
}
/**
* This function allows us to change the backup name, this is needed when uploading a local database backup to remote storage when the backup has come from another site.
*
* @param string $backup_name - the current name of the backup file
* @param string $use_time - the current timestamp we are using
* @param string $blog_name - the blog name of the current site
*
* @return string - the new filename or the original if the blog name from the job data is not set
*/
public function upload_local_backup_name($backup_name, $use_time, $blog_name) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Filter use
global $updraftplus;
$backup_blog_name = $updraftplus->jobdata_get('blog_name', '');
if ('' != $blog_name && '' != $backup_blog_name) {
return str_replace($blog_name, $backup_blog_name, $backup_name);
}
return $backup_name;
}
/**
* This function starts the updraftplus restore process it processes $_REQUEST
* (keys: updraft_*, meta_foreign, backup_timestamp and job_id)
*
* @return void
*/
public function prepare_restore() {
global $updraftplus;
// on restore start job_id is empty but if we needed file system permissions or this is a resumption then we have already started a job so reuse it
$restore_job_id = empty($_REQUEST['job_id']) ? false : stripslashes($_REQUEST['job_id']);
if (false !== $restore_job_id && !preg_match('/^[0-9a-f]+$/', $restore_job_id)) die('Invalid request (restore_job_id).');
if (isset($_REQUEST['action']) && 'updraft_ajaxrestore_continue' === $_REQUEST['action']) { // unlike updraft_ajaxrestore which requires nonce to start a new restoration, updraft_ajaxrestore_continue doesn't require nonces at all so additional checks are required
$restore_in_progress = get_site_option('updraft_restore_in_progress');
if (empty($restore_in_progress) || !$restore_job_id || $restore_job_id !== $restore_in_progress) die; // continuation requires a job ID, and if it's not presented then just abort without showing anything
}
// Set up nonces, log files etc.
$updraftplus->initiate_restore_job($restore_job_id);
// If this is the start of a restore then get the restore data from the posted data and put it into jobdata.
if (isset($_REQUEST['action']) && 'updraft_restore' == $_REQUEST['action']) {
if (empty($restore_job_id)) {
$jobdata_to_save = array();
foreach ($_REQUEST as $key => $value) {
if (false !== strpos($key, 'updraft_') || 'backup_timestamp' == $key || 'meta_foreign' == $key) {
if ('updraft_restorer_restore_options' == $key) parse_str(stripslashes($value), $value);
$jobdata_to_save[$key] = $value;
}
}
$selective_restore_types = array(
'tables',
'plugins',
'themes',
);
foreach ($selective_restore_types as $type) {
if (isset($jobdata_to_save['updraft_restorer_restore_options']['updraft_restore_'.$type.'_options']) && !empty($jobdata_to_save['updraft_restorer_restore_options']['updraft_restore_'.$type.'_options'])) {
$restore_entities_options = $jobdata_to_save['updraft_restorer_restore_options']['updraft_restore_'.$type.'_options'];
$include_unspecified_entities = false;
$entities_to_restore = array();
$entities_to_skip = array();
foreach ($restore_entities_options as $entity) {
if ('udp_all_other_'.$type == $entity) {
$include_unspecified_entities = true;
} elseif (substr($entity, 0, strlen('udp-skip-'.$type)) == 'udp-skip-'.$type) {
$entities_to_skip[] = substr($entity, strlen('udp-skip-'.$type) + 1);
} else {
$entities_to_restore[] = $entity;
}
}
$jobdata_to_save['updraft_restorer_restore_options']['include_unspecified_'.$type] = $include_unspecified_entities;
$jobdata_to_save['updraft_restorer_restore_options'][$type.'_to_restore'] = $entities_to_restore;
$jobdata_to_save['updraft_restorer_restore_options'][$type.'_to_skip'] = $entities_to_skip;
unset($jobdata_to_save['updraft_restorer_restore_options']['updraft_restore_'.$type.'_options']);
}
}
$updraftplus->jobdata_set_multi($jobdata_to_save);
// 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);
}
}
// If this is the start of an ajax restore then end execution here so it can then be booted over ajax
if (isset($_REQUEST['updraftplus_ajax_restore']) && 'start_ajax_restore' == $_REQUEST['updraftplus_ajax_restore']) {
// return to prevent any more code from running
return $this->prepare_ajax_restore();
} elseif (isset($_REQUEST['updraftplus_ajax_restore']) && 'continue_ajax_restore' == $_REQUEST['updraftplus_ajax_restore']) {
// If we enter here then in order to restore we needed to require the filesystem credentials we should save these before returning back to the browser and load them back after the AJAX call, this prevents us asking for the filesystem credentials again
$filesystem_credentials = array(
'hostname' => '',
'username' => '',
'password' => '',
'connection_type' => '',
'upgrade' => '',
);
$credentials_found = false;
foreach ($_REQUEST as $key => $value) {
if (array_key_exists($key, $filesystem_credentials)) {
$filesystem_credentials[$key] = stripslashes($value);
$credentials_found = true;
}
}
if ($credentials_found) $updraftplus->jobdata_set('filesystem_credentials', $filesystem_credentials);
// return to prevent any more code from running
return $this->prepare_ajax_restore();
}
if (!empty($_REQUEST['updraftplus_ajax_restore'])) add_filter('updraftplus_logline', array($this, 'updraftplus_logline'), 10, 5);
$is_continuation = ('updraft_ajaxrestore_continue' == $_REQUEST['action']) ? true : false;
if ($is_continuation) {
$restore_in_progress = get_site_option('updraft_restore_in_progress');
if ($restore_in_progress != $_REQUEST['job_id']) {
$abort_restore_already = true;
$updraftplus->log(__('Sufficient information about the in-progress restoration operation could not be found.', 'updraftplus') . ' (job_id_mismatch)', 'error', 'job_id_mismatch');
} else {
$restore_jobdata = $updraftplus->jobdata_getarray($restore_in_progress);
if (is_array($restore_jobdata) && isset($restore_jobdata['job_type']) && 'restore' == $restore_jobdata['job_type'] && isset($restore_jobdata['second_loop_entities']) && !empty($restore_jobdata['second_loop_entities']) && isset($restore_jobdata['job_time_ms']) && isset($restore_jobdata['backup_timestamp'])) {
$backup_timestamp = $restore_jobdata['backup_timestamp'];
$continuation_data = $restore_jobdata;
$continuation_data['updraftplus_ajax_restore'] = 'continue_ajax_restore';
} else {
$abort_restore_already = true;
$updraftplus->log(__('Sufficient information about the in-progress restoration operation could not be found.', 'updraftplus') . ' (job_id_nojobdata)', 'error', 'job_id_nojobdata');
}
}
} elseif (isset($_REQUEST['updraftplus_ajax_restore']) && 'do_ajax_restore' == $_REQUEST['updraftplus_ajax_restore']) {
$backup_timestamp = $updraftplus->jobdata_get('backup_timestamp');
$continuation_data = array('updraftplus_ajax_restore' => 'do_ajax_restore');
} else {
$backup_timestamp = (int) $_REQUEST['backup_timestamp'];
$continuation_data = null;
}
$filesystem_credentials = $updraftplus->jobdata_get('filesystem_credentials', array());
if (!empty($filesystem_credentials)) {
$continuation_data['updraftplus_ajax_restore'] = 'continue_ajax_restore';
// If the filesystem credentials are not empty then we now need to load these back into $_POST so that WP_Filesystem can access them
foreach ($filesystem_credentials as $key => $value) {
$_POST[$key] = $value;
}
}
if (empty($abort_restore_already)) {
$backup_success = $this->restore_backup($backup_timestamp, $continuation_data);
} else {
$backup_success = false;
}
if (empty($updraftplus->errors) && true === $backup_success) {
// TODO: Deal with the case of some of the work having been deferred
echo '';
$updraftplus->log_e('Restore successful!');
echo '
';
$updraftplus->log('Restore successful');
$s_val = 1;
if (!empty($this->entities_to_restore) && is_array($this->entities_to_restore)) {
foreach ($this->entities_to_restore as $v) {
if ('db' != $v) $s_val = 2;
}
}
$pval = $updraftplus->have_addons ? 1 : 0;
echo '' . __('Actions', 'updraftplus') . ': ' . __('Return to UpdraftPlus configuration', 'updraftplus') . '';
return;
} elseif (is_wp_error($backup_success)) {
echo '';
$updraftplus->log_e('Restore failed...');
echo '
';
$updraftplus->log_wp_error($backup_success);
$updraftplus->log('Restore failed');
echo '';
$updraftplus->list_errors();
echo '
';
echo '' . __('Actions', 'updraftplus') . ': ' . __('Return to UpdraftPlus configuration', 'updraftplus') . '';
return;
} elseif (false === $backup_success) {
// This means, "not yet - but stay on the page because we may be able to do it later, e.g. if the user types in the requested information"
echo '';
$updraftplus->log_e('Restore failed...');
echo '
';
$updraftplus->log("Restore failed");
echo '';
$updraftplus->list_errors();
echo '
';
echo '' . __('Actions', 'updraftplus') . ': ' . __('Return to UpdraftPlus configuration', 'updraftplus') . '';
return;
}
}
/**
* This function will load the required ajax and output any relevant html for the ajax restore
*
* @return void
*/
private function prepare_ajax_restore() {
global $updraftplus;
$debug = $updraftplus->use_unminified_scripts();
$enqueue_version = $debug ? $updraftplus->version . '.' . time() : $updraftplus->version;
$updraft_min_or_not = $updraftplus->get_updraftplus_file_version();
$ajax_action = isset($_REQUEST['updraftplus_ajax_restore']) && 'continue_ajax_restore' == $_REQUEST['updraftplus_ajax_restore'] && 'updraft_restore' != $_REQUEST['action'] ? 'updraft_ajaxrestore_continue' : 'updraft_ajaxrestore';
// get the entities info
$jobdata = $updraftplus->jobdata_getarray($updraftplus->nonce);
$restore_components = $jobdata['updraft_restore'];
usort($restore_components, array('UpdraftPlus_Manipulation_Functions', 'sort_restoration_entities'));
$backupable_entities = $updraftplus->get_backupable_file_entities(true, true);
$pretty_date = get_date_from_gmt(gmdate('Y-m-d H:i:s', (int) $jobdata['backup_timestamp']), 'M d, Y G:i');
wp_enqueue_script('updraft-admin-restore', UPDRAFTPLUS_URL . '/js/updraft-admin-restore' . $updraft_min_or_not . '.js', array(), $enqueue_version);
$updraftplus->log("Restore setup, now closing connection and starting restore over AJAX.");
echo '';
echo '
';
echo '
'. __('Warning: If you can still read these words after the page finishes loading, then there is a JavaScript or jQuery problem in the site.', 'updraftplus') .' '.__('This may prevent the restore procedure from being able to proceed.', 'updraftplus').'';
echo ' '. __('Go here for more information.', 'updraftplus') .'
';
echo '
';
echo '
'.__('UpdraftPlus Restoration', 'updraftplus').' - '.__('Backup', 'updraftplus').' '.$pretty_date.'
';
echo '
';
if ($debug) echo '
';
echo '
';
echo '
';
echo '
';
echo '
';
echo '
'.sprintf(__('The restore operation has begun (%s).', 'updraftplus'), htmlspecialchars($updraftplus->nonce)).' '.__('Do not close this page until it reports itself as having finished.', 'updraftplus').'
';
echo '
'.__('Restoration progress:', 'updraftplus').'
';
echo '
';
echo '
';
echo ' - '.__('Verifying', 'updraftplus').'
';
foreach ($restore_components as $restore_component) {
// Set Database description
if ('db' == $restore_component && !isset($backupable_entities[$restore_component]['description'])) $backupable_entities[$restore_component]['description'] = __('Database', 'updraftplus');
if (!isset($backupable_entities[$restore_component])) {
die('Abort: invalid data');
}
echo ' - '.(isset($backupable_entities[$restore_component]['description']) ? htmlspecialchars($backupable_entities[$restore_component]['description']) : htmlspecialchars($restore_component)).'
';
}
echo ' - '.__('Cleaning', 'updraftplus').'
';
echo ' - '.__('Finished', 'updraftplus').'
';
echo '
'; // end ul.updraft_restore_components_list
// Provide download link for the log file
echo '
'.__('Follow this link to download the log file for this restoration (needed for any support requests).', 'updraftplus').'
';
echo '
'; // end .updraft_restore_main--components
echo '
';
echo '
'.__('Activity log', 'updraftplus').'
';
echo '
';
echo '
'; // end .updraft_restore_main--activity
echo '
';
echo '
'; // end .updraft_restore_main
echo '
'; // 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) {
global $updraftplus;
$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;
} 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';
}
}
}
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() {
global $updraftplus;
$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'];
return $restore_options;
}
/**
* 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 ''.__('This backup does not exist in the backup history - restoration aborted.', 'updraftplus').' '.__('Timestamp:', 'updraftplus').' '.htmlspecialchars($timestamp).'
';
return new WP_Error('does_not_exist', __('Backup does not exist in the backup history', 'updraftplus')." ($timestamp)");
}
$backup_set['timestamp'] = $timestamp;
$url_parameters = array(
'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 -- Silenced to suppress errors that may arise because of the function.
// Turn off PHP output buffering for NGINX
header('X-Accel-Buffering: no');
header('Content-Encoding: none');
while (ob_get_level()) {
ob_end_flush();
}
ob_implicit_flush(1);
}
$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 ''.__('ABORT: Could not find the information on which entities to restore.', 'updraftplus').'
'.__('If making a request for support, please include this information:', 'updraftplus').' '.count($restore_jobdata).' : '.htmlspecialchars(serialize($restore_jobdata)).'
';
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'];
} else {
// 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));
$page_urls = array(
'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(
array(
'type' => 'state',
'stage' => 'finished',
'data' => array(
'actions' => array(
__('Return to UpdraftPlus configuration', 'updraftplus') => UpdraftPlus_Options::admin_page_url() . '?page=updraftplus&updraft_restore_success=' . $sval . '&pval=' . $pval
),
'urls' => $page_urls,
)
)
);
return $restore_result;
}
/**
* Called when the restore process wants to print a title
*
* @param String $title - title
*/
public function restoration_title($title) {
echo ''.$title.'
';
}
/**
* 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 -- Unused parameter is present because the method is used as a WP filter.
if ('progress' != $destination || (defined('WP_CLI') && WP_CLI) || false === $line || false === strpos($line, 'RINFO:')) return $line;
global $updraftplus;
$updraftplus->output_to_browser($line);
// Indicate that we have completely handled all logging needed
return false;
}
/**
* Ensure that what is returned is an array. Used as a WP options filter.
*
* @param Array $input - input
*
* @return Array
*/
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() {
try {
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));
} catch (Exception $e) {
$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().')';
error_log($log_message);
echo json_encode(array(
'fatal_error' => true,
'fatal_error_message' => $log_message
));
// @codingStandardsIgnoreLine
} catch (Error $e) {
$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().')';
error_log($log_message);
echo json_encode(array(
'fatal_error' => true,
'fatal_error_message' => $log_message
));
}
die;
}
public function updraft_ajax_importsettings() {
try {
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);
} catch (Exception $e) {
$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().')';
error_log($log_message);
echo json_encode(array(
'fatal_error' => true,
'fatal_error_message' => $log_message
));
// @codingStandardsIgnoreLine
} catch (Error $e) {
$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().')';
error_log($log_message);
echo json_encode(array(
'fatal_error' => true,
'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);
} else {
$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));
die;
}
/**
* 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() {
global $updraftplus;
$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 .= '
';
}
} elseif (empty($sinfo['object']) || empty($sinfo['instance_settings']) || !is_callable(array($sinfo['object'], 'options_exist'))) {
continue;
}
$instance_count = 1;
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 .= '
';
$instance_count++;
}
}
}
$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 ' '.sprintf(__("Backup won't be sent to any remote storage - none has been saved in the %s", 'updraftplus'), ''.__('settings', 'updraftplus')).'. '.__('Not got any remote storage?', 'updraftplus').' '.__("Check out UpdraftVault.", 'updraftplus').'';
}
if (empty($active_remote_storage_list)) {
$active_remote_storage_list = ''.__('No remote storage locations with valid options found.', 'updraftplus').'
';
}
return ' (...)
'. __('The following remote storage options are configured.', 'updraftplus').'
'.$active_remote_storage_list.'
';
}
/**
* 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) {
global $updraftplus;
// 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);
$mass_updated = true;
}
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)
if ($updated) {
$value = UpdraftPlus_Options::get_updraft_option($key);
if (is_string($value)) $return_array['changed'][$key] = $value;
}
// @codingStandardsIgnoreLine
} else {
// 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);
}
}
} else {
$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.', 'updraftplus'), $updraftplus->version).' '.__('Please reload the settings page before trying to save settings.', 'updraftplus'));
}
// 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
ob_start();
$service = UpdraftPlus_Options::get_updraft_option('updraft_service');
$this->setup_all_admin_notices_global($service);
$this->setup_all_admin_notices_udonly($service);
do_action('all_admin_notices');
if (!$really_is_writable) { // Check if writable
$this->show_admin_warning_unwritable();
}
if ($return_array['saved']) { //
$this->show_admin_warning(__('Your settings have been saved.', 'updraftplus'), 'updated fade');
} else {
if (isset($return_array['error_message'])) {
$this->show_admin_warning($return_array['error_message'], 'error');
} else {
$this->show_admin_warning(__('Your settings failed to save.', 'updraftplus').' '.__('Please refresh the settings page and try again', 'updraftplus'), 'error');
}
}
$messages_output = ob_get_contents();
ob_clean();
// Backup schedule output
$this->next_scheduled_backups_output('line');
$scheduled_output = ob_get_clean();
$return_array['messages'] = $messages_output;
$return_array['scheduled'] = $scheduled_output;
$return_array['files_scheduled'] = $this->next_scheduled_files_backups_output(true);
$return_array['database_scheduled'] = $this->next_scheduled_database_backups_output(true);
// Add the updated options to the return message, so we can update on screen
return $return_array;
}
/**
* Authenticate remote storage instance
*
* @param array - $data It consists of below key elements:
* $remote_method - Remote storage service
* $instance_id - Remote storage instance id
* @return array An array response containing the status of the authentication
*/
public function auth_remote_method($data) {
global $updraftplus;
$response = array();
if (isset($data['remote_method']) && isset($data['instance_id'])) {
$response['result'] = 'success';
$remote_method = $data['remote_method'];
$instance_id = $data['instance_id'];
$storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array($remote_method));
try {
$storage_objects_and_ids[$remote_method]['object']->authenticate_storage($instance_id);
} catch (Exception $e) {
$response['result'] = 'error';
$response['message'] = $updraftplus->backup_methods[$remote_method] . ' ' . __('authentication error', 'updraftplus') . ' ' . $e->getMessage();
}
} else {
$response['result'] = 'error';
$response['message'] = __('Remote storage method and instance id are required for authentication.', 'updraftplus');
}
return $response;
}
/**
* Deauthenticate remote storage instance
*
* @param array - $data It consists of below key elements:
* $remote_method - Remote storage service
* $instance_id - Remote storage instance id
* @return array An array response containing the status of the deauthentication
*/
public function deauth_remote_method($data) {
global $updraftplus;
$response = array();
if (isset($data['remote_method']) && isset($data['instance_id'])) {
$response['result'] = 'success';
$remote_method = $data['remote_method'];
$instance_id = $data['instance_id'];
$storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array($remote_method));
try {
$storage_objects_and_ids[$remote_method]['object']->deauthenticate_storage($instance_id);
} catch (Exception $e) {
$response['result'] = 'error';
$response['message'] = $updraftplus->backup_methods[$remote_method] . ' deauthentication error ' . $e->getMessage();
}
} else {
$response['result'] = 'error';
$response['message'] = 'Remote storage method and instance id are required for deauthentication.';
}
return $response;
}
/**
* A method to remove UpdraftPlus settings from the options table.
*
* @param boolean $wipe_all_settings Set to true as default as we want to remove all options, set to false if calling from UpdraftCentral, as we do not want to remove the UpdraftCentral key or we will lose connection to the site.
* @return boolean
*/
public function wipe_settings($wipe_all_settings = true) {
global $updraftplus;
$settings = $updraftplus->get_settings_keys();
// if this is false the UDC has called it we don't want to remove the UDC key other wise we will lose connection to the remote site.
if (false == $wipe_all_settings) {
$key = array_search('updraft_central_localkeys', $settings);
unset($settings[$key]);
}
foreach ($settings as $s) UpdraftPlus_Options::delete_updraft_option($s);
if (is_multisite()) $updraftplus->wipe_state_data(true, 'sitemeta');
$updraftplus->wipe_state_data(true);
$site_options = array('updraft_oneshotnonce');
foreach ($site_options as $s) delete_site_option($s);
$updraftplus->schedule_backup('manual');
$updraftplus->schedule_backup_database('manual');
$this->show_admin_warning(__("Your settings have been wiped.", 'updraftplus'));
return true;
}
/**
* This get the details for updraft vault and to be used globally
*
* @param string $instance_id - the instance_id of the current instance being used
* @return object - the UpdraftVault option setup to use the passed in instance id or if one wasn't passed then use the default set of options
*/
public function get_updraftvault($instance_id = '') {
$storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array('updraftvault'));
if (isset($storage_objects_and_ids['updraftvault']['instance_settings'][$instance_id])) {
$opts = $storage_objects_and_ids['updraftvault']['instance_settings'][$instance_id];
$vault = $storage_objects_and_ids['updraftvault']['object'];
$vault->set_options($opts, false, $instance_id);
} else {
updraft_try_include_file('methods/updraftvault.php', 'include_once');
$vault = new UpdraftPlus_BackupModule_updraftvault();
}
return $vault;
}
/**
* http_get will allow the HTTP Fetch execute available in advanced tools
*
* @param String $uri Specific URL passed to curl
* @param Boolean $curl True or False if cURL is to be used
* @return String - JSON encoded results
*/
public function http_get($uri = null, $curl = false) {
if (!preg_match('/^https?/', $uri)) return json_encode(array('e' => 'Non-http URL specified'));
if ($curl) {
if (!function_exists('curl_exec')) {
return json_encode(array('e' => 'No Curl installed'));
die;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $uri);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FAILONERROR, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_STDERR, $output = fopen('php://temp', "w+"));
$response = curl_exec($ch);
$error = curl_error($ch);
$getinfo = curl_getinfo($ch);
curl_close($ch);
rewind($output);
$verb = stream_get_contents($output);
$resp = array();
if (false === $response) {
$resp['e'] = htmlspecialchars($error);
}
$resp['r'] = (empty($response)) ? '' : htmlspecialchars(substr($response, 0, 2048));
if (!empty($verb)) $resp['r'] = htmlspecialchars($verb)."\n\n".$resp['r'];
// Extra info returned for Central
$resp['verb'] = $verb;
$resp['response'] = $response;
$resp['status'] = $getinfo;
return json_encode($resp);
} else {
$response = wp_remote_get($uri, array('timeout' => 10));
if (is_wp_error($response)) {
return json_encode(array('e' => htmlspecialchars($response->get_error_message())));
}
return json_encode(
array(
'r' => wp_remote_retrieve_response_code($response).': '.htmlspecialchars(substr(wp_remote_retrieve_body($response), 0, 2048)),
'code' => wp_remote_retrieve_response_code($response),
'html_response' => htmlspecialchars(substr(wp_remote_retrieve_body($response), 0, 2048)),
'response' => $response
)
);
}
}
/**
* This will return all the details for raw backup and file list, in HTML format
*
* @param Boolean $no_pre_tags - if set, then tags will be removed from the output
*
* @return String
*/
public function show_raw_backups($no_pre_tags = false) {
global $updraftplus;
$response = array();
$response['html'] = ''.__('Known backups (raw)', 'updraftplus').'
';
ob_start();
$history = UpdraftPlus_Backup_History::get_history();
var_dump($history);
$response["html"] .= ob_get_clean();
$response['html'] .= '
';
$response['html'] .= ''.__('Files', 'updraftplus').'
';
$updraft_dir = $updraftplus->backups_dir_location();
$raw_output = array();
$d = dir($updraft_dir);
while (false !== ($entry = $d->read())) {
$fp = $updraft_dir.'/'.$entry;
$mtime = filemtime($fp);
if (is_dir($fp)) {
$size = ' d';
} elseif (is_link($fp)) {
$size = ' l';
} elseif (is_file($fp)) {
$size = sprintf("%8.1f", round(filesize($fp)/1024, 1)).' '.gmdate('r', $mtime);
} else {
$size = ' ?';
}
if (preg_match('/^log\.(.*)\.txt$/', $entry, $lmatch)) $entry = ''.$entry.'';
$raw_output[$mtime] = empty($raw_output[$mtime]) ? sprintf("%s %s\n", $size, $entry) : $raw_output[$mtime].sprintf("%s %s\n", $size, $entry);
}
@$d->close();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
krsort($raw_output, SORT_NUMERIC);
foreach ($raw_output as $line) {
$response['html'] .= $line;
}
$response['html'] .= '
';
$response['html'] .= ''.__('Options (raw)', 'updraftplus').'
';
$opts = $updraftplus->get_settings_keys();
asort($opts);
// '.__('Key', 'updraftplus').' | '.__('Value', 'updraftplus').' |
$response['html'] .= '';
foreach ($opts as $opt) {
$response['html'] .= ''.htmlspecialchars($opt).' | '.htmlspecialchars(print_r(UpdraftPlus_Options::get_updraft_option($opt), true)).' | ';
}
// Get the option saved by yahnis-elsts/plugin-update-checker
$response['html'] .= '
external_updates-updraftplus | '.htmlspecialchars(print_r(get_site_option('external_updates-updraftplus'), true)).' | ';
$response['html'] .= '
';
ob_start();
do_action('updraftplus_showrawinfo');
$response['html'] .= ob_get_clean();
if (true == $no_pre_tags) {
$response['html'] = str_replace('', '', $response['html']);
$response['html'] = str_replace('
', '', $response['html']);
}
return $response;
}
/**
* This will call any wp_action
*
* @param Array|Null $data The array of data with the values for wpaction
* @param Callable|Boolean $close_connection_callable A callable to call to close the browser connection, or true for a default suitable for internal use, or false for none
* @return Array - results
*/
public function call_wp_action($data = null, $close_connection_callable = false) {
global $updraftplus;
ob_start();
$res = 'Request received: ';
if (preg_match('/^([^:]+)+:(.*)$/', $data['wpaction'], $matches)) {
$action = $matches[1];
if (null === ($args = json_decode($matches[2], true))) {
$res .= "The parameters (should be JSON) could not be decoded";
$action = false;
} else {
if (is_string($args)) $args = array($args);
$res .= "Will despatch action: ".htmlspecialchars($action).", parameters: ".htmlspecialchars(implode(',', $args));
}
} else {
$action = $data['wpaction'];
$res .= "Will despatch action: ".htmlspecialchars($action).", no parameters";
}
ob_get_clean();
// Need to add this as the close browser should only work for UDP
if ($close_connection_callable) {
if (is_callable($close_connection_callable)) {
call_user_func($close_connection_callable, array('r' => $res));
} else {
$updraftplus->close_browser_connection(json_encode(array('r' => $res)));
}
}
if (!empty($action)) {
if (!empty($args)) {
ob_start();
$returned = do_action_ref_array($action, $args);
$output = ob_get_clean();
$res .= " - do_action_ref_array Trigger ";
} else {
ob_start();
do_action($action);
$output = ob_get_contents();
ob_end_clean();
$res .= " - do_action Trigger ";
}
}
$response = array();
$response['response'] = $res;
$response['log'] = $output;
// Check if response is empty
if (!empty($returned)) $response['status'] = $returned;
return $response;
}
/**
* Enqueue JSTree JavaScript and CSS, taking into account whether it is already enqueued, and current debug settings
*/
public function enqueue_jstree() {
global $updraftplus;
static $already_enqueued = false;
if ($already_enqueued) return;
$already_enqueued = true;
$jstree_enqueue_version = $updraftplus->use_unminified_scripts() ? '3.3.12-rc.0'.'.'.time() : '3.3.12-rc.0';
$min_or_not = $updraftplus->use_unminified_scripts() ? '' : '.min';
wp_enqueue_script('jstree', UPDRAFTPLUS_URL.'/includes/jstree/jstree'.$min_or_not.'.js', array('jquery'), $jstree_enqueue_version);
wp_enqueue_style('jstree', UPDRAFTPLUS_URL.'/includes/jstree/themes/default/style'.$min_or_not.'.css', array(), $jstree_enqueue_version);
}
/**
* Detects byte-order mark at the start of common files and change waning message texts
*
* @return string|boolean BOM warning text or false if not bom characters detected
*/
public function get_bom_warning_text() {
$files_to_check = array(
ABSPATH.'wp-config.php',
get_template_directory().DIRECTORY_SEPARATOR.'functions.php',
);
if (is_child_theme()) {
$files_to_check[] = get_stylesheet_directory().DIRECTORY_SEPARATOR.'functions.php';
}
$corrupted_files = array();
foreach ($files_to_check as $file) {
if (!file_exists($file)) continue;
if (false === ($fp = fopen($file, 'r'))) continue;
if (false === ($file_data = fread($fp, 8192)));
fclose($fp);
$substr_file_data = array();
for ($substr_length = 2; $substr_length <= 5; $substr_length++) {
$substr_file_data[$substr_length] = substr($file_data, 0, $substr_length);
}
// Detect UTF-7, UTF-8, UTF-16 (BE), UTF-16 (LE), UTF-32 (BE) & UTF-32 (LE) Byte order marks (BOM)
$bom_decimal_representations = array(
array(43, 47, 118, 56), // UTF-7 (Hexadecimal: 2B 2F 76 38)
array(43, 47, 118, 57), // UTF-7 (Hexadecimal: 2B 2F 76 39)
array(43, 47, 118, 43), // UTF-7 (Hexadecimal: 2B 2F 76 2B)
array(43, 47, 118, 47), // UTF-7 (Hexadecimal: 2B 2F 76 2F)
array(43, 47, 118, 56, 45), // UTF-7 (Hexadecimal: 2B 2F 76 38 2D)
array(239, 187, 191), // UTF-8 (Hexadecimal: 2B 2F 76 38 2D)
array(254, 255), // UTF-16 (BE) (Hexadecimal: FE FF)
array(255, 254), // UTF-16 (LE) (Hexadecimal: FF FE)
array(0, 0, 254, 255), // UTF-32 (BE) (Hexadecimal: 00 00 FE FF)
array(255, 254, 0, 0), // UTF-32 (LE) (Hexadecimal: FF FE 00 00)
);
foreach ($bom_decimal_representations as $bom_decimal_representation) {
$no_of_chars = count($bom_decimal_representation);
array_unshift($bom_decimal_representation, 'C*');
$binary = call_user_func_array('pack', $bom_decimal_representation);
if ($binary == $substr_file_data[$no_of_chars]) {
$corrupted_files[] = $file;
break;
}
}
}
if (empty($corrupted_files)) {
return false;
} else {
$corrupted_files_count = count($corrupted_files);
return ''.__('Warning', 'updraftplus').': '.sprintf(_n('The file %s has a "byte order mark" (BOM) at its beginning.', 'The files %s have a "byte order mark" (BOM) at their beginning.', $corrupted_files_count, 'updraftplus'), ''.implode(', ', $corrupted_files).'').' '.__('Follow this link for more information', 'updraftplus').'';
}
}
/**
* Gets an instance of the "UpdraftPlus_UpdraftCentral_Cloud" class which will be
* used to login or register the user to the UpdraftCentral cloud
*
* @return object
*/
public function get_updraftcentral_cloud() {
if (!class_exists('UpdraftPlus_UpdraftCentral_Cloud')) updraft_try_include_file('includes/updraftcentral.php', 'include_once');
return new UpdraftPlus_UpdraftCentral_Cloud();
}
/**
* This function will build and return the UpdraftPlus temporary clone ui widget
*
* @param boolean $include_testing_ui - a boolean to indicate if testing-only UI elements should be shown (N.B. they can only work if the user also has testing permissions)
* @param array $supported_wp_versions - an array of supported WordPress versions
* @param array $supported_packages - an array of supported clone packages
* @param array $supported_regions - an array of supported clone regions
* @param string $nearest_region - the user's nearest region
*
* @return string - the clone UI widget
*/
public function updraftplus_clone_ui_widget($include_testing_ui, $supported_wp_versions, $supported_packages, $supported_regions, $nearest_region = '') {
global $updraftplus;
$output = '';
$output .= ''.sprintf(__('%s version:', 'updraftplus'), 'PHP').' ';
$output .= $this->output_select_data($this->clone_php_versions, 'php');
$output .= '
';
$output .= '';
$output .= ' '.sprintf(__('%s version:', 'updraftplus'), 'WordPress').' ';
$output .= $this->output_select_data($this->get_wordpress_versions($supported_wp_versions), 'wp');
$output .= '
';
$output .= '';
$output .= ' '.__('Clone region:', 'updraftplus').' ';
$output .= $this->output_select_data($supported_regions, 'region', $nearest_region);
$output .= '
';
$backup_history = UpdraftPlus_Backup_History::get_history();
foreach ($backup_history as $key => $backup) {
$backup_complete = $this->check_backup_is_complete($backup, false, true, false);
$remote_sent = !empty($backup['service']) && ((is_array($backup['service']) && in_array('remotesend', $backup['service'])) || 'remotesend' === $backup['service']);
if (!$backup_complete || $remote_sent) unset($backup_history[$key]);
}
$output .= '';
$output .= ' '.__('Clone:', 'updraftplus').' ';
$output .= '';
$output .= '
';
$output .= '';
$output .= ' '.__('Clone package', 'updraftplus').' ('.__('more info', 'updraftplus').'): ';
$output .= '';
$output .= '
';
if ((defined('UPDRAFTPLUS_UPDRAFTCLONE_DEVELOPMENT') && UPDRAFTPLUS_UPDRAFTCLONE_DEVELOPMENT) || $include_testing_ui) {
$output .= '';
$output .= ' UpdraftClone Branch: ';
$output .= '';
$output .= '
';
$output .= '';
$output .= ' UpdraftPlus Branch: ';
$output .= '';
$output .= '
';
$output .= '';
}
$output .= '';
$output .= '';
$output .= '';
$output .= '
';
$output = apply_filters('updraftplus_clone_additional_ui', $output);
return $output;
}
/**
* This function will output a select input using the passed in values.
*
* @param array $data - the keys and values for the select
* @param string $name - the name of the items in the select input
* @param string $selected - the value we want selected by default
*
* @return string - the output of the select input
*/
public function output_select_data($data, $name, $selected = '') {
$name_version = empty($selected) ? $this->get_current_version($name) : $selected;
$output = '';
return $output;
}
/**
* This function will output the clones network information
*
* @param string $url - the clone URL
*
* @return string - the clone network information
*/
public function updraftplus_clone_info($url) {
global $updraftplus;
if (!empty($url)) {
$content = '';
$content .= ''.__('You can find your temporary clone information in your updraftplus.com account here.', 'updraftplus').'
';
} else {
$content = '' . __('Your clone has started, network information is not yet available but will be displayed here and at your updraftplus.com account once it is ready.', 'updraftplus') . '
';
$content .= '' . __('You can find your temporary clone information in your updraftplus.com account here.', 'updraftplus') . '
';
}
return $content;
}
/**
* This function will build and return an array of major WordPress versions, the array is built by calling the WordPress version API once every 24 hours and adding any new entries to our existing array of versions.
*
* @param array $supported_wp_versions - an array of supported WordPress versions
*
* @return array - an array of WordPress major versions
*/
private function get_wordpress_versions($supported_wp_versions) {
if (empty($supported_wp_versions)) $supported_wp_versions[] = $this->get_current_version('wp');
$key = array_search($this->get_current_version('wp'), $supported_wp_versions);
if ($key) {
$supported_wp_versions = array_slice($supported_wp_versions, $key);
}
$version_array = $supported_wp_versions;
return $version_array;
}
/**
* This function will get the current version the server is running for the passed in item e.g WordPress or PHP
*
* @param string $name - the thing we want to get the version for e.g WordPress or PHP
*
* @return string - returns the current version of the passed in item
*/
public function get_current_version($name) {
$version = '';
if ('php' == $name) {
$parts = explode(".", PHP_VERSION);
$version = $parts[0] . "." . $parts[1];
} elseif ('wp' == $name) {
global $updraftplus;
$wp_version = $updraftplus->get_wordpress_version();
$parts = explode(".", $wp_version);
$version = $parts[0] . "." . $parts[1];
}
return $version;
}
/**
* Show which remote storage settings are partially setup error, or if manual auth is supported show the manual auth UI
*/
public function show_admin_warning_if_remote_storage_with_partial_settings() {
if ((isset($_REQUEST['page']) && 'updraftplus' == $_REQUEST['page']) || (defined('DOING_AJAX') && DOING_AJAX)) {
$enabled_services = UpdraftPlus_Storage_Methods_Interface::get_enabled_storage_objects_and_ids(array_keys($this->storage_service_with_partial_settings));
foreach ($this->storage_service_with_partial_settings as $method => $method_name) {
if (empty($enabled_services[$method]['object']) || empty($enabled_services[$method]['instance_settings']) || !$enabled_services[$method]['object']->supports_feature('manual_authentication')) {
$this->show_admin_warning(sprintf(__('The following remote storage (%s) have only been partially configured, manual authorization is not supported with this remote storage, please try again and if the problem persists contact support.', 'updraftplus'), $method), 'error');
} else {
$this->show_admin_warning($enabled_services[$method]['object']->get_manual_authorisation_template(), 'error');
}
}
} else {
$this->show_admin_warning('UpdraftPlus: '.sprintf(__('The following remote storage (%s) have only been partially configured, if you are having problems you can try to manually authorise at the UpdraftPlus settings page.', 'updraftplus'), implode(', ', $this->storage_service_with_partial_settings)).' '.__('Return to UpdraftPlus configuration', 'updraftplus').'', 'error');
}
}
/**
* Show remote storage settings are empty warning
*/
public function show_admin_warning_if_remote_storage_setting_are_empty() {
if ((isset($_REQUEST['page']) && 'updraftplus' == $_REQUEST['page']) || (defined('DOING_AJAX') && DOING_AJAX)) {
$this->show_admin_warning(sprintf(__('You have requested saving to remote storage (%s), but without entering any settings for that storage.', 'updraftplus'), implode(', ', $this->storage_service_without_settings)), 'error');
} else {
$this->show_admin_warning('UpdraftPlus: '.sprintf(__('You have requested saving to remote storage (%s), but without entering any settings for that storage.', 'updraftplus'), implode(', ', $this->storage_service_without_settings)).' '.__('Return to UpdraftPlus configuration', 'updraftplus').'', 'error');
}
}
/**
* Receive Heartbeat data and respond.
*
* Processes data received via a Heartbeat request, and returns additional data to pass back to the front end.
*
* @param array $response - Heartbeat response data to pass back to front end.
* @param array $data - Data received from the front end (unslashed).
*/
public function process_status_in_heartbeat($response, $data) {
if (!UpdraftPlus_Options::user_can_manage() || !is_array($response) || empty($data['updraftplus'])) return $response;
try {
$response['updraftplus'] = $this->get_activejobs_list(UpdraftPlus_Manipulation_Functions::wp_unslash($data['updraftplus']));
} catch (Exception $e) {
$log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during get active job list. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
error_log($log_message);
$response['updraftplus'] = array(
'fatal_error' => true,
'fatal_error_message' => $log_message
);
// @codingStandardsIgnoreLine
} catch (Error $e) {
$log_message = 'PHP Fatal error ('.get_class($e).') has occurred during get active job list. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
error_log($log_message);
$response['updraftplus'] = array(
'fatal_error' => true,
'fatal_error_message' => $log_message
);
}
if (isset($data['updraftplus']['updraft_credentialtest_nonce'])) {
if (!wp_verify_nonce($data['updraftplus']['updraft_credentialtest_nonce'], 'updraftplus-credentialtest-nonce')) {
$response['updraftplus']['updraft_credentialtest_nonce'] = wp_create_nonce('updraftplus-credentialtest-nonce');
}
}
$response['updraftplus']['time_now'] = get_date_from_gmt(gmdate('Y-m-d H:i:s'), 'D, F j, Y H:i');
return $response;
}
/**
* Show warning about restriction implied by the hosting company (can only perform a full backup once per month, incremental backup should not go above one per day)
*/
public function show_admin_warning_one_backup_per_month() {
global $updraftplus;
$hosting_company = $updraftplus->get_hosting_info();
$txt1 = sprintf(__('Your website is hosted with %s (%s).', 'updraftplus'), $hosting_company['name'], $hosting_company['website']);
$txt2 = sprintf(__('%s permits UpdraftPlus to perform only one backup per month.', 'updraftplus'), $hosting_company['name']).' '.__('Thus, we recommend you choose a full backup when performing a manual backup and to use that option when creating a scheduled backup.', 'updraftplus');
$txt3 = __('Due to the restriction, some settings can be automatically adjusted, disabled or not available.', 'updraftplus');
$this->show_plugin_page_admin_warning(''.__('Warning', 'updraftplus').': '.$txt1.' '.$txt2.' '.$txt3, 'update-nag notice notice-warning', true);
}
/**
* Find out if the current request is a backup download request, and proceed with the download if it is
*/
public function maybe_download_backup_from_email() {
global $pagenow;
if (UpdraftPlus_Options::user_can_manage() && (!defined('DOING_AJAX') || !DOING_AJAX) && UpdraftPlus_Options::admin_page() === $pagenow && isset($_REQUEST['page']) && 'updraftplus' === $_REQUEST['page'] && isset($_REQUEST['action']) && 'updraft_download_backup' === $_REQUEST['action']) {
$findexes = empty($_REQUEST['findex']) ? array(0) : $_REQUEST['findex'];
$timestamp = empty($_REQUEST['timestamp']) ? '' : $_REQUEST['timestamp'];
$nonce = empty($_REQUEST['nonce']) ? '' : $_REQUEST['nonce'];
$type = empty($_REQUEST['type']) ? '' : $_REQUEST['type'];
if (empty($timestamp) || empty($nonce) || empty($type)) wp_die(__('The download link is broken, you may have clicked the link from untrusted source', 'updraftplus'), '', array('back_link' => true));
$backup_history = UpdraftPlus_Backup_History::get_history();
if (!isset($backup_history[$timestamp]['nonce']) || $backup_history[$timestamp]['nonce'] !== $nonce) wp_die(__("The download link is broken or the backup file is no longer available", 'updraftplus'), '', array('back_link' => true));
$this->do_updraft_download_backup($findexes, $type, $timestamp, 2, false, '');
exit; // we don't need anything else but an exit
}
}
/**
* Print the phpseclib-notice-related scripts
*/
public function print_phpseclib_notice_scripts() {
static $printed = false;
if ($printed) return;
$printed = true;
?>