PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 2.1.7
Backup Migration v2.1.7
2.1.7 2.1.6 2.1.5.2 trunk 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.6.1 1.4.7 1.4.8 1.4.9 1.4.9.1 2.0.0 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.5.1
backup-backup / includes / external / google-drive.php
backup-backup / includes / external Last commit date
contracts 3 weeks ago backupbliss.php 3 weeks ago controller.php 3 weeks ago dropbox.php 3 weeks ago external-storage-manager.php 3 weeks ago ftp.php 3 weeks ago google-drive.php 3 weeks ago s3.php 3 weeks ago
google-drive.php
834 lines
1 <?php
2
3 // Namespace
4 namespace BMI\Plugin\External;
5
6 // Use
7 use BMI\Plugin\Backup_Migration_Plugin as BMP;
8 use BMI\Plugin\BMI_Logger as Logger;
9 use BMI\Plugin\BMI_Pro_Core;
10 use BMI\Plugin\BMProAjax as BMProAjax;
11 use BMI\Plugin\Scanner\BMI_BackupsScanner as Backups;
12 use BMI\Plugin\Dashboard as Dashboard;
13 use BMI\Plugin\External\Contracts\DeleteBackup;
14
15 if (!defined('ABSPATH')) {
16 exit;
17 }
18
19 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'external' . DIRECTORY_SEPARATOR . 'contracts' . DIRECTORY_SEPARATOR . 'interface-delete-backup.php';
20
21 /**
22 * BMI_External_GDrive
23 *
24 * This class is responsible for handling all Google Drive related operations
25 */
26 class BMI_External_GDrive implements DeleteBackup {
27
28 private $gdrive_access_token = false;
29
30 public function __construct() {
31
32 add_action('bmi_premium_remove_backup_file', [&$this, 'deleteBackup']);
33 add_action('bmi_premium_remove_backup_json_file', [&$this, 'deleteGoogleDriveJson']);
34
35 }
36
37 /**
38 * checkForBackupsToUpload - Will check for backups that requires to be in sync with cloud
39 *
40 * @return string[]
41 */
42 public function checkForBackupsToUpload() {
43
44 $isEnabled = Dashboard\bmi_get_config('STORAGE::EXTERNAL::GDRIVE');
45 if (!($isEnabled === true || $isEnabled === 'true')) {
46 return;
47 }
48
49 // Upload Object
50 $requiresUpload = get_option('bmip_to_be_uploaded', [
51 'current_upload' => [],
52 'queue' => [],
53 'failed' => []
54 ]);
55
56 // Local Backups
57 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'scanner' . DIRECTORY_SEPARATOR . 'backups.php';
58 $backups = Backups::getInstance();
59 $backupsAvailable = $backups->getAvailableBackups("local");
60 $localBackups = $backupsAvailable['local'];
61 $localBackups = array_reverse($localBackups);
62
63 // Google Drive
64 $gdriveFailed = false;
65 $googleDriveBackups = $this->getGoogleDriveBackups();
66 if ($googleDriveBackups && is_object($googleDriveBackups['data']) && isset($googleDriveBackups['data']->files)) {
67 $googleDriveParsed = $this->parseGoogleDriveFiles($googleDriveBackups['data']->files);
68 } else $gdriveFailed = true;
69
70 $uploadedBackupStatus = get_option('bmi_uploaded_backups_status', []);
71 foreach ($localBackups as $name => $details) {
72
73 $manifestName = $details[0];
74 $md5 = $details[7];
75 if (isset($uploadedBackupStatus[$md5]) && isset($uploadedBackupStatus[$md5]['gdrive'])) {
76 continue;
77 }
78
79 // Google Drive
80 if (!$gdriveFailed && !(isset($googleDriveParsed['md5_' . $md5]) && isset($googleDriveParsed['file_' . $md5 . '.json']))) {
81
82 // File is not uploaded action required
83 if (!isset($requiresUpload['queue']['gdrive_' . $md5])) {
84 $isAnyTaskATM = isset($requiresUpload['current_upload']['task']);
85 if (($isAnyTaskATM && $requiresUpload['current_upload']['task'] != 'gdrive_' . $md5) || !$isAnyTaskATM) {
86 $requiresUpload['queue']['gdrive_' . $md5] = [
87 'name' => $name,
88 'md5' => $md5,
89 'json' => $md5 . '.json'
90 ];
91 }
92 }
93 }
94 }
95
96 update_option('bmip_to_be_uploaded', $requiresUpload);
97 return [ 'status' => 'success' ];
98
99 }
100
101 /**
102 * parseGoogleDriveFiles - Parses Google Drive output files
103 *
104 * @param object $files Google Drive Files of Return
105 * @return array of parsed files
106 */
107 public function parseGoogleDriveFiles(&$files) {
108
109 $parsedFiles = [];
110 foreach ($files as $index => $file) {
111 $parsedFiles['file_' . $file->name] = [
112 'md5' => $file->md5Checksum,
113 'originalName' => $file->originalFilename,
114 'id' => $file->id,
115 'size' => $file->size
116 ];
117 $parsedFiles['md5_' . $file->md5Checksum] = [
118 'name' => $file->name,
119 'originalName' => $file->originalFilename,
120 'id' => $file->id,
121 'size' => $file->size
122 ];
123 }
124
125 return $parsedFiles;
126
127 }
128
129 /**
130 * getGoogleDriveAccessToken - Generates Access Token for API communication
131 *
132 * @return json status
133 */
134 private function getGoogleDriveAccessToken( $forceGetNewAccessToken = false ) {
135
136 $uri = home_url();
137 if (substr($uri, 0, 4) != 'http') {
138 if (is_ssl()) $uri = 'https://' . home_url();
139 else $uri = 'http://' . home_url();
140 }
141
142 if ($this->gdrive_access_token != false) {
143 return $this->gdrive_access_token;
144 }
145
146 $client_token = get_option('bmi_pro_gd_client_id', '');
147 $site_token = get_option('bmi_pro_gd_token', '');
148
149 if (strlen($site_token) < 60 || strlen($client_token) < 60) {
150 return false;
151 }
152
153 $savedAccessToken = get_transient('bmi_pro_access_token');
154 if ($savedAccessToken) return $savedAccessToken;
155
156 $url = 'https://authentication.backupbliss.com/v1/gdrive/token';
157 $response = wp_remote_post($url, array(
158 'method' => 'POST',
159 'timeout' => 15,
160 'redirection' => 2,
161 'httpversion' => '1.0',
162 'blocking' => true,
163 'body' => array(
164 'client_id' => get_option('bmi_pro_gd_client_id', ''),
165 'site_token' => get_option('bmi_pro_gd_token', ''),
166 'force_refresh' => $forceGetNewAccessToken,
167 'redirect_uri' => $uri
168 )
169 ));
170
171 if (is_wp_error($response)) {
172 $error_message = $response->get_error_message();
173 Logger::error('[BMI PRO] Something went wrong during getting token:' . $error_message);
174 return false;
175 } else {
176 $result = json_decode($response['body']);
177 if (isset($result->expiration) && isset($result->access_token)) {
178 $expiresInSeconds = intval($result->expiration) - intval(microtime(true));
179 $accessToken = $result->access_token;
180 set_transient('bmi_pro_access_token', $accessToken, $expiresInSeconds);
181 if (in_array(get_transient('bmip_gd_issue'), ['auth_error', 'auth_error_disconnected'])) {
182 delete_transient('bmip_gd_issue');
183 }
184
185 $this->gdrive_access_token = $accessToken;
186 return $this->gdrive_access_token;
187 }
188 return false;
189 }
190
191 }
192
193 public function verifyConnection($forceGetNewAccessToken = false) {
194 $baseurl = home_url();
195 if (substr($baseurl, 0, 4) != 'http') {
196 if (is_ssl()) $baseurl = 'https://' . home_url();
197 else $baseurl = 'http://' . home_url();
198 }
199
200 $client_token = get_option('bmi_pro_gd_client_id', '');
201 $site_token = get_option('bmi_pro_gd_token', '');
202
203 if (strlen($site_token) < 60 || strlen($client_token) < 60) {
204 return ['status' => 'success', 'result' => 'disconnected'];
205 }
206
207 $url = 'https://authentication.backupbliss.com/v1/gdrive/verify';
208 $response = wp_remote_post($url, array(
209 'method' => 'POST',
210 'timeout' => 15,
211 'redirection' => 2,
212 'httpversion' => '1.0',
213 'blocking' => true,
214 'body' => array(
215 'client_id' => get_option('bmi_pro_gd_client_id', ''),
216 'site_token' => get_option('bmi_pro_gd_token', ''),
217 'force_refresh' => $forceGetNewAccessToken || ( get_transient('bmi_pro_access_token') && get_transient('bmip_gd_issue') === 'auth_error'),
218 'redirect_uri' => $baseurl
219 )
220 ));
221
222 $res = 'disconnected';
223 if (is_wp_error($response)) {
224 $error_message = $response->get_error_message();
225 Logger::error('[BMI PRO] Something went wrong during GDrive connection verification:' . $error_message);
226 return ['status' => 'error', 'result' => 'disconnected'];
227 } else {
228 $result = json_decode($response['body']);
229 if (isset($result->status)) {
230 if (isset($result->expiration) && isset($result->access_token)) {
231 $expiresInSeconds = intval($result->expiration) - intval(microtime(true));
232 $accessToken = $result->access_token;
233 set_transient('bmi_pro_access_token', $accessToken, $expiresInSeconds);
234 }
235
236 if ($result->status == 'disconnected') {
237 $res = 'disconnected';
238 if (get_transient('bmip_gd_issue') === 'auth_error' && get_transient('bmi_pro_access_token')) {
239 set_transient('bmip_gd_issue', 'auth_error_disconnected');
240 delete_transient('bmi_pro_access_token');
241 }
242 }
243 if ($result->status == 'connected'){
244 $res = 'connected';
245 if (in_array(get_transient('bmip_gd_issue'), ['auth_error', 'auth_error_disconnected'])) delete_transient('bmip_gd_issue');
246 }
247 if ($result->status == 'error') $res = 'disconnected';
248 }
249 return ['status' => 'success', 'result' => $res];
250 }
251
252 }
253
254 /**
255 * makeGoogleDriveAPICall - Makes Call to the Google Drive API GET
256 *
257 * @return json status
258 */
259 private function makeGoogleDriveAPICall($uri, $range = false) {
260
261 $access_token = $this->getGoogleDriveAccessToken();
262
263 if ($access_token === false || $access_token === 'false') {
264 return 'error';
265 }
266
267 $headers = array(
268 'Authorization: Bearer ' . rawurlencode($access_token)
269 );
270
271 if ($range != false) $headers[] = 'Range: bytes=' . $range;
272 else $headers[] = 'Content-Type: application/json';
273
274 $url = 'https://www.googleapis.com/drive/v3/' . $uri;
275 $ch = curl_init();
276 curl_setopt($ch, CURLOPT_URL, $url);
277 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
278 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
279 curl_setopt($ch, CURLOPT_TIMEOUT, 30);
280 curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
281
282 $response = curl_exec($ch);
283
284 if (curl_errno($ch)) {
285 $error_message = curl_error($ch);
286 Logger::error('[BMI PRO] Something went wrong during getting file list/content/download:' . $error_message);
287 return 'error';
288 }
289
290 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
291 if ($http_code == 401) {
292 if (get_transient('bmi_pro_access_token') && get_transient('bmip_gd_issue') != 'auth_error_disconnected') set_transient('bmip_gd_issue', 'auth_error', HOUR_IN_SECONDS);
293 }
294
295 // Close the cURL session
296 if (is_resource($ch)) {
297 curl_close($ch);
298 }
299
300 if ($range != false) return $response;
301 else return json_decode($response);
302
303 }
304
305 /**
306 * makeGoogleDriveAPICallDelete - Makes Call to the Google Drive API DELETE
307 *
308 * @return json status
309 */
310 private function makeGoogleDriveAPICallDelete($fileId) {
311
312 $access_token = $this->getGoogleDriveAccessToken();
313
314 if ($access_token === false || $access_token === 'false') {
315 return 'error';
316 }
317
318 $url = 'https://www.googleapis.com/drive/v3/files/' . $fileId;
319 $response = wp_remote_get($url, array(
320 'method' => 'DELETE',
321 'timeout' => 15,
322 'redirection' => 5,
323 'httpversion' => '1.0',
324 'blocking' => true,
325 'headers' => array(
326 'Authorization' => 'Bearer ' . rawurlencode($access_token)
327 )
328 ));
329
330 $http_code = wp_remote_retrieve_response_code($response);
331 if ($http_code == 401) {
332 if (get_transient('bmi_pro_access_token') && get_transient('bmip_gd_issue') != 'auth_error_disconnected') set_transient('bmip_gd_issue', 'auth_error', HOUR_IN_SECONDS);
333 }
334
335 if (is_wp_error($response)) {
336 $error_message = $response->get_error_message();
337 Logger::error('[BMI PRO] Something went wrong during getting file list:' . $error_message);
338 return 'error';
339 } else return json_decode($response['body']);
340
341 }
342
343 /**
344 * makeGoogleDriveAPICallPost - Makes Call to the Google Drive API POST
345 *
346 * @return json status
347 */
348 private function makeGoogleDriveAPICallPost($uri, $postdata, $type = false) {
349
350 $access_token = $this->getGoogleDriveAccessToken();
351 if ($access_token === false || $access_token === 'false') {
352 return 'error';
353 }
354
355 if ($type == 'upload') $url = 'https://www.googleapis.com/upload/drive/v3/' . $uri;
356 else $url = 'https://www.googleapis.com/drive/v3/' . $uri;
357
358 $response = wp_remote_post($url, array(
359 'method' => 'post',
360 'timeout' => 15,
361 'redirection' => 5,
362 'httpversion' => '1.0',
363 'blocking' => true,
364 'headers' => array(
365 'Content-Type' => 'application/json',
366 'Authorization' => 'Bearer ' . rawurlencode($access_token)
367 ),
368 'body' => wp_json_encode($postdata)
369 ));
370
371 $http_code = wp_remote_retrieve_response_code($response);
372 if ($http_code == 401) {
373 if (get_transient('bmi_pro_access_token') && get_transient('bmip_gd_issue') != 'auth_error_disconnected') set_transient('bmip_gd_issue', 'auth_error', HOUR_IN_SECONDS);
374 }
375
376 if (is_wp_error($response)) {
377 $error_message = $response->get_error_message();
378 Logger::error('[BMI PRO] Something went wrong during getting file list:' . $error_message);
379 return 'error';
380 } else {
381 if ($type == 'upload') return $response['headers']['location'];
382 else return json_decode($response['body']);
383 }
384
385 }
386
387 /**
388 * makeGoogleDriveAPICallPutSingle - Makes Call to the Google Drive API PUT
389 * supports only single request upload
390 *
391 * @return json status
392 */
393 private function makeGoogleDriveAPICallPutSingle($url, &$data) {
394
395 $access_token = $this->getGoogleDriveAccessToken();
396 if ($access_token === false || $access_token === 'false') {
397 return 'error';
398 }
399
400 $response = wp_remote_post($url, array(
401 'method' => 'PUT',
402 'timeout' => 15,
403 'redirection' => 5,
404 'httpversion' => '1.0',
405 'blocking' => true,
406 'headers' => array(
407 'Content-Type' => 'text/plain',
408 'Content-Length' => strlen($data),
409 'Authorization' => 'Bearer ' . rawurlencode($access_token)
410 ),
411 'body' => $data
412 ));
413
414 $http_code = wp_remote_retrieve_response_code($response);
415 if ($http_code == 401) {
416 if (get_transient('bmi_pro_access_token') && get_transient('bmip_gd_issue') != 'auth_error_disconnected') set_transient('bmip_gd_issue', 'auth_error', HOUR_IN_SECONDS);
417 }
418
419 if (is_wp_error($response)) {
420 $error_message = $response->get_error_message();
421 Logger::error('[BMI PRO] Something went wrong during PUT upload (single):' . $error_message);
422 return 'error';
423 } else {
424 return $response;
425 }
426
427 }
428
429 /**
430 * makeGoogleDriveAPICallPut - Makes Call to the Google Drive API PUT
431 *
432 * @return json status
433 */
434 private function makeGoogleDriveAPICallPut($url, &$binaryData, $chunkSize, $chunkRange) {
435
436 $access_token = $this->getGoogleDriveAccessToken();
437 if ($access_token === false || $access_token === 'false') {
438 return 'error';
439 }
440
441 $max = ini_get('max_execution_time') - 2;
442 if ($max > 120) $max = 120;
443
444 $response = wp_remote_post($url, array(
445 'method' => 'PUT',
446 'timeout' => $max,
447 'redirection' => 5,
448 'httpversion' => '1.0',
449 'blocking' => true,
450 'headers' => array(
451 'Content-Type' => 'application/octet-stream',
452 'Authorization' => 'Bearer ' . rawurlencode($access_token),
453 'Content-Length' => $chunkSize,
454 'Content-Range' => $chunkRange
455 ),
456 'body' => $binaryData
457 ));
458
459 $http_code = wp_remote_retrieve_response_code($response);
460 if ($http_code == 401) {
461 if (get_transient('bmi_pro_access_token') && get_transient('bmip_gd_issue') != 'auth_error_disconnected') set_transient('bmip_gd_issue', 'auth_error', HOUR_IN_SECONDS);
462 }
463
464 if (is_wp_error($response)) {
465 $error_message = $response->get_error_message();
466 Logger::error('[BMI PRO] Something went wrong during file upload (resumable):' . $error_message);
467 return 'error';
468 } else {
469 return $response;
470 }
471
472 }
473
474 /**
475 * getBMIDirectoryID - Return list of Google Drive backups and their MD5s
476 *
477 * @return json status
478 */
479 private function getBMIDirectoryID() {
480
481 $dirname = esc_attr(sanitize_text_field(Dashboard\bmi_get_config('STORAGE::EXTERNAL::GDRIVE::DIRNAME')));
482
483 $search = rawurlencode("name = '" . $dirname . "' and trashed = false and mimeType = 'application/vnd.google-apps.folder'");
484 $uri = 'files?corpora=user&orderBy=folder&q=' . $search;
485 $api = $this->makeGoogleDriveAPICall($uri);
486
487 if ($api == 'error') return [ 'status' => 'error1' ];
488 if (!isset($api->files)) return [ 'status' => 'error2' ];
489
490 if (sizeof($api->files) <= 0 && !isset($api->files[0])) {
491
492 $directoryCreationData = [
493 'mimeType' => 'application/vnd.google-apps.folder',
494 'name' => $dirname,
495 'parents' => ['root'],
496 ];
497
498 $api = $this->makeGoogleDriveAPICallPost('files', $directoryCreationData);
499 $bmiAppDirectoryID = $api->id;
500
501 } else {
502
503 $bmiAppDirectoryID = $api->files[0]->id;
504
505 }
506
507 return $bmiAppDirectoryID;
508
509 }
510
511 /**
512 * getGoogleDriveBackups - Return list of Google Drive backups and their MD5s
513 *
514 * @return json status
515 */
516 public function getGoogleDriveBackups() {
517
518 $bmiAppDirectoryID = $this->getBMIDirectoryID();
519
520 if (is_array($bmiAppDirectoryID)) return false;
521
522 $search = rawurlencode("'" . $bmiAppDirectoryID . "'" . ' in parents and trashed = false');
523 $uri = 'files?corpora=user&orderBy=folder&fields=files(md5Checksum,+originalFilename,+size,+mimeType,+name,+id)&q=' . $search;
524 $api = $this->makeGoogleDriveAPICall($uri);
525
526 return [ 'status' => 'success', 'data' => $api ];
527
528 }
529
530 /**
531 * createUploadGoogleDriveURL - It will create resumable upload URL
532 *
533 * @return json status
534 */
535 public function createUploadGoogleDriveURL($backupPath, $manifestPath, $forManifest = false) {
536
537 $bmiAppDirectoryID = $this->getBMIDirectoryID();
538
539 $uri = 'files?uploadType=resumable';
540
541 // Make file LINK for RESUMABLE upload
542 $uploadBody = array();
543 if ($forManifest === true) $uploadBody['name'] = basename($manifestPath);
544 else $uploadBody['name'] = basename($backupPath);
545 $uploadBody['parents'] = [$bmiAppDirectoryID];
546 $uploadURL = $this->makeGoogleDriveAPICallPost($uri, $uploadBody, 'upload');
547
548 return [ 'status' => 'success', 'uploadURL' => $uploadURL ];
549
550 }
551
552 /**
553 * uploadManifestFile - It will upload manifest file into BMI directory on Google Drive
554 *
555 * @return array|false status
556 */
557 private function uploadManifestFile($manifestUploadURL, $manifestPath) {
558
559 if (!file_exists($manifestPath)) {
560 return false;
561 }
562
563 $contents = file_get_contents($manifestPath);
564 $api = $this->makeGoogleDriveAPICallPutSingle($manifestUploadURL, $contents);
565
566 return [ 'status' => 'success', 'data' => $api ];
567
568 }
569
570 /**
571 * getGoogleDriveFileContents - Gets file body by Google Drive file ID
572 *
573 * @return json status
574 */
575 public function getGoogleDriveFileContents($fileId, $range = false) {
576
577 $uri = 'files/' . sanitize_text_field($fileId) . '?alt=media';
578 $api = $this->makeGoogleDriveAPICall($uri, $range);
579
580 return [ 'status' => 'success', 'data' => $api ];
581
582 }
583
584 /**
585 * getGoogleDriveFileMeta - Gets file meta data by Google Drive file ID
586 *
587 * @return json status
588 */
589 public function getGoogleDriveFileMeta($fileId) {
590
591 $uri = 'files/' . sanitize_text_field($fileId) . '?fields=md5Checksum,originalFilename,size,mimeType,name,id';
592 $api = $this->makeGoogleDriveAPICall($uri);
593
594 return [ 'status' => 'success', 'data' => $api ];
595
596 }
597
598 /**
599 * @inheritDoc
600 */
601 public function deleteBackup($md5) {
602 $success = true;
603 $found = false;
604 $files = $this->getGoogleDriveBackups();
605 if (isset($files['status']) && $files['status'] == 'success') {
606 $files = $files['data']->files;
607 foreach ($files as $index => $file) {
608 if ($file->md5Checksum == $md5) {
609 $found = true;
610 $fileId = $file->id;
611 if ( $this->makeGoogleDriveAPICallDelete($fileId) == 'error') {
612 $success = false;
613 }
614 } else if ($file->originalFilename == $md5 . '.json'){
615 $found = true;
616 $fileId = $file->id;
617 if ( $this->makeGoogleDriveAPICallDelete($fileId) == 'error') {
618 $success = false;
619 }
620 }
621 }
622 } else {
623 $success = false;
624 }
625
626 return $found && $success;
627 }
628
629 /**
630 * @deprecated Use deleteBackup() instead.
631 */
632 public function deleteGoogleDriveBackup($md5) {
633 return $this->deleteBackup($md5);
634 }
635
636 /**
637 * deleteGoogleDriveJson - Deletes JSON manifest from Google Drive
638 * @deprecated Use deleteBackup() instead.
639 *
640 * @return json status
641 */
642 public function deleteGoogleDriveJson($fileName) {
643
644 $files = $this->getGoogleDriveBackups();
645 if (isset($files['status']) && $files['status'] == 'success') {
646 $files = $files['data']->files;
647 foreach ($files as $index => $file) {
648 if ($file->originalFilename == $fileName) {
649 $fileId = $file->id;
650 $this->makeGoogleDriveAPICallDelete($fileId);
651 }
652 }
653 }
654
655 }
656
657 /**
658 * uploadGoogleDriveFile - It will upload particular file into BMI directory on Google Drive
659 *
660 * @return json status
661 */
662 public function uploadGoogleDriveFile($uploadURL, $filePath, $manifestPath, $md5, $batch, $bytesPerRequest) {
663
664 if (!file_exists($filePath)) {
665
666 update_option('bmip_to_be_uploaded', [
667 'current_upload' => [],
668 'queue' => [],
669 'failed' => []
670 ]);
671
672 return ['status' => 'error'];
673
674 }
675
676 set_transient('bmip_upload_ongoing', '1', 31);
677
678 $batchNumber = intval($batch);
679 $maxLength = filesize($filePath);
680
681 $chunkSize = 256 * 1024 * 4 * intval($bytesPerRequest / 1024 / 1024);
682
683 $chunkOffset = (($batchNumber - 1) * $chunkSize);
684 $rangeEnd = (($chunkSize * $batchNumber) - 1);
685
686 if ($rangeEnd >= $maxLength) $rangeEnd = $maxLength - 1;
687 if (($chunkSize + $chunkOffset) >= $maxLength) $chunkSize = $rangeEnd - $chunkOffset + 1;
688 $range = 'bytes ' . $chunkOffset . '-' . $rangeEnd . '/' . $maxLength;
689 $nextShouldStartAt = $rangeEnd + 1;
690
691
692 if ($stream = fopen($filePath, 'r')) {
693 $binaryData = stream_get_contents($stream, $chunkSize, $chunkOffset);
694 fclose($stream);
695 }
696
697 $api = $this->makeGoogleDriveAPICallPut($uploadURL, $binaryData, $chunkSize, $range);
698 $toBeUploaded = get_option('bmip_to_be_uploaded', false);
699 if (isset($api['response']) && isset($api['response']['code'])) {
700 $code = intval($api['response']['code']);
701 if ($code == 308) {
702
703 $task = $toBeUploaded['current_upload']['task'];
704 if (!isset($toBeUploaded['failed'])) $toBeUploaded['failed'] = [];
705 if (isset($toBeUploaded['failed'][$task])) unset($toBeUploaded['failed'][$task]);
706
707 // All is good
708 if ($toBeUploaded) {
709 $toBeUploaded['current_upload']['batch'] = intval($batch) + 1;
710 $toBeUploaded['current_upload']['progress'] = number_format(($rangeEnd / $maxLength) * 100, 2) . '%';
711 update_option('bmip_to_be_uploaded', $toBeUploaded);
712 }
713
714 } else if ($code == 200) {
715
716 // Upload finished, upload manifest now
717 $manifestUploadURL = $this->createUploadGoogleDriveURL($filePath, $manifestPath, true);
718 $manifestRes = $this->uploadManifestFile($manifestUploadURL['uploadURL'], $manifestPath);
719 if ($manifestRes['status'] === 'success') {
720 $uploadedBackupStatus = get_option('bmi_uploaded_backups_status', []);
721 if (!isset($uploadedBackupStatus[$md5])) {
722 $uploadedBackupStatus[$md5] = [];
723 }
724 $uploadedBackupStatus[$md5]['gdrive'] = true;
725 update_option('bmi_uploaded_backups_status', $uploadedBackupStatus);
726 }
727
728 $task = $toBeUploaded['current_upload']['task'];
729 $toBeUploaded['current_upload'] = [];
730 if (!isset($toBeUploaded['failed'])) $toBeUploaded['failed'] = [];
731 if (isset($toBeUploaded['failed'][$task])) unset($toBeUploaded['failed'][$task]);
732 do_action('bmi_backup_upload_completed', $md5);
733
734
735 update_option('bmip_to_be_uploaded', $toBeUploaded);
736
737 } else if ($code == 403) {
738
739 $message = 'Backup file upload to Google Drive could not be completed due to insufficient free space on Google Drive.<br />';
740 $message .= 'The plugin will automatically retry uploading the backup file within an hour of this error message.<br />';
741 $message .= 'During this time, please try to resolve any issues, such as freeing up space on your Google Drive.<br />';
742
743 // Add required space option to check later
744 add_option('bmip_gd_required_space', filesize($filePath));
745 // Display message
746 set_transient('bmip_display_quota_issues', $message, HOUR_IN_SECONDS);
747 // Force to show the message again
748 delete_option('bmip_dismissed_quota_notice');
749
750 // Mark the backup as failed to upload
751 $task = $toBeUploaded['current_upload']['task'];
752 // Requeueing is handled globally
753 // $toBeUploaded['queue'][$task] = [
754 // 'name' => $toBeUploaded['current_upload']['name'],
755 // 'md5' => $toBeUploaded['current_upload']['md5'],
756 // 'json' => $toBeUploaded['current_upload']['json']
757 // ];
758
759 $toBeUploaded['current_upload'] = [];
760 if (!isset($toBeUploaded['failed'])) $toBeUploaded['failed'] = [];
761 if (isset($toBeUploaded['failed'][$task])) $toBeUploaded['failed'][$task]++;
762 else $toBeUploaded['failed'][$task] = 1;
763
764 update_option('bmip_to_be_uploaded', $toBeUploaded);
765
766 } else if ($code == 429) {
767
768 $message = 'Backup file upload to Google Drive could not be completed due to a limit error.<br />';
769 $message .= 'Received message: <i>Too Many Requests in a short amount of time.</i><br />';
770 $message .= 'The plugin will automatically retry uploading the backup file within 2 minutes of this error message.<br />';
771
772
773 set_transient('bmip_display_quota_issues', $message, 2 * MINUTE_IN_SECONDS);
774
775 } else {
776
777 Logger::error('[BMI PRO] Error during file upload (Google Drive) code:' . $code);
778 if (isset($api['body']) && is_string($api['body'])) {
779 Logger::error('[BMI PRO] Message received (body):' . print_r($api['body'], true));
780 }
781
782 $task = $toBeUploaded['current_upload']['task'];
783 // Requeueing is handled globally
784 // $toBeUploaded['queue'][$task] = [
785 // 'name' => $toBeUploaded['current_upload']['name'],
786 // 'md5' => $toBeUploaded['current_upload']['md5'],
787 // 'json' => $toBeUploaded['current_upload']['json']
788 // ];
789
790 $toBeUploaded['current_upload'] = [];
791 if (!isset($toBeUploaded['failed'])) $toBeUploaded['failed'] = [];
792 if (isset($toBeUploaded['failed'][$task])) $toBeUploaded['failed'][$task]++;
793 else $toBeUploaded['failed'][$task] = 1;
794
795 update_option('bmip_to_be_uploaded', $toBeUploaded);
796
797 }
798 } else {
799
800 $task = $toBeUploaded['current_upload']['task'];
801 // Requeueing is handled globally
802 // $toBeUploaded['queue'][$task] = [
803 // 'name' => $toBeUploaded['current_upload']['name'],
804 // 'md5' => $toBeUploaded['current_upload']['md5'],
805 // 'json' => $toBeUploaded['current_upload']['json']
806 // ];
807
808 $toBeUploaded['current_upload'] = [];
809 if (!isset($toBeUploaded['failed'])) $toBeUploaded['failed'] = [];
810 if (isset($toBeUploaded['failed'][$task])) $toBeUploaded['failed'][$task]++;
811 else $toBeUploaded['failed'][$task] = 1;
812
813 update_option('bmip_to_be_uploaded', $toBeUploaded);
814
815 }
816
817 delete_transient('bmip_upload_ongoing');
818 return [ 'status' => 'success', 'data' => $api ];
819
820 }
821
822 public function getGoogleDriveAvailableStorage() {
823 $uri = 'about?fields=storageQuota';
824 $api = $this->makeGoogleDriveAPICall($uri);
825 $quota = $api->storageQuota;
826 $totalStorage = $quota->limit;
827 $totalUsage = $quota->usage;
828 $availableStorage = $totalStorage - $totalUsage;
829
830 return $availableStorage;
831 }
832
833 }
834