| 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 |
self::gdrive_auth_token(); |
| 12 |
elseif ( $_GET['state'] == 'revoke' ) |
| 13 |
self::gdrive_auth_revoke(); |
| 14 |
} elseif (isset($_GET['updraftplus_googleauth'])) { |
| 15 |
self::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' => UpdraftPlus_Options::get_updraft_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='.UpdraftPlus_Options::get_updraft_option('updraft_googledrive_token')); |
| 61 |
UpdraftPlus_Options::update_updraft_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' => UpdraftPlus_Options::get_updraft_option('updraft_googledrive_clientid'), |
| 71 |
'client_secret' => UpdraftPlus_Options::get_updraft_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 |
UpdraftPlus_Options::update_updraft_option('updraft_googledrive_token', $json_values['refresh_token']); // Save token |
| 84 |
header('Location: '.admin_url('options-general.php?page=updraftplus&message=' . __( 'Google Drive authorisation was successful.', 'updraftplus' ) ) ); |
| 85 |
} |
| 86 |
else { |
| 87 |
header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'No refresh token was received from Google. This often means that you entered your client secret wrongly, or that you have not yet re-authenticated (below) since correcting it. Re-check it, then follow the link to authenticate again. Finally, if that does not work, then use expert mode to wipe all your settings, create a new Google client ID/secret, and start again.', '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( UpdraftPlus_Options::get_updraft_option('updraft_googledrive_token'), UpdraftPlus_Options::get_updraft_option('updraft_googledrive_clientid'), UpdraftPlus_Options::get_updraft_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(UpdraftPlus_Options::get_updraft_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, UpdraftPlus_Options::get_updraft_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->log("ERROR: $file_name: Failed to upload to Google Drive" ); |
| 121 |
$updraftplus->error("$file_name: Failed to upload to Google Drive" ); |
| 122 |
} |
| 123 |
} |
| 124 |
$updraftplus->prune_retained_backups("googledrive", $this, null); |
| 125 |
} |
| 126 |
|
| 127 |
function delete($file) { |
| 128 |
global $updraftplus; |
| 129 |
$ids = UpdraftPlus_Options::get_updraft_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 |
UpdraftPlus_Options::update_updraft_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->log("Error details: ".$msg); |
| 175 |
$updraftplus->error($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 |
// This counter is only used for when deciding what to log |
| 190 |
$counter = 0; |
| 191 |
do { |
| 192 |
$log_string = ($counter == 0) ? "Upload %: $d, URL: $res" : "Upload %: $d"; |
| 193 |
$counter++; if ($counter >= 20) $counter=0; |
| 194 |
$updraftplus->log($log_string); |
| 195 |
|
| 196 |
$res = $gdocs_object->upload_chunk(); |
| 197 |
if (is_string($res)) set_transient($transkey, $res, 3600*3); |
| 198 |
$p = $gdocs_object->get_upload_percentage(); |
| 199 |
if ( $p - $d >= 1 ) { |
| 200 |
$b = intval( $p - $d ); |
| 201 |
// echo '<span style="width:' . $b . '%"></span>'; |
| 202 |
$d += $b; |
| 203 |
} |
| 204 |
// $this->options['backup_list'][$id]['speed'] = $this->gdocs->get_upload_speed(); |
| 205 |
} while ( is_string( $res ) ); |
| 206 |
// echo '</div>'; |
| 207 |
|
| 208 |
if ( is_wp_error( $res ) || $res !== true) { |
| 209 |
$updraftplus->log( "An error occurred during GoogleDrive upload (2)" ); |
| 210 |
$updraftplus->error( "An error occurred during GoogleDrive upload (see log for more details" ); |
| 211 |
if (is_wp_error( $res )) { |
| 212 |
foreach ($res->get_error_messages() as $msg) $updraftplus->log($msg); |
| 213 |
} |
| 214 |
return false; |
| 215 |
} |
| 216 |
|
| 217 |
$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."); |
| 218 |
|
| 219 |
delete_transient($transkey); |
| 220 |
// unset( $this->options['backup_list'][$id]['location'], $this->options['backup_list'][$id]['attempt'] ); |
| 221 |
} |
| 222 |
|
| 223 |
return $gdocs_object->get_file_id(); |
| 224 |
|
| 225 |
// $this->update_quota(); |
| 226 |
// Google's "user info" service |
| 227 |
// if ( empty( $this->options['user_info'] ) ) $this->set_user_info(); |
| 228 |
|
| 229 |
} |
| 230 |
|
| 231 |
function download($file) { |
| 232 |
|
| 233 |
global $updraftplus; |
| 234 |
|
| 235 |
if( !class_exists('UpdraftPlus_GDocs')) require_once(UPDRAFTPLUS_DIR.'/includes/class-gdocs.php'); |
| 236 |
|
| 237 |
// Do we have an access token? |
| 238 |
if ( !$access_token = $this->access_token( UpdraftPlus_Options::get_updraft_option('updraft_googledrive_token'), UpdraftPlus_Options::get_updraft_option('updraft_googledrive_clientid'), UpdraftPlus_Options::get_updraft_option('updraft_googledrive_secret') )) { |
| 239 |
$updraftplus->error('Have not yet obtained an access token from Google (has the user authorised?)'); |
| 240 |
return false; |
| 241 |
} |
| 242 |
|
| 243 |
$this->gdocs_access_token = $access_token; |
| 244 |
|
| 245 |
// Make sure $this->gdocs is a UpdraftPlus_GDocs object, or give an error |
| 246 |
if ( is_wp_error( $e = $this->need_gdocs() ) ) return false; |
| 247 |
$gdocs_object = $this->gdocs; |
| 248 |
|
| 249 |
$ids = UpdraftPlus_Options::get_updraft_option('updraft_file_ids', array()); |
| 250 |
if (!isset($ids[$file])) { |
| 251 |
$updraftplus->error("Google Drive error: $file: could not download: could not find a record of the Google Drive file ID for this file"); |
| 252 |
return; |
| 253 |
} else { |
| 254 |
$content_link = $gdocs_object->get_content_link( $ids[$file], $file ); |
| 255 |
if (is_wp_error($content_link)) { |
| 256 |
$updraftplus->error("Could not find $file in order to download it (id: ".$ids[$file].")"); |
| 257 |
foreach ($content_link->get_error_messages() as $msg) $updraftplus->error($msg); |
| 258 |
return false; |
| 259 |
} |
| 260 |
// Actually download the thing |
| 261 |
$download_to = trailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_dir')).$file; |
| 262 |
$gdocs_object->download_data($content_link, $download_to); |
| 263 |
|
| 264 |
if (filesize($download_to) >0) { |
| 265 |
return true; |
| 266 |
} else { |
| 267 |
$updraftplus->error("Google Drive error: zero-size file was downloaded"); |
| 268 |
return false; |
| 269 |
} |
| 270 |
|
| 271 |
} |
| 272 |
|
| 273 |
return; |
| 274 |
|
| 275 |
} |
| 276 |
|
| 277 |
// This function modified from wordpress.org/extend/plugins/backup, by Sorin Iclanzan, under the GPLv3 or later at your choice |
| 278 |
function need_gdocs() { |
| 279 |
|
| 280 |
global $updraftplus; |
| 281 |
|
| 282 |
if ( ! $this->is_gdocs($this->gdocs) ) { |
| 283 |
if ( UpdraftPlus_Options::get_updraft_option('updraft_googledrive_token') == "" || UpdraftPlus_Options::get_updraft_option('updraft_googledrive_clientid') == "" || UpdraftPlus_Options::get_updraft_option('updraft_googledrive_secret') == "" ) { |
| 284 |
$updraftplus->log("GoogleDrive: this account is not authorised"); |
| 285 |
return new WP_Error( "not_authorized", "Account is not authorized." ); |
| 286 |
} |
| 287 |
|
| 288 |
if ( is_wp_error( $this->gdocs_access_token ) ) return $access_token; |
| 289 |
|
| 290 |
$this->gdocs = new UpdraftPlus_GDocs( $this->gdocs_access_token ); |
| 291 |
// We need to be able to upload at least one chunk within the timeout (at least, we have seen an error report where the failure to do this seemed to be the cause) |
| 292 |
// If we assume a user has at least 16kb/s (we saw one user with as low as 22kb/s), and that their provider may allow them only 15s, then we have the following settings |
| 293 |
$this->gdocs->set_option( 'chunk_size', 0.2 ); # 0.2Mb; change from default of 512Kb |
| 294 |
$this->gdocs->set_option( 'request_timeout', 15 ); # Change from default of 5s |
| 295 |
$this->gdocs->set_option( 'max_resume_attempts', 36 ); # Doesn't look like GDocs class actually uses this anyway |
| 296 |
} |
| 297 |
return true; |
| 298 |
} |
| 299 |
|
| 300 |
// This function taken from wordpress.org/extend/plugins/backup, by Sorin Iclanzan, under the GPLv3 or later at your choice |
| 301 |
function is_gdocs( $thing ) { |
| 302 |
if ( is_object( $thing ) && is_a( $thing, 'UpdraftPlus_GDocs' ) ) return true; |
| 303 |
return false; |
| 304 |
} |
| 305 |
|
| 306 |
function config_print() { |
| 307 |
?> |
| 308 |
<tr class="updraftplusmethod googledrive"> |
| 309 |
<td>Google Drive:</td> |
| 310 |
<td> |
| 311 |
<img src="https://developers.google.com/drive/images/drive_logo.png" alt="Google Drive"> |
| 312 |
<p><em>Google Drive 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></p> |
| 313 |
</td> |
| 314 |
</tr> |
| 315 |
<tr class="updraftplusmethod googledrive"> |
| 316 |
<th>Google Drive:</th> |
| 317 |
<td> |
| 318 |
<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> |
| 319 |
<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 (under "More Options") when asked. N.B. If you install UpdraftPlus on several WordPress sites, then you cannot re-use your client ID; you must create a new one from your Google API console for each blog. |
| 320 |
|
| 321 |
<?php |
| 322 |
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."; } |
| 323 |
?> |
| 324 |
</p> |
| 325 |
</td> |
| 326 |
</tr> |
| 327 |
|
| 328 |
<tr class="updraftplusmethod googledrive"> |
| 329 |
<th>Google Drive Client ID:</th> |
| 330 |
<td><input type="text" autocomplete="off" style="width:332px" name="updraft_googledrive_clientid" value="<?php echo htmlspecialchars(UpdraftPlus_Options::get_updraft_option('updraft_googledrive_clientid')) ?>" /><br><em>If Google later shows you the message "invalid_client", then you did not enter a valid client ID here.</em></td> |
| 331 |
</tr> |
| 332 |
<tr class="updraftplusmethod googledrive"> |
| 333 |
<th>Google Drive Client Secret:</th> |
| 334 |
<td><input type="text" style="width:332px" name="updraft_googledrive_secret" value="<?php echo htmlspecialchars(UpdraftPlus_Options::get_updraft_option('updraft_googledrive_secret')); ?>" /></td> |
| 335 |
</tr> |
| 336 |
<tr class="updraftplusmethod googledrive"> |
| 337 |
<th>Google Drive Folder ID:</th> |
| 338 |
<td><input type="text" style="width:332px" name="updraft_googledrive_remotepath" value="<?php echo htmlspecialchars(UpdraftPlus_Options::get_updraft_option('updraft_googledrive_remotepath')); ?>" /> <em><strong>This is NOT a folder name</strong>. 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> |
| 339 |
</tr> |
| 340 |
<tr class="updraftplusmethod googledrive"> |
| 341 |
<th>Authenticate with Google:</th> |
| 342 |
<td><p><?php if (UpdraftPlus_Options::get_updraft_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 "Save Changes" below), then come back here once and click this link to complete authentication with Google.</a> |
| 343 |
</p> |
| 344 |
</td> |
| 345 |
</tr> |
| 346 |
<?php |
| 347 |
} |
| 348 |
|
| 349 |
} |
| 350 |
|
| 351 |
?> |