UpdraftPlus WordPress Backup Plugin - Version 1.0.20

Version Description

  • 12/29/2012 =
  • Big code re-factoring; moving towards modularising cloud access methods
Download this release

Release Info

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

Code changes from version 1.0.18 to 1.0.20

Files changed (6) hide show
  1. methods/email.php +15 -7
  2. methods/ftp.php +68 -32
  3. methods/googledrive.php +285 -138
  4. methods/s3.php +146 -88
  5. readme.txt +2 -2
  6. updraftplus.php +103 -334
methods/email.php CHANGED
@@ -2,17 +2,25 @@
2
 
3
  // Files can easily get way too big for this method
4
 
5
- function updraftplus_email_backup($backup_array) {
6
 
7
- global $updraftplus;
8
 
9
- foreach ($backup_array as $type => $file) {
10
- $fullpath = trailingslashit(get_option('updraft_dir')).$file;
11
- wp_mail(get_option('updraft_email'), "WordPress Backup ".date('Y-m-d H:i',$updraftplus->backup_time), "Backup is of the $type. Be wary; email backups may fail because of file size limitations on mail servers.", null, array($fullpath));
12
- $updraftplus->uploaded_file($file);
 
 
 
 
 
 
 
 
 
13
  }
14
 
15
- $updraftplus->prune_retained_backups("local");
16
  }
17
 
18
  ?>
2
 
3
  // Files can easily get way too big for this method
4
 
5
+ class UpdraftPlus_BackupModule_email {
6
 
7
+ function backup($backup_array) {
8
 
9
+ global $updraftplus;
10
+
11
+ foreach ($backup_array as $type => $file) {
12
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
13
+ wp_mail(get_option('updraft_email'), "WordPress Backup ".date('Y-m-d H:i',$updraftplus->backup_time), "Backup is of the $type. Be wary; email backups may fail because of file size limitations on mail servers.", null, array($fullpath));
14
+ $updraftplus->uploaded_file($file);
15
+ }
16
+
17
+ $updraftplus->prune_retained_backups("local");
18
+ }
19
+
20
+ function config_print() {
21
+ return false;
22
  }
23
 
 
24
  }
25
 
26
  ?>
methods/ftp.php CHANGED
@@ -1,44 +1,80 @@
1
  <?php
2
 
