UpdraftPlus WordPress Backup Plugin - Version 1.4.48

Version Description

  • 03/11/2013 =
  • Improve batching on zip creation for sites with very large files
  • Unlimited early resumption if zip file creation takes too long
  • Suppress some warning notices that can break JavaScript on sites with notices sent to the browser
  • Earlier warning/failure if backup directory was not writable
  • Hooks for Dropbox folders add-on
  • More scheduler/overlap tweaks, to assist enormous uploads
  • When the temporary directory is within the site, store+display relatively (removes need to modify upon site move)
  • Sort existing backups display by date
  • Use WordPress time for creation of filenames
  • Fix bug in 1.4.47 which caused problems on new site installs
Download this release

Release Info

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

Code changes from version 1.4.30 to 1.4.48

includes/Dropbox/API.php CHANGED
@@ -173,7 +173,7 @@ class Dropbox_API
173
  if (isset($response['body']->offset)) {
174
  $offset = $response['body']->offset;
175
  if ($callback) {
176
- call_user_func($callback, $offset, $uploadID);
177
  }
178
  }
179
 
173
  if (isset($response['body']->offset)) {
174
  $offset = $response['body']->offset;
175
  if ($callback) {
176
+ call_user_func($callback, $offset, $uploadID, $file);
177
  }
178
  }
179
 
includes/Dropbox/OAuth/Consumer/ConsumerAbstract.php CHANGED
@@ -43,7 +43,8 @@ abstract class Dropbox_ConsumerAbstract
43
  */
44
  protected function authenticate()
