UpdraftPlus WordPress Backup Plugin - Version 1.3.2

Version Description

  • 01/23/2013 =
  • Internal reorganisation, enabling UpdraftPlus Premium
Download this release

Release Info

Developer DavidAnderson
Plugin Icon 128x128 UpdraftPlus WordPress Backup Plugin
Version 1.3.2
Comparing to
See all releases

Code changes from version 1.3.6 to 1.3.2

includes/Dropbox/OAuth/Storage/Encrypter.php CHANGED
@@ -16,7 +16,7 @@ class Dropbox_Encrypter
16
  const CIPHER = MCRYPT_RIJNDAEL_128;
17
  const MODE = MCRYPT_MODE_CBC;
18
  const KEY_SIZE = 32;
19
- const IV_SIZE = 16;
20
  const IV_SOURCE = MCRYPT_DEV_URANDOM;
21
 
22
  /**
16
  const CIPHER = MCRYPT_RIJNDAEL_128;
17
  const MODE = MCRYPT_MODE_CBC;
18
  const KEY_SIZE = 32;
19
+ const IV_SIZE = 32;
20
  const IV_SOURCE = MCRYPT_DEV_URANDOM;
21
 
22
  /**
includes/updraft-restorer.php CHANGED
@@ -14,13 +14,13 @@ class Updraft_Restorer extends WP_Upgrader {
14
 
15
  function restore_backup($backup_file, $type) {
16
 
17
- if ($type != 'plugins' && $type != 'themes' && $type != 'others' && $type != 'uploads') continue;
18
 
19
  global $wp_filesystem;
20
  $this->init();
21
  $this->backup_strings();
22
 
23
- $res = $this->fs_connect(array(ABSPATH, WP_CONTENT_DIR) );
24
  if(!$res) exit;
25
 
26
  $wp_dir = trailingslashit($wp_filesystem->abspath());
@@ -80,10 +80,10 @@ class Updraft_Restorer extends WP_Upgrader {
80
 
81
  switch($type) {
82
  case 'uploads':
83
- @$wp_filesystem->chmod($wp_dir . "wp-content/$type", 0777, true);
84
  break;
85
  default:
86
- @$wp_filesystem->chmod($wp_dir . "wp-content/$type", FS_CHMOD_DIR);
87
  }
88
  }
89
 
14
 
15
  function restore_backup($backup_file, $type) {
16
 
17
+ if ($type == 'nonce') return;
18
 
19
  global $wp_filesystem;
20
  $this->init();
21
  $this->backup_strings();
22
 
23
+ $res = $this->fs_connect( array(ABSPATH, WP_CONTENT_DIR) );
24
  if(!$res) exit;
25
 
26
  $wp_dir = trailingslashit($wp_filesystem->abspath());
80
 
81
  switch($type) {
82
  case 'uploads':
83
+ $wp_filesystem->chmod($wp_dir . "wp-content/$type", 0777, true);
84
  break;
85
  default:
86
+ $wp_filesystem->chmod($wp_dir . "wp-content/$type", FS_CHMOD_DIR);
87
  }
88
  }
89
 
methods/dropbox.php CHANGED
@@ -346,9 +346,8 @@ class UpdraftPlus_BackupModule_dropbox {
346
  require_once(UPDRAFTPLUS_DIR.'/includes/Dropbox/OAuth/Consumer/Curl.php');
347
  // require_once(UPDRAFTPLUS_DIR.'/includes/Dropbox/OAuth/Consumer/WordPress.php');
348
 
349
- // This formulation was required by Dropbox's developer programme, for what reasons you should ask them!
350
- $sec = UpdraftPlus_Options::get_updraft_option(base64_decode('dXBkcmFmdF9kcm9wYm94X2FwcGtleQ=='));
351
- $key = UpdraftPlus_Options::get_updraft_option(base64_decode('dXBkcmFmdF9kcm9wYm94X3NlY3JldA=='));
352
 
353
  // Set the callback URL
354
  $callback = admin_url('options-general.php?page=updraftplus&action=updraftmethod-dropbox-auth');
346
  require_once(UPDRAFTPLUS_DIR.'/includes/Dropbox/OAuth/Consumer/Curl.php');
347
  // require_once(UPDRAFTPLUS_DIR.'/includes/Dropbox/OAuth/Consumer/WordPress.php');
348
 
349
+ $sec = UpdraftPlus_Options::get_updraft_option('updraft_dropbox_appkey');
350
+ $key = UpdraftPlus_Options::get_updraft_option('updraft_dropbox_secret');
 
351
 
352
  // Set the callback URL
353
  $callback = admin_url('options-general.php?page=updraftplus&action=updraftmethod-dropbox-auth');
readme.txt CHANGED
@@ -1,6 +1,6 @@
1
  === UpdraftPlus Backup ===
2
  Contributors: David Anderson
3
- Tags: backup, restore, database, cloud, amazon, s3, dropbox, google drive, ftp, cloud, back up, multisite
4
  Requires at least: 3.2
5
  Tested up to: 3.5
6
  Stable tag: 1.3.2
@@ -113,7 +113,7 @@ You can check the changelog for changes; but the original Updraft, before I fork
113
 
114
  = Any known bugs ? =
115
 
116
- Not a bug as such, but one issue to be aware of is that backups of very large sites (lots of uploaded media) are quite complex matters, given the limits of running inside WordPress on a huge variety of different web hosting setups. With large sites, you need to use Amazon S3, which UpdraftPlus supports (since 0.9.20) or Google Drive (since 0.9.21) or Dropbox (since 1.2.19), because these support chunked, resumable uploads. Other backup methods have code (since 0.9.0) to retry failed uploads of an archive, but the upload cannot be chunked, so if an archive is enormous (i.e. cannot be completely uploaded in the time that PHP is allowed for running on your web host) it cannot work.
117
 
118
  = I encrypted my database - how do I decrypt it? =
119
 
@@ -141,11 +141,6 @@ Thanks for asking - yes, I have. Check out my profile page - http://profiles.wor
141
 
142
  == Changelog ==
143
 
144
- = 1.3.6 - 01/24/2013 =
145
- * Fixed faulty assumptions in 'resume' code, now leading to more reliable resuming
146
- * Removed some duplicate code; first attempt and resumptions now uses same code
147
- * Added further parameters that should be removed on a wipe operation
148
-
149
  = 1.3.2 - 01/23/2013 =
150
  * Internal reorganisation, enabling UpdraftPlus Premium
151
 
1
  === UpdraftPlus Backup ===
2
  Contributors: David Anderson
3
+ Tags: backup, restore, database, cloud, amazon, s3, dropbox, google drive, gdrive, ftp, cloud, updraft, back up, multisite
4
  Requires at least: 3.2
5
  Tested up to: 3.5
6
  Stable tag: 1.3.2
113
 
114
  = Any known bugs ? =
115
 
116
+ Not a bug as such, but one issue to be aware of is that backups of very large sites (lots of uploaded media) can fail, or require a longer period of time to succeed, due to timing out. This depends on how many seconds your web host allows a PHP process to run and how many resources they give you. With such sites, you need to use Amazon S3, which UpdraftPlus supports (since 0.9.20) or Google Drive (since 0.9.21) or Dropbox (since 1.2.19) with chunked, resumable uploads. Other backup methods have code (since 0.9.0) to retry failed uploads of an archive, but the upload cannot be chunked, so if an archive is enormous (i.e. cannot be completely uploaded in the time that PHP is allowed for running on your web host) it cannot work.
117
 
118
  = I encrypted my database - how do I decrypt it? =
119
 
141
 
142
  == Changelog ==
143
 
 
 
 
 
 
144
  = 1.3.2 - 01/23/2013 =
145
  * Internal reorganisation, enabling UpdraftPlus Premium
146
 
updraftplus.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: UpdraftPlus - Backup/Restore
4
  Plugin URI: http://wordpress.org/extend/plugins/updraftplus
5
  Description: Backup and restore: your content and database can be automatically backed up to Amazon S3, Dropbox, Google Drive, FTP or email, on separate schedules.
6
  Author: David Anderson.
7
- Version: 1.3.6
8
  Donate link: http://david.dw-perspective.org.uk/donate
9
  License: GPLv3 or later
10
  Author URI: http://wordshell.net
@@ -13,25 +13,19 @@ Author URI: http://wordshell.net
13
  /*
14
  TODO
15
  //Add SFTP, Box.Net, SugarSync and Microsoft Skydrive support??
16
- //The restorer has a hard-coded wp-content - fix
17
- //Read safe-mode only once, remembering it will be totally removed from PHP
18
- //Button for wiping files. Also auto-wipe on de-activate/de-install.
19
- //Change DB encryption to not require whole gzip in memory (twice)
20
  //improve error reporting / pretty up return messages in admin area. One thing: have a "backup is now finished" flag. Otherwise with the resuming things get ambiguous/confusing. See http://wordpress.org/support/topic/backup-status - user was not aware that backup completely failed. Maybe a "backup status" field for each nonce that gets updated? (Even via AJAX?)
21
  //?? On 'backup now', open up a Lightbox, count down 5 seconds, then start examining the log file (if it can be found)
22
  //Should make clear in dashboard what is a non-fatal error (i.e. can be retried) - leads to unnecessary bug reports
23
- // Move the inclusion, cloud and retention data into the backup job (i.e. don't read current config, make it an attribute of each job). In fact, everything should be. So audit all code for where get_option is called inside a backup run: it shouldn't happen.
24
  // Should we resume if the only errors were upon deletion (i.e. the backup itself was fine?) Presently we do, but it displays errors for the user to confuse them. Perhaps better to make pruning a separate scheuled task??
25
  // Make jobs *individually* resumable (i.e. all the state info must be keyed on the nonce; then call the resume event *specifying the nonce*)
26
  // Warn the user if their zip-file creation is slooowww...
27
  // Create a "Want Support?" button/console, that leads them through what is needed, and performs some basic tests...
28
  // Resuming partial FTP uploads
29
  // Turn expert options into a jQuery toggle
30
- // Provide backup/restoration for UpdraftPlus's settings, to allow 'bootstrap' on a fresh WP install - some kind of single-use code which a remote UpdraftPlus can use to authenticate
31
  // Multiple jobs
32
- // When looking for files to delete, is the current encryption setting used? Should not be.
33
- // Create single zip, containing even WordPress itself
34
- // When a new backup starts, AJAX-update the 'Last backup' display in the admin page.
35
 
36
  Encrypt filesystem, if memory allows (and have option for abort if not); split up into multiple zips when needed
37
  // Does not delete old custom directories upon a restore?
@@ -78,7 +72,7 @@ if (!class_exists('UpdraftPlus_Options')) require_once(UPDRAFTPLUS_DIR.'/options
78
 
79
  class UpdraftPlus {
80
 
81
- var $version = '1.3.6';
82
  var $plugin_title = 'UpdraftPlus Backup/Restore';
83
 
84
  // Choices will be shown in the admin menu in the order used here
@@ -94,6 +88,7 @@ class UpdraftPlus {
94
  var $dbhandle_isgz;
95
  var $errors = array();
96
  var $nonce;
 
97
  var $logfile_name = "";
98
  var $logfile_handle = false;
99
  var $backup_time;
@@ -101,8 +96,6 @@ class UpdraftPlus {
101
  var $opened_log_time;
102
  var $backup_dir;
103
 
104
- var $jobdata;
105
-
106
  function __construct() {
107
  // Initialisation actions - takes place on plugin load
108
  # Create admin page
@@ -112,7 +105,7 @@ class UpdraftPlus {
112
  # backup_all is used by the manual "Backup Now" button
113
  add_action('updraft_backup_all', array($this,'backup_all'));
114
  # this is our runs-after-backup event, whose purpose is to see if it succeeded or failed, and resume/mom-up etc.
115
- add_action('updraft_backup_resume', array($this,'backup_resume'), 10, 3);
116
  add_action('wp_enqueue_scripts', array($this, 'ajax_enqueue') );
117
  add_action('wp_ajax_updraft_download_backup', array($this, 'updraft_download_backup'));
118
  add_action('wp_ajax_updraft_ajax', array($this, 'updraft_ajax_handler'));
@@ -162,6 +155,8 @@ class UpdraftPlus {
162
  $this->backup_time = time();
163
  $nonce = substr(md5(time().rand()), 20);
164
  $this->nonce = $nonce;
 
 
165
  }
166
 
167
  function logfile_open($nonce) {
@@ -181,65 +176,58 @@ class UpdraftPlus {
181
  UpdraftPlus_Options::update_updraft_option("updraft_lastmessage", $line." (".date('M d H:i:s').")");
182
  }
183
 
184
- function backup_resume($resumption_no, $bnonce, $btime) {
185
-
186
  @ignore_user_abort(true);
187
  // This is scheduled for 5 minutes after a backup job starts
188
-
189
- // Restore state
190
- if ($resumption_no > 0) {
191
- $this->nonce = $bnonce;
192
- $this->backup_time = $btime;
193
- $this->logfile_open($bnonce);
 
 
 
194
  }
195
-
196
- $this->log("Backup run: resumption=$resumption_no, nonce=$bnonce, begun at=$btime");
197
-
198
  // Schedule again, to run in 5 minutes again, in case we again fail
199
  $resume_delay = 300;
200
  // A different argument than before is needed otherwise the event is ignored
201
  $next_resumption = $resumption_no+1;
202
  if ($next_resumption < 10) {
203
- $this->log("Scheduling a resumption ($next_resumption) in case this run gets aborted");
204
- wp_schedule_single_event(time()+$resume_delay, 'updraft_backup_resume', array($next_resumption, $bnonce, $btime));
205
  } else {
206
- $this->log("The current run is our tenth attempt - will not schedule a further attempt");
207
  }
 
208
 
209
- // This should be always called; if there were no files in this run, it returns us an empty array
210
- $backup_array = $this->resumable_backup_of_files($resumption_no);
211
  // This save, if there was something, is then immediately picked up again
212
  if (is_array($backup_array)) $this->save_backup_history($backup_array);
213
 
214
  // Returns an array, most recent first, of backup sets
215
  $backup_history = $this->get_backup_history();
216
- if (!isset($backup_history[$btime])) {
217
- $this->log("Could not find a record in the database of a backup with this timestamp");
218
- }
219
 
220
  $our_files=$backup_history[$btime];
221
  if (!is_array($our_files)) $our_files = array();
222
 
223
  $undone_files = array();
224
 
225
- $backup_database = $this->jobdata_get('backup_database');
226
 
227
  // The transient is read and written below (instead of using the existing variable) so that we can copy-and-paste this part as needed.
228
- if ($backup_database == "begun" || $backup_database == "finished" || $backup_database == "encrypted") {
229
  if ($backup_database == "begun") {
230
- if ($resumption_no > 0) {
231
- $this->log("Resuming creation of database dump");
232
- } else {
233
- $this->log("Beginning creation of database dump");
234
- }
235
- } elseif ($backup_database == 'encrypted') {
236
- $this->log("Database dump: Creation and encryption were completed already");
237
  } else {
238
  $this->log("Database dump: Creation was completed already");
239
  }
240
  $db_backup = $this->backup_db($backup_database);
241
  if(is_array($our_files) && is_string($db_backup)) $our_files['db'] = $db_backup;
242
- if ($backup_database != 'encrypted') $this->jobdata_set("backup_database", 'finished');
 
 
243
  } else {
244
  $this->log("Unrecognised data when trying to ascertain if the database was backed up ($backup_database)");
245
  }
@@ -253,20 +241,18 @@ class UpdraftPlus {
253
  if (isset($our_files['db']) && !preg_match("/\.crypt$/", $our_files['db'])) {
254
  $our_files['db'] = $this->encrypt_file($our_files['db']);
255
  $this->save_backup_history($our_files);
256
- $this->jobdata_set("backup_database", "encrypted");
257
  }
258
 
259
  foreach ($our_files as $key => $file) {
260
 
261
- // Only continue if the stored info was about a dump
262
- if ($key != 'plugins' && $key != 'themes' && $key != 'others' && $key != 'uploads' && $key != 'db') continue;
263
 
264
  $hash = md5($file);
265
  $fullpath = $this->backups_dir_location().'/'.$file;
266
- if ($this->jobdata_get("uploaded_$hash") === "yes") {
267
- $this->log("$file: $key: This file has already been successfully uploaded");
268
  } elseif (is_file($fullpath)) {
269
- $this->log("$file: $key: This file has not yet been successfully uploaded: will queue");
270
  $undone_files[$key] = $file;
271
  } else {
272
  $this->log("$file: Note: This file was not marked as successfully uploaded, but does not exist on the local filesystem");
@@ -276,58 +262,68 @@ class UpdraftPlus {
276
 
277
  if (count($undone_files) == 0) {
278
  $this->log("There were no more files that needed uploading; backup job is complete");
279
- // No email, as the user probably already got one if something else completed the run
280
- $this->backup_finish($next_resumption, true, false, $resumption_no);
281
  return;
282
  }
283
 
284
  $this->log("Requesting backup of the files that were not successfully uploaded");
285
  $this->cloud_backup($undone_files);
286
 
287
- $this->log("Resume backup ($bnonce, $resumption_no): finish run");
 
288
  $this->backup_finish($next_resumption, true, true, $resumption_no);
289
 
290
  }
291
 
292
  function backup_all() {
293
- $this->boot_backup(true,true);
294
  }
295
 
296
  function backup_files() {
297
  # Note that the "false" for database gets over-ridden automatically if they turn out to have the same schedules
298
- $this->boot_backup(true,false);
 
299
  }
300
 
301
  function backup_database() {
302
  # Note that nothing will happen if the file backup had the same schedule
303
- $this->boot_backup(false,true);
304
- }
305
-
306
- function jobdata_set($key, $value) {
307
- if (is_array($this->jobdata)) {
308
- $this->jobdata[$key] = $value;
309
- } else {
310
- $this->jobdata = array($key => $value);
311
- }
312
- set_transient("updraft_jobdata_".$this->nonce, $this->jobdata, 14400);
313
  }
314
 
315
- function jobdata_get($key) {
316
- if (!is_array($this->jobdata)) {
317
- $this->jobdata = get_transient("updraft_jobdata_".$this->nonce);
318
- if (!is_array($this->jobdata)) return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  }
320
- return (isset($this->jobdata[$key])) ? $this->jobdata[$key] : false;
321
  }
322
 
323
  // This uses a transient; its only purpose is to indicate *total* completion; there is no actual danger, just wasted time, in resuming when it was not needed. So the transient just helps save resources.
324
- function resumable_backup_of_files($resumption_no) {
325
  //backup directories and return a numerically indexed array of file paths to the backup files
326
- $transient_status = $this->jobdata_get("backup_files");
327
  if ($transient_status == "finished") {
328
  $this->log("Creation of backups of directories: already finished");
329
  } elseif ($transient_status == "begun") {
330
- if ($resumption_no>0) {
331
  $this->log("Creation of backups of directories: had begun; will resume");
332
  } else {
333
  $this->log("Creation of backups of directories: beginning");
@@ -339,23 +335,24 @@ class UpdraftPlus {
339
  }
340
  // We want this array, even if already finished
341
  $backup_array = $this->backup_dirs($transient_status);
 
342
  // This can get over-written later
343
- $this->jobdata_set('backup_files', 'finished');
 
344
  return $backup_array;
345
  }
346
 
347
- // This procedure initiates a backup run
348
- function boot_backup($backup_files, $backup_database) {
349
 
350
  @ignore_user_abort(true);
351
-
352
  //generate backup information
353
  $this->backup_time_nonce();
 
354
  $this->logfile_open($this->nonce);
355
 
356
  // Log some information that may be helpful
357
  global $wp_version;
358
- $this->log("Tasks: Backup files: $backup_files (schedule: ".UpdraftPlus_Options::get_updraft_option('updraft_interval', 'unset').") Backup DB: $backup_database (schedule: ".UpdraftPlus_Options::get_updraft_option('updraft_interval_database', 'unset').")");
359
 
360
  # If the files and database schedules are the same, and if this the file one, then we rope in database too.
361
  # On the other hand, if the schedules were the same and this was the database run, then there is nothing to do.
@@ -365,18 +362,87 @@ class UpdraftPlus {
365
 
366
  $this->log("Processed schedules. Tasks now: Backup files: $backup_files Backup DB: $backup_database");
367
 
368
- # If nothing to be done, then just finish
369
- if (!$backup_files && !$backup_database) {
370
- $this->backup_finish(1, false, false, 0);
371
- return;
372
- }
 
 
 
 
 
 
 
373
 
374
- // Save what *should* be done, to make it resumable from this point on
375
- if ($backup_database) $this->jobdata_set("backup_database", "begun");
376
- if ($backup_files) $this->jobdata_set("backup_files", "begun");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
 
378
- // Everthing is now set up; now go
379
- $this->backup_resume(0, $this->nonce, $this->backup_time);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
 
381
  }
382
 
@@ -392,7 +458,15 @@ class UpdraftPlus {
392
  $rijndael->setKey($encryption);
393
  $updraft_dir = $this->backups_dir_location();
394
  $file_size = @filesize($updraft_dir.'/'.$file)/1024;
395
- if (false === file_put_contents($updraft_dir.'/'.$file.'.crypt' , $rijndael->encrypt(file_get_contents($updraft_dir.'/'.$file)))) {$encryption_error = 1;}
 
 
 
 
 
 
 
 
396
  if (0 == $encryption_error) {
397
  $time_taken = max(0.000001, microtime(true)-$microstart);
398
  $this->log("$file: encryption successful: ".round($file_size,1)."Kb in ".round($time_taken,1)."s (".round($file_size/$time_taken, 1)."Kb/s)");
@@ -415,8 +489,9 @@ class UpdraftPlus {
415
  if (empty($this->errors)) {
416
  if ($clear_nonce_transient) {
417
  $this->log("There were no errors in the uploads, so the 'resume' event is being unscheduled");
418
- wp_clear_scheduled_hook('updraft_backup_resume', array($cancel_event, $this->nonce, $this->backup_time));
419
- // TODO: Delete the job transient (is presently useful for debugging, and only lasts 4 hours)
 
420
  }
421
  } else {
422
  $this->log("There were errors in the uploads, so the 'resume' event is remaining scheduled");
@@ -479,12 +554,7 @@ class UpdraftPlus {
479
 
480
  $append_log = ($debug_mode && $this->logfile_name != "") ? "\r\nLog contents:\r\n".file_get_contents($this->logfile_name) : "" ;
481
 
482
- $backup_files = $this->jobdata_get("backup_files");
483
- $backup_db = $this->jobdata_get("backup_database");
484
-
485
- $backup_contains = (substr($backup_contains,0,10) == "Files only") ? "Files and database" : "Database only (no files)";
486
-
487
- wp_mail($sendmail_to,'Backed up: '.get_bloginfo('name').' (UpdraftPlus '.$this->version.') '.date('Y-m-d H:i',time()),'Site: '.site_url()."\r\nUpdraftPlus WordPress backup is complete.\r\nBackup contains: ".$backup_contains."\r\n\r\n".$this->wordshell_random_advert(0)."\r\n".$append_log);
488
 
489
  }
490
 
@@ -498,9 +568,10 @@ class UpdraftPlus {
498
 
499
  // This should be called whenever a file is successfully uploaded
500
  function uploaded_file($file, $id = false) {
 
501
  $hash = md5($file);
502
  $this->log("Recording as successfully uploaded: $file ($hash)");
503
- $this->jobdata_set("uploaded_$hash", "yes");
504
  if ($id) {
505
  $ids = UpdraftPlus_Options::get_updraft_option('updraft_file_ids', array() );
506
  $ids[$file] = $id;
@@ -681,7 +752,7 @@ class UpdraftPlus {
681
  $rate = round($kbsize/$timetaken, 1);
682
  $this->log("Created $whichone zip - file size is ".round($kbsize,1)." Kb in ".round($timetaken,1)." s ($rate Kb/s)");
683
  }
684
-
685
  return basename($full_path);
686
  }
687
 
@@ -731,51 +802,51 @@ class UpdraftPlus {
731
 
732
  # Others
733
  if (UpdraftPlus_Options::get_updraft_option('updraft_include_others', true)) {
 
734
 
735
  if ($transient_status == 'finished') {
736
  $backup_array['others'] = $backup_file_basename.'-others.zip';
737
  } else {
738
- $this->log("Beginning backup of other directories found in the content directory");
739
-
740
- // http://www.phpconcept.net/pclzip/user-guide/53
741
- /* First parameter to create is:
742
- An array of filenames or dirnames,
743
- or
744
- A string containing the filename or a dirname,
745
- or
746
- A string containing a list of filename or dirname separated by a comma.
747
- */
748
-
749
- # Initialise
750
- $other_dirlist = array();
751
-
752
- $others_skip = preg_split("/,/",UpdraftPlus_Options::get_updraft_option('updraft_include_others_exclude', UPDRAFT_DEFAULT_OTHERS_EXCLUDE));
753
- # Make the values into the keys
754
- $others_skip = array_flip($others_skip);
755
-
756
- $this->log('Looking for candidates to back up in: '.WP_CONTENT_DIR);
757
- if ($handle = opendir(WP_CONTENT_DIR)) {
758
- while (false !== ($entry = readdir($handle))) {
759
- $candidate = WP_CONTENT_DIR.'/'.$entry;
760
- if ($entry == "." || $entry == "..") { ; }
761
- elseif ($candidate == $updraft_dir) { $this->log("others: $entry: skipping: this is the updraft directory"); }
762
- elseif ($candidate == $wp_themes_dir) { $this->log("others: $entry: skipping: this is the themes directory"); }
763
- elseif ($candidate == $wp_upload_dir) { $this->log("others: $entry: skipping: this is the uploads directory"); }
764
- elseif ($candidate == $wp_plugins_dir) { $this->log("others: $entry: skipping: this is the plugins directory"); }
765
- elseif (isset($others_skip[$entry])) { $this->log("others: $entry: skipping: excluded by options"); }
766
- else { $this->log("others: $entry: adding to list"); array_push($other_dirlist, $candidate); }
767
- }
768
- } else {
769
- $this->log('ERROR: Could not read the content directory: '.WP_CONTENT_DIR);
770
- $this->error('Could not read the content directory: '.WP_CONTENT_DIR);
771
- }
772
 