3
- function updraftplus_ftp_backup($backup_array) {
4
 
5
- global $updraftplus;
6
 
7
- if( !class_exists('ftp_wrapper')) require_once(UPDRAFTPLUS_DIR.'/includes/ftp.class.php');
8
 
9
- //handle SSL and errors at some point TODO
10
- $ftp = new ftp_wrapper(get_option('updraft_server_address'),get_option('updraft_ftp_login'),get_option('updraft_ftp_pass'));
11
- $ftp->passive = true;
12
- $ftp->connect();
13
- //$ftp->make_dir(); we may need to recursively create dirs? TODO
14
-
15
- $ftp_remote_path = trailingslashit(get_option('updraft_ftp_remote_path'));
16
- foreach($backup_array as $file) {
17
- $fullpath = trailingslashit(get_option('updraft_dir')).$file;
18
- if ($ftp->put($fullpath,$ftp_remote_path.$file,FTP_BINARY)) {
19
- $updraftplus->log("ERROR: $file_name: Successfully uploaded via FTP");
20
- $updraftplus->uploaded_file($file);
21
- } else {
22
- $updraftplus->error("$file_name: Failed to upload to FTP" );
23
- $updraftplus->log("ERROR: $file_name: Failed to upload to FTP" );
 
 
 
24
  }
 
 
25
  }
26
 
27
- $updraftplus->prune_retained_backups("ftp",$ftp,$ftp_remote_path);
28
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
- function updraftplus_download_ftp_backup($file) {
31
- if( !class_exists('ftp_wrapper')) require_once(UPDRAFTPLUS_DIR.'/includes/ftp.class.php');
32
-
33
- //handle SSL and errors at some point TODO
34
- $ftp = new ftp_wrapper(get_option('updraft_server_address'),get_option('updraft_ftp_login'),get_option('updraft_ftp_pass'));
35
- $ftp->passive = true;
36
- $ftp->connect();
37
- //$ftp->make_dir(); we may need to recursively create dirs? TODO
38
-
39
- $ftp_remote_path = trailingslashit(get_option('updraft_ftp_remote_path'));
40
- $fullpath = trailingslashit(get_option('updraft_dir')).$file;
41
- $ftp->get($fullpath,$ftp_remote_path.$file,FTP_BINARY);
42
  }
43
 
44
  ?>
1
  <?php
2
 
3
+ class UpdraftPlus_BackupModule_ftp {
4
 
5
+ function backup($backup_array) {
6
 
7
+ global $updraftplus;
8
 
9
+ if( !class_exists('ftp_wrapper')) require_once(UPDRAFTPLUS_DIR.'/includes/ftp.class.php');
10
+
11
+ //handle SSL and errors at some point TODO
12
+ $ftp = new ftp_wrapper(get_option('updraft_server_address'), get_option('updraft_ftp_login'), get_option('updraft_ftp_pass'));
13
+ $ftp->passive = true;
14
+ $ftp->connect();
15
+ //$ftp->make_dir(); we may need to recursively create dirs? TODO
16
+
17
+ $ftp_remote_path = trailingslashit(get_option('updraft_ftp_remote_path'));
18
+ foreach($backup_array as $file) {
19
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
20
+ if ($ftp->put($fullpath,$ftp_remote_path.$file,FTP_BINARY)) {
21
+ $updraftplus->log("ERROR: $file_name: Successfully uploaded via FTP");
22
+ $updraftplus->uploaded_file($file);
23
+ } else {
24
+ $updraftplus->error("$file_name: Failed to upload to FTP" );
25
+ $updraftplus->log("ERROR: $file_name: Failed to upload to FTP" );
26
+ }
27
  }
28
+
29
+ $updraftplus->prune_retained_backups("ftp", $this, array('ftp_object' => $ftp, 'ftp_remote_path' => $ftp_remote_path));
30
  }
31
 
32
+ function delete($file, $ftparr) {
33
+ $ftp = $ftparr['ftp_object'];
34
+ $ftp_remote_path = $ftparr['ftp_remote_path'];
35
+ @$ftp->delete($ftp_remote_path.$file);
36
+ }
37
+
38
+ function download($file) {
39
+ if( !class_exists('ftp_wrapper')) require_once(UPDRAFTPLUS_DIR.'/includes/ftp.class.php');
40
+
41
+ //handle SSL and errors at some point TODO
42
+ $ftp = new ftp_wrapper(get_option('updraft_server_address'),get_option('updraft_ftp_login'),get_option('updraft_ftp_pass'));
43
+ $ftp->passive = true;
44
+ $ftp->connect();
45
+ //$ftp->make_dir(); we may need to recursively create dirs? TODO
46
+
47
+ $ftp_remote_path = trailingslashit(get_option('updraft_ftp_remote_path'));
48
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
49
+ $ftp->get($fullpath,$ftp_remote_path.$file,FTP_BINARY);
50
+ }
51
+
52
+ function config_print() {
53
+ ?>
54
+ <tr class="updraftplusmethod ftp">
55
+ <th>FTP Server:</th>
56
+ <td><input type="text" style="width:260px" name="updraft_server_address" value="<?php echo htmlspecialchars(get_option('updraft_server_address')); ?>" /></td>
57
+ </tr>
58
+ <tr class="updraftplusmethod ftp">
59
+ <th>FTP Login:</th>
60
+ <td><input type="text" autocomplete="off" name="updraft_ftp_login" value="<?php echo htmlspecialchars(get_option('updraft_ftp_login')) ?>" /></td>
61
+ </tr>
62
+ <tr class="updraftplusmethod ftp">
63
+ <th>FTP Password:</th>
64
+ <td><input type="text" autocomplete="off" style="width:260px" name="updraft_ftp_pass" value="<?php echo htmlspecialchars(get_option('updraft_ftp_pass')); ?>" /></td>
65
+ </tr>
66
+ <tr class="updraftplusmethod ftp">
67
+ <th>Remote Path:</th>
68
+ <td><input type="text" style="width:260px" name="updraft_ftp_remote_path" value="<?php echo htmlspecialchars(get_option('updraft_ftp_remote_path')); ?>" /></td>
69
+ </tr>
70
+ <?php
71
+ }
72
+
73
+ function file_delete() {
74
+ $this->log("$backup_datestamp: Delete remote ftp: $remote_path/$dofile");
75
+ @$remote_object->delete($remote_path.$dofile);
76
+ }
77
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  }
79
 
80
  ?>
methods/googledrive.php CHANGED
@@ -1,190 +1,337 @@
1
  <?php
2
 
3
- // Not cleanly separated - much of the Google Drive code is still embedded in the main updraftplus.php
4
-
5
- // This function just does the formalities, and off-loads the main work to googledrive_upload_file
6
- function updraftplus_googledrive_backup($backup_array) {
7
-
8
- global $updraftplus;
 
 
 
 
 
 
 
 
 
9
 
10
- if( !class_exists('UpdraftPlus_GDocs')) require_once(UPDRAFTPLUS_DIR.'/includes/class-gdocs.php');
 
11
 
12
- // Do we have an access token?
13
- if ( !$access_token = $updraftplus->access_token( get_option('updraft_googledrive_token'), get_option('updraft_googledrive_clientid'), get_option('updraft_googledrive_secret') )) {
14
- $updraftplus->log('ERROR: Have not yet obtained an access token from Google (has the user authorised?)');
15
- return new WP_Error( "no_access_token", "Have not yet obtained an access token from Google (has the user authorised?");
16
- }
17
 
18
- $updraftplus->gdocs_access_token = $access_token;
 
19
 
20
- foreach ($backup_array as $file) {
21
- $file_path = trailingslashit(get_option('updraft_dir')).$file;
22
- $file_name = basename($file_path);
23
- $updraftplus->log("$file_name: Attempting to upload to Google Drive");
24
- $timer_start = microtime(true);
25
- if ( $id = updraftplus_googledrive_upload_file( $file_path, $file_name, get_option('updraft_googledrive_remotepath')) ) {
26
- $updraftplus->log('OK: Archive ' . $file_name . ' uploaded to Google Drive in ' . ( round(microtime( true ) - $timer_start,2) ) . ' seconds (id: '.$id.')' );
27
- $updraftplus->uploaded_file($file, $id);
28
  } else {
29
- $updraftplus->error("$file_name: Failed to upload to Google Drive" );
30
- $updraftplus->log("ERROR: $file_name: Failed to upload to Google Drive" );
 
 
 
 
 
 
31
  }
32
  }
33
- $updraftplus->prune_retained_backups("googledrive",$access_token,get_option('updraft_googledrive_remotepath'));
34
- }
35
 
36
- // Returns:
37
- // true = already uploaded
38
- // false = failure
39
- // otherwise, the file ID
40
- function updraftplus_googledrive_upload_file( $file, $title, $parent = '') {
41
-
42
- global $updraftplus;
43
-
44
- // Make sure $this->gdocs is a UpdraftPlus_GDocs object, or give an error
45
- if ( is_wp_error( $e = updraftplus_need_gdocs() ) ) return false;
46
- $gdocs_object = $updraftplus->gdocs;
47
-
48
- $hash = md5($file);
49
- $transkey = 'upd_'.$hash.'_gloc';
50
- // This is unset upon completion, so if it is set then we are resuming
51
- $possible_location = get_transient($transkey);
52
-
53
- if ( empty( $possible_location ) ) {
54
- $updraftplus->log("$file: Attempting to upload file to Google Drive.");
55
- $location = $gdocs_object->prepare_upload( $file, $title, $parent );
56
- } else {
57
- $updraftplus->log("$file: Attempting to resume upload.");
58
- $location = $gdocs_object->resume_upload( $file, $possible_location );
59
  }
60
 
61
- if ( is_wp_error( $location ) ) {
62
- $updraftplus->log("GoogleDrive upload: an error occurred");
63
- foreach ($location->get_error_messages() as $msg) {
64
- $updraftplus->error($msg);
65
- $updraftplus->log("Error details: ".$msg);
66
- }
67
- return false;
68
  }
69
 
70
- if (!is_string($location) && true == $location) {
71
- $updraftplus->log("$file: this file is already uploaded");
72
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  }
74
 
75
- if ( is_string( $location ) ) {
76
- $res = $location;
77
- $updraftplus->log("Uploading file with title ".$title);
78
- $d = 0;
79
- do {
80
- $updraftplus->log("Google Drive upload: chunk d: $d, loc: $res");
81
- $res = $gdocs_object->upload_chunk();
82
- if (is_string($res)) set_transient($transkey, $res, 3600*3);
83
- $p = $gdocs_object->get_upload_percentage();
84
- if ( $p - $d >= 1 ) {
85
- $b = intval( $p - $d );
86
- // echo '<span style="width:' . $b . '%"></span>';
87
- $d += $b;
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  }
89
- // $this->options['backup_list'][$id]['speed'] = $this->gdocs->get_upload_speed();
90
- } while ( is_string( $res ) );
91
- // echo '</div>';
92
-
93
- if ( is_wp_error( $res ) || $res !== true) {
94
- $updraftplus->log( "An error occurred during GoogleDrive upload (2)" );
95
- $updraftplus->error( "An error occurred during GoogleDrive upload (2)" );
96
- if (is_wp_error( $res )) {
97
- foreach ($res->get_error_messages() as $msg) $updraftplus->log($msg);
 
 
 
 
 
 
 
 
 
98
  }
99
- return false;
100
  }
 
 
101
 
102
- $updraftplus->log("The file was successfully uploaded to Google Drive in ".number_format_i18n( $gdocs_object->time_taken(), 3)." seconds at an upload speed of ".size_format( $gdocs_object->get_upload_speed() )."/s.");
 
 
 
 
103
 
104
- delete_transient($transkey);
105
- // unset( $this->options['backup_list'][$id]['location'], $this->options['backup_list'][$id]['attempt'] );
106
- }
107
 
108
- return $gdocs_object->get_file_id();
 
 
109
 
110
- // $this->update_quota();
111
- // Google's "user info" service
112
- // if ( empty( $this->options['user_info'] ) ) $this->set_user_info();
 
113
 
114
- }
 
 
 
 
 
 
115
 
116
- function updraftplus_download_googledrive_backup($file) {
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
- global $updraftplus;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
- if( !class_exists('UpdraftPlus_GDocs')) require_once(UPDRAFTPLUS_DIR.'/includes/class-gdocs.php');
 
 
 
 
 
 
 
 
 
 
121
 
122
- // Do we have an access token?
123
- if ( !$access_token = $updraftplus->access_token( get_option('updraft_googledrive_token'), get_option('updraft_googledrive_clientid'), get_option('updraft_googledrive_secret') )) {
124
- $updraftplus->error('ERROR: Have not yet obtained an access token from Google (has the user authorised?)');
125
- return false;
126
  }
127
 
128
- $updraftplus->gdocs_access_token = $access_token;
129
 
130
- // Make sure $this->gdocs is a UpdraftPlus_GDocs object, or give an error
131
- if ( is_wp_error( $e = updraftplus_need_gdocs() ) ) return false;
132
- $gdocs_object = $updraftplus->gdocs;
133
 
134
- $ids = get_option('updraft_file_ids', array());
135
- if (!isset($ids[$file])) {
136
- $this->error("Google Drive error: $file: could not download: could not find a record of the Google Drive file ID for this file");
137
- return;
138
- } else {
139
- $content_link = $gdocs_object->get_content_link( $ids[$file], $file );
140
- if (is_wp_error($content_link)) {
141
- $updraftplus->error("Could not find $file in order to download it (id: ".$ids[$file].")");
142
- foreach ($content_link->get_error_messages() as $msg) $updraftplus->error($msg);
143
  return false;
144
  }
145
- // Actually download the thing
146
- $download_to = trailingslashit(get_option('updraft_dir')).$file;
147
- $gdocs_object->download_data($content_link, $download_to);
148
 
149
- if (filesize($download_to) >0) {
150
- return true;
 
 
 
 
 
 
 
 
151
  } else {
152
- $updraftplus->error("Google Drive error: zero-size file was downloaded");
153
- return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  }
155
 
 
 
156
  }
157
 
158
- return;
 
159
 
160
- }
161
 
162
- // This function modified from wordpress.org/extend/plugins/backup, by Sorin Iclanzan, under the GPLv3 or later at your choice
163
- function updraftplus_need_gdocs() {
 
 
 
164
 
165
- global $updraftplus;
166
 
167
- if ( ! updraftplus_is_gdocs($updraftplus->gdocs) ) {
168
- if ( get_option('updraft_googledrive_token') == "" || get_option('updraft_googledrive_clientid') == "" || get_option('updraft_googledrive_secret') == "" ) {
169
- $updraftplus->log("GoogleDrive: this account is not authorised");
170
- return new WP_Error( "not_authorized", "Account is not authorized." );
171
  }
 
 
172
 
173
- if ( is_wp_error( $updraftplus->gdocs_access_token ) ) return $access_token;
 
 
 
 
174
 
175
- $updraftplus->gdocs = new UpdraftPlus_GDocs( $updraftplus->gdocs_access_token );
176
- $updraftplus->gdocs->set_option( 'chunk_size', 1 ); # 1Mb; change from default of 512Kb
177
- $updraftplus->gdocs->set_option( 'request_timeout', 10 ); # Change from default of 10s
178
- $updraftplus->gdocs->set_option( 'max_resume_attempts', 36 ); # Doesn't look like GDocs class actually uses this anyway
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  }
180
- return true;
181
- }
182
 
183
- // This function taken from wordpress.org/extend/plugins/backup, by Sorin Iclanzan, under the GPLv3 or later at your choice
184
- function updraftplus_is_gdocs( $thing ) {
185
- if ( is_object( $thing ) && is_a( $thing, 'UpdraftPlus_GDocs' ) ) return true;
186
- return false;
187
  }
188
 
189
-
190
  ?>
1
  <?php
2
 
3
+ class UpdraftPlus_BackupModule_googledrive {
4
+
5
+ var $gdocs;
6
+ var $gdocs_access_token;
7
+
8
+ function action_auth() {
9
+ if ( isset( $_GET['state'] ) ) {
10
+ if ( $_GET['state'] == 'token' )
11
+ $this->gdrive_auth_token();
12
+ elseif ( $_GET['state'] == 'revoke' )
13
+ $this->gdrive_auth_revoke();
14
+ } elseif (isset($_GET['updraftplus_googleauth'])) {
15
+ $this->gdrive_auth_request();
16
+ }
17
+ }
18
 
19
+ // Get a Google account access token using the refresh token
20
+ function access_token( $token, $client_id, $client_secret ) {
21
 
22
+ global $updraftplus;
23
+ $updraftplus->log("Google Drive: requesting access token: client_id=$client_id");
 
 
 
24
 
25
+ $query_body = array( 'refresh_token' => $token, 'client_id' => $client_id, 'client_secret' => $client_secret, 'grant_type' => 'refresh_token' );
26
+ $result = wp_remote_post('https://accounts.google.com/o/oauth2/token', array('timeout' => '15', 'method' => 'POST', 'body' => $query_body) );
27
 
28
+ if (is_wp_error($result)) {
29
+ $updraftplus->log("Google Drive error when requesting access token");
30
+ foreach ($result->get_error_messages() as $msg) { $updraftplus->log("Error message: $msg"); }
31
+ return false;
 
 
 
 
32
  } else {
33
+ $json_values = json_decode( $result['body'], true );
34
+ if ( isset( $json_values['access_token'] ) ) {
35
+ $updraftplus->log("Google Drive: successfully obtained access token");
36
+ return $json_values['access_token'];
37
+ } else {
38
+ $updraftplus->log("Google Drive error when requesting access token: response does not contain access_token");
39
+ return false;
40
+ }
41
  }
42
  }
 
 
43
 
44
+ // Acquire single-use authorization code from Google OAuth 2.0
45
+ function gdrive_auth_request() {
46
+ $params = array(
47
+ 'response_type' => 'code',
48
+ 'client_id' => get_option('updraft_googledrive_clientid'),
49
+ 'redirect_uri' => admin_url('options-general.php?page=updraftplus&action=updraftmethod-googledrive-auth'),
50
+ 'scope' => 'https://www.googleapis.com/auth/drive.file https://docs.google.com/feeds/ https://docs.googleusercontent.com/ https://spreadsheets.google.com/feeds/',
51
+ 'state' => 'token',
52
+ 'access_type' => 'offline',
53
+ 'approval_prompt' => 'auto'
54
+ );
55
+ header('Location: https://accounts.google.com/o/oauth2/auth?'.http_build_query($params));
 
 
 
 
 
 
 
 
 
 
 
56
  }
57
 
58
+ // Revoke a Google account refresh token
59
+ function gdrive_auth_revoke() {
60
+ $ignore = wp_remote_get('https://accounts.google.com/o/oauth2/revoke?token='.get_option('updraft_googledrive_token'));
61
+ update_option('updraft_googledrive_token','');
62
+ header('Location: '.admin_url( 'options-general.php?page=updraftplus&message=Authorisation revoked'));
 
 
63
  }
64
 
65
+ // Get a Google account refresh token using the code received from gdrive_auth_request
66
+ function gdrive_auth_token() {
67
+ if( isset( $_GET['code'] ) ) {
68
+ $post_vars = array(
69
+ 'code' => $_GET['code'],
70
+ 'client_id' => get_option('updraft_googledrive_clientid'),
71
+ 'client_secret' => get_option('updraft_googledrive_secret'),
72
+ 'redirect_uri' => admin_url('options-general.php?page=updraftplus&action=updraftmethod-googledrive-auth'),
73
+ 'grant_type' => 'authorization_code'
74
+ );
75
+
76
+ $result = wp_remote_post('https://accounts.google.com/o/oauth2/token', array('timeout' => 30, 'method' => 'POST', 'body' => $post_vars) );
77
+
78
+ if (is_wp_error($result)) {
79
+ header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'Bad response!', 'backup' ) ) );
80
+ } else {
81
+ $json_values = json_decode( $result['body'], true );
82
+ if ( isset( $json_values['refresh_token'] ) ) {
83
+ update_option('updraft_googledrive_token',$json_values['refresh_token']); // Save token
84
+ header('Location: '.admin_url('options-general.php?page=updraftplus&message=' . __( 'Google Drive authorization was successful.', 'updraftplus' ) ) );
85
+ }
86
+ else {
87
+ header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'No refresh token was received!', 'updraftplus' ) ) );
88
+ }
89
+ }
90
+ }
91
+ else {
92
+ header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'Authorization failed!', 'updraftplus' ) ) );
93
+ }
94
  }
95
 
