PluginProbe
InfiniteWP Client / trunk
InfiniteWP Client vtrunk
1.13.10 1.13.7 trunk 0.1.4 0.1.5 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.1.1 1.1.10 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.11.0 1.11.1 1.12.1 1.12.3 All 92 releases
iwp-client / backup / dropbox.php

dropbox.php in InfiniteWP Client trunk, at backup/dropbox.php

540 lines 21.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // https://www.dropbox.com/developers/apply?cont=/developers/apps
4 if ( ! defined('ABSPATH') )
5 die();
6
7 if (!class_exists('IWP_MMB_UploadModule')) require_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/backup.upload.php');
8
9 # Fix a potential problem for users who had the short-lived 1.12.35-1.12.38 free versions (see: https://wordpress.org/support/topic/1-12-37-dropbox-auth-broken/page/2/#post-8981457)
10 # Can be removed after a few months
11 $potential_options = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_dropbox');
12 if (is_array($potential_options) && isset($potential_options['version']) && isset($potential_options['settings']) && array() === $potential_options['settings']) {
13 // Wipe it, which wil force its re-creation in proper format
14 IWP_MMB_Backup_Options::delete_iwp_backup_option('IWP_dropbox');
15 }
16
17
18 class IWP_MMB_UploadModule_dropbox extends IWP_MMB_UploadModule {
19
20 private $current_file_hash;
21 private $current_file_size;
22 private $dropbox_object;
23 private $uploaded_offset;
24 private $upload_tick;
25
26 public function chunked_callback($offset, $uploadid, $fullpath = false) {
27 global $iwp_backup_core;
28
29 // Update upload ID
30 $iwp_backup_core->jobdata_set('IWP_dbid_'.$this->current_file_hash, $uploadid);
31 $iwp_backup_core->jobdata_set('IWP_dbof_'.$this->current_file_hash, $offset);
32
33 $time_now = microtime(true);
34 $storage = $this->get_storage();
35
36 $time_since_last_tick = $time_now - $this->upload_tick;
37 $data_since_last_tick = $offset - $this->uploaded_offset;
38
39 $this->upload_tick = $time_now;
40 $this->uploaded_offset = $offset;
41
42 $chunk_size = $iwp_backup_core->jobdata_get('dropbox_chunk_size', 1048576);
43 // Don't go beyond 10MB, or change the chunk size after the last segment
44 if ($chunk_size < 10485760 && $this->current_file_size > 0 && $offset < $this->current_file_size) {
45 $job_run_time = $time_now - $iwp_backup_core->job_time_ms;
46 if ($time_since_last_tick < 10) {
47 $upload_rate = $data_since_last_tick / max($time_since_last_tick, 1);
48 $upload_secs = min(floor($job_run_time), 10);
49 if ($job_run_time < 15) $upload_secs = max(6, $job_run_time*0.6);
50 $new_chunk = max(min($upload_secs * $upload_rate * 0.9, 10485760), 1048576);
51 $new_chunk = $new_chunk - ($new_chunk % 524288);
52 $chunk_size = intval($new_chunk);
53 $storage->setChunkSize($chunk_size);
54 $iwp_backup_core->jobdata_set('dropbox_chunk_size', $chunk_size);
55 }
56 }
57
58 if ($this->current_file_size > 0) {
59 $percent = round(100*($offset/$this->current_file_size),1);
60 $iwp_backup_core->record_uploaded_chunk($percent, "$uploadid, $offset, ".round($chunk_size/1024, 1)." KB", $fullpath);
61 } else {
62 $iwp_backup_core->log("Dropbox: Chunked Upload: $offset bytes uploaded");
63 // This act is done by record_uploaded_chunk, and helps prevent overlapping runs
64 touch($fullpath);
65 }
66 }
67
68 public function get_supported_features() {
69 // This options format is handled via only accessing options via $this->get_options()
70 return array('multi_options');
71 }
72
73 public function get_default_options() {
74 return array(
75 'appkey' => '',
76 'secret' => '',
77 'folder' => '',
78 'tk_access_token' => '',
79 );
80 }
81
82 public function backup($backup_array) {
83
84 global $iwp_backup_core;
85
86 $opts = $this->get_options();
87
88 if (empty($opts['tk_access_token'])) {
89 $iwp_backup_core->log('You do not appear to be authenticated with Dropbox (1)');
90 $iwp_backup_core->log(__('You do not appear to be authenticated with Dropbox','InfiniteWP'), 'error');
91 return false;
92 }
93
94 // 28 June 2017
95 $use_api_ver = 2;
96
97 if (empty($opts['tk_request_token'])) {
98 $iwp_backup_core->log("Dropbox: begin cloud upload (using API version $use_api_ver with OAuth v2 token)");
99 } else {
100 $iwp_backup_core->log("Dropbox: begin cloud upload (using API version $use_api_ver with OAuth v1 token)");
101 }
102
103 $chunk_size = $iwp_backup_core->jobdata_get('dropbox_chunk_size', 1048576);
104
105 try {
106 $dropbox = $this->bootstrap();
107 if (false === $dropbox) throw new Exception(__('You do not appear to be authenticated with Dropbox', 'InfiniteWP'));
108 $iwp_backup_core->log("Dropbox: access gained; setting chunk size to: ".round($chunk_size/1024, 1)." KB");
109 $dropbox->setChunkSize($chunk_size);
110 } catch (Exception $e) {
111 $iwp_backup_core->log('Dropbox error when trying to gain access: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
112 $iwp_backup_core->log(sprintf(__('Dropbox error: %s (see log file for more)','InfiniteWP'), $e->getMessage()), 'error');
113 return false;
114 }
115
116 $iwp_backup_dir = $iwp_backup_core->backups_dir_location();
117
118
119 foreach ($backup_array as $file) {
120
121 $available_quota = -1;
122
123 // If we experience any failures collecting account info, then carry on anyway
124 try {
125
126 /*
127 Quota information is no longer provided with account information a new call to quotaInfo must be made to get this information.
128 */
129 if (1 == $use_api_ver) {
130 $quotaInfo = $dropbox->accountInfo();
131 } else {
132 $quotaInfo = $dropbox->quotaInfo();
133 }
134
135 if ($quotaInfo['code'] != "200") {
136 $message = "Dropbox account/info did not return HTTP 200; returned: ". $quotaInfo['code'];
137 } elseif (!isset($quotaInfo['body'])) {
138 $message = "Dropbox account/info did not return the expected data";
139 } else {
140 $body = $quotaInfo['body'];
141 if (isset($body->quota_info)) {
142 $quota_info = $body->quota_info;
143 $total_quota = $quota_info->quota;
144 $normal_quota = $quota_info->normal;
145 $shared_quota = $quota_info->shared;
146 $available_quota = $total_quota - ($normal_quota + $shared_quota);
147 $message = "Dropbox quota usage: normal=".round($normal_quota/1048576,1)." MB, shared=".round($shared_quota/1048576,1)." MB, total=".round($total_quota/1048576,1)." MB, available=".round($available_quota/1048576,1)." MB";
148 } else {
149 $total_quota = max($body->allocation->allocated, 1);
150 $used = $body->used;
151 /* check here to see if the account is a team account and if so use the other used value
152 This will give us their total usage including their individual account and team account */
153 if (isset($body->allocation->used)) $used = $body->allocation->used;
154 $available_quota = $total_quota - $used;
155 $message = "Dropbox quota usage: used=".round($used/1048576,1)." MB, total=".round($total_quota/1048576,1)." MB, available=".round($available_quota/1048576,1)." MB";
156 }
157 }
158 $iwp_backup_core->log($message);
159 } catch (Exception $e) {
160 $iwp_backup_core->log("Dropbox error: exception (".get_class($e).") occurred whilst getting account info: ".$e->getMessage());
161 }
162
163 $file_success = 1;
164
165 $hash = md5($file);
166 $this->current_file_hash = $hash;
167
168 $filesize = filesize($iwp_backup_dir.'/'.$file);
169 $this->current_file_size = $filesize;
170
171 // Into KB
172 $filesize = $filesize/1024;
173 $microtime = microtime(true);
174
175 if ($upload_id = $iwp_backup_core->jobdata_get('IWP_dbid_'.$hash)) {
176 # Resume
177 $offset = $iwp_backup_core->jobdata_get('IWP_dbof_'.$hash);
178 $iwp_backup_core->log("This is a resumption: $offset bytes had already been uploaded");
179 } else {
180 $offset = 0;
181 $upload_id = null;
182 }
183
184 // We don't actually abort now - there's no harm in letting it try and then fail
185 if ($available_quota != -1 && $available_quota < ($filesize-$offset)) {
186 $iwp_backup_core->log("File upload expected to fail: file data remaining to upload ($file) size is ".($filesize-$offset)." b (overall file size; .".($filesize*1024)." b), whereas available quota is only $available_quota b");
187 }
188
189
190 $ufile = apply_filters('IWP_dropbox_modpath', $file, $this);
191
192 $iwp_backup_core->log("Dropbox: Attempt to upload: $file to: $ufile");
193
194 $this->upload_tick = microtime(true);
195 $this->uploaded_offset = $offset;
196
197 try {
198 $response = $dropbox->chunkedUpload($iwp_backup_dir.'/'.$file, '', $ufile, true, $offset, $upload_id, array($this, 'chunked_callback'));
199 if (empty($response['code']) || "200" != $response['code']) {
200 $iwp_backup_core->log('Unexpected HTTP code returned from Dropbox: '.$response['code']." (".serialize($response).")");
201 if ($response['code'] >= 400) {
202 $iwp_backup_core->log('Dropbox '.sprintf(__('error: failed to upload file to %s (see log file for more)','iwp_backup_core'), $file), 'error');
203 } else {
204 $iwp_backup_core->log(sprintf(__('%s did not return the expected response - check your log file for more details', 'iwp_backup_core'), 'Dropbox'), 'warning');
205 }
206 }
207 } catch (Exception $e) {
208 $iwp_backup_core->log("Dropbox chunked upload exception (".get_class($e)."): ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
209 if (preg_match("/Submitted input out of alignment: got \[(\d+)\] expected \[(\d+)\]/i", $e->getMessage(), $matches)) {
210 // Try the indicated offset
211 $we_tried = $matches[1];
212 $dropbox_wanted = (int)$matches[2];
213 $iwp_backup_core->log("Dropbox not yet aligned: tried=$we_tried, wanted=$dropbox_wanted; will attempt recovery");
214 $this->uploaded_offset = $dropbox_wanted;
215 try {
216 $dropbox->chunkedUpload($iwp_backup_dir.'/'.$file, '', $ufile, true, $dropbox_wanted, $upload_id, array($this, 'chunked_callback'));
217 } catch (Exception $e) {
218 $msg = $e->getMessage();
219 if (preg_match('/Upload with upload_id .* already completed/', $msg)) {
220 $iwp_backup_core->log('Dropbox returned an error, but apparently indicating previous success: '.$msg);
221 } else {
222 $iwp_backup_core->log('Dropbox error: '.$msg.' (line: '.$e->getLine().', file: '.$e->getFile().')');
223 $iwp_backup_core->log('Dropbox '.sprintf(__('error: failed to upload file to %s (see log file for more)','iwp_backup_core'), $ufile), 'error');
224 $file_success = 0;
225 if (strpos($msg, 'select/poll returned error') !== false && $this->upload_tick > 0 && time() - $this->upload_tick > 800) {
226 $iwp_backup_core->reschedule(60);
227 $iwp_backup_core->log("Select/poll returned after a long time: scheduling a resumption and terminating for now");
228 $iwp_backup_core->record_still_alive();
229 die;
230 }
231 }
232 }
233 } else {
234 $msg = $e->getMessage();
235 if (preg_match('/Upload with upload_id .* already completed/', $msg)) {
236 $iwp_backup_core->log('Dropbox returned an error, but apparently indicating previous success: '.$msg);
237 } else {
238 $iwp_backup_core->log('Dropbox error: '.$msg);
239 $iwp_backup_core->log('Dropbox '.sprintf(__('error: failed to upload file to %s (see log file for more)','iwp_backup_core'), $ufile), 'error');
240 $file_success = 0;
241 if (strpos($msg, 'select/poll returned error') !== false && $this->upload_tick > 0 && time() - $this->upload_tick > 800) {
242 $iwp_backup_core->reschedule(60);
243 $iwp_backup_core->log("Select/poll returned after a long time: scheduling a resumption and terminating for now");
244 $iwp_backup_core->record_still_alive();
245 die;
246 }
247 }
248 }
249 }
250 if ($file_success) {
251 $iwp_backup_core->uploaded_file($file);
252 $microtime_elapsed = microtime(true)-$microtime;
253 $speedps = $filesize/$microtime_elapsed;
254 $speed = sprintf("%.2d",$filesize)." KB in ".sprintf("%.2d",$microtime_elapsed)."s (".sprintf("%.2d", $speedps)." KB/s)";
255 $iwp_backup_core->log("Dropbox: File upload success (".$file."): $speed");
256 $iwp_backup_core->jobdata_delete('IWP_duido_'.$hash);
257 $iwp_backup_core->jobdata_delete('IWP_duidi_'.$hash);
258 }
259
260 }
261
262 return null;
263
264 }
265
266 # $match: a substring to require (tested via strpos() !== false)
267 public function listfiles($match = 'backup_') {
268
269 $opts = $this->get_options();
270
271 if (empty($opts['tk_access_token'])) return new WP_Error('no_settings', __('No settings were found', 'InfiniteWP').' (dropbox)');
272
273 global $iwp_backup_core;
274 try {
275 $dropbox = $this->bootstrap();
276 } catch (Exception $e) {
277 $iwp_backup_core->log('Dropbox access error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
278 return new WP_Error('access_error', $e->getMessage());
279 }
280
281 $searchpath = '/'.untrailingslashit(apply_filters('IWP_dropbox_modpath', '', $this));
282 $iwp_backup_core->log('IWP_dropbox_modpath:'.$searchpath);
283 try {
284 /* Some users could have a large amount of backups, the max search is 1000 entries we should continue to search until there are no more entries to bring back. */
285 $start = 0;
286 $matches = array();
287
288 while (true) {
289 $search = $dropbox->search($match, $searchpath, 1000, $start);
290 if (empty($search['code']) || 200 != $search['code']) return new WP_Error('response_error', sprintf(__('%s returned an unexpected HTTP response: %s', 'InfiniteWP'), 'Dropbox', $search['code']), $search['body']);
291
292 if (empty($search['body'])) return array();
293
294 if (isset($search['body']->matches) && is_array($search['body']->matches)) {
295 $matches = array_merge($matches, $search['body']->matches);
296 } elseif (is_array($search['body'])) {
297 $matches = $search['body'];
298 } else {
299 break;
300 }
301
302 if (isset($search['body']->more) && true == $search['body']->more && isset($search['body']->start)) {
303 $start = $search['body']->start;
304 } else {
305 break;
306 }
307 }
308
309 } catch (Exception $e) {
310 $iwp_backup_core->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
311 // The most likely cause of a search_error is specifying a non-existent path, which should just result in an empty result set.
312 // return new WP_Error('search_error', $e->getMessage());
313 return array();
314 }
315
316 $results = array();
317
318 foreach ($matches as $item) {
319
320 $item = $item->metadata;
321 if (!is_object($item)) continue;
322 if (is_object($item->metadata)) $item = $item->metadata;;
323
324 if ((!isset($item->size) || $item->size > 0) && $item->{'.tag'} != 'folder' && !empty($item->path_display) && 0 === strpos($item->path_display, $searchpath)) {
325
326 $path = substr($item->path_display, strlen($searchpath));
327 if ('/' == substr($path, 0, 1)) $path=substr($path, 1);
328
329 # Ones in subfolders are not wanted
330 if (false !== strpos($path, '/')) continue;
331
332 $result = array('name' => $path);
333 if (!empty($item->size)) $result['size'] = $item->size;
334
335 $results[] = $result;
336 }
337 }
338
339 return $results;
340 }
341
342 public function defaults() {
343 return apply_filters('IWP_dropbox_defaults', array('Z3Q3ZmkwbnplNHA0Zzlx', 'bTY0bm9iNmY4eWhjODRt'));
344 }
345
346 public function delete($files, $data = null, $sizeinfo = array()) {
347
348 global $iwp_backup_core;
349 if (is_string($files)) $files=array($files);
350
351 $opts = $this->get_options();
352
353 if (empty($opts['tk_access_token'])) {
354 $iwp_backup_core->log('You do not appear to be authenticated with Dropbox (3)');
355 $iwp_backup_core->log(sprintf(__('You do not appear to be authenticated with %s (whilst deleting)', 'InfiniteWP'), 'Dropbox'), 'warning');
356 return false;
357 }
358
359 try {
360 $dropbox = $this->bootstrap();
361 } catch (Exception $e) {
362 $iwp_backup_core->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
363 $iwp_backup_core->log(sprintf(__('Failed to access %s when deleting (see log file for more)', 'InfiniteWP'), 'Dropbox'), 'warning');
364 return false;
365 }
366 if (false === $dropbox) return false;
367
368 foreach ($files as $file) {
369 $ufile = apply_filters('IWP_dropbox_modpath', $file, $this);
370 $iwp_backup_core->log("Dropbox: request deletion: $ufile");
371
372 try {
373 $dropbox->delete($ufile);
374 $file_success = 1;
375 } catch (Exception $e) {
376 $iwp_backup_core->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
377 }
378
379 if (isset($file_success)) {
380 $iwp_backup_core->log('Dropbox: delete succeeded');
381 } else {
382 return false;
383 }
384 }
385
386 }
387
388 public function download($file) {
389
390 global $iwp_backup_core;
391
392 $opts = $this->get_options();
393
394 if (empty($opts['tk_access_token'])) {
395 $iwp_backup_core->log('You do not appear to be authenticated with Dropbox (4)');
396 $iwp_backup_core->log(sprintf(__('You do not appear to be authenticated with %s','InfiniteWP'), 'Dropbox'), 'error');
397 return false;
398 }
399
400 try {
401 $dropbox = $this->bootstrap();
402 $iwp_backup_core->log('Dropbox download: ');
403 } catch (Exception $e) {
404 $iwp_backup_core->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
405 $iwp_backup_core->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')', 'error');
406 return false;
407 }
408 if (false === $dropbox) return false;
409 $remote_files = $this->listfiles($file);
410
411 foreach ($remote_files as $file_info) {
412 if ($file_info['name'] == $file) {
413 return $iwp_backup_core->chunked_download($file, $this, $file_info['size'], apply_filters('iwp_dropbox_downloads_manually_break_up', true), null, 3*1048576);
414 }
415 }
416
417 $iwp_backup_core->log("$file: file not found in listing of remote directory");
418
419 return false;
420
421 }
422
423 public function chunked_download($file, $headers, $data, $fh) {
424
425 global $iwp_backup_core;
426
427 $opts = $this->get_options();
428 $storage = $this->get_storage();
429
430 $ufile = apply_filters('IWP_dropbox_modpath', $file, $this);
431
432 $options = array();
433
434 if (!empty($headers)) $options['headers'] = $headers;
435
436 try {
437 $get = $storage->download($ufile, $fh, $options);
438 } catch (Exception $e) {
439 $iwp_backup_core->log($e);
440 $iwp_backup_core->log($e->getMessage(), 'error');
441 $get = false;
442 }
443
444 return $get;
445 }
446
447 public function config_print() {
448
449 $opts = $this->get_options();
450 $ownername = empty($opts['ownername']) ? '' : $opts['ownername'];
451 if (!empty($opts['appkey'])) {
452 $appkey = empty($opts['appkey']) ? '' : $opts['appkey'];
453 $secret = empty($opts['secret']) ? '' : $opts['secret'];
454 }
455 }
456
457 public function auth_token() {
458 $this->bootstrap();
459 $opts = $this->get_options();
460 if (!empty($opts['tk_access_token'])) {
461 add_action('all_admin_notices', array($this, 'show_authed_admin_warning') );
462 }
463 }
464
465 // Acquire single-use authorization code
466 public function auth_request() {
467 $this->bootstrap();
468 }
469
470 // This basically reproduces the relevant bits of bootstrap.php from the SDK
471 public function bootstrap($deauthenticate = false) {
472 if (!empty($this->dropbox_object) && !is_wp_error($this->dropbox_object)) return $this->dropbox_object;
473
474 /*
475 Use Old Dropbox API constant is used to force bootstrap to use the old API this is for users having problems. By default we will use the new Dropbox API v2 as the old version will be deprecated as of June 2017
476 */
477 $dropbox_api = 'Dropbox2';
478
479 require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/'.$dropbox_api.'/API.php');
480 require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/'.$dropbox_api.'/Exception.php');
481 require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/'.$dropbox_api.'/OAuth/Consumer/ConsumerAbstract.php');
482 require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/'.$dropbox_api.'/OAuth/Storage/StorageInterface.php');
483 require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/'.$dropbox_api.'/OAuth/Storage/Encrypter.php');
484 require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/'.$dropbox_api.'/OAuth/Storage/WordPress.php');
485 require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/'.$dropbox_api.'/OAuth/Consumer/Curl.php');
486
487 $opts = $this->get_options();
488
489 $key = empty($opts['secret']) ? '' : $opts['secret'];
490 $sec = empty($opts['appkey']) ? '' : $opts['appkey'];
491
492 $oauth2_id = $key;
493 $old_auth = false;
494 if ( empty($opts['email']) ) {
495 $old_auth = true;
496 }
497
498
499 // Instantiate the Encrypter and storage objects
500 $encrypter = new Dropbox_Encrypter('ThisOneDoesNotMatterBeyondLength');
501
502 // Instantiate the storage
503 $dropbox_storage = new Dropbox_WordPress($encrypter, "tk_", 'IWP_dropbox', $this);
504
505 // WordPress consumer does not yet work
506 // $OAuth = new Dropbox_ConsumerWordPress($sec, $key, $storage, $callback);
507
508 // Get the DropBox API access details
509 list($d2, $d1) = $this->defaults();
510 if (empty($sec)) { $sec = base64_decode($d1); }; if (empty($key)) { $key = base64_decode($d2); }
511 $root = 'sandbox';
512 if ('dropbox:' == substr($sec, 0, 8)) {
513 $sec = substr($sec, 8);
514 $root = 'dropbox';
515 }
516
517 try {
518 $OAuth = new Dropbox_Curl($sec, $oauth2_id, $key, $dropbox_storage, null, null, $deauthenticate, null,$old_auth);
519 } catch (Exception $e) {
520 global $iwp_backup_core;
521 $iwp_backup_core->log("Dropbox Curl error: ".$e->getMessage());
522 $iwp_backup_core->log(sprintf(__("%s error: %s", 'iwp_backup_core'), "Dropbox/Curl", $e->getMessage().' ('.get_class($e).') (line: '.$e->getLine().', file: '.$e->getFile()).')', 'error');
523 return false;
524 }
525
526 if ($deauthenticate) return true;
527
528 if ( empty($opts['email']) ) {
529 $dropbox_storage->set($opts['tk_access_token'], 'access_token');
530 }
531
532 $storage = new IWP_MMB_Dropbox_API($OAuth, $root);
533
534 $this->set_storage($storage);
535
536 return $storage;
537 }
538
539 }
540