| 1 |
<?php |
| 2 |
|
| 3 |
if ( ! defined('ABSPATH') ) |
| 4 |
die(); |
| 5 |
|
| 6 |
if (!class_exists('IWP_MMB_UploadModule')) require_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/backup.upload.php'); |
| 7 |
|
| 8 |
class IWP_MMB_UploadModule_googledrive extends IWP_MMB_UploadModule { |
| 9 |
|
| 10 |
private $service; |
| 11 |
private $client; |
| 12 |
private $ids_from_paths; |
| 13 |
public $root_id; |
| 14 |
|
| 15 |
public function get_supported_features() { |
| 16 |
// This options format is handled via only accessing options via $this->get_options() |
| 17 |
return array('multi_options'); |
| 18 |
} |
| 19 |
|
| 20 |
public function get_default_options() { |
| 21 |
# 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. |
| 22 |
return array( |
| 23 |
'clientid' => '', |
| 24 |
'secret' => '', |
| 25 |
'token' => '', |
| 26 |
); |
| 27 |
} |
| 28 |
|
| 29 |
private function root_id() { |
| 30 |
if (empty($this->root_id)) $this->root_id = $this->service->about->get()->getRootFolderId(); |
| 31 |
return $this->root_id; |
| 32 |
} |
| 33 |
|
| 34 |
public function id_from_path($path, $retry = true) { |
| 35 |
global $iwp_backup_core; |
| 36 |
|
| 37 |
try { |
| 38 |
while ('/' == substr($path, 0, 1)) { $path = substr($path, 1); } |
| 39 |
|
| 40 |
$cache_key = (empty($path)) ? '/' : $path; |
| 41 |
if (!empty($this->ids_from_paths) && isset($this->ids_from_paths[$cache_key])) return $this->ids_from_paths[$cache_key]; |
| 42 |
|
| 43 |
$current_parent = $this->root_id(); |
| 44 |
$current_path = '/'; |
| 45 |
|
| 46 |
if (!empty($path)) { |
| 47 |
foreach (explode('/', $path) as $element) { |
| 48 |
$found = false; |
| 49 |
$sub_items = $this->get_subitems($current_parent, 'dir', $element); |
| 50 |
|
| 51 |
foreach ($sub_items as $item) { |
| 52 |
try { |
| 53 |
if ($item->getTitle() == $element) { |
| 54 |
$found = true; |
| 55 |
$current_path .= $element.'/'; |
| 56 |
$current_parent = $item->getId(); |
| 57 |
break; |
| 58 |
} |
| 59 |
} catch (Exception $e) { |
| 60 |
$iwp_backup_core->log("Google Drive id_from_path: exception: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 61 |
} |
| 62 |
} |
| 63 |
|
| 64 |
if (!$found) { |
| 65 |
$ref = new Google_Service_Drive_ParentReference; |
| 66 |
$ref->setId($current_parent); |
| 67 |
$dir = new Google_Service_Drive_DriveFile(); |
| 68 |
$dir->setMimeType('application/vnd.google-apps.folder'); |
| 69 |
$dir->setParents(array($ref)); |
| 70 |
$dir->setTitle($element); |
| 71 |
$iwp_backup_core->log("Google Drive: creating path: ".$current_path.$element); |
| 72 |
$dir = $this->service->files->insert( |
| 73 |
$dir, |
| 74 |
array('mimeType' => 'application/vnd.google-apps.folder') |
| 75 |
); |
| 76 |
$current_path .= $element.'/'; |
| 77 |
$current_parent = $dir->getId(); |
| 78 |
} |
| 79 |
} |
| 80 |
} |
| 81 |
|
| 82 |
if (empty($this->ids_from_paths)) $this->ids_from_paths = array(); |
| 83 |
$this->ids_from_paths[$cache_key] = $current_parent; |
| 84 |
|
| 85 |
return $current_parent; |
| 86 |
|
| 87 |
} catch (Exception $e) { |
| 88 |
$msg = $e->getMessage(); |
| 89 |
$iwp_backup_core->log("Google Drive id_from_path failure: exception (".get_class($e)."): ".$msg.' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 90 |
if (is_a($e, 'Google_Service_Exception') && false !== strpos($msg, 'Invalid json in service response') && function_exists('mb_strpos')) { |
| 91 |
// Aug 2015: saw a case where the gzip-encoding was not removed from the result |
| 92 |
// https://stackoverflow.com/questions/10975775/how-to-determine-if-a-string-was-compressed |
| 93 |
$is_gzip = false !== mb_strpos($msg , "\x1f" . "\x8b" . "\x08"); |
| 94 |
if ($is_gzip) $iwp_backup_core->log("Error: Response appears to be gzip-encoded still; something is broken in the client HTTP stack, and you should define IWP_GOOGLEDRIVE_DISABLEGZIP as true in your wp-config.php to overcome this."); |
| 95 |
} |
| 96 |
# One retry |
| 97 |
return ($retry) ? $this->id_from_path($path, false) : false; |
| 98 |
} |
| 99 |
} |
| 100 |
|
| 101 |
private function get_parent_id($opts) { |
| 102 |
$filtered = apply_filters('IWP_googledrive_parent_id', false, $opts, $this->service, $this); |
| 103 |
if (!empty($filtered)) return $filtered; |
| 104 |
if (isset($opts['parentid'])) { |
| 105 |
if (empty($opts['parentid'])) { |
| 106 |
return $this->root_id(); |
| 107 |
} else { |
| 108 |
$parent = (is_array($opts['parentid'])) ? $opts['parentid']['id'] : $opts['parentid']; |
| 109 |
} |
| 110 |
} else { |
| 111 |
$path = 'infinitewp'; |
| 112 |
if (!empty($opts['gdrive_site_folder'])) { |
| 113 |
$site_name = iwp_getSiteName(); |
| 114 |
$path = trailingslashit($path); |
| 115 |
$path.= $site_name; |
| 116 |
} |
| 117 |
$parent = $this->id_from_path($path); |
| 118 |
} |
| 119 |
return (empty($parent)) ? $this->root_id() : $parent; |
| 120 |
} |
| 121 |
|
| 122 |
public function listfiles($match = 'backup_') { |
| 123 |
|
| 124 |
$opts = $this->get_options(); |
| 125 |
|
| 126 |
if (empty($opts['secret']) || empty($opts['clientid']) || empty($opts['clientid'])) return new WP_Error('no_settings', sprintf(__('No %s settings were found', 'InfiniteWP'), __('Google Drive','InfiniteWP'))); |
| 127 |
|
| 128 |
$service = $this->bootstrap(); |
| 129 |
if (is_wp_error($service) || false == $service) return $service; |
| 130 |
|
| 131 |
global $iwp_backup_core; |
| 132 |
|
| 133 |
try { |
| 134 |
$parent_id = $this->get_parent_id($opts); |
| 135 |
$sub_items = $this->get_subitems($parent_id, 'file'); |
| 136 |
} catch (Exception $e) { |
| 137 |
return new WP_Error(__('Google Drive list files: failed to access parent folder', 'InfiniteWP').": ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 138 |
} |
| 139 |
|
| 140 |
$results = array(); |
| 141 |
|
| 142 |
foreach ($sub_items as $item) { |
| 143 |
$title = "(unknown)"; |
| 144 |
try { |
| 145 |
$title = $item->getTitle(); |
| 146 |
if (0 === strpos($title, $match)) { |
| 147 |
$results[] = array('name' => $title, 'size' => $item->getFileSize()); |
| 148 |
} |
| 149 |
} catch (Exception $e) { |
| 150 |
$iwp_backup_core->log("Google Drive delete: exception: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 151 |
$ret = false; |
| 152 |
continue; |
| 153 |
} |
| 154 |
} |
| 155 |
|
| 156 |
return $results; |
| 157 |
} |
| 158 |
|
| 159 |
// Get a Google account access token using the refresh token |
| 160 |
private function access_token($refresh_token, $client_id, $client_secret) { |
| 161 |
|
| 162 |
global $iwp_backup_core; |
| 163 |
$iwp_backup_core->log("Google Drive: requesting access token: client_id=$client_id"); |
| 164 |
|
| 165 |
$query_body = array( |
| 166 |
'refresh_token' => $refresh_token, |
| 167 |
'client_id' => $client_id, |
| 168 |
'client_secret' => $client_secret, |
| 169 |
'grant_type' => 'refresh_token' |
| 170 |
); |
| 171 |
|
| 172 |
$result = wp_remote_post('https://accounts.google.com/o/oauth2/token', |
| 173 |
array( |
| 174 |
'timeout' => '20', |
| 175 |
'method' => 'POST', |
| 176 |
'body' => $query_body |
| 177 |
) |
| 178 |
); |
| 179 |
|
| 180 |
if (is_wp_error($result)) { |
| 181 |
$iwp_backup_core->log("Google Drive error when requesting access token"); |
| 182 |
foreach ($result->get_error_messages() as $msg) $iwp_backup_core->log("Error message: $msg"); |
| 183 |
return false; |
| 184 |
} else { |
| 185 |
$json_values = json_decode(wp_remote_retrieve_body($result), true); |
| 186 |
if ( isset( $json_values['access_token'] ) ) { |
| 187 |
$iwp_backup_core->log("Google Drive: successfully obtained access token"); |
| 188 |
return $json_values['access_token']; |
| 189 |
} else { |
| 190 |
$response = json_decode($result['body'],true); |
| 191 |
if (!empty($response['error']) && 'deleted_client' == $response['error']) { |
| 192 |
$iwp_backup_core->log(__('The client has been deleted from the Google Drive API console. Please create a new Google Drive project and reconnect with iwp_backup_core.','iwp_backup_core'), 'error'); |
| 193 |
} |
| 194 |
$error_code = empty($response['error']) ? 'no error code' : $response['error']; |
| 195 |
$iwp_backup_core->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']))); |
| 196 |
return false; |
| 197 |
} |
| 198 |
} |
| 199 |
} |
| 200 |
|
| 201 |
private function redirect_uri() { |
| 202 |
return ''; |
| 203 |
} |
| 204 |
|
| 205 |
// Acquire single-use authorization code from Google OAuth 2.0 |
| 206 |
public function gdrive_auth_request() { |
| 207 |
$opts = $this->get_options(); |
| 208 |
// First, revoke any existing token, since Google doesn't appear to like issuing new ones |
| 209 |
if (!empty($opts['token'])) $this->gdrive_auth_revoke(); |
| 210 |
|
| 211 |
// We use 'force' here for the approval_prompt, not 'auto', as that deals better with messy situations where the user authenticated, then changed settings |
| 212 |
|
| 213 |
# 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 |
| 214 |
$params = array( |
| 215 |
'response_type' => 'code', |
| 216 |
'client_id' => $opts['clientid'], |
| 217 |
'redirect_uri' => $this->redirect_uri(), |
| 218 |
'scope' => 'https://www.googleapis.com/auth/drive', |
| 219 |
'state' => 'token', |
| 220 |
'access_type' => 'offline', |
| 221 |
'approval_prompt' => 'force' |
| 222 |
); |
| 223 |
if(headers_sent()) { |
| 224 |
global $iwp_backup_core; |
| 225 |
$iwp_backup_core->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'); |
| 226 |
} else { |
| 227 |
header('Location: https://accounts.google.com/o/oauth2/auth?'.http_build_query($params, '', '&')); |
| 228 |
} |
| 229 |
} |
| 230 |
|
| 231 |
|
| 232 |
// This function just does the formalities, and off-loads the main work to upload_file |
| 233 |
public function backup($backup_array) { |
| 234 |
|
| 235 |
global $iwp_backup_core, $IWP_backup; |
| 236 |
|
| 237 |
$service = $this->bootstrap(); |
| 238 |
if (false == $service || is_wp_error($service)) return $service; |
| 239 |
|
| 240 |
$iwp_backup_dir = trailingslashit($iwp_backup_core->backups_dir_location()); |
| 241 |
|
| 242 |
$opts = $this->get_options(); |
| 243 |
|
| 244 |
try { |
| 245 |
$parent_id = $this->get_parent_id($opts); |
| 246 |
} catch (Exception $e) { |
| 247 |
$iwp_backup_core->log("Google Drive upload: failed to access parent folder: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 248 |
$iwp_backup_core->log(sprintf(__('Failed to upload to %s','InfiniteWP'),__('Google Drive','InfiniteWP')).': '.__('failed to access parent folder', 'InfiniteWP').' ('.$e->getMessage().')', 'error'); |
| 249 |
return false; |
| 250 |
} |
| 251 |
|
| 252 |
foreach ($backup_array as $file) { |
| 253 |
|
| 254 |
$available_quota = -1; |
| 255 |
|
| 256 |
try { |
| 257 |
$about = $service->about->get(); |
| 258 |
$quota_total = max($about->getQuotaBytesTotal(), 1); |
| 259 |
$quota_used = $about->getQuotaBytesUsed(); |
| 260 |
$available_quota = $quota_total - $quota_used; |
| 261 |
$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"; |
| 262 |
$iwp_backup_core->log($message); |
| 263 |
} catch (Exception $e) { |
| 264 |
$iwp_backup_core->log("Google Drive quota usage: failed to obtain this information: ".$e->getMessage()); |
| 265 |
} |
| 266 |
|
| 267 |
$file_path = $iwp_backup_dir.$file; |
| 268 |
$file_name = basename($file_path); |
| 269 |
$iwp_backup_core->log("$file_name: Attempting to upload to Google Drive (into folder id: $parent_id)"); |
| 270 |
|
| 271 |
$filesize = filesize($file_path); |
| 272 |
$already_failed = false; |
| 273 |
if ($available_quota != -1) { |
| 274 |
if ($filesize > $available_quota) { |
| 275 |
$already_failed = true; |
| 276 |
$iwp_backup_core->log("File upload expected to fail: file ($file_name) size is $filesize b, whereas available quota is only $available_quota b"); |
| 277 |
$iwp_backup_core->log(sprintf(__("Account full: your %s account has only %d bytes left, but the file to be uploaded is %d bytes",'InfiniteWP'),__('Google Drive', 'InfiniteWP'), $available_quota, $filesize), +'error'); |
| 278 |
} |
| 279 |
} |
| 280 |
|
| 281 |
if (!$already_failed && $filesize > 10737418240) { |
| 282 |
# 10GB |
| 283 |
$iwp_backup_core->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)"); |
| 284 |
$iwp_backup_core->log(sprintf(__("Upload expected to fail: the %s limit for any single file is %s, whereas this file is %s GB (%d bytes)",'InfiniteWP'),__('Google Drive', 'InfiniteWP'), '10GB (1073741824)', round($filesize/1073741824, 4), $filesize), 'warning'); |
| 285 |
} |
| 286 |
|
| 287 |
try { |
| 288 |
$timer_start = microtime(true); |
| 289 |
if ($this->upload_file($file_path, $parent_id)) { |
| 290 |
$iwp_backup_core->log('OK: Archive ' . $file_name . ' uploaded to Google Drive in ' . ( round(microtime(true) - $timer_start, 2) ) . ' seconds'); |
| 291 |
$iwp_backup_core->uploaded_file($file); |
| 292 |
} else { |
| 293 |
$iwp_backup_core->log("ERROR: $file_name: Failed to upload to Google Drive" ); |
| 294 |
$iwp_backup_core->log("$file_name: ".sprintf(__('Failed to upload to %s','InfiniteWP'),__('Google Drive','InfiniteWP')), 'error'); |
| 295 |
} |
| 296 |
} catch (Exception $e) { |
| 297 |
$msg = $e->getMessage(); |
| 298 |
$iwp_backup_core->log("ERROR: Google Drive upload error: ".$msg.' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 299 |
if (false !== ($p = strpos($msg, 'The user has exceeded their Drive storage quota'))) { |
| 300 |
$iwp_backup_core->log("$file_name: ".sprintf(__('Failed to upload to %s','InfiniteWP'),__('Google Drive','InfiniteWP')).': '.substr($msg, $p), 'error'); |
| 301 |
} else { |
| 302 |
$iwp_backup_core->log("$file_name: ".sprintf(__('Failed to upload to %s','InfiniteWP'),__('Google Drive','InfiniteWP')), 'error'); |
| 303 |
} |
| 304 |
$this->client->setDefer(false); |
| 305 |
} |
| 306 |
} |
| 307 |
|
| 308 |
return null; |
| 309 |
} |
| 310 |
|
| 311 |
public function bootstrap($access_token = false) { |
| 312 |
|
| 313 |
global $iwp_backup_core; |
| 314 |
|
| 315 |
if (!empty($this->service) && is_object($this->service) && is_a($this->service, 'Google_Service_Drive')) return $this->service; |
| 316 |
|
| 317 |
$opts = $this->get_options(); |
| 318 |
|
| 319 |
if (empty($access_token)) { |
| 320 |
if (empty($opts['token']) || empty($opts['clientid']) || empty($opts['secret'])) { |
| 321 |
$iwp_backup_core->log('Google Drive: this account is not authorised'); |
| 322 |
$iwp_backup_core->log('Google Drive: '.__('Account is not authorized.', 'InfiniteWP'), 'error', 'googledrivenotauthed'); |
| 323 |
return new WP_Error('not_authorized', __('Account is not authorized.', 'InfiniteWP')); |
| 324 |
} |
| 325 |
} |
| 326 |
|
| 327 |
$spl = spl_autoload_functions(); |
| 328 |
if (is_array($spl)) { |
| 329 |
if (in_array('wpbgdc_autoloader', $spl)) spl_autoload_unregister('wpbgdc_autoloader'); |
| 330 |
// http://www.wpdownloadmanager.com/download/google-drive-explorer/ - but also others, since this is the default function name used by the Google SDK |
| 331 |
if (in_array('google_api_php_client_autoload', $spl)) spl_autoload_unregister('google_api_php_client_autoload'); |
| 332 |
} |
| 333 |
|
| 334 |
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_iwp')) { |
| 335 |
require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/Google2/autoload.php'); |
| 336 |
} |
| 337 |
|
| 338 |
if (!class_exists('IWP_MMB_Google_Http_MediaFileUpload')) { |
| 339 |
require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/google-extensions.php'); |
| 340 |
} |
| 341 |
|
| 342 |
$config = new Google_Config(); |
| 343 |
$config->setClassConfig('Google_IO_Abstract', 'request_timeout_seconds', 60); |
| 344 |
# In our testing, $service->about->get() fails if gzip is not disabled when using the stream wrapper |
| 345 |
if (!function_exists('curl_version') || !function_exists('curl_exec') || (defined('IWP_GOOGLEDRIVE_DISABLEGZIP') && IWP_GOOGLEDRIVE_DISABLEGZIP)) { |
| 346 |
$config->setClassConfig('Google_Http_Request', 'disable_gzip', true); |
| 347 |
} |
| 348 |
|
| 349 |
$client = new Google_Client($config); |
| 350 |
$client->setClientId($opts['clientid']); |
| 351 |
$client->setClientSecret($opts['secret']); |
| 352 |
// $client->setUseObjects(true); |
| 353 |
|
| 354 |
if (empty($access_token)) { |
| 355 |
$access_token = $this->access_token($opts['token'], $opts['clientid'], $opts['secret']); |
| 356 |
} |
| 357 |
|
| 358 |
// Do we have an access token? |
| 359 |
if (empty($access_token) || is_wp_error($access_token)) { |
| 360 |
$iwp_backup_core->log('ERROR: Have not yet obtained an access token from Google (has the user authorised?)'); |
| 361 |
$iwp_backup_core->log(__('Have not yet obtained an access token from Google - you need to authorise or re-authorise your connection to Google Drive.','InfiniteWP'), 'error'); |
| 362 |
return $access_token; |
| 363 |
} |
| 364 |
|
| 365 |
$client->setAccessToken(json_encode(array( |
| 366 |
'access_token' => $access_token, |
| 367 |
'refresh_token' => $opts['token'] |
| 368 |
))); |
| 369 |
|
| 370 |
$io = $client->getIo(); |
| 371 |
$setopts = array(); |
| 372 |
|
| 373 |
if (is_a($io, 'Google_IO_Curl')) { |
| 374 |
$setopts[CURLOPT_SSL_VERIFYPEER] = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_disableverify') ? false : true; |
| 375 |
if (!IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_useservercerts')) $setopts[CURLOPT_CAINFO] = $GLOBALS['iwp_mmb_plugin_dir'].'/lib/cacert.pem'; |
| 376 |
// Raise the timeout from the default of 15 |
| 377 |
$setopts[CURLOPT_TIMEOUT] = 60; |
| 378 |
$setopts[CURLOPT_CONNECTTIMEOUT] = 15; |
| 379 |
if (defined('IWP_IPV4_ONLY') && IWP_IPV4_ONLY) $setopts[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4; |
| 380 |
} elseif (is_a($io, 'Google_IO_Stream')) { |
| 381 |
$setopts['timeout'] = 60; |
| 382 |
# We had to modify the SDK to support this |
| 383 |
# https://wiki.php.net/rfc/tls-peer-verification - before PHP 5.6, there is no default CA file |
| 384 |
if (!IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_useservercerts') || (version_compare(PHP_VERSION, '5.6.0', '<'))) $setopts['cafile'] = $GLOBALS['iwp_mmb_plugin_dir'].'/lib/cacert.pem'; |
| 385 |
if (IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_disableverify')) $setopts['disable_verify_peer'] = true; |
| 386 |
} |
| 387 |
|
| 388 |
$io->setOptions($setopts); |
| 389 |
|
| 390 |
$service = new Google_Service_Drive($client); |
| 391 |
$this->client = $client; |
| 392 |
$this->service = $service; |
| 393 |
|
| 394 |
try { |
| 395 |
# Get the folder name, if not previously known (this is for the legacy situation where an id, not a name, was stored) |
| 396 |
if (!empty($opts['parentid']) && (!is_array($opts['parentid']) || empty($opts['parentid']['name']))) { |
| 397 |
$rootid = $this->root_id(); |
| 398 |
$title = ''; |
| 399 |
$parentid = is_array($opts['parentid']) ? $opts['parentid']['id'] : $opts['parentid']; |
| 400 |
while ((!empty($parentid) && $parentid != $rootid)) { |
| 401 |
$resource = $service->files->get($parentid); |
| 402 |
$title = ($title) ? $resource->getTitle().'/'.$title : $resource->getTitle(); |
| 403 |
$parents = $resource->getParents(); |
| 404 |
if (is_array($parents) && count($parents)>0) { |
| 405 |
$parent = array_shift($parents); |
| 406 |
$parentid = is_a($parent, 'Google_Service_Drive_ParentReference') ? $parent->getId() : false; |
| 407 |
} else { |
| 408 |
$parentid = false; |
| 409 |
} |
| 410 |
} |
| 411 |
if (!empty($title)) { |
| 412 |
$opts['parentid'] = array( |
| 413 |
'id' => (is_array($opts['parentid']) ? $opts['parentid']['id'] : $opts['parentid']), |
| 414 |
'name' => $title |
| 415 |
); |
| 416 |
$this->set_options($opts, true); |
| 417 |
} |
| 418 |
} |
| 419 |
} catch (Exception $e) { |
| 420 |
$iwp_backup_core->log("Google Drive: failed to obtain name of parent folder: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 421 |
} |
| 422 |
|
| 423 |
return $this->service; |
| 424 |
|
| 425 |
} |
| 426 |
|
| 427 |
// Returns array of Google_Service_Drive_DriveFile objects |
| 428 |
private function get_subitems($parent_id, $type = 'any', $match = 'backup_') { |
| 429 |
$q = '"'.$parent_id.'" in parents and trashed = false'; |
| 430 |
if ('dir' == $type) { |
| 431 |
$q .= ' and mimeType = "application/vnd.google-apps.folder"'; |
| 432 |
} elseif ('file' == $type) { |
| 433 |
$q .= ' and mimeType != "application/vnd.google-apps.folder"'; |
| 434 |
} |
| 435 |
# 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 |
| 436 |
if (!empty($match)) { |
| 437 |
if ('backup_' == $match) { |
| 438 |
$q .= " and title contains '$match'"; |
| 439 |
} else { |
| 440 |
$q .= " and title contains '$match'"; |
| 441 |
} |
| 442 |
} |
| 443 |
|
| 444 |
$result = array(); |
| 445 |
$pageToken = NULL; |
| 446 |
|
| 447 |
do { |
| 448 |
try { |
| 449 |
// Default for maxResults is 100 |
| 450 |
$parameters = array('q' => $q, 'maxResults' => 200); |
| 451 |
if ($pageToken) { |
| 452 |
$parameters['pageToken'] = $pageToken; |
| 453 |
} |
| 454 |
$files = $this->service->files->listFiles($parameters); |
| 455 |
|
| 456 |
$result = array_merge($result, $files->getItems()); |
| 457 |
$pageToken = $files->getNextPageToken(); |
| 458 |
} catch (Exception $e) { |
| 459 |
global $iwp_backup_core; |
| 460 |
$iwp_backup_core->log("Google Drive: get_subitems: An error occurred (will not fetch further): " . $e->getMessage()); |
| 461 |
$pageToken = NULL; |
| 462 |
} |
| 463 |
} while ($pageToken); |
| 464 |
|
| 465 |
return $result; |
| 466 |
} |
| 467 |
|
| 468 |
public function delete($files, $data=null, $sizeinfo = array()) { |
| 469 |
|
| 470 |
if (is_string($files)) $files=array($files); |
| 471 |
|
| 472 |
$service = $this->bootstrap(); |
| 473 |
if (is_wp_error($service) || false == $service) return $service; |
| 474 |
|
| 475 |
$opts = $this->get_options(); |
| 476 |
|
| 477 |
global $iwp_backup_core; |
| 478 |
|
| 479 |
try { |
| 480 |
$parent_id = $this->get_parent_id($opts); |
| 481 |
$iwp_getSiteName = iwp_getSiteName(); |
| 482 |
$sub_items = $this->get_subitems($parent_id, 'file', $files[0]); |
| 483 |
} catch (Exception $e) { |
| 484 |
$iwp_backup_core->log("Google Drive delete: failed to access parent folder: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 485 |
return false; |
| 486 |
} |
| 487 |
|
| 488 |
$ret = true; |
| 489 |
|
| 490 |
foreach ($sub_items as $item) { |
| 491 |
$title = "(unknown)"; |
| 492 |
try { |
| 493 |
$title = $item->getTitle(); |
| 494 |
if (in_array($title, $files)) { |
| 495 |
$service->files->delete($item->getId()); |
| 496 |
$iwp_backup_core->log("$title: Deletion successful"); |
| 497 |
if(($key = array_search($title, $files)) !== false) { |
| 498 |
unset($files[$key]); |
| 499 |
} |
| 500 |
} |
| 501 |
} catch (Exception $e) { |
| 502 |
$iwp_backup_core->log("Google Drive delete: exception: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 503 |
$ret = false; |
| 504 |
continue; |
| 505 |
} |
| 506 |
} |
| 507 |
|
| 508 |
foreach ($files as $file) { |
| 509 |
$iwp_backup_core->log("$file: Deletion failed: file was not found"); |
| 510 |
} |
| 511 |
|
| 512 |
return $ret; |
| 513 |
|
| 514 |
} |
| 515 |
|
| 516 |
private function upload_file($file, $parent_id, $try_again = true) { |
| 517 |
|
| 518 |
global $iwp_backup_core; |
| 519 |
$opts = $this->get_options(); |
| 520 |
$basename = basename($file); |
| 521 |
|
| 522 |
$service = $this->service; |
| 523 |
$client = $this->client; |
| 524 |
|
| 525 |
# 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) |
| 526 |
|
| 527 |
$client->setDefer(true); |
| 528 |
|
| 529 |
$local_size = filesize($file); |
| 530 |
|
| 531 |
$gdfile = new Google_Service_Drive_DriveFile(); |
| 532 |
$gdfile->title = $basename; |
| 533 |
|
| 534 |
$ref = new Google_Service_Drive_ParentReference; |
| 535 |
$ref->setId($parent_id); |
| 536 |
$gdfile->setParents(array($ref)); |
| 537 |
|
| 538 |
$size = 0; |
| 539 |
$request = $service->files->insert($gdfile); |
| 540 |
|
| 541 |
$chunk_bytes = 1048576; |
| 542 |
|
| 543 |
$hash = md5($file); |
| 544 |
$transkey = 'gdresume_'.$hash; |
| 545 |
// This is unset upon completion, so if it is set then we are resuming |
| 546 |
$possible_location = $iwp_backup_core->jobdata_get($transkey); |
| 547 |
|
| 548 |
if (is_array($possible_location)) { |
| 549 |
|
| 550 |
$headers = array( 'content-range' => "bytes */".$local_size); |
| 551 |
|
| 552 |
$httpRequest = new Google_Http_Request( |
| 553 |
$possible_location[0], |
| 554 |
'PUT', |
| 555 |
$headers, |
| 556 |
'' |
| 557 |
); |
| 558 |
$response = $this->client->getIo()->makeRequest($httpRequest); |
| 559 |
$can_resume = false; |
| 560 |
|
| 561 |
$response_http_code = $response->getResponseHttpCode(); |
| 562 |
|
| 563 |
if ($response_http_code == 200 || $response_http_code == 201) { |
| 564 |
$client->setDefer(false); |
| 565 |
$iwp_backup_core->jobdata_delete($transkey); |
| 566 |
$iwp_backup_core->log("$basename: upload appears to be already complete (HTTP code: $response_http_code)"); |
| 567 |
return true; |
| 568 |
} |
| 569 |
|
| 570 |
if (308 == $response_http_code) { |
| 571 |
$range = $response->getResponseHeader('range'); |
| 572 |
if (!empty($range) && preg_match('/bytes=0-(\d+)$/', $range, $matches)) { |
| 573 |
$can_resume = true; |
| 574 |
$possible_location[1] = $matches[1]+1; |
| 575 |
$iwp_backup_core->log("$basename: upload already began; attempting to resume from byte ".$matches[1]); |
| 576 |
} |
| 577 |
} |
| 578 |
if (!$can_resume) { |
| 579 |
$iwp_backup_core->log("$basename: upload already began; attempt to resume did not succeed (HTTP code: ".$response_http_code.")"); |
| 580 |
} |
| 581 |
} |
| 582 |
|
| 583 |
$media = new IWP_MMB_Google_Http_MediaFileUpload( |
| 584 |
$client, |
| 585 |
$request, |
| 586 |
(('.zip' == substr($basename, -4, 4)) ? 'application/zip' : 'application/octet-stream'), |
| 587 |
null, |
| 588 |
true, |
| 589 |
$chunk_bytes |
| 590 |
); |
| 591 |
$media->setFileSize($local_size); |
| 592 |
|
| 593 |
if (!empty($possible_location)) { |
| 594 |
// $media->resumeUri = $possible_location[0]; |
| 595 |
// $media->progress = $possible_location[1]; |
| 596 |
$media->IWP_setResumeUri($possible_location[0]); |
| 597 |
$media->IWP_setProgress($possible_location[1]); |
| 598 |
$size = $possible_location[1]; |
| 599 |
} |
| 600 |
if ($size >= $local_size) return true; |
| 601 |
|
| 602 |
$status = false; |
| 603 |
if (false == ($handle = fopen($file, 'rb'))) { |
| 604 |
$iwp_backup_core->log("Google Drive: failed to open file: $basename"); |
| 605 |
$iwp_backup_core->log("$basename: ".sprintf(__('%s Error: Failed to open local file', 'iwp_backup_core'),'Google Drive'), 'error'); |
| 606 |
return false; |
| 607 |
} |
| 608 |
if ($size > 0 && 0 != fseek($handle, $size)) { |
| 609 |
$iwp_backup_core->log("Google Drive: failed to fseek file: $basename, $size"); |
| 610 |
$iwp_backup_core->log("$basename (fseek): ".sprintf(__('%s Error: Failed to open local file', 'InfiniteWP'), 'Google Drive'), 'error'); |
| 611 |
return false; |
| 612 |
} |
| 613 |
|
| 614 |
$pointer = $size; |
| 615 |
|
| 616 |
try { |
| 617 |
while (!$status && !feof($handle)) { |
| 618 |
$chunk = fread($handle, $chunk_bytes); |
| 619 |
# Error handling?? |
| 620 |
$pointer += strlen($chunk); |
| 621 |
$status = $media->nextChunk($chunk); |
| 622 |
$iwp_backup_core->jobdata_set($transkey, array($media->IWP_getResumeUri(), $media->getProgress())); |
| 623 |
$iwp_backup_core->record_uploaded_chunk(round(100*$pointer/$local_size, 1), $media->getProgress(), $file); |
| 624 |
} |
| 625 |
|
| 626 |
} catch (Google_Service_Exception $e) { |
| 627 |
$iwp_backup_core->log("ERROR: Google Drive upload error (".get_class($e)."): ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 628 |
$client->setDefer(false); |
| 629 |
fclose($handle); |
| 630 |
$iwp_backup_core->jobdata_delete($transkey); |
| 631 |
if (false == $try_again) throw($e); |
| 632 |
# Reset this counter to prevent the something_useful_happened condition's possibility being sent into the far future and potentially missed |
| 633 |
if ($iwp_backup_core->current_resumption > 9) $iwp_backup_core->jobdata_set('uploaded_lastreset', $iwp_backup_core->current_resumption); |
| 634 |
return $this->upload_file($file, $parent_id, false); |
| 635 |
} |
| 636 |
|
| 637 |
// The final value of $status will be the data from the API for the object |
| 638 |
// that has been uploaded. |
| 639 |
$result = false; |
| 640 |
if ($status != false) $result = $status; |
| 641 |
|
| 642 |
fclose($handle); |
| 643 |
$client->setDefer(false); |
| 644 |
$iwp_backup_core->jobdata_delete($transkey); |
| 645 |
|
| 646 |
return true; |
| 647 |
|
| 648 |
} |
| 649 |
|
| 650 |
public function download($file) { |
| 651 |
|
| 652 |
global $iwp_backup_core; |
| 653 |
|
| 654 |
$service = $this->bootstrap(); |
| 655 |
if (false == $service || is_wp_error($service)) return false; |
| 656 |
|
| 657 |
global $iwp_backup_core; |
| 658 |
$opts = $this->get_options(); |
| 659 |
|
| 660 |
try { |
| 661 |
$parent_id = $this->get_parent_id($opts); |
| 662 |
#$gdparent = $service->files->get($parent_id); |
| 663 |
$site_name = iwp_getSiteName(); |
| 664 |
$sub_items = $this->get_subitems($parent_id, 'file', $file); |
| 665 |
} catch (Exception $e) { |
| 666 |
$iwp_backup_core->log("Google Drive delete: failed to access parent folder: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 667 |
return false; |
| 668 |
} |
| 669 |
$found = false; |
| 670 |
foreach ($sub_items as $item) { |
| 671 |
if ($found) continue; |
| 672 |
$title = "(unknown)"; |
| 673 |
try { |
| 674 |
$title = $item->getTitle(); |
| 675 |
if ($title == $file) { |
| 676 |
$gdfile = $item; |
| 677 |
$found = $item->getId(); |
| 678 |
$size = $item->getFileSize(); |
| 679 |
} |
| 680 |
} catch (Exception $e) { |
| 681 |
$iwp_backup_core->log("Google Drive download: exception: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 682 |
} |
| 683 |
} |
| 684 |
|
| 685 |
if (false === $found) { |
| 686 |
$iwp_backup_core->log("Google Drive download: failed: file not found"); |
| 687 |
$iwp_backup_core->log("$file: ".sprintf(__("%s Error",'InfiniteWP'), 'Google Drive').": ".__('File not found', 'InfiniteWP'), 'error'); |
| 688 |
return false; |
| 689 |
} |
| 690 |
|
| 691 |
$download_to = $iwp_backup_core->backups_dir_location().'/'.$file; |
| 692 |
|
| 693 |
$existing_size = (file_exists($download_to)) ? filesize($download_to) : 0; |
| 694 |
|
| 695 |
if ($existing_size >= $size) { |
| 696 |
$iwp_backup_core->log('Google Drive download: was already downloaded ('.filesize($download_to)."/$size bytes)"); |
| 697 |
return true; |
| 698 |
} |
| 699 |
|
| 700 |
# Chunk in units of 2MB |
| 701 |
// $chunk_size = 2097152; |
| 702 |
$chunk_size = 1024 * ( 5* 1024 ); |
| 703 |
|
| 704 |
try { |
| 705 |
while ($existing_size < $size) { |
| 706 |
|
| 707 |
if($iwp_backup_core->restore_loop_break()){ |
| 708 |
return 'partial'; |
| 709 |
} |
| 710 |
|
| 711 |
$end = min($existing_size + $chunk_size, $size); |
| 712 |
|
| 713 |
if ($existing_size > 0) { |
| 714 |
$put_flag = FILE_APPEND; |
| 715 |
$headers = array('Range' => 'bytes='.$existing_size.'-'.$end); |
| 716 |
} else { |
| 717 |
$put_flag = null; |
| 718 |
$headers = ($end < $size) ? array('Range' => 'bytes=0-'.$end) : array(); |
| 719 |
} |
| 720 |
|
| 721 |
$pstart = round(100*$existing_size/$size,1); |
| 722 |
$pend = round(100*$end/$size,1); |
| 723 |
$iwp_backup_core->log("Requesting byte range: $existing_size - $end ($pstart - $pend %)"); |
| 724 |
|
| 725 |
$request = $this->client->getAuth()->sign(new Google_Http_Request($gdfile->getDownloadUrl(), 'GET', $headers, null)); |
| 726 |
$http_request = $this->client->getIo()->makeRequest($request); |
| 727 |
$http_response = $http_request->getResponseHttpCode(); |
| 728 |
if (200 == $http_response || 206 == $http_response) { |
| 729 |
if ($put_flag == null) { |
| 730 |
file_put_contents($download_to, $http_request->getResponseBody()); |
| 731 |
}else{ |
| 732 |
file_put_contents($download_to, $http_request->getResponseBody(), $put_flag); |
| 733 |
} |
| 734 |
} else { |
| 735 |
$iwp_backup_core->log("Google Drive download: failed: unexpected HTTP response code: ".$http_response); |
| 736 |
$iwp_backup_core->log(sprintf(__("%s download: failed: file not found", 'iwp_backup_core'), 'Google Drive'), 'error'); |
| 737 |
return false; |
| 738 |
} |
| 739 |
|
| 740 |
clearstatcache(); |
| 741 |
$new_size = filesize($download_to); |
| 742 |
if ($new_size > $existing_size) { |
| 743 |
$existing_size = $new_size; |
| 744 |
} else { |
| 745 |
throw new Exception('Failed to obtain any new data at size: '.$existing_size); |
| 746 |
} |
| 747 |
} |
| 748 |
} catch (Exception $e) { |
| 749 |
$iwp_backup_core->log("Google Drive download: exception: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 750 |
} |
| 751 |
|
| 752 |
return true; |
| 753 |
} |
| 754 |
|
| 755 |
public function config_print() { |
| 756 |
$opts = $this->get_options(); |
| 757 |
|
| 758 |
if (isset($opts['parentid'])) { |
| 759 |
$parentid = (is_array($opts['parentid'])) ? $opts['parentid']['id'] : $opts['parentid']; |
| 760 |
$showparent = (is_array($opts['parentid']) && !empty($opts['parentid']['name'])) ? $opts['parentid']['name'] : $parentid; |
| 761 |
} |
| 762 |
} |
| 763 |
|
| 764 |
public function get_backup_file_size($file){ |
| 765 |
global $iwp_backup_core; |
| 766 |
|
| 767 |
$service = $this->bootstrap(); |
| 768 |
if (false == $service || is_wp_error($service)) return false; |
| 769 |
|
| 770 |
global $iwp_backup_core; |
| 771 |
$opts = $this->get_options(); |
| 772 |
try { |
| 773 |
$parent_id = $this->get_parent_id($opts); |
| 774 |
#$gdparent = $service->files->get($parent_id); |
| 775 |
$site_name = iwp_getSiteName(); |
| 776 |
$sub_items = $this->get_subitems($parent_id, 'file', $file); |
| 777 |
} catch (Exception $e) { |
| 778 |
$iwp_backup_core->log("Google Drive delete: failed to access parent folder: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 779 |
return false; |
| 780 |
} |
| 781 |
$found = false; |
| 782 |
foreach ($sub_items as $item) { |
| 783 |
if ($found) continue; |
| 784 |
$title = "(unknown)"; |
| 785 |
try { |
| 786 |
$title = $item->getTitle(); |
| 787 |
if ($title == $file) { |
| 788 |
$gdfile = $item; |
| 789 |
$found = $item->getId(); |
| 790 |
$size = $item->getFileSize(); |
| 791 |
return $size; |
| 792 |
} |
| 793 |
} catch (Exception $e) { |
| 794 |
$iwp_backup_core->log("Google Drive download: exception: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); |
| 795 |
} |
| 796 |
} |
| 797 |
} |
| 798 |
} |
| 799 |
|