96
+ // This function just does the formalities, and off-loads the main work to upload_file
97
+ function backup($backup_array) {
98
+
99
+ global $updraftplus;
100
+
101
+ if( !class_exists('UpdraftPlus_GDocs')) require_once(UPDRAFTPLUS_DIR.'/includes/class-gdocs.php');
102
+
103
+ // Do we have an access token?
104
+ if ( !$access_token = $this->access_token( get_option('updraft_googledrive_token'), get_option('updraft_googledrive_clientid'), get_option('updraft_googledrive_secret') )) {
105
+ $updraftplus->log('ERROR: Have not yet obtained an access token from Google (has the user authorised?)');
106
+ return new WP_Error( "no_access_token", "Have not yet obtained an access token from Google (has the user authorised?");
107
+ }
108
+
109
+ $this->gdocs_access_token = $access_token;
110
+
111
+ foreach ($backup_array as $file) {
112
+ $file_path = trailingslashit(get_option('updraft_dir')).$file;
113
+ $file_name = basename($file_path);
114
+ $updraftplus->log("$file_name: Attempting to upload to Google Drive");
115
+ $timer_start = microtime(true);
116
+ if ( $id = $this->upload_file( $file_path, $file_name, get_option('updraft_googledrive_remotepath')) ) {
117
+ $updraftplus->log('OK: Archive ' . $file_name . ' uploaded to Google Drive in ' . ( round(microtime( true ) - $timer_start,2) ) . ' seconds (id: '.$id.')' );
118
+ $updraftplus->uploaded_file($file, $id);
119
+ } else {
120
+ $updraftplus->error("$file_name: Failed to upload to Google Drive" );
121
+ $updraftplus->log("ERROR: $file_name: Failed to upload to Google Drive" );
122
  }
123
+ }
124
+ $updraftplus->prune_retained_backups("googledrive", $this);
125
+ }
126
+
127
+ function delete($file) {
128
+ global $updraftplus;
129
+ $ids = get_option('updraft_file_ids', array());
130
+ if (!isset($ids[$file])) {
131
+ $updraftplus->log("Could not delete: could not find a record of the Google Drive file ID for this file");
132
+ return;
133
+ } else {
134
+ $del == $this->gdocs->delete_resource($ids[$file]);
135
+ if (is_wp_error($del)) {
136
+ foreach ($del->get_error_messages() as $msg) $updraftplus->log("Deletion failed: $msg");
137
+ } else {
138
+ $updraftplus->log("Deletion successful");
139
+ unset($ids[$file]);
140
+ update_option('updraft_file_ids', $ids);
141
  }
 
142
  }
143
+ return;
144
+ }
145
 
146
+ // Returns:
147
+ // true = already uploaded
148
+ // false = failure
149
+ // otherwise, the file ID
150
+ function upload_file( $file, $title, $parent = '') {
151
 
152
+ global $updraftplus;
 
 
153
 
154
+ // Make sure $this->gdocs is a UpdraftPlus_GDocs object, or give an error
155
+ if ( is_wp_error( $e = $this->need_gdocs() ) ) return false;
156
+ $gdocs_object = $this->gdocs;
157
 
158
+ $hash = md5($file);
159
+ $transkey = 'upd_'.$hash.'_gloc';
160
+ // This is unset upon completion, so if it is set then we are resuming
161
+ $possible_location = get_transient($transkey);
162
 
163
+ if ( empty( $possible_location ) ) {
164
+ $updraftplus->log("$file: Attempting to upload file to Google Drive.");
165
+ $location = $gdocs_object->prepare_upload( $file, $title, $parent );
166
+ } else {
167
+ $updraftplus->log("$file: Attempting to resume upload.");
168
+ $location = $gdocs_object->resume_upload( $file, $possible_location );
169
+ }
170
 
171
+ if ( is_wp_error( $location ) ) {
172
+ $updraftplus->log("GoogleDrive upload: an error occurred");
173
+ foreach ($location->get_error_messages() as $msg) {
174
+ $updraftplus->error($msg);
175
+ $updraftplus->log("Error details: ".$msg);
176
+ }
177
+ return false;
178
+ }
179
+
180
+ if (!is_string($location) && true == $location) {
181
+ $updraftplus->log("$file: this file is already uploaded");
182
+ return true;
183
+ }
184
 
185
+ if ( is_string( $location ) ) {
186
+ $res = $location;
187
+ $updraftplus->log("Uploading file with title ".$title);
188
+ $d = 0;
189
+ do {
190
+ $updraftplus->log("Google Drive upload: chunk d: $d, loc: $res");
191
+ $res = $gdocs_object->upload_chunk();
192
+ if (is_string($res)) set_transient($transkey, $res, 3600*3);
193
+ $p = $gdocs_object->get_upload_percentage();
194
+ if ( $p - $d >= 1 ) {
195
+ $b = intval( $p - $d );
196
+ // echo '<span style="width:' . $b . '%"></span>';
197
+ $d += $b;
198
+ }
199
+ // $this->options['backup_list'][$id]['speed'] = $this->gdocs->get_upload_speed();
200
+ } while ( is_string( $res ) );
201
+ // echo '</div>';
202
+
203
+ if ( is_wp_error( $res ) || $res !== true) {
204
+ $updraftplus->log( "An error occurred during GoogleDrive upload (2)" );
205
+ $updraftplus->error( "An error occurred during GoogleDrive upload (2)" );
206
+ if (is_wp_error( $res )) {
207
+ foreach ($res->get_error_messages() as $msg) $updraftplus->log($msg);
208
+ }
209
+ return false;
210
+ }
211
 
212
+ $updraftplus->log("The file was successfully uploaded to Google Drive in ".number_format_i18n( $gdocs_object->time_taken(), 3)." seconds at an upload speed of ".size_format( $gdocs_object->get_upload_speed() )."/s.");
213
+
214
+ delete_transient($transkey);
215
+ // unset( $this->options['backup_list'][$id]['location'], $this->options['backup_list'][$id]['attempt'] );
216
+ }
217
+
218
+ return $gdocs_object->get_file_id();
219
+
220
+ // $this->update_quota();
221
+ // Google's "user info" service
222
+ // if ( empty( $this->options['user_info'] ) ) $this->set_user_info();
223
 
 
 
 
 
224
  }
225
 
226
+ function download($file) {
227
 
228
+ global $updraftplus;
 
 
229
 
230
+ if( !class_exists('UpdraftPlus_GDocs')) require_once(UPDRAFTPLUS_DIR.'/includes/class-gdocs.php');
231
+
232
+ // Do we have an access token?
233
+ if ( !$access_token = $updraftplus->access_token( get_option('updraft_googledrive_token'), get_option('updraft_googledrive_clientid'), get_option('updraft_googledrive_secret') )) {
234
+ $updraftplus->error('ERROR: Have not yet obtained an access token from Google (has the user authorised?)');
 
 
 
 
235
  return false;
236
  }
 
 
 
237
 
238
+ $this->gdocs_access_token = $access_token;
239
+
240
+ // Make sure $this->gdocs is a UpdraftPlus_GDocs object, or give an error
241
+ if ( is_wp_error( $e = $this->need_gdocs() ) ) return false;
242
+ $gdocs_object = $this->gdocs;
243
+
244
+ $ids = get_option('updraft_file_ids', array());
245
+ if (!isset($ids[$file])) {
246
+ $this->error("Google Drive error: $file: could not download: could not find a record of the Google Drive file ID for this file");
247
+ return;
248
  } else {
249
+ $content_link = $gdocs_object->get_content_link( $ids[$file], $file );
250
+ if (is_wp_error($content_link)) {
251
+ $updraftplus->error("Could not find $file in order to download it (id: ".$ids[$file].")");
252
+ foreach ($content_link->get_error_messages() as $msg) $updraftplus->error($msg);
253
+ return false;
254
+ }
255
+ // Actually download the thing
256
+ $download_to = trailingslashit(get_option('updraft_dir')).$file;
257
+ $gdocs_object->download_data($content_link, $download_to);
258
+
259
+ if (filesize($download_to) >0) {
260
+ return true;
261
+ } else {
262
+ $updraftplus->error("Google Drive error: zero-size file was downloaded");
263
+ return false;
264
+ }
265
+
266
  }
267
 
268
+ return;
269
+
270
  }
271
 
272
+ // This function modified from wordpress.org/extend/plugins/backup, by Sorin Iclanzan, under the GPLv3 or later at your choice
273
+ function need_gdocs() {
274
 
275
+ global $updraftplus;
276
 
277
+ if ( ! $this->is_gdocs($this->gdocs) ) {
278
+ if ( get_option('updraft_googledrive_token') == "" || get_option('updraft_googledrive_clientid') == "" || get_option('updraft_googledrive_secret') == "" ) {
279
+ $updraftplus->log("GoogleDrive: this account is not authorised");
280
+ return new WP_Error( "not_authorized", "Account is not authorized." );
281
+ }
282
 
283
+ if ( is_wp_error( $this->gdocs_access_token ) ) return $access_token;
284
 
285
+ $this->gdocs = new UpdraftPlus_GDocs( $this->gdocs_access_token );
286
+ $this->gdocs->set_option( 'chunk_size', 1 ); # 1Mb; change from default of 512Kb
287
+ $this->gdocs->set_option( 'request_timeout', 10 ); # Change from default of 10s
288
+ $this->gdocs->set_option( 'max_resume_attempts', 36 ); # Doesn't look like GDocs class actually uses this anyway
289
  }
290
+ return true;
291
+ }
292
 
293
+ // This function taken from wordpress.org/extend/plugins/backup, by Sorin Iclanzan, under the GPLv3 or later at your choice
294
+ function is_gdocs( $thing ) {
295
+ if ( is_object( $thing ) && is_a( $thing, 'UpdraftPlus_GDocs' ) ) return true;
296
+ return false;
297
+ }
298
 
299
+ function config_print() {
300
+ ?>
301
+ <tr class="updraftplusmethod googledrive">
302
+ <th>Google Drive:</th>
303
+ <td>
304
+ <p><a href="http://david.dw-perspective.org.uk/da/index.php/computer-resources/updraftplus-googledrive-authorisation/"><strong>For longer help, including screenshots, follow this link. The description below is sufficient for more expert users.</strong></a></p>
305
+ <p><a href="https://code.google.com/apis/console/">Follow this link to your Google API Console</a>, and there create a Client ID in the API Access section. Select 'Web Application' as the application type.</p><p>You must add <kbd><?php echo admin_url('options-general.php?page=updraftplus&action=updraftmethod-googledrive-auth'); ?></kbd> as the authorised redirect URI when asked.
306
+
307
+ <?php
308
+ if (!class_exists('SimpleXMLElement')) { echo " <b>WARNING:</b> You do not have the SimpleXMLElement installed. Google Drive backups will <b>not</b> work until you do."; }
309
+ ?>
310
+ </p>
311
+ </td>
312
+ </tr>
313
+
314
+ <tr class="updraftplusmethod googledrive">
315
+ <th>Google Drive Client ID:</th>
316
+ <td><input type="text" autocomplete="off" style="width:332px" name="updraft_googledrive_clientid" value="<?php echo htmlspecialchars(get_option('updraft_googledrive_clientid')) ?>" /></td>
317
+ </tr>
318
+ <tr class="updraftplusmethod googledrive">
319
+ <th>Google Drive Client Secret:</th>
320
+ <td><input type="text" style="width:332px" name="updraft_googledrive_secret" value="<?php echo htmlspecialchars(get_option('updraft_googledrive_secret')); ?>" /></td>
321
+ </tr>
322
+ <tr class="updraftplusmethod googledrive">
323
+ <th>Google Drive Folder ID:</th>
324
+ <td><input type="text" style="width:332px" name="updraft_googledrive_remotepath" value="<?php echo htmlspecialchars(get_option('updraft_googledrive_remotepath')); ?>" /> <em>(To get a folder's ID navigate to that folder in Google Drive in your web browser and copy the ID from your browser's address bar. It is the part that comes after <kbd>#folders/.</kbd> Leave empty to use your root folder)</em></td>
325
+ </tr>
326
+ <tr class="updraftplusmethod googledrive">
327
+ <th>Authenticate with Google:</th>
328
+ <td><p><?php if (get_option('updraft_googledrive_token','xyz') != 'xyz') echo "<strong>(You appear to be already authenticated).</strong>"; ?> <a href="?page=updraftplus&action=updraftmethod-googledrive-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>
329
+ </p>
330
+ </td>
331
+ </tr>
332
+ <?php
333
  }
 
 
334
 
 
 
 
 
335
  }
336
 
 
337
  ?>
methods/s3.php CHANGED
@@ -1,128 +1,186 @@
1
  <?php
2
 
