PluginProbe
WP Database Backup – Unlimited Database & Files Backup by Backup for WP / 2.1.1
WP Database Backup – Unlimited Database & Files Backup by Backup for WP v2.1.1
7.13 7.12 trunk 1.1 2.1.1 5.9 6.0 6.1 6.10 6.11 6.12 6.12.1 6.2 6.3 6.4 6.5 6.5.1 6.6 6.7 6.8 6.9 7.0 7.0.1 7.1 7.10 All 34 releases
wp-database-backup / includes / admin / Destination / Dropbox / DropboxClient.php

DropboxClient.php in WP Database Backup – Unlimited Database & Files Backup by Backup for WP 2.1.1, at includes/admin/Destination/Dropbox/DropboxClient.php

652 lines 21.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * DropPHP - A simple Dropbox client that works without cURL.
4
5 */
6
7 require_once(plugin_dir_path(__FILE__).'/OAuthSimple.php');
8
9 class DropboxClient {
10
11 const API_URL = "https://api.dropbox.com/1/";
12 const API_CONTENT_URL = "https://api-content.dropbox.com/1/";
13
14 const BUFFER_SIZE = 4096;
15
16 const MAX_UPLOAD_CHUNK_SIZE = 150000000; // 150MB
17
18 const UPLOAD_CHUNK_SIZE = 4000000; // 4MB
19
20 private $appParams;
21 private $consumerToken;
22
23 private $requestToken;
24 private $accessToken;
25
26 private $locale;
27 private $rootPath;
28
29 private $useCurl;
30
31 function __construct ($app_params, $locale = "en"){
32 $this->appParams = $app_params;
33 if(empty($app_params['app_key']))
34 throw new DropboxException("App Key is empty!");
35
36 $this->consumerToken = array('t' => $this->appParams['app_key'], 's' => $this->appParams['app_secret']);
37 $this->locale = $locale;
38 $this->rootPath = empty($app_params['app_full_access']) ? "sandbox" : "dropbox";
39
40 $this->requestToken = null;
41 $this->accessToken = null;
42
43 $this->useCurl = function_exists('curl_init');
44 }
45
46 /**
47 * Sets whether to use cURL if its available or PHP HTTP wrappers otherwise
48 *
49 * @access public
50 * @return boolean Whether to actually use cURL (always false if not installed)
51 */
52 public function SetUseCUrl($use_it)
53 {
54 return ($this->useCurl = $use_it && function_exists('curl_init'));
55 }
56
57 // ##################################################
58 // Authorization
59
60 /**
61 * Step 1 of authentication process. Retrieves a request token or returns a previously retrieved one.
62 *
63 * @access public
64 * @param boolean $get_new_token Optional (default false). Wether to retrieve a new request token.
65 * @return array Request Token array.
66 */
67 public function GetRequestToken($get_new_token=false)
68 {
69 if(!empty($this->requestToken) && !$get_new_token)
70 return $this->requestToken;
71
72 $rt = $this->authCall("oauth/request_token");
73 if(empty($rt) || empty($rt['oauth_token']))
74 throw new DropboxException('Could not get request token!');
75
76 return ($this->requestToken = array('t'=>$rt['oauth_token'], 's'=>$rt['oauth_token_secret']));
77 }
78
79 /**
80 * Step 2. Returns a URL the user must be redirected to in order to connect the app to their Dropbox account
81 *
82 * @access public
83 * @param string $return_url URL users are redirected after authorization
84 * @return string URL
85 */
86 public function BuildAuthorizeUrl($return_url)
87 {
88 $rt = $this->GetRequestToken();
89 if(empty($rt) || empty($rt['t'])) throw new DropboxException('Request Token Invalid ('.print_r($rt,true).').');
90 return "https://www.dropbox.com/1/oauth/authorize?oauth_token=".$rt['t']."&oauth_callback=".urlencode($return_url);
91 }
92
93 /**
94 * Step 3. Acquires an access token. This is the final step of authentication.
95 *
96 * @access public
97 * @param array $request_token Optional. The previously retrieved request token. This parameter can only be skipped if the DropboxClient object has been (de)serialized.
98 * @return array Access Token array.
99 */
100 public function GetAccessToken($request_token = null)
101 {
102 if(!empty($this->accessToken)) return $this->accessToken;
103
104 if(empty($request_token)) $request_token = $this->requestToken;
105 if(empty($request_token)) throw new DropboxException('Request token required!');
106
107 $at = $this->authCall("oauth/access_token", $request_token);
108 if(empty($at))
109 throw new DropboxException(sprintf('Could not get access token! (request token: %s)', $request_token['t']));
110
111 return ($this->accessToken = array('t'=>$at['oauth_token'], 's'=>$at['oauth_token_secret']));
112 }
113
114 /**
115 * Sets a previously retrieved (and stored) access token.
116 *
117 * @access public
118 * @param string|object $token The Access Token
119 * @return none
120 */
121 public function SetAccessToken($token)
122 {
123 if(empty($token['t']) || empty($token['s'])) throw new DropboxException('Passed invalid access token.');
124 $this->accessToken = $token;
125 }
126
127 /**
128 * Checks if an access token has been set.
129 *
130 * @access public
131 * @return boolean Authorized or not
132 */
133 public function IsAuthorized()
134 {
135 if(empty($this->accessToken)) return false;
136 return true;
137 }
138
139
140 // ##################################################
141 // API Functions
142
143
144 /**
145 * Retrieves information about the user's account.
146 *
147 * @access public
148 * @return object Account info object. See https://www.dropbox.com/developers/reference/api#account-info
149 */
150 public function GetAccountInfo()
151 {
152 return $this->apiCall("account/info", "GET");
153 }
154
155
156 /**
157 * Get file list of a dropbox folder.
158 *
159 * @access public
160 * @param string|object $dropbox_path Dropbox path of the folder
161 * @return array An array with metadata of files/folders keyed by paths
162 */
163 public function GetFiles($dropbox_path='', $recursive=false, $include_deleted=false)
164 {
165 if(is_object($dropbox_path) && !empty($dropbox_path->path)) $dropbox_path = $dropbox_path->path;
166 return $this->getFileTree($dropbox_path, $include_deleted, $recursive ? 1000 : 0);
167 }
168
169 /**
170 * Get file or folder metadata
171 *
172 * @access public
173 * @param $dropbox_path string Dropbox path of the file or folder
174 */
175 public function GetMetadata($dropbox_path, $include_deleted=false, $rev=null)
176 {
177 if(is_object($dropbox_path) && !empty($dropbox_path->path)) $dropbox_path = $dropbox_path->path;
178 return $this->apiCall("metadata/$this->rootPath/$dropbox_path", "GET", compact('include_deleted','rev'));
179 }
180
181 /**
182 * Download a file to the webserver
183 *
184 * @access public
185 * @param string|object $dropbox_file Dropbox path or metadata object of the file to download.
186 * @param string $dest_path Local path for destination
187 * @param string $rev Optional. The revision of the file to retrieve. This defaults to the most recent revision.
188 * @param callback $progress_changed_callback Optional. Callback that will be called during download with 2 args: 1. bytes loaded, 2. file size
189 * @return object Dropbox file metadata
190 */
191 public function DownloadFile($dropbox_file, $dest_path='', $rev=null, $progress_changed_callback = null)
192 {
193 if(is_object($dropbox_file) && !empty($dropbox_file->path))
194 $dropbox_file = $dropbox_file->path;
195
196 if(empty($dest_path)) $dest_path = basename($dropbox_file);
197
198 $url = $this->cleanUrl(self::API_CONTENT_URL."/files/$this->rootPath/$dropbox_file")
199 . (!empty($rev) ? ('?'.http_build_query(array('rev' => $rev),'','&')) : '');
200 $context = $this->createRequestContext($url, "GET");
201
202 $fh = @fopen($dest_path, 'wb'); // write binary
203 if($fh === false) {
204 @fclose($rh);
205 throw new DropboxException("Could not create file $dest_path !");
206 }
207
208 if($this->useCurl) {
209 curl_setopt($context, CURLOPT_BINARYTRANSFER, true);
210 curl_setopt($context, CURLOPT_RETURNTRANSFER, true);
211 curl_setopt($context, CURLOPT_FILE, $fh);
212 $response_headers = array();
213 self::execCurlAndClose($context, $response_headers);
214 fclose($fh);
215 $meta = self::getMetaFromHeaders($response_headers);
216 $bytes_loaded = filesize($dest_path);
217 } else {
218 $rh = @fopen($url, 'rb', false, $context); // read binary
219 if($rh === false)
220 throw new DropboxException("HTTP request to $url failed!");
221
222
223 // get file meta from HTTP header
224 $s_meta = stream_get_meta_data($rh);
225 $meta = self::getMetaFromHeaders($s_meta['wrapper_data']);
226 $bytes_loaded = 0;
227 while (!feof($rh)) {
228 if(($s=fwrite($fh, fread($rh, self::BUFFER_SIZE))) === false) {
229 @fclose($rh);
230 @fclose($fh);
231 throw new DropboxException("Writing to file $dest_path failed!'");
232 }
233 $bytes_loaded += $s;
234 if(!empty($progress_changed_callback)) {
235 call_user_func($progress_changed_callback, $bytes_loaded, $meta->bytes);
236 }
237 }
238
239 fclose($rh);
240 fclose($fh);
241 }
242
243 if($meta->bytes != $bytes_loaded)
244 throw new DropboxException("Download size mismatch! (header:{$meta->bytes} vs actual:{$bytes_loaded}");
245
246 return $meta;
247 }
248
249 /**
250 * Upload a file to dropbox
251 *
252 * @access public
253 * @param $src_file string Local file to upload
254 * @param $dropbox_path string Dropbox path for destination
255 * @return object Dropbox file metadata
256 */
257 public function UploadFile($src_file, $dropbox_path='', $overwrite=true, $parent_rev=null)
258 {
259 if(empty($dropbox_path)) $dropbox_path = basename($src_file);
260 elseif(is_object($dropbox_path) && !empty($dropbox_path->path)) $dropbox_path = $dropbox_path->path;
261
262 $file_size = filesize($src_file);
263
264 if($file_size > self::MAX_UPLOAD_CHUNK_SIZE)
265 {
266 $fh = fopen($src_file,'rb');
267 if($fh === false)
268 throw new DropboxException();
269
270 $upload_id = null;
271 $offset = 0;
272
273
274 while(!feof($fh)) {
275 $url = $this->cleanUrl(self::API_CONTENT_URL."/chunked_upload").'?'.http_build_query(compact('upload_id', 'offset'),'','&');
276
277 if($this->useCurl) {
278 $context = $this->createRequestContext($url, "PUT");
279 curl_setopt($context, CURLOPT_BINARYTRANSFER, true);
280 curl_setopt($context, CURLOPT_PUT, 1);
281 curl_setopt($context, CURLOPT_INFILE, $fh);
282 $chunk_size = min(self::UPLOAD_CHUNK_SIZE, $file_size - $offset);
283 $offset += $chunk_size;
284 curl_setopt($context, CURLOPT_INFILESIZE, $chunk_size);
285 $response = json_decode(self::execCurlAndClose($context));
286
287 fseek($fh,$offset);
288 if($offset >= $file_size)
289 break;
290 } else {
291 $content = fread($fh, self::UPLOAD_CHUNK_SIZE);
292
293 $context = $this->createRequestContext($url, "PUT", $content);
294 $offset += strlen($content);
295 unset($content);
296
297 $response = json_decode(file_get_contents($url, false, $context));
298 }
299 unset($context);
300
301 if(empty($upload_id)) {
302 $upload_id = $response->upload_id;
303 if(empty($upload_id)) throw new DropboxException("Upload ID empty!");
304 }
305 }
306
307 @fclose($fh);
308
309 $this->useCurl = $prev_useCurl;
310
311 return $this->apiCall("commit_chunked_upload/$this->rootPath/$dropbox_path", "POST", compact('overwrite','parent_rev','upload_id'), true);
312 }
313
314 $query = http_build_query(array_merge(compact('overwrite', 'parent_rev'), array('locale' => $this->locale)),'','&');
315 $url = $this->cleanUrl(self::API_CONTENT_URL."/files_put/$this->rootPath/$dropbox_path")."?$query";
316
317 if($this->useCurl) {
318 $context = $this->createRequestContext($url, "PUT");
319 curl_setopt($context, CURLOPT_BINARYTRANSFER, true);
320 $fh = fopen($src_file, 'rb');
321 curl_setopt($context, CURLOPT_PUT, 1);
322 curl_setopt($context, CURLOPT_INFILE, $fh); // file pointer
323 curl_setopt($context, CURLOPT_INFILESIZE, filesize($src_file));
324 $meta = json_decode(self::execCurlAndClose($context));
325 fclose($fh);
326 return $meta;
327 } else {
328 $content = file_get_contents($src_file);
329 if(strlen($content) == 0)
330 throw new DropboxException("Could not read file $src_file or file is empty!");
331
332 $context = $this->createRequestContext($url, "PUT", $content);
333
334 return json_decode(file_get_contents($url, false, $context));
335 }
336 }
337
338 /**
339 * Get thumbnail for a specified image
340 *
341 * @access public
342 * @param $dropbox_file string Path to the image
343 * @param $format string Image format of the thumbnail (jpeg or png)
344 * @param $size string Thumbnail size (xs, s, m, l, xl)
345 * @return mime/* Returns the thumbnail as binary image data
346 */
347 public function GetThumbnail($dropbox_file, $size = 's', $format = 'jpeg', $echo = false)
348 {
349 if(is_object($dropbox_file) && !empty($dropbox_file->path)) $dropbox_file = $dropbox_file->path;
350 $url = $this->cleanUrl(self::API_CONTENT_URL."thumbnails/$this->rootPath/$dropbox_file")
351 . '?' . http_build_query(array('format' => $format, 'size' => $size),'','&');
352 $context = $this->createRequestContext($url, "GET");
353
354 if($this->useCurl) {
355 curl_setopt($context, CURLOPT_BINARYTRANSFER, true);
356 curl_setopt($context, CURLOPT_RETURNTRANSFER, true);
357 }
358
359 $thumb = $this->useCurl ? self::execCurlAndClose($context) : file_get_contents($url, NULL, $context);
360
361 if($echo) {
362 header('Content-type: image/'.$format);
363 echo $thumb;
364 unset($thumb);
365 return;
366 }
367
368 return $thumb;
369 }
370
371
372 function GetLink($dropbox_file, $preview=true, $short=true, &$expires=null)
373 {
374 if(is_object($dropbox_file) && !empty($dropbox_file->path)) $dropbox_file = $dropbox_file->path;
375 $url = $this->apiCall(($preview?"shares":"media")."/$this->rootPath/$dropbox_file", "POST", array('locale' => null, 'short_url'=> $preview ? $short : null));
376 $expires = strtotime($url->expires);
377 return $url->url;
378 }
379
380 function Delta($cursor)
381 {
382 return $this->apiCall("delta", "POST", compact('cursor'));
383 }
384
385 function GetRevisions($dropbox_file, $rev_limit=10)
386 {
387 if(is_object($dropbox_file) && !empty($dropbox_file->path)) $dropbox_file = $dropbox_file->path;
388 return $this->apiCall("revisions/$this->rootPath/$dropbox_file", "GET", compact('rev_limit'));
389 }
390
391 function Restore($dropbox_file, $rev)
392 {
393 if(is_object($dropbox_file) && !empty($dropbox_file->path)) $dropbox_file = $dropbox_file->path;
394 return $this->apiCall("restore/$this->rootPath/$dropbox_file", "POST", compact('rev'));
395 }
396
397 function Search($path, $query, $file_limit=1000, $include_deleted=false)
398 {
399 return $this->apiCall("search/$this->rootPath/$path", "POST", compact('query','file_limit','include_deleted'));
400 }
401
402 function GetCopyRef($dropbox_file, &$expires=null)
403 {
404 if(is_object($dropbox_file) && !empty($dropbox_file->path)) $dropbox_file = $dropbox_file->path;
405 $ref = $this->apiCall("copy_ref/$this->rootPath/$dropbox_file", "GET", array('locale' => null));
406 $expires = strtotime($ref->expires);
407 return $ref->copy_ref;
408 }
409
410
411 function Copy($from_path, $to_path, $copy_ref=false)
412 {
413 if(is_object($from_path) && !empty($from_path->path)) $from_path = $from_path->path;
414 return $this->apiCall("fileops/copy", "POST", array('root'=> $this->rootPath, ($copy_ref ? 'from_copy_ref' : 'from_path') => $from_path, 'to_path' => $to_path));
415 }
416
417 /**
418 * Creates a new folder in the DropBox
419 *
420 * @access public
421 * @param $path string The path to the new folder to create
422 * @return object Dropbox folder metadata
423 */
424 function CreateFolder($path)
425 {
426 return $this->apiCall("fileops/create_folder", "POST", array('root'=> $this->rootPath, 'path' => $path));
427 }
428
429 /**
430 * Delete file or folder
431 *
432 * @access public
433 * @param $path mixed The path or metadata of the file/folder to be deleted.
434 * @return object Dropbox metadata of deleted file or folder
435 */
436 function Delete($path)
437 {
438 if(is_object($path) && !empty($path->path)) $path = $path->path;
439 return $this->apiCall("fileops/delete", "POST", array('locale' =>null, 'root'=> $this->rootPath, 'path' => $path));
440 }
441
442 function Move($from_path, $to_path)
443 {
444 if(is_object($from_path) && !empty($from_path->path)) $from_path = $from_path->path;
445 return $this->apiCall("fileops/move", "POST", array('root'=> $this->rootPath, 'from_path' => $from_path, 'to_path' => $to_path));
446 }
447
448 function getFileTree($path="", $include_deleted = false, $max_depth = 0, $depth=0)
449 {
450 static $files;
451 if($depth == 0) $files = array();
452
453 $dir = $this->apiCall("metadata/$this->rootPath/$path", "GET", compact('include_deleted'));
454
455 if(empty($dir) || !is_object($dir)) return false;
456
457 if(!empty($dir->error)) throw new DropboxException($dir->error);
458
459 foreach($dir->contents as $item)
460 {
461 $files[trim($item->path,'/')] = $item;
462 if($item->is_dir && $depth < $max_depth)
463 {
464 $this->getFileTree($item->path, $include_deleted, $max_depth, $depth+1);
465 }
466 }
467
468 return $files;
469 }
470
471 function createCurl($url, $http_context)
472 {
473 $ch = curl_init($url);
474
475 $curl_opts = array(
476 CURLOPT_HEADER => false, // exclude header from output
477 //CURLOPT_MUTE => true, // no output!
478 CURLOPT_RETURNTRANSFER => true, // but return!
479 CURLOPT_SSL_VERIFYPEER => false,
480 );
481
482 $curl_opts[CURLOPT_CUSTOMREQUEST] = $http_context['method'];
483
484 if(!empty($http_context['content'])) {
485 $curl_opts[CURLOPT_POSTFIELDS] =& $http_context['content'];
486 if(defined("CURLOPT_POSTFIELDSIZE"))
487 $curl_opts[CURLOPT_POSTFIELDSIZE] = strlen($http_context['content']);
488 }
489
490 $curl_opts[CURLOPT_HTTPHEADER] = array_map('trim',explode("\n",$http_context['header']));
491
492 curl_setopt_array($ch, $curl_opts);
493 return $ch;
494 }
495
496 static private $_curlHeadersRef;
497 static function _curlHeaderCallback($ch, $header)
498 {
499 self::$_curlHeadersRef[] = trim($header);
500 return strlen($header);
501 }
502
503 static function &execCurlAndClose($ch, &$out_response_headers = null)
504 {
505 if(is_array($out_response_headers)) {
506 self::$_curlHeadersRef =& $out_response_headers;
507 curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(__CLASS__, '_curlHeaderCallback'));
508 }
509 $res = curl_exec($ch);
510 $err_no = curl_errno($ch);
511 $err_str = curl_error($ch);
512 curl_close($ch);
513 if($err_no || $res === false) {
514 throw new DropboxException("cURL-Error ($err_no): $err_str");
515 }
516
517 return $res;
518 }
519
520 private function createRequestContext($url, $method, &$content=null, $oauth_token=-1)
521 {
522 if($oauth_token === -1)
523 $oauth_token = $this->accessToken;
524
525 $method = strtoupper($method);
526 $http_context = array('method'=>$method, 'header'=> '');
527
528 $oauth = new OAuthSimple($this->consumerToken['t'],$this->consumerToken['s']);
529
530 if(empty($oauth_token) && !empty($this->accessToken))
531 $oauth_token = $this->accessToken;
532
533 if(!empty($oauth_token)) {
534 $oauth->setParameters(array('oauth_token' => $oauth_token['t']));
535 $oauth->signatures(array('oauth_secret'=>$oauth_token['s']));
536 }
537
538 if(!empty($content)) {
539 $post_vars = ($method != "PUT" && preg_match("/^[a-z][a-z0-9_]*=/i", substr($content, 0, 32)));
540 $http_context['header'] .= "Content-Length: ".strlen($content)."\r\n";
541 $http_context['header'] .= "Content-Type: application/".($post_vars?"x-www-form-urlencoded":"octet-stream")."\r\n";
542 $http_context['content'] =& $content;
543 if($method == "POST" && $post_vars)
544 $oauth->setParameters($content);
545 } elseif($method == "POST") {
546 // make sure that content-length is always set when post request (otherwise some wrappers fail!)
547 $http_context['content'] = "";
548 $http_context['header'] .= "Content-Length: 0\r\n";
549 }
550
551
552 // check for query vars in url and add them to oauth parameters (and remove from path)
553 $path = $url;
554 $query = strrchr($url,'?');
555 if(!empty($query)) {
556 $oauth->setParameters(substr($query,1));
557 $path = substr($url, 0, -strlen($query));
558 }
559
560
561 $signed = $oauth->sign(array(
562 'action' => $method,
563 'path'=> $path));
564 //print_r($signed);
565
566 $http_context['header'] .= "Authorization: ".$signed['header']."\r\n";
567
568 return $this->useCurl ? $this->createCurl($url, $http_context) : stream_context_create(array('http'=>$http_context));
569 }
570
571 private function authCall($path, $request_token=null)
572 {
573 $url = $this->cleanUrl(self::API_URL.$path);
574 $dummy = null;
575 $context = $this->createRequestContext($url, "POST", $dummy, $request_token);
576
577 $contents = $this->useCurl ? self::execCurlAndClose($context) : file_get_contents($url, false, $context);
578 $data = array();
579 parse_str($contents, $data);
580 return $data;
581 }
582
583
584 private function apiCall($path, $method, $params=array(), $content_call=false)
585 {
586 $url = $this->cleanUrl(($content_call ? self::API_CONTENT_URL : self::API_URL).$path);
587 $content = http_build_query(array_merge(array('locale'=>$this->locale), $params),'','&');
588
589 if($method == "GET") {
590 $url .= "?".$content;
591 $content = null;
592 }
593
594 $context = $this->createRequestContext($url, $method, $content);
595 $json = $this->useCurl ? self::execCurlAndClose($context) : file_get_contents($url, false, $context);
596 //if($json === false)
597 // throw new DropboxException();
598 $resp = json_decode($json);
599 if(!empty($resp->error))
600 throw new DropboxException($resp->error);
601 return $resp;
602 }
603
604
605 private static function getMetaFromHeaders(&$header_array)
606 {
607 return json_decode(substr(@array_shift(array_filter($header_array, create_function('$s', 'return strpos($s, "x-dropbox-metadata:") === 0;'))), 20));
608 }
609
610
611 function cleanUrl($url) {
612 $p = substr($url,0,8);
613 $url = str_replace('//','/', str_replace('\\','/',substr($url,8)));
614 $url = rawurlencode($url);
615 $url = str_replace('%2F', '/', $url);
616 return $p.$url;
617 }
618 }
619
620 class DropboxException extends Exception {
621
622 public function __construct($err = null, $isDebug = FALSE)
623 {
624 if(is_null($err)) {
625 $el = error_get_last();
626 $this->message = $el['message'];
627 $this->file = $el['file'];
628 $this->line = $el['line'];
629 } else
630 $this->message = $err;
631 self::log_error($err);
632 if ($isDebug)
633 {
634 self::display_error($err, TRUE);
635 }
636 }
637
638 public static function log_error($err)
639 {
640 error_log($err, 0);
641 }
642
643 public static function display_error($err, $kill = FALSE)
644 {
645 print_r($err);
646 if ($kill === FALSE)
647 {
648 die();
649 }
650 }
651 }
652