UpdraftPlus WordPress Backup Plugin - Version 0.8.29

Version Description

  • 06/29/2012 =
  • Marking as tested up to WordPress 3.4.1
Download this release

Release Info

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

Code changes from version 0.7.7 to 0.8.29

Files changed (2) hide show
  1. readme.txt +17 -8
  2. updraftplus.php +764 -152
readme.txt CHANGED
@@ -1,14 +1,18 @@
1
  === UpdraftPlus ===
2
  Contributors: David Anderson
3
- Tags: backup, restore, database, cloud, amazon, s3, ftp, cloud
4
  Requires at least: 3.2
5
- Tested up to: 3.3.2
6
- Stable tag: 0.7.7
 
7
  License: GPLv2 or later
8
 
9
  == Description ==
10
 
11
- UpdraftPlus simplifies backups (and restoration) for your blog. Backup into the cloud (S3, FTP, and email) and restore with a single click.
 
 
 
12
 
13
  == Installation ==
14
 
@@ -41,11 +45,16 @@ Nothing, probably. That's the point of an encryption key - people who don't have
41
 
42
  Contact me! This is a complex plugin and the only way I can ensure it's robust is to get bug reports and fix the problems that crop up. Please turn on debugging mode and send me the log if you can find it. Include as much information as you can when reporting (PHP version, your blog's site, the error you saw and how you got to the page that caused it, etcetera). If you can send a patch, that's even better.
43
 
44
- == Upgrade Notice ==
45
- Added a logging mechanism for easier development
46
-
47
  == Changelog ==
48
 
 
 
 
 
 
 
 
 
49
  = 0.7.7 - 05/29/2012 =
50
  * Implementation of a logging mechanism to allow easier debugging and development
51
 
@@ -56,7 +65,7 @@ Added a logging mechanism for easier development
56
  * Added ability to decrypt encrypted database backups
57
  * Added ability to opt out of backing up each file group
58
  * Now adds database character set, the lack of which before made database backups unusable without modifications
59
- * Version number bump to make sure that this is an improvement on the original Updraft, and is now tried and tested
60
 
61
  = 0.1.3 - 01/16/2012 =
62
  * Force backup of all tables found in database (vanilla Updraft only backed up WP core tables)
1
  === UpdraftPlus ===
2
  Contributors: David Anderson
3
+ Tags: backup, restore, database, cloud, amazon, s3, google drive, google, gdrive, ftp, cloud, updraft, back up
4
  Requires at least: 3.2
5
+ Tested up to: 3.4.1
6
+ Stable tag: 0.8.29
7
+ Donate link: http://david.dw-perspective.org.uk/donate
8
  License: GPLv2 or later
9
 
10
  == Description ==
11
 
12
+ UpdraftPlus simplifies backups (and restoration) for your blog. Backup into the cloud (S3, Google Drive, FTP, and email) and restore with a single click. Backups of files and database can be upon separate schedules.
13
+
14
+ == Upgrade Notice ==
15
+ Tested up to WordPress 3.4.1
16
 
17
  == Installation ==
18
 
45
 
46
  Contact me! This is a complex plugin and the only way I can ensure it's robust is to get bug reports and fix the problems that crop up. Please turn on debugging mode and send me the log if you can find it. Include as much information as you can when reporting (PHP version, your blog's site, the error you saw and how you got to the page that caused it, etcetera). If you can send a patch, that's even better.
47
 
 
 
 
48
  == Changelog ==
49
 
50
+ = 0.8.29 - 06/29/2012 =
51
+ * Marking as tested up to WordPress 3.4.1
52
+
53
+ = 0.8.28 - 06/06/2012 =
54
+ * Now experimentally supports Google Drive (thanks to Sorin Iclanzan, code re-used from his Google Drive-only 'backup' plugin)
55
+ * New feature: backup files and database on separate schedules
56
+ * Tidied and improved retain behaviour
57
+
58
  = 0.7.7 - 05/29/2012 =
59
  * Implementation of a logging mechanism to allow easier debugging and development
60
 
65
  * Added ability to decrypt encrypted database backups
66
  * Added ability to opt out of backing up each file group
67
  * Now adds database character set, the lack of which before made database backups unusable without modifications
68
+ * Version number bump to make clear that this is an improvement on the original Updraft, and is now tried and tested
69
 
70
  = 0.1.3 - 01/16/2012 =
71
  * Force backup of all tables found in database (vanilla Updraft only backed up WP core tables)
updraftplus.php CHANGED
@@ -2,35 +2,26 @@
2
  /*
3
  Plugin Name: UpdraftPlus - Backup/Restore
4
  Plugin URI: http://wordpress.org/extend/plugins/updraftplus
5
- Description: UpdraftPlus - Backup/Restore is a plugin designed to back up your WordPress site. Uploads, themes, plugins, and your DB can be backed up to Amazon S3, sent to an FTP server, or even emailed to you on a scheduled basis.
6
  Author: David Anderson.
7
- Version: 0.7.7
 
8
  Author URI: http://wordshell.net
9
  */
10
 
11
  //TODO:
12
- //Put DB and file backups onto separate schedules. If the option is set identically then do in one run, otherwise do separately.
13
  //Add DropBox support
14
- //Add more logging
15
  //Struggles with large uploads - runs out of time before finishing. Break into chunks? Resume download on later run? (Add a new scheduled event to check on progress? Separate the upload from the creation?). Add in some logging (in a .php file that exists first).
16
- //More logging
17
  //improve error reporting. s3 and dir backup have decent reporting now, but not sure i know what to do from here
18
- //better implementation of retain. one that isn't dependent on being inside the cloud_backup method
19
  //list backups that aren't tracked (helps with double backup problem)
20
- //refactor db backup methods a bit. give full credit to wp-db-backup
21
  //investigate $php_errormsg further
22
  //pretty up return messages in admin area
23
  //check s3/ftp download
24
- //allow upload of backup files too. (specify 1-4 files to restore)
25
- //Add back donate link in readme.txt header. Donate link: URL
26
- //user permissions for WP users if ( function_exists('is_site_admin') && ! is_site_admin() ) around backups?
27
 
28
  /* More TODO:
29
  Are all directories in wp-content covered? No; only plugins, themes, content. We should check for others and allow the user the chance to choose which ones he wants
30
- Add turn-off-foreign-key-checks stuff into mysql dump (does WP even use these?)
31
  Use only one entry in WP options database
32
  Encrypt filesystem, if memory allows (and have option for abort if not); split up into multiple zips when needed
33
- More verbose debug reports, send debug report in the email
34
  */
35
 
36
  /* Portions copyright 2010 Paul Kehrer
@@ -63,7 +54,7 @@ if(!$updraft->memory_check(192)) {
63
 
64
  class UpdraftPlus {
65
 
66
- var $version = '0.7.7';
67
 
68
  var $dbhandle;
69
  var $errors = array();
@@ -77,16 +68,381 @@ class UpdraftPlus {
77
  # Create admin page
78
  add_action('admin_menu', array($this,'add_admin_pages'));
79
  add_action('admin_init', array($this,'admin_init'));
80
- add_action('updraft_backup', array($this,'backup'));
 
 
 
81
  add_action('wp_ajax_updraft_download_backup', array($this, 'updraft_download_backup'));
82
  add_filter('cron_schedules', array($this,'modify_cron_schedules'));
83
  add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  }
85
 
86
  # Adds the settings link under the plugin on the plugin screen.
87
  function plugin_action_links($links, $file) {
88
  if ($file == plugin_basename(__FILE__)){
89
- $settings_link = '<a href="'.site_url().'/wp-admin/options-general.php?page=updraft-backuprestore.php">'.__("Settings", "wp-updates-notifier").'</a>';
 
 
90
  array_unshift($links, $settings_link);
91
  }
92
  return $links;
@@ -104,56 +460,98 @@ class UpdraftPlus {
104
  }
105
  }
106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  //scheduled wp-cron events can have a race condition here if page loads are coming fast enough, but there's nothing we can do about it.
108
- function backup() {
 
109
  //generate backup information
110
  $this->backup_time_nonce();
111
 
112
- //set log file name
113
  $updraft_dir = $this->backups_dir_location();
114
  $this->logfile_name = $updraft_dir. "/log." . $this->nonce . ".txt";
115
-
116
- # Use append mode in case it already exists
117
  $this->logfile_handle = fopen($this->logfile_name, 'a');
118
- // Some information that may be helpful
 
119
  global $wp_version;
120
- $this->log("PHP version: ".phpversion()." WordPress version: ".$wp_version);
121
- //backup directories and return a numerically indexed array of file paths to the backup files
122
- $this->log("Beginning backup of directories");
123
- $backup_array = $this->backup_dirs();
124
- //backup DB and return string of file path
125
- $this->log("Beginning backup of database");
126
- $db_backup = $this->backup_db();
127
- //add db path to rest of files
128
- if(is_array($backup_array)) { $backup_array['db'] = $db_backup; }
129
- //save this to our history so we can track backups for the retain feature
130
- $this->log("Saving backup history");
131
- $this->save_backup_history($backup_array);
132
-
133
- //cloud operations (S3,FTP,email,nothing)
134
- //this also calls the retain feature at the end (done in this method to reuse existing cloud connections)
135
- if(is_array($backup_array) && count($backup_array) >0) {
136
- $this->log("Beginning dispatch of backup to remote");
137
- $this->cloud_backup($backup_array);
138
- }
139
- //delete local files if the pref is set
140
- foreach($backup_array as $file) {
141
- $this->log("Deleting local file: $file");
142
- $this->delete_local($file);
143
  }
144
-
145
- //save the last backup info, including errors, if any
146
- $this->log("Saving last backup information into WordPress db");
147
- $this->save_last_backup($backup_array);
148
-
149
- if(get_option('updraft_email') != "" && get_option('updraft_service') != 'email') {
150
- $sendmail_to = get_option('updraft_email');
151
- $this->log("Sending email report to: ".$sendmail_to);
152
- $append_log = "";
153
- if(get_option('updraft_debug_mode') && $this->logfile_name != "") {
154
- $append_log .= "\r\nLog contents:\r\n".file_get_contents($this->logfile_name);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  }
156
- wp_mail($sendmail_to,'Backed up: '.get_bloginfo('name').' (UpdraftPlus) '.date('Y-m-d H:i',time()),'Site: '.site_url()."\r\nUpdraftPlus WordPress backup is complete.\r\n\r\n".$this->wordshell_random_advert(0)."\r\n".$append_log);
157
  }
158
 
159
  // Close log file
@@ -174,6 +572,11 @@ class UpdraftPlus {
174
  $this->log("Cloud backup: S3");
175
  if (count($backup_array) >0) { $this->s3_backup($backup_array); }
176
  break;
 
 
 
 
 
177
  case 'ftp':
178
  @set_time_limit(900);
179
  $this->log("Cloud backup: FTP");
@@ -189,23 +592,130 @@ class UpdraftPlus {
189
  }
190
  //we don't break here so it goes and executes all the default behavior below as well. this gives us retain behavior for email
191
  default:
192
- /*retain behavior*/
193
- $updraft_retain = get_option('updraft_retain');
194
- $retain = (isset($updraft_retain))?get_option('updraft_retain'):1;
195
- $backup_history = $this->get_backup_history();
196
- while (count($backup_history) > $retain) {
197
- $backup_to_delete = array_pop($backup_history);
198
- foreach($backup_to_delete as $file) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  $fullpath = trailingslashit(get_option('updraft_dir')).$file;
200
  @unlink($fullpath); //delete it if it's locally available
 
 
 
 
 
 
 
 
 
 
 
 
201
  }
 
202
  }
203
- update_option('updraft_backup_history',$backup_history);
204
- /*retain behavior*/
205
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  }
 
 
207
  }
