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 / backupbliss.php
backup-backup / includes / external Last commit date
contracts 2 weeks ago backupbliss.php 2 weeks ago controller.php 2 weeks ago dropbox.php 2 weeks ago external-storage-manager.php 2 weeks ago ftp.php 2 weeks ago google-drive.php 2 weeks ago s3.php 2 weeks ago
backupbliss.php
952 lines
1 <?php
2
3 // Namespace
4 namespace BMI\Plugin\External;
5
6 // Use
7 use BMI\Plugin\BMI_Logger as Logger;
8 use BMI\Plugin\Scanner\BMI_BackupsScanner as Backups;
9 use BMI\Plugin\Dashboard as Dashboard;
10 use BMI\Plugin\External\Contracts\GetAvailableSpace as GetAvailableSpaceContract;
11 use BMI\Plugin\External\Contracts\DeleteBackup as DeleteBackupContract;
12
13 // Exit on direct access
14 if (!defined('ABSPATH')) {
15 exit;
16 }
17
18 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'external' . DIRECTORY_SEPARATOR . 'contracts' . DIRECTORY_SEPARATOR . 'interface-get-available-space.php';
19 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'external' . DIRECTORY_SEPARATOR . 'contracts' . DIRECTORY_SEPARATOR . 'interface-delete-backup.php';
20 class BMI_External_BackupBliss implements GetAvailableSpaceContract, DeleteBackupContract
21 {
22
23 public function __construct()
24 {
25
26 add_action('bmi_premium_remove_backup_file', [&$this, 'deleteBackup']);
27 add_action('bmi_premium_remove_backup_json_file', [&$this, 'deleteBackupManifest']);
28 }
29
30 public function process($action, $post) {
31
32 $uri = home_url();
33 if (substr($uri, 0, 4) != 'http') {
34 if (is_ssl()) $uri = 'https://' . home_url();
35 else $uri = 'http://' . home_url();
36 }
37
38 if ($action == "connect") {
39 $ret = $this->getSecret($post['api_key']);
40 if($ret[0] !== false) {
41 Dashboard\bmi_set_config('STORAGE::EXTERNAL::BACKUPBLISS', true);
42 update_option("bmi_pro_backupbliss_key", $ret[1]);
43 return ["status"=>'success'];
44 } else {
45 if ($ret[1] != "")
46 return ["status"=>'fail', "message"=>$ret[1]];
47 }
48
49 return ["status"=>'fail', "message"=>"Invalid API Key provided!"];
50 }
51
52 if ($action == "disconnect") {
53 $res = $this->_makeApiCall("plugin/disconnect", "POST", ["site_url"=>$uri]);
54 Dashboard\bmi_set_config('STORAGE::EXTERNAL::BACKUPBLISS', false);
55 if ($res["status"])
56 if ($res["response_data"]["status"]) {
57 delete_option("bmi_pro_backupbliss_key");
58 return ["status"=>"success"];
59 }
60
61 return ["status"=>"fail", "message"=> "Error disconnecting from the backupbliss server."];
62 }
63
64 if ($action == "storage-info") {
65 $res = $this->_makeApiCall("file/storage-info");
66 if ($res["status"])
67 if ($res["response_data"]["status"]) {
68 return ["status"=>"success", "data"=>$res["response_data"]];
69 }
70
71 return ["status"=>"fail", "message"=>"Error fetching storage info from the backupbliss server."];
72 }
73 }
74
75 /**
76 * checkForBackupsToUpload - Will check for backups that requires to be in sync with cloud
77 *
78 * @return string[]
79 */
80 public function checkForBackupsToUpload()
81 {
82 // Upload Object
83 $requiresUpload = get_option('bmip_to_be_uploaded', [
84 'current_upload' => [],
85 'queue' => [],
86 'failed' => []
87 ]);
88
89 // Local Backups
90 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'scanner' . DIRECTORY_SEPARATOR . 'backups.php';
91 $backups = Backups::getInstance();
92 $backupsAvailable = $backups->getAvailableBackups("local");
93 $localBackups = $backupsAvailable['local'];
94 $localBackups = array_reverse($localBackups);
95 $uploadedBackupStatus = get_option('bmi_uploaded_backups_status', []);
96
97 $files = $this->parseFiles($this->getAllFiles());
98 // if (BMI_DEBUG) {
99 // Logger::error( print_r($files, true));
100 // }
101
102
103 if (!$files) return;
104
105 foreach ($localBackups as $name => $details) {
106
107 $md5 = $details[7];
108 if (isset($uploadedBackupStatus[$md5]) && isset($uploadedBackupStatus[$md5]['backupbliss'])) {
109 continue;
110 }
111
112 if (!(isset($files['manifests'][$md5 . '.json']) && isset($files['backups'][$name]))) {
113
114 $task = 'backupbliss_' . $md5;
115
116 // File is not uploaded action required
117 if (!isset($requiresUpload['queue'][$task])) {
118 $isAnyTaskATM = isset($requiresUpload['current_upload']['task']);
119
120
121 // if (isset($requiresUpload['failed'][$task])) unset($requiresUpload['failed'][$task]);
122
123 if (($isAnyTaskATM && $requiresUpload['current_upload']['task'] != $task) || !$isAnyTaskATM) {
124 $requiresUpload['queue'][$task] = [
125 'name' => $name,
126 'md5' => $md5,
127 'json' => $md5 . '.json'
128 ];
129 }
130 }
131 }
132 }
133
134 update_option('bmip_to_be_uploaded', $requiresUpload);
135 return ['status' => 'success'];
136 }
137
138 public function parseFiles($files)
139 {
140
141 $parsedFiles = ['backups' => [], 'manifests' => []];
142
143 if ($files === false) return false;
144
145 foreach ($files as $index => $file) {
146 if (isset($file['folder'])) {
147 continue; //Skip directories
148 }
149
150 $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
151
152 if (BMI_DEBUG) {
153 // Logger::error("parseFiles - " . $file['name'] . " - " . print_r($file['file'], true));
154 // Logger::error("parseFiles - ext - $extension");
155 }
156
157 if (in_array($extension, ["zip", "tar", "gz"])) {
158 $type = 'backups';
159 }
160
161 if ($extension === 'json') {
162 $type = 'manifests';
163 }
164
165 if (isset($type)) {
166 $parsedFiles[$type][$file['name']] = [
167 'size' => $file['size']
168 ];
169 unset($type);
170 }
171 }
172
173
174 if (BMI_DEBUG) {
175 // Logger::error("parseFiles - " . print_r($parsedFiles, true));
176 }
177
178 return $parsedFiles;
179 }
180
181 public function getSecret($api_key = false)
182 {
183 $tempKeyBackupBlissFiles = BMI_TMP . DIRECTORY_SEPARATOR . 'backupblissKeys.php';
184 if (file_exists($tempKeyBackupBlissFiles)) {
185 $backupblissKeys = file_get_contents($tempKeyBackupBlissFiles);
186 if (strpos($backupblissKeys, "\n") !== false) {
187 $lines = explode("\n", $backupblissKeys);
188 if (sizeof($lines) == 3) {
189 $backupbliss_key = substr($lines[1], 2);
190 if (function_exists('wp_load_alloptions')) {
191 wp_load_alloptions(true);
192 }
193 delete_option("bmi_pro_backupbliss_key");
194 if (function_exists('wp_load_alloptions')) {
195 wp_load_alloptions(true);
196 }
197 update_option("bmi_pro_backupbliss_key", $backupbliss_key);
198 }
199 }
200 if (strpos(site_url(), 'tastewp') !== false) {
201 if (function_exists('wp_load_alloptions')) {
202 wp_load_alloptions(true);
203 }
204
205 update_option('__tastewp_redirection_performed', true);
206 update_option('auto_smart_tastewp_redirect_performed', 1);
207 update_option('tastewp_auto_activated', true);
208 update_option('__tastewp_sub_requested', true);
209 }
210
211 unlink($tempKeyBackupBlissFiles);
212 }
213
214 $uri = home_url();
215 if (substr($uri, 0, 4) != 'http') {
216 if (is_ssl()) $uri = 'https://' . home_url();
217 else $uri = 'http://' . home_url();
218 }
219
220 if (!$api_key)
221 $key = get_option('bmi_pro_backupbliss_key', false);
222 else
223 $key = $api_key;
224
225 if ($key === false) {
226 return [false, ""];
227 } elseif($api_key === false) {
228 return [$key !== false, $key];
229 }
230
231 $url = BMI_BB_STORAGE_API_URI . '/plugin/verify';
232 $response = wp_remote_post($url, array(
233 'method' => 'POST',
234 'timeout' => 15,
235 'redirection' => 2,
236 'httpversion' => '1.0',
237 'blocking' => true,
238 'headers' => ["Content-Type"=>"application/json", "Authorization" => "Bearer ". $key],
239 'body' => json_encode([
240 "site_url" => $uri
241 ])
242 ));
243
244 if (is_wp_error($response)) {
245 $error_message = $response->get_error_message();
246 Logger::error('[BMI PRO] Something went wrong while authenticating the BackupBliss api key:' . $error_message);
247 return [false, $error_message];
248 } else {
249
250 $http_code = wp_remote_retrieve_response_code($response);
251 if (BMI_DEBUG) {
252 Logger::error("[BMI PRO] BackupBliss getSecret: $http_code - " . print_r($response['body'], true));
253 }
254 if ($http_code == 200) {
255 $this->removeNotice("invalid_key");
256 $result = json_decode($response['body']);
257
258
259 if ($result->status) {
260 return [true, $key];
261 }
262 } else if ($http_code == 401) {
263 if ($api_key) //Authentication request so no need to show notice.
264 return [false, ""];
265 $this->_keyDeactivatedNotice();
266 } else if ($http_code == 429) {
267 $result = json_decode($response['body']);
268 return [false, $result->message];
269 }
270 return [false, ""];
271 }
272 }
273
274 public function showNotice($type, $message, $time = 0)
275 {
276 if (BMI_DEBUG) {
277 Logger::error("showNotice($type, $message, $time)");
278 }
279
280 set_transient('bmip_backupbliss_notice_' . $type, $message, $time);
281 $transients = [];
282 $current_trasients = get_transient('bmip_backupbliss_notices');
283 if ($current_trasients) $transients = $current_trasients;
284 $transients[$type] = $type;
285 set_transient('bmip_backupbliss_notices', $transients);
286 }
287
288 public function hideFailureWarnNotice($exp) {
289 set_transient('bmip_backupbliss_hide_failure_notice', true, $exp);
290 }
291
292 public function showFailureWarnNotice() {
293 delete_transient('bmip_backupbliss_hide_failure_notice');
294 }
295
296 public function canShowFailureWarnNotice() {
297 return !get_transient("bmip_backupbliss_hide_failure_notice", false);
298 }
299
300 public function hasRequiredSpaceBeenFreed()
301 {
302 $required_space_file = get_option("bmip_backupbliss_required_space", false);
303 if ($required_space_file === false || !file_exists($required_space_file)) {
304 return true;
305 }
306
307 $required_space = @filesize($required_space_file);
308
309 if ($required_space === false) {
310 return false;
311 }
312
313 $storage_info = $this->getStorageInfo();
314 if (is_array($storage_info) && isset($storage_info["remaining_space"]) && $storage_info["remaining_space"] > $required_space) {
315 return true;
316 }
317 return false;
318 }
319
320 public function getAvailableSpace()
321 {
322 $storage_info = $this->getStorageInfo();
323
324 return is_array($storage_info) && isset($storage_info["remaining_space"]) ? $storage_info["remaining_space"] : false;
325 }
326
327 public function removeNotice($type)
328 {
329 // if (BMI_DEBUG) {
330 // Logger::error("removeNotice($type)");
331 // }
332
333 delete_transient('bmip_backupbliss_notice_' . $type);
334 $current_trasients = get_transient('bmip_backupbliss_notices');
335 if (isset($current_trasients[$type])) {
336 unset($current_trasients[$type]);
337 set_transient('bmip_backupbliss_notices', $current_trasients);
338 }
339 }
340
341 public function hideNotice($type, $time = 0)
342 {
343 if (BMI_DEBUG) {
344 Logger::error("hideNotice($type, $time)");
345 }
346
347 set_transient('bmip_backupbliss_notice_hide_' . $type, true, $time);
348 }
349
350 public function canShowNotice($type)
351 {
352 return !get_transient('bmip_backupbliss_notice_hide_' . $type, false);
353 }
354
355 public function getNotice($type)
356 {
357 // if (BMI_DEBUG) {
358 // Logger::error("getNotice($type)");
359 // }
360
361 return get_transient("bmip_backupbliss_notice_" . $type);
362 }
363
364 public function getNotices()
365 {
366 $bmip_backupbliss_notices = get_transient("bmip_backupbliss_notices");
367 $temp_notices = $bmip_backupbliss_notices;
368 $notices = [];
369 if ($bmip_backupbliss_notices) {
370 foreach ($bmip_backupbliss_notices as $notice) {
371 $noticemessage = get_transient("bmip_backupbliss_notice_" . $notice);
372 if ($noticemessage) {
373 $notices[$notice] = $noticemessage;
374 } else {
375 unset($temp_notices[$notice]);
376 }
377 }
378 }
379
380 if ($bmip_backupbliss_notices !== $temp_notices) {
381 set_transient("bmip_backupbliss_notices", $temp_notices);
382 }
383
384 return $notices;
385 }
386
387
388 private function _makeApiCall($url, $req_type = "GET", $body = [], $custom_headers = null)
389 {
390 $url = BMI_BB_STORAGE_API_URI . $url;
391
392 if (BMI_DEBUG) {
393 $backtrace = debug_backtrace();
394 // Get the caller's function name
395 $callerFunction = isset($backtrace[1]['function']) ? $backtrace[1]['function'] : 'unknown';
396 }
397 if (BMI_DEBUG) {
398 Logger::error("[BMI PRO][BackupBliss] REQUEST FROM $callerFunction() in _makeApiCall($url, $req_type, " . ($req_type != "PUT" ? print_r($body, true) : "BINARYDATA") . ", " . print_r($custom_headers, true) . ")");
399 }
400
401 $secret = $this->getSecret();
402 if (!$secret[0]) {
403 return ["status" => false];
404 }
405
406 $max_execution_time_pre_limit = ini_get('max_execution_time') - 2;
407
408 $headers = $custom_headers == null ? [
409 'Authorization: Bearer ' . $secret[1],
410 'Accept: application/json',
411 'Content-Type: application/json',
412 ] : $custom_headers;
413
414
415
416 $ch = curl_init();
417 curl_setopt($ch, CURLOPT_URL, $url);
418 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
419 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
420 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $req_type);
421 curl_setopt($ch, CURLOPT_TIMEOUT, $max_execution_time_pre_limit);
422
423 if ($req_type == "POST") {
424 curl_setopt($ch, CURLOPT_POST, true);
425 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
426 } elseif ($req_type == "PUT") {
427 curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
428 }
429
430 $response = curl_exec($ch);
431 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
432
433 if ($req_type == "DELETE") {
434 return $http_code;
435 }
436
437 if (curl_errno($ch)) {
438 Logger::error("[BMI PRO][BackupBliss] Error in _makeApiCall request: Type: $req_type HTTP Code: $http_code. Error: " . curl_error($ch));
439 return ["status" => false, "http_code" => $http_code, "response_data" => $response];
440 }
441
442 if (is_resource($ch)) {
443 curl_close($ch);
444 }
445
446 if ($http_code >= 200 && $http_code <= 299) {
447 $response_data = $custom_headers == null ? json_decode($response, true) : $response;
448 if (BMI_DEBUG) {
449 //Logger::error("[BMI PRO] RESPONSE _makeApiCall - " . print_r($response_data, true));
450 }
451 return ["status" => true, "http_code" => $http_code, "response_data" => $response_data];
452 } else {
453 if ($http_code == 401) {
454 $this->_keyDeactivatedNotice();
455 } elseif ($http_code == 403) {
456 $response_data = json_decode($response, true);
457 $this->_accessPermissionNotice($response_data['message']);
458 }
459 Logger::error("[BMI PRO][BackupBliss] Error in _makeApiCall request: Type: $req_type HTTP Code: $http_code. Response: $response");
460 return ["status" => false, "http_code" => $http_code, "response_data" => $response];
461 }
462 }
463
464 private function _accessPermissionNotice($message) {
465 Logger::error("[BMI] " . $message);
466 $this->showNotice("invalid_permission", $message, 0);
467 }
468
469 private function _keyDeactivatedNotice() {
470 Logger::error("[BMI] The API key is either invalid or deactivated.");
471 $message = 'There was an error while authenticating the BackupBliss API key.<br />';
472 $message .= 'Your BackupBliss API key got deactivated, hence moving backups to the BackupBliss storage is failing or will fail.<br>';
473 $message .= 'Please connect with a new API key by accessing your <a target="_blank" href="' . BMI_BB_STORAGE_URI . '">backupbliss storage</a> account.';
474
475 $this->showNotice("invalid_key", $message);
476 delete_option('bmi_pro_backupbliss_key');
477 $this->removeNotice("storage_warn");
478 $this->removeNotice("upload_issue");
479 $this->removeNotice("upload_issue_space");
480 }
481
482 public function getFileDetailByName($file_name) {
483 $response_data = $this->_makeApiCall("file/file-info/$file_name");
484
485 if($response_data["status"] && $response_data["response_data"]["status"]) {
486 return $response_data["response_data"]["file_info"];
487 }
488
489 return false;
490 }
491
492 private function deleteFile($file_name)
493 {
494 if (BMI_DEBUG) {
495 Logger::error("deleteFile($file_name)");
496 }
497
498 $url = "file/delete/$file_name";
499
500 $http_code = $this->_makeApiCall($url, "DELETE");
501
502 // Handle the response based on the status code
503 if ($http_code == 200) {
504 // 'File deleted successfully'
505 return True;
506 } else {
507 //Logger::error("[BMI PRO] Error in deleteFile. HTTP Code: $http_code.");
508 return False;
509 }
510 }
511
512 /**
513 * @inheritDoc
514 */
515 public function deleteBackup($md5)
516 {
517
518 if (BMI_DEBUG) {
519 Logger::error("deleteBackup($md5)");
520 }
521
522 $manifest = $this->getManifest($md5);
523 if ($manifest) {
524 $deleteBackup = $this->deleteFile($manifest->name); // BB delete the backup and the related manifest file as well
525
526 if ($deleteBackup) {
527 return true;
528 }
529 return false;
530 }
531 return false;
532 }
533
534 public function getManifest($md5)
535 {
536 $manifest = false;
537 $localManifest = BMI_BACKUPS . DIRECTORY_SEPARATOR . $md5 . '.json';
538
539 if (file_exists($localManifest)) {
540
541 $manifestData = file_get_contents($localManifest);
542 $manifest = json_decode($manifestData);
543 } else {
544
545 $manifestData = $this->getFile($md5 . '.json');
546 if (is_array($manifestData) && $manifestData["file_data"]) {
547
548 $manifest = json_decode($manifestData["file_data"]);
549 }
550 }
551 return $manifest;
552 }
553
554 /**
555 * @deprecated Use deleteBackup() instead.
556 */
557 public function deleteBackupManifest($md5_json)
558 {
559 if (BMI_DEBUG) {
560 Logger::error("deleteBackupManifest($md5_json)");
561 }
562
563 $this->deleteFile($md5_json);
564 }
565
566 public function initiateUploadSession($file_path)
567 {
568
569 $uri = home_url();
570 if (substr($uri, 0, 4) != 'http') {
571 if (is_ssl()) $uri = 'https://' . home_url();
572 else $uri = 'http://' . home_url();
573 }
574
575 $file_name = basename($file_path);
576
577 if (BMI_DEBUG) {
578 Logger::error("[BMI PRO][BackupBliss] initiateUploadSession - $file_path - $file_name");
579 }
580
581
582 $url = 'file/initiate-upload-session';
583 $body = [
584 'filename' => $file_name,
585 'site_url' => $uri,
586 'file_size' => file_exists($file_path) ? filesize($file_path) : 0
587 ];
588
589 $response = $this->_makeApiCall($url, "POST", $body);
590 if ($response["status"]) {
591 $session = $response["response_data"];
592 if ($session["status"] && isset($session['upload_id']) && !empty($session['upload_id'])) {
593 return $session;
594 } else {
595 Logger::error("[BMI PRO][BackupBliss] Failed to create upload session: " . json_encode($session));
596 return false;
597 }
598 } else {
599 Logger::error("[BMI PRO][BackupBliss] Failed to create upload session: " . json_encode($response));
600 return false;
601 }
602 }
603
604 public function uploadChunkWithSession($upload_session, $chunk_data, $start_byte, $end_byte, $total_size)
605 {
606 if (BMI_DEBUG) {
607 // Logger::error("[BMI PRO] BEFORE UPLOAD uploadChunkWithSession(" . $upload_session['uploadUrl'] . ", $start_byte, $end_byte, $total_size" . ")\n" . print_r($headers, true));
608 }
609
610 $response = $this->_makeApiCall("file/upload-chunk/".$upload_session['upload_id'], "PUT", $chunk_data);
611
612 if ($response["status"]) {
613 if (BMI_DEBUG) {
614 // Logger::error("[BMI PRO] AFTER UPLOAD uploadChunkWithSession\n" . print_r($response, true));
615 }
616 return $response;
617 }
618
619 Logger::error("[BMI PRO] Failed to upload chunks. Start byte: $start_byte. End byte: $end_byte. Total Size: $total_size");
620 if (isset($response['http_code'])) {
621 if ($response['http_code'] == 429) {
622 $retryAfter = $this->parseRetryAfterError($response['response_data']);
623 Logger::error("[BMI PRO] Received 429 Too Many Requests. Retrying after $retryAfter seconds.");
624 $response['retry_after'] = $retryAfter;
625 return $response;
626 }
627 }
628 return $response;
629 }
630
631 public function getAllFiles()
632 {
633 $response = $this->_makeApiCall('file/backups');
634 if (BMI_DEBUG) {
635 // Logger::error("[BMI PRO] getAllBackups " . print_r($response, true));
636 }
637 if ($response["status"] && $response["response_data"]["status"]) {
638 $files = $response["response_data"];
639 return $files['backups'];
640 }
641
642 return false;
643 }
644
645 public function verifyConnection()
646 {
647 $res = $this->getSecret()[0] ? 'connected' : 'disconnected';
648 return [ 'status' => 'success', 'result' => $res ];
649 }
650
651 private function downloadFile($file_details, $start_byte = 0, $end_byte = null)
652 {
653 if (BMI_DEBUG) {
654 //Logger::error("downloadFile(" . print_r($file_details, true) . ", $start_byte, $end_byte)");
655 }
656 if (!isset($file_details['download_hash'])) {
657 Logger::error('[BMI PRO] Download URL not available in the file details.');
658 return false;
659 }
660
661 $headers = [];
662
663 // Set the Range header for partial download
664 if ($end_byte !== null) {
665 $headers = [
666 'Range: bytes=' . $start_byte . '-' . $end_byte
667 ];
668 } else {
669 $headers = [
670 'Range: bytes=' . $start_byte . '-'
671 ];
672 }
673
674 $response = $this->_makeApiCall("file/download/".$file_details["download_hash"], "GET", [], $headers);
675
676 if ($response["status"]) {
677 return $response["response_data"];
678 }
679
680 return false;
681 }
682
683 public function getFile($file_name, $start_byte = 0, $end_byte = null)
684 {
685 if (BMI_DEBUG) {
686 Logger::error("getFile $file_name");
687 }
688 $file_detail = $this->getFileDetailByName($file_name);
689 if ($file_detail) {
690 return ["file_detail" => $file_detail, "file_data" => $this->downloadFile($file_detail, $start_byte, $end_byte)];
691 }
692 return false;
693 }
694
695 public function getStorageInfo()
696 {
697 $response = $this->_makeApiCall("file/storage-info");
698
699 if (BMI_DEBUG) {
700 // Logger::error("getStorageInfo - " . print_r($response, true));
701 }
702
703 if ($response["status"] && $response["response_data"]["status"]) {
704 return $response["response_data"]["storage_info"];
705 }
706
707 return false;
708 }
709
710 public function completeUpload($upload_id)
711 {
712 $response = $this->_makeApiCall('file/complete-upload', "POST", ['upload_id'=>$upload_id]);
713 if ($response["status"]) {
714 return $response["response_data"];
715 }
716
717 return false;
718 }
719
720 public function uploadFile($uploadSession, $filePath, $manifestPath, $md5, $batch, $bytesPerRequest)
721 {
722
723 if (!file_exists($filePath)) {
724
725 update_option('bmip_to_be_uploaded', [
726 'current_upload' => [],
727 'queue' => [],
728 'failed' => []
729 ]);
730
731 return ['status' => 'error'];
732 }
733
734 set_transient('bmip_upload_ongoing', '1', 31);
735
736 $batchNumber = intval($batch);
737 $maxLength = filesize($filePath);
738
739 # Microsoft recommended multiple value 320Kb
740 $chunkSize = 4 * 327680 * intval($bytesPerRequest / 1024 / 1024);
741
742 # Limit the chunk size to max of 50MB with multiple of recommended value
743 $maxChunkSize = 50 * 1024 * 1024;
744 $chunkSize = $chunkSize > $maxChunkSize ? $maxChunkSize : $chunkSize;
745
746 $chunkOffset = (($batchNumber - 1) * $chunkSize);
747 $rangeEnd = (($chunkSize * $batchNumber) - 1);
748
749 if ($rangeEnd >= $maxLength) $rangeEnd = $maxLength - 1;
750 if (($chunkSize + $chunkOffset) >= $maxLength) $chunkSize = $rangeEnd - $chunkOffset + 1;
751 $nextShouldStartAt = $rangeEnd + 1;
752
753
754 if ($stream = fopen($filePath, 'r')) {
755 $binaryData = stream_get_contents($stream, $chunkSize, $chunkOffset);
756 fclose($stream);
757 }
758
759
760 $toBeUploaded = get_option('bmip_to_be_uploaded', false);
761
762 if (isset($toBeUploaded['current_upload']['verifying'])) {
763 if (!$this->getFileDetailByName($md5 . '.json') || !$this->getFileDetailByName(basename($filePath))) {
764 $task = $toBeUploaded['current_upload']['task'];
765 $toBeUploaded['current_upload'] = [];
766 if (!isset($toBeUploaded['failed'])) $toBeUploaded['failed'] = [];
767 $toBeUploaded['failed'][$task] = 1;
768
769 update_option('bmip_to_be_uploaded', $toBeUploaded);
770 return ['status' => 'fail', 'data' => 'File not found on server during verification.'];
771 }
772 $uploadedBackupStatus = get_option('bmi_uploaded_backups_status', []);
773 if (!isset($uploadedBackupStatus[$md5])) {
774 $uploadedBackupStatus[$md5] = [];
775 }
776 $uploadedBackupStatus[$md5]['backupbliss'] = true;
777 update_option('bmi_uploaded_backups_status', $uploadedBackupStatus);
778
779 $task = $toBeUploaded['current_upload']['task'];
780 $toBeUploaded['current_upload'] = [];
781 if (!isset($toBeUploaded['failed'])) $toBeUploaded['failed'] = [];
782 if (isset($toBeUploaded['failed'][$task])) unset($toBeUploaded['failed'][$task]);
783 do_action('bmi_backup_upload_completed', $md5);
784 update_option('bmip_to_be_uploaded', $toBeUploaded);
785 return ['status' => 'success', 'data' => 'File verified successfully.'];
786 }
787
788 if (BMI_DEBUG)
789 {
790 Logger::error("Before uploadFile - " . print_r($uploadSession, true));
791 Logger::error("Before uploadFile - " . print_r($toBeUploaded['current_upload']['batch'], true));
792 }
793
794 $response = $this->uploadChunkWithSession($uploadSession, $binaryData, $chunkOffset, $rangeEnd, $maxLength);
795
796 $code = intval($response['http_code']);
797
798 if ($response["status"]) {
799
800 if ($rangeEnd == $maxLength - 1) //Last chunk already uploaded so complete upload and upload manifest
801 {
802 if ($code != 201) //If upload is not already completed
803 {
804 $response = $this->_makeApiCall('file/complete-upload', "POST", ['upload_id'=>$uploadSession['upload_id']]);
805 $code = intval($response['http_code']);
806 if ($code != 201) { //Something failed while completing the upload
807 $task = $toBeUploaded['current_upload']['task'];
808 $toBeUploaded['current_upload'] = [];
809 if (!isset($toBeUploaded['failed'])) $toBeUploaded['failed'] = [];
810 $toBeUploaded['failed'][$task] = 1;
811
812 update_option('bmip_to_be_uploaded', $toBeUploaded);
813 return ['status' => 'fail', 'data' => $response];
814 }
815 }
816
817 $manifestUploadSession = $this->initiateUploadSession($manifestPath);
818
819 if (!$manifestUploadSession) { //Something failed while completing the upload
820 $task = $toBeUploaded['current_upload']['task'];
821 $toBeUploaded['current_upload'] = [];
822 if (!isset($toBeUploaded['failed'])) $toBeUploaded['failed'] = [];
823 $toBeUploaded['failed'][$task] = 1;
824
825 update_option('bmip_to_be_uploaded', $toBeUploaded);
826 return ['status' => 'fail', 'data' => $response];
827 }
828
829 if ($stream = fopen($manifestPath, 'r')) {
830 $binaryData = stream_get_contents($stream);
831 fclose($stream);
832 }
833
834 $size = strlen($binaryData);
835 $manifestRes = $this->uploadChunkWithSession($manifestUploadSession, $binaryData, 0, $size - 1, $size);
836 if (
837 $manifestRes['status'] == true
838 && intval($manifestRes['http_code']) == 201
839 ) {
840 $toBeUploaded['current_upload']['verifying'] = true;
841 update_option('bmip_to_be_uploaded', $toBeUploaded);
842 return ['status' => 'success', 'data' => $response];
843 }
844
845 return ['status' => 'fail', 'data' => $manifestRes];
846 }
847
848
849 //Chunk accepted, let's continue uploading
850 if ($code == 202) {
851
852 $task = $toBeUploaded['current_upload']['task'];
853 if (!isset($toBeUploaded['failed'])) $toBeUploaded['failed'] = [];
854 if (isset($toBeUploaded['failed'][$task])) unset($toBeUploaded['failed'][$task]);
855
856 $toBeUploaded['current_upload']['batch'] = intval($batch) + 1;
857 $toBeUploaded['current_upload']['progress'] = number_format(($rangeEnd / $maxLength) * 100, 2) . '%';
858 update_option('bmip_to_be_uploaded', $toBeUploaded);
859
860 if (BMI_DEBUG)
861 Logger::error("After uploadFile - " . print_r($toBeUploaded['current_upload']['batch'], true));
862
863 $test = get_option('bmip_to_be_uploaded', false);
864
865 if (BMI_DEBUG)
866 Logger::error("After updating option uploadFile - " . print_r($test['current_upload']['batch'], true));
867 }
868 } elseif ($code == 507) {
869
870 $error_message_notice = 'Moving backups to your storage is failing or will fail because you don’t have enough space.';
871
872 add_option("bmip_backupbliss_required_space", $filePath);
873 $this->showNotice("upload_issue_space", $error_message_notice, 60 * 60);
874 } elseif ($code == 508) {
875
876 $error_message_notice = 'You’re using more space than allowed. No new backups will be moved to your storage and some of the <b>existing backups will be deleted very soon</b>. ';
877
878 $this->showNotice("upload_issue_space", $error_message_notice, 60 * 60);
879 } elseif ($code == 429) {
880
881 $error_message_notice = 'Upload to BackupBliss could not finish, due to rate limit error.<br />';
882 $error_message_notice .= 'Received message: <i>Too Many Requests in a short amount of time.</i><br />';
883 $error_message_notice .= 'Plugin will retry uploading automatically after 2 minutes.<br />';
884
885 $this->showNotice('upload_issue', $error_message_notice, 60 * 2);
886 } else {
887
888 Logger::error('[BMI PRO] Error during file upload (BackupBliss) code:' . $code);
889 if (isset($response['response_data']) && is_string($response['response_data'])) {
890 Logger::error('[BMI PRO] Message received (body):' . print_r($response['response_data'], true));
891 }
892
893 $error_message_notice = 'Upload to BackupBliss could not finish, due to an error:<br />';
894 if (is_string($response['response_data'])) {
895 $error = json_decode($response['response_data']);
896 if (isset($error->error->message) || isset($error->message)) {
897 $errorMessage = isset($error->error->message) ? $error->error->message : $error->message;
898 $error_message_notice .= "Code: $code - Received message:<i>" . $errorMessage . '</i><br />';
899 } else if ($error == null) {
900 $cleanResponse = strip_tags(trim($response["response_data"]));
901 $cleanResponse = preg_replace('/\s+/', ' ', $cleanResponse);
902 if (empty($cleanResponse)) {
903 $cleanResponse = 'Unknown server error occurred';
904 }
905 $error_message_notice .= "HTTP Error Response: <i>" . htmlspecialchars($cleanResponse) . '</i><br />';
906 }
907 }
908
909 if ($code == 0) {
910 $error_message_notice .= "Received Message: <i>Connection timed out. (This is most likely due to a connection issue in your server.)</i><br />";
911 }
912
913
914
915 $error_message_notice .= "Plugin will retry uploading automatically within a minute since this error.";
916 $this->showNotice("upload_issue", $error_message_notice, 60);
917 }
918
919
920 if (isset($error_message_notice)) {
921 $task = $toBeUploaded['current_upload']['task'];
922 // Requeueing is handled globally
923 // $toBeUploaded['queue'][$task] = [
924 // 'name' => $toBeUploaded['current_upload']['name'],
925 // 'md5' => $toBeUploaded['current_upload']['md5'],
926 // 'json' => $toBeUploaded['current_upload']['json']
927 // ];
928
929 $toBeUploaded['current_upload'] = [];
930 if (!isset($toBeUploaded['failed'])) $toBeUploaded['failed'] = [];
931 if (isset($toBeUploaded['failed'][$task])) $toBeUploaded['failed'][$task]++;
932 else $toBeUploaded['failed'][$task] = 1;
933
934 update_option('bmip_to_be_uploaded', $toBeUploaded);
935 }
936
937 delete_transient('bmip_upload_ongoing');
938 return ['status' => 'success', 'data' => $response];
939 }
940
941
942 public function parseRetryAfterError($response)
943 {
944 $regex = 'after (\d+) seconds';
945 if (preg_match("/$regex/i", $response, $matches)) {
946 return intval($matches[1]);
947 } else {
948 return 60; //Default retry after time in seconds
949 }
950 }
951 }
952