2016-11-28 21:52:15 -08:00
< ? php
if ( ! defined ( 'UPDRAFTPLUS_DIR' )) die ( 'No direct access allowed.' );
2018-01-26 15:50:15 +01:00
// Converted to multi-options (Feb 2017-) and previous options conversion removed: Yes
if ( ! class_exists ( 'UpdraftPlus_BackupModule' )) require_once ( UPDRAFTPLUS_DIR . '/methods/backup-module.php' );
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
class UpdraftPlus_BackupModule_googledrive extends UpdraftPlus_BackupModule {
2016-11-28 21:52:15 -08:00
private $client ;
2018-01-26 15:50:15 +01:00
2016-11-28 21:52:15 -08:00
private $ids_from_paths ;
2018-01-26 15:50:15 +01:00
private $client_id ;
private $callback_url ;
public function __construct () {
$this -> client_id = defined ( 'UPDRAFTPLUS_GOOGLEDRIVE_CLIENT_ID' ) ? UPDRAFTPLUS_GOOGLEDRIVE_CLIENT_ID : '916618189494-u3ehb1fl7u3meb63nb2b4fqi0r9pcfe2.apps.googleusercontent.com' ;
$this -> callback_url = defined ( 'UPDRAFTPLUS_GOOGLEDRIVE_CALLBACK_URL' ) ? UPDRAFTPLUS_GOOGLEDRIVE_CALLBACK_URL : 'https://auth.updraftplus.com/auth/googledrive' ;
}
2016-11-28 21:52:15 -08:00
public function action_auth () {
if ( isset ( $_GET [ 'state' ])) {
2018-01-26 15:50:15 +01:00
$parts = explode ( ':' , $_GET [ 'state' ]);
$state = $parts [ 0 ];
if ( 'success' == $state ) {
// If these are set then this is a request from our master app and the auth server has returned these to be saved.
if ( isset ( $_GET [ 'user_id' ]) && isset ( $_GET [ 'access_token' ])) {
$opts = $this -> get_options ();
$opts [ 'user_id' ] = base64_decode ( $_GET [ 'user_id' ]);
$opts [ 'tmp_access_token' ] = base64_decode ( $_GET [ 'access_token' ]);
// Unset this value if it is set as this is a fresh auth we will set this value in the next step
if ( isset ( $opts [ 'expires_in' ])) unset ( $opts [ 'expires_in' ]);
$this -> set_options ( $opts , true );
}
add_action ( 'all_admin_notices' , array ( $this , 'show_authed_admin_success' ));
} elseif ( 'token' == $state ) {
$this -> gdrive_auth_token ();
} elseif ( 'revoke' == $state ) {
$this -> gdrive_auth_revoke ();
}
} elseif ( isset ( $_GET [ 'updraftplus_googledriveauth' ])) {
if ( 'doit' == $_GET [ 'updraftplus_googledriveauth' ]) {
$this -> action_authenticate_storage ();
} elseif ( 'deauth' == $_GET [ 'updraftplus_googledriveauth' ]) {
$this -> action_deauthenticate_storage ();
}
2016-11-28 21:52:15 -08:00
}
}
2018-01-26 15:50:15 +01:00
/**
* This method overrides the parent method and lists the supported features of this remote storage option .
*
* @ return Array - an array of supported features ( any features not mentioned are asuumed to not be supported )
*/
public function get_supported_features () {
// This options format is handled via only accessing options via $this->get_options()
return array ( 'multi_options' , 'config_templates' , 'multi_storage' );
2016-11-28 21:52:15 -08:00
}
2018-01-26 15:50:15 +01:00
public function get_default_options () {
// parentid is deprecated since April 2014; it should not be in the default options (its presence is used to detect an upgraded-from-previous-SDK situation). For the same reason, 'folder' is also unset; which enables us to know whether new-style settings have ever been set.
return array (
'clientid' => '' ,
'secret' => '' ,
'token' => '' ,
);
2016-11-28 21:52:15 -08:00
}
private function root_id () {
2018-01-26 15:50:15 +01:00
$storage = $this -> get_storage ();
if ( empty ( $this -> root_id )) $this -> root_id = $storage -> about -> get () -> getRootFolderId ();
2016-11-28 21:52:15 -08:00
return $this -> root_id ;
}
public function id_from_path ( $path , $retry = true ) {
global $updraftplus ;
2018-01-26 15:50:15 +01:00
$storage = $this -> get_storage ();
2016-11-28 21:52:15 -08:00
try {
2018-01-26 15:50:15 +01:00
while ( '/' == substr ( $path , 0 , 1 )) {
$path = substr ( $path , 1 );
}
2016-11-28 21:52:15 -08:00
$cache_key = ( empty ( $path )) ? '/' : $path ;
if ( ! empty ( $this -> ids_from_paths ) && isset ( $this -> ids_from_paths [ $cache_key ])) return $this -> ids_from_paths [ $cache_key ];
$current_parent = $this -> root_id ();
$current_path = '/' ;
if ( ! empty ( $path )) {
foreach ( explode ( '/' , $path ) as $element ) {
$found = false ;
$sub_items = $this -> get_subitems ( $current_parent , 'dir' , $element );
foreach ( $sub_items as $item ) {
try {
if ( $item -> getTitle () == $element ) {
$found = true ;
$current_path .= $element . '/' ;
$current_parent = $item -> getId ();
break ;
}
} catch ( Exception $e ) {
$updraftplus -> log ( " Google Drive id_from_path: exception: " . $e -> getMessage () . ' (line: ' . $e -> getLine () . ', file: ' . $e -> getFile () . ')' );
}
}
if ( ! $found ) {
$ref = new Google_Service_Drive_ParentReference ;
$ref -> setId ( $current_parent );
$dir = new Google_Service_Drive_DriveFile ();
$dir -> setMimeType ( 'application/vnd.google-apps.folder' );
$dir -> setParents ( array ( $ref ));
$dir -> setTitle ( $element );
$updraftplus -> log ( " Google Drive: creating path: " . $current_path . $element );
2018-01-26 15:50:15 +01:00
$dir = $storage -> files -> insert (
2016-11-28 21:52:15 -08:00
$dir ,
array ( 'mimeType' => 'application/vnd.google-apps.folder' )
);
$current_path .= $element . '/' ;
$current_parent = $dir -> getId ();
}
}
}
if ( empty ( $this -> ids_from_paths )) $this -> ids_from_paths = array ();
$this -> ids_from_paths [ $cache_key ] = $current_parent ;
return $current_parent ;
} catch ( Exception $e ) {
$msg = $e -> getMessage ();
$updraftplus -> log ( " Google Drive id_from_path failure: exception ( " . get_class ( $e ) . " ): " . $msg . ' (line: ' . $e -> getLine () . ', file: ' . $e -> getFile () . ')' );
if ( is_a ( $e , 'Google_Service_Exception' ) && false !== strpos ( $msg , 'Invalid json in service response' ) && function_exists ( 'mb_strpos' )) {
// Aug 2015: saw a case where the gzip-encoding was not removed from the result
// https://stackoverflow.com/questions/10975775/how-to-determine-if-a-string-was-compressed
2018-01-26 15:50:15 +01:00
// @codingStandardsIgnoreLine
$is_gzip = false !== mb_strpos ( $msg , " \x1f " . " \x8b " . " \x08 " );
2016-11-28 21:52:15 -08:00
if ( $is_gzip ) $updraftplus -> log ( " Error: Response appears to be gzip-encoded still; something is broken in the client HTTP stack, and you should define UPDRAFTPLUS_GOOGLEDRIVE_DISABLEGZIP as true in your wp-config.php to overcome this. " );
}
2018-01-26 15:50:15 +01:00
// One retry
2016-11-28 21:52:15 -08:00
return ( $retry ) ? $this -> id_from_path ( $path , false ) : false ;
}
}
private function get_parent_id ( $opts ) {
2018-01-26 15:50:15 +01:00
$storage = $this -> get_storage ();
$filtered = apply_filters ( 'updraftplus_googledrive_parent_id' , false , $opts , $storage , $this );
2016-11-28 21:52:15 -08:00
if ( ! empty ( $filtered )) return $filtered ;
if ( isset ( $opts [ 'parentid' ])) {
if ( empty ( $opts [ 'parentid' ])) {
return $this -> root_id ();
} else {
$parent = ( is_array ( $opts [ 'parentid' ])) ? $opts [ 'parentid' ][ 'id' ] : $opts [ 'parentid' ];
}
} else {
$parent = $this -> id_from_path ( 'UpdraftPlus' );
}
return ( empty ( $parent )) ? $this -> root_id () : $parent ;
}
public function listfiles ( $match = 'backup_' ) {
2018-01-26 15:50:15 +01:00
$opts = $this -> get_options ();
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
$use_master = $this -> use_master ( $opts );
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
if ( ! $use_master ) {
if ( empty ( $opts [ 'secret' ]) || empty ( $opts [ 'clientid' ]) || empty ( $opts [ 'clientid' ])) return new WP_Error ( 'no_settings' , sprintf ( __ ( 'No %s settings were found' , 'updraftplus' ), __ ( 'Google Drive' , 'updraftplus' )));
} else {
if ( empty ( $opts [ 'user_id' ]) || empty ( $opts [ 'tmp_access_token' ])) return new WP_Error ( 'no_settings' , sprintf ( __ ( 'No %s settings were found' , 'updraftplus' ), __ ( 'Google Drive' , 'updraftplus' )));
}
$storage = $this -> bootstrap ();
if ( is_wp_error ( $storage ) || false == $storage ) return $storage ;
2016-11-28 21:52:15 -08:00
global $updraftplus ;
try {
$parent_id = $this -> get_parent_id ( $opts );
$sub_items = $this -> get_subitems ( $parent_id , 'file' );
} catch ( Exception $e ) {
return new WP_Error ( __ ( 'Google Drive list files: failed to access parent folder' , 'updraftplus' ) . " : " . $e -> getMessage () . ' (line: ' . $e -> getLine () . ', file: ' . $e -> getFile () . ')' );
}
$results = array ();
foreach ( $sub_items as $item ) {
$title = " (unknown) " ;
try {
$title = $item -> getTitle ();
if ( 0 === strpos ( $title , $match )) {
$results [] = array ( 'name' => $title , 'size' => $item -> getFileSize ());
}
} catch ( Exception $e ) {
$updraftplus -> log ( " Google Drive delete: exception: " . $e -> getMessage () . ' (line: ' . $e -> getLine () . ', file: ' . $e -> getFile () . ')' );
$ret = false ;
continue ;
}
}
return $results ;
}
2018-01-26 15:50:15 +01:00
/**
* Get a Google account access token using the refresh token
*
* @ param string $refresh_token Specify refresh token
* @ param string $client_id Specify Client ID
* @ param string $client_secret Specify client secret
* @ return boolean
*/
2016-11-28 21:52:15 -08:00
private function access_token ( $refresh_token , $client_id , $client_secret ) {
global $updraftplus ;
$updraftplus -> log ( " Google Drive: requesting access token: client_id= $client_id " );
$query_body = array (
'refresh_token' => $refresh_token ,
'client_id' => $client_id ,
'client_secret' => $client_secret ,
'grant_type' => 'refresh_token'
);
$result = wp_remote_post ( 'https://accounts.google.com/o/oauth2/token' ,
array (
'timeout' => '20' ,
'method' => 'POST' ,
'body' => $query_body
)
);
if ( is_wp_error ( $result )) {
$updraftplus -> log ( " Google Drive error when requesting access token " );
foreach ( $result -> get_error_messages () as $msg ) $updraftplus -> log ( " Error message: $msg " );
return false ;
} else {
$json_values = json_decode ( wp_remote_retrieve_body ( $result ), true );
2018-01-26 15:50:15 +01:00
if ( isset ( $json_values [ 'access_token' ])) {
2016-11-28 21:52:15 -08:00
$updraftplus -> log ( " Google Drive: successfully obtained access token " );
return $json_values [ 'access_token' ];
} else {
2018-01-26 15:50:15 +01:00
$response = json_decode ( $result [ 'body' ], true );
2016-11-28 21:52:15 -08:00
if ( ! empty ( $response [ 'error' ]) && 'deleted_client' == $response [ 'error' ]) {
2018-01-26 15:50:15 +01:00
$updraftplus -> log ( __ ( 'The client has been deleted from the Google Drive API console. Please create a new Google Drive project and reconnect with UpdraftPlus.' , 'updraftplus' ), 'error' );
2016-11-28 21:52:15 -08:00
}
$error_code = empty ( $response [ 'error' ]) ? 'no error code' : $response [ 'error' ];
$updraftplus -> log ( " Google Drive error ( $error_code ) when requesting access token: response does not contain access_token. Response: " . ( is_string ( $result [ 'body' ]) ? str_replace ( " \n " , '' , $result [ 'body' ]) : json_encode ( $result [ 'body' ])));
return false ;
}
}
}
2018-01-26 15:50:15 +01:00
/**
* This method will return a redirect URL depending on the parameter passed . It will either return the redirect for the user ' s site or the auth server .
*
* @ param Boolean $master - indicate whether we want the master redirect URL
* @ return String - a redirect URL
*/
private function redirect_uri ( $master = false ) {
if ( $master ) {
return $this -> callback_url ;
} else {
return UpdraftPlus_Options :: admin_page_url () . '?action=updraftmethod-googledrive-auth' ;
}
2016-11-28 21:52:15 -08:00
}
2018-01-26 15:50:15 +01:00
/**
* Acquire single - use authorization code from Google via OAuth 2.0
*
* @ param String $instance_id - the instance id of the settings we want to authenticate
*/
public function do_authenticate_storage ( $instance_id ) {
$opts = $this -> get_options ();
$use_master = $this -> use_master ( $opts );
2016-11-28 21:52:15 -08:00
// First, revoke any existing token, since Google doesn't appear to like issuing new ones
2018-01-26 15:50:15 +01:00
if ( ! empty ( $opts [ 'token' ]) && ! $use_master ) $this -> gdrive_auth_revoke ();
$prefixed_instance_id = ':' . $instance_id ;
2016-11-28 21:52:15 -08:00
// We use 'force' here for the approval_prompt, not 'auto', as that deals better with messy situations where the user authenticated, then changed settings
2018-01-26 15:50:15 +01:00
if ( $use_master ) {
$client_id = $this -> client_id ;
$token = 'token' . $prefixed_instance_id . $this -> redirect_uri ();
} else {
$client_id = $opts [ 'clientid' ];
$token = 'token' . $prefixed_instance_id ;
}
// We require access to all Google Drive files (not just ones created by this app - scope https://www.googleapis.com/auth/drive.file) - because we need to be able to re-scan storage for backups uploaded by other installs
2016-11-28 21:52:15 -08:00
$params = array (
'response_type' => 'code' ,
2018-01-26 15:50:15 +01:00
'client_id' => $client_id ,
'redirect_uri' => $this -> redirect_uri ( $use_master ),
'scope' => apply_filters ( 'updraft_googledrive_scope' , 'https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/userinfo.profile' ),
'state' => $token ,
2016-11-28 21:52:15 -08:00
'access_type' => 'offline' ,
'approval_prompt' => 'force'
);
2018-01-26 15:50:15 +01:00
if ( headers_sent ()) {
2016-11-28 21:52:15 -08:00
global $updraftplus ;
$updraftplus -> log ( sprintf ( __ ( 'The %s authentication could not go ahead, because something else on your site is breaking it. Try disabling your other plugins and switching to a default theme. (Specifically, you are looking for the component that sends output (most likely PHP warnings/errors) before the page begins. Turning off any debugging settings may also help).' , '' ), 'Google Drive' ), 'error' );
} else {
header ( 'Location: https://accounts.google.com/o/oauth2/auth?' . http_build_query ( $params , null , '&' ));
}
}
2018-01-26 15:50:15 +01:00
/**
* Revoke a Google account refresh token
* Returns the parameter fed in , so can be used as a WordPress options filter
* Can be called statically from UpdraftPlus :: googledrive_clientid_checkchange ()
*
* @ param boolean $unsetopt unset options is set to true unless otherwise specified
*/
public function gdrive_auth_revoke ( $unsetopt = true ) {
$opts = $this -> get_options ();
2016-11-28 21:52:15 -08:00
$ignore = wp_remote_get ( 'https://accounts.google.com/o/oauth2/revoke?token=' . $opts [ 'token' ]);
if ( $unsetopt ) {
$opts [ 'token' ] = '' ;
unset ( $opts [ 'ownername' ]);
2018-01-26 15:50:15 +01:00
$this -> set_options ( $opts , true );
2016-11-28 21:52:15 -08:00
}
}
2018-01-26 15:50:15 +01:00
/**
* Get a Google account refresh token using the code received from do_authenticate_storage
*/
2016-11-28 21:52:15 -08:00
public function gdrive_auth_token () {
2018-01-26 15:50:15 +01:00
$opts = $this -> get_options ();
if ( isset ( $_GET [ 'code' ])) {
2016-11-28 21:52:15 -08:00
$post_vars = array (
'code' => $_GET [ 'code' ],
'client_id' => $opts [ 'clientid' ],
'client_secret' => $opts [ 'secret' ],
'redirect_uri' => UpdraftPlus_Options :: admin_page_url () . '?action=updraftmethod-googledrive-auth' ,
'grant_type' => 'authorization_code'
);
2018-01-26 15:50:15 +01:00
$result = wp_remote_post ( 'https://accounts.google.com/o/oauth2/token' , array ( 'timeout' => 25 , 'method' => 'POST' , 'body' => $post_vars ));
2016-11-28 21:52:15 -08:00
if ( is_wp_error ( $result )) {
$add_to_url = " Bad response when contacting Google: " ;
2018-01-26 15:50:15 +01:00
foreach ( $result -> get_error_messages () as $message ) {
2016-11-28 21:52:15 -08:00
global $updraftplus ;
$updraftplus -> log ( " Google Drive authentication error: " . $message );
$add_to_url .= $message . " . " ;
}
header ( 'Location: ' . UpdraftPlus_Options :: admin_page_url () . '?page=updraftplus&error=' . urlencode ( $add_to_url ));
} else {
$json_values = json_decode ( wp_remote_retrieve_body ( $result ), true );
if ( isset ( $json_values [ 'refresh_token' ])) {
// Save token
$opts [ 'token' ] = $json_values [ 'refresh_token' ];
2018-01-26 15:50:15 +01:00
$this -> set_options ( $opts , true );
2016-11-28 21:52:15 -08:00
if ( isset ( $json_values [ 'access_token' ])) {
$opts [ 'tmp_access_token' ] = $json_values [ 'access_token' ];
2018-01-26 15:50:15 +01:00
$this -> set_options ( $opts , true );
2016-11-28 21:52:15 -08:00
// We do this to clear the GET parameters, otherwise WordPress sticks them in the _wp_referer in the form and brings them back, leading to confusion + errors
2018-01-26 15:50:15 +01:00
header ( 'Location: ' . UpdraftPlus_Options :: admin_page_url () . '?action=updraftmethod-googledrive-auth&page=updraftplus&state=success:' . urlencode ( $this -> get_instance_id ()));
2016-11-28 21:52:15 -08:00
}
} else {
$msg = __ ( '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' );
if ( isset ( $json_values [ 'error' ])) $msg .= ' ' . sprintf ( __ ( 'Error: %s' , 'updraftplus' ), $json_values [ 'error' ]);
header ( 'Location: ' . UpdraftPlus_Options :: admin_page_url () . '?page=updraftplus&error=' . urlencode ( $msg ));
}
}
} else {
header ( 'Location: ' . UpdraftPlus_Options :: admin_page_url () . '?page=updraftplus&error=' . urlencode ( __ ( 'Authorization failed' , 'updraftplus' )));
}
}
public function show_authed_admin_success () {
global $updraftplus_admin ;
2018-01-26 15:50:15 +01:00
$opts = $this -> get_options ();
2016-11-28 21:52:15 -08:00
if ( empty ( $opts [ 'tmp_access_token' ])) return ;
$updraftplus_tmp_access_token = $opts [ 'tmp_access_token' ];
$message = '' ;
try {
2018-01-26 15:50:15 +01:00
$storage = $this -> bootstrap ( $updraftplus_tmp_access_token );
if ( false != $storage && ! is_wp_error ( $storage )) {
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
$about = $storage -> about -> get ();
2016-11-28 21:52:15 -08:00
$quota_total = max ( $about -> getQuotaBytesTotal (), 1 );
$quota_used = $about -> getQuotaBytesUsed ();
$username = $about -> getName ();
$opts [ 'ownername' ] = $username ;
if ( is_numeric ( $quota_total ) && is_numeric ( $quota_used )) {
$available_quota = $quota_total - $quota_used ;
$used_perc = round ( $quota_used * 100 / $quota_total , 1 );
2018-01-26 15:50:15 +01:00
$message .= sprintf ( __ ( 'Your %s quota usage: %s %% used, %s available' , 'updraftplus' ), 'Google Drive' , $used_perc , round ( $available_quota / 1048576 , 1 ) . ' MB' );
2016-11-28 21:52:15 -08:00
}
}
} catch ( Exception $e ) {
if ( is_a ( $e , 'Google_Service_Exception' )) {
$errs = $e -> getErrors ();
$message .= __ ( 'However, subsequent access attempts failed:' , 'updraftplus' );
if ( is_array ( $errs )) {
$message .= '<ul style="list-style: disc inside;">' ;
foreach ( $errs as $err ) {
$message .= '<li>' ;
if ( ! empty ( $err [ 'reason' ])) $message .= '<strong>' . htmlspecialchars ( $err [ 'reason' ]) . ':</strong> ' ;
if ( ! empty ( $err [ 'message' ])) {
$message .= htmlspecialchars ( $err [ 'message' ]);
} else {
$message .= htmlspecialchars ( serialize ( $err ));
}
$message .= '</li>' ;
}
$message .= '</ul>' ;
} else {
$message .= htmlspecialchars ( serialize ( $errs ));
}
}
}
2018-01-26 15:50:15 +01:00
$updraftplus_admin -> show_admin_warning ( __ ( 'Success' , 'updraftplus' ) . ': ' . sprintf ( __ ( 'you have authenticated your %s account.' , 'updraftplus' ), __ ( 'Google Drive' , 'updraftplus' )) . ' ' . (( ! empty ( $username )) ? sprintf ( __ ( 'Name: %s.' , 'updraftplus' ), $username ) . ' ' : '' ) . $message );
2016-11-28 21:52:15 -08:00
unset ( $opts [ 'tmp_access_token' ]);
2018-01-26 15:50:15 +01:00
$this -> set_options ( $opts , true );
2016-11-28 21:52:15 -08:00
}
2018-01-26 15:50:15 +01:00
/**
* This function just does the formalities , and off - loads the main work to upload_file
*
* @ param array $backup_array
*/
2016-11-28 21:52:15 -08:00
public function backup ( $backup_array ) {
global $updraftplus , $updraftplus_backup ;
2018-01-26 15:50:15 +01:00
$storage = $this -> bootstrap ();
if ( false == $storage || is_wp_error ( $storage )) return $storage ;
2016-11-28 21:52:15 -08:00
$updraft_dir = trailingslashit ( $updraftplus -> backups_dir_location ());
2018-01-26 15:50:15 +01:00
$opts = $this -> get_options ();
2016-11-28 21:52:15 -08:00
try {
$parent_id = $this -> get_parent_id ( $opts );
} catch ( Exception $e ) {
$updraftplus -> log ( " Google Drive upload: failed to access parent folder: " . $e -> getMessage () . ' (line: ' . $e -> getLine () . ', file: ' . $e -> getFile () . ')' );
2018-01-26 15:50:15 +01:00
$updraftplus -> log ( sprintf ( __ ( 'Failed to upload to %s' , 'updraftplus' ), __ ( 'Google Drive' , 'updraftplus' )) . ': ' . __ ( 'failed to access parent folder' , 'updraftplus' ) . ' (' . $e -> getMessage () . ')' , 'error' );
2016-11-28 21:52:15 -08:00
return false ;
}
foreach ( $backup_array as $file ) {
$available_quota = - 1 ;
try {
2018-01-26 15:50:15 +01:00
$about = $storage -> about -> get ();
2016-11-28 21:52:15 -08:00
$quota_total = max ( $about -> getQuotaBytesTotal (), 1 );
$quota_used = $about -> getQuotaBytesUsed ();
$available_quota = $quota_total - $quota_used ;
2018-01-26 15:50:15 +01:00
$message = " Google Drive quota usage: used= " . round ( $quota_used / 1048576 , 1 ) . " MB, total= " . round ( $quota_total / 1048576 , 1 ) . " MB, available= " . round ( $available_quota / 1048576 , 1 ) . " MB " ;
2016-11-28 21:52:15 -08:00
$updraftplus -> log ( $message );
} catch ( Exception $e ) {
$updraftplus -> log ( " Google Drive quota usage: failed to obtain this information: " . $e -> getMessage ());
}
$file_path = $updraft_dir . $file ;
$file_name = basename ( $file_path );
$updraftplus -> log ( " $file_name : Attempting to upload to Google Drive (into folder id: $parent_id ) " );
$filesize = filesize ( $file_path );
$already_failed = false ;
2018-01-26 15:50:15 +01:00
if ( - 1 != $available_quota ) {
2016-11-28 21:52:15 -08:00
if ( $filesize > $available_quota ) {
$already_failed = true ;
$updraftplus -> log ( " File upload expected to fail: file ( $file_name ) size is $filesize b, whereas available quota is only $available_quota b " );
2018-01-26 15:50:15 +01:00
$updraftplus -> log ( sprintf ( __ ( " Account full: your %s account has only %d bytes left, but the file to be uploaded is %d bytes " , 'updraftplus' ), __ ( 'Google Drive' , 'updraftplus' ), $available_quota , $filesize ), + 'error' );
2016-11-28 21:52:15 -08:00
}
}
if ( ! $already_failed && $filesize > 10737418240 ) {
2018-01-26 15:50:15 +01:00
// 10GB
2016-11-28 21:52:15 -08:00
$updraftplus -> log ( " File upload expected to fail: file ( $file_name ) size is $filesize b ( " . round ( $filesize / 1073741824 , 4 ) . " GB), whereas Google Drive's limit is 10GB (1073741824 bytes) " );
2018-01-26 15:50:15 +01:00
$updraftplus -> log ( sprintf ( __ ( " Upload expected to fail: the %s limit for any single file is %s, whereas this file is %s GB (%d bytes) " , 'updraftplus' ), __ ( 'Google Drive' , 'updraftplus' ), '10GB (1073741824)' , round ( $filesize / 1073741824 , 4 ), $filesize ), 'warning' );
}
2016-11-28 21:52:15 -08:00
try {
$timer_start = microtime ( true );
if ( $this -> upload_file ( $file_path , $parent_id )) {
2018-01-26 15:50:15 +01:00
$updraftplus -> log ( 'OK: Archive ' . $file_name . ' uploaded to Google Drive in ' . ( round ( microtime ( true ) - $timer_start , 2 )) . ' seconds' );
2016-11-28 21:52:15 -08:00
$updraftplus -> uploaded_file ( $file );
} else {
2018-01-26 15:50:15 +01:00
$updraftplus -> log ( " ERROR: $file_name : Failed to upload to Google Drive " );
$updraftplus -> log ( " $file_name : " . sprintf ( __ ( 'Failed to upload to %s' , 'updraftplus' ), __ ( 'Google Drive' , 'updraftplus' )), 'error' );
2016-11-28 21:52:15 -08:00
}
} catch ( Exception $e ) {
$msg = $e -> getMessage ();
$updraftplus -> log ( " ERROR: Google Drive upload error: " . $msg . ' (line: ' . $e -> getLine () . ', file: ' . $e -> getFile () . ')' );
if ( false !== ( $p = strpos ( $msg , 'The user has exceeded their Drive storage quota' ))) {
2018-01-26 15:50:15 +01:00
$updraftplus -> log ( " $file_name : " . sprintf ( __ ( 'Failed to upload to %s' , 'updraftplus' ), __ ( 'Google Drive' , 'updraftplus' )) . ': ' . substr ( $msg , $p ), 'error' );
2016-11-28 21:52:15 -08:00
} else {
2018-01-26 15:50:15 +01:00
$updraftplus -> log ( " $file_name : " . sprintf ( __ ( 'Failed to upload to %s' , 'updraftplus' ), __ ( 'Google Drive' , 'updraftplus' )), 'error' );
2016-11-28 21:52:15 -08:00
}
$this -> client -> setDefer ( false );
}
}
return null ;
}
public function bootstrap ( $access_token = false ) {
global $updraftplus ;
2018-01-26 15:50:15 +01:00
$storage = $this -> get_storage ();
if ( ! empty ( $storage ) && is_object ( $storage ) && is_a ( $storage , 'Google_Service_Drive' )) return $storage ;
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
$opts = $this -> get_options ();
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
$use_master = $this -> use_master ( $opts );
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
if ( ! $use_master ) {
2016-11-28 21:52:15 -08:00
if ( empty ( $opts [ 'token' ]) || empty ( $opts [ 'clientid' ]) || empty ( $opts [ 'secret' ])) {
2018-01-26 15:50:15 +01:00
$updraftplus -> log ( 'Google Drive: this account is not authorised' );
$updraftplus -> log ( 'Google Drive: ' . __ ( 'Account is not authorized.' , 'updraftplus' ), 'error' , 'googledrivenotauthed' );
return new WP_Error ( 'not_authorized' , __ ( 'Account is not authorized.' , 'updraftplus' ) . ' (Google Drive)' );
}
if ( empty ( $access_token )) {
$access_token = $this -> access_token ( $opts [ 'token' ], $opts [ 'clientid' ], $opts [ 'secret' ]);
}
} else {
if ( empty ( $opts [ 'user_id' ])) {
2016-11-28 21:52:15 -08:00
$updraftplus -> log ( 'Google Drive: this account is not authorised' );
$updraftplus -> log ( 'Google Drive: ' . __ ( 'Account is not authorized.' , 'updraftplus' ), 'error' , 'googledrivenotauthed' );
return new WP_Error ( 'not_authorized' , __ ( 'Account is not authorized.' , 'updraftplus' ));
}
2018-01-26 15:50:15 +01:00
if ( ! isset ( $opts [ 'expires_in' ]) || $opts [ 'expires_in' ] < time ()) {
$user_id = empty ( $opts [ 'user_id' ]) ? '' : $opts [ 'user_id' ];
$args = array (
'code' => 'ud_googledrive_code' ,
'user_id' => $user_id ,
);
$result = wp_remote_post ( $this -> callback_url , array (
'timeout' => 60 ,
'headers' => apply_filters ( 'updraftplus_auth_headers' , '' ),
'body' => $args
));
if ( is_wp_error ( $result )) {
$body = array ( 'result' => 'error' , 'error' => $result -> get_error_code (), 'error_description' => $result -> get_error_message ());
} else {
$body_json = wp_remote_retrieve_body ( $result );
$body = json_decode ( $body_json , true );
}
if ( ! empty ( $body [ 'result' ]) && 'error' == $body [ 'result' ]) {
$access_token = new WP_Error ( $body [ 'error' ], empty ( $body [ 'error_description' ]) ? __ ( 'Have not yet obtained an access token from Google - you need to authorise or re-authorise your connection to Google Drive.' , 'updraftplus' ) : $body [ 'error_description' ]);
} else {
$result_body_json = base64_decode ( $body [ 0 ]);
$result_body = json_decode ( $result_body_json );
if ( isset ( $result_body -> access_token )) {
$access_token = array (
'access_token' => $result_body -> access_token ,
'created' => time (),
'expires_in' => $result_body -> expires_in ,
'refresh_token' => ''
);
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
$opts [ 'tmp_access_token' ] = $access_token ;
$opts [ 'expires_in' ] = $access_token [ 'created' ] + $access_token [ 'expires_in' ] - 30 ;
$this -> set_options ( $opts , true );
} else {
$access_token = '' ;
}
}
} else {
$access_token = $opts [ 'tmp_access_token' ];
}
}
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
// Do we have an access token?
if ( empty ( $access_token ) || is_wp_error ( $access_token )) {
$message = 'ERROR: Have not yet obtained an access token from Google (has the user authorised?)' ;
$extra = '' ;
if ( is_wp_error ( $access_token )) {
$message .= ' (' . $access_token -> get_error_message () . ') (' . $access_token -> get_error_code () . ')' ;
$extra = ' (' . $access_token -> get_error_message () . ') (' . $access_token -> get_error_code () . ')' ;
}
$updraftplus -> log ( $message );
$updraftplus -> log ( __ ( 'Have not yet obtained an access token from Google - you need to authorise or re-authorise your connection to Google Drive.' , 'updraftplus' ) . $extra , 'error' );
return $access_token ;
}
2016-11-28 21:52:15 -08:00
$spl = spl_autoload_functions ();
if ( is_array ( $spl )) {
// Workaround for Google Drive CDN plugin's autoloader
if ( in_array ( 'wpbgdc_autoloader' , $spl )) spl_autoload_unregister ( 'wpbgdc_autoloader' );
// http://www.wpdownloadmanager.com/download/google-drive-explorer/ - but also others, since this is the default function name used by the Google SDK
if ( in_array ( 'google_api_php_client_autoload' , $spl )) spl_autoload_unregister ( 'google_api_php_client_autoload' );
}
if (( ! class_exists ( 'Google_Config' ) || ! class_exists ( 'Google_Client' ) || ! class_exists ( 'Google_Service_Drive' ) || ! class_exists ( 'Google_Http_Request' )) && ! function_exists ( 'google_api_php_client_autoload_updraftplus' )) {
2018-01-26 15:50:15 +01:00
include_once ( UPDRAFTPLUS_DIR . '/includes/Google/autoload.php' );
2016-11-28 21:52:15 -08:00
}
if ( ! class_exists ( 'UpdraftPlus_Google_Http_MediaFileUpload' )) {
2018-01-26 15:50:15 +01:00
include_once ( UPDRAFTPLUS_DIR . '/includes/google-extensions.php' );
2016-11-28 21:52:15 -08:00
}
$config = new Google_Config ();
$config -> setClassConfig ( 'Google_IO_Abstract' , 'request_timeout_seconds' , 60 );
2018-01-26 15:50:15 +01:00
// In our testing, $storage->about->get() fails if gzip is not disabled when using the stream wrapper
2016-11-28 21:52:15 -08:00
if ( ! function_exists ( 'curl_version' ) || ! function_exists ( 'curl_exec' ) || ( defined ( 'UPDRAFTPLUS_GOOGLEDRIVE_DISABLEGZIP' ) && UPDRAFTPLUS_GOOGLEDRIVE_DISABLEGZIP )) {
$config -> setClassConfig ( 'Google_Http_Request' , 'disable_gzip' , true );
}
2018-01-26 15:50:15 +01:00
if ( ! $use_master ) {
$client_id = $opts [ 'clientid' ];
$client_secret = $opts [ 'secret' ];
$refresh_token = $opts [ 'token' ];
} else {
$client_id = $this -> client_id ;
$client_secret = '' ;
$refresh_token = '' ;
2016-11-28 21:52:15 -08:00
}
2018-01-26 15:50:15 +01:00
$client = new Google_Client ( $config );
$client -> setClientId ( $client_id );
$client -> setClientSecret ( $client_secret );
// $client->setUseObjects(true);
if ( ! $use_master ) {
$client -> setAccessToken ( json_encode ( array (
'access_token' => $access_token ,
'refresh_token' => $opts [ 'token' ]
)));
} else {
$client -> setAccessToken ( json_encode ( $access_token ));
2016-11-28 21:52:15 -08:00
}
$io = $client -> getIo ();
$setopts = array ();
if ( is_a ( $io , 'Google_IO_Curl' )) {
$setopts [ CURLOPT_SSL_VERIFYPEER ] = UpdraftPlus_Options :: get_updraft_option ( 'updraft_ssl_disableverify' ) ? false : true ;
if ( ! UpdraftPlus_Options :: get_updraft_option ( 'updraft_ssl_useservercerts' )) $setopts [ CURLOPT_CAINFO ] = UPDRAFTPLUS_DIR . '/includes/cacert.pem' ;
// Raise the timeout from the default of 15
$setopts [ CURLOPT_TIMEOUT ] = 60 ;
$setopts [ CURLOPT_CONNECTTIMEOUT ] = 15 ;
if ( defined ( 'UPDRAFTPLUS_IPV4_ONLY' ) && UPDRAFTPLUS_IPV4_ONLY ) $setopts [ CURLOPT_IPRESOLVE ] = CURL_IPRESOLVE_V4 ;
} elseif ( is_a ( $io , 'Google_IO_Stream' )) {
$setopts [ 'timeout' ] = 60 ;
2018-01-26 15:50:15 +01:00
// We had to modify the SDK to support this
// https://wiki.php.net/rfc/tls-peer-verification - before PHP 5.6, there is no default CA file
2016-11-28 21:52:15 -08:00
if ( ! UpdraftPlus_Options :: get_updraft_option ( 'updraft_ssl_useservercerts' ) || ( version_compare ( PHP_VERSION , '5.6.0' , '<' ))) $setopts [ 'cafile' ] = UPDRAFTPLUS_DIR . '/includes/cacert.pem' ;
if ( UpdraftPlus_Options :: get_updraft_option ( 'updraft_ssl_disableverify' )) $setopts [ 'disable_verify_peer' ] = true ;
}
$io -> setOptions ( $setopts );
2018-01-26 15:50:15 +01:00
$storage = new Google_Service_Drive ( $client );
2016-11-28 21:52:15 -08:00
$this -> client = $client ;
2018-01-26 15:50:15 +01:00
$this -> set_storage ( $storage );
2016-11-28 21:52:15 -08:00
try {
2018-01-26 15:50:15 +01:00
// Get the folder name, if not previously known (this is for the legacy situation where an id, not a name, was stored)
2016-11-28 21:52:15 -08:00
if ( ! empty ( $opts [ 'parentid' ]) && ( ! is_array ( $opts [ 'parentid' ]) || empty ( $opts [ 'parentid' ][ 'name' ]))) {
$rootid = $this -> root_id ();
$title = '' ;
$parentid = is_array ( $opts [ 'parentid' ]) ? $opts [ 'parentid' ][ 'id' ] : $opts [ 'parentid' ];
while (( ! empty ( $parentid ) && $parentid != $rootid )) {
2018-01-26 15:50:15 +01:00
$resource = $storage -> files -> get ( $parentid );
2016-11-28 21:52:15 -08:00
$title = ( $title ) ? $resource -> getTitle () . '/' . $title : $resource -> getTitle ();
$parents = $resource -> getParents ();
if ( is_array ( $parents ) && count ( $parents ) > 0 ) {
$parent = array_shift ( $parents );
$parentid = is_a ( $parent , 'Google_Service_Drive_ParentReference' ) ? $parent -> getId () : false ;
} else {
$parentid = false ;
}
}
if ( ! empty ( $title )) {
$opts [ 'parentid' ] = array (
'id' => ( is_array ( $opts [ 'parentid' ]) ? $opts [ 'parentid' ][ 'id' ] : $opts [ 'parentid' ]),
'name' => $title
);
2018-01-26 15:50:15 +01:00
$this -> set_options ( $opts , true );
2016-11-28 21:52:15 -08:00
}
}
} catch ( Exception $e ) {
$updraftplus -> log ( " Google Drive: failed to obtain name of parent folder: " . $e -> getMessage () . ' (line: ' . $e -> getLine () . ', file: ' . $e -> getFile () . ')' );
2018-01-26 15:50:15 +01:00
}
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
return $storage ;
2016-11-28 21:52:15 -08:00
}
2018-01-26 15:50:15 +01:00
/**
* Acts as a WordPress options filter
*
* @ param Array $google - An array of Google Drive options
* @ return Array - the returned array can either be the set of updated Google Drive settings or a WordPress error array
*/
public function options_filter ( $google ) {
global $updraftplus ;
// Get the current options (and possibly update them to the new format)
$opts = $updraftplus -> update_remote_storage_options_format ( 'googledrive' );
if ( is_wp_error ( $opts )) {
if ( 'recursion' !== $opts -> get_error_code ()) {
$msg = " Google Drive ( " . $opts -> get_error_code () . " ): " . $opts -> get_error_message ();
$updraftplus -> log ( $msg );
error_log ( " UpdraftPlus: $msg " );
}
// The saved options had a problem; so, return the new ones
return $google ;
}
// $opts = UpdraftPlus_Options::get_updraft_option('updraft_googledrive');
if ( ! is_array ( $google )) return $opts ;
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
// Remove instances that no longer exist
foreach ( $opts [ 'settings' ] as $instance_id => $storage_options ) {
if ( ! isset ( $google [ 'settings' ][ $instance_id ])) unset ( $opts [ 'settings' ][ $instance_id ]);
}
if ( empty ( $google [ 'settings' ])) return $opts ;
foreach ( $google [ 'settings' ] as $instance_id => $storage_options ) {
if ( empty ( $opts [ 'settings' ][ $instance_id ][ 'user_id' ])) {
$old_client_id = ( empty ( $opts [ 'settings' ][ $instance_id ][ 'clientid' ])) ? '' : $opts [ 'settings' ][ $instance_id ][ 'clientid' ];
if ( ! empty ( $opts [ 'settings' ][ $instance_id ][ 'token' ]) && $old_client_id != $storage_options [ 'clientid' ]) {
include_once ( UPDRAFTPLUS_DIR . '/methods/googledrive.php' );
$updraftplus -> register_wp_http_option_hooks ();
$googledrive = new UpdraftPlus_BackupModule_googledrive ();
$googledrive -> gdrive_auth_revoke ( false );
$updraftplus -> register_wp_http_option_hooks ( false );
$opts [ 'settings' ][ $instance_id ][ 'token' ] = '' ;
unset ( $opts [ 'settings' ][ $instance_id ][ 'ownername' ]);
}
}
foreach ( $storage_options as $key => $value ) {
// Trim spaces - I got support requests from users who didn't spot the spaces they introduced when copy/pasting
$opts [ 'settings' ][ $instance_id ][ $key ] = ( 'clientid' == $key || 'secret' == $key ) ? trim ( $value ) : $value ;
}
if ( isset ( $opts [ 'settings' ][ $instance_id ][ 'folder' ])) {
$opts [ 'settings' ][ $instance_id ][ 'folder' ] = apply_filters ( 'updraftplus_options_googledrive_foldername' , 'UpdraftPlus' , $opts [ 'settings' ][ $instance_id ][ 'folder' ]);
unset ( $opts [ 'settings' ][ $instance_id ][ 'parentid' ]);
}
}
return $opts ;
}
/**
* This function checks if the user has any options for Google Drive saved or if they have defined to use a custom app and if they have we will not use the master Google Drive app and allow them to enter their own client ID and secret
*
* @ param Array $opts - the Google Drive options array
* @ return Bool - a bool value to indicate if we should use the master app or not
*/
protected function use_master ( $opts ) {
if (( ! empty ( $opts [ 'clientid' ]) && ! empty ( $opts [ 'secret' ])) || ( defined ( 'UPDRAFTPLUS_CUSTOM_GOOGLEDRIVE_APP' ) && UPDRAFTPLUS_CUSTOM_GOOGLEDRIVE_APP )) return false ;
return true ;
}
/**
* Returns array of Google_Service_Drive_DriveFile objects
*
* @ param string $parent_id This is the Parent ID
* @ param string $type This is the type of file or directory but by default it is set to 'any' unless specified
* @ param string $match This will specify which match is used for the SQL but by default it is set to 'backup_' unless specified
* @ return array
*/
2016-11-28 21:52:15 -08:00
private function get_subitems ( $parent_id , $type = 'any' , $match = 'backup_' ) {
2018-01-26 15:50:15 +01:00
$storage = $this -> get_storage ();
2016-11-28 21:52:15 -08:00
$q = '"' . $parent_id . '" in parents and trashed = false' ;
if ( 'dir' == $type ) {
$q .= ' and mimeType = "application/vnd.google-apps.folder"' ;
} elseif ( 'file' == $type ) {
$q .= ' and mimeType != "application/vnd.google-apps.folder"' ;
}
2018-01-26 15:50:15 +01:00
// We used to use 'contains' in both cases, but this exposed some bug that might be in the SDK or at the Google end - a result that matched for = was not returned with contains
2016-11-28 21:52:15 -08:00
if ( ! empty ( $match )) {
if ( 'backup_' == $match ) {
$q .= " and title contains ' $match ' " ;
} else {
$q .= " and title = ' $match ' " ;
}
}
$result = array ();
2018-01-26 15:50:15 +01:00
$page_token = null ;
2016-11-28 21:52:15 -08:00
do {
try {
// Default for maxResults is 100
$parameters = array ( 'q' => $q , 'maxResults' => 200 );
2018-01-26 15:50:15 +01:00
if ( $page_token ) {
$parameters [ 'pageToken' ] = $page_token ;
2016-11-28 21:52:15 -08:00
}
2018-01-26 15:50:15 +01:00
$files = $storage -> files -> listFiles ( $parameters );
2016-11-28 21:52:15 -08:00
$result = array_merge ( $result , $files -> getItems ());
2018-01-26 15:50:15 +01:00
$page_token = $files -> getNextPageToken ();
2016-11-28 21:52:15 -08:00
} catch ( Exception $e ) {
global $updraftplus ;
$updraftplus -> log ( " Google Drive: get_subitems: An error occurred (will not fetch further): " . $e -> getMessage ());
2018-01-26 15:50:15 +01:00
$page_token = null ;
2016-11-28 21:52:15 -08:00
}
2018-01-26 15:50:15 +01:00
} while ( $page_token );
2016-11-28 21:52:15 -08:00
return $result ;
2018-01-26 15:50:15 +01:00
}
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
public function delete ( $files , $data = null , $sizeinfo = array ()) {
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
if ( is_string ( $files )) $files = array ( $files );
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
$storage = $this -> bootstrap ();
if ( is_wp_error ( $storage ) || false == $storage ) return $storage ;
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
$opts = $this -> get_options ();
2016-11-28 21:52:15 -08:00
global $updraftplus ;
try {
$parent_id = $this -> get_parent_id ( $opts );
$sub_items = $this -> get_subitems ( $parent_id , 'file' );
} catch ( Exception $e ) {
$updraftplus -> log ( " Google Drive delete: failed to access parent folder: " . $e -> getMessage () . ' (line: ' . $e -> getLine () . ', file: ' . $e -> getFile () . ')' );
return false ;
}
$ret = true ;
foreach ( $sub_items as $item ) {
$title = " (unknown) " ;
try {
$title = $item -> getTitle ();
if ( in_array ( $title , $files )) {
2018-01-26 15:50:15 +01:00
$storage -> files -> delete ( $item -> getId ());
2016-11-28 21:52:15 -08:00
$updraftplus -> log ( " $title : Deletion successful " );
2018-01-26 15:50:15 +01:00
if (( $key = array_search ( $title , $files )) !== false ) {
2016-11-28 21:52:15 -08:00
unset ( $files [ $key ]);
}
}
} catch ( Exception $e ) {
$updraftplus -> log ( " Google Drive delete: exception: " . $e -> getMessage () . ' (line: ' . $e -> getLine () . ', file: ' . $e -> getFile () . ')' );
$ret = false ;
continue ;
}
}
foreach ( $files as $file ) {
$updraftplus -> log ( " $file : Deletion failed: file was not found " );
}
return $ret ;
}
2018-01-26 15:50:15 +01:00
/**
* Used internally to upload files
*
* @ param String $file - the full path to the file to upload
* @ param String $parent_id - the internal Google Drive folder identifier
* @ param Boolean $try_again - whether to retry in the event of a problem
*
* @ return Boolean - success or failure state
*/
2016-11-28 21:52:15 -08:00
private function upload_file ( $file , $parent_id , $try_again = true ) {
global $updraftplus ;
2018-01-26 15:50:15 +01:00
$opts = $this -> get_options ();
2016-11-28 21:52:15 -08:00
$basename = basename ( $file );
2018-01-26 15:50:15 +01:00
$storage = $this -> get_storage ();
2016-11-28 21:52:15 -08:00
$client = $this -> client ;
2018-01-26 15:50:15 +01:00
// See: https://github.com/google/google-api-php-client/blob/master/examples/fileupload.php (at time of writing, only shows how to upload in chunks, not how to resume)
2016-11-28 21:52:15 -08:00
$client -> setDefer ( true );
$local_size = filesize ( $file );
$gdfile = new Google_Service_Drive_DriveFile ();
$gdfile -> title = $basename ;
$ref = new Google_Service_Drive_ParentReference ;
$ref -> setId ( $parent_id );
$gdfile -> setParents ( array ( $ref ));
$size = 0 ;
2018-01-26 15:50:15 +01:00
$request = $storage -> files -> insert ( $gdfile );
2016-11-28 21:52:15 -08:00
$chunk_bytes = 1048576 ;
$hash = md5 ( $file );
2018-01-26 15:50:15 +01:00
$transkey = 'resume_' . $hash ;
2016-11-28 21:52:15 -08:00
// This is unset upon completion, so if it is set then we are resuming
2018-01-26 15:50:15 +01:00
$possible_location = $this -> jobdata_get ( $transkey , null , 'gd' . $transkey );
2016-11-28 21:52:15 -08:00
if ( is_array ( $possible_location )) {
2018-01-26 15:50:15 +01:00
$headers = array ( 'content-range' => " bytes */ " . $local_size );
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
$http_request = new Google_Http_Request (
2016-11-28 21:52:15 -08:00
$possible_location [ 0 ],
'PUT' ,
$headers ,
''
);
2018-01-26 15:50:15 +01:00
$response = $this -> client -> getIo () -> makeRequest ( $http_request );
2016-11-28 21:52:15 -08:00
$can_resume = false ;
$response_http_code = $response -> getResponseHttpCode ();
2018-01-26 15:50:15 +01:00
if ( 200 == $response_http_code || 201 == $response_http_code ) {
2016-11-28 21:52:15 -08:00
$client -> setDefer ( false );
2018-01-26 15:50:15 +01:00
$this -> jobdata_delete ( $transkey , 'gd' . $transkey );
2016-11-28 21:52:15 -08:00
$updraftplus -> log ( " $basename : upload appears to be already complete (HTTP code: $response_http_code ) " );
return true ;
}
if ( 308 == $response_http_code ) {
$range = $response -> getResponseHeader ( 'range' );
if ( ! empty ( $range ) && preg_match ( '/bytes=0-(\d+)$/' , $range , $matches )) {
$can_resume = true ;
$possible_location [ 1 ] = $matches [ 1 ] + 1 ;
$updraftplus -> log ( " $basename : upload already began; attempting to resume from byte " . $matches [ 1 ]);
}
}
if ( ! $can_resume ) {
$updraftplus -> log ( " $basename : upload already began; attempt to resume did not succeed (HTTP code: " . $response_http_code . " ) " );
}
}
// UpdraftPlus_Google_Http_MediaFileUpload extends Google_Http_MediaFileUpload, with a few extra methods to change private properties to public ones
$media = new UpdraftPlus_Google_Http_MediaFileUpload (
$client ,
$request ,
(( '.zip' == substr ( $basename , - 4 , 4 )) ? 'application/zip' : 'application/octet-stream' ),
null ,
true ,
$chunk_bytes
);
$media -> setFileSize ( $local_size );
if ( ! empty ( $possible_location )) {
2018-01-26 15:50:15 +01:00
// $media->resumeUri = $possible_location[0];
// $media->progress = $possible_location[1];
2016-11-28 21:52:15 -08:00
$media -> updraftplus_setResumeUri ( $possible_location [ 0 ]);
$media -> updraftplus_setProgress ( $possible_location [ 1 ]);
$size = $possible_location [ 1 ];
}
if ( $size >= $local_size ) return true ;
$status = false ;
if ( false == ( $handle = fopen ( $file , 'rb' ))) {
$updraftplus -> log ( " Google Drive: failed to open file: $basename " );
2018-01-26 15:50:15 +01:00
$updraftplus -> log ( " $basename : " . sprintf ( __ ( '%s Error: Failed to open local file' , 'updraftplus' ), 'Google Drive' ), 'error' );
2016-11-28 21:52:15 -08:00
return false ;
}
if ( $size > 0 && 0 != fseek ( $handle , $size )) {
$updraftplus -> log ( " Google Drive: failed to fseek file: $basename , $size " );
$updraftplus -> log ( " $basename (fseek): " . sprintf ( __ ( '%s Error: Failed to open local file' , 'updraftplus' ), 'Google Drive' ), 'error' );
return false ;
}
$pointer = $size ;
try {
while ( ! $status && ! feof ( $handle )) {
$chunk = fread ( $handle , $chunk_bytes );
2018-01-26 15:50:15 +01:00
// Error handling??
2016-11-28 21:52:15 -08:00
$pointer += strlen ( $chunk );
$status = $media -> nextChunk ( $chunk );
2018-01-26 15:50:15 +01:00
$this -> jobdata_set ( $transkey , array ( $media -> updraftplus_getResumeUri (), $media -> getProgress ()));
2016-11-28 21:52:15 -08:00
$updraftplus -> record_uploaded_chunk ( round ( 100 * $pointer / $local_size , 1 ), $media -> getProgress (), $file );
}
} catch ( Google_Service_Exception $e ) {
$updraftplus -> log ( " ERROR: Google Drive upload error ( " . get_class ( $e ) . " ): " . $e -> getMessage () . ' (line: ' . $e -> getLine () . ', file: ' . $e -> getFile () . ')' );
$client -> setDefer ( false );
fclose ( $handle );
2018-01-26 15:50:15 +01:00
$this -> jobdata_delete ( $transkey , 'gd' . $transkey );
2016-11-28 21:52:15 -08:00
if ( false == $try_again ) throw ( $e );
2018-01-26 15:50:15 +01:00
// Reset this counter to prevent the something_useful_happened condition's possibility being sent into the far future and potentially missed
2016-11-28 21:52:15 -08:00
if ( $updraftplus -> current_resumption > 9 ) $updraftplus -> jobdata_set ( 'uploaded_lastreset' , $updraftplus -> current_resumption );
return $this -> upload_file ( $file , $parent_id , false );
}
// The final value of $status will be the data from the API for the object
// that has been uploaded.
$result = false ;
2018-01-26 15:50:15 +01:00
if ( false != $status ) $result = $status ;
2016-11-28 21:52:15 -08:00
fclose ( $handle );
$client -> setDefer ( false );
2018-01-26 15:50:15 +01:00
$this -> jobdata_delete ( $transkey , 'gd' . $transkey );
2016-11-28 21:52:15 -08:00
return true ;
}
public function download ( $file ) {
global $updraftplus ;
2018-01-26 15:50:15 +01:00
$storage = $this -> bootstrap ();
if ( false == $storage || is_wp_error ( $storage )) return false ;
2016-11-28 21:52:15 -08:00
global $updraftplus ;
2018-01-26 15:50:15 +01:00
$opts = $this -> get_options ();
2016-11-28 21:52:15 -08:00
try {
$parent_id = $this -> get_parent_id ( $opts );
2018-01-26 15:50:15 +01:00
// $gdparent = $storage->files->get($parent_id);
2016-11-28 21:52:15 -08:00
$sub_items = $this -> get_subitems ( $parent_id , 'file' );
} catch ( Exception $e ) {
$updraftplus -> log ( " Google Drive delete: failed to access parent folder: " . $e -> getMessage () . ' (line: ' . $e -> getLine () . ', file: ' . $e -> getFile () . ')' );
return false ;
}
$found = false ;
foreach ( $sub_items as $item ) {
if ( $found ) continue ;
$title = " (unknown) " ;
try {
$title = $item -> getTitle ();
if ( $title == $file ) {
$gdfile = $item ;
$found = $item -> getId ();
$size = $item -> getFileSize ();
}
} catch ( Exception $e ) {
$updraftplus -> log ( " Google Drive download: exception: " . $e -> getMessage () . ' (line: ' . $e -> getLine () . ', file: ' . $e -> getFile () . ')' );
}
}
if ( false === $found ) {
$updraftplus -> log ( " Google Drive download: failed: file not found " );
2018-01-26 15:50:15 +01:00
$updraftplus -> log ( " $file : " . sprintf ( __ ( " %s Error " , 'updraftplus' ), 'Google Drive' ) . " : " . __ ( 'File not found' , 'updraftplus' ), 'error' );
2016-11-28 21:52:15 -08:00
return false ;
}
$download_to = $updraftplus -> backups_dir_location () . '/' . $file ;
$existing_size = ( file_exists ( $download_to )) ? filesize ( $download_to ) : 0 ;
if ( $existing_size >= $size ) {
$updraftplus -> log ( 'Google Drive download: was already downloaded (' . filesize ( $download_to ) . " / $size bytes) " );
return true ;
}
2018-01-26 15:50:15 +01:00
// Chunk in units of 2MB
2016-11-28 21:52:15 -08:00
$chunk_size = 2097152 ;
try {
while ( $existing_size < $size ) {
$end = min ( $existing_size + $chunk_size , $size );
if ( $existing_size > 0 ) {
$put_flag = FILE_APPEND ;
$headers = array ( 'Range' => 'bytes=' . $existing_size . '-' . $end );
} else {
$put_flag = null ;
$headers = ( $end < $size ) ? array ( 'Range' => 'bytes=0-' . $end ) : array ();
}
2018-01-26 15:50:15 +01:00
$pstart = round ( 100 * $existing_size / $size , 1 );
$pend = round ( 100 * $end / $size , 1 );
2016-11-28 21:52:15 -08:00
$updraftplus -> log ( " Requesting byte range: $existing_size - $end ( $pstart - $pend %) " );
$request = $this -> client -> getAuth () -> sign ( new Google_Http_Request ( $gdfile -> getDownloadUrl (), 'GET' , $headers , null ));
$http_request = $this -> client -> getIo () -> makeRequest ( $request );
$http_response = $http_request -> getResponseHttpCode ();
if ( 200 == $http_response || 206 == $http_response ) {
file_put_contents ( $download_to , $http_request -> getResponseBody (), $put_flag );
} else {
$updraftplus -> log ( " Google Drive download: failed: unexpected HTTP response code: " . $http_response );
$updraftplus -> log ( sprintf ( __ ( " %s download: failed: file not found " , 'updraftplus' ), 'Google Drive' ), 'error' );
return false ;
}
clearstatcache ();
$new_size = filesize ( $download_to );
if ( $new_size > $existing_size ) {
$existing_size = $new_size ;
} else {
throw new Exception ( 'Failed to obtain any new data at size: ' . $existing_size );
}
}
} catch ( Exception $e ) {
$updraftplus -> log ( " Google Drive download: exception: " . $e -> getMessage () . ' (line: ' . $e -> getLine () . ', file: ' . $e -> getFile () . ')' );
}
return true ;
}
2018-01-26 15:50:15 +01:00
/**
* Get the pre configuration template
*
* @ return String - the template
*/
public function get_pre_configuration_template () {
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
global $updraftplus_admin ;
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
$classes = $this -> get_css_classes ( false );
?>
< tr class = " <?php echo $classes . ' ' . 'googledrive_pre_config_container';?> " >
< td colspan = " 2 " >
< img src = " https://developers.google.com/drive/images/drive_logo.png " alt = " <?php _e('Google Drive', 'updraftplus');?> " >
{{ #unless use_master}}
< br >
2016-11-28 21:52:15 -08:00
< ? php
2018-01-26 15:50:15 +01:00
$admin_page_url = UpdraftPlus_Options :: admin_page_url ();
// This is advisory - so the fact it doesn't match IPv6 addresses isn't important
if ( preg_match ( '#^(https?://(\d+)\.(\d+)\.(\d+)\.(\d+))/#i' , $admin_page_url , $matches )) {
echo '<p><strong>' . htmlspecialchars ( sprintf ( __ ( " %s does not allow authorisation of sites hosted on direct IP addresses. You will need to change your site's address (%s) before you can use %s for storage. " , 'updraftplus' ), __ ( 'Google Drive' , 'updraftplus' ), $matches [ 1 ], __ ( 'Google Drive' , 'updraftplus' ))) . '</strong></p>' ;
} else {
// If we are not using the master app then show them the instructions for Client ID and Secret
?>
< p >< a href = " <?php echo apply_filters('updraftplus_com_link', 'https://updraftplus.com/support/configuring-google-drive-api-access-in-updraftplus/');
?> "><strong><?php _e('For longer help, including screenshots, follow this link. The description below is sufficient for more expert users.', 'updraftplus');?></strong></a></p>
< p >< a href = " https://console.developers.google.com " >< ? php _e ( 'Follow this link to your Google API Console, and there activate the Drive API and create a Client ID in the API Access section.' , 'updraftplus' ); ?> </a> <?php _e("Select 'Web Application' as the application type.", 'updraftplus');?></p><p><?php echo htmlspecialchars(__('You must add the following as the authorised redirect URI (under "More Options") when asked', 'updraftplus'));?>: <kbd><?php echo UpdraftPlus_Options::admin_page_url().'?action=updraftmethod-googledrive-auth'; ?></kbd> <?php _e('N.B. If you install UpdraftPlus on several WordPress sites, then you cannot re-use your project; you must create a new one from your Google API console for each site.', 'updraftplus');?>
</ p >
< ? php
}
?>
{{ / unless }}
</ td >
2016-11-28 21:52:15 -08:00
</ tr >
2018-01-26 15:50:15 +01:00
< ? php
}
2016-11-28 21:52:15 -08:00
2018-01-26 15:50:15 +01:00
/**
* Get the configuration template
*
* @ return String - the template , ready for substitutions to be carried out
*/
public function get_configuration_template () {
$classes = $this -> get_css_classes ();
ob_start ();
?>
{{ #unless use_master}}
< tr class = " <?php echo $classes ;?> " >
< th >< ? php echo __ ( 'Google Drive' , 'updraftplus' ) . ' ' . __ ( 'Client ID' , 'updraftplus' ); ?> :</th>
< td >< input type = " text " autocomplete = " off " style = " width:442px " < ? php $this -> output_settings_field_name_and_id ( 'clientid' ); ?> value="{{clientid}}" /><br><em><?php _e('If Google later shows you the message "invalid_client", then you did not enter a valid client ID here.', 'updraftplus');?></em></td>
</ tr >
< tr class = " <?php echo $classes ;?> " >
< th >< ? php echo __ ( 'Google Drive' , 'updraftplus' ) . ' ' . __ ( 'Client Secret' , 'updraftplus' ); ?> :</th>
< td >< input type = " <?php echo apply_filters('updraftplus_admin_secret_field_type', 'password'); ?> " style = " width:442px " < ? php $this -> output_settings_field_name_and_id ( 'secret' ); ?> value="{{secret}}" /></td>
</ tr >
{{ / unless }}
{{ #if is_google_enhanced_addon}}
2016-11-28 21:52:15 -08:00
< ? php
2018-01-26 15:50:15 +01:00
echo apply_filters ( 'updraftplus_options_googledrive_others' , '' , $this );
2016-11-28 21:52:15 -08:00
?>
2018-01-26 15:50:15 +01:00
{{ else }}
{{ #if parentid}}
< tr class = " <?php echo $classes ;?> " >
< th >< ? php echo __ ( 'Google Drive' , 'updraftplus' ) . ' ' . __ ( 'Folder' , 'updraftplus' ); ?> :</th>
< td >
< input type = " hidden " < ? php $this -> output_settings_field_name_and_id ( array ( 'parentid' , 'id' )); ?> value="{{parentid_str}}">
< input type = " text " title = " { { parentid_str}} " readonly = " readonly " style = " width:442px " value = " { { showparent}} " >
{{ #if is_id_number_instruction}}
< em >< ? php echo __ ( " <strong>This is NOT a folder name</strong>. " , 'updraftplus' ) . ' ' . __ ( 'It is an ID number internal to Google Drive' , 'updraftplus' ); ?> </em>
{{ else }}
< input type = " hidden " < ? php $this -> output_settings_field_name_and_id ( array ( 'parentid' , 'name' )); ?> ' value="{{parentid.name}}">';
{{ / if }}
{{ else }}
< tr class = " <?php echo $classes ;?> " >
< th >< ? php echo __ ( 'Google Drive' , 'updraftplus' ) . ' ' . __ ( 'Folder' , 'updraftplus' ); ?> :</th>
< td >
< input type = " text " readonly = " readonly " style = " width:442px " < ? php $this -> output_settings_field_name_and_id ( 'folder' ); ?> value="UpdraftPlus" />
{{ / if }}
< br >
< em >
< a href = " <?php echo apply_filters( " updraftplus_com_link " , " https :// updraftplus . com / shop / updraftplus - premium / " );?> " >
< ? php echo __ ( 'To be able to set a custom folder name, use UpdraftPlus Premium.' , 'updraftplus' ); ?>
</ a >
</ em >
</ td >
</ tr >
{{ / if }}
< tr class = " <?php echo $classes ;?> " >
< th >< ? php _e ( 'Authenticate with Google' , 'updraftplus' ); ?> :</th>
< td >
{{ #if is_authenticate_with_google}}
< ? php
echo '<p>' ;
echo __ ( " <strong>(You appear to be already authenticated,</strong> though you can authenticate again to refresh your access if you've had a problem). " , 'updraftplus' );
$this -> get_deauthentication_link ();
echo '</p>' ;
?>
{{ #if use_master}}
< p >< a target = " _blank " href = " https://myaccount.google.com/permissions " >< ? php _e ( 'To de-authorize UpdraftPlus (all sites) from accessing your Google Drive, follow this link to your Google account settings.' , 'updraftplus' ); ?> </a></p>
{{ / if }}
{{ / if }}
{{ #if is_ownername_display}}
< br >
< ? php
echo sprintf ( __ ( " Account holder's name: %s. " , 'updraftplus' ), '{{ownername}}' ) . ' ' ;
?>
{{ / if }}
< ? php
echo '<p>' ;
$this -> get_authentication_link ();
echo '</p>' ;
?>
2016-11-28 21:52:15 -08:00
</ td >
</ tr >
< ? php
2018-01-26 15:50:15 +01:00
return ob_get_clean ();
}
/**
* Modifies handerbar template options
*
* @ param array $opts
* @ return array - Modified handerbar template options
*/
public function transform_options_for_template ( $opts ) {
$opts [ 'use_master' ] = $this -> use_master ( $opts );
$opts [ 'is_google_enhanced_addon' ] = class_exists ( 'UpdraftPlus_Addon_Google_Enhanced' ) ? true : false ;
if ( isset ( $opts [ 'parentid' ])) {
$opts [ 'parentid_str' ] = ( is_array ( $opts [ 'parentid' ])) ? $opts [ 'parentid' ][ 'id' ] : $opts [ 'parentid' ];
$opts [ 'showparent' ] = ( is_array ( $opts [ 'parentid' ]) && ! empty ( $opts [ 'parentid' ][ 'name' ])) ? $opts [ 'parentid' ][ 'name' ] : $opts [ 'parentid_str' ];
$opts [ 'is_id_number_instruction' ] = ( ! empty ( $parentid ) && ( ! is_array ( $opts [ 'parentid' ]) || empty ( $opts [ 'parentid' ][ 'name' ])));
}
$opts [ 'is_authenticate_with_google' ] = ( ! empty ( $opts [ 'token' ]) || ! empty ( $opts [ 'user_id' ]));
$opts [ 'is_ownername_display' ] = (( ! empty ( $opts [ 'token' ]) || ! empty ( $opts [ 'user_id' ])) && ! empty ( $opts [ 'ownername' ]));
$opts = apply_filters ( 'updraftplus_options_googledrive_options' , $opts );
return $opts ;
}
/**
* Gives settings keys which values should not passed to handlebarsjs context .
* The settings stored in UD in the database sometimes also include internal information that it would be best not to send to the front - end ( so that it can ' t be stolen by a man - in - the - middle attacker )
*
* @ return array - Settings array keys which should be filtered
*/
public function filter_frontend_settings_keys () {
return array (
'expires_in' ,
'tmp_access_token' ,
'token' ,
'user_id' ,
);
}
2016-11-28 21:52:15 -08:00
}