45
  {
46
- if ((!$this->storage->get('access_token'))) {
 
47
  try {
48
  $this->getAccessToken();
49
  } catch(Dropbox_Exception $e) {
43
  */
44
  protected function authenticate()
45
  {
46
+ $access_token = $this->storage->get('access_token');
47
+ if (empty($access_token) || !isset($access_token->oauth_token)) {
48
  try {
49
  $this->getAccessToken();
50
  } catch(Dropbox_Exception $e) {
includes/updraft-restorer.php CHANGED
@@ -43,6 +43,18 @@ class Updraft_Restorer extends WP_Upgrader {
43
  if ( !empty($upgrade_files) ) {
44
  foreach ( $upgrade_files as $filestruc ) {
45
  $file = $filestruc['name'];
 
 
 
 
 
 
 
 
 
 
 
 
46
  # Sanity check (should not be possible as these were excluded at backup time)
47
  if ($file != "plugins" && $file != "themes" && $file != "uploads" && $file != "upgrade") {
48
  # First, move the existing one, if necessary (may not be present)
43
  if ( !empty($upgrade_files) ) {
44
  foreach ( $upgrade_files as $filestruc ) {
45
  $file = $filestruc['name'];
46
+
47
+ // Correctly restore files in 'others' in no directory that were wrongly backed up in versions 1.4.0 - 1.4.48
48
+ if (preg_match('/^([\-_A-Za-z0-9]+\.php)$/', $file, $matches) && $wp_filesystem->exists($working_dir . "/$file/$file")) {
49
+ echo "Found file: $file/$file: presuming this is a backup with a known fault (backup made with versions 1.4.0 - 1.4.48); will rename to simply $file<br>";
50
+ $file = $matches[1];
51
+ $tmp_file = rand(0,999999999).'.php';
52
+ // Rename directory
53
+ $wp_filesystem->move($working_dir . "/$file", $working_dir . "/".$tmp_file, true);
54
+ $wp_filesystem->move($working_dir . "/$tmp_file/$file", $working_dir ."/".$file, true);
55
+ $wp_filesystem->rmdir($working_dir . "/$tmp_file", false);
56
+ }
57
+
58
  # Sanity check (should not be possible as these were excluded at backup time)
59
  if ($file != "plugins" && $file != "themes" && $file != "uploads" && $file != "upgrade") {
60
  # First, move the existing one, if necessary (may not be present)
methods/dropbox.php CHANGED
@@ -7,7 +7,7 @@ class UpdraftPlus_BackupModule_dropbox {
7
  private $current_file_hash;
8
  private $current_file_size;
9
 
10
- function chunked_callback($offset, $uploadid) {
11
  global $updraftplus;
12
 
13
  // Update upload ID
@@ -16,9 +16,11 @@ class UpdraftPlus_BackupModule_dropbox {
16
 
17
  if ($this->current_file_size > 0) {
18
  $percent = round(100*($offset/$this->current_file_size),1);
19
- $updraftplus->record_uploaded_chunk($percent, "$uploadid, $offset");
20
  } else {
21
  $updraftplus->log("Dropbox: Chunked Upload: $offset bytes uploaded");
 
 
22
  }
23
 
24
  }
@@ -34,6 +36,8 @@ class UpdraftPlus_BackupModule_dropbox {
34
  return false;
35
  }
36
 
 
 
37
  try {
38
  $dropbox = $this->bootstrap();
39
  $dropbox->setChunkSize(524288); // 512Kb
@@ -265,6 +269,11 @@ class UpdraftPlus_BackupModule_dropbox {
265
  if ( isset( $_GET['oauth_token'] ) ) {
266
  self::auth_token();
267
  } elseif (isset($_GET['updraftplus_dropboxauth'])) {
 
 
 
 
 
268
  self::auth_request();
269
  }
270
  }
7
  private $current_file_hash;
8
  private $current_file_size;
9
 
10
+ function chunked_callback($offset, $uploadid, $fullpath = false) {
11
  global $updraftplus;
12
 
13
  // Update upload ID
16
 
17
  if ($this->current_file_size > 0) {
18
  $percent = round(100*($offset/$this->current_file_size),1);
19
+ $updraftplus->record_uploaded_chunk($percent, "$uploadid, $offset", $fullpath);
20
  } else {
21
  $updraftplus->log("Dropbox: Chunked Upload: $offset bytes uploaded");
22
+ // This act is done by record_uploaded_chunk, and helps prevent overlapping runs
23
+ touch($fullpath);
24
  }
25
 
26
  }
36
  return false;
37
  }
38
 
39
+ $updraftplus->log("Dropbox: access gained");
40
+
41
  try {
42
  $dropbox = $this->bootstrap();
43
  $dropbox->setChunkSize(524288); // 512Kb
269
  if ( isset( $_GET['oauth_token'] ) ) {
270
  self::auth_token();
271
  } elseif (isset($_GET['updraftplus_dropboxauth'])) {
272
+ // Clear out the existing credentials
273
+ if ('doit' == $_GET['updraftplus_dropboxauth']) {
274
+ UpdraftPlus_Options::update_updraft_option("updraft_dropboxtk_request_token",'');
275
+ UpdraftPlus_Options::update_updraft_option("updraft_dropboxtk_access_token",'');
276
+ }
277
  self::auth_request();
278
  }
279
  }
methods/email.php CHANGED
@@ -8,8 +8,10 @@ class UpdraftPlus_BackupModule_email {
8
 
9
  global $updraftplus;
10
 
 
 
11
  foreach ($backup_array as $type => $file) {
12
- $fullpath = trailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_dir')).$file;
13
  wp_mail(UpdraftPlus_Options::get_updraft_option('updraft_email'), "WordPress Backup ".date('Y-m-d H:i',$updraftplus->backup_time), "Backup is of the $type. Be wary; email backups may fail because of file size limitations on mail servers.", null, array($fullpath));
14
  $updraftplus->uploaded_file($file);
15
  }
@@ -20,7 +22,7 @@ class UpdraftPlus_BackupModule_email {
20
  ?>
21
  <tr class="updraftplusmethod email">
22
  <th>Note:</th>
23
- <td>The email address entered above will be used. If choosing &quot;E-Mail&quot;, then be aware that mail servers tend to have size limits; typically around 10-20Mb; backups larger than any limits will not arrive. If you really need a large backup via email, then you could fund a new feature (to break the backup set into configurable-size pieces) - but the demand has not yet existed for such a feature.</td>
24
  </tr>
25
  <?php
26
  }
8
 
9
  global $updraftplus;
10
 
11
+ $updraft_dir = $updraftplus->backups_dir_location().'/';
12
+
13
  foreach ($backup_array as $type => $file) {
14
+ $fullpath = $updraft_dir.$file;
15
  wp_mail(UpdraftPlus_Options::get_updraft_option('updraft_email'), "WordPress Backup ".date('Y-m-d H:i',$updraftplus->backup_time), "Backup is of the $type. Be wary; email backups may fail because of file size limitations on mail servers.", null, array($fullpath));
16
  $updraftplus->uploaded_file($file);
17
  }
22
  ?>
23
  <tr class="updraftplusmethod email">
24
  <th>Note:</th>
25
+ <td>The email address entered above will be used. If choosing &quot;E-Mail&quot;, then <strong>be aware</strong> that mail servers tend to have size limits; typically around 10-20Mb; backups larger than any limits will not arrive. If you really need a large backup via email, then you could fund a new feature (to break the backup set into configurable-size pieces) - but the demand has not yet existed for such a feature.</td>
26
  </tr>
27
  <?php
28
  }
methods/ftp.php CHANGED
@@ -22,9 +22,11 @@ class UpdraftPlus_BackupModule_ftp {
22
 
23
  //$ftp->make_dir(); we may need to recursively create dirs? TODO
24
 
 
 
25
  $ftp_remote_path = trailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_ftp_remote_path'));
26
  foreach($backup_array as $file) {
27
- $fullpath = trailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_dir')).$file;
28
  $updraftplus->log("FTP upload attempt: $file -> ftp://$user@$server/${ftp_remote_path}${file}");
29
  $timer_start = microtime(true);
30
  $size_k = round(filesize($fullpath)/1024,1);
@@ -67,7 +69,7 @@ class UpdraftPlus_BackupModule_ftp {
67
  //$ftp->make_dir(); we may need to recursively create dirs? TODO
68
 
69
  $ftp_remote_path = trailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_ftp_remote_path'));
70
- $fullpath = trailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_dir')).$file;
71
 
72
  $ftp->get($fullpath, $ftp_remote_path.$file, FTP_BINARY);
73
  }
22
 
23
  //$ftp->make_dir(); we may need to recursively create dirs? TODO
24
 
25
+ $updraft_dir = $updraftplus->backups_dir_location().'/';
26
+
27
  $ftp_remote_path = trailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_ftp_remote_path'));
28
  foreach($backup_array as $file) {
29
+ $fullpath = $updraft_dir.$file;
30
  $updraftplus->log("FTP upload attempt: $file -> ftp://$user@$server/${ftp_remote_path}${file}");
31
  $timer_start = microtime(true);
32
  $size_k = round(filesize($fullpath)/1024,1);
69
  //$ftp->make_dir(); we may need to recursively create dirs? TODO
70
 
71
  $ftp_remote_path = trailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_ftp_remote_path'));
72
+ $fullpath = $updraftplus->backups_dir_location().'/'.$file;
73
 
74
  $ftp->get($fullpath, $ftp_remote_path.$file, FTP_BINARY);
75
  }
methods/googledrive.php CHANGED
@@ -119,8 +119,10 @@ class UpdraftPlus_BackupModule_googledrive {
119
 
120
  $this->gdocs_access_token = $access_token;
121
 
 
 
122
  foreach ($backup_array as $file) {
123
- $file_path = trailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_dir')).$file;
124
  $file_name = basename($file_path);
125
  $updraftplus->log("$file_name: Attempting to upload to Google Drive");
126
  $timer_start = microtime(true);
@@ -201,7 +203,7 @@ class UpdraftPlus_BackupModule_googledrive {
201
  $counter = 0;
202
  do {
203
  $log_string = ($counter == 0) ? "URL: $res" : "";
204
- $updraftplus->record_uploaded_chunk($d, $log_string);
205
 
206
  $counter++; if ($counter >= 20) $counter=0;
207
 
@@ -271,7 +273,7 @@ class UpdraftPlus_BackupModule_googledrive {
271
  }
272
  // Actually download the thing
273
 
274
- $download_to = trailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_dir')).$file;
275
  $gdocs_object->download_data($content_link, $download_to, true);
276
 
277
  if (filesize($download_to) > 0) {
119
 
120
  $this->gdocs_access_token = $access_token;
121
 
122
+ $updraft_dir = $updraftplus->backups_dir_location().'/';
123
+
124
  foreach ($backup_array as $file) {
125
+ $file_path = $updraft_dir.$file;
126
  $file_name = basename($file_path);
127
  $updraftplus->log("$file_name: Attempting to upload to Google Drive");
128
  $timer_start = microtime(true);
203
  $counter = 0;
204
  do {
205
  $log_string = ($counter == 0) ? "URL: $res" : "";
206
+ $updraftplus->record_uploaded_chunk($d, $log_string, $file);
207
 
208
  $counter++; if ($counter >= 20) $counter=0;
209
 
273
  }
274
  // Actually download the thing
275
 
276
+ $download_to = $updraftplus->backups_dir_location().'/'.$file;
277
  $gdocs_object->download_data($content_link, $download_to, true);
278
 
279
  if (filesize($download_to) > 0) {
methods/s3.php CHANGED
@@ -53,11 +53,13 @@ class UpdraftPlus_BackupModule_s3 {
53
  if (empty($region)) $region = $s3->getBucketLocation($bucket_name);
54
  $this->set_endpoint($s3, $region);
55
 
 
 
56
  foreach($backup_array as $key => $file) {
57
 
58
  // We upload in 5Mb chunks to allow more efficient resuming and hence uploading of larger files
59
  // N.B.: 5Mb is Amazon's minimum. So don't go lower or you'll break it.
60
- $fullpath = trailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_dir')).$file;
61
  $orig_file_size = filesize($fullpath);
62
  $chunks = floor($orig_file_size / 5242880);
63
  // There will be a remnant unless the file size was exactly on a 5Mb boundary
@@ -119,7 +121,7 @@ class UpdraftPlus_BackupModule_s3 {
119
  }
120
  $etag = $s3->uploadPart($bucket_name, $filepath, $uploadId, $fullpath, $i);
121
  if ($etag !== false && is_string($etag)) {
122
- $updraftplus->record_uploaded_chunk(round(100*$i/$chunks,1), "$i, $etag");
123
  array_push($etags, $etag);
124
  set_transient("upd_${hash}_e$i", $etag, UPDRAFT_TRANSTIME);
125
  $successes++;
@@ -205,7 +207,7 @@ class UpdraftPlus_BackupModule_s3 {
205
  $region = @$s3->getBucketLocation($bucket_name);
206
  if (!empty($region)) {
207
  $this->set_endpoint($s3, $region);
208
- $fullpath = trailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_dir')).$file;
209
  if (!$s3->getObject($bucket_name, $bucket_path.$file, $fullpath, true)) {
210
  $updraftplus->log("S3 Error: Failed to download $file. Check your permissions and credentials.");
211
  $updraftplus->error("S3 Error: Failed to download $file. Check your permissions and credentials.");
53
  if (empty($region)) $region = $s3->getBucketLocation($bucket_name);
54
  $this->set_endpoint($s3, $region);
55
 
56
+ $updraft_dir = $updraftplus->backups_dir_location().'/';
57
+
58
  foreach($backup_array as $key => $file) {
59
 
60
  // We upload in 5Mb chunks to allow more efficient resuming and hence uploading of larger files
61
  // N.B.: 5Mb is Amazon's minimum. So don't go lower or you'll break it.
62
+ $fullpath = $updraft_dir.$file;
63
  $orig_file_size = filesize($fullpath);
64
  $chunks = floor($orig_file_size / 5242880);
65
  // There will be a remnant unless the file size was exactly on a 5Mb boundary
121
  }
122
  $etag = $s3->uploadPart($bucket_name, $filepath, $uploadId, $fullpath, $i);
123
  if ($etag !== false && is_string($etag)) {
124
+ $updraftplus->record_uploaded_chunk(round(100*$i/$chunks,1), "$i, $etag", $fullpath);
125
  array_push($etags, $etag);
126
  set_transient("upd_${hash}_e$i", $etag, UPDRAFT_TRANSTIME);
127
  $successes++;
207
  $region = @$s3->getBucketLocation($bucket_name);
208
  if (!empty($region)) {
209
  $this->set_endpoint($s3, $region);
210
+ $fullpath = $updraftplus->backups_dir_location().'/'.$file;
211
  if (!$s3->getObject($bucket_name, $bucket_path.$file, $fullpath, true)) {
212
  $updraftplus->log("S3 Error: Failed to download $file. Check your permissions and credentials.");
213
  $updraftplus->error("S3 Error: Failed to download $file. Check your permissions and credentials.");
options.php CHANGED
@@ -60,7 +60,7 @@ class UpdraftPlus_Options {
60
  register_setting('updraft-options-group', 'updraft_ftp_pass' );
61
  register_setting('updraft-options-group', 'updraft_ftp_remote_path' );
62
  register_setting('updraft-options-group', 'updraft_server_address' );
63
- register_setting('updraft-options-group', 'updraft_dir' );
64
  register_setting('updraft-options-group', 'updraft_email');
65
  register_setting('updraft-options-group', 'updraft_delete_local', 'absint' );
66
  register_setting('updraft-options-group', 'updraft_debug_mode', 'absint' );
60
  register_setting('updraft-options-group', 'updraft_ftp_pass' );
61
  register_setting('updraft-options-group', 'updraft_ftp_remote_path' );
62
  register_setting('updraft-options-group', 'updraft_server_address' );
63
+ register_setting('updraft-options-group', 'updraft_dir', array($updraftplus, 'prune_updraft_dir_prefix') );
64
  register_setting('updraft-options-group', 'updraft_email');
65
  register_setting('updraft-options-group', 'updraft_delete_local', 'absint' );
66
  register_setting('updraft-options-group', 'updraft_debug_mode', 'absint' );
readme.txt CHANGED
@@ -3,13 +3,13 @@ Contributors: David Anderson
3
  Tags: backup, restore, database, cloud, amazon, s3, dropbox, google drive, ftp, webdav, back up, multisite
4
  Requires at least: 3.2
5
  Tested up to: 3.5.1
6
- Stable tag: 1.4.30
7
  Author URI: http://updraftplus.com
8
  Donate link: http://david.dw-perspective.org.uk/donate
9
  License: GPLv3 or later
10
 
11
  == Upgrade Notice ==
12
- Various tweaks and bug-fixes; plus, asynchronous downloading
13
 
14
  == Description ==
15
 
@@ -25,8 +25,13 @@ Various tweaks and bug-fixes; plus, asynchronous downloading
25
  * Database backups can be encrypted for security
26
  * Debug mode that gives full logging of the backup
27
  * Thousands of users: widely tested and reliable
 
28
  * Premium/multi-site version and support available - <a href="http://updraftplus.com">http://updraftplus.com</a>
29
 
 
 
 
 
30
  = Best New WordPress Plugin =
31
 
32
  That's according to WordPress big cheese, Vladimir Prelovac. Check out his weekly chart to see where UpdraftPlus is right now: http://www.prelovac.com/vladimir/wordpress-plugins-rising-stars
@@ -41,6 +46,10 @@ If you need WordPress multisite compatibility (you'll know if you do), <a href="
41
 
42
  UpdraftPlus is written by professional WordPress developers. If your site needs guaranteed support, then we are available. Just <a href="http://updraftplus.com/shop/">go to our shop.</a>
43
 
 
 
 
 
44
  = Other support =
45
 
46
  We hang out in the support forum for this plugin - http://wordpress.org/support/plugin/updraftplus - however, to save our time so that we can spend it on development, please read the plugin's Frequently Asked Questions - <a href="http://updraftplus.com/support/frequently-asked-questions/">http://updraftplus.com/support/frequently-asked-questions/</a> - before going there, and ensure that you have updated to the latest released version of UpdraftPlus.
@@ -101,6 +110,18 @@ Thanks for asking - yes, I have. Check out my profile page - http://profiles.wor
101
 
102
  == Changelog ==
103
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  = 1.4.30 - 03/04/2013 =
105
  * Hooks for WebDAV support via add-on
106
 
3
  Tags: backup, restore, database, cloud, amazon, s3, dropbox, google drive, ftp, webdav, back up, multisite
4
  Requires at least: 3.2
5
  Tested up to: 3.5.1
6
+ Stable tag: 1.4.48
7
  Author URI: http://updraftplus.com
8
  Donate link: http://david.dw-perspective.org.uk/donate
9
  License: GPLv3 or later
10
 
11
  == Upgrade Notice ==
12
+ Many tweaks + small bug-fixes: recommended upgraded for all
13
 
14
  == Description ==
15
 
25
  * Database backups can be encrypted for security
26
  * Debug mode that gives full logging of the backup
27
  * Thousands of users: widely tested and reliable
28
+ * Internationalised (translations very welcome - see below)
29
  * Premium/multi-site version and support available - <a href="http://updraftplus.com">http://updraftplus.com</a>
30
 
31
+ = Don't Risk Anything Less =
32
+
33
+ Your backups are worth the same as your entire investment in your website. The day may come when you get hacked, or your hosting company does, or they go bust - without good backups, you lose everything. Do you really want to entrust all your work to plugins with only a few thousand downloads, or that has no professional backing or support? Believe us - writing a reliable backup plugin that works consistently across the huge range of WordPress deployments is hard.
34
+
35
  = Best New WordPress Plugin =
36
 
37
  That's according to WordPress big cheese, Vladimir Prelovac. Check out his weekly chart to see where UpdraftPlus is right now: http://www.prelovac.com/vladimir/wordpress-plugins-rising-stars
46
 
47
  UpdraftPlus is written by professional WordPress developers. If your site needs guaranteed support, then we are available. Just <a href="http://updraftplus.com/shop/">go to our shop.</a>
48
 
49
+ = Are you multi-lingual? Can you translate? =
50
+
51
+ Are you able to translate UpdraftPlus into another language? UpdraftPlus's code is already internationalized. Just grab the .pot file from <a href="http://plugins.svn.wordpress.org/updraftplus/trunk/languages/updraftplus.pot">http://plugins.svn.wordpress.org/updraftplus/trunk/languages/updraftplus.pot</a>, load it into any application for translating, and sent it to us (contact@updraftplus.com) when done. We will add your name to the 'Notes' page here.
52
+
53
  = Other support =
54
 
55
  We hang out in the support forum for this plugin - http://wordpress.org/support/plugin/updraftplus - however, to save our time so that we can spend it on development, please read the plugin's Frequently Asked Questions - <a href="http://updraftplus.com/support/frequently-asked-questions/">http://updraftplus.com/support/frequently-asked-questions/</a> - before going there, and ensure that you have updated to the latest released version of UpdraftPlus.
110
 
111
  == Changelog ==
112
 
113
+ = 1.4.48 - 03/11/2013 =
114
+ * Improve batching on zip creation for sites with very large files
115
+ * Unlimited early resumption if zip file creation takes too long
116
+ * Suppress some warning notices that can break JavaScript on sites with notices sent to the browser
117
+ * Earlier warning/failure if backup directory was not writable
118
+ * Hooks for Dropbox folders add-on
119
+ * More scheduler/overlap tweaks, to assist enormous uploads
120
+ * When the temporary directory is within the site, store+display relatively (removes need to modify upon site move)
121
+ * Sort existing backups display by date
122
+ * Use WordPress time for creation of filenames
123
+ * Fix bug in 1.4.47 which caused problems on new site installs
124
+
125
  = 1.4.30 - 03/04/2013 =
126
  * Hooks for WebDAV support via add-on
127
 
updraftplus.php CHANGED
@@ -3,15 +3,17 @@
3
  Plugin Name: UpdraftPlus - Backup/Restore
4
  Plugin URI: http://updraftplus.com
5
  Description: Backup and restore: your site can be backed up locally or to Amazon S3, Dropbox, Google Drive, (S)FTP, WebDAV & email, on automatic schedules.
6
- Author: David Anderson
7
- Version: 1.4.30
8
  Donate link: http://david.dw-perspective.org.uk/donate
9
  License: GPLv3 or later
10
  Author URI: http://wordshell.net
11
  */
12
 
13
  /*
14
- TODO - some are out of date/done, needs pruning
 
 
15
  //When a manual backup is run, use a timer to update the 'Download backups and logs' section, just like 'Last finished backup run'. Beware of over-writing anything that's in there from a resumable downloader.
16
  //Change DB encryption to not require whole gzip in memory (twice)
17
  //Add Rackspace, Box.Net, SugarSync and Microsoft Skydrive support??
@@ -25,12 +27,10 @@ TODO - some are out of date/done, needs pruning
25
  // Resuming partial (S)FTP uploads
26
  // Translations
27
  // Make disk space check more intelligent (currently hard-coded at 35Mb)
28
- // Specific folders on DropBox
29
  // 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
30
  // Multiple jobs
31
  // Multisite - a separate 'blogs' zip
32
  // Allow connecting to remote storage, scanning + populating backup history from it
33
- // Change FTP to use SSL by default
34
  // GoogleDrive in-dashboard download resumption loads the whole archive into memory - should instead either chunk or directly stream fo the file handle
35
  // Multisite add-on should allow restoring of each blog individually
36
  // When looking for files to delete, is the current encryption setting used? Should not be.
@@ -88,7 +88,7 @@ if ($dir_handle = opendir(UPDRAFTPLUS_DIR.'/addons')) {
88
  if (!isset($updraftplus)) $updraftplus = new UpdraftPlus();
89
 
90
  if (!$updraftplus->memory_check(192)) {
91
- # TODO: Better solution is to split the backup set into manageable chunks based on this limit
92
  @ini_set('memory_limit', '192M'); //up the memory limit for large backup files
93
  }
94
 
@@ -146,7 +146,7 @@ class UpdraftPlus {
146
  if (preg_match("/Version: ([\d\.]+)(\r|\n)/", $file_data, $matches)) {
147
  $this->version = $matches[1];
148
  }
149
- fclose( $fp );
150
  }
151
 
152
  # Create admin page
@@ -164,7 +164,7 @@ class UpdraftPlus {
164
  add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
165
  add_action('init', array($this, 'handle_url_actions'));
166
 
167
- if (defined('UPDRAFTPLUS_PREFERPCLZIP') && UPDRAFTPLUS_PREFERPCLZIP === true) { $this->zip_preferpcl = true; }
168
 
169
  }
170
 
@@ -213,8 +213,8 @@ class UpdraftPlus {
213
  if ($file == plugin_basename(__FILE__)){
214
  $settings_link = '<a href="'.site_url().'/wp-admin/options-general.php?page=updraftplus">'.__("Settings", "UpdraftPlus").'</a>';
215
  array_unshift($links, $settings_link);
216
- $settings_link = '<a href="http://david.dw-perspective.org.uk/donate">'.__("Donate","UpdraftPlus").'</a>';
217
- array_unshift($links, $settings_link);
218
  $settings_link = '<a href="http://updraftplus.com">'.__("Add-Ons / Pro Support","UpdraftPlus").'</a>';
219
  array_unshift($links, $settings_link);
220
  }
@@ -228,15 +228,18 @@ class UpdraftPlus {
228
  }
229
 
230
  function logfile_open($nonce) {
 
231
  //set log file name and open log file
232
  $updraft_dir = $this->backups_dir_location();
233
  $this->logfile_name = $updraft_dir. "/log.$nonce.txt";
 
234
  // Use append mode in case it already exists
235
  $this->logfile_handle = fopen($this->logfile_name, 'a');
236
  $this->opened_log_time = microtime(true);
237
  $this->log("Opened log file at time: ".date('r'));
238
  global $wp_version;
239
- $logline = "UpdraftPlus: ".$this->version." WordPress: ".$wp_version." PHP: ".phpversion()." (".php_uname().") PHP Max Execution Time: ".@ini_get("max_execution_time")." ZipArchive::addFile exists: ";
 
240
  // method_exists causes some faulty PHP installations to segfault, leading to support requests
241
  if (version_compare(phpversion(), '5.2.0', '>=') && extension_loaded('zip')) {
242
  $logline .= 'Y';
@@ -250,7 +253,7 @@ class UpdraftPlus {
250
 
251
  # Logs the given line, adding (relative) time stamp and newline
252
  function log($line) {
253
- if ($this->logfile_handle) fwrite($this->logfile_handle, sprintf("%08.03f", round(microtime(true)-$this->opened_log_time, 3))." ".$line."\n");
254
  if ('download' == $this->jobdata_get('job_type')) {
255
  // Download messages are keyed on the job (since they could be running several), and transient
256
  // The values of the POST array were checked before
@@ -258,11 +261,15 @@ class UpdraftPlus {
258
  } else {
259
  UpdraftPlus_Options::update_updraft_option('updraft_lastmessage', $line." (".date('M d H:i:s').")");
260
  }
261
- if (isset($_SERVER['KONSOLE_DBUS_SESSION'])) print $line."\n";
262
  }
263
 
264
  // This function is used by cloud methods to provide standardised logging, but more importantly to help us detect that meaningful activity took place during a resumption run, so that we can schedule further resumptions if it is worthwhile
265
- function record_uploaded_chunk($percent, $extra) {
 
 
 
 
266
  // Log it
267
  $service = $this->jobdata_get('service');
268
  $log = ucfirst($service)." chunked upload: $percent % uploaded";
@@ -274,19 +281,17 @@ class UpdraftPlus {
274
  // If they get 2 minutes on each run, and the file is 1Gb, then that equals 10.2Mb/120s = minimum 87Kb/s upload speed required
275
 
276
  if ($this->current_resumption >= 9 && $this->newresumption_scheduled == false && $percent > ( $this->current_resumption - 9)) {
277
- $resume_interval = $this->jobdata_get('resume_interval');
278
- if (!is_numeric($resume_interval) || $resume_interval<$this->minimum_resume_interval()) { $resume_interval = $this->minimum_resume_interval(); }
279
- $schedule_for = time()+$resume_interval;
280
- $this->newresumption_scheduled = $schedule_for;
281
- $this->log("This is resumption ".$this->current_resumption.", but meaningful uploading is still taking place; so a new one will be scheduled");
282
- wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($this->current_resumption + 1, $this->nonce));
283
  }
284
  }
285
 
286
  function minimum_resume_interval() {
287
- $inter = ini_get('max_execution_time');
288
- if (!$inter || $inter>300) $inter = 300;
289
- return $inter;
 
 
 
290
  }
291
 
292
  function backup_resume($resumption_no, $bnonce) {
@@ -307,8 +312,10 @@ class UpdraftPlus {
307
 
308
  $updraft_dir = $this->backups_dir_location();
309
 
310
- $this->log("Backup run: resumption=$resumption_no, nonce=$bnonce, begun at=$btime, job type: $job_type");
 
311
  $this->current_resumption = $resumption_no;
 
312
 
313
  // Schedule again, to run in 5 minutes again, in case we again fail
314
  // The actual interval can be increased (for future resumptions) by other code, if it detects apparent overlapping
@@ -318,14 +325,20 @@ class UpdraftPlus {
318
  // A different argument than before is needed otherwise the event is ignored
319
  $next_resumption = $resumption_no+1;
320
  if ($next_resumption < 10) {
321
- $this->log("Scheduling a resumption ($next_resumption) after $resume_interval seconds in case this run gets aborted");
322
  $schedule_for = time()+$resume_interval;
 
323
  wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($next_resumption, $bnonce));
324
  $this->newresumption_scheduled = $schedule_for;
325
  } else {
326
  $this->log("The current run is our tenth attempt - will not schedule a further attempt until we see something useful happening");
327
  }
328
 
 
 
 
 
 
 
329
  // This should be always called; if there were no files in this run, it returns us an empty array
330
  $backup_array = $this->resumable_backup_of_files($resumption_no);
331
 
@@ -444,14 +457,15 @@ class UpdraftPlus {
444
  $value = func_get_arg($i*2-1);
445
  $this->jobdata[$key] = $value;
446
  }
447
- if ($this->nonce) set_transient("updraft_jobdata_".$this->nonce, $this->jobdata, UPDRAFT_TRANSTIME);
448
  }
449
 
450
  function jobdata_set($key, $value) {
451
  if (is_array($this->jobdata)) {
452
  $this->jobdata[$key] = $value;
453
  } else {
454
- $this->jobdata = array($key => $value);
 
455
  }
456
  set_transient("updraft_jobdata_".$this->nonce, $this->jobdata, 14400);
457
  }
@@ -498,6 +512,12 @@ class UpdraftPlus {
498
  $this->backup_time_nonce();
499
  $this->logfile_open($this->nonce);
500
 
 
 
 
 
 
 
501
  // Some house-cleaning
502
  $this->clean_temporary_files();
503
 
@@ -519,8 +539,8 @@ class UpdraftPlus {
519
  }
520
 
521
  $resume_interval = $this->minimum_resume_interval();
522
- $max_execution_time = ini_get('max_execution_time');
523
- if ($max_execution_time >0 && $max_execution_time<300 && $resume_interval< $max_execution_time + 30) $resume_interval = $max_execution_time + 30;
524
 
525
  $initial_jobdata = array(
526
  'resume_interval', $resume_interval,
@@ -536,7 +556,7 @@ class UpdraftPlus {
536
  // Use of jobdata_set_multi saves around 200ms
537
  call_user_func_array(array($this, 'jobdata_set_multi'), $initial_jobdata);
538
 
539
- // Everthing is now set up; now go
540
  $this->backup_resume(0, $this->nonce);
541
 
542
  }
@@ -817,22 +837,40 @@ class UpdraftPlus {
817
  return true;
818
  }
819
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
820
  function reschedule($how_far_ahead) {
821
  // Reschedule - remove presently scheduled event
822
- wp_clear_scheduled_hook('updraft_backup_resume', array($this->current_resumption + 1, $this->nonce));
 
823
  // Add new event
824
  if ($how_far_ahead < $this->minimum_resume_interval()) $how_far_ahead=$this->minimum_resume_interval();
825
  $schedule_for = time() + $how_far_ahead;
826
- wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($this->current_resumption + 1, $this->nonce));
 
827
  $this->newresumption_scheduled = $schedule_for;
828
  }
829
 
830
  function increase_resume_and_reschedule($howmuch = 120) {
831
  $resume_interval = $this->jobdata_get('resume_interval');
832
- if (!is_numeric($resume_interval) || $resume_interval<$this->minimum_resume_interval()) { $resume_interval = $this->minimum_resume_interval(); }
833
- if ($this->newresumption_scheduled != false) $this->reschedule($resume_interval+$howmuch);
834
  $this->jobdata_set('resume_interval', $resume_interval+$howmuch);
835
- $this->log("To decrease the likelihood of overlaps, increasing resumption interval to: ".($resume_interval+$howmuch));
836
  }
837
 
838
  function create_zip($create_from_dir, $whichone, $create_in_dir, $backup_file_basename) {
@@ -842,9 +880,16 @@ class UpdraftPlus {
842
  if ($whichone != "others") $this->log("Beginning creation of dump of $whichone");
843
 
844
  $full_path = $create_in_dir.'/'.$backup_file_basename.'-'.$whichone.'.zip';
 
845
 
846
  if (file_exists($full_path)) {
847
  $this->log("$backup_file_basename-$whichone.zip: this file has already been created");
 
 
 
 
 
 
848
  return basename($full_path);
849
  }
850
 
@@ -852,7 +897,6 @@ class UpdraftPlus {
852
 
853
  // Firstly, make sure that the temporary file is not already being written to - which can happen if a resumption takes place whilst an old run is still active
854
  $zip_name = $full_path.'.tmp';
855
- $time_now = time();
856
  $time_mod = (int)@filemtime($zip_name);
857
  if (file_exists($zip_name) && $time_mod>100 && ($time_now-$time_mod)<30) {
858
  $file_size = filesize($zip_name);
@@ -881,6 +925,19 @@ class UpdraftPlus {
881
  return basename($full_path);
882
  }
883
 
 
 
 
 
 
 
 
 
 
 
 
 
 
884
  // This function is resumable
885
  function backup_dirs($transient_status) {
886
 
@@ -898,7 +955,7 @@ class UpdraftPlus {
898
  $blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name);
899
  if(!$blog_name) $blog_name = 'non_alpha_name';
900
 
901
- $backup_file_basename = 'backup_'.date('Y-m-d-Hi', $this->backup_time).'_'.$blog_name.'_'.$this->nonce;
902
 
903
  $backup_array = array();
904
 
@@ -911,10 +968,16 @@ class UpdraftPlus {
911
 
912
  # Plugins, themes, uploads
913
  foreach ($possible_backups as $youwhat => $whichdir) {
 
914
  if (UpdraftPlus_Options::get_updraft_option("updraft_include_$youwhat", true)) {
 
 
 
 
 
915
  if ($transient_status == 'finished') {
916
  $backup_array[$youwhat] = $backup_file_basename.'-'.$youwhat.'.zip';
917
- if (file_exists($updraft_dir.'/'.$backup_file_basename.'-'.$youwhat.'.zip')) $backup_array[$youwhat.'-size'] = filesize($updraft_dir.'/'.$backup_file_basename.'-'.$youwhat.'.zip');
918
  } else {
919
  $created = $this->create_zip($whichdir, $youwhat, $updraft_dir, $backup_file_basename);
920
  if ($created) {
@@ -930,9 +993,12 @@ class UpdraftPlus {
930
  # Others
931
  if (UpdraftPlus_Options::get_updraft_option('updraft_include_others', true)) {
932
 
 
 
 
933
  if ($transient_status == 'finished') {
934
  $backup_array['others'] = $backup_file_basename.'-others.zip';
935
- if (file_exists($updraft_dir.'/'.$backup_file_basename.'-others.zip')) $backup_array['others-size'] = filesize($updraft_dir.'/'.$backup_file_basename.'-others.zip');
936
  } else {
937
  $this->log("Beginning backup of other directories found in the content directory");
938
 
@@ -1065,7 +1131,8 @@ class UpdraftPlus {
1065
  // Get the blog name and rip out all non-alphanumeric chars other than _
1066
  $blog_name = preg_replace('/[^A-Za-z0-9_]/','', str_replace(' ','_', substr(get_bloginfo(), 0, 96)));
1067
  if (!$blog_name) $blog_name = 'non_alpha_name';
1068
- $file_base = 'backup_'.date('Y-m-d-Hi',$this->backup_time).'_'.$blog_name.'_'.$this->nonce;
 
1069
  $backup_file_base = $updraft_dir.'/'.$file_base;
1070
 
1071
  if ('finished' == $already_done) return basename($backup_file_base.'-db.gz');
@@ -1406,19 +1473,40 @@ class UpdraftPlus {
1406
  return $schedules;
1407
  }
1408
 
 
 
 
 
 
 
 
 
 
 
1409
  function backups_dir_location() {
 
1410
  if (!empty($this->backup_dir)) return $this->backup_dir;
 
1411
  $updraft_dir = untrailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_dir'));
1412
  $default_backup_dir = WP_CONTENT_DIR.'/updraft';
1413
- //if the option isn't set, default it to /backups inside the upload dir
1414
  $updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
 
 
 
 
 
 
 
1415
  //check for the existence of the dir and an enumeration preventer.
1416
- if(!is_dir($updraft_dir) || !is_file($updraft_dir.'/index.html') || !is_file($updraft_dir.'/.htaccess')) {
 
1417
  @mkdir($updraft_dir, 0775, true);
1418
- @file_put_contents($updraft_dir.'/index.html','Nothing to see here.');
1419
- @file_put_contents($updraft_dir.'/.htaccess','deny from all');
1420
  }
 
1421
  $this->backup_dir = $updraft_dir;
 
1422
  return $updraft_dir;
1423
  }
1424
 
@@ -1834,7 +1922,7 @@ class UpdraftPlus {
1834
  return $this->url_start($urls,'wordshell.net')."Check out WordShell".$this->url_end($urls,'www.wordshell.net')." - manage WordPress from the command line - huge time-saver";
1835
  break;
1836
  case 3:
1837
- return "Want some more useful plugins? ".$this->url_start($urls,'profiles.wordpress.org/DavidAnderson/')."See my WordPress profile page for others.".$this->url_end($urls,'profiles.wordpress.org/DavidAnderson/');
1838
  break;
1839
  case 4:
1840
  return $this->url_start($urls,'www.simbahosting.co.uk')."Need high-quality WordPress hosting from WordPress specialists? (Including automatic backups and 1-click installer). Get it from the creators of UpdraftPlus.".$this->url_end($urls,'www.simbahosting.co.uk');
@@ -2027,7 +2115,7 @@ class UpdraftPlus {
2027
  jQuery('.expertmode').fadeIn();
2028
  return false;
2029
  });
2030
- <?php if (!is_writable($updraft_dir)) echo "jQuery('.backupdirrow').show();\n"; ?>
2031
  setTimeout(function(){updraft_showlastlog();}, 1200);
2032
  jQuery('.updraftplusmethod').hide();
2033
  <?php
@@ -2064,15 +2152,16 @@ class UpdraftPlus {
2064
 
2065
  <tr class="expertmode backupdirrow" style="display:none;">
2066
  <th>Backup directory:</th>
2067
- <td><input type="text" name="updraft_dir" style="width:525px" value="<?php echo htmlspecialchars($updraft_dir); ?>" /></td>
2068
  </tr>
2069
  <tr class="expertmode backupdirrow" style="display:none;">
2070
  <td></td><td><?php
2071
 
2072
- if(is_writable($updraft_dir)) {
 
2073
  $dir_info = '<span style="color:green">Backup directory specified is writable, which is good.</span>';
2074
  } else {
2075
- $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>';
2076
  }
2077
 
2078
  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>
@@ -2206,7 +2295,7 @@ class UpdraftPlus {
2206
  if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_all') { $this->boot_backup(true,true); }
2207
  elseif (isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_db') { $this->backup_db(); }
2208
  elseif (isset($_POST['action']) && $_POST['action'] == 'updraft_wipesettings') {
2209
- $settings = array('updraft_interval', 'updraft_interval_database', 'updraft_retain', 'updraft_retain_db', 'updraft_encryptionphrase', 'updraft_service', '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_dropboxtk_request_token', 'updraft_dropboxtk_access_token', 'updraft_dropbox_folder', 'updraft_last_backup', 'updraft_starttime_files', 'updraft_starttime_db', 'updraft_sftp_settings');
2210
  foreach ($settings as $s) {
2211
  UpdraftPlus_Options::delete_updraft_option($s);
2212
  }
@@ -2233,8 +2322,8 @@ class UpdraftPlus {
2233
  <div style="color:orange">Your PHP memory limit is quite low. UpdraftPlus attempted to raise it but was unsuccessful. This plugin may not work properly with a memory limit of less than 96 Mb (though on the other hand, it has been used successfully with a 32Mb limit - your mileage may vary, but don't blame us!). Current limit is: <?php echo $this->memory_check_current(); ?> Mb</div>
2234
  <?php
2235
  }
2236
- if(!$this->execution_time_check(60)) {?>
2237
- <div style="color:orange">Your PHP max_execution_time is less than 60 seconds. This possibly means you're running in safe_mode. Either disable safe_mode or modify your php.ini to set max_execution_time to a higher number. If you do not, then longer will be needed to complete a backup. Present limit is: <?php echo ini_get('max_execution_time'); ?> seconds.</div>
2238
  <?php
2239
  }
2240
 
@@ -2356,7 +2445,7 @@ class UpdraftPlus {
2356
  </tr>
2357
  <tr>
2358
  <td></td><td class="download-backups" style="display:none">
2359
- <p><em><strong>Note</strong> - Pressing a button will make UpdraftPlus try to bring a backup file back from the remote storage (if any - e.g. Amazon S3, Dropbox, Google Drive, FTP) to your webserver, before then allowing you to download it to your computer. If the fetch from the remote storage stops progressing (wait 30 seconds to make sure), then click again to resume from where it left off. Remember that you can always visit the cloud storage website vendor's website directly.</em></p>
2360
  <div id="ud_downloadstatus"></div>
2361
  <script>
2362
  var lastlog_lastmessage = "";
@@ -2498,6 +2587,9 @@ class UpdraftPlus {
2498
  $updraft_dir = $this->backups_dir_location();
2499
 
2500
  echo '<table>';
 
 
 
2501
  foreach($backup_history as $key=>$value) {
2502
  ?>
2503
  <tr>
@@ -2604,7 +2696,7 @@ class UpdraftPlus {
2604
  // - No zip extension present and no relevant method present
2605
  // The zip extension check is not redundant, because method_exists segfaults some PHP installs, leading to support requests
2606
 
2607
- // Fallback to PclZip - which my tests show is 25% slower
2608
  if ($this->zip_preferpcl || (!extension_loaded('zip') && !method_exists('ZipArchive', 'AddFile'))) {
2609
  if(!class_exists('PclZip')) require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
2610
  $zip_object = new PclZip($destination);
@@ -2639,7 +2731,8 @@ class UpdraftPlus {
2639
  $this->zipfiles_dirbatched = array();
2640
  $this->zipfiles_batched = array();
2641
 
2642
- $last_error = -1;
 
2643
  if (is_array($source)) {
2644
  foreach ($source as $element) {
2645
  $howmany = $this->makezip_recursive_add($destination, $element, basename($element), $element);
@@ -2662,8 +2755,8 @@ class UpdraftPlus {
2662
  }
2663
  }
2664
 
2665
- if ($this->zipfiles_added > 0) {
2666
- // ZipArchive::addFile sometimes fails
2667
  if (filesize($destination) < 100) {
2668
  // Retry with PclZip
2669
  $this->log("Zip::addFile apparently failed - retrying with PclZip");
@@ -2682,27 +2775,75 @@ class UpdraftPlus {
2682
 
2683
  // We batch up the files, rather than do them one at a time. So we are more efficient than open,one-write,close.
2684
  function makezip_addfiles($zipfile) {
 
 
 
 
 
 
 
 
2685
  $zip = new ZipArchive();
2686
  if (file_exists($zipfile)) {
2687
  $opencode = $zip->open($zipfile);
 
2688
  } else {
2689
  $opencode = $zip->open($zipfile, ZIPARCHIVE::CREATE);
 
2690
  }
 
2691
  if ($opencode !== true) return array($opencode, 0);
2692
  // Make sure all directories are created before we start creating files
2693
  while ($dir = array_pop($this->zipfiles_dirbatched)) {
2694
  $zip->addEmptyDir($dir);
2695
  }
2696
  foreach ($this->zipfiles_batched as $file => $add_as) {
2697
- if (!isset($this->existing_files[$add_as]) || $this->existing_files[$add_as] != filesize($file)) {
 
 
 
2698
  $zip->addFile($file, $add_as);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2699
  }
2700
  $this->zipfiles_added++;
2701
- if ($this->zipfiles_added % 100 == 0) $this->log("Zip: ".basename($zipfile).": ".$this->zipfiles_added." files added (size: ".round(filesize($zipfile)/1024,1)." Kb)");
2702
  }
2703
  // Reset the array
2704
  $this->zipfiles_batched = array();
2705
- return $zip->close();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2706
  }
2707
 
2708
  // This function recursively packs the zip, dereferencing symlinks but packing into a single-parent tree for universal unpacking
@@ -2720,8 +2861,8 @@ class UpdraftPlus {
2720
 
2721
  if(is_file($fullpath)) {
2722
  if (is_readable($fullpath)) {
2723
- $key = $use_path_when_storing.'/'.basename($fullpath);
2724
- $this->zipfiles_batched[$fullpath] = $use_path_when_storing.'/'.basename($fullpath);
2725
  @touch($zipfile);
2726
  } else {
2727
  $this->log("$fullpath: unreadable file");
3
  Plugin Name: UpdraftPlus - Backup/Restore
4
  Plugin URI: http://updraftplus.com
5
  Description: Backup and restore: your site can be backed up locally or to Amazon S3, Dropbox, Google Drive, (S)FTP, WebDAV & email, on automatic schedules.
6
+ Author: UpdraftPlus.Com, DavidAnderson
7
+ Version: 1.4.48
8
  Donate link: http://david.dw-perspective.org.uk/donate
9
  License: GPLv3 or later
10
  Author URI: http://wordshell.net
11
  */
12
 
13
  /*
14
+ TODO - some of these are out of date/done, needs pruning
15
+ //Allow use of /usr/bin/zip - since this can escape from PHP's memory limit. Can still batch as we do so, in order to monitor/measure progress
16
+ //Do an automated test periodically for the success of loop-back connections
17
  //When a manual backup is run, use a timer to update the 'Download backups and logs' section, just like 'Last finished backup run'. Beware of over-writing anything that's in there from a resumable downloader.
18
  //Change DB encryption to not require whole gzip in memory (twice)
19
  //Add Rackspace, Box.Net, SugarSync and Microsoft Skydrive support??
27
  // Resuming partial (S)FTP uploads
28
  // Translations
29
  // Make disk space check more intelligent (currently hard-coded at 35Mb)
 
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
  // Multisite - a separate 'blogs' zip
33
  // Allow connecting to remote storage, scanning + populating backup history from it
 
34
  // GoogleDrive in-dashboard download resumption loads the whole archive into memory - should instead either chunk or directly stream fo the file handle
35
  // Multisite add-on should allow restoring of each blog individually
36
  // When looking for files to delete, is the current encryption setting used? Should not be.
88
  if (!isset($updraftplus)) $updraftplus = new UpdraftPlus();
89
 
90
  if (!$updraftplus->memory_check(192)) {
91
+ // Experience appears to show that the memory limit is only likely to be hit (unless it is very low) by single files that are larger than available memory (when compressed)
92
  @ini_set('memory_limit', '192M'); //up the memory limit for large backup files
93
  }
94
 
146
  if (preg_match("/Version: ([\d\.]+)(\r|\n)/", $file_data, $matches)) {
147
  $this->version = $matches[1];
148
  }
149
+ fclose($fp);
150
  }
151
 
152
  # Create admin page
164
  add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
165
  add_action('init', array($this, 'handle_url_actions'));
166
 
167
+ if (defined('UPDRAFTPLUS_PREFERPCLZIP') && UPDRAFTPLUS_PREFERPCLZIP == true) { $this->zip_preferpcl = true; }
168
 
169
  }
170
 
213
  if ($file == plugin_basename(__FILE__)){
214
  $settings_link = '<a href="'.site_url().'/wp-admin/options-general.php?page=updraftplus">'.__("Settings", "UpdraftPlus").'</a>';
215
  array_unshift($links, $settings_link);
216
+ // $settings_link = '<a href="http://david.dw-perspective.org.uk/donate">'.__("Donate","UpdraftPlus").'</a>';
217
+ // array_unshift($links, $settings_link);
218
  $settings_link = '<a href="http://updraftplus.com">'.__("Add-Ons / Pro Support","UpdraftPlus").'</a>';
219
  array_unshift($links, $settings_link);
220
  }
228
  }
229
 
230
  function logfile_open($nonce) {
231
+
232
  //set log file name and open log file
233
  $updraft_dir = $this->backups_dir_location();
234
  $this->logfile_name = $updraft_dir. "/log.$nonce.txt";
235
+
236
  // Use append mode in case it already exists
237
  $this->logfile_handle = fopen($this->logfile_name, 'a');
238
  $this->opened_log_time = microtime(true);
239
  $this->log("Opened log file at time: ".date('r'));
240
  global $wp_version;
241
+ $logline = "UpdraftPlus: ".$this->version." WP: ".$wp_version." PHP: ".phpversion()." (".php_uname().") max_execution_time: ".@ini_get("max_execution_time")." memory_limit: ".ini_get('memory_limit')." ZipArchive::addFile : ";
242
+
243
  // method_exists causes some faulty PHP installations to segfault, leading to support requests
244
  if (version_compare(phpversion(), '5.2.0', '>=') && extension_loaded('zip')) {
245
  $logline .= 'Y';
253
 
254
  # Logs the given line, adding (relative) time stamp and newline
255
  function log($line) {
256
+ if ($this->logfile_handle) fwrite($this->logfile_handle, sprintf("%08.03f", round(microtime(true)-$this->opened_log_time, 3))." (".$this->current_resumption.") $line\n");
257
  if ('download' == $this->jobdata_get('job_type')) {
258
  // Download messages are keyed on the job (since they could be running several), and transient
259
  // The values of the POST array were checked before
261
  } else {
262
  UpdraftPlus_Options::update_updraft_option('updraft_lastmessage', $line." (".date('M d H:i:s').")");
263
  }
264
+ if (defined('UPDRAFTPLUS_CONSOLELOG')) print $line."\n";
265
  }
266
 
267
  // This function is used by cloud methods to provide standardised logging, but more importantly to help us detect that meaningful activity took place during a resumption run, so that we can schedule further resumptions if it is worthwhile
268
+ function record_uploaded_chunk($percent, $extra, $file_path = false) {
269
+
270
+ // Touch the original file, which helps prevent overlapping runs
271
+ if ($file_path) touch($file_path);
272
+
273
  // Log it
274
  $service = $this->jobdata_get('service');
275
  $log = ucfirst($service)." chunked upload: $percent % uploaded";
281
  // If they get 2 minutes on each run, and the file is 1Gb, then that equals 10.2Mb/120s = minimum 87Kb/s upload speed required
282
 
283
  if ($this->current_resumption >= 9 && $this->newresumption_scheduled == false && $percent > ( $this->current_resumption - 9)) {
284
+ $this->something_useful_happened();
 
 
 
 
 
285
  }
286
  }
287
 
288
  function minimum_resume_interval() {
289
+ // Bringing this down brings in more risk of undetectable overlaps than is worth it
290
+ return 300;
291
+ // $inter = (int)ini_get('max_execution_time');
292
+ // if (!$inter || $inter>300) $inter = 300;
293
+ // if ($inter<35) $inter=35;
294
+ // return $inter;
295
  }
296
 
297
  function backup_resume($resumption_no, $bnonce) {
312
 
313
  $updraft_dir = $this->backups_dir_location();
314
 
315
+ $time_ago = time()-$btime;
316
+
317
  $this->current_resumption = $resumption_no;
318
+ $this->log("Backup run: resumption=$resumption_no, nonce=$bnonce, begun at=$btime (${time_ago}s ago), job type: $job_type");
319
 
320
  // Schedule again, to run in 5 minutes again, in case we again fail
321
  // The actual interval can be increased (for future resumptions) by other code, if it detects apparent overlapping
325
  // A different argument than before is needed otherwise the event is ignored
326
  $next_resumption = $resumption_no+1;
327
  if ($next_resumption < 10) {
 
328
  $schedule_for = time()+$resume_interval;
329
+ $this->log("Scheduling a resumption ($next_resumption) after $resume_interval seconds ($schedule_for) in case this run gets aborted");
330
  wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($next_resumption, $bnonce));
331
  $this->newresumption_scheduled = $schedule_for;
332
  } else {
333
  $this->log("The current run is our tenth attempt - will not schedule a further attempt until we see something useful happening");
334
  }
335
 
336
+ // Sanity check
337
+ if (empty($this->backup_time)) {
338
+ $this->log('Abort this run: the backup_time parameter appears to be empty');
339
+ return false;
340
+ }
341
+
342
  // This should be always called; if there were no files in this run, it returns us an empty array
343
  $backup_array = $this->resumable_backup_of_files($resumption_no);
344
 
457
  $value = func_get_arg($i*2-1);
458
  $this->jobdata[$key] = $value;
459
  }
460
+ if (!empty($this->nonce)) set_transient("updraft_jobdata_".$this->nonce, $this->jobdata, UPDRAFT_TRANSTIME);
461
  }
462
 
463
  function jobdata_set($key, $value) {
464
  if (is_array($this->jobdata)) {
465
  $this->jobdata[$key] = $value;
466
  } else {
467
+ $this->jobdata = get_transient("updraft_jobdata_".$this->nonce);
468
+ if (!is_array($this->jobdata)) $this->jobdata = array($key => $value);
469
  }
470
  set_transient("updraft_jobdata_".$this->nonce, $this->jobdata, 14400);
471
  }
512
  $this->backup_time_nonce();
513
  $this->logfile_open($this->nonce);
514
 
515
+ if (!is_file($this->logfile_name)) {
516
+ $this->log('Failed to open log file ('.$this->logfile_name.') - you need to check your UpdraftPlus settings (your chosen directory for creating files in is not writable, or you ran out of disk space). Backup aborted.');
517
+ $this->error('Could not create files in the backup directory. Backup aborted - check your UpdraftPlus settings.');
518
+ return false;
519
+ }
520
+
521
  // Some house-cleaning
522
  $this->clean_temporary_files();
523
 
539
  }
540
 
541
  $resume_interval = $this->minimum_resume_interval();
542
+ // $max_execution_time = ini_get('max_execution_time');
543
+ // if ($max_execution_time >0 && $max_execution_time<300 && $resume_interval< $max_execution_time + 30) $resume_interval = $max_execution_time + 30;
544
 
545
  $initial_jobdata = array(
546
  'resume_interval', $resume_interval,
556
  // Use of jobdata_set_multi saves around 200ms
557
  call_user_func_array(array($this, 'jobdata_set_multi'), $initial_jobdata);
558
 
559
+ // Everything is now set up; now go
560
  $this->backup_resume(0, $this->nonce);
561
 
562
  }
837
  return true;
838
  }
839
 
840
+ // This function is not needed for backup success, according to the design, but it helps with efficient scheduling
841
+ function reschedule_if_needed() {
842
+ // If nothing is scheduled, then return
843
+ if (empty($this->newresumption_scheduled)) return;
844
+ $time_now = time();
845
+ $time_away = $this->newresumption_scheduled - $time_now;
846
+ // 30 is chosen because it is also used to detect recent activity on files (file mod times)
847
+ if ($time_away >1 && $time_away <= 30) {
848
+ $this->log('The scheduled resumption is within 30 seconds - will reschedule');
849
+ // Push 30 seconds into the future
850
+ // $this->reschedule(60);
851
+ // Increase interval generally by 30 seconds, on the assumption that our prior estimates were innaccurate (i.e. not just 30 seconds *this* time)
852
+ $this->increase_resume_and_reschedule(30);
853
+ }
854
+ }
855
+
856
  function reschedule($how_far_ahead) {
857
  // Reschedule - remove presently scheduled event
858
+ $next_resumption = $this->current_resumption + 1;
859
+ wp_clear_scheduled_hook('updraft_backup_resume', array($next_resumption, $this->nonce));
860
  // Add new event
861
  if ($how_far_ahead < $this->minimum_resume_interval()) $how_far_ahead=$this->minimum_resume_interval();
862
  $schedule_for = time() + $how_far_ahead;
863
+ $this->log("Rescheduling resumption $next_resumption: moving to $how_far_ahead seconds from now ($schedule_for)");
864
+ wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($next_resumption, $this->nonce));
865
  $this->newresumption_scheduled = $schedule_for;
866
  }
867
 
868
  function increase_resume_and_reschedule($howmuch = 120) {
869
  $resume_interval = $this->jobdata_get('resume_interval');
870
+ if (!is_numeric($resume_interval) || $resume_interval < $this->minimum_resume_interval()) { $resume_interval = $this->minimum_resume_interval(); }
871
+ if (!empty($this->newresumption_scheduled)) $this->reschedule($resume_interval+$howmuch);
872
  $this->jobdata_set('resume_interval', $resume_interval+$howmuch);
873
+ $this->log("To decrease the likelihood of overlaps, increasing resumption interval to: $resume_interval + $howmuch = ".($resume_interval+$howmuch));
874
  }
875
 
876
  function create_zip($create_from_dir, $whichone, $create_in_dir, $backup_file_basename) {
880
  if ($whichone != "others") $this->log("Beginning creation of dump of $whichone");
881
 
882
  $full_path = $create_in_dir.'/'.$backup_file_basename.'-'.$whichone.'.zip';
883
+ $time_now = time();
884
 
885
  if (file_exists($full_path)) {
886
  $this->log("$backup_file_basename-$whichone.zip: this file has already been created");
887
+ $time_mod = (int)@filemtime($full_path);
888
+ if ($time_mod>100 && ($time_now-$time_mod)<30) {
889
+ $this->log("Terminate: the zip $full_path already exists, and was modified within the last 30 seconds (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod).", size=".filesize($full_path)."). This likely means that another UpdraftPlus run is still at work; so we will exit.");
890
+ $this->increase_resume_and_reschedule(120);
891
+ die;
892
+ }
893
  return basename($full_path);
894
  }
895
 
897
 
898
  // Firstly, make sure that the temporary file is not already being written to - which can happen if a resumption takes place whilst an old run is still active
899
  $zip_name = $full_path.'.tmp';
 
900
  $time_mod = (int)@filemtime($zip_name);
901
  if (file_exists($zip_name) && $time_mod>100 && ($time_now-$time_mod)<30) {
902
  $file_size = filesize($zip_name);
925
  return basename($full_path);
926
  }
927
 
928
+ // For detecting another run, and aborting if one was found
929
+ function check_recent_modification($file) {
930
+ if (file_exists($file)) {
931
+ $time_mod = (int)@filemtime($file);
932
+ $time_now = time();
933
+ if ($time_mod>100 && ($time_now-$time_mod)<30) {
934
+ $this->log("Terminate: the file $file already exists, and was modified within the last 30 seconds (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod).", size=".filesize($file)."). This likely means that another UpdraftPlus run is still at work; so we will exit.");
935
+ $this->increase_resume_and_reschedule(120);
936
+ die;
937
+ }
938
+ }
939
+ }
940
+
941
  // This function is resumable
942
  function backup_dirs($transient_status) {
943
 
955
  $blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name);
956
  if(!$blog_name) $blog_name = 'non_alpha_name';
957
 
958
+ $backup_file_basename = 'backup_'.get_date_from_gmt(gmdate('Y-m-d H:i:s', $this->backup_time), 'Y-m-d-Hi').'_'.$blog_name.'_'.$this->nonce;
959
 
960
  $backup_array = array();
961
 
968
 
969
  # Plugins, themes, uploads
970
  foreach ($possible_backups as $youwhat => $whichdir) {
971
+
972
  if (UpdraftPlus_Options::get_updraft_option("updraft_include_$youwhat", true)) {
973
+
974
+ $zip_file = $updraft_dir.'/'.$backup_file_basename.'-'.$youwhat.'.zip';
975
+
976
+ $this->check_recent_modification($zip_file);
977
+
978
  if ($transient_status == 'finished') {
979
  $backup_array[$youwhat] = $backup_file_basename.'-'.$youwhat.'.zip';
980
+ if (file_exists($zip_file)) $backup_array[$youwhat.'-size'] = filesize($zip_file);
981
  } else {
982
  $created = $this->create_zip($whichdir, $youwhat, $updraft_dir, $backup_file_basename);
983
  if ($created) {
993
  # Others
994
  if (UpdraftPlus_Options::get_updraft_option('updraft_include_others', true)) {
995
 
996
+ $zip_file = $updraft_dir.'/'.$backup_file_basename.'-others.zip';
997
+ $this->check_recent_modification($zip_file);
998
+
999
  if ($transient_status == 'finished') {
1000
  $backup_array['others'] = $backup_file_basename.'-others.zip';
1001
+ if (file_exists($zip_file)) $backup_array['others-size'] = filesize($zip_file);
1002
  } else {
1003
  $this->log("Beginning backup of other directories found in the content directory");
1004
 
1131
  // Get the blog name and rip out all non-alphanumeric chars other than _
1132
  $blog_name = preg_replace('/[^A-Za-z0-9_]/','', str_replace(' ','_', substr(get_bloginfo(), 0, 96)));
1133
  if (!$blog_name) $blog_name = 'non_alpha_name';
1134
+
1135
+ $file_base = 'backup_'.get_date_from_gmt(gmdate('Y-m-d H:i:s', $this->backup_time), 'Y-m-d-Hi').'_'.$blog_name.'_'.$this->nonce;
1136
  $backup_file_base = $updraft_dir.'/'.$file_base;
1137
 
1138
  if ('finished' == $already_done) return basename($backup_file_base.'-db.gz');
1473
  return $schedules;
1474
  }
1475
 
1476
+ // This options filter removes ABSPATH off the front of updraft_dir, if it is given absolutely and contained within it
1477
+ function prune_updraft_dir_prefix($updraft_dir) {
1478
+ if ('/' == substr($updraft_dir, 0, 1) || "\\" == substr($updraft_dir, 0, 1) || preg_match('/^[a-zA-Z]:/', $updraft_dir)) {
1479
+ if (strpos($updraft_dir, ABSPATH) === 0) {
1480
+ $updraft_dir = substr($updraft_dir, strlen(ABSPATH));
1481
+ }
1482
+ }
1483
+ return $updraft_dir;
1484
+ }
1485
+
1486
  function backups_dir_location() {
1487
+
1488
  if (!empty($this->backup_dir)) return $this->backup_dir;
1489
+
1490
  $updraft_dir = untrailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_dir'));
1491
  $default_backup_dir = WP_CONTENT_DIR.'/updraft';
 
1492
  $updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
1493
+
1494
+ // Do a test for a relative path
1495
+ if ('/' != substr($updraft_dir, 0, 1) && "\\" != substr($updraft_dir, 0, 1) && !preg_match('/^[a-zA-Z]:/', $updraft_dir)) {
1496
+ $updraft_dir = ABSPATH.$updraft_dir;
1497
+ }
1498
+
1499
+ //if the option isn't set, default it to /backups inside the upload dir
1500
  //check for the existence of the dir and an enumeration preventer.
1501
+ // index.php is for a sanity check - make sure that we're not somewhere unexpected
1502
+ if((!is_dir($updraft_dir) || !is_file($updraft_dir.'/index.html') || !is_file($updraft_dir.'/.htaccess')) && !is_file($updraft_dir.'/index.php')) {
1503
  @mkdir($updraft_dir, 0775, true);
1504
+ @file_put_contents($updraft_dir.'/index.html',"<html><body><a href=\"http://updraftplus.com\">WordPress backups by UpdraftPlus</a></body></html>");
1505
+ if (!is_file($updraft_dir.'/.htaccess')) @file_put_contents($updraft_dir.'/.htaccess','deny from all');
1506
  }
1507
+
1508
  $this->backup_dir = $updraft_dir;
1509
+
1510
  return $updraft_dir;
1511
  }
1512
 
1922
  return $this->url_start($urls,'wordshell.net')."Check out WordShell".$this->url_end($urls,'www.wordshell.net')." - manage WordPress from the command line - huge time-saver";
1923
  break;
1924
  case 3:
1925
+ return "Like UpdraftPlus and can spare one minute? ".$this->url_start($urls,'wordpress.org/support/view/plugin-reviews/updraftplus#postform')."Please help UpdraftPlus by giving a positive review at wordpress.org.".$this->url_end($urls,'wordpress.org/support/view/plugin-reviews/updraftplus#postform');
1926
  break;
1927
  case 4:
1928
  return $this->url_start($urls,'www.simbahosting.co.uk')."Need high-quality WordPress hosting from WordPress specialists? (Including automatic backups and 1-click installer). Get it from the creators of UpdraftPlus.".$this->url_end($urls,'www.simbahosting.co.uk');
2115
  jQuery('.expertmode').fadeIn();
2116
  return false;
2117
  });
2118
+ <?php if (!@is_writable($updraft_dir)) echo "jQuery('.backupdirrow').show();\n"; ?>
2119
  setTimeout(function(){updraft_showlastlog();}, 1200);
2120
  jQuery('.updraftplusmethod').hide();
2121
  <?php
2152
 
2153
  <tr class="expertmode backupdirrow" style="display:none;">
2154
  <th>Backup directory:</th>
2155
+ <td><input type="text" name="updraft_dir" id="updraft_dir" style="width:525px" value="<?php echo htmlspecialchars($this->prune_updraft_dir_prefix($updraft_dir)); ?>" /></td>
2156
  </tr>
2157
  <tr class="expertmode backupdirrow" style="display:none;">
2158
  <td></td><td><?php
2159
 
2160
+ // Suppress warnings, since if the user is dumping warnings to screen, then invalid JavaScript results and the screen breaks.
2161
+ if(@is_writable($updraft_dir)) {
2162
  $dir_info = '<span style="color:green">Backup directory specified is writable, which is good.</span>';
2163
  } else {
2164
+ $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, or <a href="#" onclick="jQuery(\'#updraft_dir\').val(\''.WP_CONTENT_DIR.'/updraft\'); return false;">here to reset this option</a>. 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>';
2165
  }
2166
 
2167
  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>
2295
  if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_all') { $this->boot_backup(true,true); }
2296
  elseif (isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_db') { $this->backup_db(); }
2297
  elseif (isset($_POST['action']) && $_POST['action'] == 'updraft_wipesettings') {
2298
+ $settings = array('updraft_interval', 'updraft_interval_database', 'updraft_retain', 'updraft_retain_db', 'updraft_encryptionphrase', 'updraft_service', 'updraft_dropbox_appkey', 'updraft_dropbox_secret', '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_dropboxtk_request_token', 'updraft_dropboxtk_access_token', 'updraft_dropbox_folder', 'updraft_last_backup', 'updraft_starttime_files', 'updraft_starttime_db', 'updraft_sftp_settings');
2299
  foreach ($settings as $s) {
2300
  UpdraftPlus_Options::delete_updraft_option($s);
2301
  }
2322
  <div style="color:orange">Your PHP memory limit is quite low. UpdraftPlus attempted to raise it but was unsuccessful. This plugin may not work properly with a memory limit of less than 96 Mb (though on the other hand, it has been used successfully with a 32Mb limit - your mileage may vary, but don't blame us!). Current limit is: <?php echo $this->memory_check_current(); ?> Mb</div>
2323
  <?php
2324
  }
2325
+ if(1==0 && !$this->execution_time_check(60)) {?>
2326
+ <div style="color:orange">Your PHP max_execution_time is less than 60 seconds. This possibly means you're running in safe_mode. Either disable safe_mode or modify your php.ini to set max_execution_time to a higher number. If you do not, then longer will be needed to complete a backup (but that is all). Present limit is: <?php echo ini_get('max_execution_time'); ?> seconds.</div>
2327
  <?php
2328
  }
2329
 
2445
  </tr>
2446
  <tr>
2447
  <td></td><td class="download-backups" style="display:none">
2448
+ <p><em><strong>Note</strong> - Pressing a button will make UpdraftPlus try to bring a backup file back from the remote storage (if any - e.g. Amazon S3, Dropbox, Google Drive, FTP) to your webserver, before then allowing you to download it to your computer. If the fetch from the remote storage stops progressing (wait 30 seconds to make sure), then click again to resume from where it left off. Remember that you can always visit the cloud storage website vendor's website directly. <strong>If you are using the Opera web browser, </strong> then turn Turbo/Road mode off.</em></p>
2449
  <div id="ud_downloadstatus"></div>
2450
  <script>
2451
  var lastlog_lastmessage = "";
2587
  $updraft_dir = $this->backups_dir_location();
2588
 
2589
  echo '<table>';
2590
+
2591
+ krsort($backup_history);
2592
+
2593
  foreach($backup_history as $key=>$value) {
2594
  ?>
2595
  <tr>
2696
  // - No zip extension present and no relevant method present
2697
  // The zip extension check is not redundant, because method_exists segfaults some PHP installs, leading to support requests
2698
 
2699
+ // Fallback to PclZip - which my tests show is 25% slower (and we can't resume)
2700
  if ($this->zip_preferpcl || (!extension_loaded('zip') && !method_exists('ZipArchive', 'AddFile'))) {
2701
  if(!class_exists('PclZip')) require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
2702
  $zip_object = new PclZip($destination);
2731
  $this->zipfiles_dirbatched = array();
2732
  $this->zipfiles_batched = array();
2733
 
2734
+ // Magic value, used later to detect no error occurring
2735
+ $last_error = 2349864;
2736
  if (is_array($source)) {
2737
  foreach ($source as $element) {
2738
  $howmany = $this->makezip_recursive_add($destination, $element, basename($element), $element);
2755
  }
2756
  }
2757
 
2758
+ if ($this->zipfiles_added > 0 || $last_error == 2349864) {
2759
+ // ZipArchive::addFile sometimes fails
2760
  if (filesize($destination) < 100) {
2761
  // Retry with PclZip
2762
  $this->log("Zip::addFile apparently failed - retrying with PclZip");
2775
 
2776
  // We batch up the files, rather than do them one at a time. So we are more efficient than open,one-write,close.
2777
  function makezip_addfiles($zipfile) {
2778
+
2779
+ // Short-circuit the null case, because we want to detect later if something useful happenned
2780
+ if (count($this->zipfiles_dirbatched) == 0 && count($this->zipfiles_batched) == 0) return true;
2781
+
2782
+ // 05-Mar-2013 - added a new check on the total data added; it appears that things fall over if too much data is contained in the cumulative total of files that were addFile'd without a close-open cycle; presumably data is being stored in memory. In the case in question, it was a batch of MP3 files of around 100Mb each - 25 of those equals 2.5Gb!
2783
+
2784
+ $data_added_since_reopen = 0;
2785
+
2786
  $zip = new ZipArchive();
2787
  if (file_exists($zipfile)) {
2788
  $opencode = $zip->open($zipfile);
2789
+ $original_size = filesize($zipfile);
2790
  } else {
2791
  $opencode = $zip->open($zipfile, ZIPARCHIVE::CREATE);
2792
+ $original_size = 0;
2793
  }
2794
+
2795
  if ($opencode !== true) return array($opencode, 0);
2796
  // Make sure all directories are created before we start creating files
2797
  while ($dir = array_pop($this->zipfiles_dirbatched)) {
2798
  $zip->addEmptyDir($dir);
2799
  }
2800
  foreach ($this->zipfiles_batched as $file => $add_as) {
2801
+ $fsize = filesize($file);
2802
+ if (!isset($this->existing_files[$add_as]) || $this->existing_files[$add_as] != $fsize) {
2803
+
2804
+ touch($file);
2805
  $zip->addFile($file, $add_as);
2806
+
2807
+ $data_added_since_reopen += $fsize;
2808
+ # 25Mb - force a write-out and re-open
2809
+ if ($data_added_since_reopen > 26214400) {
2810
+
2811
+ $before_size = filesize($zipfile);
2812
+
2813
+ $this->log("Adding batch to zip file: over 25Mb added on this batch (".round($data_added_since_reopen/1048576,1)." Mb); re-opening (prior size: ".round($before_size/1024,1).' Kb)');
2814
+ if (!$zip->close()) {
2815
+ $this->log("zip::Close returned an error");
2816
+ }
2817
+ unset($zip);
2818
+ $zip = new ZipArchive();
2819
+ $opencode = $zip->open($zipfile);
2820
+ if ($opencode !== true) return array($opencode, 0);
2821
+ $data_added_since_reopen = 0;
2822
+ // Call here, in case we've got so many big files that we don't complete the whole routine
2823
+ if (filesize($zipfile) > $before_size) $this->something_useful_happened();
2824
+ }
2825
  }
2826
  $this->zipfiles_added++;
2827
+ if ($this->zipfiles_added % 100 == 0) $this->log("Zip: ".basename($zipfile).": ".$this->zipfiles_added." files added (on-disk size: ".round(filesize($zipfile)/1024,1)." Kb)");
2828
  }
2829
  // Reset the array
2830
  $this->zipfiles_batched = array();
2831
+ $ret = $zip->close();
2832
+ if (filesize($zipfile) > $original_size) $this->something_useful_happened();
2833
+ return $ret;
2834
+ }
2835
+
2836
+ function something_useful_happened() {
2837
+ if ($this->current_resumption >= 9 && $this->newresumption_scheduled == false) {
2838
+ $resume_interval = $this->jobdata_get('resume_interval');
2839
+ if (!is_numeric($resume_interval) || $resume_interval<$this->minimum_resume_interval()) { $resume_interval = $this->minimum_resume_interval(); }
2840
+ $schedule_for = time()+$resume_interval;
2841
+ $this->newresumption_scheduled = $schedule_for;
2842
+ $this->log("This is resumption ".$this->current_resumption.", but meaningful activity is still taking place; so a new one will be scheduled");
2843
+ wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($this->current_resumption + 1, $this->nonce));
2844
+ } else {
2845
+ $this->reschedule_if_needed();
2846
+ }
2847
  }
2848
 
2849
  // This function recursively packs the zip, dereferencing symlinks but packing into a single-parent tree for universal unpacking
2861
 
2862
  if(is_file($fullpath)) {
2863
  if (is_readable($fullpath)) {
2864
+ $key = ($fullpath == $original_fullpath) ? basename($fullpath) : $use_path_when_storing.'/'.basename($fullpath);
2865
+ $this->zipfiles_batched[$fullpath] = $key;
2866
  @touch($zipfile);
2867
  } else {
2868
  $this->log("$fullpath: unreadable file");