208
-
209
  function s3_backup($backup_array) {
210
  if(!class_exists('S3')) {
211
  require_once(dirname(__FILE__).'/includes/S3.php');
@@ -219,29 +729,29 @@ class UpdraftPlus {
219
  $this->error("S3 Error: Failed to upload $fullpath. Error was ".$php_errormsg);
220
  }
221
  }
 
222
  } else {
223
  $this->error("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
224
  }
225
- /*retain behavior*/
226
- $updraft_retain = get_option('updraft_retain');
227
- $retain = (isset($updraft_retain))?get_option('updraft_retain'):1;
228
- $backup_history = $this->get_backup_history();
229
- while (count($backup_history) > $retain) {
230
- $backup_to_delete = array_pop($backup_history);
231
- foreach($backup_to_delete as $file) {
232
- //if for some reason one of the backup files is an empty string let's skip it.
233
- if($file == '') {
234
- continue;
235
- }
236
- $fullpath = trailingslashit(get_option('updraft_dir')).$file;
237
- @unlink($fullpath); //delete it if it's locally available
238
- if (!$s3->deleteObject($bucket_name, $file)) {
239
- $this->error("S3 Error: Failed to delete object $file. Error was ".$php_errormsg);
240
  }
241
  }
 
 
 
242
  }
243
- update_option('updraft_backup_history',$backup_history);
244
- /*retain behavior*/
245
  }
246
 
247
  function ftp_backup($backup_array) {
@@ -259,30 +769,13 @@ class UpdraftPlus {
259
  $fullpath = trailingslashit(get_option('updraft_dir')).$file;
260
  $ftp->put($fullpath,$ftp_remote_path.$file,FTP_BINARY);
261
  }
262
-
263
- /*retain behavior*/
264
- $updraft_retain = get_option('updraft_retain');
265
- $retain = (isset($updraft_retain))?get_option('updraft_retain'):1;
266
- $backup_history = $this->get_backup_history();
267
- while (count($backup_history) > $retain) {
268
- $backup_to_delete = array_pop($backup_history);
269
- foreach($backup_to_delete as $file) {
270
- //if for some reason one of the backup files is an empty string let's skip it.
271
- if($file == '') {
272
- continue;
273
- }
274
- $fullpath = trailingslashit(get_option('updraft_dir')).$file;
275
- @unlink($fullpath); //delete it if it's locally available
276
- @$ftp->delete($ftp_remote_path.$file);
277
- }
278
- }
279
- update_option('updraft_backup_history',$backup_history);
280
- /*retain behavior*/
281
  }
282
 
