PluginProbe
UpdraftPlus: WP Backup & Migration Plugin / 1.13.9
UpdraftPlus: WP Backup & Migration Plugin v1.13.9
1.26.7 1.26.6 1.26.5 1.26.4 1.26.3 1.9.19 1.9.25 1.9.26 1.9.30 1.9.31 1.9.32 1.9.4 1.9.40 1.9.41 1.9.42 1.9.43 1.9.44 1.9.45 1.9.46 1.9.5 1.9.50 1.9.51 1.9.60 1.9.62 1.9.63 All 371 releases
updraftplus / methods / dropbox.php

dropbox.php in UpdraftPlus: WP Backup & Migration Plugin 1.13.9, at methods/dropbox.php

794 lines 32.4 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 */
5
6 if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
7
8 // Converted to multi-options (Feb 2017-) and previous options conversion removed: Yes
9
10 if (!class_exists('UpdraftPlus_BackupModule')) require_once(UPDRAFTPLUS_DIR.'/methods/backup-module.php');
11
12 // 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)
13 // Can be removed after a few months
14 $potential_options = UpdraftPlus_Options::get_updraft_option('updraft_dropbox');
15 if (is_array($potential_options) && isset($potential_options['version']) && isset($potential_options['settings']) && array() === $potential_options['settings']) {
16 // Wipe it, which wil force its re-creation in proper format
17 UpdraftPlus_Options::delete_updraft_option('updraft_dropbox');
18 }
19
20
21 class UpdraftPlus_BackupModule_dropbox extends UpdraftPlus_BackupModule {
22
23 private $current_file_hash;
24
25 private $current_file_size;
26
27 private $dropbox_object;
28
29 private $uploaded_offset;
30
31 private $upload_tick;
32
33 /**
34 * This callback is called as upload progress is made
35 *
36 * @param Integer $offset - the byte offset
37 * @param String $uploadid - identifier for the upload in progress
38 * @param Boolean|String $fullpath - optional full path to the file being uploaded
39 */
40 public function chunked_callback($offset, $uploadid, $fullpath = false) {
41
42 global $updraftplus;
43
44 // Update upload ID
45 $this->jobdata_set('upload_id_'.$this->current_file_hash, $uploadid);
46 $this->jobdata_set('upload_offset_'.$this->current_file_hash, $offset);
47
48 $time_now = microtime(true);
49
50 $time_since_last_tick = $time_now - $this->upload_tick;
51 $data_since_last_tick = $offset - $this->uploaded_offset;
52
53 $this->upload_tick = $time_now;
54 $this->uploaded_offset = $offset;
55
56 // Here we use job-wide data, because we don't expect wildly different performance for different Dropbox accounts
57 $chunk_size = $updraftplus->jobdata_get('dropbox_chunk_size', 1048576);
58 // Don't go beyond 10MB, or change the chunk size after the last segment
59 if ($chunk_size < 10485760 && $this->current_file_size > 0 && $offset < $this->current_file_size) {
60 $job_run_time = $time_now - $updraftplus->job_time_ms;
61 if ($time_since_last_tick < 10) {
62 $upload_rate = $data_since_last_tick / max($time_since_last_tick, 1);
63 $upload_secs = min(floor($job_run_time), 10);
64 if ($job_run_time < 15) $upload_secs = max(6, $job_run_time*0.6);
65 $new_chunk = max(min($upload_secs * $upload_rate * 0.9, 10485760), 1048576);
66 $new_chunk = $new_chunk - ($new_chunk % 524288);
67 $chunk_size = (int) $new_chunk;
68 $this->dropbox_object->setChunkSize($chunk_size);
69 $updraftplus->jobdata_set('dropbox_chunk_size', $chunk_size);
70 }
71 }
72
73 if ($this->current_file_size > 0) {
74 $percent = round(100*($offset/$this->current_file_size), 1);
75 $updraftplus->record_uploaded_chunk($percent, "$uploadid, $offset, ".round($chunk_size/1024, 1)." KB", $fullpath);
76 } else {
77 $updraftplus->log("Dropbox: Chunked Upload: $offset bytes uploaded");
78 // This act is done by record_uploaded_chunk, and helps prevent overlapping runs
79 if ($fullpath) touch($fullpath);
80 }
81 }
82
83 public function get_supported_features() {
84 // This options format is handled via only accessing options via $this->get_options()
85 return array('multi_options');
86 }
87
88 public function get_default_options() {
89 return array(
90 'appkey' => '',
91 'secret' => '',
92 'folder' => '',
93 'tk_access_token' => '',
94 );
95 }
96
97 public function backup($backup_array) {
98
99 global $updraftplus;
100
101 $opts = $this->get_options();
102
103 if (empty($opts['tk_access_token'])) {
104 $updraftplus->log('You do not appear to be authenticated with Dropbox (1)');
105 $updraftplus->log(__('You do not appear to be authenticated with Dropbox', 'updraftplus'), 'error');
106 return false;
107 }
108
109 // 28 June 2017
110 $use_api_ver = (defined('UPDRAFTPLUS_DROPBOX_API_V1') && UPDRAFTPLUS_DROPBOX_API_V1 && time() < 1498608000) ? 1 : 2;
111
112 if (empty($opts['tk_request_token'])) {
113 $updraftplus->log("Dropbox: begin cloud upload (using API version $use_api_ver with OAuth v2 token)");
114 } else {
115 $updraftplus->log("Dropbox: begin cloud upload (using API version $use_api_ver with OAuth v1 token)");
116 }
117
118 $chunk_size = $updraftplus->jobdata_get('dropbox_chunk_size', 1048576);
119
120 try {
121 $dropbox = $this->bootstrap();
122 if (false === $dropbox) throw new Exception(__('You do not appear to be authenticated with Dropbox', 'updraftplus'));
123 $updraftplus->log("Dropbox: access gained; setting chunk size to: ".round($chunk_size/1024, 1)." KB");
124 $dropbox->setChunkSize($chunk_size);
125 } catch (Exception $e) {
126 $updraftplus->log('Dropbox error when trying to gain access: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
127 $updraftplus->log(sprintf(__('Dropbox error: %s (see log file for more)', 'updraftplus'), $e->getMessage()), 'error');
128 return false;
129 }
130
131 $updraft_dir = $updraftplus->backups_dir_location();
132 $dropbox_folder = trailingslashit($opts['folder']);
133
134 foreach ($backup_array as $file) {
135
136 $available_quota = -1;
137
138 // If we experience any failures collecting account info, then carry on anyway
139 try {
140
141 /*
142 Quota information is no longer provided with account information a new call to quotaInfo must be made to get this information.
143 */
144 if (1 == $use_api_ver) {
145 $quota_info = $dropbox->accountInfo();
146 } else {
147 $quota_info = $dropbox->quotaInfo();
148 }
149
150 if ("200" != $quota_info['code']) {
151 $message = "Dropbox account/info did not return HTTP 200; returned: ". $quota_info['code'];
152 } elseif (!isset($quota_info['body'])) {
153 $message = "Dropbox account/info did not return the expected data";
154 } else {
155 $body = $quota_info['body'];
156 if (isset($body->quota_info)) {
157 $quota_info = $body->quota_info;
158 $total_quota = $quota_info->quota;
159 $normal_quota = $quota_info->normal;
160 $shared_quota = $quota_info->shared;
161 $available_quota = $total_quota - ($normal_quota + $shared_quota);
162 $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";
163 } else {
164 $total_quota = max($body->allocation->allocated, 1);
165 $used = $body->used;
166 /* check here to see if the account is a team account and if so use the other used value
167 This will give us their total usage including their individual account and team account */
168 if (isset($body->allocation->used)) $used = $body->allocation->used;
169 $available_quota = $total_quota - $used;
170 $message = "Dropbox quota usage: used=".round($used/1048576, 1)." MB, total=".round($total_quota/1048576, 1)." MB, available=".round($available_quota/1048576, 1)." MB";
171 }
172 }
173 $updraftplus->log($message);
174 } catch (Exception $e) {
175 $updraftplus->log("Dropbox error: exception (".get_class($e).") occurred whilst getting account info: ".$e->getMessage());
176 // $updraftplus->log(sprintf(__("%s error: %s", 'updraftplus'), 'Dropbox', $e->getMessage()).' ('.$e->getCode().')', 'warning', md5($e->getMessage()));
177 }
178
179 $file_success = 1;
180
181 $hash = md5($file);
182 $this->current_file_hash = $hash;
183
184 $filesize = filesize($updraft_dir.'/'.$file);
185 $this->current_file_size = $filesize;
186
187 // Into KB
188 $filesize = $filesize/1024;
189 $microtime = microtime(true);
190
191 if ($upload_id = $this->jobdata_get('upload_id_'.$hash, null, 'updraf_dbid_'.$hash)) {
192 // Resume
193 $offset = $this->jobdata_get('upload_offset_'.$hash, null, 'updraf_dbof_'.$hash);
194 $updraftplus->log("This is a resumption: $offset bytes had already been uploaded");
195 } else {
196 $offset = 0;
197 $upload_id = null;
198 }
199
200 // We don't actually abort now - there's no harm in letting it try and then fail
201 if (-1 != $available_quota && $available_quota < ($filesize-$offset)) {
202 $updraftplus->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");
203 // $updraftplus->log(sprintf(__("Account full: your %s account has only %d bytes left, but the file to be uploaded has %d bytes remaining (total size: %d bytes)",'updraftplus'),'Dropbox', $available_quota, $filesize-$offset, $filesize), 'warning');
204 }
205
206 // Old-style, single file put: $put = $dropbox->putFile($updraft_dir.'/'.$file, $dropbox_folder.$file);
207
208 $ufile = apply_filters('updraftplus_dropbox_modpath', $file, $this);
209
210 $updraftplus->log("Dropbox: Attempt to upload: $file to: $ufile");
211
212 $this->upload_tick = microtime(true);
213 $this->uploaded_offset = $offset;
214
215 try {
216 $response = $dropbox->chunkedUpload($updraft_dir.'/'.$file, '', $ufile, true, $offset, $upload_id, array($this, 'chunked_callback'));
217 if (empty($response['code']) || "200" != $response['code']) {
218 $updraftplus->log('Unexpected HTTP code returned from Dropbox: '.$response['code']." (".serialize($response).")");
219 if ($response['code'] >= 400) {
220 $updraftplus->log('Dropbox '.sprintf(__('error: failed to upload file to %s (see log file for more)', 'updraftplus'), $file), 'error');
221 } else {
222 $updraftplus->log(sprintf(__('%s did not return the expected response - check your log file for more details', 'updraftplus'), 'Dropbox'), 'warning');
223 }
224 }
225 } catch (Exception $e) {
226 $updraftplus->log("Dropbox chunked upload exception (".get_class($e)."): ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
227 if (preg_match("/Submitted input out of alignment: got \[(\d+)\] expected \[(\d+)\]/i", $e->getMessage(), $matches)) {
228 // Try the indicated offset
229 $we_tried = $matches[1];
230 $dropbox_wanted = (int) $matches[2];
231 $updraftplus->log("Dropbox not yet aligned: tried=$we_tried, wanted=$dropbox_wanted; will attempt recovery");
232 $this->uploaded_offset = $dropbox_wanted;
233 try {
234 $dropbox->chunkedUpload($updraft_dir.'/'.$file, '', $ufile, true, $dropbox_wanted, $upload_id, array($this, 'chunked_callback'));
235 } catch (Exception $e) {
236 $msg = $e->getMessage();
237 if (preg_match('/Upload with upload_id .* already completed/', $msg)) {
238 $updraftplus->log('Dropbox returned an error, but apparently indicating previous success: '.$msg);
239 } else {
240 $updraftplus->log('Dropbox error: '.$msg.' (line: '.$e->getLine().', file: '.$e->getFile().')');
241 $updraftplus->log('Dropbox '.sprintf(__('error: failed to upload file to %s (see log file for more)', 'updraftplus'), $ufile), 'error');
242 $file_success = 0;
243 if (strpos($msg, 'select/poll returned error') !== false && $this->upload_tick > 0 && time() - $this->upload_tick > 800) {
244 $updraftplus->reschedule(60);
245 $updraftplus->log("Select/poll returned after a long time: scheduling a resumption and terminating for now");
246 $updraftplus->record_still_alive();
247 die;
248 }
249 }
250 }
251 } else {
252 $msg = $e->getMessage();
253 if (preg_match('/Upload with upload_id .* already completed/', $msg)) {
254 $updraftplus->log('Dropbox returned an error, but apparently indicating previous success: '.$msg);
255 } else {
256 $updraftplus->log('Dropbox error: '.$msg);
257 $updraftplus->log('Dropbox '.sprintf(__('error: failed to upload file to %s (see log file for more)', 'updraftplus'), $ufile), 'error');
258 $file_success = 0;
259 if (strpos($msg, 'select/poll returned error') !== false && $this->upload_tick > 0 && time() - $this->upload_tick > 800) {
260 $updraftplus->reschedule(60);
261 $updraftplus->log("Select/poll returned after a long time: scheduling a resumption and terminating for now");
262 $updraftplus->record_still_alive();
263 die;
264 }
265 }
266 }
267 }
268 if ($file_success) {
269 $updraftplus->uploaded_file($file);
270 $microtime_elapsed = microtime(true)-$microtime;
271 $speedps = ($microtime_elapsed > 0) ? $filesize/$microtime_elapsed : 0;
272 $speed = sprintf("%.2d", $filesize)." KB in ".sprintf("%.2d", $microtime_elapsed)."s (".sprintf("%.2d", $speedps)." KB/s)";
273 $updraftplus->log("Dropbox: File upload success (".$file."): $speed");
274 $this->jobdata_delete('upload_id_'.$hash, 'updraf_dbid_'.$hash);
275 $this->jobdata_delete('upload_offset_'.$hash, 'updraf_dbof_'.$hash);
276 }
277
278 }
279
280 return null;
281
282 }
283
284 /**
285 * This method gets a list of files from the remote stoage that match the string passed in and returns an array of backups
286 *
287 * @param string $match a substring to require (tested via strpos() !== false)
288 * @return array
289 */
290 public function listfiles($match = 'backup_') {
291
292 $opts = $this->get_options();
293
294 if (empty($opts['tk_access_token'])) return new WP_Error('no_settings', __('No settings were found', 'updraftplus').' (dropbox)');
295
296 global $updraftplus;
297 try {
298 $dropbox = $this->bootstrap();
299 } catch (Exception $e) {
300 $updraftplus->log('Dropbox access error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
301 return new WP_Error('access_error', $e->getMessage());
302 }
303
304 $searchpath = '/'.untrailingslashit(apply_filters('updraftplus_dropbox_modpath', '', $this));
305
306 try {
307 /* 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. */
308 $start = 0;
309 $matches = array();
310
311 while (true) {
312 $search = $dropbox->search($match, $searchpath, 1000, $start);
313 if (empty($search['code']) || 200 != $search['code']) return new WP_Error('response_error', sprintf(__('%s returned an unexpected HTTP response: %s', 'updraftplus'), 'Dropbox', $search['code']), $search['body']);
314
315 if (empty($search['body'])) return array();
316
317 if (isset($search['body']->matches) && is_array($search['body']->matches)) {
318 $matches = array_merge($matches, $search['body']->matches);
319 } elseif (is_array($search['body'])) {
320 $matches = $search['body'];
321 } else {
322 break;
323 }
324
325 if (isset($search['body']->more) && true == $search['body']->more && isset($search['body']->start)) {
326 $start = $search['body']->start;
327 } else {
328 break;
329 }
330 }
331
332 } catch (Exception $e) {
333 $updraftplus->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
334 // The most likely cause of a search_error is specifying a non-existent path, which should just result in an empty result set.
335 // return new WP_Error('search_error', $e->getMessage());
336 return array();
337 }
338
339 $results = array();
340
341 foreach ($matches as $item) {
342 // 28 June 2017 - https://blogs.dropbox.com/developers/2016/06/api-v1-deprecated/
343 if (defined('UPDRAFTPLUS_DROPBOX_API_V1') && UPDRAFTPLUS_DROPBOX_API_V1 && time() < 1498608000) {
344 if (!is_object($item)) continue;
345
346 if ((!isset($item->bytes) || $item->bytes > 0) && empty($item->is_dir) && !empty($item->path) && 0 === strpos($item->path, $searchpath)) {
347
348 $path = substr($item->path, strlen($searchpath));
349 if ('/' == substr($path, 0, 1)) $path = substr($path, 1);
350
351 // Ones in subfolders are not wanted
352 if (false !== strpos($path, '/')) continue;
353
354 $result = array('name' => $path);
355 if (!empty($item->bytes)) $result['size'] = $item->bytes;
356
357 $results[] = $result;
358
359 }
360 } else {
361 $item = $item->metadata;
362 if (!is_object($item)) continue;
363
364 if ((!isset($item->size) || $item->size > 0) && 'folder' != $item->{'.tag'} && !empty($item->path_display) && 0 === strpos($item->path_display, $searchpath)) {
365
366 $path = substr($item->path_display, strlen($searchpath));
367 if ('/' == substr($path, 0, 1)) $path = substr($path, 1);
368
369 // Ones in subfolders are not wanted
370 if (false !== strpos($path, '/')) continue;
371
372 $result = array('name' => $path);
373 if (!empty($item->size)) $result['size'] = $item->size;
374
375 $results[] = $result;
376 }
377 }
378
379 }
380
381 return $results;
382 }
383
384 public function defaults() {
385 return apply_filters('updraftplus_dropbox_defaults', array('Z3Q3ZmkwbnplNHA0Zzlx', 'bTY0bm9iNmY4eWhjODRt'));
386 }
387
388 public function delete($files, $data = null, $sizeinfo = array()) {
389
390 global $updraftplus;
391 if (is_string($files)) $files = array($files);
392
393 $opts = $this->get_options();
394
395 if (empty($opts['tk_access_token'])) {
396 $updraftplus->log('You do not appear to be authenticated with Dropbox (3)');
397 $updraftplus->log(sprintf(__('You do not appear to be authenticated with %s (whilst deleting)', 'updraftplus'), 'Dropbox'), 'warning');
398 return false;
399 }
400
401 try {
402 $dropbox = $this->bootstrap();
403 } catch (Exception $e) {
404 $updraftplus->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
405 $updraftplus->log(sprintf(__('Failed to access %s when deleting (see log file for more)', 'updraftplus'), 'Dropbox'), 'warning');
406 return false;
407 }
408 if (false === $dropbox) return false;
409
410 foreach ($files as $file) {
411 $ufile = apply_filters('updraftplus_dropbox_modpath', $file, $this);
412 $updraftplus->log("Dropbox: request deletion: $ufile");
413
414 try {
415 $dropbox->delete($ufile);
416 $file_success = 1;
417 } catch (Exception $e) {
418 $updraftplus->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
419 }
420
421 if (isset($file_success)) {
422 $updraftplus->log('Dropbox: delete succeeded');
423 } else {
424 return false;
425 }
426 }
427
428 }
429
430 public function download($file) {
431
432 global $updraftplus;
433
434 $opts = $this->get_options();
435
436 if (empty($opts['tk_access_token'])) {
437 $updraftplus->log('You do not appear to be authenticated with Dropbox (4)');
438 $updraftplus->log(sprintf(__('You do not appear to be authenticated with %s', 'updraftplus'), 'Dropbox'), 'error');
439 return false;
440 }
441
442 try {
443 $dropbox = $this->bootstrap();
444 } catch (Exception $e) {
445 $updraftplus->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
446 $updraftplus->log('Dropbox error: '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')', 'error');
447 return false;
448 }
449 if (false === $dropbox) return false;
450
451 $updraft_dir = $updraftplus->backups_dir_location();
452 $microtime = microtime(true);
453
454 $try_the_other_one = false;
455
456 $ufile = apply_filters('updraftplus_dropbox_modpath', $file, $this);
457
458 try {
459 $get = $dropbox->getFile($ufile, $updraft_dir.'/'.$file, null, true);
460 } catch (Exception $e) {
461 // TODO: Remove this October 2013 (we stored in the wrong place for a while...)
462 $try_the_other_one = true;
463 $possible_error = $e->getMessage();
464 $updraftplus->log('Dropbox error: '.$e);
465 $get = false;
466 }
467
468 // TODO: Remove this October 2013 (we stored files in the wrong place for a while...)
469 if ($try_the_other_one) {
470 $dropbox_folder = trailingslashit($opts['folder']);
471 try {
472 $get = $dropbox->getFile($dropbox_folder.'/'.$file, $updraft_dir.'/'.$file, null, true);
473 if (isset($get['response']['body'])) {
474 $updraftplus->log("Dropbox: downloaded ".round(strlen($get['response']['body'])/1024, 1).' KB');
475 }
476 } catch (Exception $e) {
477 $updraftplus->log($possible_error, 'error');
478 $updraftplus->log($e->getMessage(), 'error');
479 $get = false;
480 }
481 }
482
483 return $get;
484
485 }
486
487 public function config_print() {
488
489 $opts = $this->get_options();
490
491 $classes = $this->get_css_classes();
492 ?>
493 <tr class="<?php echo $classes;?>">
494 <td></td>
495 <td>
496 <img alt="<?php _e(sprintf(__('%s logo', 'updraftplus'), 'Dropbox')); ?>" src="<?php echo UPDRAFTPLUS_URL.'/images/dropbox-logo.png'; ?>">
497 <p><em><?php printf(__('%s is a great choice, because UpdraftPlus supports chunked uploads - no matter how big your site is, UpdraftPlus can upload it a little at a time, and not get thwarted by timeouts.', 'updraftplus'), 'Dropbox');?></em></p>
498 </td>
499 </tr>
500
501 <tr class="<?php echo $classes;?>">
502 <th></th>
503 <td>
504 <?php
505 global $updraftplus_admin;
506 $updraftplus_admin->curl_check('Dropbox', false, 'dropbox');
507 ?>
508 </td>
509 </tr>
510
511 <?php
512
513 $defmsg = '<tr class="'.$classes.'"><td></td><td><strong>'.__('Need to use sub-folders?', 'updraftplus').'</strong> '.__('Backups are saved in', 'updraftplus').' apps/UpdraftPlus. '.__('If you back up several sites into the same Dropbox and want to organise with sub-folders, then ', 'updraftplus').'<a href="https://updraftplus.com/shop/">'.__("there's an add-on for that.", 'updraftplus').'</a></td></tr>';
514
515 $defmsg = '<tr class="'.$classes.'"><td></td><td><strong>'.__('Need to use sub-folders?', 'updraftplus').'</strong> '.__('Backups are saved in', 'updraftplus').' apps/UpdraftPlus. '.__('If you back up several sites into the same Dropbox and want to organise with sub-folders, then ', 'updraftplus').'<a href="'.apply_filters("updraftplus_com_link", "https://updraftplus.com/shop/").'">'.__("there's an add-on for that.", 'updraftplus').'</a></td></tr>';
516
517 $extra_config = apply_filters('updraftplus_dropbox_extra_config', $defmsg, $this);
518
519 echo $extra_config;
520 ?>
521
522 <tr class="<?php echo $classes;?>">
523 <th><?php echo sprintf(__('Authenticate with %s', 'updraftplus'), __('Dropbox', 'updraftplus'));?>:</th>
524 <td><p>
525 <?php
526 $rt = (empty($opts['tk_access_token'])) ? '' : $opts['tk_access_token'];
527 if (!empty($rt)) {
528 echo "<p><strong>".__('(You appear to be already authenticated).', 'updraftplus')."</strong>";
529 echo ' <a class="updraft_deauthlink" href="';
530 echo UpdraftPlus_Options::admin_page_url();
531 echo '?page=updraftplus&action=updraftmethod-dropbox-auth&updraftplus_dropboxauth=deauth&nonce='.wp_create_nonce('dropbox_deauth_nonce').'">';
532 echo sprintf(__('Follow this link to deauthenticate with %s.', 'updraftplus'), __('Dropbox', 'updraftplus'));
533 echo '</a></p>';
534 }
535 echo '<p><a class="updraft_authlink" href="';
536 echo UpdraftPlus_Options::admin_page_url();
537 echo '?page=updraftplus&action=updraftmethod-dropbox-auth&updraftplus_dropboxauth=doit">';
538 echo sprintf(__('<strong>After</strong> you have saved your settings (by clicking \'Save Changes\' below), then come back here once and click this link to complete authentication with %s.', 'updraftplus'), __('Dropbox', 'updraftplus'));
539 echo '</a></p>';
540 ?>
541 </p>
542 <?php
543 if (!empty($rt)) {
544 $ownername = empty($opts['ownername']) ? '' : $opts['ownername'];
545 if (!empty($ownername)) {
546 echo '<br>'.sprintf(__("Account holder's name: %s.", 'updraftplus'), htmlspecialchars($opts['ownername'])).' ';
547 }
548 }
549 ?>
550 </td>
551 </tr>
552
553 <?php
554 // Legacy: only show this next setting to old users who had a setting stored
555 if (!empty($opts['appkey']) || (defined('UPDRAFTPLUS_CUSTOM_DROPBOX_APP') && UPDRAFTPLUS_CUSTOM_DROPBOX_APP)) {
556
557 $appkey = empty($opts['appkey']) ? '' : $opts['appkey'];
558 $secret = empty($opts['secret']) ? '' : $opts['secret'];
559 ?>
560 <tr class="<?php echo $classes;?>">
561 <th></th>
562 <td>
563 <?php echo '<p>'.htmlspecialchars(__('You must add the following as the authorised redirect URI in your Dropbox console (under "API Settings") when asked', 'updraftplus')).': <kbd>'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&action=updraftmethod-dropbox-auth</kbd></p>'; ?>
564 </td>
565 </tr>
566 <tr class="<?php echo $classes;?>">
567 <th>Your Dropbox App Key:</th>
568 <td><input type="text" autocomplete="off" style="width:332px" <?php $this->output_settings_field_name_and_id('appkey');?> value="<?php echo esc_attr($appkey); ?>" /></td>
569 </tr>
570 <tr class="<?php echo $classes;?>">
571 <th>Your Dropbox App Secret:</th>
572 <td><input type="text" style="width:332px" <?php $this->output_settings_field_name_and_id('secret');?> value="<?php echo esc_attr($secret); ?>" /></td>
573 </tr>
574
575 <?php } elseif (false === strpos($extra_config, '<input')) {
576 // We need to make sure that it is not the case that the module has no settings whatsoever - this can result in the module being effectively invisible.
577 ?>
578 <input type="hidden" <?php $this->output_settings_field_name_and_id('tk_access_token');?> value="0">
579 <?php } ?>
580 <?php
581 }
582
583 public function action_auth() {
584 if (isset($_GET['updraftplus_dropboxauth'])) {
585 // Clear out the existing credentials
586 if ('doit' == $_GET['updraftplus_dropboxauth']) {
587 $opts = $this->get_options();
588 $opts['tk_access_token'] = '';
589 unset($opts['tk_request_token']);
590 $opts['ownername'] = '';
591 $this->set_options($opts, true);
592 } elseif ('deauth' == $_GET['updraftplus_dropboxauth'] && isset($_GET['nonce']) && wp_verify_nonce($_GET['nonce'], 'dropbox_deauth_nonce')) {
593
594 try {
595 $this->bootstrap(true);
596 } catch (Exception $e) {
597 global $updraftplus;
598 $updraftplus->log(sprintf(__("%s error: %s", 'updraftplus'), sprintf(__("%s de-authentication", 'updraftplus'), 'Dropbox'), $e->getMessage()), 'error');
599 }
600
601 return;
602
603 }
604 } elseif (isset($_GET['state'])) {
605 // Get the CSRF from setting and check it matches the one returned if it does no CSRF attack has happened
606 $opts = $this->get_options();
607 $csrf = $opts['CSRF'];
608 $state = stripslashes($_GET['state']);
609 if (strcmp($csrf, $state) == 0) {
610 $opts['CSRF'] = '';
611 if (isset($_GET['code'])) {
612 // set code so it can be accessed in the next authentication step
613 $opts['code'] = stripslashes($_GET['code']);
614 $this->set_options($opts, true);
615 $this->auth_token();
616 }
617 } else {
618 error_log("UpdraftPlus: CSRF comparison failure: $csrf != $state");
619 }
620 }
621 try {
622 $this->auth_request();
623 } catch (Exception $e) {
624 global $updraftplus;
625 $updraftplus->log(sprintf(__("%s error: %s", 'updraftplus'), sprintf(__("%s authentication", 'updraftplus'), 'Dropbox'), $e->getMessage()), 'error');
626 }
627 }
628
629 public function show_authed_admin_warning() {
630 global $updraftplus_admin, $updraftplus;
631
632 $dropbox = $this->bootstrap();
633 if (false === $dropbox) return false;
634
635 try {
636 $account_info = $dropbox->accountInfo();
637 } catch (Exception $e) {
638 $accountinfo_err = sprintf(__("%s error: %s", 'updraftplus'), 'Dropbox', $e->getMessage()).' ('.$e->getCode().')';
639 }
640
641 $message = "<strong>".__('Success:', 'updraftplus').'</strong> '.sprintf(__('you have authenticated your %s account', 'updraftplus'), 'Dropbox');
642 // We log, because otherwise people get confused by the most recent log message of 'Parameter not found: oauth_token' and raise support requests
643 $updraftplus->log(__('Success:', 'updraftplus').' '.sprintf(__('you have authenticated your %s account', 'updraftplus'), 'Dropbox'));
644
645 if (empty($account_info['code']) || "200" != $account_info['code']) {
646 $message .= " (".__('though part of the returned information was not as expected - your mileage may vary', 'updraftplus').") ". $account_info['code'];
647 if (!empty($accountinfo_err)) $message .= "<br>".htmlspecialchars($accountinfo_err);
648 } else {
649 $body = $account_info['body'];
650 $name = '';
651 if (isset($body->display_name)) {
652 $name = $body->display_name;
653 } else {
654 $name = $body->name->display_name;
655 }
656 $message .= ". <br>".sprintf(__('Your %s account name: %s', 'updraftplus'), 'Dropbox', htmlspecialchars($name));
657 $opts = $this->get_options();
658 $opts['ownername'] = $name;
659 $this->set_options($opts, true);
660
661 try {
662 /**
663 * Quota information is no longer provided with account information a new call to qoutaInfo must be made to get this information.
664 * 28 June 2017 - https://blogs.dropbox.com/developers/2016/06/api-v1-deprecated/
665 */
666 if (defined('UPDRAFTPLUS_DROPBOX_API_V1') && UPDRAFTPLUS_DROPBOX_API_V1 && time() < 1498608000) {
667 $quota_info = $account_info;
668 } else {
669 $quota_info = $dropbox->quotaInfo();
670 }
671
672 if (empty($quota_info['code']) || "200" != $quota_info['code']) {
673 $message .= " (".__('though part of the returned information was not as expected - your mileage may vary', 'updraftplus').")". $quota_info['code'];
674 if (!empty($accountinfo_err)) $message .= "<br>".htmlspecialchars($accountinfo_err);
675 } else {
676 $body = $quota_info['body'];
677 if (isset($body->quota_info)) {
678 $quota_info = $body->quota_info;
679 $total_quota = max($quota_info->quota, 1);
680 $normal_quota = $quota_info->normal;
681 $shared_quota = $quota_info->shared;
682 $available_quota =$total_quota - ($normal_quota + $shared_quota);
683 $used_perc = round(($normal_quota + $shared_quota)*100/$total_quota, 1);
684 $message .= ' <br>'.sprintf(__('Your %s quota usage: %s %% used, %s available', 'updraftplus'), 'Dropbox', $used_perc, round($available_quota/1048576, 1).' MB');
685 } else {
686 $total_quota = max($body->allocation->allocated, 1);
687 $used = $body->used;
688 /* check here to see if the account is a team account and if so use the other used value
689 This will give us their total usage including their individual account and team account */
690 if (isset($body->allocation->used)) $used = $body->allocation->used;
691 $available_quota =$total_quota - $used;
692 $used_perc = round($used*100/$total_quota, 1);
693 $message .= ' <br>'.sprintf(__('Your %s quota usage: %s %% used, %s available', 'updraftplus'), 'Dropbox', $used_perc, round($available_quota/1048576, 1).' MB');
694 }
695 }
696 } catch (Exception $e) {
697 // Catch
698 }
699
700 }
701 $updraftplus_admin->show_admin_warning($message);
702
703 }
704
705 public function auth_token() {
706 $this->bootstrap();
707 $opts = $this->get_options();
708 if (!empty($opts['tk_access_token'])) {
709 add_action('all_admin_notices', array($this, 'show_authed_admin_warning'));
710 }
711 }
712
713 /**
714 * Acquire single-use authorization code
715 */
716 public function auth_request() {
717 $this->bootstrap();
718 }
719
720 /**
721 * This basically reproduces the relevant bits of bootstrap.php from the SDK
722 *
723 * @param boolean $deauthenticate indicates if we should bootstrap for a deauth or auth request
724 * @return object
725 */
726 public function bootstrap($deauthenticate = false) {
727 if (!empty($this->dropbox_object) && !is_wp_error($this->dropbox_object)) return $this->dropbox_object;
728
729 /*
730 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
731 */
732 $dropbox_api = (defined('UPDRAFTPLUS_DROPBOX_API_V1') && UPDRAFTPLUS_DROPBOX_API_V1 && time() < 1498608000) ? 'Dropbox' : 'Dropbox2';
733
734 include_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/API.php');
735 include_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/Exception.php');
736 include_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/OAuth/Consumer/ConsumerAbstract.php');
737 include_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/OAuth/Storage/StorageInterface.php');
738 include_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/OAuth/Storage/Encrypter.php');
739 include_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/OAuth/Storage/WordPress.php');
740 include_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/OAuth/Consumer/Curl.php');
741 // require_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/OAuth/Consumer/WordPress.php');
742
743 $opts = $this->get_options();
744
745 $key = empty($opts['secret']) ? '' : $opts['secret'];
746 $sec = empty($opts['appkey']) ? '' : $opts['appkey'];
747
748 $oauth2_id = base64_decode('aXA3NGR2Zm1sOHFteTA5');
749
750 // Set the callback URL
751 $callbackhome = UpdraftPlus_Options::admin_page_url().'?page=updraftplus&action=updraftmethod-dropbox-auth';
752 $callback = defined('UPDRAFTPLUS_DROPBOX_AUTH_RETURN_URL') ? UPDRAFTPLUS_DROPBOX_AUTH_RETURN_URL : 'https://auth.updraftplus.com/auth/dropbox/';
753
754 // Instantiate the Encrypter and storage objects
755 $encrypter = new Dropbox_Encrypter('ThisOneDoesNotMatterBeyondLength');
756
757 // Instantiate the storage
758 $storage = new Dropbox_WordPress($encrypter, "tk_", 'updraft_dropbox', $this);
759
760 // WordPress consumer does not yet work
761 // $oauth = new Dropbox_ConsumerWordPress($sec, $key, $storage, $callback);
762
763 // Get the DropBox API access details
764 list($d2, $d1) = $this->defaults();
765 if (empty($sec)) {
766 $sec = base64_decode($d1);
767 }
768
769 if (empty($key)) {
770 $key = base64_decode($d2);
771 }
772
773 $root = 'sandbox';
774 if ('dropbox:' == substr($sec, 0, 8)) {
775 $sec = substr($sec, 8);
776 $root = 'dropbox';
777 }
778
779 try {
780 $oauth = new Dropbox_Curl($sec, $oauth2_id, $key, $storage, $callback, $callbackhome, $deauthenticate);
781 } catch (Exception $e) {
782 global $updraftplus;
783 $updraftplus->log("Dropbox Curl error: ".$e->getMessage());
784 $updraftplus->log(sprintf(__("%s error: %s", 'updraftplus'), "Dropbox/Curl", $e->getMessage().' ('.get_class($e).') (line: '.$e->getLine().', file: '.$e->getFile()).')', 'error');
785 return false;
786 }
787
788 if ($deauthenticate) return true;
789
790 $this->dropbox_object = new UpdraftPlus_Dropbox_API($oauth, $root);
791 return $this->dropbox_object;
792 }
793 }
794