our_siteurl = untrailingslashit(site_url());
// Line up a wpdb-like object
if (!$this->use_wpdb()) {
// We have our own extension which drops lots of the overhead on the query
$wpdb_obj = new UpdraftPlus_WPDB(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);
// Was that successful?
if (!$wpdb_obj->is_mysql || !$wpdb_obj->ready) {
$this->use_wpdb = true;
} else {
$this->wpdb_obj = $wpdb_obj;
$this->mysql_dbh = $wpdb_obj->updraftplus_get_database_handle();
$this->use_mysqli = $wpdb_obj->updraftplus_use_mysqli();
}
}
if ($short_init) return;
$this->ud_backup_info = $info;
do_action('updraftplus_restorer_restore_options', $restore_options);
$this->ud_multisite_selective_restore = (is_array($restore_options) && !empty($restore_options['updraft_restore_ms_whichsites']) && $restore_options['updraft_restore_ms_whichsites'] > 0) ? $restore_options['updraft_restore_ms_whichsites'] : false;
$this->restore_options = $restore_options;
$this->ud_foreign = empty($info['meta_foreign']) ? false : $info['meta_foreign'];
if (isset($info['is_multisite'])) $this->ud_backup_is_multisite = $info['is_multisite'];
if (isset($info['created_by_version'])) $this->created_by_version = $info['created_by_version'];
add_filter('updraftplus_logline', array($this, 'updraftplus_logline'), 10, 5);
$this->backup_strings();
$this->is_multisite = is_multisite();
if (!class_exists('WP_Upgrader')) include_once(ABSPATH.'wp-admin/includes/class-wp-upgrader.php');
$this->skin = $skin;
$this->wp_upgrader = new WP_Upgrader($skin);
$this->wp_upgrader->init();
}
/**
* Get the wpdb-like object that we are using, if we are using one
*
* @return UpdraftPlus_WPDB|Boolean
*/
public function get_db_object() {
return $this->wpdb_obj;
}
/**
* Whether or not we must use the global $wpdb object for database queries.
* That is to say: we *can* always use it. But we prefer to avoid the overhead since we are potentially doing very many queries.
*
* This is the getter. We have no use-case for a setter outside of this class, so we just set it directly.
*
* @return Boolean
*/
public function use_wpdb() {
if (!is_bool($this->use_wpdb)) {
global $wpdb;
if (defined('UPDRAFTPLUS_USE_WPDB')) {
$this->use_wpdb = (bool) UPDRAFTPLUS_USE_WPDB;
} else {
$this->use_wpdb = ((!function_exists('mysql_query') && !function_exists('mysqli_query')) || !$wpdb->is_mysql || !$wpdb->ready) ? true : false;
}
}
return $this->use_wpdb;
}
/**
* Get the skin
*
* @return WP_Upgrader_Skin
*/
public function ud_get_skin() {
return $this->skin;
}
/**
* Ensure that needed files are present, and return data for the next step (plus do some internal configuration)
*
* @param Array $entities_to_restore - as returned by self::get_entities_to_restore()
* @param Array $backupable_entities - list of entities that can be backed u
* @param Array $continuation_data - continuation data
* @param Array $services - list of services that the backup can be found at
*
* @uses self::pre_restore_backup() (and some other internal properties)
* @uses UpdraftPlus::log()
*
* @return Boolean|Array|WP_Error, or false or a WP_Error if there was an error
*/
private function ensure_restore_files_present($entities_to_restore, $backupable_entities, $continuation_data, $services) {
global $updraftplus;
$entities_to_download = $this->get_entities_to_download($entities_to_restore);
$backup_set = $this->ud_backup_info;
$timestamp = $backup_set['timestamp'];
$updraft_dir = $updraftplus->backups_dir_location();
$foreign_known = apply_filters('updraftplus_accept_archivename', array());
// First loop: make sure that files are present + readable; and populate array for second loop
foreach ($backup_set as $type => $files) {
// All restorable entities must be given explicitly, as we can store other arbitrary data in the history array
if (!isset($backupable_entities[$type]) && 'db' != $type) continue;
if (isset($backupable_entities[$type]['restorable']) && false == $backupable_entities[$type]['restorable']) continue;
if (!isset($entities_to_download[$type])) continue;
if ('wpcore' == $type && is_multisite() && 0 === $this->ud_backup_is_multisite) {
$updraftplus->log('wpcore: '.__('Skipping restoration of WordPress core when importing a single site into a multisite installation. If you had anything necessary in your WordPress directory then you will need to re-add it manually from the zip file.', 'updraftplus'), 'notice-restore');
// TODO
// $updraftplus->log_e('Skipping restoration of WordPress core when importing a single site into a multisite installation. If you had anything necessary in your WordPress directory then you will need to re-add it manually from the zip file.');
continue;
}
if (is_string($files)) $files = array($files);
foreach ($files as $ind => $file) {
$fullpath = $updraft_dir.'/'.$file;
$updraftplus->log(sprintf(__("Looking for %s archive: file name: %s", 'updraftplus'), $type, $file), 'notice-restore');
if (is_array($continuation_data) && isset($continuation_data['second_loop_entities'][$type]) && !in_array($file, $continuation_data['second_loop_entities'][$type])) {
$updraftplus->log(__('Skipping: this archive was already restored.', 'updraftplus'), 'notice-restore');
// Set the marker so that the existing directory isn't moved out of the way
$this->been_restored[$type] = true;
continue;
}
if (!is_readable($fullpath) || 0 == filesize($fullpath)) UpdraftPlus_Storage_Methods_Interface::get_remote_file($services, $file, $timestamp, true);
$index = (0 == $ind) ? '' : $ind;
// If a file size is stored in the backup data, then verify correctness of the local file
if (isset($backup_set[$type.$index.'-size'])) {
$fs = $backup_set[$type.$index.'-size'];
$print_message = __("Archive is expected to be size:", 'updraftplus')." ".round($fs/1024, 1)." KB: ";
$as = @filesize($fullpath);
if ($as == $fs) {
$updraftplus->log($print_message.__('OK', 'updraftplus'), 'notice-restore');
} else {
$updraftplus->log($print_message.__('Error:', 'updraftplus')." ".__('file is size:', 'updraftplus')." ".round($as/1024)." ($fs, $as)", 'warning-restore');
}
} else {
$updraftplus->log(__("The backup records do not contain information about the proper size of this file.", 'updraftplus'), 'notice-restore');
}
if (!is_readable($fullpath)) {
$updraftplus->log(__('Could not find one of the files for restoration', 'updraftplus')." ($file)", 'warning-restore');
$updraftplus->log("$file: ".__('Could not find one of the files for restoration', 'updraftplus'), 'error');
return false;
}
}
if (empty($this->ud_foreign)) {
$types = array($type);
} else {
if ('db' != $type || empty($foreign_known[$this->ud_foreign]['separatedb'])) {
$types = array('wpcore');
} else {
$types = array('db');
}
}
foreach ($types as $check_type) {
$info = isset($backupable_entities[$check_type]) ? $backupable_entities[$check_type] : array();
$val = $this->pre_restore_backup($files, $check_type, $info, $continuation_data);
if (is_wp_error($val)) {
$updraftplus->log_wp_error($val);
foreach ($val->get_error_messages() as $msg) {
$updraftplus->log(__('Error:', 'updraftplus').' '.$msg, 'warning-restore');
}
return $val;
} elseif (false === $val) {
return false;
}
}
foreach ($entities_to_restore as $entity => $via) {
if ($via == $type) {
if ('wpcore' == $via && 'db' == $entity && count($files) > 1) {
$second_loop[$entity] = apply_filters('updraftplus_select_wpcore_file_with_db', $files, $this->ud_foreign);
} else {
$second_loop[$entity] = $files;
}
}
}
}
$this->delete = UpdraftPlus_Options::get_updraft_option('updraft_delete_local', 1) ? true : false;
if (empty($services) || array('email') === $services || !empty($this->ud_foreign)) {
if ($this->delete) $updraftplus->log_e('Will not delete any archives after unpacking them, because there was no cloud storage for this backup');
$this->delete = false;
}
if (!empty($this->ud_foreign)) $updraftplus->log("Foreign backup; created by: ".$this->ud_foreign);
// Second loop: now actually do the restoration
uksort($second_loop, array('UpdraftPlus_Manipulation_Functions', 'sort_restoration_entities'));
// If continuing, then prune those already done
if (is_array($continuation_data)) {
foreach ($second_loop as $type => $files) {
if (isset($continuation_data['second_loop_entities'][$type])) $second_loop[$type] = $continuation_data['second_loop_entities'][$type];
}
}
return $second_loop;
}
/**
* Perform the restoration
*
* @param Array $entities_to_restore - entities to restore
* @param Array $restore_options - restoration options
* @param Array|null $continuation_data - continuation data
*
* @uses the WordPress action updraftplus_restoration_title, allowing the title to be printed
*
* @return Boolean
*/
public function perform_restore($entities_to_restore, $restore_options, $continuation_data = null) {
global $updraftplus;
$backup_set = $this->ud_backup_info;
$services = isset($backup_set['service']) ? $updraftplus->get_canonical_service_list($backup_set['service']) : array();
$foreign_known = apply_filters('updraftplus_accept_archivename', array());
$entities_to_download = $this->get_entities_to_download($entities_to_restore);
$backupable_entities = $updraftplus->get_backupable_file_entities(true, true);
$remove_zip = isset($restore_options['delete_during_restore']) ? $restore_options['delete_during_restore'] : false;
if (!empty($restore_options['dummy_db_restore'])) {
$this->is_dummy_db_restore = true;
add_filter('updraftplus_restore_table_prefix', array($this, 'updraftplus_restore_table_prefix_dummy'));
}
// Allow add-ons to adjust the restore directory (but only in the case of restore - otherwise, they could just use the filter built into UpdraftPlus::get_backupable_file_entities)
$backupable_entities = apply_filters('updraft_backupable_file_entities_on_restore', $backupable_entities, $restore_options, $backup_set);
@set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);
// Get an ordered list of things to restore
// This requires the global $updraft_restorer to be set up
$second_loop = $this->ensure_restore_files_present($entities_to_download, $backupable_entities, $continuation_data, $services);
if (!is_array($second_loop)) return $second_loop;
$timestamp = $backup_set['timestamp'];
$updraftplus->jobdata_set('second_loop_entities', $second_loop);
$updraftplus->jobdata_set('backup_timestamp', $timestamp);
// Use a site option, as otherwise on multisite when all the array of options is updated via UpdraftPlus_Options::update_site_option(), it will over-write any restored UD options from the backup
update_site_option('updraft_restore_in_progress', $updraftplus->nonce);
// Now process the actual restoration of the entities
foreach ($second_loop as $type => $files) {
// Types: uploads, themes, plugins, others, db
$info = isset($backupable_entities[$type]) ? $backupable_entities[$type] : array();
$restoration_title = ('db' == $type) ? __('Database', 'updraftplus') : $info['description'];
do_action('updraftplus_restoration_title', $restoration_title);
$updraftplus->log('Entity: '.$type);
if (is_string($files)) $files = array($files);
foreach ($files as $fkey => $file) {
$last_one = (1 == count($second_loop) && 1 == count($files));
$last_entity = (1 == count($files));
try {
// Returns a boolean or WP_Error
$restore_result = $this->restore_backup($file, $type, $info, $last_one, $last_entity);
} catch (Exception $e) {
$log_message = 'Exception ('.get_class($e).') occurred during restore: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
$display_log_message = sprintf(__('A PHP exception (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage());
error_log($log_message);
// @codingStandardsIgnoreLine
if (function_exists('wp_debug_backtrace_summary')) $log_message .= ' Backtrace: '.wp_debug_backtrace_summary();
$updraftplus->log($log_message);
$updraftplus->log($display_log_message, 'notice-restore');
die();
// @codingStandardsIgnoreLine
} catch (Error $e) {
$log_message = 'PHP Fatal error ('.get_class($e).') has occurred. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
error_log($log_message);
// @codingStandardsIgnoreLine
if (function_exists('wp_debug_backtrace_summary')) $log_message .= ' Backtrace: '.wp_debug_backtrace_summary();
$updraftplus->log($log_message);
$display_log_message = sprintf(__('A PHP fatal error (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage());
$updraftplus->log($display_log_message, 'notice-restore');
die();
}
if (is_wp_error($restore_result)) {
$codes = $restore_result->get_error_codes();
if (is_array($codes) && in_array('not_found', $codes) && !empty($this->ud_foreign) && apply_filters('updraftplus_foreign_allow_missing_entity', false, $type, $this->ud_foreign)) {
$updraftplus->log('Entity to move not found in this zip - but this is possible with this foreign backup type');
} else {
$updraftplus->log_e($restore_result);
foreach ($restore_result->get_error_messages() as $msg) {
$updraftplus->log(__('Error message', 'updraftplus').': '.$msg, 'notice-restore');
}
return $restore_result;
}
} elseif (false === $restore_result) {
return false;
} elseif ($restore_result && $remove_zip) {
$deleted = unlink($updraftplus->backups_dir_location().'/'.$file);
$updraftplus->log("Delete zip during restore active; removing backup file: $file: ".($deleted ? 'OK' : 'Failed'));
}
unset($files[$fkey]);
$second_loop[$type] = $files;
$updraftplus->jobdata_set('second_loop_entities', $second_loop);
$updraftplus->jobdata_set('backup_timestamp', $timestamp);
do_action('updraft_restored_archive', $file, $type, $restore_result, $fkey, $timestamp);
}
// Update the job data each time we go round the loop, so that if it aborts, it can be resumed from the correct point
unset($second_loop[$type]);
update_site_option('updraft_restore_in_progress', $updraftplus->nonce);
$updraftplus->jobdata_set('second_loop_entities', $second_loop);
$updraftplus->jobdata_set('backup_timestamp', $timestamp);
}
// If the database was restored, then check active plugins and make sure they all exist; otherwise, the site may go down
if (null !== $this->import_table_prefix) $this->check_active_plugins($this->import_table_prefix);
return true;
}
/**
* Calculate the entities to download for a given backup set
*
* @param Array $entities_to_restore - entities to restore, in the format returned by UpdraftPlus_Admin::get_entities_to_restore
*
* @return Array - keys are entities, and values are 0|1
*/
public function get_entities_to_download($entities_to_restore) {
$backup_set = $this->ud_backup_info;
$foreign_known = apply_filters('updraftplus_accept_archivename', array());
if (empty($backup_set['meta_foreign'])) return $entities_to_restore;
if (empty($foreign_known[$backup_set['meta_foreign']]['separatedb'])) return array('wpcore' => 1);
$entities_to_download = array();
if (in_array('db', $entities_to_restore)) $entities_to_download['db'] = 1;
if (count($entities_to_restore) > 1 || !in_array('db', $entities_to_restore)) {
$entities_to_download['wpcore'] = 1;
}
return $entities_to_download;
}
/**
* Logs a line from the restore process, being called from UpdraftPlus::log(). Currently, this means adding it to the browser output log file and echoing it.
* 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 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) {
if ('restore' != $destination) return $line;
global $updraftplus;
static $logfile_handle;
static $opened_log_time;
if (empty($logfile_handle)) {
$logfile_name = $updraftplus->backups_dir_location()."/log.$nonce-browser.txt";
$logfile_handle = fopen($logfile_name, 'a');
}
if (!empty($logfile_handle)) {
$rtime = microtime(true)-$updraftplus->job_time_ms;
fwrite($logfile_handle, sprintf("%08.03f", round($rtime, 3))." (R) ".'['.$level.'] '.$line."\n");
}
if (defined('WP_CLI') && WP_CLI) {
switch ($level) {
case 'error':
case 'warning':
// WP_CLI::error() displays message with the prefix "Error: ", We don't like message which are double prefixed like the "Error: Error: ".
if (0 === stripos($line, 'Error: ')) {
$log_line = substr($line, 7);
} else {
$log_line = $line;
}
WP_CLI::error($log_line, false);
break;
case 'notice':
default:
WP_CLI::log($line, false);
break;
}
} else {
if ('warning' == $destination || 'error' == $destination || $uniq_id) {
$line = ''.htmlspecialchars($line).'';
} else {
$line = htmlspecialchars($line);
}
echo $line.'
';
}
return false;
}
private function backup_strings() {
$this->strings['not_possible'] = __('UpdraftPlus is not able to directly restore this kind of entity. It must be restored manually.', 'updraftplus');
$this->strings['no_package'] = __('Backup file not available.', 'updraftplus');
$this->strings['copy_failed'] = __('Copying this entity failed.', 'updraftplus');
$this->strings['unpack_package'] = __('Unpacking backup...', 'updraftplus');
$this->strings['decrypt_database'] = __('Decrypting database (can take a while)...', 'updraftplus');
$this->strings['decrypted_database'] = __('Database successfully decrypted.', 'updraftplus');
$this->strings['moving_old'] = __('Moving old data out of the way...', 'updraftplus');
$this->strings['moving_backup'] = __('Moving unpacked backup into place...', 'updraftplus');
$this->strings['restore_database'] = __('Restoring the database (on a large site this can take a long time - if it times out (which can happen if your web hosting company has configured your hosting to limit resources) then you should use a different method, such as phpMyAdmin)...', 'updraftplus');
$this->strings['cleaning_up'] = __('Cleaning up rubbish...', 'updraftplus');
$this->strings['old_move_failed'] = __('Could not move old files out of the way.', 'updraftplus').' '.__('You should check the file ownerships and permissions in your WordPress installation', 'updraftplus');
$this->strings['old_delete_failed'] = __('Could not delete old directory.', 'updraftplus');
$this->strings['new_move_failed'] = __('Could not move new files into place. Check your wp-content/upgrade folder.', 'updraftplus');
$this->strings['move_failed'] = __('Could not move the files into place. Check your file permissions.', 'updraftplus');
$this->strings['delete_failed'] = __('Failed to delete working directory after restoring.', 'updraftplus');
$this->strings['multisite_error'] = __('You are running on WordPress multisite - but your backup is not of a multisite site.', 'updraftplus');
$this->strings['unpack_failed'] = __('Failed to unpack the archive', 'updraftplus');
$this->strings['read_manifest_failed'] = __('Failed to read the manifest file from backup.', 'updraftplus');
$this->strings['manifest_not_found'] = __('Failed to find a manifest file in the backup.', 'updraftplus');
$this->strings['read_working_dir_failed'] = __('Failed to read from the working directory.', 'updraftplus');
}
/**
* This function is copied from class WP_Upgrader (WP 3.8 - no significant changes since 3.2 at least); we only had to fork it because it hard-codes using the basename of the zip file as its unpack directory; which can be long; and then combining that with long pathnames in the zip being unpacked can overflow a 256-character path limit (yes, they apparently still exist - amazing!)
* Subsequently, we have also added the ability to unpack tarballs
*
* @param string $package specify package
* @param boolean $delete_package check to delete package
* @param string $type type of archieve e.g. db. THis can also be false
* @return string
*/
private function unpack_package_archive($package, $delete_package = true, $type = false) {
global $wp_filesystem, $updraftplus;
if (!empty($this->ud_foreign) && !empty($this->ud_foreign_working_dir) && $package == $this->ud_foreign_package) {
if (is_dir($this->ud_foreign_working_dir)) {
return $this->ud_foreign_working_dir;
} else {
$updraftplus->log('Previously unpacked directory seems to have disappeared; will unpack again');
}
}
$packsize = round(filesize($package)/1048576, 1).' Mb';
$this->skin->feedback($this->strings['unpack_package'].' ('.basename($package).', '.$packsize.')');
$upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
// Clean up contents of upgrade directory beforehand.
$upgrade_files = $wp_filesystem->dirlist($upgrade_folder);
if (!empty($upgrade_files)) {
foreach ($upgrade_files as $file)
if (!$wp_filesystem->delete($upgrade_folder . $file['name'], true)) {
$this->restore_log_permission_failure_message($upgrade_folder, 'Delete '.$upgrade_folder.$file['name']);
}
}
// We need a working directory
// This is the only change from the WP core version - minimise path length
// $working_dir = $upgrade_folder . basename($package, '.zip');
$working_dir = $upgrade_folder . substr(md5($package), 0, 8);
// Clean up working directory
if ($wp_filesystem->is_dir($working_dir)) {
if (!$wp_filesystem->delete($working_dir, true)) {
$this->restore_log_permission_failure_message(dirname($working_dir), 'Delete '.$working_dir);
}
}
// Unzip package to working directory
if ('.zip' == strtolower(substr($package, -4, 4))) {
$result = unzip_file($package, $working_dir);
} elseif ('.tar' == strtolower(substr($package, -4, 4)) || '.tar.gz' == strtolower(substr($package, -7, 7)) || '.tar.bz2' == strtolower(substr($package, -8, 8))) {
if (!class_exists('UpdraftPlus_Archive_Tar')) {
if (false === strpos(get_include_path(), UPDRAFTPLUS_DIR.'/includes/PEAR')) set_include_path(UPDRAFTPLUS_DIR.'/includes/PEAR'.PATH_SEPARATOR.get_include_path());
include_once(UPDRAFTPLUS_DIR.'/includes/PEAR/Archive/Tar.php');
}
$p_compress = null;
if ('.tar.gz' == strtolower(substr($package, -7, 7))) {
$p_compress = 'gz';
} elseif ('.tar.bz2' == strtolower(substr($package, -8, 8))) {
$p_compress = 'bz2';
}
// It's not pretty. But it works.
if (is_a($wp_filesystem, 'WP_Filesystem_Direct')) {
$extract_dir = $working_dir;
} else {
$updraft_dir = $updraftplus->backups_dir_location();
if (!UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) {
$updraftplus->log_e("Backup directory (%s) is not writable, or does not exist.", $updraft_dir);
$result = new WP_Error('unpack_failed', $this->strings['unpack_failed'], $tar->extract);
} else {
$extract_dir = $updraft_dir.'/'.basename($working_dir).'-old';
if (file_exists($extract_dir)) UpdraftPlus_Filesystem_Functions::remove_local_directory($extract_dir);
$updraftplus->log("Using a temporary folder to extract before moving over WPFS: $extract_dir");
}
}
// Slightly hackish - rather than re-write Archive_Tar to use wp_filesystem, we instead unpack into the location that we already require to be directly writable for other reasons, and then move from there.
if (empty($result)) {
$this->ud_extract_count = 0;
$this->ud_working_dir = trailingslashit($working_dir);
$this->ud_extract_dir = untrailingslashit($extract_dir);
$this->ud_made_dirs = array();
add_filter('updraftplus_tar_wrote', array($this, 'tar_wrote'), 10, 2);
$tar = new UpdraftPlus_Archive_Tar($package, $p_compress);
$result = $tar->extract($extract_dir, false);
if (!is_a($wp_filesystem, 'WP_Filesystem_Direct')) UpdraftPlus_Filesystem_Functions::remove_local_directory($extract_dir);
if (true != $result) {
$result = new WP_Error('unpack_failed', $this->strings['unpack_failed'], $result);
} else {
if (!is_a($wp_filesystem, 'WP_Filesystem_Direct')) {
$updraftplus->log('Moved unpacked tarball contents');
}
}
remove_filter('updraftplus_tar_wrote', array($this, 'tar_wrote'), 10, 2);
}
}
// Once extracted, delete the package if required.
if ($delete_package)
unlink($package);
if (is_wp_error($result)) {
$wp_filesystem->delete($working_dir, true);
if ('incompatible_archive' == $result->get_error_code()) {
return new WP_Error('incompatible_archive', $this->wp_upgrader->strings['incompatible_archive'], $result->get_error_data());
}
return $result;
}
if (!empty($this->ud_foreign)) {
$this->ud_foreign_working_dir = $working_dir;
$this->ud_foreign_package = $package;
// Zip containing an SQL file. We try a default pattern.
if ('db' === $type) {
$basepack = basename($package, '.zip');
if ($wp_filesystem->exists($working_dir.'/'.$basepack.'.sql')) {
if (!$wp_filesystem->move($working_dir.'/'.$basepack.'.sql', $working_dir . "/backup.db", true)) {
$this->restore_log_permission_failure_message($working_dir, 'Move '. $working_dir.'/'.$basepack.'.sql'.' -> '.$working_dir . "/backup.db", 'Destination');
}
$updraftplus->log("Moving database file $basepack.sql to backup.db");
}
}
}
return $working_dir;
}
public function tar_wrote($result, $file) {
if (0 !== strpos($file, $this->ud_extract_dir)) return false;
global $wp_filesystem, $updraftplus;
if (!is_a($wp_filesystem, 'WP_Filesystem_Direct')) {
$modint = 100;
$leaf = substr($file, strlen($this->ud_extract_dir));
$dirname = dirname($leaf);
$need_dirs = explode('/', $dirname);
if (empty($this->ud_made_dirs[$dirname])) {
$cdir = '';
foreach ($need_dirs as $ndir) {
$cdir .= ($cdir) ? '/'.$ndir : $ndir;
if (empty($this->ud_made_dirs[$cdir])) {
if (!$wp_filesystem->mkdir($this->ud_working_dir.$cdir, FS_CHMOD_DIR) && !$wp_filesystem->is_dir($this->ud_working_dir.$cdir)) {
$updraftplus->log("Failed to create WPFS directory: ".$this->ud_working_dir.$cdir);
return false;
} else {
$this->ud_made_dirs[$cdir] = true;
}
}
}
}
$put = $wp_filesystem->put_contents($this->ud_working_dir.$leaf, file_get_contents($file));
if (is_wp_error($put)) $updraftplus->log_wp_error($put);
@unlink($file);
} else {
$modint = 500;
$put = true;
}
if ($put) {
$this->ud_extract_count++;
if (0 == $this->ud_extract_count % $modint) {
$updraftplus->log_e("%s files have been extracted", $this->ud_extract_count);
}
}
return (true == $put);
}
// This returns a wp_filesystem location (and we musn't change that, as we must retain compatibility with the class parent)
/**
* This returns a wp_filesystem location (and we musn't change that, as we must retain compatibility with the class parent)
* along with unpacking the encrypted db file and checking its contents before going off and restoring the Db
*
* @param string $package The file name of the encrypted File
* @param boolean $delete_package the file can be removed before going off to the restore stage (this is just incase the user dont want to proceed)
* @param boolean $type Check if the type is true or false
* @return string Returns success or Fail depending on errors and restors DB
*/
public function unpack_package($package, $delete_package = true, $type = false) {
global $wp_filesystem, $updraftplus;
$updraft_dir = $updraftplus->backups_dir_location();
// If not database, then it is a zip - unpack in the usual way
if (!preg_match('/-db(\.gz(\.crypt)?)?$/i', $package) && !preg_match('/\.sql(\.gz|\.bz2)?$/i', $package)) return $this->unpack_package_archive($updraft_dir.'/'.$package, $delete_package, $type);
$backup_dir = $wp_filesystem->find_folder($updraft_dir);
// Unpack a database. The general shape of the following is copied from class-wp-upgrader.php
@set_time_limit(1800);
$packsize = round(filesize($backup_dir.$package)/1048576, 1).' Mb';
$this->skin->feedback($this->strings['unpack_package'].' ('.basename($package).', '.$packsize.')');
$upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
@$wp_filesystem->mkdir($upgrade_folder, octdec($this->calculate_additive_chmod_oct(FS_CHMOD_DIR, 0775)));
// Clean up contents of upgrade directory beforehand.
$upgrade_files = $wp_filesystem->dirlist($upgrade_folder);
if (!empty($upgrade_files)) {
foreach ($upgrade_files as $file)
if (!$wp_filesystem->delete($upgrade_folder.$file['name'], true)) {
$this->restore_log_permission_failure_message($upgrade_folder, 'Delete '.$upgrade_folder.$file['name']);
}
}
// We need a working directory
$working_dir = $upgrade_folder . basename($package, '.crypt');
// $working_dir_localpath = WP_CONTENT_DIR.'/upgrade/'. basename($package, '.crypt');
// Clean up working directory
if ($wp_filesystem->is_dir($working_dir)) {
if (!$wp_filesystem->delete($working_dir, true)) {
$this->restore_log_permission_failure_message(dirname($working_dir), 'Delete '.$working_dir);
}
}
if (!$wp_filesystem->mkdir($working_dir, octdec($this->calculate_additive_chmod_oct(FS_CHMOD_DIR, 0775)))) return new WP_Error('mkdir_failed', __('Failed to create a temporary directory', 'updraftplus').' ('.$working_dir.')');
// Unpack package to working directory
if (UpdraftPlus_Encryption::is_file_encrypted($package)) {
$this->skin->feedback($this->strings['decrypt_database']);
$encryption = empty($this->restore_options['updraft_encryptionphrase']) ? UpdraftPlus_Options::get_updraft_option('updraft_encryptionphrase') : $this->restore_options['updraft_encryptionphrase'];
if (!$encryption) return new WP_Error('no_encryption_key', __('Decryption failed. The database file is encrypted, but you have no encryption key entered.', 'updraftplus'));
// function decrypt
$decrypted_file = UpdraftPlus_Encryption::decrypt($backup_dir.$package, $encryption);
if (is_array($decrypted_file)) {
$this->skin->feedback($this->strings['decrypted_database']);
if (!copy($decrypted_file['fullpath'], $working_dir.'/backup.db.gz')) {
return new WP_Error('write_failed', __('Failed to write out the decrypted database to the filesystem', 'updraftplus'));
} else {
unlink($decrypted_file['fullpath']);
}
} else {
return new WP_Error('decryption_failed', __('Decryption failed. The most likely cause is that you used the wrong key.', 'updraftplus'));
}
} else {
if (preg_match('/\.sql$/i', $package)) {
if (!$wp_filesystem->copy($backup_dir.$package, $working_dir.'/backup.db')) {
if ($wp_filesystem->errors->get_error_code()) {
foreach ($wp_filesystem->errors->get_error_messages() as $message) show_message($message);
}
return new WP_Error('copy_failed', $this->strings['copy_failed']);
}
} elseif (preg_match('/\.bz2$/i', $package)) {
if (!$wp_filesystem->copy($backup_dir.$package, $working_dir.'/backup.db.bz2')) {
if ($wp_filesystem->errors->get_error_code()) {
foreach ($wp_filesystem->errors->get_error_messages() as $message) show_message($message);
}
return new WP_Error('copy_failed', $this->strings['copy_failed']);
}
} elseif (!$wp_filesystem->copy($backup_dir.$package, $working_dir.'/backup.db.gz')) {
if ($wp_filesystem->errors->get_error_code()) {
foreach ($wp_filesystem->errors->get_error_messages() as $message) show_message($message);
}
return new WP_Error('copy_failed', $this->strings['copy_failed']);
}
}
// Once extracted, delete the package if required (non-recursive, is a file)
// if ($delete_package) $wp_filesystem->delete($decrypted_file['fullpath'], false, true);
if ($delete_package) {
if (!$wp_filesystem->delete($backup_dir.$package, false, true)) {
$this->restore_log_permission_failure_message($backup_dir, 'Delete '.$backup_dir.$package);
}
}
$updraftplus->log("Database successfully unpacked");
return $working_dir;
}
/**
* For moving files out of a directory into their new location
* The purposes of the $type parameter are 1) to detect 'others' and apply a historical bugfix 2) to detect wpcore, and apply the setting for what to do with wp-config.php 3) to work out whether to delete the directory itself
* Must use only wp_filesystem
* $dest_dir must already have a trailing slash
* $preserve_existing: this setting only applies at the top level: 0 = overwrite with no backup; 1 = make backup of existing; 2 = do nothing if there is existing, 3 = do nothing to the top level directory, but do copy-in contents (and over-write files). Thus, on a multi-archive set where you want a backup, you'd do this: first call with $preserve_existing === 1, then on subsequent zips call with 3
*
* @param string $working_dir specify working directory
* @param string $dest_dir specify destination directory
* @param integer $preserve_existing check to preserve exisitng file
* @param array $do_not_overwrite Specify files or directories not to overwrite
* @param string $type specify type
* @param boolean $send_actions send actions
* @param boolean $force_local force local
* @return boolean
*/
public function move_backup_in($working_dir, $dest_dir, $preserve_existing = 1, $do_not_overwrite = array('plugins', 'themes', 'uploads', 'upgrade'), $type = 'not-others', $send_actions = false, $force_local = false) {
global $wp_filesystem, $updraftplus;
$updraft_dir = $updraftplus->backups_dir_location();
if (true == $force_local) {
$wpfs = new UpdraftPlus_WP_Filesystem_Direct(true);
} else {
$wpfs = $wp_filesystem;
}
// Get the content to be moved in. Include hidden files = true. Recursion is only required if we're likely to copy-in
$recursive = (self::MOVEIN_COPY_IN_CONTENTS == $preserve_existing) ? true : false;
$upgrade_files = $wpfs->dirlist($working_dir, true, $recursive);
if (empty($upgrade_files)) return true;
if (!$wpfs->is_dir($dest_dir)) {
return new WP_Error('no_such_dir', __('The directory does not exist', 'updraftplus')." ($dest_dir)");
}
$wpcore_config_moved = false;
if ('plugins' == $type || 'themes' == $type) $updraftplus->log("Top-level entities being moved: ".implode(', ', array_keys($upgrade_files)));
foreach ($upgrade_files as $file => $filestruc) {
if (empty($file)) continue;
if ($dest_dir.$file == $updraft_dir) {
$updraftplus->log('Skipping attempt to replace updraft_dir whilst processing '.$type);
continue;
}
// Correctly restore files in 'others' in no directory that were wrongly backed up in versions 1.4.0 - 1.4.48
if (('others' == $type || 'wpcore' == $type) && preg_match('/^([\-_A-Za-z0-9]+\.php)$/i', $file, $matches) && $wpfs->exists($working_dir . "/$file/$file")) {
if ('others' == $type) {
$updraftplus->log("Found file: $file/$file: presuming this is a backup with a known fault (backup made with versions 1.4.0 - 1.4.48, and sometimes up to 1.6.55 on some Windows servers); will rename to simply $file", 'notice-restore');
} else {
$updraftplus->log("Found file: $file/$file: presuming this is a backup with a known fault (backup made with versions before 1.6.55 in certain situations on Windows servers); will rename to simply $file", 'notice-restore');
}
$updraftplus->log("$file/$file: rename to $file");
$file = $matches[1];
$tmp_file = rand(0, 999999999).'.php';
// Rename directory
if (!$wpfs->move($working_dir . "/$file", $working_dir . "/".$tmp_file, true)) {
$this->restore_log_permission_failure_message($working_dir, 'Move '. $working_dir . "/$file -> ".$working_dir . "/".$tmp_file, 'Destination');
}
if (!$wpfs->move($working_dir . "/$tmp_file/$file", $working_dir ."/".$file, true)) {
$this->restore_log_permission_failure_message($working_dir, 'Move '.$working_dir . "/$tmp_file/$file -> ".$working_dir ."/".$file, 'Destination');
}
if (!$wpfs->rmdir($working_dir . "/$tmp_file", false)) {
$this->restore_log_permission_failure_message($working_dir, 'Delete '.$working_dir . "/$tmp_file");
}
}
if ('wp-config.php' == $file && 'wpcore' == $type) {
if (empty($this->restore_options['updraft_restorer_wpcore_includewpconfig'])) {
$updraftplus->log_e('wp-config.php from backup: will restore as wp-config-backup.php', 'updraftplus');
if (!$wpfs->move($working_dir . "/$file", $working_dir . "/wp-config-backup.php", true)) {
$this->restore_log_permission_failure_message($working_dir, 'Move '.$working_dir . "/$file -> ".$working_dir . "/wp-config-backup.php", 'Destination');
}
$file = "wp-config-backup.php";
$wpcore_config_moved = true;
} else {
$updraftplus->log_e("wp-config.php from backup: restoring (as per user's request)", 'updraftplus');
}
} elseif ('wpcore' == $type && 'wp-config-backup.php' == $file && $wpcore_config_moved) {
// The file is already gone; nothing to do
continue;
}
// Sanity check (should not be possible as these were excluded at backup time)
if (in_array($file, $do_not_overwrite)) continue;
if (('object-cache.php' == $file || 'advanced-cache.php' == $file) && 'others' == $type) {
if (false == apply_filters('updraftplus_restorecachefiles', true, $file)) {
$nfile = preg_replace('/\.php$/', '-backup.php', $file);
if (!$wpfs->move($working_dir . "/$file", $working_dir . "/" .$nfile, true)) {
$this->restore_log_permission_failure_message($working_dir, 'Move '. $working_dir . '/' . $file .' -> '.$working_dir . '/' . $nfile, 'Destination');
}
$file = $nfile;
}
} elseif (('object-cache-backup.php' == $file || 'advanced-cache-backup.php' == $file) && 'others' == $type) {
if (!$wpfs->delete($working_dir."/".$file)) {
$this->restore_log_permission_failure_message($working_dir, 'Delete '.$working_dir."/".$file);
}
continue;
}
// First, move the existing one, if necessary (may not be present)
if ($wpfs->exists($dest_dir.$file)) {
if (self::MOVEIN_MAKE_BACKUP_OF_EXISTING == $preserve_existing) {
if (!$wpfs->move($dest_dir.$file, $dest_dir.$file.'-old', true)) {
$this->restore_log_permission_failure_message($dest_dir, 'Move '. $dest_dir.$file.' -> '.$dest_dir.$file.'-old', 'Destination');
return new WP_Error('old_move_failed', $this->strings['old_move_failed']." ($dest_dir$file)");
}
} elseif (self::MOVEIN_OVERWRITE_NO_BACKUP == $preserve_existing) {
if (!$wpfs->delete($dest_dir.$file, true)) {
$this->restore_log_permission_failure_message($dest_dir, 'Delete '.$dest_dir.$file);
return new WP_Error('old_delete_failed', $this->strings['old_delete_failed']." ($file)");
}
}
}
// Secondly, move in the new one
$is_dir = $wpfs->is_dir($working_dir."/".$file);
if (self::MOVEIN_DO_NOTHING_IF_EXISTING == $preserve_existing && $wpfs->exists($dest_dir.$file)) {
// Something exists - no move. Remove it from the temporary directory - so that it will be clean later
@$wpfs->delete($working_dir.'/'.$file, true);
// The $is_dir check was added in version 1.11.18; without this, files in the top-level that weren't in the first archive didn't get over-written
} elseif (self::MOVEIN_COPY_IN_CONTENTS != $preserve_existing || !$wpfs->exists($dest_dir.$file) || !$is_dir) {
if ($wpfs->move($working_dir."/".$file, $dest_dir.$file, true)) {
if ($send_actions) do_action('updraftplus_restored_'.$type.'_one', $file);
// Make sure permissions are at least as great as those of the parent
if ($is_dir) {
// This method is broken due to https://core.trac.wordpress.org/ticket/26598
if (empty($chmod)) $chmod = octdec(sprintf("%04d", $this->get_current_chmod($dest_dir, $wpfs)));
if (!empty($chmod)) $this->chmod_if_needed($dest_dir.$file, $chmod, false, $wpfs);
}
} else {
$this->restore_log_permission_failure_message($dest_dir, 'Move '. $working_dir."/".$file." -> ".$dest_dir.$file, 'Destination');
return new WP_Error('move_failed', $this->strings['move_failed'], $working_dir."/".$file." -> ".$dest_dir.$file);
}
} elseif (self::MOVEIN_COPY_IN_CONTENTS == $preserve_existing && !empty($filestruc['files'])) {
// The directory ($dest_dir) already exists, and we've been requested to copy-in. We need to perform the recursive copy-in
// $filestruc['files'] is then a new structure like $upgrade_files
// First pass: create directory structure
// Get chmod value for the parent directory, and re-use it (instead of passing false)
// This method is broken due to https://core.trac.wordpress.org/ticket/26598
if (empty($chmod)) $chmod = octdec(sprintf("%04d", $this->get_current_chmod($dest_dir, $wpfs)));
// Copy in the files. This also needs to make sure the directories exist, in case the zip file lacks entries
$delete_root = ('others' == $type || 'wpcore' == $type) ? false : true;
$copy_in = $this->copy_files_in($working_dir.'/'.$file, $dest_dir.$file, $filestruc['files'], $chmod, $delete_root);
if (!empty($chmod)) $this->chmod_if_needed($dest_dir.$file, $chmod, false, $wpfs);
if (is_wp_error($copy_in) || !$copy_in) {
$this->restore_log_permission_failure_message($dest_dir, 'Move '. $working_dir."/".$file." -> ".$dest_dir.$file, 'Destination');
}
if (is_wp_error($copy_in)) return $copy_in;
if (!$copy_in) return new WP_Error('move_failed', $this->strings['move_failed'], "(2) ".$working_dir.'/'.$file." -> ".$dest_dir.$file);
if (!$wpfs->rmdir($working_dir.'/'.$file)) {
$this->restore_log_permission_failure_message($working_dir, 'Delete '.$working_dir.'/'.$file);
}
} else {
if (!$wpfs->rmdir($working_dir.'/'.$file)) {
$this->restore_log_permission_failure_message($working_dir, 'Delete '.$working_dir.'/'.$file);
}
}
}
return true;
}
/**
* $dest_dir must already exist
*
* @param string $source_dir source directory
* @param string $dest_dir destintion directory
* @param string $files files to be placed in directory
* @param boolean $chmod chmod type
* @param boolean $delete_source indicate whether source needs deleting
* @return boolean
*/
private function copy_files_in($source_dir, $dest_dir, $files, $chmod = false, $delete_source = false) {
global $wp_filesystem, $updraftplus;
foreach ($files as $rname => $rfile) {
if ('d' != $rfile['type']) {
// Delete it if it already exists (or perhaps WP does it for us)
if (!$wp_filesystem->move($source_dir.'/'.$rname, $dest_dir.'/'.$rname, true)) {
$this->restore_log_permission_failure_message($dest_dir, $source_dir.'/'.$rname.' -> '.$dest_dir.'/'.$rname, 'Destination');
return false;
}
} else {
// Directory
if ($wp_filesystem->is_file($dest_dir.'/'.$rname)) @$wp_filesystem->delete($dest_dir.'/'.$rname, false, 'f');
// No such directory yet: just move it
if (!$wp_filesystem->is_dir($dest_dir.'/'.$rname)) {
if (!$wp_filesystem->move($source_dir.'/'.$rname, $dest_dir.'/'.$rname, false)) {
$this->restore_log_permission_failure_message($dest_dir, 'Move '.$source_dir.'/'.$rname.' -> '.$dest_dir.'/'.$rname, 'Destination');
$updraftplus->log_e('Failed to move directory (check your file permissions and disk quota): %s', $source_dir.'/'.$rname." -> ".$dest_dir.'/'.$rname);
return false;
}
} elseif (!empty($rfile['files'])) {
// There is a directory - and we want to to copy in
$docopy = $this->copy_files_in($source_dir.'/'.$rname, $dest_dir.'/'.$rname, $rfile['files'], $chmod, false);
if (is_wp_error($docopy)) return $docopy;
if (false === $docopy) {
return false;
}
} else {
// There is a directory: but nothing to copy in to it
@$wp_filesystem->rmdir($source_dir.'/'.$rname);
}
}
}
// We are meant to leave the working directory empty. Hence, need to rmdir() once a directory is empty. But not the root of it all in case of others/wpcore.
if ($delete_source || strpos($source_dir, '/') !== false) {
if (!$wp_filesystem->rmdir($source_dir, false)) {
$this->restore_log_permission_failure_message($source_dir, 'Delete '.$source_dir);
}
}
return true;
}
/**
* Pre-flight check: chance to complain and abort before anything at all is done
*
* @param array $backup_files An array of backup files
* @param string $type Type of file
* @param array $info Information about the backup
* @param array $continuation_data Information about continuing from an already-begun restore
* @return boolean|WP_Error
*/
public function pre_restore_backup($backup_files, $type, $info, $continuation_data = null) {
if (is_string($backup_files)) $backup_files = array($backup_files);
if ('more' == $type) {
$this->skin->feedback($this->strings['not_possible']);
return;
}
// Ensure access to the indicated directory - and to WP_CONTENT_DIR (in which we use upgrade/)
$need_these = array(WP_CONTENT_DIR);
if (!empty($info['path'])) $need_these[] = $info['path'];
$res = $this->wp_upgrader->fs_connect($need_these);
if (false === $res || is_wp_error($res)) return $res;
// Check upgrade directory is writable (instead of having non-obvious messages when we try to write)
// In theory, this is redundant (since we already checked for access to WP_CONTENT_DIR); but in practice, this extra check has been needed
global $wp_filesystem, $updraftplus, $updraftplus_admin, $updraftplus_addons_migrator;
if (empty($this->pre_restore_updatedir_writable)) {
$upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
@$wp_filesystem->mkdir($upgrade_folder, octdec($this->calculate_additive_chmod_oct(FS_CHMOD_DIR, 0775)));
if (!$wp_filesystem->is_dir($upgrade_folder)) {
return new WP_Error('no_dir', sprintf(__('UpdraftPlus needed to create a %s in your content directory, but failed - please check your file permissions and enable the access (%s)', 'updraftplus'), __('folder', 'updraftplus'), $upgrade_folder));
}
$rand_file = 'testfile_'.rand(0, 9999999).md5(microtime(true)).'.txt';
if ($wp_filesystem->put_contents($upgrade_folder.$rand_file, 'testing...')) {
@$wp_filesystem->delete($upgrade_folder.$rand_file);
$this->pre_restore_updatedir_writable = true;
} else {
$this->restore_log_permission_failure_message($upgrade_folder, 'Put contents '.$upgrade_folder.$rand_file, 'Destination');
return new WP_Error('no_file', sprintf(__('UpdraftPlus needed to create a %s in your content directory, but failed - please check your file permissions and enable the access (%s)', 'updraftplus'), __('file', 'updraftplus'), $upgrade_folder.$rand_file));
}
}
// Code below here assumes that we're dealing with file-based entities
if ('db' == $type) return true;
$wp_filesystem_dir = $this->get_wp_filesystem_dir($info['path']);
if (false === $wp_filesystem_dir) return false;
$ret_val = true;
$updraft_dir = $updraftplus->backups_dir_location();
if (!is_array($continuation_data) && (('plugins' == $type || 'uploads' == $type || 'themes' == $type) && (!is_multisite() || 0 !== $this->ud_backup_is_multisite || ('uploads' != $type || empty($updraftplus_addons_migrator->new_blogid))))) {
if (file_exists($updraft_dir.'/'.basename($wp_filesystem_dir)."-old")) {
$ret_val = new WP_Error('already_exists', sprintf(__('Existing unremoved folders from a previous restore exist (please use the "Delete Old Directories" button to delete them before trying again): %s', 'updraftplus'), $wp_filesystem_dir.'-old'));
}
}
if (!empty($this->ud_foreign)) {
$known_foreigners = apply_filters('updraftplus_accept_archivename', array());
if (!is_array($known_foreigners) || empty($known_foreigners[$this->ud_foreign])) {
return new WP_Error('uk_foreign', __('This version of UpdraftPlus does not know how to handle this type of foreign backup', 'updraftplus').' ('.$this->ud_foreign.')');
}
}
return $ret_val;
}
private function get_wp_filesystem_dir($path) {
global $wp_filesystem;
// Get the wp_filesystem location for the folder on the local install
switch ($path) {
case ABSPATH:
case '':
$wp_filesystem_dir = $wp_filesystem->abspath();
break;
case WP_CONTENT_DIR:
$wp_filesystem_dir = $wp_filesystem->wp_content_dir();
break;
case WP_PLUGIN_DIR:
$wp_filesystem_dir = $wp_filesystem->wp_plugins_dir();
break;
case WP_CONTENT_DIR . '/themes':
$wp_filesystem_dir = $wp_filesystem->wp_themes_dir();
break;
default:
$wp_filesystem_dir = $wp_filesystem->find_folder($path);
break;
}
if (!$wp_filesystem_dir) return false;
return untrailingslashit($wp_filesystem_dir);
}
private function can_version_ajax_restore($version) {
if (!defined('UPDRAFTPLUS_EXPERIMENTAL_AJAX_RESTORE') || !UPDRAFTPLUS_EXPERIMENTAL_AJAX_RESTORE) return false;
return ((version_compare($version, '2.0', '<') && version_compare($version, '1.11.10', '>=')) || (version_compare($version, '2.0', '>=') && version_compare($version, '2.11.10', '>='))) ? true : false;
}
/**
* $backup_file is just the basename, and must be a string; we expect the caller to deal with looping over an array (multi-archive sets). We do, however, record whether we have already unpacked an entity of the same type - so that we know to add (not replace).
*
* @param string $backup_file name of file being backed up
* @param string $type type of file
* @param array $info information array
* @param boolean $last_one indicate if this is the last file to be restored
* @param boolean $last_entity indicate if this is the last entity of this type to be restored
* @return WP_Error|Boolean - true if successful; otherwise false or an error
*/
private function restore_backup($backup_file, $type, $info, $last_one = false, $last_entity = false) {
if ('more' == $type) {
$this->skin->feedback($this->strings['not_possible']);
return false;
}
global $wp_filesystem, $updraftplus_addons_migrator, $updraftplus;
$updraftplus->log("restore_backup(backup_file=$backup_file, type=$type, info=".serialize($info).", last_one=$last_one)");
$get_dir = empty($info['path']) ? '' : $info['path'];
if (false === ($wp_filesystem_dir = $this->get_wp_filesystem_dir($get_dir))) return false;
if (empty($this->abspath)) $this->abspath = trailingslashit($wp_filesystem->abspath());
@set_time_limit(1800);
/*
TODO:
- The backup set may no longer be in the DB - a restore may have over-written it.
- UD might be installed, but not active. Test that too. (All combinations need testing - new/old UD vers, logged-in/not, etc.).
- logging
- authorisation on the AJAX call, given that our login may not even be valid any more.
- pass on the WP filesystem credentials somehow - they have been POSTed, and should be included in what's POSTed back.
- the restore function wants to know the UD version the backup set came from
- the restore function wants to know whether we're restoring an individual blog into a multisite
- the restore function has some things only done the first time, which isn't directly tracked (uses internal state instead, which won't work over AJAX)
- how to handle/set this->delete
- how to show the final result
- how to do the clear-up of restored stuff in the restore function
- remember, we need to do unauthenticated AJAX, as the authentication is happening via a different means. Use a separate procedure from the usual one (and no nonce, as login status may have changed).
*/
if (defined('UPDRAFTPLUS_EXPERIMENTAL_AJAX_RESTORE') && UPDRAFTPLUS_EXPERIMENTAL_AJAX_RESTORE && 'uploads' == $type) {
// Read this each time, as we don't know what might have been done in the mean-time (specifically with UD being replaced by a different UD from a backup). Of course, we know what the currently running process is capable of.
if (file_exists(UPDRAFTPLUS_DIR.'/updraftplus.php') && $fp = fopen(UPDRAFTPLUS_DIR.'/updraftplus.php', 'r')) {
$file_data = fread($fp, 1024);
if (preg_match("/Version: ([\d\.]+)(\r|\n)/", $file_data, $matches)) {
$ud_version = $matches[1];
}
fclose($fp);
}
if (!empty($ud_version) && $this->can_version_ajax_restore($ud_version) && !empty($this->ud_backup_info['timestamp'])) {
$nonce = $updraftplus->nonce;
if (!function_exists('crypt_random_string')) $updraftplus->ensure_phpseclib('Crypt_Random', 'Crypt/Random');
$this->ajax_restore_auth_code = bin2hex(crypt_random_string(32));
// TODO: Delete this when done, to prevent abuse
update_site_option('updraft_ajax_restore_'.$nonce, $this->ajax_restore_auth_code.':'.time());
$this->add_ajax_restore_admin_footer();
$print_last_one = ($last_one) ? "1" : "0";
// TODO: Also want the timestamp
// We don't bother to include info, as that is backup-independent information that can be re-created when needed
// TODO: Change to new log style, if ever using
echo '
'."\n"; $updraftplus->log("Deferring handling of uploads ($backup_file)"); echo "$backup_file: ".''.__('Deferring...', 'updraftplus').''; echo '
'; return true; } } // This returns the wp_filesystem path $working_dir = $this->unpack_package($backup_file, $this->delete, $type); if (is_wp_error($working_dir)) return $working_dir; $working_dir_localpath = WP_CONTENT_DIR.'/upgrade/'.basename($working_dir); @set_time_limit(1800); // We copy the variable because we may be importing with a different prefix (e.g. on multisite imports of individual blog data) // The filter allows you to restore to a completely different prefix - i.e. don't replace this site; possibly useful for testing the restore process (but not yet tested) $import_table_prefix = apply_filters('updraftplus_restore_table_prefix', $updraftplus->get_table_prefix(false)); $this->import_table_prefix = $import_table_prefix; $now_done = apply_filters('updraftplus_pre_restore_move_in', false, $type, $working_dir, $info, $this->ud_backup_info, $this, $wp_filesystem_dir); if (is_wp_error($now_done)) return $now_done; // A slightly ugly way of getting a particular result back if (is_string($now_done)) { $wp_filesystem_dir = $now_done; $now_done = false; $do_not_move_old = true; } if (!$now_done) { if ('db' == $type) { $rdb = $this->restore_backup_db($working_dir, $working_dir_localpath, $import_table_prefix); if (false === $rdb || is_wp_error($rdb)) return $rdb; } elseif ('others' == $type) { $dirname = basename($info['path']); // For foreign 'Simple Backup', we need to keep going down until we find wp-content if (empty($this->ud_foreign)) { $move_from = $working_dir; } else { $move_from = $this->search_for_folder('wp-content', $working_dir); if (!is_string($move_from)) return new WP_Error('not_found', __('The WordPress content folder (wp-content) was not found in this zip file.', 'updraftplus')); } // In this special case, the backup contents are not in a folder, so it is not simply a case of moving the folder around, but rather looping over all that we find // On subsequent archives of a multi-archive set, don't move anything; but do on the first $preserve_existing = isset($this->been_restored['others']) ? self::MOVEIN_COPY_IN_CONTENTS : self::MOVEIN_MAKE_BACKUP_OF_EXISTING; $preserve_existing = apply_filters('updraft_move_others_preserve_existing', $preserve_existing, $this->been_restored, $this->restore_options, $this->ud_backup_info); $new_move_from = apply_filters('updraft_restore_backup_move_from', $move_from, 'others', $this->restore_options, $this->ud_backup_info); if ($new_move_from != $move_from && 0 === strpos($new_move_from, $move_from)) { $new_suffix = substr($new_move_from, strlen($move_from)); $wp_filesystem_dir .= $new_suffix; $move_from = $new_move_from; } $move_in = $this->move_backup_in($move_from, trailingslashit($wp_filesystem_dir), $preserve_existing, array('plugins', 'themes', 'uploads', 'upgrade'), 'others'); if (is_wp_error($move_in)) return $move_in; if (!$move_in) return new WP_Error('new_move_failed', $this->strings['new_move_failed']); $this->been_restored['others'] = true; } else { // Default action: used for plugins, themes and uploads (and wpcore, via a filter) // Multi-archive sets: we record what we've already begun on, and on subsequent runs, copy in instead of replacing $movedin = apply_filters('updraftplus_restore_movein_'.$type, $working_dir, $this->abspath, $wp_filesystem_dir); // A filter, to allow add-ons to perform the install of non-standard entities, or to indicate that it's not possible if (false === $movedin) { $this->skin->feedback($this->strings['not_possible']); } elseif (is_wp_error($movedin)) { return $movedin; } elseif (true !== $movedin) { // We get the directory to move from early, in case there is a problem with the backup that affects the result - we want to detect that before moving existing data out of the way $short_circuit = false; // For foreign 'Simple Backup', we need to keep going down until we find wp-content if (empty($this->ud_foreign)) { $working_dir_use = $working_dir; } else { $working_dir_use = $this->search_for_folder('wp-content', $working_dir); if (!is_string($working_dir_use)) { if (empty($this->ud_foreign) || !apply_filters('updraftplus_foreign_allow_missing_entity', false, $type, $this->ud_foreign)) { return new WP_Error('not_found', __('The WordPress content folder (wp-content) was not found in this zip file.', 'updraftplus')); } else { $short_circuit = true; } } } // The backup may not actually have /$type, since that is info from the present site $move_from = $this->get_first_directory($working_dir_use, array(basename($info['path']), $type)); if (false !== $move_from) $move_from = apply_filters('updraft_restore_backup_move_from', $move_from, $type, $this->restore_options, $this->ud_backup_info); if (false === $move_from) { if (!empty($this->ud_foreign) && !apply_filters('updraftplus_foreign_allow_missing_entity', false, $type, $this->ud_foreign)) { return new WP_Error('new_move_failed', $this->strings['new_move_failed']); } } // On the first time, create the -old directory in updraft_dir // (Old style was: On the first time, move the existing data to -old) if (!isset($this->been_restored[$type]) && empty($do_not_move_old)) { $this->move_existing_to_old($type, $get_dir, $wp_filesystem, $wp_filesystem_dir); } if (empty($short_circuit)) { if (false === $move_from) { if (!empty($this->ud_foreign) && !apply_filters('updraftplus_foreign_allow_missing_entity', false, $type, $this->ud_foreign)) { return new WP_Error('new_move_failed', $this->strings['new_move_failed']); } } else { $this->skin->feedback($this->strings['moving_backup']); $move_in = $this->move_backup_in($move_from, trailingslashit($wp_filesystem_dir), self::MOVEIN_COPY_IN_CONTENTS, array(), $type); if (is_wp_error($move_in)) return $move_in; if (!$move_in) return new WP_Error('new_move_failed', $this->strings['new_move_failed']); if (!$wp_filesystem->rmdir($move_from)) { $this->restore_log_permission_failure_message(dirname($move_from), 'Delete '.$move_from); } } } } $this->been_restored[$type] = true; } } $attempt_delete = (!empty($this->ud_foreign) && !$last_one) ? false : true; if ($attempt_delete) { // Non-recursive, so the directory needs to be empty $this->skin->feedback($this->strings['cleaning_up']); if (!empty($do_not_move_old)) @$wp_filesystem->delete($working_dir.'/'.$type); // Foreign backups can contain extra data and thus leave stuff behind, thus causing errors $recurse = empty($this->ud_foreign) ? false : true; $recurse = apply_filters('updraftplus_restore_delete_recursive', $recurse, $this->ud_foreign, $this->restore_options, $type); if (!$wp_filesystem->delete($working_dir, $recurse)) { // Can remove this after 1-Jan-2015; or at least, make it so that it requires the version number to be present. $fixed_it_now = false; // Deal with a corner-case in version 1.8.5 if ('uploads' == $type && (empty($this->created_by_version) || (version_compare($this->created_by_version, '1.8.5', '>=') && version_compare($this->created_by_version, '1.8.8', '<')))) { $updraftplus->log("Clean-up failed with uploads: will attempt 1.8.5-1.8.7 fix (".$this->created_by_version.")"); $move_in = @$this->move_backup_in(dirname($move_from), trailingslashit($wp_filesystem_dir), 3, array(), $type); $updraftplus->log("Result: ".serialize($move_in)); if ($wp_filesystem->delete($working_dir)) $fixed_it_now = true; } if (file_exists($working_dir.DIRECTORY_SEPARATOR.'updraftplus-manifest.json')) { // Before we cleanup and remove the manifest check if this is the last entity of this type, if it is then we want to remove anything that no longer exists in this manifest if ($last_entity) { $incremental_restore_prune = $this->incremental_restore_prune_files($working_dir, $type); if (is_wp_error($incremental_restore_prune)) return $incremental_restore_prune; } $wp_filesystem->delete($working_dir.DIRECTORY_SEPARATOR.'updraftplus-manifest.json'); if ($wp_filesystem->delete($working_dir)) $fixed_it_now = true; } if (!$fixed_it_now) { $updraftplus->log_e('Error: %s', $this->strings['delete_failed'].' ('.$working_dir.')'); // List contents // No need to make this a restoration-aborting error condition - it's not $dirlist = $wp_filesystem->dirlist($working_dir, true, true); if (is_array($dirlist)) { $updraftplus->log(__('Files found:', 'updraftplus'), 'notice-restore'); foreach ($dirlist as $name => $struc) { $updraftplus->log("* $name", 'notice-restore'); } } else { $updraftplus->log_e('Unable to enumerate files in that directory.'); } } } } // Permissions changes (at the top level - i.e. this does not apply if using recursion) are now *additive* - i.e. there's no danger of permissions being removed from what's on-disk switch ($type) { case 'wpcore': $this->chmod_if_needed($wp_filesystem_dir, FS_CHMOD_DIR, false, $wp_filesystem); // In case we restored a .htaccess which is incorrect for the local setup $this->flush_rewrite_rules(); break; case 'uploads': $this->chmod_if_needed($wp_filesystem_dir, FS_CHMOD_DIR, false, $wp_filesystem); break; case 'themes': // Cherry Framework needs its cache files removing after migration if ((empty($this->old_siteurl) || ($this->old_siteurl != $this->our_siteurl)) && function_exists('glob')) { $cherry_child = glob(WP_CONTENT_DIR.'/themes/theme*'); if (is_array($cherry_child)) { foreach ($cherry_child as $theme) { if (file_exists($theme.'/style.less.cache')) unlink($theme.'/style.less.cache'); if (file_exists($theme.'/bootstrap/less/bootstrap.less.cache')) unlink($theme.'/bootstrap/less/bootstrap.less.cache'); } } } break; case 'db': if (function_exists('wp_cache_flush')) wp_cache_flush(); do_action('updraftplus_restored_db', array( 'expected_oldsiteurl' => $this->old_siteurl, 'expected_oldhome' => $this->old_home, 'expected_oldcontent' => $this->old_content ), $import_table_prefix); // N.B. flush_rewrite_rules() causes $wp_rewrite to become up to date again - important for the no_mod_rewrite() call $this->flush_rewrite_rules(); if ($updraftplus->mod_rewrite_unavailable()) { $updraftplus->log("Using Apache, with permalinks (".get_option('permalink_structure').") but no mod_rewrite enabled - enable it to make your permalinks work"); $warn_no_rewrite = sprintf(__('You are using the %s webserver, but do not seem to have the %s module loaded.', 'updraftplus'), 'Apache', 'mod_rewrite').' '.sprintf(__('You should enable %s to make any pretty permalinks (e.g. %s) work', 'updraftplus'), 'mod_rewrite', 'http://example.com/my-page/'); $updraftplus->log($warn_no_rewrite, 'warning-restore'); } break; default: $this->chmod_if_needed($wp_filesystem_dir, FS_CHMOD_DIR, false, $wp_filesystem); } // db was already done if ('db' != $type) do_action('updraftplus_restored_'.$type); return true; } /** * This method will read in the latest manifest file for an entity type and start the file prune. * * @param string $working_dir - the directory we are working in * @param string $type - the type of file * @return boolean|WP_Error */ private function incremental_restore_prune_files($working_dir, $type) { // Check file exists again just in case it some how got removed if (file_exists($working_dir.DIRECTORY_SEPARATOR.'updraftplus-manifest.json')) { $entity_manifest = file_get_contents($working_dir.DIRECTORY_SEPARATOR.'updraftplus-manifest.json'); $entity_manifest = json_decode($entity_manifest, true); $base_path = trailingslashit(WP_CONTENT_DIR); $path = $base_path.$type; return $this->incremental_restore_scan_dir($base_path, $path, 1, $entity_manifest); } else { return new WP_Error('manifest_not_found', $this->strings['manifest_not_found']); } } /** * This method will recursively scan each directory to the given listed_level which is located in the manifest and prune and files or folders that do not exist in the manifest. * * @param string $base_path - the base path of the entity type * @param string $path - the current path we are scanning * @param integer $current_level - the level we are currently scanning at * @param array $entity_manifest - the manifest array which includes the listed_level, directories and files to keep * @return boolean|WP_Error */ private function incremental_restore_scan_dir($base_path, $path, $current_level, $entity_manifest) { global $wp_filesystem; $directroy_level = $entity_manifest['listed_levels']; $entity_directories = $entity_manifest['contents']['directories']; $entity_files = $entity_manifest['contents']['files']; if (!isset($directroy_level) || !isset($entity_directories) || !isset($entity_files)) return new WP_Error('read_manifest_failed', $this->strings['read_manifest_failed']); $directory_files = $wp_filesystem->dirlist($path); if (isset($directory_files)) { foreach ($directory_files as $file => $filestruc) { if ($wp_filesystem->is_dir($path . DIRECTORY_SEPARATOR . $file)) { $directory = $path . DIRECTORY_SEPARATOR . $file; // Check if we should go deeper in the file path, if not then check if this directory exists in the manifest, if not then remove it. if ($current_level + 1 < $directroy_level) { $incremental_restore_prune = $this->incremental_restore_scan_dir($base_path, $directory, $current_level + 1, $entity_manifest); if (is_wp_error($incremental_restore_prune)) return $incremental_restore_prune; } else { $directory = str_replace($base_path, "", $directory); if (!in_array($directory, $entity_directories)) { $wp_filesystem->delete($base_path . $directory, true); } } } else { $file = str_replace($base_path, "", $path . DIRECTORY_SEPARATOR . $file); if (!in_array($file, $entity_files)) { $wp_filesystem->delete($base_path . $file, false); } } } return true; } else { return new WP_Error('read_working_dir_failed', $this->strings['read_working_dir_failed']); } } private function move_existing_to_old($type, $get_dir, $wp_filesystem, $wp_filesystem_dir) { if (apply_filters('updraft_move_existing_to_old_short_circuit', false, $type, $this->restore_options)) { // Users of the filter should do their own logging return; } global $updraftplus; $updraft_dir = $updraftplus->backups_dir_location(); // Firstly, if there's already an '-old' directory, get rid of it // Try filesystem-level move $old_dir = $updraft_dir.'/'.$type.'-old'; if (is_dir($old_dir)) { $updraftplus->log_e('%s: This directory already exists, and will be replaced', $old_dir); UpdraftPlus_Filesystem_Functions::remove_local_directory($old_dir); } $move_old_destination = apply_filters('updraftplus_restore_move_old_mode', 0, $type, $this->restore_options); if (0 == $move_old_destination && @mkdir($old_dir)) { $updraftplus->log("Moving old data: filesystem method / updraft_dir is potentially possible"); $move_old_destination = 1; } // Try wp_filesystem instead if ($wp_filesystem->exists($wp_filesystem_dir."-old")) { // Is better to warn and delete the restore than abort mid-restore and leave inconsistent site $updraftplus->log_e('%s: This directory already exists, and will be replaced', $wp_filesystem_dir."-old"); // In theory, supplying true as the 3rd parameter achieves this; in practice, not always so (leads to support requests) $wp_filesystem->delete($wp_filesystem_dir."-old", true); if ($wp_filesystem->exists($wp_filesystem_dir."-old")) { $updraftplus->log("Failed to remove existing directory (".$wp_filesystem_dir."-old"); $failed_to_remove = true; } } if (-1 != $move_old_destination && empty($failed_to_remove) && @$wp_filesystem->mkdir($wp_filesystem_dir."-old")) { $updraftplus->log("Moving old data: can potentially use wp_filesystem method / -old"); $move_old_destination += 2; } if (0 == $move_old_destination) { $updraftplus->log_e("File permissions do not allow the old data to be moved and retained; instead, it will be deleted."); } $this->skin->feedback($this->strings['moving_old']); // Firstly, try direct filesystem method into updraft_dir if ($move_old_destination > 0 && 1 == $move_old_destination % 2) { // The final 'true' forces direct filesystem access $move_old = @$this->move_backup_in($get_dir, $updraft_dir.'/'.$type.'-old/', 3, array(), $type, false, true); if (is_wp_error($move_old)) $updraftplus->log_wp_error($move_old); } // Try wp_filesystem method into -old if that failed if (2 >= $move_old_destination && (0 == $move_old_destination % 2 || (!empty($move_old) && is_wp_error($move_old)))) { $move_old = @$this->move_backup_in($wp_filesystem_dir, $wp_filesystem_dir."-old/", 3, array(), $type); if (is_wp_error($move_old)) $updraftplus->log_wp_error($move_old); } // Finally, when all else fails, nuke it if (-1 == $move_old_destination || 0 == $move_old_destination || (!empty($move_old) && is_wp_error($move_old))) { if (-1 == $move_old_destination) { $updraftplus->log("$type: $wp_filesystem_dir: deleting contents"); } else { $updraftplus->log("$type: $wp_filesystem_dir: deleting contents (as attempts to copy failed)"); } $del_files = $wp_filesystem->dirlist($wp_filesystem_dir, true, false); if (empty($del_files)) $del_files = array(); foreach ($del_files as $file => $filestruc) { if (empty($file)) continue; if (!$wp_filesystem->delete($wp_filesystem_dir.'/'.$file, true)) { $this->restore_log_permission_failure_message($wp_filesystem_dir, 'Delete '.$wp_filesystem_dir.'/'.$file); } } } } private function add_ajax_restore_admin_footer() { static $already = false; if (!$already) { $already = true; add_action('admin_footer', array($this, 'admin_footer_ajax_restore')); } } /** * Unused */ public function admin_footer_ajax_restore() { // TODO: The timestamp parameter is mandatory - we should abort (earlier) if there isn't one. global $updraftplus; $nonce = $updraftplus->nonce; // TODO: Apparently empty $auth_code = esc_js($this->ajax_restore_auth_code); echo <<