3
- function updraftplus_s3_backup($backup_array) {
4
 
5
- global $updraftplus;
6
 
7
- if (!class_exists('S3')) require_once(UPDRAFTPLUS_DIR.'/includes/S3.php');
8
 
9
- $s3 = new S3(get_option('updraft_s3_login'), get_option('updraft_s3_pass'));
10
 
11
- $bucket_name = untrailingslashit(get_option('updraft_s3_remote_path'));
12
- $bucket_path = "";
13
- $orig_bucket_name = $bucket_name;
14
 
15
- if (preg_match("#^([^/]+)/(.*)$#",$bucket_name,$bmatches)) {
16
- $bucket_name = $bmatches[1];
17
- $bucket_path = $bmatches[2]."/";
18
- }
19
 
20
- // See if we can detect the region (which implies the bucket exists and is ours), or if not create it
21
- if (@$s3->getBucketLocation($bucket_name) || @$s3->putBucket($bucket_name, S3::ACL_PRIVATE)) {
 
 
22
 
23
- foreach($backup_array as $file) {
 
24
 
25
- // We upload in 5Mb chunks to allow more efficient resuming and hence uploading of larger files
26
- $fullpath = trailingslashit(get_option('updraft_dir')).$file;
27
- $chunks = floor(filesize($fullpath) / 5242880)+1;
28
- $hash = md5($file);
29
 
30
- $updraftplus->log("S3 upload: $fullpath (chunks: $chunks) -> s3://$bucket_name/$bucket_path$file");
 
 
 
31
 
32
- $filepath = $bucket_path.$file;
33
 
34
- // This is extra code for the 1-chunk case, but less overhead (no bothering with transients)
35
- if ($chunks < 2) {
36
- if (!$s3->putObjectFile($fullpath, $bucket_name, $filepath)) {
37
- $updraftplus->log("S3 regular upload: failed");
38
- $updraftplus->error("S3 Error: Failed to upload $fullpath.");
39
- } else {
40
- $updraftplus->log("S3 regular upload: success");
41
- $updraftplus->uploaded_file($file);
42
- }
43
- } else {
44
 
45
- // Retrieve the upload ID
46
- $uploadId = get_transient("updraft_${hash}_uid");
47
- if (empty($uploadId)) {
48
- $uploadId = $s3->initiateMultipartUpload($bucket_name, $filepath);
49
- if (empty($uploadId)) {
50
- $updraftplus->log("S3 upload: failed: could not get uploadId for multipart upload");
51
- continue;
52
  } else {
53
- $updraftplus->log("S3 chunked upload: got multipart ID: $uploadId");
54
- set_transient("updraft_${hash}_uid", $uploadId, 3600*3);
55
  }
56
  } else {
57
- $updraftplus->log("S3 chunked upload: retrieved previously obtained multipart ID: $uploadId");
58
- }
59
 
60
- $successes = 0;
61
- $etags = array();
62
- for ($i = 1 ; $i <= $chunks; $i++) {
63
- # Shorted to upd here to avoid hitting the 45-character limit
64
- $etag = get_transient("upd_${hash}_e$i");
65
- if (strlen($etag) > 0) {
66
- $updraftplus->log("S3 chunk $i: was already completed (etag: $etag)");
67
- $successes++;
68
- array_push($etags, $etag);
 
 
69
  } else {
70
- $etag = $s3->uploadPart($bucket_name, $filepath, $uploadId, $fullpath, $i);
71
- if (is_string($etag)) {
72
- $updraftplus->log("S3 chunk $i: uploaded (etag: $etag)");
73
- array_push($etags, $etag);
74
- set_transient("upd_${hash}_e$i", $etag, 3600*3);
 
 
 
 
 
75
  $successes++;
 
76
  } else {
77
- $updraftplus->error("S3 chunk $i: upload failed");
78
- $updraftplus->log("S3 chunk $i: upload failed");
 
 
 
 
 
 
 
 
79
  }
80
  }
81
- }
82
- if ($successes >= $chunks) {
83
- $updraftplus->log("S3 upload: all chunks uploaded; will now instruct S3 to re-assemble");
84
- if ($s3->completeMultipartUpload ($bucket_name, $filepath, $uploadId, $etags)) {
85
- $updraftplus->log("S3 upload: re-assembly succeeded");
86
- $updraftplus->uploaded_file($file);
 
 
 
87
  } else {
88
- $updraftplus->log("S3 upload: re-assembly failed");
89
- $updraftplus->error("S3 upload: re-assembly failed");
90
  }
91
- } else {
92
- $updraftplus->log("S3 upload: upload was not completely successful on this run");
93
  }
94
  }
 
 
 
 
95
  }
96
- $updraftplus->prune_retained_backups('s3',$s3,$orig_bucket_name);
97
- } else {
98
- $updraftplus->log("S3 Error: Failed to create bucket $bucket_name.");
99
- $updraftplus->error("S3 Error: Failed to create bucket $bucket_name. Check your permissions and credentials.");
100
  }
101
- }
102
 