773
- if (count($other_dirlist)>0) {
774
- $created = $this->create_zip($other_dirlist, 'others', $updraft_dir, $backup_file_basename);
775
- if ($created) $backup_array['others'] = $created;
776
- } else {
777
- $this->log("No backup of other directories: there was nothing found to back up");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
778
  }
 
 
 
 
 
 
 
 
 
 
 
779
  # If we are not already finished
780
  }
781
  } else {
@@ -803,7 +874,7 @@ class UpdraftPlus {
803
  global $wpdb;
804
  $backup_history = @unserialize($wpdb->get_var($wpdb->prepare("SELECT option_value from $wpdb->options WHERE option_name='updraft_backup_history'")));
805
  if(is_array($backup_history)) {
806
- krsort($backup_history); //reverse sort so earliest backup is last on the array. Then we can array_pop.
807
  } else {
808
  $backup_history = array();
809
  }
@@ -864,7 +935,6 @@ class UpdraftPlus {
864
  $backup_file_base = $updraft_dir.'/'.$file_base;
865
 
866
  if ("finished" == $already_done) return basename($backup_file_base.'-db.gz');
867
- if ("encrypted" == $already_done) return basename($backup_file_base.'-db.gz.crypt');
868
 
869
  $total_tables = 0;
870
 
@@ -1002,7 +1072,10 @@ class UpdraftPlus {
1002
  }
1003
 
1004
  // Comment in SQL-file
1005
- $this->stow("\n\n#\n# " . sprintf(__('Data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n#\n");
 
 
 
1006
  }
1007
 
1008
  // In UpdraftPlus, segment is always 'none'
@@ -1018,16 +1091,19 @@ class UpdraftPlus {
1018
  }
1019
  }
1020
 
 
 
 
1021
  if($segment == 'none') {
1022
  $row_start = 0;
1023
- $row_inc = 100;
1024
  } else {
1025
- $row_start = $segment * 100;
1026
- $row_inc = 100;
1027
  }
1028
-
1029
  do {
1030
- if ( !@ini_get('safe_mode') || strtolower(@ini_get('safe_mode')) == "off") @set_time_limit(15*60);
 
1031
  $table_data = $wpdb->get_results("SELECT * FROM $table LIMIT {$row_start}, {$row_inc}", ARRAY_A);
1032
  $entries = 'INSERT INTO ' . $this->backquote($table) . ' VALUES (';
1033
  // \x08\\x09, not required
@@ -1445,7 +1521,6 @@ class UpdraftPlus {
1445
  }
1446
 
1447
  function wordshell_random_advert($urls) {
1448
- if (defined('UPDRAFTPLUS_PREMIUM')) return "";
1449
  $rad = rand(0,6);
1450
  switch ($rad) {
1451
  case 0:
@@ -1652,21 +1727,16 @@ class UpdraftPlus {
1652
  $dir_info = '<span style="color:red">Backup directory specified is <b>not</b> writable, or does not exist. <span style="font-size:110%;font-weight:bold"><a href="options-general.php?page=updraftplus&action=updraft_create_backup_dir">Click here</a></span> to attempt to create the directory and set the permissions. If that is unsuccessful check the permissions on your server or change it to another directory that is writable by your web server process.</span>';
1653
  }
1654
 
1655
- echo $dir_info ?> This is where UpdraftPlus will write the zip files it creates initially. This directory must be writable by your web server. Typically you'll want to have it inside your wp-content folder (this is the default). <b>Do not</b> place it inside your uploads dir, as that will cause recursion issues (backups of backups of backups of...).</td>
1656
  </tr>
1657
  <tr>
1658
  <td></td>
1659
  <td>
1660
- <?php
1661
- $ws_ad = $this->wordshell_random_advert(1);
1662
- if ($ws_ad) {
1663
- ?>
1664
  <p style="margin: 10px 0; padding: 10px; font-size: 140%; background-color: lightYellow; border-color: #E6DB55; border: 1px solid; border-radius: 4px;">
1665
- <?php echo $ws_ad; ?>
1666
- </p>
1667
  <?php
1668
- }
1669
  ?>
 
1670
  </td>
1671
  </tr>
1672
  <tr>
@@ -1748,10 +1818,10 @@ class UpdraftPlus {
1748
  echo '</div>';
1749
  }
1750
 
1751
- if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_all') { $this->boot_backup(true,true); }
1752
  elseif (isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_db') { $this->backup_db(); }
1753
  elseif (isset($_POST['action']) && $_POST['action'] == 'updraft_wipesettings') {
1754
- $settings = array('updraft_interval', 'updraft_interval_database', 'updraft_retain', 'updraft_retain_db', 'updraft_encryptionphrase', 'updraft_service', 'updraft_s3_login', 'updraft_s3_pass', 'updraft_s3_remote_path', 'updraft_dropbox_appkey', 'updraft_dropbox_secret', 'updraft_dropbox_folder', 'updraft_googledrive_clientid', 'updraft_googledrive_secret', 'updraft_googledrive_remotepath', 'updraft_ftp_login', 'updraft_ftp_pass', 'updraft_ftp_remote_path', 'updraft_server_address', 'updraft_dir', 'updraft_email', 'updraft_delete_local', 'updraft_debug_mode', 'updraft_include_plugins', 'updraft_include_themes', 'updraft_include_uploads', 'updraft_include_others', 'updraft_include_others_exclude', 'updraft_lastmessage', 'updraft_googledrive_clientid', 'updraft_googledrive_token', 'updraft_googledrive_secret', 'updraft_dropboxtk_request_token', 'updraft_dropboxtk_access_token', 'updraft_dropbox_folder', '');
1755
  foreach ($settings as $s) {
1756
  UpdraftPlus_Options::delete_updraft_option($s);
1757
  }
@@ -1762,7 +1832,7 @@ class UpdraftPlus {
1762
  <div class="wrap">
1763
  <h1><?php echo $this->plugin_title; ?></h1>
1764
 
1765
- Maintained by <b>David Anderson</b> (<a href="http://david.dw-perspective.org.uk">Homepage</a><?php if (!defined('UPDRAFTPLUS_PREMIUM')) { ?> | <a href="http://updraftplus.com">Premium</a> | <a href="http://wordshell.net">WordShell - WordPress command line</a> | <a href="http://david.dw-perspective.org.uk/donate">Donate</a><?php } ?> | <a href="http://wordpress.org/extend/plugins/updraftplus/faq/">FAQs</a> | <a href="http://profiles.wordpress.org/davidanderson/">My other WordPress plugins</a>). Version: <?php echo $this->version; ?>
1766
  <br>
1767
  <?php
1768
  if(isset($_GET['updraft_restore_success'])) {
@@ -1770,7 +1840,9 @@ class UpdraftPlus {
1770
  }
1771
 
1772
  $ws_advert = $this->wordshell_random_advert(1);
1773
- if ($ws_advert) { echo '<div class="updated fade" style="max-width: 800px; font-size:140%; line-height: 140%; padding:14px; clear:left;">'.$ws_advert.'</div>'; }
 
 
1774
 
1775
  if($deleted_old_dirs) echo '<div style="color:blue">Old directories successfully deleted.</div>';
1776
 
4
  Plugin URI: http://wordpress.org/extend/plugins/updraftplus
5
  Description: Backup and restore: your content and database can be automatically backed up to Amazon S3, Dropbox, Google Drive, FTP or email, on separate schedules.
6
  Author: David Anderson.
7
+ Version: 1.3.2
8
  Donate link: http://david.dw-perspective.org.uk/donate
9
  License: GPLv3 or later
10
  Author URI: http://wordshell.net
13
  /*
14
  TODO
15
  //Add SFTP, Box.Net, SugarSync and Microsoft Skydrive support??
 
 
 
 
16
  //improve error reporting / pretty up return messages in admin area. One thing: have a "backup is now finished" flag. Otherwise with the resuming things get ambiguous/confusing. See http://wordpress.org/support/topic/backup-status - user was not aware that backup completely failed. Maybe a "backup status" field for each nonce that gets updated? (Even via AJAX?)
17
  //?? On 'backup now', open up a Lightbox, count down 5 seconds, then start examining the log file (if it can be found)
18
  //Should make clear in dashboard what is a non-fatal error (i.e. can be retried) - leads to unnecessary bug reports
19
+ //Eventually, when everything can be resumed, we will no longer need the backup() routine; it can be replaced with the resume() routine
20
  // Should we resume if the only errors were upon deletion (i.e. the backup itself was fine?) Presently we do, but it displays errors for the user to confuse them. Perhaps better to make pruning a separate scheuled task??
21
  // Make jobs *individually* resumable (i.e. all the state info must be keyed on the nonce; then call the resume event *specifying the nonce*)
22
  // Warn the user if their zip-file creation is slooowww...
23
  // Create a "Want Support?" button/console, that leads them through what is needed, and performs some basic tests...
24
  // Resuming partial FTP uploads
25
  // Turn expert options into a jQuery toggle
26
+ // Provide backup/restoration for UpdraftPlus's settings, to allow 'bootstrap' on a fresh WP install
27
  // Multiple jobs
28
+ // Multi-site
 
 
29
 
30
  Encrypt filesystem, if memory allows (and have option for abort if not); split up into multiple zips when needed
31
  // Does not delete old custom directories upon a restore?
72
 
73
  class UpdraftPlus {
74
 
75
+ var $version = '1.3.2';
76
  var $plugin_title = 'UpdraftPlus Backup/Restore';
77
 
78
  // Choices will be shown in the admin menu in the order used here
88
  var $dbhandle_isgz;
89
  var $errors = array();
90
  var $nonce;
91
+ var $cronrun_type = "none";
92
  var $logfile_name = "";
93
  var $logfile_handle = false;
94
  var $backup_time;
96
  var $opened_log_time;
97
  var $backup_dir;
98
 
 
 
99
  function __construct() {
100
  // Initialisation actions - takes place on plugin load
101
  # Create admin page
105
  # backup_all is used by the manual "Backup Now" button
106
  add_action('updraft_backup_all', array($this,'backup_all'));
107
  # this is our runs-after-backup event, whose purpose is to see if it succeeded or failed, and resume/mom-up etc.
108
+ add_action('updraft_backup_resume', array($this,'backup_resume'));
109
  add_action('wp_enqueue_scripts', array($this, 'ajax_enqueue') );
110
  add_action('wp_ajax_updraft_download_backup', array($this, 'updraft_download_backup'));
111
  add_action('wp_ajax_updraft_ajax', array($this, 'updraft_ajax_handler'));
155
  $this->backup_time = time();
156
  $nonce = substr(md5(time().rand()), 20);
157
  $this->nonce = $nonce;
158
+ // Short-lived, as we only use this for detecting a race condition
159
+ set_transient("updraftplus_runtype_$nonce", $this->cronrun_type, 300);
160
  }
161
 
162
  function logfile_open($nonce) {
176
  UpdraftPlus_Options::update_updraft_option("updraft_lastmessage", $line." (".date('M d H:i:s').")");
177
  }
178
 
179
+ function backup_resume($resumption_no) {
 
180
  @ignore_user_abort(true);
181
  // This is scheduled for 5 minutes after a backup job starts
182
+ $bnonce = get_transient('updraftplus_backup_job_nonce');
183
+ if (!$bnonce) return;
184
+ $this->nonce = $bnonce;
185
+ $this->logfile_open($bnonce);
186
+ $this->log("Resume backup ($resumption_no): begin run (will check for any remaining jobs)");
187
+ $btime = get_transient('updraftplus_backup_job_time');
188
+ if (!$btime) {
189
+ $this->log("Did not find stored time setting - aborting");
190
+ return;
191
  }
192
+ $this->log("Resuming backup: resumption=$resumption_no, nonce=$bnonce, begun at=$btime");
 
 
193
  // Schedule again, to run in 5 minutes again, in case we again fail
194
  $resume_delay = 300;
195
  // A different argument than before is needed otherwise the event is ignored
196
  $next_resumption = $resumption_no+1;
197
  if ($next_resumption < 10) {
198
+ wp_schedule_single_event(time()+$resume_delay, 'updraft_backup_resume' ,array($next_resumption));
 
199
  } else {
200
+ $this->log("The current run is our tenth attempt - will not try again");
201
  }
202
+ $this->backup_time = $btime;
203
 
204
+ $backup_array = $this->resumable_backup_of_files(false);
 
205
  // This save, if there was something, is then immediately picked up again
206
  if (is_array($backup_array)) $this->save_backup_history($backup_array);
207
 
208
  // Returns an array, most recent first, of backup sets
209
  $backup_history = $this->get_backup_history();
210
+ if (!isset($backup_history[$btime])) $this->log("Could not find a record in the database of a backup with this timestamp");
 
 
211
 
212
  $our_files=$backup_history[$btime];
213
  if (!is_array($our_files)) $our_files = array();
214
 
215
  $undone_files = array();
216
 
217
+ $backup_database = get_transient("updraft_backdb_".$bnonce);
218
 
219
  // The transient is read and written below (instead of using the existing variable) so that we can copy-and-paste this part as needed.
220
+ if ($backup_database == "begun" || $backup_database == "finished") {
221
  if ($backup_database == "begun") {
222
+ $this->log("Resuming creation of database dump");
 
 
 
 
 
 
223
  } else {
224
  $this->log("Database dump: Creation was completed already");
225
  }
226
  $db_backup = $this->backup_db($backup_database);
227
  if(is_array($our_files) && is_string($db_backup)) $our_files['db'] = $db_backup;
228
+ $backup_contains = get_transient("updraft_backupcontains_".$this->nonce);
229
+ $backup_contains = (substr($backup_contains,0,10) == "Files only") ? "Files and database" : "Database only (no files)";
230
+ set_transient("updraft_backupcontains_".$this->nonce, $backup_contains, 3600*3);
231
  } else {
232
  $this->log("Unrecognised data when trying to ascertain if the database was backed up ($backup_database)");
233
  }
241
  if (isset($our_files['db']) && !preg_match("/\.crypt$/", $our_files['db'])) {
242
  $our_files['db'] = $this->encrypt_file($our_files['db']);
243
  $this->save_backup_history($our_files);
 
244
  }
245
 
246
  foreach ($our_files as $key => $file) {
247
 
248
+ if ($key == 'nonce') continue;
 
249
 
250
  $hash = md5($file);
251
  $fullpath = $this->backups_dir_location().'/'.$file;
252
+ if (get_transient('updraft_'.$hash) === "yes") {
253
+ $this->log("$file: $key: This file has been successfully uploaded in the last 3 hours");
254
  } elseif (is_file($fullpath)) {
255
+ $this->log("$file: $key: This file has NOT been successfully uploaded in the last 3 hours: will retry");
256
  $undone_files[$key] = $file;
257
  } else {
258
  $this->log("$file: Note: This file was not marked as successfully uploaded, but does not exist on the local filesystem");
262
 
263
  if (count($undone_files) == 0) {
264
  $this->log("There were no more files that needed uploading; backup job is complete");
 
 
265
  return;
266
  }
267
 
268
  $this->log("Requesting backup of the files that were not successfully uploaded");
269
  $this->cloud_backup($undone_files);
270
 
271
+ $this->log("Resume backup ($resumption_no): finish run");
272
+
273
  $this->backup_finish($next_resumption, true, true, $resumption_no);
274
 
275
  }
276
 
277
  function backup_all() {
278
+ $this->backup(true,true);
279
  }
280
 
281
  function backup_files() {
282
  # Note that the "false" for database gets over-ridden automatically if they turn out to have the same schedules
283
+ $this->cronrun_type = "files";
284
+ $this->backup(true,false);
285
  }
286
 
287
  function backup_database() {
288
  # Note that nothing will happen if the file backup had the same schedule
289
+ $this->cronrun_type = "database";
290
+ $this->backup(false,true);
 
 
 
 
 
 
 
 
291
  }
292
 
293
+ function check_backup_race( $to_delete = false ) {
294
+ // Avoid caching
295
+ global $wpdb;
296
+ $row = $wpdb->get_row($wpdb->prepare("SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", "_transient_updraftplus_backup_job_nonce"));
297
+ $cur_trans = ( is_object( $row ) ) ? $row->option_value : "";
298
+ // Check if another backup job ID is stored in the transient
299
+ if ($cur_trans != "" && $cur_trans != $this->nonce) {
300
+ // Also check if that job is of the same type as ours, as two cron jobs could legitimately fire at the same time
301
+ $otherjob_crontype = get_transient("updraftplus_runtype_".$cur_trans);
302
+ // $this->cronrun_type should be "files", "database" or blank (if we were not run via a cron job)
303
+ if ($otherjob_crontype == $this->cronrun_type) {
304
+ $this->log("Another backup job ($cur_trans) of the same type ($otherjob_crontype) appears to now be running - terminating our run (apparent race condition)");
305
+ $bdir = $this->backups_dir_location();
306
+ if (is_array($to_delete)) {
307
+ foreach ($to_delete as $key => $file) {
308
+ if (is_file($bdir.'/'.$file)) {
309
+ $this->log("Deleting the file we created: ".$file);
310
+ @unlink($bdir.'/'.$file);
311
+ }
312
+ }
313
+ }
314
+ exit;
315
+ }
316
  }
 
317
  }
318
 
319
  // This uses a transient; its only purpose is to indicate *total* completion; there is no actual danger, just wasted time, in resuming when it was not needed. So the transient just helps save resources.
320
+ function resumable_backup_of_files($resumptionrun) {
321
  //backup directories and return a numerically indexed array of file paths to the backup files
322
+ $transient_status = get_transient("updraft_backf_".$this->nonce);
323
  if ($transient_status == "finished") {
324
  $this->log("Creation of backups of directories: already finished");
325
  } elseif ($transient_status == "begun") {
326
+ if ($resumptionrun) {
327
  $this->log("Creation of backups of directories: had begun; will resume");
328
  } else {
329
  $this->log("Creation of backups of directories: beginning");
335
  }
336
  // We want this array, even if already finished
337
  $backup_array = $this->backup_dirs($transient_status);
338
+ $backup_contains = "Files only (no database)";
339
  // This can get over-written later
340
+ set_transient("updraft_backupcontains_".$this->nonce, $backup_contains, 3600*3);
341
+ set_transient("updraft_backf_".$this->nonce, "finished", 3600*3);
342
  return $backup_array;
343
  }
344
 
345
+ function backup($backup_files, $backup_database) {
 
346
 
347
  @ignore_user_abort(true);
 
348
  //generate backup information
349
  $this->backup_time_nonce();
350
+
351
  $this->logfile_open($this->nonce);
352
 
353
  // Log some information that may be helpful
354
  global $wp_version;
355
+ $this->log("Tasks: Backup files: $backup_files (schedule: ".UpdraftPlus_Options::get_updraft_option('updraft_interval','unset').") Backup DB: $backup_database (schedule: ".UpdraftPlus_Options::get_updraft_option('updraft_interval_database','unset').")");
356
 
357
  # If the files and database schedules are the same, and if this the file one, then we rope in database too.
358
  # On the other hand, if the schedules were the same and this was the database run, then there is nothing to do.
362
 
363
  $this->log("Processed schedules. Tasks now: Backup files: $backup_files Backup DB: $backup_database");
364
 
365
+ $clear_nonce_transient = false;
366
+
367
+ # Possibly now nothing is to be done, except to close the log file
368
+ if ($backup_files || $backup_database) {
369
+
370
+ $clear_nonce_transient = true;
371
+
372
+ // Do not set the transient or schedule the resume event until now, when we know there is something to do - otherwise 'vacatated' runs (when the database is on the same schedule as the files, and they get combined, leading to an empty run) can over-write the resume event and prevent resumption (because it is 'successful' - there was nothing to do).
373
+ // If we don't finish in 3 hours, then we won't finish
374
+ // This transient indicates the identity of the current backup job (which can be used to find the files and logfile)
375
+ set_transient("updraftplus_backup_job_nonce", $this->nonce, 3600*3);
376
+ set_transient("updraftplus_backup_job_time", $this->backup_time, 3600*3);
377
 
378
+ // Schedule the event to run later, which checks on success and can resume the backup
379
+ // We save the time to a variable because it is needed for un-scheduling
380
+ $resume_delay = 300;
381
+ wp_schedule_single_event(time()+$resume_delay, 'updraft_backup_resume', array(1));
382
+ $this->log("In case we run out of time, scheduled a resumption at: $resume_delay seconds from now");
383
+
384
+ $backup_contains = "";
385
+ set_transient("updraft_backupcontains_".$this->nonce, "");
386
+
387
+ $backup_array = array();
388
+
389
+ $this->check_backup_race();
390
+
391
+ // Save what *should* be done, to make it resumable from this point on
392
+ set_transient("updraft_backdb_".$this->nonce, "begun", 3600*3);
393
+
394
+ // The function itself will set to 'finished' if relevant
395
+ // The presence of the transient indicates that files are supposed to be in this set
396
+ if ($backup_files) {
397
+ set_transient("updraft_backf_".$this->nonce, "begun", 3600*3);
398
+ $backup_array = $this->resumable_backup_of_files(false);
399
+ }
400
+
401
+ // Save this to our history so we can track backups for the retain feature
402
+ $this->log("Saving backup history");
403
+ $this->save_backup_history($backup_array);
404
+
405
+ $this->check_backup_race($backup_array);
406
+
407
+ // The transient is read and written below (instead of using the existing variable) so that we can copy-and-paste this part as needed.
408
+ if ($backup_database) {
409
+ $this->log("Beginning backup of database");
410
+ $db_backup = $this->backup_db();
411
+ if ($db_backup) $backup_array['db'] = $db_backup;
412
+ $backup_contains = get_transient("updraft_backupcontains_".$this->nonce);
413
+ $backup_contains = (substr($backup_contains,0,10) == "Files only") ? "Files and database" : "Database only (no files)";
414
+ set_transient("updraft_backupcontains_".$this->nonce, $backup_contains, 3600*3);
415
+ set_transient("updraft_backdb_".$this->nonce, "finished", 3600*3);
416
+ }
417
 
418
+ $this->check_backup_race($backup_array);
419
+
420
+ // Save this to our history so we can track backups for the retain feature
421
+ $this->log("Saving backup history");
422
+ // This is done before cloud despatch, because we want a record of what *should* be in the backup. Whether it actually makes it there or not is not yet known.
423
+ $this->save_backup_history($backup_array);
424
+
425
+ // Now encrypt the database, and re-save
426
+ if ($backup_database && isset($backup_array['db'])) {
427
+ $backup_array['db'] = $this->encrypt_file($backup_array['db']);
428
+ // Re-save with the possibly-altered database filename
429
+ $this->save_backup_history($backup_array);
430
+ }
431
+
432
+ //cloud operations (S3,Google Drive,FTP,email,nothing)
433
+ //this also calls the retain (prune) feature at the end (done in this method to reuse existing cloud connections)
434
+ if(is_array($backup_array) && count($backup_array) >0) {
435
+ $this->cloud_backup($backup_array);
436
+ }
437
+
438
+ //save the last backup info, including errors, if any
439
+ $this->log("Saving last backup information into WordPress db");
440
+ $this->save_last_backup($backup_array);
441
+
442
+ }
443
+
444
+ // Close log file; delete and also delete transients if not in debug mode
445
+ $this->backup_finish(1, $clear_nonce_transient, $clear_nonce_transient, 0);
446
 
447
  }
448
 
458
  $rijndael->setKey($encryption);
459
  $updraft_dir = $this->backups_dir_location();
460
  $file_size = @filesize($updraft_dir.'/'.$file)/1024;
461
+ $in_handle = @fopen($updraft_dir.'/'.$file,'r');
462
+ $buffer = "";
463
+ while (!feof ($in_handle)) {
464
+ $buffer .= fread($in_handle, 16384);
465
+ }
466
+ fclose ($in_handle);
467
+ $out_handle = @fopen($updraft_dir.'/'.$file.'.crypt','w');
468
+ if (!fwrite($out_handle, $rijndael->encrypt($buffer))) {$encryption_error = 1;}
469
+ fclose ($out_handle);
470
  if (0 == $encryption_error) {
471
  $time_taken = max(0.000001, microtime(true)-$microstart);
472
  $this->log("$file: encryption successful: ".round($file_size,1)."Kb in ".round($time_taken,1)."s (".round($file_size/$time_taken, 1)."Kb/s)");
489
  if (empty($this->errors)) {
490
  if ($clear_nonce_transient) {
491
  $this->log("There were no errors in the uploads, so the 'resume' event is being unscheduled");
492
+ wp_clear_scheduled_hook('updraft_backup_resume', array($cancel_event));
493
+ delete_transient("updraftplus_backup_job_nonce");
494
+ delete_transient("updraftplus_backup_job_time");
495
  }
496
  } else {
497
  $this->log("There were errors in the uploads, so the 'resume' event is remaining scheduled");
554
 
555
  $append_log = ($debug_mode && $this->logfile_name != "") ? "\r\nLog contents:\r\n".file_get_contents($this->logfile_name) : "" ;
556
 
557
+ wp_mail($sendmail_to,'Backed up: '.get_bloginfo('name').' (UpdraftPlus '.$this->version.') '.date('Y-m-d H:i',time()),'Site: '.site_url()."\r\nUpdraftPlus WordPress backup is complete.\r\nBackup contains: ".get_transient("updraft_backupcontains_".$this->nonce)."\r\n\r\n".$this->wordshell_random_advert(0)."\r\n".$append_log);
 
 
 
 
 
558
 
559
  }
560
 
568
 
569
  // This should be called whenever a file is successfully uploaded
570
  function uploaded_file($file, $id = false) {
571
+ # We take an MD5 hash because set_transient wants a name of 45 characters or less
572
  $hash = md5($file);
573
  $this->log("Recording as successfully uploaded: $file ($hash)");
574
+ set_transient("updraft_".$hash, "yes", 3600*4);
575
  if ($id) {
576
  $ids = UpdraftPlus_Options::get_updraft_option('updraft_file_ids', array() );
577
  $ids[$file] = $id;
752
  $rate = round($kbsize/$timetaken, 1);
753
  $this->log("Created $whichone zip - file size is ".round($kbsize,1)." Kb in ".round($timetaken,1)." s ($rate Kb/s)");
754
  }
755
+ $this->check_backup_race($backup_array);
756
  return basename($full_path);
757
  }
758
 
802
 
803
  # Others
804
  if (UpdraftPlus_Options::get_updraft_option('updraft_include_others', true)) {
805
+ $this->log("Beginning backup of other directories found in the content directory");
806
 
807
  if ($transient_status == 'finished') {
808
  $backup_array['others'] = $backup_file_basename.'-others.zip';
809
  } else {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
810
 
811
+ // http://www.phpconcept.net/pclzip/user-guide/53
812
+ /* First parameter to create is:
813
+ An array of filenames or dirnames,
814
+ or
815
+ A string containing the filename or a dirname,
816
+ or
817
+ A string containing a list of filename or dirname separated by a comma.
818
+ */
819
+
820
+ # Initialise
821
+ $other_dirlist = array();
822
+
823
+ $others_skip = preg_split("/,/",UpdraftPlus_Options::get_updraft_option('updraft_include_others_exclude', UPDRAFT_DEFAULT_OTHERS_EXCLUDE));
824
+ # Make the values into the keys
825
+ $others_skip = array_flip($others_skip);
826
+
827
+ $this->log('Looking for candidates to back up in: '.WP_CONTENT_DIR);
828
+ if ($handle = opendir(WP_CONTENT_DIR)) {
829
+ while (false !== ($entry = readdir($handle))) {
830
+ $candidate = WP_CONTENT_DIR.'/'.$entry;
831
+ if ($entry == "." || $entry == "..") { ; }
832
+ elseif ($candidate == $updraft_dir) { $this->log("others: $entry: skipping: this is the updraft directory"); }
833
+ elseif ($candidate == $wp_themes_dir) { $this->log("others: $entry: skipping: this is the themes directory"); }
834
+ elseif ($candidate == $wp_upload_dir) { $this->log("others: $entry: skipping: this is the uploads directory"); }
835
+ elseif ($candidate == $wp_plugins_dir) { $this->log("others: $entry: skipping: this is the plugins directory"); }
836
+ elseif (isset($others_skip[$entry])) { $this->log("others: $entry: skipping: excluded by options"); }
837
+ else { $this->log("others: $entry: adding to list"); array_push($other_dirlist, $candidate); }
838
  }
839
+ } else {
840
+ $this->log('ERROR: Could not read the content directory: '.WP_CONTENT_DIR);
841
+ $this->error('Could not read the content directory: '.WP_CONTENT_DIR);
842
+ }
843
+
844
+ if (count($other_dirlist)>0) {
845
+ $created = $this->create_zip($other_dirlist, 'others', $updraft_dir, $backup_file_basename);
846
+ if ($created) $backup_array['others'] = $created;
847
+ } else {
848
+ $this->log("No backup of other directories: there was nothing found to back up");
849
+ }
850
  # If we are not already finished
851
  }
852
  } else {
874
  global $wpdb;
875
  $backup_history = @unserialize($wpdb->get_var($wpdb->prepare("SELECT option_value from $wpdb->options WHERE option_name='updraft_backup_history'")));
876
  if(is_array($backup_history)) {
877
+ krsort($backup_history); //reverse sort so earliest backup is last on the array. this way we can array_pop
878
  } else {
879
  $backup_history = array();
880
  }
935
  $backup_file_base = $updraft_dir.'/'.$file_base;
936
 
937
  if ("finished" == $already_done) return basename($backup_file_base.'-db.gz');
 
938
 
939
  $total_tables = 0;
940
 
1072
  }
1073
 
1074
  // Comment in SQL-file
1075
+ $this->stow("\n\n");
1076
+ $this->stow("#\n");
1077
+ $this->stow('# ' . sprintf(__('Data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
1078
+ $this->stow("#\n");
1079
  }
1080
 
1081
  // In UpdraftPlus, segment is always 'none'
1091
  }
1092
  }
1093
 
1094
+ // Batch by $row_inc
1095
+ if ( ! defined('ROWS_PER_SEGMENT') ) define('ROWS_PER_SEGMENT', 100);
1096
+
1097
  if($segment == 'none') {
1098
  $row_start = 0;
1099
+ $row_inc = ROWS_PER_SEGMENT;
1100
  } else {
1101
+ $row_start = $segment * ROWS_PER_SEGMENT;
1102
+ $row_inc = ROWS_PER_SEGMENT;
1103
  }
 
1104
  do {
1105
+
1106
+ if ( !ini_get('safe_mode') || strtolower(ini_get('safe_mode')) == "off") @set_time_limit(15*60);
1107
  $table_data = $wpdb->get_results("SELECT * FROM $table LIMIT {$row_start}, {$row_inc}", ARRAY_A);
1108
  $entries = 'INSERT INTO ' . $this->backquote($table) . ' VALUES (';
1109
  // \x08\\x09, not required
1521
  }
1522
 
1523
  function wordshell_random_advert($urls) {
 
1524
  $rad = rand(0,6);
1525
  switch ($rad) {
1526
  case 0:
1727
  $dir_info = '<span style="color:red">Backup directory specified is <b>not</b> writable, or does not exist. <span style="font-size:110%;font-weight:bold"><a href="options-general.php?page=updraftplus&action=updraft_create_backup_dir">Click here</a></span> to attempt to create the directory and set the permissions. If that is unsuccessful check the permissions on your server or change it to another directory that is writable by your web server process.</span>';
1728
  }
1729
 
1730
+ echo $dir_info ?> This is where Updraft Backup/Restore will write the zip files it creates initially. This directory must be writable by your web server. Typically you'll want to have it inside your wp-content folder (this is the default). <b>Do not</b> place it inside your uploads dir, as that will cause recursion issues (backups of backups of backups of...).</td>
1731
  </tr>
1732
  <tr>
1733
  <td></td>
1734
  <td>
 
 
 
 
1735
  <p style="margin: 10px 0; padding: 10px; font-size: 140%; background-color: lightYellow; border-color: #E6DB55; border: 1px solid; border-radius: 4px;">
 
 
1736
  <?php
1737
+ echo $this->wordshell_random_advert(1);
1738
  ?>
1739
+ </p>
1740
  </td>
1741
  </tr>
1742
  <tr>
1818
  echo '</div>';
1819
  }
1820
 
1821
+ if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_all') { $this->backup(true,true); }
1822
  elseif (isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_db') { $this->backup_db(); }
1823
  elseif (isset($_POST['action']) && $_POST['action'] == 'updraft_wipesettings') {
1824
+ $settings = array('updraft_interval', 'updraft_interval_database', 'updraft_retain', 'updraft_retain_db', 'updraft_encryptionphrase', 'updraft_service', 'updraft_s3_login', 'updraft_s3_pass', 'updraft_s3_remote_path', 'updraft_dropbox_appkey', 'updraft_dropbox_secret', 'updraft_dropbox_folder', 'updraft_googledrive_clientid', 'updraft_googledrive_secret', 'updraft_googledrive_remotepath', 'updraft_ftp_login', 'updraft_ftp_pass', 'updraft_ftp_remote_path', 'updraft_server_address', 'updraft_dir', 'updraft_email', 'updraft_delete_local', 'updraft_debug_mode', 'updraft_include_plugins', 'updraft_include_themes', 'updraft_include_uploads', 'updraft_include_others', 'updraft_include_others_exclude', 'updraft_lastmessage');
1825
  foreach ($settings as $s) {
1826
  UpdraftPlus_Options::delete_updraft_option($s);
1827
  }
1832
  <div class="wrap">
1833
  <h1><?php echo $this->plugin_title; ?></h1>
1834
 
1835
+ Maintained by <b>David Anderson</b> (<a href="http://david.dw-perspective.org.uk">Homepage</a> | <a href="http://updraftplus.com">Premium</a> | <a href="http://wordshell.net">WordShell - WordPress command line</a> | <a href="http://david.dw-perspective.org.uk/donate">Donate</a> | <a href="http://wordpress.org/extend/plugins/updraftplus/faq/">FAQs</a> | <a href="http://profiles.wordpress.org/davidanderson/">My other WordPress plugins</a>). Version: <?php echo $this->version; ?>
1836
  <br>
1837
  <?php
1838
  if(isset($_GET['updraft_restore_success'])) {
1840
  }
1841
 
1842
  $ws_advert = $this->wordshell_random_advert(1);
1843
+ echo <<<ENDHERE
1844
+ <div class="updated fade" style="max-width: 800px; font-size:140%; line-height: 140%; padding:14px; clear:left;">${ws_advert}</div>
1845
+ ENDHERE;
1846
 
1847
  if($deleted_old_dirs) echo '<div style="color:blue">Old directories successfully deleted.</div>';
1848