* @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]) {
$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();
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);
$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 = '<div class="active-jobs updraft_finished" id="updraft-jobid-'.$this_job_only.'"><em>'.__('The backup has finished running', 'updraftplus').'</em> - <a class="updraft-log-link" data-jobid="'.$this_job_only.'">'.__('View Log', 'updraftplus').'</a></div>';
* 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
private function print_active_job($job_id, $is_oneshot = false, $time = false, $next_resumption = false) {
$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'];
$curstage = __('Backup begun', 'updraftplus');
$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)));
$curstage = __('Created file backup zips', 'updraftplus');
$curstage = __('Clone server being provisioned and booted (can take several minutes)', 'updraftplus');
$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);
$curstage .= ' ('.floor(100*$pd).'%, '.sprintf(__('file %d of %d', 'updraftplus'), (int)$jobdata['uploading_substatus']['i']+1, $t).')';
$curstage = __('Pruning old backup sets', 'updraftplus');
case 'resumingforerrors':
$curstage = __('Waiting until scheduled time to retry because of errors', 'updraftplus');
$curstage = __('Backup finished', 'updraftplus');
// 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');
$curstage = __('Unknown', 'updraftplus');
$runs_started = empty($jobdata['runs_started']) ? array() : $jobdata['runs_started'];
$time_passed = empty($jobdata['run_times']) ? array() : $jobdata['run_times'];
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;
$show_inline_info = $next_res_txt;
$title_info = $last_activity_txt;
$ret .= '<div class="updraft_row">';
$ret .= '<div class="updraft_col"><div class="updraft_jobtimings next-resumption';
if (!empty($jobdata['is_autobackup'])) $ret .= ' isautobackup';
$is_clone = empty($jobdata['clone_job']) ? '0' : '1';
$clone_url = empty($jobdata['clone_url']) ? false : true;
$ret .= '" data-jobid="'.$job_id.'" data-lastactivity="'.(int) $last_checkin_ago.'" data-nextresumption="'.$next_resumption.'" data-nextresumptionafter="'.$next_res_after.'" title="'.esc_attr(sprintf(__('Job ID: %s', 'updraftplus'), $job_id)).$title_info.'">'.(!empty($backup_label) ? esc_html($backup_label) : $began_at).
$ret .= '<div class="updraft_col updraft_progress_container">';
// Existence of the 'updraft-jobid-(id)' id is checked for in other places, so do not modify this
$ret .= '<div class="job-id" data-isclone="'.$is_clone.'" id="updraft-jobid-'.$job_id.'">';
if ($clone_url) $ret .= '<div class="updraft_clone_url" data-clone_url="' . $jobdata['clone_url'] . '"></div>';
$ret .= apply_filters('updraft_printjob_beforewarnings', '', $jobdata, $job_id);
if (!empty($jobdata['warnings']) && is_array($jobdata['warnings'])) {
$ret .= '<ul class="disc">';
foreach ($jobdata['warnings'] as $warning) {
$ret .= '<li>'.sprintf(__('Warning: %s', 'updraftplus'), make_clickable(htmlspecialchars($warning))).'</li>';
$ret .= '<div class="curstage">';
// $ret .= '<span class="curstage-info">'.htmlspecialchars($curstage).'</span>';
$ret .= htmlspecialchars($curstage);
// we need to add this data-progress attribute in order to be able to update the progress bar in UDC
$ret .= '<div class="updraft_percentage" data-info="'.esc_attr($curstage).'" data-progress="'.(($stage>0) ? (ceil((100/6)*$stage)) : '0').'" style="height: 100%; width:'.(($stage>0) ? (ceil((100/6)*$stage)) : '0').'%"></div>';
$ret .= '<div class="updraft_last_activity">';
$ret .= $show_inline_info;
if (!empty($show_inline_info)) $ret .= ' - ';
$file_nonce = empty($jobdata['file_nonce']) ? $job_id : $jobdata['file_nonce'];
$ret .= '<a data-fileid="'.$file_nonce.'" data-jobid="'.$job_id.'" href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&action=downloadlog&updraftplus_backup_nonce='.$file_nonce.'" class="updraft-log-link">'.__('show log', 'updraftplus').'</a>';
if (!$is_oneshot) $ret .=' - <a href="#" data-jobid="'.$job_id.'" title="'.esc_attr(__('Note: the progress bar below is based on stages, NOT time. Do not stop the backup simply because it seems to have remained in the same place for a while - that is normal.', 'updraftplus')).'" class="updraft_jobinfo_delete">'.__('stop', 'updraftplus').'</a>';
private function delete_old_dirs_go($show_return = true) {
echo $show_return ? '<h1>UpdraftPlus - '.__('Remove old directories', 'updraftplus').'</h1>' : '<h2>'.__('Remove old directories', 'updraftplus').'</h2>';
if ($this->delete_old_dirs()) {
echo '<p>'.__('Old directories successfully removed.', 'updraftplus').'</p><br>';
echo '<p>',__('Old directory removal failed for some reason. You may want to do this manually.', 'updraftplus').'</p><br>';
if ($show_return) echo '<b>'.__('Actions', 'updraftplus').':</b> <a href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus">'.__('Return to UpdraftPlus configuration', 'updraftplus').'</a>';
* 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);
// From WP_CONTENT_DIR - which contains 'themes'
$ret = $this->delete_old_dirs_dir($wp_filesystem->wp_content_dir());
$updraft_dir = $updraftplus->backups_dir_location();
$ret4 = $updraft_dir ? $this->delete_old_dirs_dir($updraft_dir, false) : true;
$plugs = untrailingslashit($wp_filesystem->wp_plugins_dir());
if ($wp_filesystem->is_dir($plugs.'-old')) {
echo "<strong>".__('Delete', 'updraftplus').": </strong>plugins-old: ";
if (!$wp_filesystem->delete($plugs.'-old', true)) {
echo "<strong>".__('Failed', 'updraftplus')."</strong><br>";
echo $updraftplus->log_permission_failure_message($wp_filesystem->wp_content_dir(), 'Delete '.$plugs.'-old');
echo "<strong>".__('OK', 'updraftplus')."</strong><br>";
return $ret && $ret3 && $ret4;
private function delete_old_dirs_dir($dir, $wpfs = true) {
$dir = trailingslashit($dir);
global $wp_filesystem, $updraftplus;
$list = $wp_filesystem->dirlist($dir);
if (!is_array($list)) return false;
foreach ($list as $item) {
$name = (is_array($item)) ? $item['name'] : $item;
if ("-old" == substr($name, -4, 4)) {
print "<strong>".__('Delete', 'updraftplus').": </strong>".htmlspecialchars($name).": ";
if (!$wp_filesystem->delete($dir.$name, true)) {
echo "<strong>".__('Failed', 'updraftplus')."</strong><br>";
echo $updraftplus->log_permission_failure_message($dir, 'Delete '.$dir.$name);
echo "<strong>".__('OK', 'updraftplus')."</strong><br>";
if (UpdraftPlus_Filesystem_Functions::remove_local_directory($dir.$name)) {
echo "<strong>".__('OK', 'updraftplus')."</strong><br>";
echo "<strong>".__('Failed', 'updraftplus')."</strong><br>";
echo $updraftplus->log_permission_failure_message($dir, 'Delete '.$dir.$name);
* 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')))) {
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);
$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)) {
if ($wp_filesystem->errors->get_error_code()) {
foreach ($wp_filesystem->errors->get_error_messages() as $message) {
$wperr->add('mkdir_error', $message);
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
if (UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) return true;
@$wp_filesystem->chmod($default_backup_dir, 0777);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
if (UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) {
echo '<p>'.__('The folder was created, but we had to change its file permissions to 777 (world-writable) to be able to write to it. You should check with your hosting provider that this will not cause any problems', 'updraftplus').'</p>';
@$wp_filesystem->chmod($default_backup_dir, 0775);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
$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.')');
* scans the content dir to see if any -old dirs are present
* @param Boolean $print_as_comment Echo information in an HTML comment
private function scan_old_dirs($print_as_comment = false) {
$dirs = scandir(untrailingslashit(WP_CONTENT_DIR));
if (!is_array($dirs)) $dirs = array();
$dirs_u = @scandir($updraftplus->backups_dir_location());// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
if (!is_array($dirs_u)) $dirs_u = array();
foreach (array_merge($dirs, $dirs_u) as $dir) {
if (preg_match('/-old$/', $dir)) {
if ($print_as_comment) echo '<!--'.htmlspecialchars($dir).'-->';
// No need to scan ABSPATH - we don't backup there
if (is_dir(untrailingslashit(WP_PLUGIN_DIR).'-old')) {
if ($print_as_comment) echo '<!--'.htmlspecialchars(untrailingslashit(WP_PLUGIN_DIR).'-old').'-->';
* 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) {
<tr class="updraftplusmethod <?php echo $method;?>">
<th><?php echo $header;?></th>
<td><?php echo $contents;?></td>
* Outputs html for a storage method using the parameters passed in, this version of the method is compatible with multi storage options
* @param string $classes a list of classes to be used when
* @param string $header the table header content
* @param string $contents the table contents
public function storagemethod_row_multi($classes, $header, $contents) {
<tr class="<?php echo $classes;?>">
<th><?php echo $header;?></th>
<td><?php echo $contents;?></td>
* Returns html for a storage method using the parameters passed in, this version of the method is compatible with multi storage options
* @param string $classes a list of classes to be used when
* @param string $header the table header content
* @param string $contents the table contents
* @return string handlebars html template
public function get_storagemethod_row_multi_configuration_template($classes, $header, $contents) {
return '<tr class="'.esc_attr($classes).'">