103
- function updraftplus_download_s3_backup($file) {
104
 
105
- global $updraftplus;
106
- if(!class_exists('S3')) require_once(UPDRAFTPLUS_DIR.'/includes/S3.php');
107
 
108
- $s3 = new S3(get_option('updraft_s3_login'), get_option('updraft_s3_pass'));
109
- $bucket_name = untrailingslashit(get_option('updraft_s3_remote_path'));
110
- $bucket_path = "";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
- if (preg_match("#^([^/]+)/(.*)$#",$bucket_name,$bmatches)) {
113
- $bucket_name = $bmatches[1];
114
- $bucket_path = $bmatches[2]."/";
115
  }
116
 
117
- if (@$s3->getBucketLocation($bucket_name)) {
118
- $fullpath = trailingslashit(get_option('updraft_dir')).$file;
119
- if (!$s3->getObject($bucket_name, $bucket_path.$file, $fullpath)) {
120
- $updraftplus->error("S3 Error: Failed to download $fullpath. Check your permissions and credentials.");
 
 
 
 
 
 
 
 
121
  }
122
- } else {
123
- $updraftplus->error("S3 Error: Failed to access bucket $bucket_name. Check your permissions and credentials.");
 
 
 
 
 
 
 
 
124
  }
125
 
126
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
 
128
  ?>
1
  <?php
2
 
3
+ class UpdraftPlus_BackupModule_s3 {
4
 
5
+ function backup($backup_array) {
6
 
7
+ global $updraftplus;
8
 
9
+ if (!class_exists('S3')) require_once(UPDRAFTPLUS_DIR.'/includes/S3.php');
10
 
11
+ $s3 = new S3(get_option('updraft_s3_login'), get_option('updraft_s3_pass'));
 
 
12
 
13
+ $bucket_name = untrailingslashit(get_option('updraft_s3_remote_path'));
14
+ $bucket_path = "";
15
+ $orig_bucket_name = $bucket_name;
 
16
 
17
+ if (preg_match("#^([^/]+)/(.*)$#",$bucket_name,$bmatches)) {
18
+ $bucket_name = $bmatches[1];
19
+ $bucket_path = $bmatches[2]."/";
20
+ }
21
 
22
+ // See if we can detect the region (which implies the bucket exists and is ours), or if not create it
23
+ if (@$s3->getBucketLocation($bucket_name) || @$s3->putBucket($bucket_name, S3::ACL_PRIVATE)) {
24
 
25
+ foreach($backup_array as $file) {
 
 
 
26
 
27
+ // We upload in 5Mb chunks to allow more efficient resuming and hence uploading of larger files
28
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
29
+ $chunks = floor(filesize($fullpath) / 5242880)+1;
30
+ $hash = md5($file);
31
 
32
+ $updraftplus->log("S3 upload: $fullpath (chunks: $chunks) -> s3://$bucket_name/$bucket_path$file");
33
 
34
+ $filepath = $bucket_path.$file;
 
 
 
 
 
 
 
 
 
35
 
36
+ // This is extra code for the 1-chunk case, but less overhead (no bothering with transients)
37
+ if ($chunks < 2) {
38
+ if (!$s3->putObjectFile($fullpath, $bucket_name, $filepath)) {
39
+ $updraftplus->log("S3 regular upload: failed");
40
+ $updraftplus->error("S3 Error: Failed to upload $fullpath.");
 
 
41
  } else {
42
+ $updraftplus->log("S3 regular upload: success");
43
+ $updraftplus->uploaded_file($file);
44
  }
45
  } else {
 
 
46
 
47
+ // Retrieve the upload ID
48
+ $uploadId = get_transient("updraft_${hash}_uid");
49
+ if (empty($uploadId)) {
50
+ $uploadId = $s3->initiateMultipartUpload($bucket_name, $filepath);
51
+ if (empty($uploadId)) {
52
+ $updraftplus->log("S3 upload: failed: could not get uploadId for multipart upload");
53
+ continue;
54
+ } else {
55
+ $updraftplus->log("S3 chunked upload: got multipart ID: $uploadId");
56
+ set_transient("updraft_${hash}_uid", $uploadId, 3600*3);
57
+ }
58
  } else {
59
+ $updraftplus->log("S3 chunked upload: retrieved previously obtained multipart ID: $uploadId");
60
+ }
61
+
62
+ $successes = 0;
63
+ $etags = array();
64
+ for ($i = 1 ; $i <= $chunks; $i++) {
65
+ # Shorted to upd here to avoid hitting the 45-character limit
66
+ $etag = get_transient("upd_${hash}_e$i");
67
+ if (strlen($etag) > 0) {
68
+ $updraftplus->log("S3 chunk $i: was already completed (etag: $etag)");
69
  $successes++;
70
+ array_push($etags, $etag);
71
  } else {
72
+ $etag = $s3->uploadPart($bucket_name, $filepath, $uploadId, $fullpath, $i);
73
+ if (is_string($etag)) {
74
+ $updraftplus->log("S3 chunk $i: uploaded (etag: $etag)");
75
+ array_push($etags, $etag);
76
+ set_transient("upd_${hash}_e$i", $etag, 3600*3);
77
+ $successes++;
78
+ } else {
79
+ $updraftplus->error("S3 chunk $i: upload failed");
80
+ $updraftplus->log("S3 chunk $i: upload failed");
81
+ }
82
  }
83
  }
84
+ if ($successes >= $chunks) {
85
+ $updraftplus->log("S3 upload: all chunks uploaded; will now instruct S3 to re-assemble");
86
+ if ($s3->completeMultipartUpload ($bucket_name, $filepath, $uploadId, $etags)) {
87
+ $updraftplus->log("S3 upload: re-assembly succeeded");
88
+ $updraftplus->uploaded_file($file);
89
+ } else {
90
+ $updraftplus->log("S3 upload: re-assembly failed");
91
+ $updraftplus->error("S3 upload: re-assembly failed");
92
+ }
93
  } else {
94
+ $updraftplus->log("S3 upload: upload was not completely successful on this run");
 
95
  }
 
 
96
  }
97
  }
98
+ $updraftplus->prune_retained_backups('s3', $this, array('s3_object' => $s3, 's3_orig_bucket_name' => $orig_bucket_name));
99
+ } else {
100
+ $updraftplus->log("S3 Error: Failed to create bucket $bucket_name.");
101
+ $updraftplus->error("S3 Error: Failed to create bucket $bucket_name. Check your permissions and credentials.");
102
  }
 
 
 
 
103
  }
 
104
 
105
+ function delete($file, $s3arr) {
106
 
107
+ global $updraftplus;
 
108
 
109
+ $s3 = $s3arr['s3_object'];
110
+ $orig_bucket_name = $s3arr['s3_orig_bucket_name'];
111
+
112
+ if (preg_match("#^([^/]+)/(.*)$#", $orig_bucket_name, $bmatches)) {
113
+ $s3_bucket=$bmatches[1];
114
+ $s3_uri = $bmatches[2]."/".$file;
115
+ } else {
116
+ $s3_bucket = $remote_path;
117
+ $s3_uri = $file;
118
+ }
119
+ $updraftplus->log("S3: Delete remote: bucket=$s3_bucket, URI=$s3_uri");
120
+
121
+ # Here we brought in the contents of the S3.php function deleteObject in order to get more direct access to any error
122
+ $rest = new S3Request('DELETE', $s3_bucket, $s3_uri);
123
+ $rest = $rest->getResponse();
124
+ if ($rest->error === false && $rest->code !== 204) {
125
+ $updraftplus->log("S3 Error: Expected HTTP response 204; got: ".$rest->code);
126
+ $updraftplus->error("S3 Error: Unexpected HTTP response code ".$rest->code." (expected 204)");
127
+ } elseif ($rest->error !== false) {
128
+ $updraftplus->log("S3 Error: ".$rest->error['code'].": ".$rest->error['message']);
129
+ $updraftplus->error("S3 delete error: ".$rest->error['code'].": ".$rest->error['message']);
130
+ }
131
 
 
 
 
132
  }
133
 
134
+ function download($file) {
135
+
136
+ global $updraftplus;
137
+ if(!class_exists('S3')) require_once(UPDRAFTPLUS_DIR.'/includes/S3.php');
138
+
139
+ $s3 = new S3(get_option('updraft_s3_login'), get_option('updraft_s3_pass'));
140
+ $bucket_name = untrailingslashit(get_option('updraft_s3_remote_path'));
141
+ $bucket_path = "";
142
+
143
+ if (preg_match("#^([^/]+)/(.*)$#",$bucket_name,$bmatches)) {
144
+ $bucket_name = $bmatches[1];
145
+ $bucket_path = $bmatches[2]."/";
146
  }
147
+
148
+ if (@$s3->getBucketLocation($bucket_name)) {
149
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
150
+ if (!$s3->getObject($bucket_name, $bucket_path.$file, $fullpath)) {
151
+ $updraftplus->error("S3 Error: Failed to download $fullpath. Check your permissions and credentials.");
152
+ }
153
+ } else {
154
+ $updraftplus->error("S3 Error: Failed to access bucket $bucket_name. Check your permissions and credentials.");
155
+ }
156
+
157
  }
158
 
159
+ function config_print() {
160
+
161
+ ?>
162
+ <tr class="updraftplusmethod s3">
163
+ <td></td>
164
+ <td><em>Amazon S3 is a great choice, because UpdraftPlus supports chunked uploads - no matter how big your blog is, UpdraftPlus can upload it a little at a time, and not get thwarted by timeouts.</em></td>
165
+ </tr>
166
+ <tr class="updraftplusmethod s3">
167
+ <th>S3 access key:</th>
168
+ <td><input type="text" autocomplete="off" style="width: 292px" name="updraft_s3_login" value="<?php echo htmlspecialchars(get_option('updraft_s3_login')) ?>" /></td>
169
+ </tr>
170
+ <tr class="updraftplusmethod s3">
171
+ <th>S3 secret key:</th>
172
+ <td><input type="text" autocomplete="off" style="width: 292px" name="updraft_s3_pass" value="<?php echo htmlspecialchars(get_option('updraft_s3_pass')); ?>" /></td>
173
+ </tr>
174
+ <tr class="updraftplusmethod s3">
175
+ <th>S3 location:</th>
176
+ <td>s3://<input type="text" style="width: 292px" name="updraft_s3_remote_path" value="<?php echo htmlspecialchars(get_option('updraft_s3_remote_path')); ?>" /></td>
177
+ </tr>
178
+ <tr class="updraftplusmethod s3">
179
+ <th></th>
180
+ <td><p>Get your access key and secret key from your AWS page, then pick a (globally unique) bucket name (letters and numbers) (and optionally a path) to use for storage. This bucket will be created for you if it does not already exist.</p></td>
181
+ </tr>
182
+ <?php
183
+ }
184
 
185
+ }
186
  ?>
readme.txt CHANGED
@@ -87,8 +87,8 @@ No, there's no warranty or guarantee, etc. It's completely up to you to verify t
87
 
88
  == Changelog ==
89
 
90
- = 1.0.18 - 12/26/2012 =
91
- * First steps towards modularising cloud upload methods
92
 
93
  = 1.0.16 - 12/24/2012 =
94
  * Improve race detection and clean up already-created files when detected
87
 
88
  == Changelog ==
89
 
90
+ = 1.0.20 - 12/29/2012 =
91
+ * Big code re-factoring; moving towards modularising cloud access methods
92
 
93
  = 1.0.16 - 12/24/2012 =
94
  * Improve race detection and clean up already-created files when detected
updraftplus.php CHANGED
@@ -4,7 +4,7 @@ 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, on separate schedules.
6
  Author: David Anderson.
7
- Version: 1.0.18
8
  Donate link: http://david.dw-perspective.org.uk/donate
9
  License: GPLv3 or later
10
  Author URI: http://wordshell.net
@@ -56,7 +56,15 @@ define('UPDRAFT_DEFAULT_OTHERS_EXCLUDE','upgrade,cache,updraft,index.php');
56
 
57
  class UpdraftPlus {
58
 
59
- var $version = '1.0.18';
 
 
 
 
 
 
 
 
60
 
61
  var $dbhandle;
62
  var $errors = array();
@@ -64,11 +72,9 @@ class UpdraftPlus {
64
  var $logfile_name = "";
65
  var $logfile_handle = false;
66
  var $backup_time;
67
- var $gdocs;
68
- var $gdocs_access_token;
69
 
70
  function __construct() {
71
- // Initialisation actions
72
  # Create admin page
73
  add_action('admin_menu', array($this,'add_admin_pages'));
74
  add_action('admin_init', array($this,'admin_init'));
@@ -85,20 +91,17 @@ class UpdraftPlus {
85
  add_action('init', array($this, 'handle_url_actions'));
86
  }
87
 
88
- // Handle Google OAuth 2.0 - ?page=updraftplus&action=auth
89
  // Also handle action=downloadlog
90
  function handle_url_actions() {
91
  // First, basic security check: must be an admin page, with ability to manage options, with the right parameters
92
  if ( is_admin() && current_user_can('manage_options') && isset( $_GET['page'] ) && $_GET['page'] == 'updraftplus' && isset($_GET['action']) ) {
93
- if ($_GET['action'] == 'auth') {
94
- if ( isset( $_GET['state'] ) ) {
95
- if ( $_GET['state'] == 'token' )
96
- $this->gdrive_auth_token();
97
- elseif ( $_GET['state'] == 'revoke' )
98
- $this->gdrive_auth_revoke();
99
- } elseif (isset($_GET['updraftplus_googleauth'])) {
100
- $this->gdrive_auth_request();
101
- }
102
  } elseif ($_GET['action'] == 'downloadlog' && isset($_GET['updraftplus_backup_nonce']) && preg_match("/^[0-9a-f]{12}$/",$_GET['updraftplus_backup_nonce'])) {
103
  $updraft_dir = $this->backups_dir_location();
104
  $log_file = $updraft_dir.'/log.'.$_GET['updraftplus_backup_nonce'].'.txt';
@@ -113,101 +116,6 @@ class UpdraftPlus {
113
  }
114
  }
115
 
116
- // Acquire single-use authorization code from Google OAuth 2.0
117
- function gdrive_auth_request() {
118
- $params = array(
119
- 'response_type' => 'code',
120
- 'client_id' => get_option('updraft_googledrive_clientid'),
121
- 'redirect_uri' => admin_url('options-general.php?page=updraftplus&action=auth'),
122
- 'scope' => 'https://www.googleapis.com/auth/drive.file https://docs.google.com/feeds/ https://docs.googleusercontent.com/ https://spreadsheets.google.com/feeds/',
123
- 'state' => 'token',
124
- 'access_type' => 'offline',
125
- 'approval_prompt' => 'auto'
126
- );
127
- header('Location: https://accounts.google.com/o/oauth2/auth?'.http_build_query($params));
128
- }
129
-
130
- // Get a Google account access token using the refresh token
131
- function access_token( $token, $client_id, $client_secret ) {
132
- $this->log("Google Drive: requesting access token: client_id=$client_id");
133
-
134
- $query_body = array( 'refresh_token' => $token, 'client_id' => $client_id, 'client_secret' => $client_secret, 'grant_type' => 'refresh_token' );
135
- $result = wp_remote_post('https://accounts.google.com/o/oauth2/token', array('timeout' => '15', 'method' => 'POST', 'body' => $query_body) );
136
-
137
- if (is_wp_error($result)) {
138
- $this->log("Google Drive error when requesting access token");
139
- foreach ($result->get_error_messages() as $msg) { $this->log("Error message: $msg"); }
140
- return false;
141
- } else {
142
- $json_values = json_decode( $result['body'], true );
143
- if ( isset( $json_values['access_token'] ) ) {
144
- $this->log("Google Drive: successfully obtained access token");
145
- return $json_values['access_token'];
146
- } else {
147
- $this->log("Google Drive error when requesting access token: response does not contain access_token");
148
- return false;
149
- }
150
- }
151
- }
152
-
153
- function googledrive_delete_file( $file, $token) {
154
- $ids = get_option('updraft_file_ids', array());
155
- if (!isset($ids[$file])) {
156
- $this->log("Could not delete: could not find a record of the Google Drive file ID for this file");
157
- return;
158
- } else {
159
- $del == $this->gdocs->delete_resource($ids[$file]);
160
- if (is_wp_error($del)) {
161
- foreach ($del->get_error_messages() as $msg) {
162
- $this->log("Deletion failed: $msg");
163
- }
164
- } else {
165
- $this->log("Deletion successful");
166
- unset($ids[$file]);
167
- update_option('updraft_file_ids', $ids);
168
- }
169
- }
170
- return;
171
- }
172
-
173
- // Get a Google account refresh token using the code received from gdrive_auth_request
174
- function gdrive_auth_token() {
175
- if( isset( $_GET['code'] ) ) {
176
- $post_vars = array(
177
- 'code' => $_GET['code'],
178
- 'client_id' => get_option('updraft_googledrive_clientid'),
179
- 'client_secret' => get_option('updraft_googledrive_secret'),
180
- 'redirect_uri' => admin_url('options-general.php?page=updraftplus&action=auth'),
181
- 'grant_type' => 'authorization_code'
182
- );
183
-
184
- $result = wp_remote_post('https://accounts.google.com/o/oauth2/token', array('timeout' => 30, 'method' => 'POST', 'body' => $post_vars) );
185
-
186
- if (is_wp_error($result)) {
187
- header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'Bad response!', 'backup' ) ) );
188
- } else {
189
- $json_values = json_decode( $result['body'], true );
190
- if ( isset( $json_values['refresh_token'] ) ) {
191
- update_option('updraft_googledrive_token',$json_values['refresh_token']); // Save token
192
- header('Location: '.admin_url('options-general.php?page=updraftplus&message=' . __( 'Google Drive authorization was successful.', 'updraftplus' ) ) );
193
- }
194
- else {
195
- header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'No refresh token was received!', 'updraftplus' ) ) );
196
- }
197
- }
198
- }
199
- else {
200
- header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'Authorization failed!', 'updraftplus' ) ) );
201
- }
202
- }
203
-
204
- // Revoke a Google account refresh token
205
- function gdrive_auth_revoke() {
206
- $ignore = wp_remote_get('https://accounts.google.com/o/oauth2/revoke?token='.get_option('updraft_googledrive_token'));
207
- update_option('updraft_googledrive_token','');
208
- header('Location: '.admin_url( 'options-general.php?page=updraftplus&message=Authorisation revoked'));
209
- }
210
-
211
  # Adds the settings link under the plugin on the plugin screen.