283
  function delete_local($file) {
284
  if(get_option('updraft_delete_local')) {
285
- //need error checking so we don't delete what isn't successfully uploaded?
 
286
  $fullpath = trailingslashit(get_option('updraft_dir')).$file;
287
  return unlink($fullpath);
288
  }
@@ -320,41 +813,49 @@ class UpdraftPlus {
320
  # Plugins
321
  @set_time_limit(900);
322
  if (get_option('updraft_include_plugins', true)) {
 
323
  $plugins = new PclZip($backup_file_base.'-plugins.zip');
324
  if (!$plugins->create($wp_plugins_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
325
  $this->error('Could not create plugins zip. Error was '.$php_errmsg,'fatal');
326
  }
327
  $backup_array['plugins'] = basename($backup_file_base.'-plugins.zip');
 
 
328
  }
329
-
330
  # Themes
331
  @set_time_limit(900);
332
  if (get_option('updraft_include_themes', true)) {
 
333
  $themes = new PclZip($backup_file_base.'-themes.zip');
334
  if (!$themes->create($wp_themes_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
335
  $this->error('Could not create themes zip. Error was '.$php_errmsg,'fatal');
336
  }
337
  $backup_array['themes'] = basename($backup_file_base.'-themes.zip');
 
 
338
  }
339
 
340
  # Uploads
341
  @set_time_limit(900);
342
  if (get_option('updraft_include_uploads', true)) {
 
343
  $uploads = new PclZip($backup_file_base.'-uploads.zip');
344
  if (!$uploads->create($wp_upload_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
345
  $this->error('Could not create uploads zip. Error was '.$php_errmsg,'fatal');
346
  }
347
  $backup_array['uploads'] = basename($backup_file_base.'-uploads.zip');
 
 
348
  }
349
-
350
  return $backup_array;
351
  }
352
 
353
  function save_backup_history($backup_array) {
354
  //this stores full paths right now. should probably concatenate with ABSPATH to make it easier to move sites
355
- $backup_history = get_option('updraft_backup_history');
356
- $backup_history = (!is_array($backup_history))?array():$backup_history;
357
  if(is_array($backup_array)) {
 
 
358
  $backup_history[$this->backup_time] = $backup_array;
359
  update_option('updraft_backup_history',$backup_history);
360
  } else {
@@ -379,6 +880,9 @@ class UpdraftPlus {
379
  /*START OF WB-DB-BACKUP BLOCK*/
380
 
381
  function backup_db() {
 
 
 
382
  global $table_prefix, $wpdb;
383
  if(!$this->backup_time) {
384
  $this->backup_time_nonce();
@@ -409,7 +913,6 @@ class UpdraftPlus {
409
  //$this->error(__('The backup directory is not writable!','wp-db-backup'));
410
  }
411
 
412
-
413
  //Begin new backup of MySql
414
  $this->stow("# " . __('WordPress MySQL database backup','wp-db-backup') . "\n");
415
  $this->stow("#\n");
@@ -427,6 +930,7 @@ class UpdraftPlus {
427
  }
428
 
429
  foreach ($all_tables as $table) {
 
430
  // Increase script execution time-limit to 15 min for every table.
431
  if ( !ini_get('safe_mode')) @set_time_limit(15*60);
432
  if ( strpos($table, $table_prefix) == 0 ) {
@@ -456,6 +960,7 @@ class UpdraftPlus {
456
  # Encrypt, if requested
457
  $encryption = get_option('updraft_encryptionphrase');
458
  if (strlen($encryption) > 0) {
 
459
  $encryption_error = 0;
460
  require_once(dirname(__FILE__).'/includes/Rijndael.php');
461
  $rijndael = new Crypt_Rijndael();
@@ -480,6 +985,7 @@ class UpdraftPlus {
480
  return basename($backup_file_base.'-db.gz');
481
  }
482
  }
 
483
 
484
  } //wp_db_backup
485
 
@@ -496,6 +1002,8 @@ class UpdraftPlus {
496
  function backup_table($table, $segment = 'none') {
497
  global $wpdb;
498
 
 
 
499
  $table_structure = $wpdb->get_results("DESCRIBE $table");
500
  if (! $table_structure) {
501
  //$this->error(__('Error getting table details','wp-db-backup') . ": $table");
@@ -585,6 +1093,7 @@ class UpdraftPlus {
585
  $replace = array('\0', '\n', '\r', '\Z');
586
  if($table_data) {
587
  foreach ($table_data as $row) {
 
588
  $values = array();
589
  foreach ($row as $key => $value) {
590
  if ($ints[strtolower($key)]) {
@@ -611,6 +1120,8 @@ class UpdraftPlus {
611
  $this->stow("# --------------------------------------------------------\n");
612
  $this->stow("\n");
613
  }
 
 
614
  } // end backup_table()
615
 
616
 
@@ -648,8 +1159,6 @@ class UpdraftPlus {
648
  return true;
649
  }
650
 
651
-
652
-
653
  /**
654
  * Add backquotes to tables and db-names in
655
  * SQL queries. Taken from phpMyAdmin.
@@ -680,10 +1189,6 @@ class UpdraftPlus {
680
  return str_replace('\'', '\\\'', $a_string);
681
  }
682
 
683
- /*END OF WP-DB-BACKUP BLOCK */
684
- /*END OF WP-DB-BACKUP BLOCK */
685
- /*END OF WP-DB-BACKUP BLOCK */
686
- /*END OF WP-DB-BACKUP BLOCK */
687
  /*END OF WP-DB-BACKUP BLOCK */
688
 
689
  /*
@@ -705,6 +1210,19 @@ class UpdraftPlus {
705
  return wp_filter_nohtml_kses($interval);
706
  }
707
 
 
 
 
 
 
 
 
 
 
 
 
 
 
708
  //wp-cron only has hourly, daily and twicedaily, so we need to add weekly and monthly.
709
  function modify_cron_schedules($schedules) {
710
  $schedules['weekly'] = array(
@@ -785,12 +1303,15 @@ class UpdraftPlus {
785
  $this->delete_local($file);
786
  exit; //we exit immediately because otherwise admin-ajax appends an additional zero to the end for some reason I don't understand. seriously, why die('0')?
787
  } else {
788
- echo 'Download failed. File '.$fullpath.' did not exist or was unreadable. If you delete local backups then S3 or FTP retrieval may have failed.';
789
  }
790
  }
791
 
792
  function download_backup($file) {
793
  switch(get_option('updraft_service')) {
 
 
 
794
  case 's3':
795
  $this->download_s3_backup($file);
796
  break;
@@ -802,6 +1323,10 @@ class UpdraftPlus {
802
  }
803
  }
804
 
 
 
 
 
805
  function download_s3_backup($file) {
806
  if(!class_exists('S3')) {
807
  require_once(dirname(__FILE__).'/includes/S3.php');
@@ -841,7 +1366,7 @@ class UpdraftPlus {
841
  return false;
842
  }
843
 
844
- $credentials = request_filesystem_credentials("options-general.php?page=updraft-backuprestore.php&action=updraft_restore&backup_timestamp=$timestamp");
845
  WP_Filesystem($credentials);
846
  if ( $wp_filesystem->errors->get_error_code() ) {
847
  foreach ( $wp_filesystem->errors->get_error_messages() as $message )
@@ -880,11 +1405,10 @@ class UpdraftPlus {
880
  return true;
881
  }
882
 
883
-
884
  //deletes the -old directories that are created when a backup is restored.
885
  function delete_old_dirs() {
886
  global $wp_filesystem;
887
- $credentials = request_filesystem_credentials("options-general.php?page=updraft-backuprestore.php&action=updraft_delete_old_dirs");
888
  WP_Filesystem($credentials);
889
  if ( $wp_filesystem->errors->get_error_code() ) {
890
  foreach ( $wp_filesystem->errors->get_error_messages() as $message )
@@ -926,7 +1450,7 @@ class UpdraftPlus {
926
 
927
  function create_backup_dir() {
928
  global $wp_filesystem;
929
- $credentials = request_filesystem_credentials("options-general.php?page=updraft-backuprestore.php&action=updraft_create_backup_dir");
930
  WP_Filesystem($credentials);
931
  if ( $wp_filesystem->errors->get_error_code() ) {
932
  foreach ( $wp_filesystem->errors->get_error_messages() as $message )
@@ -982,16 +1506,20 @@ class UpdraftPlus {
982
  }
983
  wp_enqueue_script('jquery');
984
  register_setting( 'updraft-options-group', 'updraft_interval', array($this,'schedule_backup') );
 
985
  register_setting( 'updraft-options-group', 'updraft_retain', array($this,'retain_range') );
986
  register_setting( 'updraft-options-group', 'updraft_encryptionphrase', 'wp_filter_nohtml_kses' );
987
  register_setting( 'updraft-options-group', 'updraft_service', 'wp_filter_nohtml_kses' );
988
  register_setting( 'updraft-options-group', 'updraft_s3_login', 'wp_filter_nohtml_kses' );
989
  register_setting( 'updraft-options-group', 'updraft_s3_pass', 'wp_filter_nohtml_kses' );
 
 
 
 
990
  register_setting( 'updraft-options-group', 'updraft_ftp_login', 'wp_filter_nohtml_kses' );
991
  register_setting( 'updraft-options-group', 'updraft_ftp_pass', 'wp_filter_nohtml_kses' );
992
  register_setting( 'updraft-options-group', 'updraft_dir', 'wp_filter_nohtml_kses' );
993
  register_setting( 'updraft-options-group', 'updraft_email', 'wp_filter_nohtml_kses' );
994
- register_setting( 'updraft-options-group', 'updraft_s3_remote_path', 'wp_filter_nohtml_kses' );
995
  register_setting( 'updraft-options-group', 'updraft_ftp_remote_path', 'wp_filter_nohtml_kses' );
996
  register_setting( 'updraft-options-group', 'updraft_server_address', 'wp_filter_nohtml_kses' );
997
  register_setting( 'updraft-options-group', 'updraft_delete_local', 'absint' );
@@ -1000,6 +1528,7 @@ class UpdraftPlus {
1000
  register_setting( 'updraft-options-group', 'updraft_include_themes', 'absint' );
1001
  register_setting( 'updraft-options-group', 'updraft_include_uploads', 'absint' );
1002
 
 
1003
  if (current_user_can('manage_options')) {
1004
  $updraft_dir = $this->backups_dir_location();
1005
  if(strpos($updraft_dir,WP_CONTENT_DIR) !== false) {
@@ -1013,15 +1542,16 @@ class UpdraftPlus {
1013
  add_action('admin_notices', array($this,'show_admin_warning_accessible') );
1014
  }
1015
  }
1016
- if (isset($dir_protection_info)) {
1017
- }
1018
  }
1019
  }
 
 
 
 
1020
  }
1021
-
1022
 
1023
  function add_admin_pages() {
1024
- add_submenu_page('options-general.php', "UpdraftPlus", "UpdraftPlus", "manage_options", "updraft-backuprestore.php",
1025
  array($this,"settings_output"));
1026
  }
1027
 
@@ -1051,15 +1581,15 @@ ENDHERE;
1051
  $backup_success = $this->restore_backup($_REQUEST['backup_timestamp']);
1052
  if(empty($this->errors) && $backup_success == true) {
1053
  echo '<p>Restore successful!</p><br/>';
1054
- echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php&updraft_restore_success=true">Return to Updraft Configuration</a>.';
1055
  return;
1056
  } else {
1057
  echo '<p>Restore failed...</p><br/>';
1058
- echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php">Return to Updraft Configuration</a>.';
1059
  return;
1060
  }
1061
  //uncomment the below once i figure out how i want the flow of a restoration to work.
1062
- //echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php">Return to Updraft Configuration</a>.';
1063
  }
1064
  $deleted_old_dirs = false;
1065
  if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_delete_old_dirs') {
@@ -1069,24 +1599,31 @@ ENDHERE;
1069
  echo '<p>Old directory removal failed for some reason. You may want to do this manually.</p><br/>';
1070
  }
1071
  echo '<p>Old directories successfully removed.</p><br/>';
1072
- echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php">Return to Updraft Configuration</a>.';
1073
  return;
1074
  }
1075
 
 
 
 
 
 
 
 
1076
  if(isset($_GET['action']) && $_GET['action'] == 'updraft_create_backup_dir') {
1077
  if(!$this->create_backup_dir()) {
1078
  echo '<p>Backup directory could not be created...</p><br/>';
1079
  }
1080
  echo '<p>Backup directory successfully created.</p><br/>';
1081
- echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php">Return to Updraft Configuration</a>.';
1082
  return;
1083
  }
1084
 
1085
  if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup') {
1086
- wp_schedule_single_event(time()+3, 'updraft_backup');
1087
  }
1088
  if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_all') {
1089
- $this->backup();
1090
  }
1091
  if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_db') {
1092
  $this->backup_db();
@@ -1097,7 +1634,7 @@ ENDHERE;
1097
  <h2>UpdraftPlus - Backup/Restore</h2>
1098
 
1099
  Version: <b><?php echo $this->version; ?></b><br />
1100
- Maintained by <b>David Anderson</b> (<a href="http://david.dw-perspective.org.uk">Homepage</a> | <a href="http://wordshell.net">WordShell - WordPress command line</a> )
1101
  <br />
1102
  Based on Updraft by <b>Paul Kehrer</b> (<a href="http://langui.sh" target="_blank">Blog</a> | <a href="http://twitter.com/reaperhulk" target="_blank">Twitter</a> )
1103
  <br />
@@ -1136,10 +1673,12 @@ ENDHERE;
1136
  <tr>
1137
  <?php
1138
  $next_scheduled_backup = wp_next_scheduled('updraft_backup');
1139
- if($next_scheduled_backup) {
1140
- $next_scheduled_backup = date('D, F j, Y H:i T',$next_scheduled_backup);
 
 
1141
  } else {
1142
- $next_scheduled_backup = 'No backups are scheduled at this time.';
1143
  }
1144
  $current_time = date('D, F j, Y H:i T',time());
1145
  $updraft_last_backup = get_option('updraft_last_backup');
@@ -1162,16 +1701,20 @@ ENDHERE;
1162
  $backup_disabled = "";
1163
  } else {
1164
  $backup_disabled = 'disabled="disabled"';
1165
- $dir_info = '<span style="color:red">Backup directory specified is <b>not</b> writable. <span style="font-size:110%;font-weight:bold"><a href="options-general.php?page=updraft-backuprestore.php&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>';
1166
  }
1167
  ?>
1168
- <th>Current Time:</th>
1169
  <td style="color:blue"><?php echo $current_time?></td>
1170
  </tr>
1171
  <tr>
1172
- <th>Next Scheduled Backup:</th>
1173
  <td style="color:blue"><?php echo $next_scheduled_backup?></td>
1174
  </tr>
 
 
 
 
1175
  <tr>
1176
  <th>Last Backup:</th>
1177
  <td style="color:<?php echo $last_backup_color ?>"><?php echo $last_backup?></td>
@@ -1280,20 +1823,33 @@ ENDHERE;
1280
  <td></td><td><?php 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>
1281
  </tr>
1282
  <tr>
1283
- <th>Backup Intervals:</th>
1284
  <td><select name="updraft_interval">
1285
  <?php
1286
  $intervals = array ("manual", "daily", "weekly", "monthly");
1287
  foreach ($intervals as $ival) {
1288
  echo "<option value=\"$ival\" ";
1289
- if ($ival == get_option('updraft_interval')) { echo 'selected="selected"';}
 
 
 
 
 
 
 
 
 
 
 
 
 
1290
  echo ">".ucfirst($ival)."</option>\n";
1291
  }
1292
  ?>
1293
  </select></td>
1294
  </tr>
1295
  <tr class="backup-interval-description">
1296
- <td></td><td>If you would like to automatically schedule backups, choose a schedule from the dropdown above. Backups will occur at the interval specified starting five minutes after the current time. If you choose manual you must click the &quot;Backup Now!&quot; button to cause a backup to occur.</td>
1297
  </tr>
1298
  <?php
1299
  # The true (default value if non-existent) here has the effect of forcing a default of on.
@@ -1302,12 +1858,12 @@ ENDHERE;
1302
  $include_uploads = (get_option('updraft_include_uploads',true)) ? 'checked="checked"' : "";
1303
  ?>
1304
  <tr>
1305
- <th>Include in Backup:</th>
1306
  <td>
1307
  <input type="checkbox" name="updraft_include_plugins" value="1" <?php echo $include_plugins; ?> /> Plugins<br />
1308
  <input type="checkbox" name="updraft_include_themes" value="1" <?php echo $include_themes; ?> /> Themes<br />
1309
  <input type="checkbox" name="updraft_include_uploads" value="1" <?php echo $include_uploads; ?> /> Uploads<br />
1310
- Include all of these, unless you are backing them up separately. Note that presently UpdraftPlus backs up these directories only - which is usually everything (except for WordPress core itself which you can download afresh from WordPress.org). But if you have made customised modifications outside of these directories, you need to back them up another way. The database is always included.<br />(<a href="http://wordshell.net">Use WordShell</a> for automatic backup, version control and patching).<br /></td>
1311
  </td>
1312
  </tr>
1313
  <tr>
@@ -1319,10 +1875,10 @@ ENDHERE;
1319
  <td><input type="text" name="updraft_retain" value="<?php echo $retain ?>" style="width:50px" /></td>
1320
  </tr>
1321
  <tr class="backup-retain-description">
1322
- <td></td><td>By default only the most recent backup is retained. If you'd like to preserve more, specify the number here.</td>
1323
  </tr>
1324
  <tr>
1325
- <th>Encryption phrase:</th>
1326
  <?php
1327
  $updraft_encryptionphrase = get_option('updraft_encryptionphrase');
1328
  ?>
@@ -1340,28 +1896,37 @@ ENDHERE;
1340
  $debug_mode = (get_option('updraft_debug_mode')) ? 'checked="checked"' : "";
1341
 
1342
  $display_none = 'style="display:none"';
1343
- $s3 = ""; $ftp = ""; $email = "";
1344
  $email_display="";
1345
  $display_email_complete = "";
1346
  $set = 'selected="selected"';
1347
  switch(get_option('updraft_service')) {
1348
  case 's3':
1349
  $s3 = $set;
 
 
 
 
 
 
1350
  $ftp_display = $display_none;
1351
  break;
1352
  case 'ftp':
1353
  $ftp = $set;
 
1354
  $s3_display = $display_none;
1355
  break;
1356
  case 'email':
1357
  $email = $set;
1358
  $ftp_display = $display_none;
1359
  $s3_display = $display_none;
 
1360
  $display_email_complete = $display_none;
1361
  break;
1362
  default:
1363
  $none = $set;
1364
  $ftp_display = $display_none;
 
1365
  $s3_display = $display_none;
1366
  $display_delete_local = $display_none;
1367
  break;
@@ -1369,14 +1934,17 @@ ENDHERE;
1369
  ?>
1370
  <option value="none" <?php echo $none?>>None</option>
1371
  <option value="s3" <?php echo $s3?>>Amazon S3</option>
 
1372
  <option value="ftp" <?php echo $ftp?>>FTP</option>
1373
  <option value="email" <?php echo $email?>>E-mail</option>
1374
  </select></td>
1375
  </tr>
1376
  <tr class="backup-service-description">
1377
- <td></td><td>Choose which backup method you would like to employ. Be aware that email servers tend to have strict file size limitations and it is possible you will not receive your backup emails (>10MB is a typical threshold). Select none if you do not wish to send your backups anywhere. <b>Not recommended.</b></td>
1378
 
1379
  </tr>
 
 
1380
  <tr class="s3" <?php echo $s3_display?>>
1381
  <th>S3 access key:</th>
1382
  <td><input type="text" autocomplete="off" style="width:292px" name="updraft_s3_login" value="<?php echo get_option('updraft_s3_login') ?>" /></td>
@@ -1393,6 +1961,43 @@ ENDHERE;
1393
  <th></th>
1394
  <td><p>Get your access key and secret key from your AWS page, then pick a (globally unique) bucket name (letters and numbers) to use for storage. (Do not enter the s3:// prefix).</p></td>
1395
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1396
  <tr class="ftp" <?php echo $ftp_display?>>
1397
  <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Server:</a></th>
1398
  <td><input type="text" style="width:260px" name="updraft_server_address" value="<?php echo get_option('updraft_server_address'); ?>" /></td>
@@ -1418,7 +2023,7 @@ ENDHERE;
1418
  </tr>
1419
  <tr class="deletelocal s3 ftp email" <?php echo $display_delete_local?>>
1420
  <th>Delete local backup:</th>
1421
- <td><input type="checkbox" name="updraft_delete_local" value="1" <?php echo $delete_local; ?> /> <br />Check this to delete the local backup file after it has been sent off the server.</td>
1422
  </tr>
1423
  <tr>
1424
  <th>Debug mode:</th>
@@ -1459,19 +2064,23 @@ ENDHERE;
1459
  jQuery('#updraft-service').change(function() {
1460
  switch(jQuery(this).val()) {
1461
  case 'none':
1462
- jQuery('.deletelocal,.s3,.ftp,.s3-description,.ftp-description').hide()
1463
  jQuery('.email,.email-complete').show()
1464
  break;
1465
  case 's3':
1466
- jQuery('.ftp,.ftp-description').hide()
1467
  jQuery('.s3,.deletelocal,.email,.email-complete').show()
1468
  break;
 
 
 
 
1469
  case 'ftp':
1470
- jQuery('.s3,.s3-description').hide()
1471
  jQuery('.ftp,.deletelocal,.email,.email-complete').show()
1472
  break;
1473
  case 'email':
1474
- jQuery('.s3,.ftp,.s3-description,.ftp-description,.email-complete').hide()
1475
  jQuery('.email,.deletelocal').show()
1476
  break;
1477
  }
@@ -1538,6 +2147,9 @@ ENDHERE;
1538
  function show_admin_warning_accessible() {
1539
  $this->show_admin_warning("UpdraftPlus backup directory specified is accessible via the web. This is a potential security problem (people may be able to download your backups - which is undesirable if your database is not encrypted and if you have non-public assets amongst the files). If using Apache, enable .htaccess support to allow web access to be denied; otherwise, you should deny access manually.");
1540
  }
 
 
 
1541
  function show_admin_warning_accessible_unknownresult() {
1542
  $this->show_admin_warning("UpdraftPlus tried to check if the backup directory is accessible via web, but the result was unknown.");
1543
  }
2
  /*
3
  Plugin Name: UpdraftPlus - Backup/Restore
4
  Plugin URI: http://wordpress.org/extend/plugins/updraftplus
5
+ Description: Uploads, themes, plugins, and your DB can be automatically backed up to Amazon S3, Google Drive, FTP, or emailed. Files and DB can be on separate schedules.
6
  Author: David Anderson.
7
+ Version: 0.8.29
8
+ Donate link: http://david.dw-perspective.org.uk/donate
9
  Author URI: http://wordshell.net
10
  */
11
 
12
  //TODO:
 
13
  //Add DropBox support
 
14
  //Struggles with large uploads - runs out of time before finishing. Break into chunks? Resume download on later run? (Add a new scheduled event to check on progress? Separate the upload from the creation?). Add in some logging (in a .php file that exists first).
 
15
  //improve error reporting. s3 and dir backup have decent reporting now, but not sure i know what to do from here
 
16
  //list backups that aren't tracked (helps with double backup problem)
 
17
  //investigate $php_errormsg further
18
  //pretty up return messages in admin area
19
  //check s3/ftp download
 
 
 
20
 
21
  /* More TODO:
22
  Are all directories in wp-content covered? No; only plugins, themes, content. We should check for others and allow the user the chance to choose which ones he wants
 
23
  Use only one entry in WP options database
24
  Encrypt filesystem, if memory allows (and have option for abort if not); split up into multiple zips when needed
 
25
  */
26
 
27
  /* Portions copyright 2010 Paul Kehrer
54
 
55
  class UpdraftPlus {
56
 
57
+ var $version = '0.8.29';
58
 
59
  var $dbhandle;
60
  var $errors = array();
68
  # Create admin page
69
  add_action('admin_menu', array($this,'add_admin_pages'));
70
  add_action('admin_init', array($this,'admin_init'));
71
+ add_action('updraft_backup', array($this,'backup_files'));
72
+ add_action('updraft_backup_database', array($this,'backup_database'));
73
+ # backup_all is used by the manual "Backup Now" button
74
+ add_action('updraft_backup_all', array($this,'backup_all'));
75
  add_action('wp_ajax_updraft_download_backup', array($this, 'updraft_download_backup'));
76
  add_filter('cron_schedules', array($this,'modify_cron_schedules'));
77
  add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
78
+ add_action('init', array($this, 'googledrive_backup_auth'));
79
+ }
80
+
81
+ // Handle Google OAuth 2.0
82
+ function googledrive_backup_auth() {
83
+ if ( is_admin() && isset( $_GET['page'] ) && $_GET['page'] == 'updraftplus' && isset( $_GET['action'] ) && $_GET['action'] == 'auth' ) {
84
+ if ( isset( $_GET['state'] ) ) {
85
+ if ( $_GET['state'] == 'token' )
86
+ $this->auth_token();
87
+ elseif ( $_GET['state'] == 'revoke' )
88
+ $this->auth_revoke();
89
+ } elseif (isset($_GET['updraftplus_googleauth'])) {
90
+ $this->auth_request();
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Acquire single-use authorization code from Google OAuth 2.0
97
+ */
98
+ function auth_request() {
99
+ $params = array(
100
+ 'response_type' => 'code',
101
+ 'client_id' => get_option('updraft_googledrive_clientid'),
102
+ 'redirect_uri' => admin_url('options-general.php?page=updraftplus&action=auth'),
103
+ 'scope' => 'https://www.googleapis.com/auth/drive.file https://docs.google.com/feeds/ https://docs.googleusercontent.com/ https://spreadsheets.google.com/feeds/',
104
+ 'state' => 'token',
105
+ 'access_type' => 'offline',
106
+ 'approval_prompt' => 'auto'
107
+ );
108
+ header('Location: https://accounts.google.com/o/oauth2/auth?'.http_build_query($params));
109
+ }
110
+
111
+ /**
112
+ * Get a Google account access token using the refresh token
113
+ */
114
+ function access_token( $token, $client_id, $client_secret ) {
115
+ $context = array(
116
+ 'http' => array(
117
+ 'method' => 'POST',
118
+ 'header' => 'Content-type: application/x-www-form-urlencoded',
119
+ 'content' => http_build_query( array(
120
+ 'refresh_token' => $token,
121
+ 'client_id' => $client_id,
122
+ 'client_secret' => $client_secret,
123
+ 'grant_type' => 'refresh_token'
124
+ ) )
125
+ )
126
+ );
127
+ $this->log("Google Drive: requesting access token: client_id=$client_id");
128
+ $result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));
129
+ if($result) {
130
+ $result = json_decode( $result, true );
131
+ if ( isset( $result['access_token'] ) ) {
132
+ $this->log("Google Drive: successfully obtained access token");
133
+ return $result['access_token'];
134
+ } else {
135
+ $this->log("Google Drive error when requesting access token: response does not contain access_token");
136
+ return false;
137
+ }
138
+ } else {
139
+ $this->log("Google Drive error when requesting access token: no response");
140
+ return false;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Function to upload a file to Google Drive
146
+ *
147
+ * @param string $file Path to the file that is to be uploaded
148
+ * @param string $title Title to be given to the file
149
+ * @param string $parent ID of the folder in which to upload the file
150
+ * @param string $token Access token from Google Account
151
+ * @return boolean Returns TRUE on success, FALSE on failure
152
+ */
153
+ function googledrive_upload_file( $file, $title, $parent = '', $token) {
154
+
155
+ $size = filesize( $file );
156
+
157
+ $content = '<?xml version=\'1.0\' encoding=\'UTF-8\'?>
158
+ <entry xmlns="http://www.w3.org/2005/Atom" xmlns:docs="http://schemas.google.com/docs/2007">
159
+ <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/docs/2007#file"/>
160
+ <title>' . $title . '</title>
161
+ </entry>';
162
+
163
+ $header = array(
164
+ 'Authorization: Bearer ' . $token,
165
+ 'Content-Length: ' . strlen( $content ),
166
+ 'Content-Type: application/atom+xml',
167
+ 'X-Upload-Content-Type: application/octet-stream',
168
+ 'X-Upload-Content-Length: ' . $size,
169
+ 'GData-Version: 3.0'
170
+ );
171
+
172
+ $context = array(
173
+ 'http' => array(
174
+ 'ignore_errors' => true,
175
+ 'follow_location' => false,
176
+ 'method' => 'POST',
177
+ 'header' => join( "\r\n", $header ),
178
+ 'content' => $content
179
+ )
180
+ );
181
+
182
+ $url = $this->get_resumable_create_media_link( $token, $parent );
183
+ if ( $url ) {
184
+ $url .= '?convert=false'; // needed to upload a file
185
+ $this->log("Google Drive: resumable create media link: ".$url);
186
+ } else {
187
+ $this->log('Could not retrieve resumable create media link.', __FILE__, __LINE__ );
188
+ return false;
189
+ }
190
+
191
+ $result = @file_get_contents( $url, false, stream_context_create( $context ) );
192
+
193
+ if ( $result !== FALSE ) {
194
+ if ( strpos( $response = array_shift( $http_response_header ), '200' ) ) {
195
+ $response_header = array();
196
+ foreach ( $http_response_header as $header_line ) {
197
+ list( $key, $value ) = explode( ':', $header_line, 2 );
198
+ $response_header[trim( $key )] = trim( $value );
199
+ #$this->log("Google Drive: header: ".trim($key).": ".trim($value));
200
+ }
201
+ if ( isset( $response_header['Location'] ) ) {
202
+ $next_location = $response_header['Location'];
203
+ $pointer = 0;
204
+ # 1Mb
205
+ $max_chunk_size = 524288*2;
206
+ while ( $pointer < $size - 1 ) {
207
+ $this->log(basename($file).": Google Drive upload: pointer=$pointer (size=$size)");
208
+ $chunk = file_get_contents( $file, false, NULL, $pointer, $max_chunk_size );
209
+ $next_location = $this->upload_chunk( $next_location, $chunk, $pointer, $size, $token );
210
+ if( $next_location === false ) {
211
+ $this->log("Google Drive Upload: next_location is false (pointer: $pointer; chunk length: ".strlen($chunk).")");
212
+ return false;
213
+ }
214
+ $pointer += strlen( $chunk );
215
+ // if object it means we have our simpleXMLElement response
216
+ if ( is_object( $next_location ) ) {
217
+ // return resource Id
218
+ $this->log("Google Drive Upload: Success");
219
+ # Google Drive returns 501 not implemented for me for some reason instead of expected result...
220
+ #return substr( $next_location->children( "http://schemas.google.com/g/2005" )->resourceId, 5 );
221
+ return true;
222
+ }
223
+
224
+ }
225
+ }
226
+ }
227
+ else {
228
+ $this->log( 'Bad response: ' . $response . ' Response header: ' . var_export( $response_header, true ) . ' Response body: ' . $result . ' Request URL: ' . $url, __FILE__, __LINE__ );
229
+ return false;
230
+ }
231
+ }
232
+ else {
233
+ $this->log( 'Unable to request file from ' . $url, __FILE__, __LINE__ );
234
+ }
235
+
236
+ return true;
237
+
238
+ }
239
+
240
+ /**
241
+ * Get the resumable-create-media link needed to upload files
242
+ *
243
+ * @param string $token The Google Account access token
244
+ * @param string $parent The Id of the folder where the upload is to be made. Default is empty string.
245
+ * @return string|boolean Returns a link on success, FALSE on failure.
246
+ */
247
+ function get_resumable_create_media_link( $token, $parent = '' ) {
248
+ $header = array(
249
+ 'Authorization: Bearer ' . $token,
250
+ 'GData-Version: 3.0'
251
+ );
252
+ $context = array(
253
+ 'http' => array(
254
+ 'ignore_errors' => true,
255
+ 'method' => 'GET',
256
+ 'header' => join( "\r\n", $header )
257
+ )
258
+ );
259
+ $url = 'https://docs.google.com/feeds/default/private/full';
260
+
261
+ if ( $parent ) {
262
+ $url .= '/' . $parent;
263
+ }
264
+
265
+ $result = @file_get_contents( $url, false, stream_context_create( $context ) );
266
+
267
+ if ( $result !== false ) {
268
+ $xml = simplexml_load_string( $result );
269
+ if ( $xml === false ) {
270
+ $this->log( 'Could not create SimpleXMLElement from ' . $result, __FILE__, __LINE__ );
271
+ return false;
272
+ }
273
+ else {
274
+ foreach ( $xml->link as $link ) {
275
+ if ( $link['rel'] == 'http://schemas.google.com/g/2005#resumable-create-media' ) { return $link['href']; }
276
+ }
277
+ }
278
+ }
279
+ return false;
280
+ }
281
+
282
+
283
+ /**
284
+ * Handles the upload to Google Drive of a single chunk of a file
285
+ *
286
+ * @param string $location URL where the chunk needs to be uploaded
287
+ * @param string $chunk Part of the file to upload
288
+ * @param integer $pointer The byte number marking the beginning of the chunk in file
289
+ * @param integer $size The size of the file the chunk is part of, in bytes
290
+ * @param string $token Google Account access token
291
+ * @return string|boolean The funcion returns the location where the next chunk needs to be uploaded, TRUE if the last chunk was uploaded or FALSE on failure
292
+ */
293
+ function upload_chunk( $location, $chunk, $pointer, $size, $token ) {
294
+ $chunk_size = strlen( $chunk );
295
+ $bytes = (string)$pointer . '-' . (string)($pointer + $chunk_size - 1) . '/' . (string)$size;
296
+ $this->log("Google Drive chunk: location=$location, length=$chunk_size, range=$bytes");
297
+ $header = array(
298
+ 'Authorization: Bearer ' . $token,
299
+ 'Content-Length: ' . $chunk_size,
300
+ 'Content-Type: application/octet-stream',
301
+ 'Content-Range: bytes ' . $bytes,
302
+ 'GData-Version: 3.0'
303
+ );
304
+ $context = array(
305
+ 'http' => array(
306
+ 'ignore_errors' => true,
307
+ 'follow_location' => false,
308
+ 'method' => 'PUT',
309
+ 'header' => join( "\r\n", $header ),
310
+ 'content' => $chunk
311
+ )
312
+ );
313
+
314
+ $result = @file_get_contents( $location, false, stream_context_create( $context ) );
315
+
316
+ if ( isset( $http_response_header ) ) {
317
+ $response = array_shift( $http_response_header );
318
+ $headers = array();
319
+ foreach ( $http_response_header as $header_line ) {
320
+ list( $key, $value ) = explode( ':', $header_line, 2 );
321
+ $headers[trim( $key )] = trim( $value );
322
+ }
323
+
324
+ if ( strpos( $response, '308' ) ) {
325
+ if ( isset( $headers['Location'] ) ) {
326
+ $this->log('Google Drive: 308 response: '.$headers['Location']);
327
+ return $headers['Location'];
328
+ }
329
+ else {
330
+ $this->log('Google Drive 308 response: no location header: '.$location);
331
+ return $location;
332
+ }
333
+ }
334
+ elseif ( strpos( $response, '201' ) ) {
335
+ #$this->log("Google Drive response: ".$result);
336
+ $xml = simplexml_load_string( $result );
337
+ if ( $xml === false ) {
338
+ $this->log('ERROR: Could not create SimpleXMLElement from ' . $result, __FILE__, __LINE__ );
339
+ return false;
340
+ }
341
+ else {
342
+ return $xml;
343
+ }
344
+ }
345
+ else {
346
+ $this->log('ERROR: Bad response: ' . $response, __FILE__, __LINE__ );
347
+ return false;
348
+ }
349
+ }
350
+ else {
351
+ $this->log('ERROR: Received no response from ' . $location . ' while trying to upload bytes ' . $bytes );
352
+ return false;
353
+ }
354
+ }
355
+
356
+ function googledrive_delete_file( $file, $token) {
357
+ $this->log("Delete from Google Drive: $file: not yet implemented");
358
+ # TODO - somehow, turn this into a Gdata resource ID, then despatch it to googledrive_delete_file_byid
359
+ return;
360
+ }
361
+
362
+ /**
363
+ * Deletes a file from Google Drive
364
+ *
365
+ * @param string $id Gdata resource Id of the file to be deleted
366
+ * @param string $token Google Account access token
367
+ * @return boolean Returns TRUE on success, FALSE on failure
368
+ */
369
+ function googledrive_delete_file_byid( $id, $token ) {
370
+ $header = array(
371
+ 'If-Match: *',
372
+ 'Authorization: Bearer ' . $token,
373
+ 'GData-Version: 3.0'
374
+ );
375
+ $context = array(
376
+ 'http' => array(
377
+ 'method' => 'DELETE',
378
+ 'header' => join( "\r\n", $header )
379
+ )
380
+ );
381
+ stream_context_set_default( $context );
382
+ $headers = get_headers( 'https://docs.google.com/feeds/default/private/full/' . $id . '?delete=true',1 );
383
+
384
+ if ( strpos( $headers[0], '200' ) ) { return true; }
385
+ return false;
386
+ }
387
+
388
+ /**
389
+ * Get a Google account refresh token using the code received from auth_request
390
+ */
391
+ function auth_token() {
392
+ if( isset( $_GET['code'] ) ) {
393
+ $context = array(
394
+ 'http' => array(
395
+ 'timeout' => 30,
396
+ 'method' => 'POST',
397
+ 'header' => 'Content-type: application/x-www-form-urlencoded',
398
+ 'content' => http_build_query( array(
399
+ 'code' => $_GET['code'],
400
+ 'client_id' => get_option('updraft_googledrive_clientid'),
401
+ 'client_secret' => get_option('updraft_googledrive_secret'),
402
+ 'redirect_uri' => admin_url('options-general.php?page=updraftplus&action=auth'),
403
+ 'grant_type' => 'authorization_code'
404
+ ) )
405
+ )
406
+ );
407
+ $result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));
408
+ # Oddly, sometimes fails and then trying again works...
409
+ /*
410
+ if (!$result) { sleep(1); $result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));}
411
+ if (!$result) { sleep(1); $result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));}
412
+ */
413
+ if($result) {
414
+ $result = json_decode( $result, true );
415
+ if ( isset( $result['refresh_token'] ) ) {
416
+ update_option('updraft_googledrive_token',$result['refresh_token']); // Save token
417
+ header('Location: '.admin_url('options-general.php?page=updraftplus&message=' . __( 'Authorization was successful.', 'updraftplus' ) ) );
418
+ }
419
+ else {
420
+ header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'No refresh token was received!', 'updraftplus' ) ) );
421
+ }
422
+ } else {
423
+ header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'Bad response!', 'backup' ) ) );
424
+ }
425
+ }
426
+ else {
427
+ header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'Authorisation failed!', 'backup' ) ) );
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Revoke a Google account refresh token
433
+ */
434
+ function auth_revoke() {
435
+ @file_get_contents( 'https://accounts.google.com/o/oauth2/revoke?token=' . get_option('updraft_googledrive_token') );
436
+ update_option('updraft_googledrive_token','');
437
+ header( 'Location: '.admin_url( 'options-general.php?page=updraftplus&message=' . __( 'Authorization revoked.', 'backup' ) ) );
438
  }
439
 
440
  # Adds the settings link under the plugin on the plugin screen.
441
  function plugin_action_links($links, $file) {
442
  if ($file == plugin_basename(__FILE__)){
443
+ $settings_link = '<a href="'.site_url().'/wp-admin/options-general.php?page=updraftplus">'.__("Settings", "UpdraftPlus").'</a>';
444
+ array_unshift($links, $settings_link);
445
+ $settings_link = '<a href="http://david.dw-perspective.org.uk/donate">'.__("Donate","UpdraftPlus").'</a>';
446
  array_unshift($links, $settings_link);
447
  }
448
  return $links;
460
  }
461
  }
462
 
463
+ function backup_all() {
464
+ $this->backup(true,true);
465
+ }
466
+
467
+ function backup_files() {
468
+ # Note that the "false" for database gets over-ridden automatically if they turn out to have the same schedules
469
+ $this->backup(true,false);
470
+ }
471
+
472
+ function backup_database() {
473
+ # Note that nothing will happen if the file backup had the same schedule
474
+ $this->backup(false,true);
475
+ }
476
+
477
  //scheduled wp-cron events can have a race condition here if page loads are coming fast enough, but there's nothing we can do about it.
478
+ function backup($backup_files, $backup_database) {
479
+
480
  //generate backup information
481
  $this->backup_time_nonce();
482
 
483
+ //set log file name and open log file
484
  $updraft_dir = $this->backups_dir_location();
485
  $this->logfile_name = $updraft_dir. "/log." . $this->nonce . ".txt";
486
+ // Use append mode in case it already exists
 
487
  $this->logfile_handle = fopen($this->logfile_name, 'a');
488
+
489
+ // Log some information that may be helpful
490
  global $wp_version;
491
+ $this->log("PHP version: ".phpversion()." WordPress version: ".$wp_version." Backup files: $backup_files (schedule: ".get_option('updraft_interval','unset').") Backup DB: $backup_database (schedule: ".get_option('updraft_interval_database','unset').")");
492
+
493
+ # If the files and database schedules are the same, and if this the file one, then we rope in database too.
494
+ # On the other hand, if the schedules were the same and this was the database run, then there is nothing to do.
495
+ if (get_option('updraft_interval') == get_option('updraft_interval_database') || get_option('updraft_interval_database','xyz') == 'xyz' ) {
496
+ if ($backup_files == true)
497
+ { $backup_database = true; }
498
+ else
499
+ { $backup_database = false; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
500
  }
501
+
502
+ $this->log("Processed schedules. Tasks now: Backup files: $backup_files Backup DB: $backup_database");
503
+
504
+ # Possibly now nothing is to be done, except to close the log file
505
+ if ($backup_files || $backup_database) {
506
+
507
+ $backup_contains = "";
508
+
509
+ $backup_array = array();
510
+
511
+ //backup directories and return a numerically indexed array of file paths to the backup files
512
+ if ($backup_files) {
513
+ $this->log("Beginning backup of directories");
514
+ $backup_array = $this->backup_dirs();
515
+ $backup_contains = "Files only (no database)";
516
+ }
517
+
518
+ //backup DB and return string of file path
519
+ if ($backup_database) {
520
+ $this->log("Beginning backup of database");
521
+ $db_backup = $this->backup_db();
522
+ //add db path to rest of files
523
+ if(is_array($backup_array)) { $backup_array['db'] = $db_backup; }
524
+ $backup_contains = ($backup_files) ? "Files and database" : "Database only (no files)";
525
+ }
526
+
527
+ //save this to our history so we can track backups for the retain feature
528
+ $this->log("Saving backup history");
529
+ $this->save_backup_history($backup_array);
530
+
531
+ //cloud operations (S3,Google Drive,FTP,email,nothing)
532
+ //this also calls the retain feature at the end (done in this method to reuse existing cloud connections)
533
+ if(is_array($backup_array) && count($backup_array) >0) {
534
+ $this->log("Beginning dispatch of backup to remote");
535
+ $this->cloud_backup($backup_array);
536
+ }
537
+ //delete local files if the pref is set
538
+ foreach($backup_array as $file) {
539
+ $this->delete_local($file);
540
+ }
541
+
542
+ //save the last backup info, including errors, if any
543
+ $this->log("Saving last backup information into WordPress db");
544
+ $this->save_last_backup($backup_array);
545
+
546
+ if(get_option('updraft_email') != "" && get_option('updraft_service') != 'email') {
547
+ $sendmail_to = get_option('updraft_email');
548
+ $this->log("Sending email report to: ".$sendmail_to);
549
+ $append_log = "";
550
+ if(get_option('updraft_debug_mode') && $this->logfile_name != "") {
551
+ $append_log .= "\r\nLog contents:\r\n".file_get_contents($this->logfile_name);
552
+ }
553
+ wp_mail($sendmail_to,'Backed up: '.get_bloginfo('name').' (UpdraftPlus) '.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);
554
  }
 
555
  }
556
 
557
  // Close log file
572
  $this->log("Cloud backup: S3");
573
  if (count($backup_array) >0) { $this->s3_backup($backup_array); }
574
  break;
575
+ case 'googledrive':
576
+ @set_time_limit(900);
577
+ $this->log("Cloud backup: Google Drive");
578
+ if (count($backup_array) >0) { $this->googledrive_backup($backup_array); }
579
+ break;
580
  case 'ftp':
581
  @set_time_limit(900);
582
  $this->log("Cloud backup: FTP");
592
  }
593
  //we don't break here so it goes and executes all the default behavior below as well. this gives us retain behavior for email
594
  default:
595
+ $this->prune_retained_backups("local");
596
+ break;
597
+ }
598
+ }
599
+
600
+ // Carries out retain behaviour. Pass in a valid S3 or FTP object and path if relevant.
601
+ function prune_retained_backups($updraft_service,$remote_object,$remote_path) {
602
+ $this->log("Retain: beginning examination of existing backup sets");
603
+ $updraft_retain = get_option('updraft_retain');
604
+ // Number of backups to retain
605
+ $retain = (isset($updraft_retain))?get_option('updraft_retain'):1;
606
+ $this->log("Retain: user setting: number to retain = $retain");
607
+ // Returns an array, most recent first, of backup sets
608
+ $backup_history = $this->get_backup_history();
609
+ $db_backups_found = 0; $file_backups_found = 0;
610
+ $this->log("Number of backup sets in history: ".count($backup_history));
611
+ foreach ($backup_history as $backup_datestamp => $backup_to_examine) {
612
+ // $backup_to_examine is an array of file names, keyed on db/plugins/themes/uploads
613
+ // The new backup_history array is saved afterwards, so remember to unset the ones that are to be deleted
614
+ $this->log("Examining backup set with datestamp: $backup_datestamp");
615
+ if (isset($backup_to_examine['db'])) {
616
+ $db_backups_found++;
617
+ $this->log("$backup_datestamp: this set includes a database (".$backup_to_examine['db']."); db count is now $db_backups_found");
618
+ if ($db_backups_found > $retain) {
619
+ $this->log("$backup_datestamp: over retain limit; will delete this database");
620
+ $file = $backup_to_examine['db'];
621
+ $this->log("$backup_datestamp: Delete this file: $file");
622
+ if ($file != '') {
623
  $fullpath = trailingslashit(get_option('updraft_dir')).$file;
624
  @unlink($fullpath); //delete it if it's locally available
625
+ if ($updraft_service == "s3") {
626
+ $this->log("$backup_datestamp: Delete remote: s3://$remote_path/$file");
627
+ if (!$remote_object->deleteObject($remote_path, $file)) {
628
+ $this->error("S3 Error: Failed to delete object $file. Error was ".$php_errormsg);
629
+ }
630
+ } elseif ($updraft_service == "ftp") {
631
+ $this->log("$backup_datestamp: Delete remote ftp: $remote_path/$file");
632
+ @$remote_object->delete($remote_path.$file);
633
+ } elseif ($updraft_service == "googledrive") {
634
+ $this->log("$backup_datestamp: Delete remote file from Google Drive: $remote_path/$file");
635
+ $this->googledrive_delete_file($remote_path.'/'.$file,$remote_object);
636
+ }
637
  }
638
+ unset($backup_to_examine['db']);
639
  }
640
+ }
641
+ if (isset($backup_to_examine['plugins']) || isset($backup_to_examine['themes']) || isset($backup_to_examine['uploads'])) {
642
+ $file_backups_found++;
643
+ $this->log("$backup_datestamp: this set includes files; fileset count is now $file_backups_found");
644
+ if ($file_backups_found > $retain) {
645
+ $this->log("$backup_datestamp: over retain limit; will delete this file set");
646
+ $file = isset($backup_to_examine['plugins']) ? $backup_to_examine['plugins'] : "";
647
+ $file2 = isset($backup_to_examine['themes']) ? $backup_to_examine['themes'] : "";
648
+ $file3 = isset($backup_to_examine['uploads']) ? $backup_to_examine['uploads'] : "";
649
+ if ($file) {
650
+ $this->log("$backup_datestamp: Delete this file: $file");
651
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
652
+ @unlink($fullpath); //delete it if it's locally available
653
+ if ($updraft_service == "s3") {
654
+ $this->log("$backup_datestamp: Delete remote: s3://$remote_path/$file");
655
+ if (!$remote_object->deleteObject($bucket_name, $file)) {
656
+ $this->error("S3 Error: Failed to delete object $file. Error was ".$php_errormsg);
657
+ }
658
+ } elseif ($updraft_service == "ftp") {
659
+ $this->log("$backup_datestamp: Delete remote ftp: $remote_path/$file");
660
+ @$remote_object->delete($remote_path.$file);
661
+ } elseif ($updraft_service == "googledrive") {
662
+ $this->log("$backup_datestamp: Delete remote file from Google Drive: $remote_path/$file");
663
+ $this->googledrive_delete_file($remote_path.'/'.$file,$remote_object);
664
+ }
665
+ }
666
+ if ($file2) {
667
+ $this->log("$backup_datestamp: Delete this file: $file2");
668
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file2;
669
+ @unlink($fullpath); //delete it if it's locally available
670
+ if ($updraft_service == "s3") {
671
+ $this->log("$backup_datestamp: Delete remote: s3://$remote_path/$file2");
672
+ if (!$remote_object->deleteObject($bucket_name, $file2)) {
673
+ $this->error("S3 Error: Failed to delete object $file2. Error was ".$php_errormsg);
674
+ }
675
+ } elseif ($updraft_service == "ftp") {
676
+ $this->log("$backup_datestamp: Delete remote ftp: $remote_path/$file2");
677
+ @$remote_object->delete($remote_path.$file2);
678
+ } elseif ($updraft_service == "googledrive") {
679
+ $this->log("$backup_datestamp: Delete remote file from Google Drive: $remote_path/$file");
680
+ $this->googledrive_delete_file($remote_path.'/'.$file,$remote_object);
681
+ }
682
+
683
+ }
684
+ if ($file3) {
685
+ $this->log("$backup_datestamp: Delete this file: $file3");
686
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file3;
687
+ @unlink($fullpath); //delete it if it's locally available
688
+ if ($updraft_service == "s3") {
689
+ $this->log("$backup_datestamp: Delete remote: s3://$remote_path/$file3");
690
+ if (!$remote_object->deleteObject($bucket_name, $file3)) {
691
+ $this->error("S3 Error: Failed to delete object $file3. Error was ".$php_errormsg);
692
+ }
693
+ } elseif ($updraft_service == "ftp") {
694
+ $this->log("$backup_datestamp: Delete remote ftp: $remote_path/$file3");
695
+ @$remote_object->delete($remote_path.$file3);
696
+ } elseif ($updraft_service == "googledrive") {
697
+ $this->log("$backup_datestamp: Delete remote file from Google Drive: $remote_path/$file");
698
+ $this->googledrive_delete_file($remote_path.'/'.$file,$remote_object);
699
+ }
700
+ }
701
+ unset($backup_to_examine['plugins']);
702
+ unset($backup_to_examine['themes']);
703
+ unset($backup_to_examine['uploads']);
704
+ }
705
+ }
706
+ // Delete backup set completely if empty, o/w just remove DB
707
+ if (count($backup_to_examine)==0) {
708
+ $this->log("$backup_datestamp: this backup set is now empty; will remove from history");
709
+ unset($backup_history[$backup_datestamp]);
710
+ } else {
711
+ $this->log("$backup_datestamp: this backup set remains non-empty; will retain in history");
712
+ $backup_history[$backup_datestamp] = $backup_to_examine;
713
+ }
714
  }
715
+ $this->log("Retain: saving new backup history (sets now: ".count($backup_history).") and finishing retain operation");
716
+ update_option('updraft_backup_history',$backup_history);
717
  }
718
+
719
  function s3_backup($backup_array) {
720
  if(!class_exists('S3')) {
721
  require_once(dirname(__FILE__).'/includes/S3.php');
729
  $this->error("S3 Error: Failed to upload $fullpath. Error was ".$php_errormsg);
730
  }
731
  }
732
+ $this->prune_retained_backups('s3',$s3,$bucket_name);
733
  } else {
734
  $this->error("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
735
  }
736
+ }
737
+
738
+ function googledrive_backup($backup_array) {
739
+ if ( $access = $this->access_token( get_option('updraft_googledrive_token'), get_option('updraft_googledrive_clientid'), get_option('updraft_googledrive_secret') ) ) {
740
+ foreach ($backup_array as $file) {
741
+ $file_path = trailingslashit(get_option('updraft_dir')).$file;
742
+ $file_name = basename($file_path);
743
+ $this->log("$file_name: Attempting to upload to Google Drive");
744
+ $timer_start = microtime( true );
745
+ if ( $id = $this->googledrive_upload_file( $file_path, $file_name, get_option('updraft_googledrive_remotepath'), $access ) ) {
746
+ $this->log('OK: Archive ' . $file_name . ' uploaded to Google Drive in ' . ( round(microtime( true ) - $timer_start,2) ) . ' seconds' );
747
+ } else {
748
+ $this->log("ERROR: $file_name: Failed to upload to Google Drive" );
 
 
749
  }
750
  }
751
+ $this->prune_retained_backups("googledrive",$access,get_option('updraft_googledrive_remotepath'));
752
+ } else {
753
+ $this->log('ERROR: Did not receive an access token from Google', __FILE__, __LINE__ );
754
  }
 
 
755
  }
756
 
757
  function ftp_backup($backup_array) {
769
  $fullpath = trailingslashit(get_option('updraft_dir')).$file;
770
  $ftp->put($fullpath,$ftp_remote_path.$file,FTP_BINARY);
771
  }
772
+ $this->prune_retained_backups("ftp",$ftp,$ftp_remote_path);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
773
  }
774
 
775
  function delete_local($file) {
776
  if(get_option('updraft_delete_local')) {
777
+ $this->log("Deleting local file: $file");
778
+ //need error checking so we don't delete what isn't successfully uploaded?
779
  $fullpath = trailingslashit(get_option('updraft_dir')).$file;
780
  return unlink($fullpath);
781
  }
813
  # Plugins
814
  @set_time_limit(900);
815
  if (get_option('updraft_include_plugins', true)) {
816
+ $this->log("Beginning backup of plugins");
817
  $plugins = new PclZip($backup_file_base.'-plugins.zip');
818
  if (!$plugins->create($wp_plugins_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
819
  $this->error('Could not create plugins zip. Error was '.$php_errmsg,'fatal');
820
  }
821
  $backup_array['plugins'] = basename($backup_file_base.'-plugins.zip');
822
+ } else {
823
+ $this->log("No backup of plugins: excluded by user's options");
824
  }
825
+
826
  # Themes
827
  @set_time_limit(900);
828
  if (get_option('updraft_include_themes', true)) {
829
+ $this->log("Beginning backup of themes");
830
  $themes = new PclZip($backup_file_base.'-themes.zip');
831
  if (!$themes->create($wp_themes_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
832
  $this->error('Could not create themes zip. Error was '.$php_errmsg,'fatal');
833
  }
834
  $backup_array['themes'] = basename($backup_file_base.'-themes.zip');
835
+ } else {
836
+ $this->log("No backup of themes: excluded by user's options");
837
  }
838
 
839
  # Uploads
840
  @set_time_limit(900);
841
  if (get_option('updraft_include_uploads', true)) {
842
+ $this->log("Beginning backup of uploads");
843
  $uploads = new PclZip($backup_file_base.'-uploads.zip');
844
  if (!$uploads->create($wp_upload_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
845
  $this->error('Could not create uploads zip. Error was '.$php_errmsg,'fatal');
846
  }
847
  $backup_array['uploads'] = basename($backup_file_base.'-uploads.zip');
848
+ } else {
849
+ $this->log("No backup of uploads: excluded by user's options");
850
  }
 
851
  return $backup_array;
852
  }
853
 
854
  function save_backup_history($backup_array) {
855
  //this stores full paths right now. should probably concatenate with ABSPATH to make it easier to move sites
 
 
856
  if(is_array($backup_array)) {
857
+ $backup_history = get_option('updraft_backup_history');
858
+ $backup_history = (is_array($backup_history)) ? $backup_history : array();
859
  $backup_history[$this->backup_time] = $backup_array;
860
  update_option('updraft_backup_history',$backup_history);
861
  } else {
880
  /*START OF WB-DB-BACKUP BLOCK*/
881
 
882
  function backup_db() {
883
+
884
+ $total_tables = 0;
885
+
886
  global $table_prefix, $wpdb;
887
  if(!$this->backup_time) {
888
  $this->backup_time_nonce();
913
  //$this->error(__('The backup directory is not writable!','wp-db-backup'));
914
  }
915
 
 
916
  //Begin new backup of MySql
917
  $this->stow("# " . __('WordPress MySQL database backup','wp-db-backup') . "\n");
918
  $this->stow("#\n");
930
  }
931
 
932
  foreach ($all_tables as $table) {
933
+ $total_tables++;
934
  // Increase script execution time-limit to 15 min for every table.
935
  if ( !ini_get('safe_mode')) @set_time_limit(15*60);
936
  if ( strpos($table, $table_prefix) == 0 ) {
960
  # Encrypt, if requested
961
  $encryption = get_option('updraft_encryptionphrase');
962
  if (strlen($encryption) > 0) {
963
+ $this->log("Database: applying encryption");
964
  $encryption_error = 0;
965
  require_once(dirname(__FILE__).'/includes/Rijndael.php');
966
  $rijndael = new Crypt_Rijndael();
985
  return basename($backup_file_base.'-db.gz');
986
  }
987
  }
988
+ $this->log("Total database tables backed up: $total_tables");
989
 
990
  } //wp_db_backup
991
 
1002
  function backup_table($table, $segment = 'none') {
1003
  global $wpdb;
1004
 
1005
+ $total_rows = 0;
1006
+
1007
  $table_structure = $wpdb->get_results("DESCRIBE $table");
1008
  if (! $table_structure) {
1009
  //$this->error(__('Error getting table details','wp-db-backup') . ": $table");
1093
  $replace = array('\0', '\n', '\r', '\Z');
1094
  if($table_data) {
1095
  foreach ($table_data as $row) {
1096
+ $total_rows++;
1097
  $values = array();
1098
  foreach ($row as $key => $value) {
1099
  if ($ints[strtolower($key)]) {
1120
  $this->stow("# --------------------------------------------------------\n");
1121
  $this->stow("\n");
1122
  }
1123
+ $this->log("Table $table: Total rows added: $total_rows");
1124
+
1125
  } // end backup_table()
1126
 
1127
 
1159
  return true;
1160
  }
1161
 
 
 
1162
  /**
1163
  * Add backquotes to tables and db-names in
1164
  * SQL queries. Taken from phpMyAdmin.
1189
  return str_replace('\'', '\\\'', $a_string);
1190
  }
1191
 
 
 
 
 
1192
  /*END OF WP-DB-BACKUP BLOCK */
1193
 
1194
  /*
1210
  return wp_filter_nohtml_kses($interval);
1211
  }
1212
 
1213
+ function schedule_backup_database($interval) {
1214
+ //clear schedule and add new so we don't stack up scheduled backups
1215
+ wp_clear_scheduled_hook('updraft_backup_database');
1216
+ switch($interval) {
1217
+ case 'daily':
1218
+ case 'weekly':
1219
+ case 'monthly':
1220
+ wp_schedule_event(time()+30, $interval, 'updraft_backup_database');
1221
+ break;
1222
+ }
1223
+ return wp_filter_nohtml_kses($interval);
1224
+ }
1225
+
1226
  //wp-cron only has hourly, daily and twicedaily, so we need to add weekly and monthly.
1227
  function modify_cron_schedules($schedules) {
1228
  $schedules['weekly'] = array(
1303
  $this->delete_local($file);
1304
  exit; //we exit immediately because otherwise admin-ajax appends an additional zero to the end for some reason I don't understand. seriously, why die('0')?
1305
  } else {
1306
+ echo 'Download failed. File '.$fullpath.' did not exist or was unreadable. If you delete local backups then S3 or Google Drive or FTP retrieval may have failed. (Note that Google Drive downloading is not yet supported - you need to download manually if you use Google Drive).';
1307
  }
1308
  }
1309
 
1310
  function download_backup($file) {
1311
  switch(get_option('updraft_service')) {
1312
+ case 'googledrive':
1313
+ $this->download_googledrive_backup($file);
1314
+ break;
1315
  case 's3':
1316
  $this->download_s3_backup($file);
1317
  break;
1323
  }
1324
  }
1325
 
1326
+ function download_googledrive_backup($file) {
1327
+ $this->error("Google Drive error: we do not yet support downloading existing backups from Google Drive - you need to restore the backup manually");
1328
+ }
1329
+
1330
  function download_s3_backup($file) {
1331
  if(!class_exists('S3')) {
1332
  require_once(dirname(__FILE__).'/includes/S3.php');
1366
  return false;
1367
  }
1368
 
1369
+ $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_restore&backup_timestamp=$timestamp");
1370
  WP_Filesystem($credentials);
1371
  if ( $wp_filesystem->errors->get_error_code() ) {
1372
  foreach ( $wp_filesystem->errors->get_error_messages() as $message )
1405
  return true;
1406
  }
1407
 
 
1408
  //deletes the -old directories that are created when a backup is restored.
1409
  function delete_old_dirs() {
1410
  global $wp_filesystem;
1411
+ $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_delete_old_dirs");
1412
  WP_Filesystem($credentials);
1413
  if ( $wp_filesystem->errors->get_error_code() ) {
1414
  foreach ( $wp_filesystem->errors->get_error_messages() as $message )
1450
 
1451
  function create_backup_dir() {
1452
  global $wp_filesystem;
1453
+ $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_create_backup_dir");
1454
  WP_Filesystem($credentials);
1455
  if ( $wp_filesystem->errors->get_error_code() ) {
1456
  foreach ( $wp_filesystem->errors->get_error_messages() as $message )
1506
  }
1507
  wp_enqueue_script('jquery');
1508
  register_setting( 'updraft-options-group', 'updraft_interval', array($this,'schedule_backup') );
1509
+ register_setting( 'updraft-options-group', 'updraft_interval_database', array($this,'schedule_backup_database') );
1510
  register_setting( 'updraft-options-group', 'updraft_retain', array($this,'retain_range') );
1511
  register_setting( 'updraft-options-group', 'updraft_encryptionphrase', 'wp_filter_nohtml_kses' );
1512
  register_setting( 'updraft-options-group', 'updraft_service', 'wp_filter_nohtml_kses' );
1513
  register_setting( 'updraft-options-group', 'updraft_s3_login', 'wp_filter_nohtml_kses' );
1514
  register_setting( 'updraft-options-group', 'updraft_s3_pass', 'wp_filter_nohtml_kses' );
1515
+ register_setting( 'updraft-options-group', 'updraft_s3_remote_path', 'wp_filter_nohtml_kses' );
1516
+ register_setting( 'updraft-options-group', 'updraft_googledrive_clientid', 'wp_filter_nohtml_kses' );
1517
+ register_setting( 'updraft-options-group', 'updraft_googledrive_secret', 'wp_filter_nohtml_kses' );
1518
+ register_setting( 'updraft-options-group', 'updraft_googledrive_remotepath', 'wp_filter_nohtml_kses' );
1519
  register_setting( 'updraft-options-group', 'updraft_ftp_login', 'wp_filter_nohtml_kses' );
1520
  register_setting( 'updraft-options-group', 'updraft_ftp_pass', 'wp_filter_nohtml_kses' );
1521
  register_setting( 'updraft-options-group', 'updraft_dir', 'wp_filter_nohtml_kses' );
1522
  register_setting( 'updraft-options-group', 'updraft_email', 'wp_filter_nohtml_kses' );
 
1523
  register_setting( 'updraft-options-group', 'updraft_ftp_remote_path', 'wp_filter_nohtml_kses' );
1524
  register_setting( 'updraft-options-group', 'updraft_server_address', 'wp_filter_nohtml_kses' );
1525
  register_setting( 'updraft-options-group', 'updraft_delete_local', 'absint' );
1528
  register_setting( 'updraft-options-group', 'updraft_include_themes', 'absint' );
1529
  register_setting( 'updraft-options-group', 'updraft_include_uploads', 'absint' );
1530
 
1531
+ /* I see no need for this check; people can only download backups/logs if they can guess a nonce formed from a random number and if .htaccess files have no effect. The database will be encrypted. Very unlikely.
1532
  if (current_user_can('manage_options')) {
1533
  $updraft_dir = $this->backups_dir_location();
1534
  if(strpos($updraft_dir,WP_CONTENT_DIR) !== false) {
1542
  add_action('admin_notices', array($this,'show_admin_warning_accessible') );
1543
  }
1544
  }
 
 
1545
  }
1546
  }
1547
+ */
1548
+ if (current_user_can('manage_options') && get_option('updraft_googledrive_clientid') != "" && get_option('updraft_googledrive_token','xyz') == 'xyz') {
1549
+ add_action('admin_notices', array($this,'show_admin_warning_googledrive') );
1550
+ }
1551
  }
 
1552
 
1553
  function add_admin_pages() {
1554
+ add_submenu_page('options-general.php', "UpdraftPlus", "UpdraftPlus", "manage_options", "updraftplus",
1555
  array($this,"settings_output"));
1556
  }
1557
 
1581
  $backup_success = $this->restore_backup($_REQUEST['backup_timestamp']);
1582
  if(empty($this->errors) && $backup_success == true) {
1583
  echo '<p>Restore successful!</p><br/>';
1584
+ echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus&updraft_restore_success=true">Return to Updraft Configuration</a>.';
1585
  return;
1586
  } else {
1587
  echo '<p>Restore failed...</p><br/>';
1588
+ echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1589
  return;
1590
  }
1591
  //uncomment the below once i figure out how i want the flow of a restoration to work.
1592
+ //echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1593
  }
1594
  $deleted_old_dirs = false;
1595
  if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_delete_old_dirs') {
1599
  echo '<p>Old directory removal failed for some reason. You may want to do this manually.</p><br/>';
1600
  }
1601
  echo '<p>Old directories successfully removed.</p><br/>';
1602
+ echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1603
  return;
1604
  }
1605
 
1606
+ if(isset($_GET['error'])) {
1607
+ echo "<p><strong>ERROR:</strong> ".htmlspecialchars($_GET['error'])."</p>";
1608
+ }
1609
+ if(isset($_GET['message'])) {
1610
+ echo "<p><strong>Note:</strong> ".htmlspecialchars($_GET['message'])."</p>";
1611
+ }
1612
+
1613
  if(isset($_GET['action']) && $_GET['action'] == 'updraft_create_backup_dir') {
1614
  if(!$this->create_backup_dir()) {
1615
  echo '<p>Backup directory could not be created...</p><br/>';
1616
  }
1617
  echo '<p>Backup directory successfully created.</p><br/>';
1618
+ echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1619
  return;
1620
  }
1621
 
1622
  if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup') {
1623
+ wp_schedule_single_event(time()+5, 'updraft_backup_all');
1624
  }
1625
  if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_all') {
1626
+ $this->backup(true,true);
1627
  }
1628
  if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_db') {
1629
  $this->backup_db();
1634
  <h2>UpdraftPlus - Backup/Restore</h2>
1635
 
1636
  Version: <b><?php echo $this->version; ?></b><br />
1637
+ Maintained by <b>David Anderson</b> (<a href="http://david.dw-perspective.org.uk">Homepage</a> | <a href="http://wordshell.net">WordShell - WordPress command line</a> | <a href="http://david.dw-perspective.org.uk/donate">Donate</a>)
1638
  <br />
1639
  Based on Updraft by <b>Paul Kehrer</b> (<a href="http://langui.sh" target="_blank">Blog</a> | <a href="http://twitter.com/reaperhulk" target="_blank">Twitter</a> )
1640
  <br />
1673
  <tr>
1674
  <?php
1675
  $next_scheduled_backup = wp_next_scheduled('updraft_backup');
1676
+ $next_scheduled_backup = ($next_scheduled_backup) ? date('D, F j, Y H:i T',$next_scheduled_backup) : 'No backups are scheduled at this time.';
1677
+ $next_scheduled_backup_database = wp_next_scheduled('updraft_backup_database');
1678
+ if (get_option('updraft_interval_database',get_option('updraft_interval')) == get_option('updraft_interval')) {
1679
+ $next_scheduled_backup_database = "Will take place at the same time as the files backup.";
1680
  } else {
1681
+ $next_scheduled_backup_database = ($next_scheduled_backup_database) ? date('D, F j, Y H:i T',$next_scheduled_backup_database) : 'No backups are scheduled at this time.';
1682
  }
1683
  $current_time = date('D, F j, Y H:i T',time());
1684
  $updraft_last_backup = get_option('updraft_last_backup');
1701
  $backup_disabled = "";
1702
  } else {
1703
  $backup_disabled = 'disabled="disabled"';
1704
+ $dir_info = '<span style="color:red">Backup directory specified is <b>not</b> writable. <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>';
1705
  }
1706
  ?>
1707
+ <th>Now:</th>
1708
  <td style="color:blue"><?php echo $current_time?></td>
1709
  </tr>
1710
  <tr>
1711
+ <th>Next Scheduled Files Backup:</th>
1712
  <td style="color:blue"><?php echo $next_scheduled_backup?></td>
1713
  </tr>
1714
+ <tr>
1715
+ <th>Next Scheduled DB Backup:</th>
1716
+ <td style="color:blue"><?php echo $next_scheduled_backup_database?></td>
1717
+ </tr>
1718
  <tr>
1719
  <th>Last Backup:</th>
1720
  <td style="color:<?php echo $last_backup_color ?>"><?php echo $last_backup?></td>
1823
  <td></td><td><?php 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>
1824
  </tr>
1825
  <tr>
1826
+ <th>File Backup Intervals:</th>
1827
  <td><select name="updraft_interval">
1828
  <?php
1829
  $intervals = array ("manual", "daily", "weekly", "monthly");
1830
  foreach ($intervals as $ival) {
1831
  echo "<option value=\"$ival\" ";
1832
+ if ($ival == get_option('updraft_interval','manual')) { echo 'selected="selected"';}
1833
+ echo ">".ucfirst($ival)."</option>\n";
1834
+ }
1835
+ ?>
1836
+ </select></td>
1837
+ </tr>
1838
+ <tr>
1839
+ <th>Database Backup Intervals:</th>
1840
+ <td><select name="updraft_interval_database">
1841
+ <?php
1842
+ $intervals = array ("manual", "daily", "weekly", "monthly");
1843
+ foreach ($intervals as $ival) {
1844
+ echo "<option value=\"$ival\" ";
1845
+ if ($ival == get_option('updraft_interval_database',get_option('updraft_interval'))) { echo 'selected="selected"';}
1846
  echo ">".ucfirst($ival)."</option>\n";
1847
  }
1848
  ?>
1849
  </select></td>
1850
  </tr>
1851
  <tr class="backup-interval-description">
1852
+ <td></td><td>If you would like to automatically schedule backups, choose schedules from the dropdown above. Backups will occur at the interval specified starting just after the current time. If you choose manual you must click the &quot;Backup Now!&quot; button whenever you wish a backup to occur. If the two schedules are the same, then the two backups will take place together.</td>
1853
  </tr>
1854
  <?php
1855
  # The true (default value if non-existent) here has the effect of forcing a default of on.
1858
  $include_uploads = (get_option('updraft_include_uploads',true)) ? 'checked="checked"' : "";
1859
  ?>
1860
  <tr>
1861
+ <th>Include in Files Backup:</th>
1862
  <td>
1863
  <input type="checkbox" name="updraft_include_plugins" value="1" <?php echo $include_plugins; ?> /> Plugins<br />
1864
  <input type="checkbox" name="updraft_include_themes" value="1" <?php echo $include_themes; ?> /> Themes<br />
1865
  <input type="checkbox" name="updraft_include_uploads" value="1" <?php echo $include_uploads; ?> /> Uploads<br />
1866
+ Include all of these, unless you are backing them up separately. Note that presently UpdraftPlus backs up these directories only - which is usually everything (except for WordPress core itself which you can download afresh from WordPress.org). But if you have made customised modifications outside of these directories, you need to back them up another way.<br />(<a href="http://wordshell.net">Use WordShell</a> for automatic backup, version control and patching).<br /></td>
1867
  </td>
1868
  </tr>
1869
  <tr>
1875
  <td><input type="text" name="updraft_retain" value="<?php echo $retain ?>" style="width:50px" /></td>
1876
  </tr>
1877
  <tr class="backup-retain-description">
1878
+ <td></td><td>By default only the most recent backup is retained. If you'd like to preserve more, specify the number here. (This many of <strong>both</strong> files and database backups will be retained.)</td>
1879
  </tr>
1880
  <tr>
1881
+ <th>Database encryption phrase:</th>
1882
  <?php
1883
  $updraft_encryptionphrase = get_option('updraft_encryptionphrase');
1884
  ?>
1896
  $debug_mode = (get_option('updraft_debug_mode')) ? 'checked="checked"' : "";
1897
 
1898
  $display_none = 'style="display:none"';
1899
+ $s3 = ""; $ftp = ""; $email = ""; $googledrive="";
1900
  $email_display="";
1901
  $display_email_complete = "";
1902
  $set = 'selected="selected"';
1903
  switch(get_option('updraft_service')) {
1904
  case 's3':
1905
  $s3 = $set;
1906
+ $googledrive_display = $display_none;
1907
+ $ftp_display = $display_none;
1908
+ break;
1909
+ case 'googledrive':
1910
+ $googledrive = $set;
1911
+ $s3_display = $display_none;
1912
  $ftp_display = $display_none;
1913
  break;
1914
  case 'ftp':
1915
  $ftp = $set;
1916
+ $googledrive_display = $display_none;
1917
  $s3_display = $display_none;
1918
  break;
1919
  case 'email':
1920
  $email = $set;
1921
  $ftp_display = $display_none;
1922
  $s3_display = $display_none;
1923
+ $googledrive_display = $display_none;
1924
  $display_email_complete = $display_none;
1925
  break;
1926
  default:
1927
  $none = $set;
1928
  $ftp_display = $display_none;
1929
+ $googledrive_display = $display_none;
1930
  $s3_display = $display_none;
1931
  $display_delete_local = $display_none;
1932
  break;
1934
  ?>
1935
  <option value="none" <?php echo $none?>>None</option>
1936
  <option value="s3" <?php echo $s3?>>Amazon S3</option>
1937
+ <option value="googledrive" <?php echo $googledrive?>>Google Drive (experimental, may work for you, may not)</option>
1938
  <option value="ftp" <?php echo $ftp?>>FTP</option>
1939
  <option value="email" <?php echo $email?>>E-mail</option>
1940
  </select></td>
1941
  </tr>
1942
  <tr class="backup-service-description">
1943
+ <td></td><td>Choose your backup method. Be aware that mail servers tend to have strict file size limitations; typically around 10-20Mb; backups larger than this may not arrive. Select none if you do not wish to send your backups anywhere <b>(not recommended)</b>.</td>
1944
 
1945
  </tr>
1946
+
1947
+ <!-- Amazon S3 -->
1948
  <tr class="s3" <?php echo $s3_display?>>
1949
  <th>S3 access key:</th>
1950
  <td><input type="text" autocomplete="off" style="width:292px" name="updraft_s3_login" value="<?php echo get_option('updraft_s3_login') ?>" /></td>
1961
  <th></th>
1962
  <td><p>Get your access key and secret key from your AWS page, then pick a (globally unique) bucket name (letters and numbers) to use for storage. (Do not enter the s3:// prefix).</p></td>
1963
  </tr>
1964
+
1965
+ <!-- Google Drive -->
1966
+ <tr class="googledrive" <?php echo $googledrive_display?>>
1967
+ <th>Google Drive Client ID:</th>
1968
+ <td><input type="text" autocomplete="off" style="width:332px" name="updraft_googledrive_clientid" value="<?php echo get_option('updraft_googledrive_clientid') ?>" /></td>
1969
+ </tr>
1970
+ <tr class="googledrive" <?php echo $googledrive_display?>>
1971
+ <th>Google Drive Client Secret:</th>
1972
+ <td><input type="password" autocomplete="off" style="width:332px" name="updraft_googledrive_secret" value="<?php echo get_option('updraft_googledrive_secret'); ?>" /></td>
1973
+ </tr>
1974
+ <tr class="googledrive" <?php echo $googledrive_display?>>
1975
+ <th>Google Drive Folder ID:</th>
1976
+ <td><input type="text" style="width:332px" name="updraft_googledrive_remotepath" value="<?php echo get_option('updraft_googledrive_remotepath'); ?>" /> <em>(Leave empty to use your root folder)</em></td>
1977
+ </tr>
1978
+ <tr class="googledrive" <?php echo $googledrive_display?>>
1979
+ <th>Authenticate with Google:</th>
1980
+ <td><p><a href="?page=updraftplus&action=auth&updraftplus_googleauth=doit"><strong>After</strong> you have saved your settings (by clicking &quot;Save Changes&quot; below), then come back here once and click this link to complete authentication with Google.</a>
1981
+
1982
+ <?php
1983
+ if (get_option('updraft_googledrive_token','xyz') != 'xyz') {
1984
+ echo " (You appear to be already authenticated)";
1985
+ }
1986
+ ?>
1987
+ </p>
1988
+ <p>To get a folder's ID navigate to that folder in Google Drive and copy the ID from your browser's address bar. It is the part that comes after <kbd>#folders/.</kbd></p>
1989
+ <p><strong>N.B. : If you choose Google Drive, then no backups will be deleted - all will be retained. Patches welcome!</strong></p>
1990
+ </td>
1991
+ </tr>
1992
+ <tr class="googledrive" <?php echo $googledrive_display?>>
1993
+ <th></th>
1994
+ <td><p>Create a Client ID in the API Access section of your <a href="https://code.google.com/apis/console/">Google API Console</a>. Select 'Web Application' as the application type.</p><p>You must add <kbd><?php echo admin_url('options-general.php?page=updraftplus&action=auth'); ?></kbd> as the authorised redirect URI when asked.</p>
1995
+ <?php
1996
+ if (!class_exists('SimpleXMLElement')) { echo "<p><b>WARNING:</b> You do not have SimpleXMLElement installed. Google Drive backups will <b>not</b> work until you do.</p>"; }
1997
+ ?>
1998
+ </td>
1999
+ </tr>
2000
+
2001
  <tr class="ftp" <?php echo $ftp_display?>>
2002
  <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Server:</a></th>
2003
  <td><input type="text" style="width:260px" name="updraft_server_address" value="<?php echo get_option('updraft_server_address'); ?>" /></td>
2023
  </tr>
2024
  <tr class="deletelocal s3 ftp email" <?php echo $display_delete_local?>>
2025
  <th>Delete local backup:</th>
2026
+ <td><input type="checkbox" name="updraft_delete_local" value="1" <?php echo $delete_local; ?> /> <br />Check this to delete the local backup file (only sensible if you have enabled a remote backup, otherwise you will have no backup remaining).</td>
2027
  </tr>
2028
  <tr>
2029
  <th>Debug mode:</th>
2064
  jQuery('#updraft-service').change(function() {
2065
  switch(jQuery(this).val()) {
2066
  case 'none':
2067
+ jQuery('.deletelocal,.s3,.ftp,.googledrive,.s3-description,.ftp-description').hide()
2068
  jQuery('.email,.email-complete').show()
2069
  break;
2070
  case 's3':
2071
+ jQuery('.ftp,.ftp-description,.googledrive').hide()
2072
  jQuery('.s3,.deletelocal,.email,.email-complete').show()
2073
  break;
2074
+ case 'googledrive':
2075
+ jQuery('.ftp,.ftp-description,.s3').hide()
2076
+ jQuery('.googledrive,.deletelocal,.googledrive,.email,.email-complete').show()
2077
+ break;
2078
  case 'ftp':
2079
+ jQuery('.googledrive,.s3,.s3-description').hide()
2080
  jQuery('.ftp,.deletelocal,.email,.email-complete').show()
2081
  break;
2082
  case 'email':
2083
+ jQuery('.s3,.ftp,.s3-description,.googledrive,.ftp-description,.email-complete').hide()
2084
  jQuery('.email,.deletelocal').show()
2085
  break;
2086
  }
2147
  function show_admin_warning_accessible() {
2148
  $this->show_admin_warning("UpdraftPlus backup directory specified is accessible via the web. This is a potential security problem (people may be able to download your backups - which is undesirable if your database is not encrypted and if you have non-public assets amongst the files). If using Apache, enable .htaccess support to allow web access to be denied; otherwise, you should deny access manually.");
2149
  }
2150
+ function show_admin_warning_googledrive() {
2151
+ $this->show_admin_warning('UpdraftPlus notice: <a href="?page=updraftplus&action=auth&updraftplus_googleauth=doit">Click here to authenticate your Google Drive account (you will not be able to back up to Google Drive without it).</a>');
2152
+ }
2153
  function show_admin_warning_accessible_unknownresult() {
2154
  $this->show_admin_warning("UpdraftPlus tried to check if the backup directory is accessible via web, but the result was unknown.");
2155
  }