PluginProbe
UpdraftPlus: WP Backup & Migration Plugin / 1.9.31
UpdraftPlus: WP Backup & Migration Plugin v1.9.31
1.26.7 1.26.6 1.26.5 1.26.4 1.26.3 1.9.19 1.9.25 1.9.26 1.9.30 1.9.31 1.9.32 1.9.4 1.9.40 1.9.41 1.9.42 1.9.43 1.9.44 1.9.45 1.9.46 1.9.5 1.9.50 1.9.51 1.9.60 1.9.62 1.9.63 All 371 releases
updraftplus / restorer.php

restorer.php in UpdraftPlus: WP Backup & Migration Plugin 1.9.31, at restorer.php

1,762 lines 79.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed');
3
4 if (!class_exists('WP_Upgrader')) require_once(ABSPATH.'wp-admin/includes/class-wp-upgrader.php');
5
6 class Updraft_Restorer extends WP_Upgrader {
7
8 public $ud_backup_is_multisite = -1;
9
10 private $is_multisite;
11
12 // This is just used so far for detecting whether we're on the second run for an entity or not.
13 public $been_restored = array();
14 private $tables_been_dropped = array();
15
16 public $delete = false;
17
18 private $created_by_version = false;
19
20 private $ud_backup_info;
21 public $ud_foreign;
22
23 # The default of false means "use the global $wpdb"
24 private $wpdb_obj = false;
25
26 private $line_last_logged = 0;
27
28 public function __construct($skin = null, $info = null, $shortinit = false) {
29
30 global $wpdb;
31 // Line up a wpdb-like object to use
32 $this->use_wpdb = ((!function_exists('mysql_query') && !function_exists('mysqli_query')) || !$wpdb->is_mysql || !$wpdb->ready) ? true : false;
33
34 if (false == $this->use_wpdb) {
35 // We have our own extension which drops lots of the overhead on the query
36 $wpdb_obj = new UpdraftPlus_WPDB(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);
37 // Was that successful?
38 if (!$wpdb_obj->is_mysql || !$wpdb_obj->ready) {
39 $this->use_wpdb = true;
40 } else {
41 $this->wpdb_obj = $wpdb_obj;
42 $this->mysql_dbh = $wpdb_obj->updraftplus_getdbh();
43 $this->use_mysqli = $wpdb_obj->updraftplus_use_mysqli();
44 }
45 }
46
47 if ($shortinit) return;
48 $this->ud_backup_info = $info;
49 $this->ud_foreign = (empty($info['meta_foreign'])) ? false : $info['meta_foreign'];
50 parent::__construct($skin);
51 $this->init();
52 $this->backup_strings();
53 $this->is_multisite = is_multisite();
54 }
55
56 function backup_strings() {
57 $this->strings['not_possible'] = __('UpdraftPlus is not able to directly restore this kind of entity. It must be restored manually.','updraftplus');
58 $this->strings['no_package'] = __('Backup file not available.','updraftplus');
59 $this->strings['copy_failed'] = __('Copying this entity failed.','updraftplus');
60 $this->strings['unpack_package'] = __('Unpacking backup...','updraftplus');
61 $this->strings['decrypt_database'] = __('Decrypting database (can take a while)...','updraftplus');
62 $this->strings['decrypted_database'] = __('Database successfully decrypted.','updraftplus');
63 $this->strings['moving_old'] = __('Moving old data out of the way...','updraftplus');
64 $this->strings['moving_backup'] = __('Moving unpacked backup into place...','updraftplus');
65 $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');
66 $this->strings['cleaning_up'] = __('Cleaning up rubbish...','updraftplus');
67 $this->strings['old_move_failed'] = __('Could not move old files out of the way.','updraftplus').' '.__('You should check the file permissions in your WordPress installation', 'updraftplus');
68 $this->strings['old_delete_failed'] = __('Could not delete old directory.','updraftplus');
69 $this->strings['new_move_failed'] = __('Could not move new files into place. Check your wp-content/upgrade folder.','updraftplus');
70 $this->strings['move_failed'] = __('Could not move the files into place. Check your file permissions.','updraftplus');
71 $this->strings['delete_failed'] = __('Failed to delete working directory after restoring.','updraftplus');
72 $this->strings['multisite_error'] = __('You are running on WordPress multisite - but your backup is not of a multisite site.', 'updraftplus');
73 $this->strings['unpack_failed'] = __('Failed to unpack the archive', 'updraftplus');
74 }
75
76 # 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!)
77 # Subsequently, we have also added the ability to unpack tarballs
78 private function unpack_package_archive($package, $delete_package = true, $type = false) {
79
80 if (!empty($this->ud_foreign) && !empty($this->ud_foreign_working_dir)) {
81 if (is_dir($this->ud_foreign_working_dir)) {
82 return $this->ud_foreign_working_dir;
83 } else {
84 global $updraftplus;
85 $updraftplus->log('Previously unpacked directory seems to have disappeared; will unpack again');
86 }
87 }
88
89 global $wp_filesystem, $updraftplus;
90
91 $packsize = round(filesize($package)/1048576, 1).' Mb';
92
93 $this->skin->feedback($this->strings['unpack_package'].' ('.basename($package).', '.$packsize.')');
94
95 $upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
96
97 //Clean up contents of upgrade directory beforehand.
98 $upgrade_files = $wp_filesystem->dirlist($upgrade_folder);
99 if ( !empty($upgrade_files) ) {
100 foreach ( $upgrade_files as $file )
101 $wp_filesystem->delete($upgrade_folder . $file['name'], true);
102 }
103
104 //We need a working directory
105 #This is the only change from the WP core version - minimise path length
106 #$working_dir = $upgrade_folder . basename($package, '.zip');
107 $working_dir = $upgrade_folder . substr(md5($package), 0, 8);
108
109 // Clean up working directory
110 if ( $wp_filesystem->is_dir($working_dir) )
111 $wp_filesystem->delete($working_dir, true);
112
113 // Unzip package to working directory
114 if ('.zip' == strtolower(substr($package, -4, 4))) {
115 $result = unzip_file( $package, $working_dir );
116 } elseif ('.tar' == strtolower(substr($package, -4, 4)) || '.tar.gz' == strtolower(substr($package, -7, 7)) || '.tar.bz2' == strtolower(substr($package, -8, 8))) {
117 if (!class_exists('UpdraftPlus_Archive_Tar')) {
118 if (false === strpos(get_include_path(), UPDRAFTPLUS_DIR.'/includes/PEAR')) set_include_path(UPDRAFTPLUS_DIR.'/includes/PEAR'.PATH_SEPARATOR.get_include_path());
119
120 require_once(UPDRAFTPLUS_DIR.'/includes/PEAR/Archive/Tar.php');
121 $p_compress = null;
122 if ('.tar.gz' == strtolower(substr($package, -7, 7))) {
123 $p_compress = 'gz';
124 } elseif ('.tar.bz2' == strtolower(substr($package, -8, 8))) {
125 $p_compress = 'bz2';
126 }
127
128 # It's not pretty. But it works.
129 if (is_a($wp_filesystem, 'WP_Filesystem_Direct')) {
130 $extract_dir = $working_dir;
131 } else {
132 $updraft_dir = $updraftplus->backups_dir_location();
133 if (!$updraftplus->really_is_writable($updraft_dir)) {
134 $updraftplus->log_e("Backup directory (%s) is not writable, or does not exist.", $updraft_dir);
135 $result = new WP_Error('unpack_failed', $this->strings['unpack_failed'], $tar->extract);
136 } else {
137 $extract_dir = $updraft_dir.'/'.basename($working_dir).'-old';
138 if (file_exists($extract_dir)) $updraftplus->remove_local_directory($extract_dir);
139 $updraftplus->log("Using a temporary folder to extract before moving over WPFS: $extract_dir");
140 }
141 }
142 # 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.
143
144 if (empty($result)) {
145
146 $this->ud_extract_count = 0;
147 $this->ud_working_dir = trailingslashit($working_dir);
148 $this->ud_extract_dir = untrailingslashit($extract_dir);
149 $this->ud_made_dirs = array();
150 add_filter('updraftplus_tar_wrote', array($this, 'tar_wrote'), 10, 2);
151 $tar = new UpdraftPlus_Archive_Tar($package, $p_compress);
152 $result = $tar->extract($extract_dir, false);
153 if (!is_a($wp_filesystem, 'WP_Filesystem_Direct')) $updraftplus->remove_local_directory($extract_dir);
154 if (true != $result) {
155 $result = new WP_Error('unpack_failed', $this->strings['unpack_failed'], $result);
156 } else {
157 if (!is_a($wp_filesystem, 'WP_Filesystem_Direct')) {
158 $updraftplus->log('Moved unpacked tarball contents');
159 }
160 }
161 remove_filter('updraftplus_tar_wrote', array($this, 'tar_wrote'), 10, 2);
162 }
163
164 }
165 }
166
167 // Once extracted, delete the package if required.
168 if ( $delete_package )
169 unlink($package);
170
171 if ( is_wp_error($result) ) {
172 $wp_filesystem->delete($working_dir, true);
173 if ( 'incompatible_archive' == $result->get_error_code() ) {
174 return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $result->get_error_data() );
175 }
176 return $result;
177 }
178
179 if (!empty($this->ud_foreign)) {
180 $this->ud_foreign_working_dir = $working_dir;
181 # Zip containing an SQL file. We only know of one pattern.
182 if ('db' === $type) {
183 $basepack = basename($package, '.zip');
184 if ($wp_filesystem->exists($working_dir.'/'.$basepack.'.sql')) {
185 $wp_filesystem->move($working_dir.'/'.$basepack.'.sql', $working_dir . "/backup.db", true);
186 $updraftplus->log("Moving database file $basepack.sql to backup.db");
187 }
188 }
189 }
190
191 return $working_dir;
192 }
193
194 public function tar_wrote($result, $file) {
195 if (0 !== strpos($file, $this->ud_extract_dir)) return false;
196 global $wp_filesystem, $updraftplus;
197 if (!is_a($wp_filesystem, 'WP_Filesystem_Direct')) {
198 $modint = 100;
199 $leaf = substr($file, strlen($this->ud_extract_dir));
200 $dirname = dirname($leaf);
201 $need_dirs = explode('/', $dirname);
202 if (empty($this->ud_made_dirs[$dirname])) {
203 $cdir = '';
204 foreach ($need_dirs as $ndir) {
205 $cdir .= ($cdir) ? '/'.$ndir : $ndir;
206 if (empty($this->ud_made_dirs[$cdir])) {
207 if ( !$wp_filesystem->mkdir( $this->ud_working_dir.$cdir, FS_CHMOD_DIR) && ! $wp_filesystem->is_dir($this->ud_working_dir.$cdir) ) {
208 $updraftplus->log("Failed to create WPFS directory: ".$this->ud_working_dir.$cdir);
209 return false;
210 } else {
211 $this->ud_made_dirs[$cdir] = true;
212 }
213 }
214 }
215 }
216 $put = $wp_filesystem->put_contents($this->ud_working_dir.$leaf, file_get_contents($file));
217 if (is_wp_error($put)) $updraftplus->log_wp_error($put);
218 @unlink($file);
219 } else {
220 $modint = 500;
221 $put = true;
222 }
223 if ($put) {
224 $this->ud_extract_count++;
225 if ($this->ud_extract_count % $modint == 0) {
226 $updraftplus->log_e("%s files have been extracted", $this->ud_extract_count);
227 }
228 }
229 return ($put == true);
230 }
231
232 // This returns a wp_filesystem location (and we musn't change that, as we must retain compatibility with the class parent)
233 function unpack_package($package, $delete_package = true, $type = false) {
234
235 global $wp_filesystem, $updraftplus;
236
237 $updraft_dir = $updraftplus->backups_dir_location();
238
239 // If not database, then it is a zip - unpack in the usual way
240 #if (!preg_match('/db\.gz(\.crypt)?$/i', $package)) return parent::unpack_package($updraft_dir.'/'.$package, $delete_package);
241 if (!preg_match('/db\.gz(\.crypt)?$/i', $package) && !preg_match('/\.sql(\.gz)?$/i', $package)) return $this->unpack_package_archive($updraft_dir.'/'.$package, $delete_package, $type);
242
243 $backup_dir = $wp_filesystem->find_folder($updraft_dir);
244
245 // Unpack a database. The general shape of the following is copied from class-wp-upgrader.php
246
247 @set_time_limit(1800);
248
249 $this->skin->feedback('unpack_package');
250
251 $upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
252 @$wp_filesystem->mkdir($upgrade_folder, octdec($this->calculate_additive_chmod_oct(FS_CHMOD_DIR, 0775)));
253
254 //Clean up contents of upgrade directory beforehand.
255 $upgrade_files = $wp_filesystem->dirlist($upgrade_folder);
256 if ( !empty($upgrade_files) ) {
257 foreach ( $upgrade_files as $file )
258 $wp_filesystem->delete($upgrade_folder.$file['name'], true);
259 }
260
261 //We need a working directory
262 $working_dir = $upgrade_folder . basename($package, '.crypt');
263 # $working_dir_localpath = WP_CONTENT_DIR.'/upgrade/'. basename($package, '.crypt');
264
265 // Clean up working directory
266 if ($wp_filesystem->is_dir($working_dir)) $wp_filesystem->delete($working_dir, true);
267
268 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.')');
269
270 // Unpack package to working directory
271 if ($updraftplus->is_db_encrypted($package)) {
272 $this->skin->feedback('decrypt_database');
273 $encryption = UpdraftPlus_Options::get_updraft_option('updraft_encryptionphrase');
274 if (!$encryption) return new WP_Error('no_encryption_key', __('Decryption failed. The database file is encrypted, but you have no encryption key entered.', 'updraftplus'));
275
276 $plaintext = $updraftplus->decrypt(false, $encryption, $wp_filesystem->get_contents($backup_dir.$package));
277
278 if ($plaintext) {
279 $this->skin->feedback('decrypted_database');
280 if (!$wp_filesystem->put_contents($working_dir.'/backup.db.gz', $plaintext)) {
281 return new WP_Error('write_failed', __('Failed to write out the decrypted database to the filesystem','updraftplus'));
282 }
283 } else {
284 return new WP_Error('decryption_failed', __('Decryption failed. The most likely cause is that you used the wrong key.','updraftplus'));
285 }
286 } else {
287
288 if (preg_match('/\.sql$/i', $package)) {
289 if (!$wp_filesystem->copy($backup_dir.$package, $working_dir.'/backup.db')) {
290 if ( $wp_filesystem->errors->get_error_code() ) {
291 foreach ( $wp_filesystem->errors->get_error_messages() as $message ) show_message($message);
292 }
293 return new WP_Error('copy_failed', $this->strings['copy_failed']);
294 }
295 } elseif (!$wp_filesystem->copy($backup_dir.$package, $working_dir.'/backup.db.gz')) {
296 if ( $wp_filesystem->errors->get_error_code() ) {
297 foreach ( $wp_filesystem->errors->get_error_messages() as $message ) show_message($message);
298 }
299 return new WP_Error('copy_failed', $this->strings['copy_failed']);
300 }
301
302 }
303
304 // Once extracted, delete the package if required (non-recursive, is a file)
305 if ($delete_package) $wp_filesystem->delete($backup_dir.$package, false, true);
306
307 $updraftplus->log("Database successfully unpacked");
308
309 return $working_dir;
310
311 }
312
313 // For moving files out of a directory into their new location
314 // 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
315 // Must use only wp_filesystem
316 // $dest_dir must already have a trailing slash
317 // $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. 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
318 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) {
319
320 global $wp_filesystem, $updraftplus;
321 $updraft_dir = $updraftplus->backups_dir_location();
322
323 # && !is_a($wp_filesystem, 'WP_Filesystem_Direct')
324 if (true == $force_local) {
325 $wpfs = new UpdraftPlus_WP_Filesystem_Direct(true);
326 } else {
327 $wpfs = $wp_filesystem;
328 }
329
330 # Get the content to be moved in. Include hidden files = true. Recursion is only required if we're likely to copy-in
331 $recursive = (3 == $preserve_existing) ? true : false;
332 $upgrade_files = $wpfs->dirlist($working_dir, true, $recursive);
333
334 if (empty($upgrade_files)) return true;
335
336 if (!$wpfs->is_dir($dest_dir)) {
337 return new WP_Error('no_such_dir', __('The directory does not exist', 'updraftplus')." ($dest_dir)");
338 // $updraftplus->log_e("The directory does not exist, so will be created (%s).", $dest_dir);
339 // # Attempts to create the directory fail, as due to a core bug, $dest_dir will be the wrong value if it did not already exist (at least for themes - the value of it depends on an is_dir() check wrongly used to detect a relative path)
340 // if (!$wpfs->mkdir($dest_dir)) {
341 // return new WP_Error('create_failed', __('Failed to create directory', 'updraftplus')." ($dest_dir)");
342 // }
343 }
344
345 $wpcore_config_moved = false;
346
347 foreach ( $upgrade_files as $file => $filestruc ) {
348
349 if (empty($file)) continue;
350
351 if ($dest_dir.$file == $updraft_dir) {
352 $updraftplus->log('Skipping attempt to replace updraft_dir whilst processing '.$type);
353 continue;
354 }
355
356 // Correctly restore files in 'others' in no directory that were wrongly backed up in versions 1.4.0 - 1.4.48
357 if (('others' == $type || 'wpcore' == $type) && preg_match('/^([\-_A-Za-z0-9]+\.php)$/i', $file, $matches) && $wpfs->exists($working_dir . "/$file/$file")) {
358 if ('others' == $type) {
359 echo "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<br>";
360 } else {
361 echo "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<br>";
362 }
363 $updraftplus->log("$file/$file: rename to $file");
364 $file = $matches[1];
365 $tmp_file = rand(0,999999999).'.php';
366 // Rename directory
367 $wpfs->move($working_dir . "/$file", $working_dir . "/".$tmp_file, true);
368 $wpfs->move($working_dir . "/$tmp_file/$file", $working_dir ."/".$file, true);
369 $wpfs->rmdir($working_dir . "/$tmp_file", false);
370 }
371
372 if ('wp-config.php' == $file && 'wpcore' == $type) {
373 if (empty($_POST['updraft_restorer_wpcore_includewpconfig'])) {
374 $updraftplus->log_e('wp-config.php from backup: will restore as wp-config-backup.php', 'updraftplus');
375 $wpfs->move($working_dir . "/$file", $working_dir . "/wp-config-backup.php", true);
376 $file = "wp-config-backup.php";
377 $wpcore_config_moved = true;
378 } else {
379 $updraftplus->log_e("wp-config.php from backup: restoring (as per user's request)", 'updraftplus');
380 }
381 } elseif ('wpcore' == $type && 'wp-config-backup.php' == $file && $wpcore_config_moved) {
382 # The file is already gone; nothing to do
383 continue;
384 }
385
386 # Sanity check (should not be possible as these were excluded at backup time)
387 if (in_array($file, $do_not_overwrite)) continue;
388
389 if (('object-cache.php' == $file || 'advanced-cache.php' == $file) && 'others' == $type) {
390 if (false == apply_filters('updraftplus_restorecachefiles', true, $file)) {
391 $nfile = preg_replace('/\.php$/', '-backup.php', $file);
392 $wpfs->move($working_dir . "/$file", $working_dir . "/".$nfile, true);
393 $file=$nfile;
394 }
395 } elseif (('object-cache-backup.php' == $file || 'advanced-cache-backup.php' == $file) && 'others' == $type) {
396 $wpfs->delete($working_dir."/".$file);
397 continue;
398 }
399
400 # First, move the existing one, if necessary (may not be present)
401 if ($wpfs->exists($dest_dir.$file)) {
402 if ($preserve_existing == 1) {
403 # Move existing to -old
404 if ( !$wpfs->move($dest_dir.$file, $dest_dir.$file.'-old', true) ) {
405 return new WP_Error('old_move_failed', $this->strings['old_move_failed']." ($dest_dir.$file)");
406 }
407 } elseif ($preserve_existing == 0) {
408 # Over-write, no backup
409 if (!$wpfs->delete($dest_dir.$file, true)) {
410 return new WP_Error('old_delete_failed', $this->strings['old_delete_failed']." ($file)");
411 }
412 }
413 }
414
415 # Secondly, move in the new one
416 if (2 == $preserve_existing && $wpfs->exists($dest_dir.$file)) {
417 # Something exists - no move. Remove it from the temporary directory - so that it will be clean later
418 @$wpfs->delete($working_dir.'/'.$file, true);
419 } elseif (3 != $preserve_existing || !$wpfs->exists($dest_dir.$file)) {
420 $is_dir = $wpfs->is_dir($working_dir."/".$file);
421 # This method is broken due to https://core.trac.wordpress.org/ticket/26598
422 #if (empty($chmod)) $chmod = $wpfs->getnumchmodfromh($wpfs->gethchmod($dest_dir));
423 if (empty($chmod)) $chmod = octdec(sprintf("%04d", $this->get_current_chmod($dest_dir, $wpfs)));
424 if ($wpfs->move($working_dir."/".$file, $dest_dir.$file, true) ) {
425 if ($send_actions) do_action('updraftplus_restored_'.$type.'_one', $file);
426 # Make sure permissions are at least as great as those of the parent
427 if ($is_dir && !empty($chmod)) $this->chmod_if_needed($dest_dir.$file, $chmod, false, $wpfs);
428 } else {
429 return new WP_Error('move_failed', $this->strings['move_failed'], $working_dir."/".$file." -> ".$dest_dir.$file);
430 }
431 } elseif (3 == $preserve_existing && !empty($filestruc['files'])) {
432 # The directory ($dest_dir) already exists, and we've been requested to copy-in. We need to perform the recursive copy-in
433 # $filestruc['files'] is then a new structure like $upgrade_files
434 # First pass: create directory structure
435 # Get chmod value for the parent directory, and re-use it (instead of passing false)
436
437 # This method is broken due to https://core.trac.wordpress.org/ticket/26598
438 #if (empty($chmod)) $chmod = $wpfs->getnumchmodfromh($wpfs->gethchmod($dest_dir));
439 if (empty($chmod)) $chmod = octdec(sprintf("%04d", $this->get_current_chmod($dest_dir, $wpfs)));
440 # Copy in the files. This also needs to make sure the directories exist, in case the zip file lacks entries
441 $delete_root = ('others' == $type || 'wpcore' == $type) ? false : true;
442
443 $copy_in = $this->copy_files_in($working_dir.'/'.$file, $dest_dir.$file, $filestruc['files'], $chmod, $delete_root);
444 if (!empty($chmod)) $this->chmod_if_needed($dest_dir.$file, $chmod, false, $wpfs);
445
446 if (is_wp_error($copy_in)) return $copy_in;
447 if (!$copy_in) return new WP_Error('move_failed', $this->strings['move_failed'], "(2) ".$working_dir.'/'.$file." -> ".$dest_dir.$file);
448
449 $wpfs->rmdir($working_dir.'/'.$file);
450 } else {
451 $wpfs->rmdir($working_dir.'/'.$file);
452 }
453 }
454
455 return true;
456
457 }
458
459 # $dest_dir must already exist
460 private function copy_files_in($source_dir, $dest_dir, $files, $chmod = false, $deletesource = false) {
461 global $wp_filesystem, $updraftplus;
462 foreach ($files as $rname => $rfile) {
463 if ('d' != $rfile['type']) {
464 # Delete it if it already exists (or perhaps WP does it for us)
465 if (!$wp_filesystem->move($source_dir.'/'.$rname, $dest_dir.'/'.$rname, true)) {
466 $updraftplus->log_e('Failed to move file (check your file permissions and disk quota): %s', $source_dir.'/'.$rname." -&gt; ".$dest_dir.'/'.$rname);
467 return false;
468 }
469 } else {
470 # Directory
471 if ($wp_filesystem->is_file($dest_dir.'/'.$rname)) @$wp_filesystem->delete($dest_dir.'/'.$rname, false, 'f');
472 # No such directory yet: just move it
473 if (!$wp_filesystem->is_dir($dest_dir.'/'.$rname)) {
474 if (!$wp_filesystem->move($source_dir.'/'.$rname, $dest_dir.'/'.$rname, false)) {
475 $updraftplus->log_e('Failed to move directory (check your file permissions and disk quota): %s', $source_dir.'/'.$rname." -&gt; ".$dest_dir.'/'.$rname);
476 return false;
477 }
478 } elseif (!empty($rfile['files'])) {
479 # There is a directory - and we want to to copy in
480 $docopy = $this->copy_files_in($source_dir.'/'.$rname, $dest_dir.'/'.$rname, $rfile['files'], $chmod, false);
481 if (is_wp_error($docopy)) return $docopy;
482 if (false === $docopy) {
483 return false;
484 }
485 } else {
486 # There is a directory: but nothing to copy in to it
487 @$wp_filesystem->rmdir($source_dir.'/'.$rname);
488 }
489 }
490 }
491 # 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.
492 if ($deletesource || strpos($source_dir, '/') !== false) {
493 $wp_filesystem->rmdir($source_dir, false);
494 }
495
496 return true;
497
498 }
499
500 // Pre-flight check: chance to complain and abort before anything at all is done
501 public function pre_restore_backup($backup_files, $type, $info) {
502
503 if (is_string($backup_files)) $backup_files=array($backup_files);
504
505 if ('more' == $type) {
506 $this->skin->feedback('not_possible');
507 return;
508 }
509
510 // Ensure access to the indicated directory - and to WP_CONTENT_DIR (in which we use upgrade/)
511 $need_these = array(WP_CONTENT_DIR);
512 if (!empty($info['path'])) $need_these[] = $info['path'];
513
514 $res = $this->fs_connect($need_these);
515 if (false === $res || is_wp_error($res)) return $res;
516
517 # Check upgrade directory is writable (instead of having non-obvious messages when we try to write)
518 # In theory, this is redundant (since we already checked for access to WP_CONTENT_DIR); but in practice, this extra check has been needed
519
520 global $wp_filesystem, $updraftplus, $updraftplus_admin, $updraftplus_addons_migrator;
521
522 if (empty($this->pre_restore_updatedir_writable)) {
523 $upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
524 @$wp_filesystem->mkdir($upgrade_folder, octdec($this->calculate_additive_chmod_oct(FS_CHMOD_DIR, 0775)));
525 if (!$wp_filesystem->is_dir($upgrade_folder)) {
526 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));
527 }
528 $rand_file = 'testfile_'.rand(0,9999999).md5(microtime(true)).'.txt';
529 if ($wp_filesystem->put_contents($upgrade_folder.$rand_file, 'testing...')) {
530 @$wp_filesystem->delete($upgrade_folder.$rand_file);
531 $this->pre_restore_updatedir_writable = true;
532 } else {
533 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));
534 }
535 }
536
537 # Code below here assumes that we're dealing with file-based entities
538 if ('db' == $type) return true;
539
540 $wp_filesystem_dir = $this->get_wp_filesystem_dir($info['path']);
541 if ($wp_filesystem_dir === false) return false;
542
543 // $this->maintenance_mode(true);
544 //
545 // $updraftplus->log_e('Testing file permissions...');
546
547 $ret_val = true;
548
549 $updraft_dir = $updraftplus->backups_dir_location();
550
551 if (('plugins' == $type || 'uploads' == $type || 'themes' == $type) && (!is_multisite() || $this->ud_backup_is_multisite !== 0 || ('uploads' != $type || empty($updraftplus_addons_migrator->new_blogid )))) {
552 // if ($wp_filesystem->exists($wp_filesystem_dir.'-old')) {
553 if (file_exists($updraft_dir.'/'.basename($wp_filesystem_dir)."-old")) {
554 $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'));
555
556 } else {
557 // No longer used - since we now do not move the directories themselves
558 // # File permissions test; see if we can move the directory back and forth
559 // if (!$wp_filesystem->move($wp_filesystem_dir, $wp_filesystem_dir."-old", false)) {
560 // $ret_val = new WP_Error('old_move_failed', $this->strings['old_move_failed']);
561 // } else {
562 // $wp_filesystem->move($wp_filesystem_dir."-old", $wp_filesystem_dir, false);
563 // }
564 }
565 }
566
567 // $this->maintenance_mode(false);
568
569 if (!empty($this->ud_foreign)) {
570 $known_foreigners = apply_filters('updraftplus_accept_archivename', array());
571 if (!is_array($known_foreigners) || empty($known_foreigners[$this->ud_foreign])) {
572 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.')');
573 }
574 }
575
576 return $ret_val;
577 }
578
579 private function get_wp_filesystem_dir($path) {
580 global $wp_filesystem;
581 // Get the wp_filesystem location for the folder on the local install
582 switch ($path) {
583 case ABSPATH:
584 case '';
585 $wp_filesystem_dir = $wp_filesystem->abspath();
586 break;
587 case WP_CONTENT_DIR:
588 $wp_filesystem_dir = $wp_filesystem->wp_content_dir();
589 break;
590 case WP_PLUGIN_DIR:
591 $wp_filesystem_dir = $wp_filesystem->wp_plugins_dir();
592 break;
593 case WP_CONTENT_DIR . '/themes':
594 $wp_filesystem_dir = $wp_filesystem->wp_themes_dir();
595 break;
596 default:
597 $wp_filesystem_dir = $wp_filesystem->find_folder($path);
598 break;
599 }
600 if ( ! $wp_filesystem_dir ) return false;
601 return untrailingslashit($wp_filesystem_dir);
602 }
603
604 // $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).
605 public function restore_backup($backup_file, $type, $info, $last_one = false) {
606
607 if ('more' == $type) {
608 $this->skin->feedback('not_possible');
609 return;
610 }
611
612 global $wp_filesystem, $updraftplus_addons_migrator, $updraftplus;
613
614 $updraftplus->log("restore_backup(backup_file=$backup_file, type=$type, info=".serialize($info).", last_one=$last_one)");
615
616 $updraft_dir = $updraftplus->backups_dir_location();
617
618 $get_dir = (empty($info['path'])) ? '' : $info['path'];
619 $wp_filesystem_dir = $this->get_wp_filesystem_dir($get_dir);
620 if ($wp_filesystem_dir === false) return false;
621
622 if (empty($this->abspath)) $this->abspath = trailingslashit($wp_filesystem->abspath());
623
624 @set_time_limit(1800);
625
626 // This returns the wp_filesystem path
627 $working_dir = $this->unpack_package($backup_file, $this->delete, $type);
628
629 if (is_wp_error($working_dir)) return $working_dir;
630
631 $working_dir_localpath = WP_CONTENT_DIR.'/upgrade/'.basename($working_dir);
632 @set_time_limit(1800);
633
634 // We copy the variable because we may be importing with a different prefix (e.g. on multisite imports of individual blog data)
635 $import_table_prefix = $updraftplus->get_table_prefix(false);
636
637 if (is_multisite() && $this->ud_backup_is_multisite === 0 && ( ( 'plugins' == $type || 'themes' == $type ) || ( 'uploads' == $type && !empty($updraftplus_addons_migrator->new_blogid)) )) {
638
639 # Migrating a single site into a multisite
640 if ('plugins' == $type || 'themes' == $type) {
641
642 $move_from = $this->get_first_directory($working_dir, array(basename($info['path']), $type));
643
644 $this->skin->feedback('moving_backup');
645
646 // Only move in entities that are not already there (2)
647 $new_move_failed = (false === $move_from) ? true : false;
648 if (false === $new_move_failed) {
649 $move_in = $this->move_backup_in($move_from, trailingslashit($wp_filesystem_dir), 2, array(), $type, true);
650 if (is_wp_error($move_in)) return $move_in;
651 if (!$move_in) $new_move_failed = true;
652 }
653 if ($new_move_failed) return new WP_Error('new_move_failed', $this->strings['new_move_failed']);
654 @$wp_filesystem->delete($move_from);
655
656 } else {
657 // Uploads
658
659 $this->skin->feedback('moving_old');
660
661 switch_to_blog($updraftplus_addons_migrator->new_blogid);
662
663 $ud = wp_upload_dir();
664 $wpud = $ud['basedir'];
665 $fsud = trailingslashit($wp_filesystem->find_folder($wpud));
666 restore_current_blog();
667
668 // TODO: What is below will move the entire uploads directory if blog id is 1. Detect this situation. (Can that happen? We created a new blog, so should not be possible).
669
670 // TODO: the upload dir is not necessarily reachable through wp_filesystem - try ordinary method instead
671 if (is_string($fsud)) {
672 // This is not expected to exist, since we created a new blog
673
674 if ( $wp_filesystem->exists($fsud) && !$wp_filesystem->move($fsud, untrailingslashit($fsud)."-old", true) ) {
675 return new WP_Error('old_move_failed', $this->strings['old_move_failed']);
676 }
677
678 $this->skin->feedback('moving_backup');
679
680 $move_from = $this->get_first_directory($working_dir, array(basename($info['path']), $type));
681
682 if ( !$wp_filesystem->move($move_from, $fsud, true) ) {
683 return new WP_Error('new_move_failed', $this->strings['new_move_failed']);
684 }
685
686 @$wp_filesystem->delete($move_from);
687
688 } else {
689 return new WP_Error('new_move_failed', $this->strings['new_move_failed']);
690 }
691
692 }
693 } elseif ('db' == $type) {
694
695 // $import_table_prefix is received as a reference
696 $rdb = $this->restore_backup_db($working_dir, $working_dir_localpath, $import_table_prefix);
697 if (false === $rdb || is_wp_error($rdb)) return $rdb;
698
699 } elseif ('others' == $type) {
700
701 $dirname = basename($info['path']);
702
703 # For foreign 'Simple Backup', we need to keep going down until we find wp-content
704 if (empty($this->ud_foreign)) {
705 $move_from = $working_dir;
706 } else {
707 $move_from = $this->search_for_folder('wp-content', $working_dir);
708 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'));
709 }
710
711 // 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
712
713 # On subsequent archives of a multi-archive set, don't move anything; but do on the first
714 $preserve_existing = (isset($this->been_restored['others'])) ? 3 : 1;
715
716 $this->move_backup_in($move_from, trailingslashit($wp_filesystem_dir), $preserve_existing, array('plugins', 'themes', 'uploads', 'upgrade'), 'others');
717
718 $this->been_restored['others'] = true;
719
720 } else {
721
722 // Default action: used for plugins, themes and uploads (and wpcore, via a filter)
723
724 // Multi-archive sets: we record what we've already begun on, and on subsequent runs, copy in instead of replacing
725 $movedin = apply_filters('updraftplus_restore_movein_'.$type, $working_dir, $this->abspath, $wp_filesystem_dir);
726 // A filter, to allow add-ons to perform the install of non-standard entities, or to indicate that it's not possible
727 if (false === $movedin) {
728 $this->skin->feedback('not_possible');
729 } elseif (is_wp_error($movedin)) {
730 return $movedin;
731 } elseif (true !== $movedin) {
732
733 # On the first time, create the -old directory in updraft_dir
734 # (Old style: On the first time, move the existing data to -old)
735 if (!isset($this->been_restored[$type])) {
736
737 # First, try filesystem-level move
738 $old_dir = $updraft_dir.'/'.$type.'-old';
739 if (is_dir($old_dir)) {
740 $updraftplus->log_e('%s: This directory already exists, and will be replaced', $old_dir);
741 $updraftplus->remove_local_directory($old_dir);
742 }
743
744 $move_old_destination = 0;
745
746 if (@mkdir($old_dir)) {
747 $updraftplus->log("Moving old data: filesystem method / updraft_dir is potentially possible");
748 $move_old_destination = 1;
749 }
750
751 # Try wp_filesystem instead
752 if ($wp_filesystem->exists($wp_filesystem_dir."-old")) {
753 // Is better to warn and delete the backup than abort mid-restore and leave inconsistent site
754 $updraftplus->log_e('%s: This directory already exists, and will be replaced', $wp_filesystem_dir."-old");
755 # In theory, supply true as the 3rd parameter of true achieves this; in practice, not always so (leads to support requests)
756 $wp_filesystem->delete($wp_filesystem_dir."-old", true);
757 if ($wp_filesystem->exists($wp_filesystem_dir."-old")) {
758 $updraftplus->log("Failed to remove existing directory (".$wp_filesystem_dir."-old");
759 $failed_to_remove = true;
760 #return new WP_Error('old_move_failed', $this->strings['old_move_failed']);
761 }
762 }
763
764 if (empty($failed_to_remove) && @$wp_filesystem->mkdir($wp_filesystem_dir."-old")) {
765 $updraftplus->log("Moving old data: can potentially use wp_filesystem method / -old");
766 $move_old_destination += 2;
767 }
768
769 if (0 == $move_old_destination) {
770 $updraftplus->log_e("File permissions do not allow the old data to be moved and retained; instead, it will be deleted.");
771 }
772
773 $this->skin->feedback('moving_old');
774
775 # First, try direct filesystem method into updraft_dir
776 if (1 == $move_old_destination % 2) {
777 # The final 'true' forces direct filesystem access
778 $move_old = @$this->move_backup_in($get_dir, $updraft_dir.'/'.$type.'-old/' , 3, array(), $type, false, true);
779 if (is_wp_error($move_old)) $updraftplus->log_wp_error($move_old);
780 }
781
782 # Try wp_filesystem method into -old if that failed
783 if (2 >= $move_old_destination && (0 == $move_old_destination % 2 || (!empty($move_old) && is_wp_error($move_old)))) {
784 $move_old = @$this->move_backup_in($wp_filesystem_dir, $wp_filesystem_dir."-old/" , 3, array(), $type);
785 #if (is_wp_error($move_old)) return $move_old;
786 if (is_wp_error($move_old)) $updraftplus->log_wp_error($move_old);
787 // if ( !$wp_filesystem->move($wp_filesystem_dir, $wp_filesystem_dir."-old", false) ) {
788 // return new WP_Error('old_move_failed', $this->strings['old_move_failed']);
789 // }
790 }
791
792 # Finally, when all else fails, nuke it
793 if (0 == $move_old_destination || (!empty($move_old) && is_wp_error($move_old))) {
794 $updraftplus->log("$type: $wp_filesystem_dir: deleting contents (as attempts to copy failed)");
795 $del_files = $wp_filesystem->dirlist($wp_filesystem_dir, true, false);
796 if (empty($del_files)) $del_files = array();
797 foreach ( $del_files as $file => $filestruc ) {
798 if (empty($file)) continue;
799 $wp_filesystem->delete($wp_filesystem_dir.'/'.$file, true);
800 }
801 }
802
803 }
804
805 # For foreign 'Simple Backup', we need to keep going down until we find wp-content
806 if (empty($this->ud_foreign)) {
807 $working_dir_use = $working_dir;
808 } else {
809 $working_dir_use = $this->search_for_folder('wp-content', $working_dir);
810 if (!is_string($working_dir_use)) return new WP_Error('not_found', __('The WordPress content folder (wp-content) was not found in this zip file.', 'updraftplus'));
811 }
812
813 // The backup may not actually have /$type, since that is info from the present site
814 $move_from = $this->get_first_directory($working_dir_use, array(basename($info['path']), $type));
815 if (false === $move_from) return new WP_Error('new_move_failed', $this->strings['new_move_failed']);
816
817 $this->skin->feedback('moving_backup');
818
819 // Old-style
820 // if (!isset($this->been_restored[$type])) {
821 // if (!$wp_filesystem->move($move_from, $wp_filesystem_dir, true) ) {
822 // return new WP_Error('new_move_failed', $this->strings['new_move_failed']);
823 // }
824 // } else {
825 $move_in = $this->move_backup_in($move_from, trailingslashit($wp_filesystem_dir), 3, array(), $type);
826 if (is_wp_error($move_in)) return $move_in;
827 if (!$move_in) return new WP_Error('new_move_failed', $this->strings['new_move_failed']);
828 $wp_filesystem->rmdir($move_from);
829 // }
830
831 }
832
833 $this->been_restored[$type] = true;
834
835 }
836
837 $attempt_delete = true;
838 if (!empty($this->ud_foreign) && !$last_one) $attempt_delete = false;
839
840 // Non-recursive, so the directory needs to be empty
841 if ($attempt_delete) $this->skin->feedback('cleaning_up');
842
843 if ($attempt_delete && !$wp_filesystem->delete($working_dir, !empty($this->ud_foreign))) {
844
845 # TODO: Can remove this after 1-Jan-2015; or at least, make it so that it requires the version number to be present.
846 $fixed_it_now = false;
847 # Deal with a corner-case in version 1.8.5
848 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', '<')))) {
849 $updraftplus->log("Clean-up failed with uploads: will attempt 1.8.5-1.8.7 fix (".$this->created_by_version.")");
850 $move_in = @$this->move_backup_in(dirname($move_from), trailingslashit($wp_filesystem_dir), 3, array(), $type);
851 $updraftplus->log("Result: ".serialize($move_in));
852 if ($wp_filesystem->delete($working_dir)) $fixed_it_now = true;
853 }
854
855 if (!$fixed_it_now) {
856 $updraftplus->log_e('Error: %s', $this->strings['delete_failed'].' ('.$working_dir.')');
857 # List contents
858 // No need to make this a restoration-aborting error condition - it's not
859 #return new WP_Error('delete_failed', $this->strings['delete_failed'].' ('.$working_dir.')');
860 $dirlist = $wp_filesystem->dirlist($working_dir, true, true);
861 if (is_array($dirlist)) {
862 echo __('Files found:', 'updraftplus').'<br><ul style="list-style: disc inside;">';
863 foreach ($dirlist as $name => $struc) {
864 echo "<li>".htmlspecialchars($name)."</li>";
865 }
866 echo '</ul>';
867 } else {
868 $updraftplus->log_e('Unable to enumerate files in that directory.');
869 }
870 }
871 }
872
873 # Permissions changes (at the top level - i.e. this does not reply if using recursion) are now *additive* - i.e. there's no danger of permissions being removed from what's on-disk
874 switch($type) {
875 case 'wpcore':
876 $this->chmod_if_needed($wp_filesystem_dir, FS_CHMOD_DIR, false, $wp_filesystem);
877 // In case we restored a .htaccess which is incorrect for the local setup
878 $this->flush_rewrite_rules();
879 break;
880 case 'uploads':
881 $this->chmod_if_needed($wp_filesystem_dir, FS_CHMOD_DIR, false, $wp_filesystem);
882 break;
883 case 'db':
884 if (function_exists('wp_cache_flush')) wp_cache_flush();
885 do_action('updraftplus_restored_db', array('expected_oldsiteurl' => $this->old_siteurl, 'expected_oldhome' => $this->old_home, 'expected_oldcontent' => $this->old_content), $import_table_prefix);
886 $this->flush_rewrite_rules();
887
888 # N.B. flush_rewrite_rules() causes $wp_rewrite to become up to date again
889 if (function_exists('apache_get_modules')) {
890 global $wp_rewrite;
891 $mods = apache_get_modules();
892 if (($wp_rewrite->using_mod_rewrite_permalinks() && in_array('core', $mods) || in_array('http_core', $mods)) && !in_array('mod_rewrite', $mods)) {
893 $updraftplus->log("Using Apache, with permalinks (".get_option('permalink_structure').") but no mod_rewrite enabled");
894 $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 your pretty permalinks (e.g. %s) work', 'updraftplus'), 'mod_rewrite', 'http://example.com/my-page/');
895 echo '<p><strong>'.htmlspecialchars($warn_no_rewrite).'</strong></p>';
896 }
897 }
898
899 break;
900 default:
901 $this->chmod_if_needed($wp_filesystem_dir, FS_CHMOD_DIR, false, $wp_filesystem);
902 }
903 # db was already done
904 if ('db' != $type) do_action('updraftplus_restored_'.$type);
905
906 return true;
907
908 }
909
910 private function search_for_folder($folder, $startat) {
911 # Exists in this folder?
912 if (is_dir($startat.'/'.$folder)) return trailingslashit($startat).$folder;
913 # Does not
914 if($handle = opendir($startat)) {
915 while (($file = readdir($handle)) !== false) {
916 if ($file != '.' && $file != '..' && is_dir($startat).'/'.$file) {
917 $ss = $this->search_for_folder($folder, trailingslashit($startat).$file);
918 if (is_string($ss)) return $ss;
919 }
920 }
921 closedir($handle);
922 }
923 return false;
924 }
925
926 # Returns an octal string (but not an octal number)
927 private function get_current_chmod($file, $wpfs = false) {
928 if (false == $wpfs) {
929 global $wp_filesystem;
930 $wpfs = $wp_filesystem;
931 }
932 # getchmod() is broken at least as recently as WP3.8 - see: https://core.trac.wordpress.org/ticket/26598
933 return (is_a($wpfs, 'WP_Filesystem_Direct')) ? substr(sprintf("%06d", decoct(@fileperms($file))),3) : $wpfs->getchmod($file);
934 }
935
936 # Returns a string in octal format
937 # $new_chmod should be an octal, i.e. what you'd pass to chmod()
938 function calculate_additive_chmod_oct($old_chmod, $new_chmod) {
939 # chmod() expects octal form, which means a preceding zero - see http://php.net/chmod
940 $old_chmod = sprintf("%04d", $old_chmod);
941 $new_chmod = sprintf("%04d", decoct($new_chmod));
942
943 for ($i=1; $i<=3; $i++) {
944 $oldbit = substr($old_chmod, $i, 1);
945 $newbit = substr($new_chmod, $i, 1);
946 for ($j=0; $j<=2; $j++) {
947 if (($oldbit & (1<<$j)) && !($newbit & (1<<$j))) {
948 $newbit = (string)($newbit | 1<<$j);
949 $new_chmod = sprintf("%04d", substr($new_chmod, 0, $i).$newbit.substr($new_chmod, $i+1));
950 }
951 }
952 }
953
954 return $new_chmod;
955 }
956
957 # "If needed" means, "If the permissions are not already more permissive than this". i.e. This will not tighten permissions from what the user had before (we trust them)
958 # $chmod should be an octal - i.e. the same as you'd pass to chmod()
959 private function chmod_if_needed($dir, $chmod, $recursive = false, $wpfs = false, $suppress = true) {
960
961 # Do nothing on Windows
962 if (strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN') return true;
963
964 if (false == $wpfs) {
965 global $wp_filesystem;
966 $wpfs = $wp_filesystem;
967 }
968
969 $old_chmod = $this->get_current_chmod($dir, $wpfs);
970
971 # Sanity fcheck
972 if (strlen($old_chmod) < 3) return;
973
974 $new_chmod = $this->calculate_additive_chmod_oct($old_chmod, $chmod);
975
976 # Don't fix what isn't broken
977 if (!$recursive && $new_chmod == $old_chmod) return true;
978
979 $new_chmod = octdec($new_chmod);
980
981 if ($suppress) {
982 return @$wpfs->chmod($dir, $new_chmod, $recursive);
983 } else {
984 return $wpfs->chmod($dir, $new_chmod, $recursive);
985 }
986 }
987
988 // $dirnames: an array of preferred names
989 private function get_first_directory($working_dir, $dirnames) {
990 global $wp_filesystem, $updraftplus;
991 $fdirnames = array_flip($dirnames);
992 $dirlist = $wp_filesystem->dirlist($working_dir, true, false);
993 if (is_array($dirlist)) {
994 $move_from = false;
995 foreach ($dirlist as $name => $struc) {
996 if (isset($struc['type']) && 'd' != $struc['type']) continue;
997 if (false === $move_from) {
998 if (isset($fdirnames[$name])) {
999 $move_from = $working_dir . "/".$name;
1000 } elseif (preg_match('/^([^\.].*)$/', $name, $fmatch)) {
1001 $first_entry = $working_dir."/".$fmatch[1];
1002 }
1003 }
1004 }
1005 if ($move_from === false && isset($first_entry)) {
1006 $updraftplus->log_e('Using directory from backup: %s', basename($first_entry));
1007 $move_from = $first_entry;
1008 }
1009 } else {
1010 # That shouldn't happen. Fall back to default
1011 $move_from = $working_dir."/".$dirnames[0];
1012 }
1013 return $move_from;
1014 }
1015
1016 private function pre_sql_actions($import_table_prefix) {
1017
1018 $import_table_prefix = apply_filters('updraftplus_restore_set_table_prefix', $import_table_prefix, $this->ud_backup_is_multisite);
1019
1020 if (!is_string($import_table_prefix)) {
1021 if ($import_table_prefix === false) {
1022 echo '<p>'.__('Please supply the requested information, and then continue.', 'updraftplus').'</p>';
1023 return false;
1024 } else {
1025 return new WP_Error('invalid_table_prefix', __('Error:', 'updraftplus').' '.serialize($import_table_prefix));
1026 }
1027 }
1028
1029 global $updraftplus;
1030 echo $updraftplus->log_e('New table prefix: %s', $import_table_prefix);
1031
1032 return $import_table_prefix;
1033
1034 }
1035
1036 public function option_filter_permalink_structure($val) {
1037 global $updraftplus;
1038 return $updraftplus->option_filter_get('permalink_structure');
1039 }
1040
1041 public function option_filter_page_on_front($val) {
1042 global $updraftplus;
1043 return $updraftplus->option_filter_get('page_on_front');
1044 }
1045
1046 public function option_filter_rewrite_rules($val) {
1047 global $updraftplus;
1048 return $updraftplus->option_filter_get('rewrite_rules');
1049 }
1050
1051 // The pass-by-reference on $import_table_prefix is due to historical refactoring
1052 private function restore_backup_db($working_dir, $working_dir_localpath, &$import_table_prefix) {
1053
1054 do_action('updraftplus_restore_db_pre');
1055
1056 # This is now a legacy option (at least on the front end), so we should not see it much
1057 $this->prior_upload_path = get_option('upload_path');
1058
1059 // There is a file backup.db(.gz) inside the working directory
1060
1061 # The 'off' check is for badly configured setups - http://wordpress.org/support/topic/plugin-wp-super-cache-warning-php-safe-mode-enabled-but-safe-mode-is-off
1062 if (@ini_get('safe_mode') && 'off' != strtolower(@ini_get('safe_mode'))) {
1063 echo "<p>".__('Warning: PHP safe_mode is active on your server. Timeouts are much more likely. If these happen, then you will need to manually restore the file via phpMyAdmin or another method.', 'updraftplus')."</p><br/>";
1064 }
1065
1066 $db_basename = 'backup.db.gz';
1067 if (!empty($this->ud_foreign)) {
1068
1069 $plugins = apply_filters('updraftplus_accept_archivename', array());
1070
1071 if (empty($plugins[$this->ud_foreign])) return new WP_Error('unknown', sprintf(__('Backup created by unknown source (%s) - cannot be restored.', 'updraftplus'), $this->ud_foreign));
1072
1073 if (empty($plugins[$this->ud_foreign]['separatedb'])) {
1074 $db_basename = apply_filters('updraftplus_foreign_separatedbname', false, $this->ud_foreign, $this->ud_backup_info, $working_dir_localpath);
1075 } elseif (file_exists($working_dir_localpath.'/backup.db')) {
1076 $db_basename = 'backup.db';
1077 }
1078 }
1079
1080 // wp_filesystem has no gzopen method, so we switch to using the local filesystem (which is harmless, since we are performing read-only operations)
1081 if (false === $db_basename || !is_readable($working_dir_localpath.'/'.$db_basename)) return new WP_Error('dbopen_failed',__('Failed to find database file','updraftplus')." ($working_dir/".$db_basename.")");
1082
1083 global $wpdb, $updraftplus;
1084
1085 $this->skin->feedback('restore_database');
1086
1087 $is_plain = (substr($db_basename, -3, 3) == '.db');
1088
1089 // Read-only access: don't need to go through WP_Filesystem
1090 if ($is_plain) {
1091 $dbhandle = fopen($working_dir_localpath.'/'.$db_basename, 'r');
1092 } else {
1093 $dbhandle = gzopen($working_dir_localpath.'/'.$db_basename, 'r');
1094 }
1095 if (!$dbhandle) return new WP_Error('dbopen_failed',__('Failed to open database file','updraftplus'));
1096
1097 $this->line = 0;
1098
1099 if (true == $this->use_wpdb) {
1100 $updraftplus->log_e('Database access: Direct MySQL access is not available, so we are falling back to wpdb (this will be considerably slower)');
1101 } else {
1102 $updraftplus->log("Using direct MySQL access; value of use_mysqli is: ".($this->use_mysqli ? '1' : '0'));
1103 if ($this->use_mysqli) {
1104 @mysqli_query($this->mysql_dbh, 'SET SESSION query_cache_type = OFF;');
1105 } else {
1106 @mysql_query('SET SESSION query_cache_type = OFF;', $this->mysql_dbh );
1107 }
1108 }
1109
1110 // Find the supported engines - in case the dump had something else (case seen: saved from MariaDB with engine Aria; imported into plain MySQL without)
1111 $supported_engines = $wpdb->get_results("SHOW ENGINES", OBJECT_K);
1112
1113 $this->errors = 0;
1114 $this->statements_run = 0;
1115 $this->insert_statements_run = 0;
1116 $this->tables_created = 0;
1117
1118 $sql_line = "";
1119 $sql_type = -1;
1120
1121 $this->start_time = microtime(true);
1122
1123 $old_wpversion = '';
1124 $this->old_siteurl = '';
1125 $this->old_home = '';
1126 $this->old_content = '';
1127 $old_table_prefix = '';
1128 $old_siteinfo = array();
1129 $gathering_siteinfo = true;
1130
1131 $this->create_forbidden = false;
1132 $this->drop_forbidden = false;
1133
1134 $this->last_error = '';
1135 $random_table_name = 'updraft_tmp_'.rand(0,9999999).md5(microtime(true));
1136
1137 # The only purpose in funnelling queries directly here is to be able to get the error number
1138 if ($this->use_wpdb) {
1139 $req = $wpdb->query("CREATE TABLE $random_table_name");
1140 if (!$req) $this->last_error = $wpdb->last_error;
1141 $this->last_error_no = false;
1142 } else {
1143 if ($this->use_mysqli) {
1144 $req = mysqli_query($this->mysql_dbh, "CREATE TABLE $random_table_name");
1145 } else {
1146 $req = mysql_unbuffered_query("CREATE TABLE $random_table_name", $this->mysql_dbh);
1147 }
1148 if (!$req) {
1149 $this->last_error = ($this->use_mysqli) ? mysqli_error($this->mysql_dbh) : mysql_error($this->mysql_dbh);
1150 $this->last_error_no = ($this->use_mysqli) ? mysqli_errno($this->mysql_dbh) : mysql_errno($this->mysql_dbh);
1151 }
1152 }
1153
1154 if (!$req && ($this->use_wpdb || 1142 === $this->last_error_no)) {
1155 $this->create_forbidden = true;
1156 # If we can't create, then there's no point dropping
1157 $this->drop_forbidden = true;
1158 echo '<strong>'.__('Warning:', 'updraftplus').'</strong> ';
1159 $updraftplus->log_e('Your database user does not have permission to create tables. We will attempt to restore by simply emptying the tables; this should work as long as a) you are restoring from a WordPress version with the same database structure, and b) Your imported database does not contain any tables which are not already present on the importing site.', ' ('.$this->last_error.')');
1160 } else {
1161 if ($this->use_wpdb) {
1162 $req = $wpdb->query("DROP TABLE $random_table_name");
1163 if (!$req) $this->last_error = $wpdb->last_error;
1164 $this->last_error_no = false;
1165 } else {
1166 if ($this->use_mysqli) {
1167 $req = mysqli_query($this->mysql_dbh, "DROP TABLE $random_table_name");
1168 } else {
1169 $req = mysql_unbuffered_query("DROP TABLE $random_table_name", $this->mysql_dbh);
1170 }
1171 if (!$req) {
1172 $this->last_error = ($this->use_mysqli) ? mysqli_error($this->mysql_dbh) : mysql_error($this->mysql_dbh);
1173 $this->last_error_no = ($this->use_mysqli) ? mysqli_errno($this->mysql_dbh) : mysql_errno($this->mysql_dbh);
1174 }
1175 }
1176 if (!$req && ($this->use_wpdb || $this->last_error_no === 1142)) {
1177 $this->drop_forbidden = true;
1178 echo '<strong>'.__('Warning:','updraftplus').'</strong> ';
1179 $updraftplus->log_e('Your database user does not have permission to drop tables. We will attempt to restore by simply emptying the tables; this should work as long as you are restoring from a WordPress version with the same database structure (%s)', ' ('.$this->last_error.')');
1180 }
1181 }
1182
1183 $restoring_table = '';
1184
1185 $this->max_allowed_packet = $updraftplus->get_max_packet_size();
1186
1187 while (($is_plain && !feof($dbhandle)) || (!$is_plain && !gzeof($dbhandle))) {
1188 // Up to 1Mb
1189 $buffer = ($is_plain) ? rtrim(fgets($dbhandle, 1048576)) : rtrim(gzgets($dbhandle, 1048576));
1190 // Discard comments
1191 if (empty($buffer) || substr($buffer, 0, 1) == '#' || preg_match('/^--(\s|$)/', substr($buffer, 0, 3))) {
1192 if ('' == $this->old_siteurl && preg_match('/^\# Backup of: (http(.*))$/', $buffer, $matches)) {
1193 $this->old_siteurl = untrailingslashit($matches[1]);
1194 $updraftplus->log_e('<strong>Backup of:</strong> %s', htmlspecialchars($this->old_siteurl));
1195 do_action('updraftplus_restore_db_record_old_siteurl', $this->old_siteurl);
1196 } elseif (false === $this->created_by_version && preg_match('/^\# Created by UpdraftPlus version ([\d\.]+)/', $buffer, $matches)) {
1197 $this->created_by_version = trim($matches[1]);
1198 echo '<strong>'.__('Backup created by:', 'updraftplus').'</strong> '.htmlspecialchars($this->created_by_version).'<br>';
1199 $updraftplus->log('Backup created by: '.$this->created_by_version);
1200 } elseif ('' == $this->old_home && preg_match('/^\# Home URL: (http(.*))$/', $buffer, $matches)) {
1201 $this->old_home = untrailingslashit($matches[1]);
1202 if ($this->old_siteurl && $this->old_home != $this->old_siteurl) {
1203 echo '<strong>'.__('Site home:', 'updraftplus').'</strong> '.htmlspecialchars($this->old_home).'<br>';
1204 $updraftplus->log('Site home: '.$this->old_home);
1205 }
1206 do_action('updraftplus_restore_db_record_old_home', $this->old_home);
1207 } elseif ('' == $this->old_content && preg_match('/^\# Content URL: (http(.*))$/', $buffer, $matches)) {
1208 $this->old_content = untrailingslashit($matches[1]);
1209 echo '<strong>'.__('Content URL:', 'updraftplus').'</strong> '.htmlspecialchars($this->old_content).'<br>';
1210 $updraftplus->log('Content URL: '.$this->old_content);
1211 do_action('updraftplus_restore_db_record_old_content', $this->old_content);
1212 } elseif ('' == $old_table_prefix && (preg_match('/^\# Table prefix: (\S+)$/', $buffer, $matches) || preg_match('/^-- Table Prefix: (\S+)$/i', $buffer, $matches))) {
1213 # We also support backwpup style:
1214 # -- Table Prefix: wp_
1215 $old_table_prefix = $matches[1];
1216 echo '<strong>'.__('Old table prefix:', 'updraftplus').'</strong> '.htmlspecialchars($old_table_prefix).'<br>';
1217 $updraftplus->log("Old table prefix: ".$old_table_prefix);
1218 } elseif ($gathering_siteinfo && preg_match('/^\# Site info: (\S+)$/', $buffer, $matches)) {
1219 if ('end' == $matches[1]) {
1220 $gathering_siteinfo = false;
1221 // Sanity checks
1222 if (isset($old_siteinfo['multisite']) && !$old_siteinfo['multisite'] && is_multisite()) {
1223 // Just need to check that you're crazy
1224 if (!defined('UPDRAFTPLUS_EXPERIMENTAL_IMPORTINTOMULTISITE') || UPDRAFTPLUS_EXPERIMENTAL_IMPORTINTOMULTISITE != true) {
1225 return new WP_Error('multisite_error', $this->strings['multisite_error']);
1226 }
1227 // Got the needed code?
1228 if (!class_exists('UpdraftPlusAddOn_MultiSite') || !class_exists('UpdraftPlus_Addons_Migrator')) {
1229 return new WP_Error('missing_addons', __('To import an ordinary WordPress site into a multisite installation requires both the multisite and migrator add-ons.', 'updraftplus'));
1230 }
1231 }
1232 } elseif (preg_match('/^([^=]+)=(.*)$/', $matches[1], $kvmatches)) {
1233 $key = $kvmatches[1];
1234 $val = $kvmatches[2];
1235 echo '<strong>'.__('Site information:','updraftplus').'</strong>'.' '.htmlspecialchars($key).' = '.htmlspecialchars($val).'<br>';
1236 $updraftplus->log("Site information: ".$key."=".$val);
1237 $old_siteinfo[$key]=$val;
1238 if ('multisite' == $key) {
1239 if ($val) { $this->ud_backup_is_multisite=1; } else { $this->ud_backup_is_multisite = 0;}
1240 }
1241 }
1242 }
1243 continue;
1244 }
1245
1246 // Detect INSERT commands early, so that we can split them if necessary
1247 if ($sql_line && preg_match('/^\s*(insert into \`?([^\`]*)\`?\s+(values|\())/i', $sql_line, $matches)) {
1248 $sql_type = 3;
1249 $insert_prefix = $matches[1];
1250 }
1251
1252 # Deal with case where adding this line will take us over the MySQL max_allowed_packet limit - must split, if we can (if it looks like consecutive rows)
1253 # ALlow a 100-byte margin for error (including searching/replacing table prefix)
1254 if (3 == $sql_type && $sql_line && strlen($sql_line.$buffer) > ($this->max_allowed_packet - 100) && preg_match('/,\s*$/', $sql_line) && preg_match('/^\s*\(/', $buffer)) {
1255 // Remove the final comma; replace with semi-colon
1256 $sql_line = substr(rtrim($sql_line), 0, strlen($sql_line)-1).';';
1257 if ('' != $old_table_prefix && $import_table_prefix != $old_table_prefix) $sql_line = $updraftplus->str_replace_once($old_table_prefix, $import_table_prefix, $sql_line);
1258 # Run the SQL command; then set up for the next one.
1259 $this->line++;
1260 echo __("Split line to avoid exceeding maximum packet size", 'updraftplus')." (".strlen($sql_line)." + ".strlen($buffer)." : ".$this->max_allowed_packet.")<br>";
1261 $updraftplus->log("Split line to avoid exceeding maximum packet size (".strlen($sql_line)." + ".strlen($buffer)." : ".$this->max_allowed_packet.")");
1262 $do_exec = $this->sql_exec($sql_line, $sql_type, $import_table_prefix);
1263 if (is_wp_error($do_exec)) return $do_exec;
1264 # Reset, then carry on
1265 $sql_line = $insert_prefix." ";
1266 }
1267
1268 $sql_line .= $buffer;
1269 # Do we have a complete line yet? We used to just test the final character for ';' here (up to 1.8.12), but that was too unsophisticated
1270 if (
1271 (3 == $sql_type && !preg_match('/\)\s*;$/', substr($sql_line, -3, 3)))
1272 || (3 != $sql_type && ';' != substr($sql_line, -1, 1))
1273 ) continue;
1274
1275 $this->line++;
1276
1277 # We now have a complete line - process it
1278
1279 if (3 == $sql_type && $sql_line && strlen($sql_line) > $this->max_allowed_packet) {
1280 $this->log_oversized_packet($sql_line);
1281 # Reset
1282 $sql_line = '';
1283 $sql_type = -1;
1284 # If this is the very first SQL line of the options table, we need to bail; it's essential
1285 if (0 == $this->insert_statements_run && $restoring_table && $restoring_table == $import_table_prefix.'options') {
1286 return new WP_Error('initial_db_error', sprintf(__('An error occurred on the first %s command - aborting run','updraftplus'), 'INSERT (options)'));
1287 }
1288 continue;
1289 }
1290
1291 # The timed overhead of this is negligible
1292 if (preg_match('/^\s*drop table if exists \`?([^\`]*)\`?\s*;/i', $sql_line, $matches)) {
1293
1294 $sql_type = 1;
1295
1296 if (!isset($printed_new_table_prefix)) {
1297 $import_table_prefix = $this->pre_sql_actions($import_table_prefix);
1298 if (false===$import_table_prefix || is_wp_error($import_table_prefix)) return $import_table_prefix;
1299 $printed_new_table_prefix = true;
1300 }
1301
1302 $this->table_name = $matches[1];
1303
1304 // Legacy, less reliable - in case it was not caught before
1305 if ('' == $old_table_prefix && preg_match('/^([a-z0-9]+)_.*$/i', $this->table_name, $tmatches)) {
1306 $old_table_prefix = $tmatches[1].'_';
1307 echo '<strong>'.__('Old table prefix:', 'updraftplus').'</strong> '.htmlspecialchars($old_table_prefix).'<br>';
1308 $updraftplus->log("Old table prefix: $old_table_prefix");
1309 }
1310
1311 $this->new_table_name = ($old_table_prefix) ? $updraftplus->str_replace_once($old_table_prefix, $import_table_prefix, $this->table_name) : $this->table_name;
1312
1313 if ('' != $old_table_prefix && $import_table_prefix != $old_table_prefix) {
1314 $sql_line = $updraftplus->str_replace_once($old_table_prefix, $import_table_prefix, $sql_line);
1315 }
1316 $this->tables_been_dropped[] = $this->new_table_name;
1317
1318 } elseif (preg_match('/^\s*create table \`?([^\`\(]*)\`?\s*\(/i', $sql_line, $matches)) {
1319
1320 $sql_type = 2;
1321 $this->insert_statements_run = 0;
1322 $this->table_name = $matches[1];
1323
1324 // Legacy, less reliable - in case it was not caught before. We added it in here (CREATE) as well as in DROP because of SQL dumps which lack DROP statements.
1325 if ('' == $old_table_prefix && preg_match('/^([a-z0-9]+)_.*$/i', $this->table_name, $tmatches)) {
1326 $old_table_prefix = $tmatches[1].'_';
1327 echo '<strong>'.__('Old table prefix:', 'updraftplus').'</strong> '.htmlspecialchars($old_table_prefix).'<br>';
1328 $updraftplus->log("Old table prefix: $old_table_prefix");
1329 }
1330
1331 // MySQL 4.1 outputs TYPE=, but accepts ENGINE=; 5.1 onwards accept *only* ENGINE=
1332 $sql_line = $updraftplus->str_lreplace('TYPE=', 'ENGINE=', $sql_line);
1333
1334 if (empty($printed_new_table_prefix)) {
1335 $import_table_prefix = $this->pre_sql_actions($import_table_prefix);
1336 if (false === $import_table_prefix || is_wp_error($import_table_prefix)) return $import_table_prefix;
1337 $printed_new_table_prefix = true;
1338 }
1339
1340 $this->new_table_name = ($old_table_prefix) ? $updraftplus->str_replace_once($old_table_prefix, $import_table_prefix, $this->table_name) : $this->table_name;
1341
1342 // This CREATE TABLE command may be the de-facto mark for the end of processing a previous table (which is so if this is not the first table in the SQL dump)
1343 if ($restoring_table) {
1344
1345 # Attempt to reconnect if the DB connection dropped (may not succeed, of course - but that will soon become evident)
1346 $updraftplus->check_db_connection($this->wpdb_obj);
1347
1348 // After restoring the options table, we can set old_siteurl if on legacy (i.e. not already set)
1349 if ($restoring_table == $import_table_prefix.'options') {
1350 if ('' == $this->old_siteurl || '' == $this->old_home || '' == $this->old_content) {
1351 global $updraftplus_addons_migrator;
1352 if (isset($updraftplus_addons_migrator->new_blogid)) switch_to_blog($updraftplus_addons_migrator->new_blogid);
1353
1354 if ('' == $this->old_siteurl) {
1355 $this->old_siteurl = untrailingslashit($wpdb->get_row("SELECT option_value FROM $wpdb->options WHERE option_name='siteurl'")->option_value);
1356 do_action('updraftplus_restore_db_record_old_siteurl', $this->old_siteurl);
1357 }
1358 if ('' == $this->old_home) {
1359 $this->old_home = untrailingslashit($wpdb->get_row("SELECT option_value FROM $wpdb->options WHERE option_name='home'")->option_value);
1360 do_action('updraftplus_restore_db_record_old_home', $this->old_home);
1361 }
1362 if ('' == $this->old_content) {
1363 $this->old_content = $this->old_siteurl.'/wp-content';
1364 do_action('updraftplus_restore_db_record_old_content', $this->old_content);
1365 }
1366 if (isset($updraftplus_addons_migrator->new_blogid)) restore_current_blog();
1367 }
1368 }
1369
1370 $this->restored_table($restoring_table, $import_table_prefix, $old_table_prefix);
1371
1372 }
1373
1374 $engine = "(?)"; $engine_change_message = '';
1375 if (preg_match('/ENGINE=([^\s;]+)/', $sql_line, $eng_match)) {
1376 $engine = $eng_match[1];
1377 if (isset($supported_engines[$engine])) {
1378 #echo sprintf(__('Requested table engine (%s) is present.', 'updraftplus'), $engine);
1379 if ('myisam' == strtolower($engine)) {
1380 $sql_line = preg_replace('/PAGE_CHECKSUM=\d\s?/', '', $sql_line, 1);
1381 }
1382 } else {
1383 $engine_change_message = sprintf(__('Requested table engine (%s) is not present - changing to MyISAM.', 'updraftplus'), $engine)."<br>";
1384 $sql_line = $updraftplus->str_lreplace("ENGINE=$eng_match", "ENGINE=MyISAM", $sql_line);
1385 // Remove (M)aria options
1386 if ('maria' == strtolower($engine) || 'aria' == strtolower($engine)) {
1387 $sql_line = preg_replace('/PAGE_CHECKSUM=\d\s?/', '', $sql_line, 1);
1388 $sql_line = preg_replace('/TRANSACTIONAL=\d\s?/', '', $sql_line, 1);
1389 }
1390 }
1391 }
1392
1393 $this->table_name = $matches[1];
1394 echo '<strong>'.sprintf(__('Restoring table (%s)','updraftplus'), $engine).":</strong> ".htmlspecialchars($this->table_name);
1395 $logline = "Restoring table ($engine): ".$this->table_name;
1396 if ('' != $old_table_prefix && $import_table_prefix != $old_table_prefix) {
1397 echo ' - '.__('will restore as:', 'updraftplus').' '.htmlspecialchars($this->new_table_name);
1398 $logline .= " - will restore as: ".$this->new_table_name;
1399 $sql_line = $updraftplus->str_replace_once($old_table_prefix, $import_table_prefix, $sql_line);
1400 }
1401 $updraftplus->log($logline);
1402 $restoring_table = $this->new_table_name;
1403 echo '<br>';
1404 if ($engine_change_message) echo $engine_change_message;
1405
1406 } elseif (preg_match('/^\s*(insert into \`?([^\`]*)\`?\s+(values|\())/i', $sql_line, $matches)) {
1407 $sql_type = 3;
1408 if ('' != $old_table_prefix && $import_table_prefix != $old_table_prefix) $sql_line = $updraftplus->str_replace_once($old_table_prefix, $import_table_prefix, $sql_line);
1409 } elseif (preg_match('/^\s*(\/\*\!40000 )?(alter|lock) tables? \`?([^\`\(]*)\`?\s+(write|disable|enable)/i', $sql_line, $matches)) {
1410 # Only binary mysqldump produces this pattern (LOCK TABLES `table` WRITE, ALTER TABLE `table` (DISABLE|ENABLE) KEYS)
1411 $sql_type = 4;
1412 if ('' != $old_table_prefix && $import_table_prefix != $old_table_prefix) $sql_line = $updraftplus->str_replace_once($old_table_prefix, $import_table_prefix, $sql_line);
1413 } elseif (preg_match('/^(un)?lock tables/i', $sql_line)) {
1414 # BackWPup produces these
1415 $sql_type = 5;
1416 } elseif (preg_match('/^(create|drop) database /i', $sql_line)) {
1417 # WPB2D produces these, as do some phpMyAdmin dumps
1418 $sql_type = 6;
1419 } elseif (preg_match('/^use /i', $sql_line)) {
1420 # WPB2D produces these, as do some phpMyAdmin dumps
1421 $sql_type = 7;
1422 }
1423 // if (5 !== $sql_type) {
1424 if ($sql_type < 6) {
1425 $do_exec = $this->sql_exec($sql_line, $sql_type);
1426 if (is_wp_error($do_exec)) return $do_exec;
1427 } else {
1428 $updraftplus->log("Skipped SQL statement (unwanted type=$sql_type): $sql_line");
1429 }
1430
1431 # Reset
1432 $sql_line = '';
1433 $sql_type = -1;
1434
1435 }
1436
1437 if ($restoring_table) $this->restored_table($restoring_table, $import_table_prefix, $old_table_prefix);
1438
1439 $time_taken = microtime(true) - $this->start_time;
1440 $updraftplus->log_e('Finished: lines processed: %d in %.2f seconds', $this->line, $time_taken);
1441 if ($is_plain) {
1442 fclose($dbhandle);
1443 } else {
1444 gzclose($dbhandle);
1445 }
1446
1447 global $wp_filesystem;
1448
1449 $wp_filesystem->delete($working_dir.'/'.$db_basename, false, 'f');
1450 return true;
1451
1452 }
1453
1454 private function log_oversized_packet($sql_line) {
1455 global $updraftplus;
1456 $logit = substr($sql_line, 0, 100);
1457 $updraftplus->log(sprintf("An SQL line that is larger than the maximum packet size and cannot be split was found: %s", '('.strlen($sql_line).', '.$logit.' ...)'));
1458 echo '<strong>'.__('Warning:', 'updraftplus').'</strong> '.sprintf(__("An SQL line that is larger than the maximum packet size and cannot be split was found; this line will not be processed, but will be dropped: %s", 'updraftplus'), '('.strlen($sql_line).', '.$this->max_allowed_packet.', '.$logit.' ...)')."<br>";
1459 }
1460
1461 # UPDATE is sql_type=5 (not used in the function, but used in Migrator and so noted here for reference)
1462 # $import_table_prefix is only use in one place in this function (long INSERTs), and otherwise need/should not be supplied
1463 public function sql_exec($sql_line, $sql_type, $import_table_prefix = '') {
1464
1465 global $wpdb, $updraftplus;
1466 $ignore_errors = false;
1467 # Type 2 = CREATE TABLE
1468 if (2 == $sql_type && $this->create_forbidden) {
1469 $updraftplus->log_e('Cannot create new tables, so skipping this command (%s)', htmlspecialchars($sql_line));
1470 $req = true;
1471 } else {
1472
1473 if (2 == $sql_type && !$this->drop_forbidden) {
1474 # We choose, for now, to be very conservative - we only do the apparently-missing drop if we have never seen any drop - i.e. assume that in SQL dumps with missing DROPs, that it's because there are no DROPs at all
1475 if (!in_array($this->new_table_name, $this->tables_been_dropped)) {
1476 $updraftplus->log_e('Table to be implicitly dropped: %s', $this->new_table_name);
1477 # TODO: Actually drop
1478 $this->sql_exec('DROP TABLE IF EXISTS '.esc_sql($this->new_table_name), 1);
1479 $this->tables_been_dropped[] = $this->new_table_name;
1480 }
1481 }
1482
1483 # Type 1 = DROP TABLE
1484 if (1 == $sql_type && $this->drop_forbidden) {
1485 $sql_line = "DELETE FROM ".$updraftplus->backquote($this->new_table_name);
1486 $updraftplus->log_e('Cannot drop tables, so deleting instead (%s)', $sql_line);
1487 $ignore_errors = true;
1488 }
1489
1490 if (3 == $sql_type && $sql_line && strlen($sql_line) > $this->max_allowed_packet) {
1491 $this->log_oversized_packet($sql_line);
1492 # If this is the very first SQL line of the options table, we need to bail; it's essential
1493 $this->errors++;
1494 if (0 == $this->insert_statements_run && $this->new_table_name && $this->new_table_name == $import_table_prefix.'options') {
1495 return new WP_Error('initial_db_error', sprintf(__('An error occurred on the first %s command - aborting run','updraftplus'), 'INSERT (options)'));
1496 }
1497 return false;
1498 }
1499
1500 if ($this->use_wpdb) {
1501 $req = $wpdb->query($sql_line);
1502 if (!$req) $this->last_error = $wpdb->last_error;
1503 } else {
1504 if ($this->use_mysqli) {
1505 $req = mysqli_query($this->mysql_dbh, $sql_line);
1506 if (!$req) $this->last_error = mysqli_error($this->mysql_dbh);
1507 } else {
1508 $req = mysql_unbuffered_query($sql_line, $this->mysql_dbh);
1509 if (!$req) $this->last_error = mysql_error($this->mysql_dbh);
1510 }
1511 }
1512 if (3 == $sql_type) $this->insert_statements_run++;
1513 if (1 == $sql_type) $this->tables_been_dropped[] = $this->new_table_name;
1514 $this->statements_run++;
1515 }
1516
1517 if (!$req) {
1518 if (!$ignore_errors) $this->errors++;
1519 $print_err = (strlen($sql_line) > 100) ? substr($sql_line, 0, 100).' ...' : $sql_line;
1520 echo sprintf(_x('An error (%s) occurred:', 'The user is being told the number of times an error has happened, e.g. An error (27) occurred', 'updraftplus'), $this->errors)." - ".htmlspecialchars($this->last_error)." - ".__('the database query being run was:','updraftplus').' '.htmlspecialchars($print_err).'<br>';
1521 $updraftplus->log("An error (".$this->errors.") occurred: ".$this->last_error." - SQL query was: ".substr($sql_line, 0, 65536));
1522 // First command is expected to be DROP TABLE
1523 if (1 == $this->errors && 2 == $sql_type && 0 == $this->tables_created) {
1524 return new WP_Error('initial_db_error', sprintf(__('An error occurred on the first %s command - aborting run','updraftplus'), 'CREATE TABLE'));
1525 }
1526 if ($this->errors>49) {
1527 return new WP_Error('too_many_db_errors', __('Too many database errors have occurred - aborting','updraftplus'));
1528 }
1529 } elseif ($sql_type == 2) {
1530 $this->tables_created++;
1531 }
1532
1533 if ($this->line >0 && ($this->line)%50 == 0) {
1534 if ($this->line > $this->line_last_logged && (($this->line)%250 == 0 || $this->line<250)) {
1535 $this->line_last_logged = $this->line;
1536 $time_taken = microtime(true) - $this->start_time;
1537 $updraftplus->log_e('Database queries processed: %d in %.2f seconds',$this->line, $time_taken);
1538 }
1539 }
1540 return $req;
1541 }
1542
1543 // function option_filter($which) {
1544 // if (strpos($which, 'pre_option') !== false) { echo "OPT_FILT: $which<br>\n"; }
1545 // return false;
1546 // }
1547
1548 private function flush_rewrite_rules() {
1549
1550 // We have to deal with the fact that the procedures used call get_option, which could be looking at the wrong table prefix, or have the wrong thing cached
1551
1552 global $updraftplus_addons_migrator;
1553 if (!empty($updraftplus_addons_migrator->new_blogid)) switch_to_blog($updraftplus_addons_migrator->new_blogid);
1554
1555 foreach (array('permalink_structure', 'rewrite_rules', 'page_on_front') as $opt) {
1556 add_filter('pre_option_'.$opt, array($this, 'option_filter_'.$opt));
1557 }
1558
1559 global $wp_rewrite;
1560 $wp_rewrite->init();
1561 // Don't do this: it will cause rules created by plugins that weren't active at the start of the restore run to be lost
1562 # flush_rewrite_rules(true);
1563
1564 if ( function_exists( 'save_mod_rewrite_rules' ) )
1565 save_mod_rewrite_rules();
1566 if ( function_exists( 'iis7_save_url_rewrite_rules' ) )
1567 iis7_save_url_rewrite_rules();
1568
1569 foreach (array('permalink_structure', 'rewrite_rules', 'page_on_front') as $opt) {
1570 remove_filter('pre_option_'.$opt, array($this, 'option_filter_'.$opt));
1571 }
1572
1573 if (!empty($updraftplus_addons_migrator->new_blogid)) restore_current_blog();
1574
1575 }
1576
1577 private function restored_table($table, $import_table_prefix, $old_table_prefix) {
1578
1579 global $wpdb, $updraftplus;
1580
1581 // WordPress has an option name predicated upon the table prefix. Yuk.
1582 // if ($table == $import_table_prefix.'options') {
1583 if (preg_match('/^([\d+]_)?options$/', substr($table, strlen($import_table_prefix)), $matches)) {
1584 if (($this->is_multisite && !empty($matches[1])) || !$this->is_multisite && $table == $import_table_prefix.'options') {
1585
1586 $mprefix = (empty($matches[1])) ? '' : $matches[1];
1587
1588 if ($import_table_prefix != $old_table_prefix) {
1589 $updraftplus->log("Table prefix has changed: changing options table field(s) accordingly (".$mprefix."options)");
1590 echo sprintf(__('Table prefix has changed: changing %s table field(s) accordingly:', 'updraftplus'),'option').' ';
1591 if (false === $wpdb->query("UPDATE ${import_table_prefix}".$mprefix."options SET option_name='${import_table_prefix}".$mprefix."user_roles' WHERE option_name='${old_table_prefix}".$mprefix."user_roles' LIMIT 1")) {
1592 echo __('Error','updraftplus');
1593 $updraftplus->log("Error when changing options table fields");
1594 } else {
1595 $updraftplus->log("Options table fields changed OK");
1596 echo __('OK', 'updraftplus');
1597 }
1598 echo '<br>';
1599 }
1600
1601 // Now deal with the situation where the imported database sets a new over-ride upload_path that is absolute - which may not be wanted
1602 $new_upload_path = $wpdb->get_row($wpdb->prepare("SELECT option_value FROM ${import_table_prefix}".$mprefix."options WHERE option_name = %s LIMIT 1", 'upload_path'));
1603 $new_upload_path = (is_object($new_upload_path)) ? $new_upload_path->option_value : '';
1604 // The danger situation is absolute and points somewhere that is now perhaps not accessible at all
1605 if (!empty($new_upload_path) && $new_upload_path != $this->prior_upload_path && (strpos($new_upload_path, '/') === 0) || preg_match('#^[A-Za-z]:[/\\\]#', $new_upload_path)) {
1606 if (!file_exists($new_upload_path)) {
1607 $updraftplus->log_e("Uploads path (%s) does not exist - resetting (%s)", $new_upload_path, $this->prior_upload_path);
1608 if (false === $wpdb->query("UPDATE ${import_table_prefix}".$mprefix."options SET option_value='".esc_sql($this->prior_upload_path)."' WHERE option_name='upload_path' LIMIT 1")) {
1609 echo __('Error','updraftplus');
1610 $updraftplus->log("Failed");
1611 }
1612 #update_option('upload_path', $this->prior_upload_path);
1613 }
1614 }
1615
1616 # TODO: Do on all WPMU tables
1617 if ($table == $import_table_prefix.'options') {
1618 # Bad plugin that hard-codes path references - https://wordpress.org/plugins/custom-content-type-manager/
1619 $cctm_data = $wpdb->get_row($wpdb->prepare("SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", 'cctm_data'));
1620 if (!empty($cctm_data->option_value)) {
1621 $cctm_data = maybe_unserialize($cctm_data->option_value);
1622 if (is_array($cctm_data) && !empty($cctm_data['cache']) && is_array($cctm_data['cache'])) {
1623 $cctm_data['cache'] = array();
1624 $updraftplus->log_e("Custom content type manager plugin data detected: clearing option cache");
1625 update_option('cctm_data', $cctm_data);
1626 }
1627 }
1628 # Another - http://www.elegantthemes.com/gallery/elegant-builder/
1629 $elegant_data = $wpdb->get_row($wpdb->prepare("SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", 'et_images_temp_folder'));
1630 if (!empty($elegant_data->option_value)) {
1631 $dbase = basename($elegant_data->option_value);
1632 $wp_upload_dir = wp_upload_dir();
1633 $edir = $wp_upload_dir['basedir'];
1634 if (!is_dir($edir.'/'.$dbase)) @mkdir($edir.'/'.$dbase);
1635 $updraftplus->log_e("Elegant themes theme builder plugin data detected: resetting temporary folder");
1636 update_option('et_images_temp_folder', $edir.'/'.$dbase);
1637 }
1638 # The gantry menu plugin sometimes uses too-long transient names, causing the timeout option to be missing; and hence the transient becomes permanent.
1639 # WP 3.4 onwards has $wpdb->delete(). But we support 3.2 onwards.
1640 $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_gantry-menu%' OR option_name LIKE '_transient_timeout_gantry-menu%'");
1641 }
1642 }
1643
1644 } elseif ($import_table_prefix != $old_table_prefix && preg_match('/^([\d+]_)?usermeta$/', substr($table, strlen($import_table_prefix)), $matches)) {
1645
1646 # This table is not a per-site table, but per-install
1647
1648 $updraftplus->log("Table prefix has changed: changing usermeta table field(s) accordingly");
1649 echo sprintf(__('Table prefix has changed: changing %s table field(s) accordingly:', 'updraftplus'),'usermeta').' ';
1650
1651 $um_sql = "SELECT umeta_id, meta_key
1652 FROM ${import_table_prefix}usermeta
1653 WHERE meta_key
1654 LIKE '".str_replace('_', '\_', $old_table_prefix)."%'";
1655
1656 $meta_keys = $wpdb->get_results($um_sql);
1657
1658 $old_prefix_length = strlen($old_table_prefix);
1659
1660 $errors_occurred = false;
1661 foreach ($meta_keys as $meta_key ) {
1662 //Create new meta key
1663 $new_meta_key = $import_table_prefix . substr($meta_key->meta_key, $old_prefix_length);
1664
1665 $query = "UPDATE " . $import_table_prefix . "usermeta
1666 SET meta_key='".$new_meta_key."'
1667 WHERE umeta_id=".$meta_key->umeta_id;
1668
1669 if (false === $wpdb->query($query)) $errors_occurred = true;
1670 }
1671
1672 if ($errors_occurred) {
1673 $updraftplus->log("Error when changing usermeta table fields");
1674 echo __('Error', 'updraftplus');
1675 } else {
1676 $updraftplus->log("Usermeta table fields changed OK");
1677 echo __('OK', 'updraftplus');
1678 }
1679 echo "<br>";
1680
1681 }
1682
1683 do_action('updraftplus_restored_db_table', $table, $import_table_prefix);
1684
1685 // Re-generate permalinks. Do this last - i.e. make sure everything else is fixed up first.
1686 if ($table == $import_table_prefix.'options') $this->flush_rewrite_rules();
1687
1688 }
1689
1690 }
1691
1692 // The purpose of this is that, in a certain case, we want to forbid the "move" operation from doing a copy/delete if a direct move fails... because we have our own method for retrying (and don't want to risk copying a tonne of data if we can avoid it)
1693 if (!class_exists('WP_Filesystem_Direct')) {
1694 if (!class_exists('WP_Filesystem_Base')) require_once(ABSPATH.'wp-admin/includes/class-wp-filesystem-base.php');
1695 require_once(ABSPATH.'wp-admin/includes/class-wp-filesystem-direct.php');
1696 }
1697 class UpdraftPlus_WP_Filesystem_Direct extends WP_Filesystem_Direct {
1698
1699 function move($source, $destination, $overwrite = false) {
1700 if ( ! $overwrite && $this->exists($destination) )
1701 return false;
1702
1703 // try using rename first. if that fails (for example, source is read only) try copy
1704 if ( @rename($source, $destination) )
1705 return true;
1706
1707 return false;
1708 }
1709
1710 }
1711
1712 if (!class_exists('WP_Upgrader_Skin')) require_once(ABSPATH.'wp-admin/includes/class-wp-upgrader.php');
1713 class Updraft_Restorer_Skin extends WP_Upgrader_Skin {
1714
1715 function header() {}
1716 function footer() {}
1717 function bulk_header() {}
1718 function bulk_footer() {}
1719
1720 function error($error) {
1721 if (!$error) return;
1722 global $updraftplus;
1723 if (is_wp_error($error)) {
1724 $updraftplus->log_wp_error($error, true);
1725 } elseif (is_string($error)) {
1726 echo '<strong>';
1727 $updraftplus->log_e($error);
1728 echo '</strong>';
1729 }
1730 }
1731
1732 function feedback($string) {
1733
1734 if ( isset( $this->upgrader->strings[$string] ) )
1735 $string = $this->upgrader->strings[$string];
1736
1737 if ( strpos($string, '%') !== false ) {
1738 $args = func_get_args();
1739 $args = array_splice($args, 1);
1740 if ( $args ) {
1741 $args = array_map( 'strip_tags', $args );
1742 $args = array_map( 'esc_html', $args );
1743 $string = vsprintf($string, $args);
1744 }
1745 }
1746 if ( empty($string) ) return;
1747
1748 global $updraftplus;
1749 $updraftplus->log_e($string);
1750 }
1751 }
1752
1753 // Get a protected property
1754 class UpdraftPlus_WPDB extends wpdb {
1755 public function updraftplus_getdbh() {
1756 return $this->dbh;
1757 }
1758 public function updraftplus_use_mysqli() {
1759 return !empty($this->use_mysqli);
1760 }
1761 }
1762