212
  function plugin_action_links($links, $file) {
213
  if ($file == plugin_basename(__FILE__)){
@@ -483,7 +391,6 @@ class UpdraftPlus {
483
 
484
  // Dispatch to the relevant function
485
  function cloud_backup($backup_array) {
486
-
487
  $service = get_option('updraft_service');
488
  $this->log("Cloud backup selection: ".$service);
489
  @set_time_limit(900);
@@ -491,67 +398,57 @@ class UpdraftPlus {
491
  $method_include = UPDRAFTPLUS_DIR.'/methods/'.$service.'.php';
492
  if (file_exists($method_include)) require_once($method_include);
493
 
494
- if (function_exists("updraftplus_${service}_backup")) {
 
495
  // New style - external, allowing more plugability
496
- call_user_func("updraftplus_${service}_backup", $backup_array);
 
497
  } else {
498
  $this->prune_retained_backups("local");
499
  }
500
  }
501
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
  // Carries out retain behaviour. Pass in a valid S3 or FTP object and path if relevant.
503
- function prune_retained_backups($updraft_service,$remote_object,$remote_path) {
 
504
  $this->log("Retain: beginning examination of existing backup sets");
505
- $updraft_retain = get_option('updraft_retain');
506
  // Number of backups to retain
507
- $retain = (isset($updraft_retain)) ? get_option('updraft_retain') : 1;
 
508
  $this->log("Retain: user setting: number to retain = $retain");
 
509
  // Returns an array, most recent first, of backup sets
510
  $backup_history = $this->get_backup_history();
511
  $db_backups_found = 0;
512
  $file_backups_found = 0;
513
  $this->log("Number of backup sets in history: ".count($backup_history));
 
514
  foreach ($backup_history as $backup_datestamp => $backup_to_examine) {
515
  // $backup_to_examine is an array of file names, keyed on db/plugins/themes/uploads
516
  // The new backup_history array is saved afterwards, so remember to unset the ones that are to be deleted
517
  $this->log("Examining backup set with datestamp: $backup_datestamp");
 
518
  if (isset($backup_to_examine['db'])) {
519
  $db_backups_found++;
520
  $this->log("$backup_datestamp: this set includes a database (".$backup_to_examine['db']."); db count is now $db_backups_found");
521
  if ($db_backups_found > $retain) {
522
  $this->log("$backup_datestamp: over retain limit; will delete this database");
523
- $file = $backup_to_examine['db'];
524
- $this->log("$backup_datestamp: Delete this file: $file");
525
- if ($file != '') {
526
- $fullpath = trailingslashit(get_option('updraft_dir')).$file;
527
- @unlink($fullpath); //delete it if it's locally available
528
- if ($updraft_service == "s3") {
529
- if (preg_match("#^([^/]+)/(.*)$#",$remote_path,$bmatches)) {
530
- $s3_bucket=$bmatches[1];
531
- $s3_uri = $bmatches[2]."/".$file;
532
- } else {
533
- $s3_bucket = $remote_path;
534
- $s3_uri = $file;
535
- }
536
- $this->log("$backup_datestamp: Delete remote: bucket=$s3_bucket, URI=$s3_uri");
537
- # Here we brought in the function deleteObject in order to get more direct access to any error
538
- $rest = new S3Request('DELETE', $s3_bucket, $s3_uri);
539
- $rest = $rest->getResponse();
540
- if ($rest->error === false && $rest->code !== 204) {
541
- $this->log("S3 Error: Expected HTTP response 204; got: ".$rest->code);
542
- $this->error("S3 Error: Unexpected HTTP response code ".$rest->code." (expected 204)");
543
- } elseif ($rest->error !== false) {
544
- $this->log("S3 Error: ".$rest->error['code'].": ".$rest->error['message']);
545
- $this->error("S3 delete error: ".$rest->error['code'].": ".$rest->error['message']);
546
- }
547
- } elseif ($updraft_service == "ftp") {
548
- $this->log("$backup_datestamp: Delete remote ftp: $remote_path/$file");
549
- @$remote_object->delete($remote_path.$file);
550
- } elseif ($updraft_service == "googledrive") {
551
- $this->log("$backup_datestamp: Delete remote file from Google Drive: $file");
552
- $this->googledrive_delete_file($file,$remote_object);
553
- }
554
- }
555
  unset($backup_to_examine['db']);
556
  }
557
  }
@@ -564,38 +461,8 @@ class UpdraftPlus {
564
  $file2 = isset($backup_to_examine['themes']) ? $backup_to_examine['themes'] : "";
565
  $file3 = isset($backup_to_examine['uploads']) ? $backup_to_examine['uploads'] : "";
566
  $file4 = isset($backup_to_examine['others']) ? $backup_to_examine['others'] : "";
567
- foreach (array($file,$file2,$file3,$file4) as $dofile) {
568
- if ($dofile) {
569
- $this->log("$backup_datestamp: Delete this file: $dofile");
570
- $fullpath = trailingslashit(get_option('updraft_dir')).$dofile;
571
- @unlink($fullpath); //delete it if it's locally available
572
- if ($updraft_service == "s3") {
573
- if (preg_match("#^([^/]+)/(.*)$#",$remote_path,$bmatches)) {
574
- $s3_bucket=$bmatches[1];
575
- $s3_uri = $bmatches[2]."/".$dofile;
576
- } else {
577
- $s3_bucket = $remote_path;
578
- $s3_uri = $dofile;
579
- }
580
- $this->log("$backup_datestamp: Delete remote: bucket=$s3_bucket, URI=$s3_uri");
581
- # Here we brought in the function deleteObject in order to get more direct access to any error
582
- $rest = new S3Request('DELETE', $s3_bucket, $s3_uri);
583
- $rest = $rest->getResponse();
584
- if ($rest->error === false && $rest->code !== 204) {
585
- $this->log("S3 Error: Expected HTTP response 204; got: ".$rest->code);
586
- $this->error("S3 Error: Unexpected HTTP response code ".$rest->code." (expected 204)");
587
- } elseif ($rest->error !== false) {
588
- $this->log("S3 Error: ".$rest->error['code'].": ".$rest->error['message']);
589
- $this->error("S3 delete error: ".$rest->error['code'].": ".$rest->error['message']);
590
- }
591
- } elseif ($updraft_service == "ftp") {
592
- $this->log("$backup_datestamp: Delete remote ftp: $remote_path/$dofile");
593
- @$remote_object->delete($remote_path.$dofile);
594
- } elseif ($updraft_service == "googledrive") {
595
- $this->log("$backup_datestamp: Delete remote file from Google Drive: $dofile");
596
- $this->googledrive_delete_file($dofile,$remote_object);
597
- }
598
- }
599
  }
600
  unset($backup_to_examine['plugins']);
601
  unset($backup_to_examine['themes']);
@@ -1221,8 +1088,10 @@ class UpdraftPlus {
1221
  $method_include = UPDRAFTPLUS_DIR.'/methods/'.$service.'.php';
1222
  if (file_exists($method_include)) require_once($method_include);
1223
 
1224
- if (function_exists("updraftplus_download_${service}_backup")) {
1225
- call_user_func("updraftplus_download_${service}_backup", $file);
 
 
1226
  } else {
1227
  $this->error('Automatic backup restoration is not available with the method: $service.');
1228
  }
@@ -1259,7 +1128,7 @@ class UpdraftPlus {
1259
  if(!class_exists('WP_Upgrader')) {
1260
  require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
1261
  }
1262
- require_once('includes/updraft-restorer.php');
1263
  $restorer = new Updraft_Restorer();
1264
  $val = $restorer->restore_backup($fullpath,$type);
1265
  if(is_wp_error($val)) {
@@ -1380,6 +1249,7 @@ class UpdraftPlus {
1380
  register_setting( 'updraft-options-group', 'updraft_retain', array($this,'retain_range') );
1381
  register_setting( 'updraft-options-group', 'updraft_encryptionphrase', 'wp_filter_nohtml_kses' );
1382
  register_setting( 'updraft-options-group', 'updraft_service', 'wp_filter_nohtml_kses' );
 
1383
  register_setting( 'updraft-options-group', 'updraft_s3_login', 'wp_filter_nohtml_kses' );
1384
  register_setting( 'updraft-options-group', 'updraft_s3_pass', 'wp_filter_nohtml_kses' );
1385
  register_setting( 'updraft-options-group', 'updraft_s3_remote_path', 'wp_filter_nohtml_kses' );
@@ -1388,10 +1258,10 @@ class UpdraftPlus {
1388
  register_setting( 'updraft-options-group', 'updraft_googledrive_remotepath', 'wp_filter_nohtml_kses' );
1389
  register_setting( 'updraft-options-group', 'updraft_ftp_login', 'wp_filter_nohtml_kses' );
1390
  register_setting( 'updraft-options-group', 'updraft_ftp_pass', 'wp_filter_nohtml_kses' );
1391
- register_setting( 'updraft-options-group', 'updraft_dir', 'wp_filter_nohtml_kses' );
1392
- register_setting( 'updraft-options-group', 'updraft_email', 'wp_filter_nohtml_kses' );
1393
  register_setting( 'updraft-options-group', 'updraft_ftp_remote_path', 'wp_filter_nohtml_kses' );
1394
  register_setting( 'updraft-options-group', 'updraft_server_address', 'wp_filter_nohtml_kses' );
 
 
1395
  register_setting( 'updraft-options-group', 'updraft_delete_local', 'absint' );
1396
  register_setting( 'updraft-options-group', 'updraft_debug_mode', 'absint' );
1397
  register_setting( 'updraft-options-group', 'updraft_include_plugins', 'absint' );
@@ -1399,7 +1269,7 @@ class UpdraftPlus {
1399
  register_setting( 'updraft-options-group', 'updraft_include_uploads', 'absint' );
1400
  register_setting( 'updraft-options-group', 'updraft_include_others', 'absint' );
1401
  register_setting( 'updraft-options-group', 'updraft_include_others_exclude', 'wp_filter_nohtml_kses' );
1402
-
1403
  if (current_user_can('manage_options') && get_option('updraft_service') == "googledrive" && get_option('updraft_googledrive_clientid') != "" && get_option('updraft_googledrive_token','xyz') == 'xyz') {
1404
  add_action('admin_notices', array($this,'show_admin_warning_googledrive') );
1405
  }
@@ -1758,19 +1628,19 @@ ENDHERE;
1758
  ?>
1759
  <td><input type="text" name="updraft_retain" value="<?php echo $retain ?>" style="width:50px" /></td>
1760
  </tr>
1761
- <tr class="email" <?php echo $email_display?>>
 
 
 
1762
  <th>Email:</th>
1763
  <td><input type="text" style="width:260px" name="updraft_email" value="<?php echo get_option('updraft_email'); ?>" /> <br>Enter an address here to have a report sent (and the whole backup, if you choose) to it.</td>
1764
  </tr>
1765
- <tr class="deletelocal s3 ftp email" <?php echo $display_delete_local?>>
1766
  <th>Delete local backup:</th>
1767
  <td><input type="checkbox" name="updraft_delete_local" value="1" <?php $delete_local = (get_option('updraft_delete_local')) ? 'checked="checked"' : "";
1768
  echo $delete_local; ?> /> <br>Check this to delete the local backup file (only sensible if you have enabled a remote backup (below), otherwise you will have no backup remaining).</td>
1769
  </tr>
1770
 
1771
- <tr class="backup-retain-description">
1772
- <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>
1773
- </tr>
1774
  <tr>
1775
  <th>Database encryption phrase:</th>
1776
  <?php
@@ -1792,137 +1662,53 @@ echo $delete_local; ?> /> <br>Check this to delete the local backup file (only s
1792
  <?php
1793
  $debug_mode = (get_option('updraft_debug_mode')) ? 'checked="checked"' : "";
1794
 
1795
- $display_none = 'style="display:none"';
1796
- $s3 = ""; $ftp = ""; $email = ""; $googledrive="";
1797
- $email_display="";
1798
- $display_email_complete = "";
1799
  $set = 'selected="selected"';
1800
- switch(get_option('updraft_service')) {
1801
- case 's3':
1802
- $s3 = $set;
1803
- $googledrive_display = $display_none;
1804
- $ftp_display = $display_none;
1805
- break;
1806
- case 'googledrive':
1807
- $googledrive = $set;
1808
- $s3_display = $display_none;
1809
- $ftp_display = $display_none;
1810
- break;
1811
- case 'ftp':
1812
- $ftp = $set;
1813
- $googledrive_display = $display_none;
1814
- $s3_display = $display_none;
1815
- break;
1816
- case 'email':
1817
- $email = $set;
1818
- $ftp_display = $display_none;
1819
- $s3_display = $display_none;
1820
- $googledrive_display = $display_none;
1821
- $display_email_complete = $display_none;
1822
- break;
1823
- default:
1824
- $none = $set;
1825
- $ftp_display = $display_none;
1826
- $googledrive_display = $display_none;
1827
- $s3_display = $display_none;
1828
- $display_delete_local = $display_none;
1829
- break;
1830
  }
1831
  ?>
1832
- <option value="none" <?php echo $none?>>None</option>
1833
- <option value="s3" <?php echo $s3?>>Amazon S3</option>
1834
- <option value="googledrive" <?php echo $googledrive?>>Google Drive</option>
1835
- <option value="ftp" <?php echo $ftp?>>FTP</option>
1836
- <option value="email" <?php echo $email?>>E-mail</option>
1837
  </select></td>
1838
  </tr>
1839
  <tr class="backup-service-description">
1840
  <td></td><td>Choose your backup method. If choosing &quot;E-Mail&quot;, then be aware that mail servers tend to have size limits; typically around 10-20Mb; backups larger than any limits will not arrive.</td>
1841
 
1842
  </tr>
1843
-
1844
- <!-- Amazon S3 -->
1845
- <tr class="s3" <?php echo $s3_display?>>
1846
- <td></td>
1847
- <td><em>Amazon S3 is a great choice, because UpdraftPlus supports chunked uploads - no matter how big your blog is, UpdraftPlus can upload it a little at a time, and not get thwarted by timeouts.</em></td>
1848
- </tr>
1849
- <tr class="s3" <?php echo $s3_display?>>
1850
- <th>S3 access key:</th>
1851
- <td><input type="text" autocomplete="off" style="width:292px" name="updraft_s3_login" value="<?php echo get_option('updraft_s3_login') ?>" /></td>
1852
- </tr>
1853
- <tr class="s3" <?php echo $s3_display?>>
1854
- <th>S3 secret key:</th>
1855
- <td><input type="text" autocomplete="off" style="width:292px" name="updraft_s3_pass" value="<?php echo get_option('updraft_s3_pass'); ?>" /></td>
1856
- </tr>
1857
- <tr class="s3" <?php echo $s3_display?>>
1858
- <th>S3 location:</th>
1859
- <td>s3://<input type="text" style="width:292px" name="updraft_s3_remote_path" value="<?php echo get_option('updraft_s3_remote_path'); ?>" /></td>
1860
- </tr>
1861
- <tr class="s3" <?php echo $s3_display?>>
1862
- <th></th>
1863
- <td><p>Get your access key and secret key from your AWS page, then pick a (globally unique) bucket name (letters and numbers) (and optionally a path) to use for storage. This bucket will be created for you if it does not already exist.</p></td>
1864
- </tr>
1865
-
1866
- <!-- Google Drive -->
1867
-
1868
- <tr class="googledrive" <?php echo $googledrive_display?>>
1869
- <th>Google Drive:</th>
1870
- <td>
1871
- <p><a href="http://david.dw-perspective.org.uk/da/index.php/computer-resources/updraftplus-googledrive-authorisation/"><strong>For longer help, including screenshots, follow this link. The description below is sufficient for more expert users.</strong></a></p>
1872
- <p><a href="https://code.google.com/apis/console/">Follow this link to your Google API Console</a>, and there create a Client ID in the API Access section. 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.
1873
-
1874
  <?php
1875
- if (!class_exists('SimpleXMLElement')) { echo " <b>WARNING:</b> You do not have the SimpleXMLElement installed. Google Drive backups will <b>not</b> work until you do."; }
 
 
 
 
1876
  ?>
1877
- </p>
1878
- </td>
1879
- </tr>
1880
-
1881
- <tr class="googledrive" <?php echo $googledrive_display?>>
1882
- <th>Google Drive Client ID:</th>
1883
- <td><input type="text" autocomplete="off" style="width:332px" name="updraft_googledrive_clientid" value="<?php echo get_option('updraft_googledrive_clientid') ?>" /></td>
1884
- </tr>
1885
- <tr class="googledrive" <?php echo $googledrive_display?>>
1886
- <th>Google Drive Client Secret:</th>
1887
- <td><input type="text" style="width:332px" name="updraft_googledrive_secret" value="<?php echo get_option('updraft_googledrive_secret'); ?>" /></td>
1888
- </tr>
1889
- <tr class="googledrive" <?php echo $googledrive_display?>>
1890
- <th>Google Drive Folder ID:</th>
1891
- <td><input type="text" style="width:332px" name="updraft_googledrive_remotepath" value="<?php echo get_option('updraft_googledrive_remotepath'); ?>" /> <em>(To get a folder's ID navigate to that folder in Google Drive in your web browser and copy the ID from your browser's address bar. It is the part that comes after <kbd>#folders/.</kbd> Leave empty to use your root folder)</em></td>
1892
- </tr>
1893
- <tr class="googledrive" <?php echo $googledrive_display?>>
1894
- <th>Authenticate with Google:</th>
1895
- <td><p><?php if (get_option('updraft_googledrive_token','xyz') != 'xyz') echo "<strong>(You appear to be already authenticated).</strong>"; ?> <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>
1896
-
1897
- </p>
1898
- </td>
1899
- </tr>
1900
-
1901
- <tr class="ftp" <?php echo $ftp_display?>>
1902
- <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Server:</a></th>
1903
- <td><input type="text" style="width:260px" name="updraft_server_address" value="<?php echo get_option('updraft_server_address'); ?>" /></td>
1904
- </tr>
1905
- <tr class="ftp" <?php echo $ftp_display?>>
1906
- <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Login:</a></th>
1907
- <td><input type="text" autocomplete="off" name="updraft_ftp_login" value="<?php echo get_option('updraft_ftp_login') ?>" /></td>
1908
- </tr>
1909
- <tr class="ftp" <?php echo $ftp_display?>>
1910
- <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Password:</a></th>
1911
- <td><input type="text" autocomplete="off" style="width:260px" name="updraft_ftp_pass" value="<?php echo get_option('updraft_ftp_pass'); ?>" /></td>
1912
- </tr>
1913
- <tr class="ftp" <?php echo $ftp_display?>>
1914
- <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">Remote Path:</a></th>
1915
- <td><input type="text" style="width:260px" name="updraft_ftp_remote_path" value="<?php echo get_option('updraft_ftp_remote_path'); ?>" /></td>
1916
- </tr>
1917
- <tr class="ftp-description" style="display:none">
1918
- <td colspan="2">An FTP remote path will look like '/home/backup/some/folder'</td>
1919
- </tr>
1920
  </table>
 
 
 
 
 
 
 
 
 
 
1921
  <table class="form-table" style="width:850px;">
1922
- <tr><td colspan="2"><h2>Advanced / Debugging Settings</h2></td></tr>
 
 
1923
  <tr>
1924
  <th>Backup Directory:</th>
1925
- <td><input type="text" name="updraft_dir" style="width:525px" value="<?php echo $updraft_dir ?>" /></td>
1926
  </tr>
1927
  <tr>
1928
  <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>
@@ -1967,30 +1753,12 @@ echo $delete_local; ?> /> <br>Check this to delete the local backup file (only s
1967
 
1968
 
1969
  <script type="text/javascript">
 
1970
  jQuery(document).ready(function() {
1971
  jQuery('#updraft-service').change(function() {
1972
- switch(jQuery(this).val()) {
1973
- case 'none':
1974
- jQuery('.deletelocal,.s3,.ftp,.googledrive,.s3-description,.ftp-description').fadeOut()
1975
- jQuery('.email,.email-complete').fadeIn()
1976
- break;
1977
- case 's3':
1978
- jQuery('.ftp,.ftp-description,.googledrive').fadeOut()
1979
- jQuery('.s3,.deletelocal,.email,.email-complete').fadeIn()
1980
- break;
1981
- case 'googledrive':
1982
- jQuery('.ftp,.ftp-description,.s3').fadeOut()
1983
- jQuery('.googledrive,.deletelocal,.googledrive,.email,.email-complete').fadeIn()
1984
- break;
1985
- case 'ftp':
1986
- jQuery('.googledrive,.s3,.s3-description').fadeOut()
1987
- jQuery('.ftp,.deletelocal,.email,.email-complete').fadeIn()
1988
- break;
1989
- case 'email':
1990
- jQuery('.s3,.ftp,.s3-description,.googledrive,.ftp-description,.email-complete').fadeOut()
1991
- jQuery('.email,.deletelocal').fadeIn()
1992
- break;
1993
- }
1994
  })
1995
  })
1996
  jQuery(window).load(function() {
@@ -2000,13 +1768,14 @@ echo $delete_local; ?> /> <br>Check this to delete the local backup file (only s
2000
  jQuery('#updraft-restore-progress').toggle(500)
2001
  })
2002
  })
 
2003
  </script>
2004
  <?php
2005
  }
2006
 
2007
  /*array2json provided by bin-co.com under BSD license*/
2008
  function array2json($arr) {
2009
- if(function_exists('json_encode')) return stripslashes(json_encode($arr)); //Latest versions of PHP already have this functionality.
2010
  $parts = array();
2011
  $is_list = false;
2012
 
@@ -2051,14 +1820,14 @@ echo $delete_local; ?> /> <br>Check this to delete the local backup file (only s
2051
  echo '<div id="updraftmessage" class="updated fade">'."<p>$message</p></div>";
2052
  }
2053
 
2054
- function show_admin_warning_googledrive() {
2055
- $this->show_admin_warning('<strong>UpdraftPlus notice:</strong> <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>');
2056
- }
2057
-
2058
  function show_admin_warning_unreadablelog() {
2059
  $this->show_admin_warning('<strong>UpdraftPlus notice:</strong> The log file could not be read.</a>');
2060
  }
2061
 
 
 
 
 
2062
 
2063
  }
2064
 
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, on separate schedules.
6
  Author: David Anderson.
7
+ Version: 1.0.20
8
  Donate link: http://david.dw-perspective.org.uk/donate
9
  License: GPLv3 or later
10
  Author URI: http://wordshell.net
56
 
57
  class UpdraftPlus {
58
 
59
+ var $version = '1.0.20';
60
+
61
+ // Choices will be shown in the admin menu in the order used here
62
+ var $backup_methods = array (
63
+ "s3" => "Amazon S3",
64
+ "googledrive" => "Google Drive",
65
+ "ftp" => "FTP",
66
+ "email" => "Email"
67
+ );
68
 
69
  var $dbhandle;
70
  var $errors = array();
72
  var $logfile_name = "";
73
  var $logfile_handle = false;
74
  var $backup_time;
 
 
75
 
76
  function __construct() {
77
+ // Initialisation actions - takes place on plugin load
78
  # Create admin page
79
  add_action('admin_menu', array($this,'add_admin_pages'));
80
  add_action('admin_init', array($this,'admin_init'));
91
  add_action('init', array($this, 'handle_url_actions'));
92
  }
93
 
94
+ // Handle actions passed on to method plugins; e.g. Google OAuth 2.0 - ?page=updraftplus&action=updraftmethod-googledrive-auth
95
  // Also handle action=downloadlog
96
  function handle_url_actions() {
97
  // First, basic security check: must be an admin page, with ability to manage options, with the right parameters
98
  if ( is_admin() && current_user_can('manage_options') && isset( $_GET['page'] ) && $_GET['page'] == 'updraftplus' && isset($_GET['action']) ) {
99
+ if (preg_match("/^updraftmethod-([a-z]+)-([a-z]+)$/", $_GET['action'], $matches) && file_exists(UPDRAFTPLUS_DIR.'/methods/'.$matches[1].'.php')) {
100
+ $method = $matches[1];
101
+ require_once(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php');
102
+ $call_class = "UpdraftPlus_BackupModule_".$method;
103
+ $call_method = "action_".$matches[2];
104
+ if (method_exists($call_class, $call_method)) call_user_func(array($call_class,$call_method));
 
 
 
105
  } elseif ($_GET['action'] == 'downloadlog' && isset($_GET['updraftplus_backup_nonce']) && preg_match("/^[0-9a-f]{12}$/",$_GET['updraftplus_backup_nonce'])) {
106
  $updraft_dir = $this->backups_dir_location();
107
  $log_file = $updraft_dir.'/log.'.$_GET['updraftplus_backup_nonce'].'.txt';
116
  }
117
  }
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  # Adds the settings link under the plugin on the plugin screen.
120
  function plugin_action_links($links, $file) {
121
  if ($file == plugin_basename(__FILE__)){
391
 
392
  // Dispatch to the relevant function
393
  function cloud_backup($backup_array) {
 
394
  $service = get_option('updraft_service');
395
  $this->log("Cloud backup selection: ".$service);
396
  @set_time_limit(900);
398
  $method_include = UPDRAFTPLUS_DIR.'/methods/'.$service.'.php';
399
  if (file_exists($method_include)) require_once($method_include);
400
 
401
+ $objname = "UpdraftPlus_BackupModule_${service}";
402
+ if (method_exists($objname, "backup")) {
403
  // New style - external, allowing more plugability
404
+ $remote_obj = new $objname;
405
+ $remote_obj->backup($backup_array);
406
  } else {
407
  $this->prune_retained_backups("local");
408
  }
409
  }
410
 
411
+ function prune_file($updraft_service, $dofile, $method_object = null, $object_passback = null ) {
412
+ $this->log("Delete this file: $dofile, service=$updraft_service");
413
+ $fullpath = trailingslashit(get_option('updraft_dir')).$dofile;
414
+ // delete it if it's locally available
415
+ if (file_exists($fullpath)) {
416
+ $this->log("Deleting local copy ($fullpath)");
417
+ @unlink($fullpath);
418
+ }
419
+
420
+ // Despatch to the particular method's deletion routine
421
+ if (!is_null($method_object)) $method_object->delete($dofile, $object_passback);
422
+ }
423
+
424
  // Carries out retain behaviour. Pass in a valid S3 or FTP object and path if relevant.
425
+ function prune_retained_backups($updraft_service, $backup_method_object = null, $backup_passback = null) {
426
+
427
  $this->log("Retain: beginning examination of existing backup sets");
428
+
429
  // Number of backups to retain
430
+ $updraft_retain = get_option('updraft_retain', 1);
431
+ $retain = (is_numeric($updraft_retain)) ? $updraft_retain : 1;
432
  $this->log("Retain: user setting: number to retain = $retain");
433
+
434
  // Returns an array, most recent first, of backup sets
435
  $backup_history = $this->get_backup_history();
436
  $db_backups_found = 0;
437
  $file_backups_found = 0;
438
  $this->log("Number of backup sets in history: ".count($backup_history));
439
+
440
  foreach ($backup_history as $backup_datestamp => $backup_to_examine) {
441
  // $backup_to_examine is an array of file names, keyed on db/plugins/themes/uploads
442
  // The new backup_history array is saved afterwards, so remember to unset the ones that are to be deleted
443
  $this->log("Examining backup set with datestamp: $backup_datestamp");
444
+
445
  if (isset($backup_to_examine['db'])) {
446
  $db_backups_found++;
447
  $this->log("$backup_datestamp: this set includes a database (".$backup_to_examine['db']."); db count is now $db_backups_found");
448
  if ($db_backups_found > $retain) {
449
  $this->log("$backup_datestamp: over retain limit; will delete this database");
450
+ $dofile = $backup_to_examine['db'];
451
+ if (!empty($dofile)) $this->prune_file($updraft_service, $dofile, $backup_method_object, $backup_passback);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  unset($backup_to_examine['db']);
453
  }
454
  }
461
  $file2 = isset($backup_to_examine['themes']) ? $backup_to_examine['themes'] : "";
462
  $file3 = isset($backup_to_examine['uploads']) ? $backup_to_examine['uploads'] : "";
463
  $file4 = isset($backup_to_examine['others']) ? $backup_to_examine['others'] : "";
464
+ foreach (array($file, $file2, $file3, $file4) as $dofile) {
465
+ if (!empty($dofile)) $this->prune_file($updraft_service, $dofile, $backup_method_object, $backup_passback);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
466
  }
467
  unset($backup_to_examine['plugins']);
468
  unset($backup_to_examine['themes']);
1088
  $method_include = UPDRAFTPLUS_DIR.'/methods/'.$service.'.php';
1089
  if (file_exists($method_include)) require_once($method_include);
1090
 
1091
+ $objname = "UpdraftPlus_BackupModule_${service}";
1092
+ if (method_exists($objname, "download")) {
1093
+ $remote_obj = new $objname;
1094
+ $remote_obj->download($file);
1095
  } else {
1096
  $this->error('Automatic backup restoration is not available with the method: $service.');
1097
  }
1128
  if(!class_exists('WP_Upgrader')) {
1129
  require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
1130
  }
1131
+ require_once(UPDRAFTPLUS_DIR.'/includes/updraft-restorer.php');
1132
  $restorer = new Updraft_Restorer();
1133
  $val = $restorer->restore_backup($fullpath,$type);
1134
  if(is_wp_error($val)) {
1249
  register_setting( 'updraft-options-group', 'updraft_retain', array($this,'retain_range') );
1250
  register_setting( 'updraft-options-group', 'updraft_encryptionphrase', 'wp_filter_nohtml_kses' );
1251
  register_setting( 'updraft-options-group', 'updraft_service', 'wp_filter_nohtml_kses' );
1252
+
1253
  register_setting( 'updraft-options-group', 'updraft_s3_login', 'wp_filter_nohtml_kses' );
1254
  register_setting( 'updraft-options-group', 'updraft_s3_pass', 'wp_filter_nohtml_kses' );
1255
  register_setting( 'updraft-options-group', 'updraft_s3_remote_path', 'wp_filter_nohtml_kses' );
1258
  register_setting( 'updraft-options-group', 'updraft_googledrive_remotepath', 'wp_filter_nohtml_kses' );
1259
  register_setting( 'updraft-options-group', 'updraft_ftp_login', 'wp_filter_nohtml_kses' );
1260
  register_setting( 'updraft-options-group', 'updraft_ftp_pass', 'wp_filter_nohtml_kses' );
 
 
1261
  register_setting( 'updraft-options-group', 'updraft_ftp_remote_path', 'wp_filter_nohtml_kses' );
1262
  register_setting( 'updraft-options-group', 'updraft_server_address', 'wp_filter_nohtml_kses' );
1263
+ register_setting( 'updraft-options-group', 'updraft_dir', 'wp_filter_nohtml_kses' );
1264
+ register_setting( 'updraft-options-group', 'updraft_email', 'wp_filter_nohtml_kses' );
1265
  register_setting( 'updraft-options-group', 'updraft_delete_local', 'absint' );
1266
  register_setting( 'updraft-options-group', 'updraft_debug_mode', 'absint' );
1267
  register_setting( 'updraft-options-group', 'updraft_include_plugins', 'absint' );
1269
  register_setting( 'updraft-options-group', 'updraft_include_uploads', 'absint' );
1270
  register_setting( 'updraft-options-group', 'updraft_include_others', 'absint' );
1271
  register_setting( 'updraft-options-group', 'updraft_include_others_exclude', 'wp_filter_nohtml_kses' );
1272
+
1273
  if (current_user_can('manage_options') && get_option('updraft_service') == "googledrive" && get_option('updraft_googledrive_clientid') != "" && get_option('updraft_googledrive_token','xyz') == 'xyz') {
1274
  add_action('admin_notices', array($this,'show_admin_warning_googledrive') );
1275
  }
1628
  ?>
1629
  <td><input type="text" name="updraft_retain" value="<?php echo $retain ?>" style="width:50px" /></td>
1630
  </tr>
1631
+ <tr class="backup-retain-description">
1632
+ <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>
1633
+ </tr>
1634
+ <tr>
1635
  <th>Email:</th>
1636
  <td><input type="text" style="width:260px" name="updraft_email" value="<?php echo get_option('updraft_email'); ?>" /> <br>Enter an address here to have a report sent (and the whole backup, if you choose) to it.</td>
1637
  </tr>
1638
+ <tr class="deletelocal">
1639
  <th>Delete local backup:</th>
1640
  <td><input type="checkbox" name="updraft_delete_local" value="1" <?php $delete_local = (get_option('updraft_delete_local')) ? 'checked="checked"' : "";
1641
  echo $delete_local; ?> /> <br>Check this to delete the local backup file (only sensible if you have enabled a remote backup (below), otherwise you will have no backup remaining).</td>
1642
  </tr>
1643
 
 
 
 
1644
  <tr>
1645
  <th>Database encryption phrase:</th>
1646
  <?php
1662
  <?php
1663
  $debug_mode = (get_option('updraft_debug_mode')) ? 'checked="checked"' : "";
1664
 
 
 
 
 
1665
  $set = 'selected="selected"';
1666
+
1667
+ // Should be one of s3, ftp, googledrive, email, or whatever else is added
1668
+ $active_service = get_option('updraft_service');
1669
+
1670
+ ?>
1671
+ <option value="none" <?php
1672
+ if ($active_service == "none") echo $set; ?>>None</option>
1673
+ <?php
1674
+ foreach ($this->backup_methods as $method => $description) {
1675
+ echo "<option value=\"$method\"";
1676
+ if ($active_service == $method) echo ' '.$set;
1677
+ echo '>'.$description;
1678
+ echo "</option>\n";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1679
  }
1680
  ?>
 
 
 
 
 
1681
  </select></td>
1682
  </tr>
1683
  <tr class="backup-service-description">
1684
  <td></td><td>Choose your backup method. If choosing &quot;E-Mail&quot;, then be aware that mail servers tend to have size limits; typically around 10-20Mb; backups larger than any limits will not arrive.</td>
1685
 
1686
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1687
  <?php
1688
+ foreach ($this->backup_methods as $method => $description) {
1689
+ require_once(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php');
1690
+ $call_method = "UpdraftPlus_BackupModule_$method";
1691
+ $call_method::config_print();
1692
+ }
1693
  ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1694
  </table>
1695
+ <script type="text/javascript">
1696
+ /* <![CDATA[ */
1697
+ jQuery(document).ready(function() {
1698
+ jQuery('.updraftplusmethod').hide();
1699
+ <?php
1700
+ if ($active_service) { echo "jQuery('.<?php echo $active_service ?>').show();"; }
1701
+ ?>
1702
+ });
1703
+ /* ]]> */
1704
+ </script>
1705
  <table class="form-table" style="width:850px;">
1706
+ <tr>
1707
+ <td colspan="2"><h2>Advanced / Debugging Settings</h2></td>
1708
+ </tr>
1709
  <tr>
1710
  <th>Backup Directory:</th>
1711
+ <td><input type="text" name="updraft_dir" style="width:525px" value="<?php echo htmlspecialchars($updraft_dir); ?>" /></td>
1712
  </tr>
1713
  <tr>
1714
  <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>
1753
 
1754
 
1755
  <script type="text/javascript">
1756
+ /* <![CDATA[ */
1757
  jQuery(document).ready(function() {
1758
  jQuery('#updraft-service').change(function() {
1759
+ jQuery('.updraftplusmethod').hide();
1760
+ var active_class = jQuery(this).val();
1761
+ jQuery('.'+active_class).show();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1762
  })
1763
  })
1764
  jQuery(window).load(function() {
1768
  jQuery('#updraft-restore-progress').toggle(500)
1769
  })
1770
  })
1771
+ /* ]]> */
1772
  </script>
1773
  <?php
1774
  }
1775
 
1776
  /*array2json provided by bin-co.com under BSD license*/
1777
  function array2json($arr) {
1778
+ if(function_exists('json_encode')) return stripslashes(json_encode($arr)); // PHP >= 5.2 already has this functionality.
1779
  $parts = array();
1780
  $is_list = false;
1781
 
1820
  echo '<div id="updraftmessage" class="updated fade">'."<p>$message</p></div>";
1821
  }
1822
 
 
 
 
 
1823
  function show_admin_warning_unreadablelog() {
1824
  $this->show_admin_warning('<strong>UpdraftPlus notice:</strong> The log file could not be read.</a>');
1825
  }
1826
 
1827
+ function show_admin_warning_googledrive() {
1828
+ $this->show_admin_warning('<strong>UpdraftPlus notice:</strong> <a href="?page=updraftplus&action=updraftmethod-googledrive-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>');
1829
+ }
1830
+
1831
 
1832
  }
1833