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 / lib / Dropbox2 / API.php

API.php in InfiniteWP Client trunk, at lib/Dropbox2/API.php

682 lines 26.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Dropbox API base class
5 * @author Ben Tadiar <ben@handcraftedbyben.co.uk>
6 * @link https://github.com/benthedesigner/dropbox
7 * @link https://www.dropbox.com/developers
8 * @link https://status.dropbox.com Dropbox status
9 * @package Dropbox
10 */
11 class IWP_MMB_Dropbox_API {
12 // API Endpoints
13 const API_URL_V2 = 'https://api.dropboxapi.com/';
14 const CONTENT_URL_V2 = 'https://content.dropboxapi.com/2/';
15
16 /**
17 * OAuth consumer object
18 * @var null|OAuth\Consumer
19 */
20 private $OAuth;
21
22 /**
23 * The root level for file paths
24 * Either `dropbox` or `sandbox` (preferred)
25 * @var null|string
26 */
27 private $root;
28
29 /**
30 * Format of the API response
31 * @var string
32 */
33 private $responseFormat = 'php';
34
35 /**
36 * JSONP callback
37 * @var string
38 */
39 private $callback = 'dropboxCallback';
40
41 /**
42 * Chunk size used for chunked uploads
43 * @see \Dropbox\API::chunkedUpload()
44 */
45 private $chunkSize = 4194304;
46
47 /**
48 * Set the OAuth consumer object
49 * See 'General Notes' at the link below for information on access type
50 * @link https://www.dropbox.com/developers/reference/api
51 * @param OAuth\Consumer\ConsumerAbstract $OAuth
52 * @param string $root Dropbox app access type
53 */
54 public function __construct(Dropbox_ConsumerAbstract $OAuth, $root = 'sandbox') {
55 $this->OAuth = $OAuth;
56 $this->setRoot($root);
57 }
58
59 /**
60 * Set the root level
61 * @param mixed $root
62 * @throws Exception
63 * @return void
64 */
65 public function setRoot($root) {
66 if ($root !== 'sandbox' && $root !== 'dropbox') {
67 throw new Exception("Expected a root of either 'dropbox' or 'sandbox', got '$root'");
68 } else {
69 $this->root = $root;
70 }
71 }
72
73 /**
74 * This function will make a request to refresh the access token
75 *
76 * @return void
77 */
78 public function refreshAccessToken() {
79 $this->OAuth->refreshAccessToken();
80 }
81
82 /**
83 * Retrieves information about the user's account
84 * @return object stdClass
85 */
86 public function accountInfo() {
87 $call = '2/users/get_current_account';
88 $params = array('api_v2' => true);
89 $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
90 return $response;
91 }
92
93 /**
94 * Retrieves information about the user's quota
95 * @param array $options - valid keys are 'timeout'
96 * @return object stdClass
97 */
98 public function quotaInfo($options = array()) {
99 $call = '2/users/get_space_usage';
100 // Cases have been seen (Apr 2019) where a response came back (HTTP/2.0 response header - suspected outgoing web hosting proxy, as everyone else seems to get HTTP/1.0 and I'm not aware that current Curl versions would do HTTP/2.0 without specifically being told to) after 180 seconds; a valid response, but took a long time.
101 $params = array(
102 'api_v2' => true,
103 'timeout' => isset($options['timeout']) ? $options['timeout'] : 20
104 );
105 $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
106 return $response;
107 }
108
109 /**
110 * Uploads large files to Dropbox in mulitple chunks
111 * @param string $file Absolute path to the file to be uploaded
112 * @param string|bool $filename The destination filename of the uploaded file
113 * @param string $path Path to upload the file to, relative to root
114 * @param boolean $overwrite Should the file be overwritten? (Default: true)
115 * @param integer $offset position to seek to when opening the file
116 * @param string $uploadID existing upload_id to resume an upload
117 * @param string|array function to call back to upon each chunk
118 * @return stdClass
119 */
120
121 public function chunked_upload($file, $path = '', $overwrite = true, $uploadID = null, $offset = 0, $isCommit = false) {
122 $starting_backup_path_time = time();
123
124 $file = str_replace("\\", "/",$file);
125 if (!file_exists($file)) throw new Exception('Local file ' . $file . ' does not exist');
126
127 if (!($handle = @fopen($file, 'r'))) throw new Exception('Could not open ' . $file . ' for reading');
128
129 // Seek to the correct position on the file pointer
130 fseek($handle, $offset);
131 $to_exit = false;
132
133 //Set firstCommit to true so that the upload session start endpoint is called.
134 $firstCommit = (0 == $offset);
135
136 // Read from the file handle until EOF, uploading each chunk
137 if ($data = fread($handle, $this->chunkSize)) {
138
139 // Set the file, request parameters and send the request
140 $this->OAuth->setInFile($data);
141
142 if ($firstCommit) {
143 $params = array(
144 'close' => false,
145 'api_v2' => true,
146 'content_upload' => true
147 );
148 $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/start', $params);
149 $firstCommit = false;
150
151 } else {
152 $params = array(
153 'cursor' => array(
154 'session_id' => $uploadID,
155 // If you send it as a string, Dropbox will be unhappy
156 'offset' => (int)$offset
157 ),
158 'api_v2' => true,
159 'content_upload' => true
160 );
161 $response = $this->append_upload($params, false);
162 }
163
164 // On subsequent chunks, use the upload ID returned by the previous request
165 if (isset($response['body']->session_id)) {
166 $uploadID = $response['body']->session_id;
167 }
168
169 /*
170 API v2 no longer returns the offset, we need to manually work this out. So check that there are no errors and update the offset as well as calling the callback method.
171 */
172 if (!isset($response['body']->error)) {
173 $offset = ftell($handle);
174 $output['response']= $response;
175 if($isCommit ==false){
176
177 $output['offset']= $offset;
178 $output['upload_id']= $uploadID;
179 }
180 $this->OAuth->setInFile(null);
181 }
182
183 }
184 // Complete the chunked upload
185 if ($isCommit) {
186 $filename = (isset($filename) && is_string($filename)) ? $filename : basename($file);
187 $params = array(
188 'cursor' => array(
189 'session_id' => $uploadID,
190 'offset' => (int)$offset
191 ),
192 'commit' => array(
193 'path' => '/' . $this->encodePath($path .'/'. $filename),
194 'mode' => 'overwrite'
195 ),
196 'api_v2' => true,
197 'content_upload' => true
198 );
199 $response = $this->append_upload($params, true);
200 $offset = ftell($handle);
201 $output['response']= $response;
202 }
203
204 fclose($handle);
205 return $output;
206 }
207
208 public function chunkedUpload($file, $filename = false, $path = '', $overwrite = true, $offset = 0, $uploadID = null, $callback = null) {
209
210 if (file_exists($file)) {
211 if ($handle = @fopen($file, 'r')) {
212 // Set initial upload ID and offset
213 if ($offset > 0) {
214 fseek($handle, $offset);
215 }
216
217 /*
218 Set firstCommit to true so that the upload session start endpoint is called.
219 */
220 $firstCommit = (0 == $offset);
221
222 // Read from the file handle until EOF, uploading each chunk
223 while ($data = fread($handle, $this->chunkSize)) {
224
225 // Set the file, request parameters and send the request
226 $this->OAuth->setInFile($data);
227
228 if ($firstCommit) {
229 $params = array(
230 'close' => false,
231 'api_v2' => true,
232 'content_upload' => true
233 );
234 $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/start', $params);
235 $firstCommit = false;
236 } else {
237 $params = array(
238 'cursor' => array(
239 'session_id' => $uploadID,
240 // If you send it as a string, Dropbox will be unhappy
241 'offset' => (int)$offset
242 ),
243 'api_v2' => true,
244 'content_upload' => true
245 );
246 $response = $this->append_upload($params, false);
247 }
248
249 // On subsequent chunks, use the upload ID returned by the previous request
250 if (isset($response['body']->session_id)) {
251 $uploadID = $response['body']->session_id;
252 }
253
254 /*
255 API v2 no longer returns the offset, we need to manually work this out. So check that there are no errors and update the offset as well as calling the callback method.
256 */
257 if (!isset($response['body']->error)) {
258 $offset = ftell($handle);
259 if ($callback) {
260 call_user_func($callback, $offset, $uploadID, $file);
261 }
262 $this->OAuth->setInFile(null);
263 }
264 }
265
266 // Complete the chunked upload
267 $filename = (is_string($filename)) ? $filename : basename($file);
268 $params = array(
269 'cursor' => array(
270 'session_id' => $uploadID,
271 'offset' => $offset
272 ),
273 'commit' => array(
274 'path' => '/' . $this->encodePath($path . $filename),
275 'mode' => 'add'
276 ),
277 'api_v2' => true,
278 'content_upload' => true
279 );
280 $response = $this->append_upload($params, true);
281 return $response;
282 } else {
283 throw new Exception('Could not open ' . $file . ' for reading');
284 }
285 }
286
287 // Throw an Exception if the file does not exist
288 throw new Exception('Local file ' . $file . ' does not exist');
289 }
290
291 private function append_upload($params, $last_call) {
292 try {
293 if ($last_call){
294 $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/finish', $params);
295 } else {
296 $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/append_v2', $params);
297 }
298 } catch (Exception $e) {
299 $responseCheck = json_decode($e->getMessage());
300 if (isset($responseCheck) && strpos($responseCheck[0] , 'incorrect_offset') !== false) {
301 $expected_offset = $responseCheck[1];
302 throw new Exception('Submitted input out of alignment: got ['.$params['cursor']['offset'].'] expected ['.$expected_offset.']');
303
304 // $params['cursor']['offset'] = $responseCheck[1];
305 // $response = $this->append_upload($params, $last_call);
306 } elseif (isset($responseCheck) && strpos($responseCheck[0], 'closed') !== false) {
307 throw new Exception("Upload with upload_id {$params['cursor']['session_id']} already completed");
308 } else {
309 throw $e;
310 }
311 }
312 return $response;
313 }
314
315 /**
316 * Chunked downloads a file from Dropbox, it will return false if a file handle is not passed and will return true if the call was successful.
317 *
318 * @param string $file Path - to file, relative to root, including path
319 * @param resource $outFile - the local file handle
320 * @param array $options - any extra options to be passed e.g headers
321 * @return boolean - a boolean to indicate success or failure
322 */
323 public function download($file, $outFile = null, $options = array()) {
324 // Only allow php response format for this call
325 if ($this->responseFormat !== 'php') {
326 throw new Exception('This method only supports the `php` response format');
327 }
328
329 if ($outFile) {
330 $this->OAuth->setOutFile($outFile);
331
332 $params = array('path' => '/' . $file, 'api_v2' => true, 'content_download' => true);
333
334 if (isset($options['headers'])) {
335 foreach ($options['headers'] as $key => $header) {
336 $headers[] = $key . ': ' . $header;
337 }
338 $params['headers'] = $headers;
339 }
340
341 $file = $this->encodePath($file);
342 $call = 'files/download';
343
344 $response = $this->fetch('GET', self::CONTENT_URL_V2, $call, $params);
345
346 fclose($outFile);
347
348 return true;
349 } else {
350 return false;
351 }
352 }
353
354 /**
355 * Calls the relevant method to return metadata for all files and folders that match the search query
356 * @param mixed $query The search string. Must be at least 3 characters long
357 * @param string [$path=''] The path to the folder you want to search in
358 * @param integer [$limit=1000] Maximum number of results to return (1-1000)
359 * @param integer [$cursor=''] A Dropbox ID to start the search from
360 * @return array
361 */
362 public function search($query, $path = '', $limit = 1000, $cursor = '') {
363 if (empty($cursor)) {
364 return $this->start_search($query, $path, $limit);
365 } else {
366 return $this->continue_search($cursor);
367 }
368 }
369
370 /**
371 * This method will start a search for all files and folders that match the search query
372 *
373 * @param mixed $query - the search string, must be at least 3 characters long
374 * @param string $path - the path to the folder you want to search in
375 * @param integer $limit - maximum number of results to return (1-1000)
376 *
377 * @return array - an array of search results
378 */
379 private function start_search($query, $path, $limit) {
380 $call = '2/files/search_v2';
381 $path = $this->encodePath($path);
382 // APIv2 requires that the path match this regex: String(pattern="(/(.|[\r\n])*)?|(ns:[0-9]+(/.*)?)")
383 if ($path && '/' != substr($path, 0, 1)) $path = "/$path";
384 $params = array(
385 'query' => $query,
386 'options' => array(
387 'path' => $path,
388 'max_results' => ($limit < 1) ? 1 : (($limit > 1000) ? 1000 : (int) $limit),
389 ),
390 'api_v2' => true,
391 );
392 $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
393 return $response;
394 }
395
396 /**
397 * This method will continue a previous search for all files and folders that match the previous search query
398 *
399 * @param string $cursor - a Dropbox ID to continue the search
400 *
401 * @return array - an array of search results
402 */
403 private function continue_search($cursor) {
404 $call = '2/files/search/continue_v2';
405 $params = array(
406 'cursor' => $cursor,
407 'api_v2' => true,
408 );
409 $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
410 return $response;
411 }
412
413 /**
414 * Deletes a file or folder
415 * @param string $path The path to the file or folder to be deleted
416 * @return object stdClass
417 */
418 public function delete($path) {
419 $call = '2/files/delete_v2';
420 $params = array('path' => '/' . $this->normalisePath($path), 'api_v2' => true);
421 $response = $this->fetch('POST', self::API_URL_V2, $call, $params);
422 return $response;
423 }
424
425 /**
426 * Intermediate fetch function
427 * @param string $method The HTTP method
428 * @param string $url The API endpoint
429 * @param string $call The API method to call
430 * @param array $params Additional parameters
431 * @return mixed
432 */
433 private function fetch($method, $url, $call, array $params = array()) {
434 // Make the API call via the consumer
435 $response = $this->OAuth->fetch($method, $url, $call, $params);
436
437 // Format the response and return
438 switch ($this->responseFormat) {
439 case 'json':
440 return json_encode($response);
441 case 'jsonp':
442 $response = json_encode($response);
443 return $this->callback . '(' . $response . ')';
444 default:
445 return $response;
446 }
447 }
448
449 /**
450 * Set the API response format
451 * @param string $format One of php, json or jsonp
452 * @return void
453 */
454 public function setResponseFormat($format) {
455 $format = strtolower($format);
456 if (!in_array($format, array('php', 'json', 'jsonp'))) {
457 throw new Exception("Expected a format of php, json or jsonp, got '$format'");
458 } else {
459 $this->responseFormat = $format;
460 }
461 }
462
463 /**
464 * Set the chunk size for chunked uploads
465 * If $chunkSize is empty, set to 4194304 bytes (4 MB)
466 * @see \Dropbox\API\chunkedUpload()
467 */
468 public function setChunkSize($chunkSize = 4194304) {
469 if (!is_int($chunkSize)) {
470 throw new Exception('Expecting chunk size to be an integer, got ' . gettype($chunkSize));
471 } elseif ($chunkSize > 157286400) {
472 throw new Exception('Chunk size must not exceed 157286400 bytes, got ' . $chunkSize);
473 } else {
474 $this->chunkSize = $chunkSize;
475 }
476 }
477
478 /**
479 * Set the JSONP callback function
480 * @param string $function
481 * @return void
482 */
483 public function setCallback($function) {
484 $this->callback = $function;
485 }
486
487 public function getFile($file, $outFile = false, $revision = null, $allow_resume = false) {
488 // Only allow php response format for this call
489 if ($this->responseFormat !== 'php') {
490 throw new Exception('This method only supports the `php` response format');
491 }
492
493 $handle = null;
494 if ($outFile !== false) {
495 // Create a file handle if $outFile is specified
496 if ($allow_resume && file_exists($outFile)) {
497 if (!$handle = fopen($outFile, 'a')) {
498 throw new Exception("Unable to open file handle for $outFile");
499 } else {
500 $this->OAuth->setOutFile($handle);
501 $params['headers'] = array('Range: bytes='.filesize($outFile).'-');
502 }
503 }
504 elseif (!$handle = fopen($outFile, 'w')) {
505 throw new Exception("Unable to open file handle for $outFile");
506 } else {
507 $this->OAuth->setOutFile($handle);
508 }
509 }
510
511 $file = $this->encodePath($file);
512 $call = 'files/download';
513 $params = array('path' => '/' . $file, 'api_v2' => true, 'content_download' => true);
514 $response = $this->fetch('GET', self::CONTENT_URL_V2, $call, $params);
515
516 // Close the file handle if one was opened
517 if ($handle) fclose($handle);
518
519 return array(
520 'name' => ($outFile) ? $outFile : basename($file),
521 'mime' => $this->getMimeType(($outFile) ? $outFile : $response['body'], $outFile),
522 'meta' => json_decode($response['headers']['dropbox-api-result']),
523 'data' => $response['body'],
524 );
525 }
526
527 /**
528 * Get the mime type of downloaded file
529 * If the Fileinfo extension is not loaded, return false
530 * @param string $data File contents as a string or filename
531 * @param string $isFilename Is $data a filename?
532 * @return boolean|string Mime type and encoding of the file
533 */
534 private function getMimeType($data, $isFilename = false) {
535 if (extension_loaded('fileinfo')) {
536 $finfo = new finfo(FILEINFO_MIME);
537 if ($isFilename !== false) {
538 return $finfo->file($data);
539 }
540 return $finfo->buffer($data);
541 }
542 return false;
543 }
544
545 /**
546 * Trim the path of forward slashes and replace
547 * consecutive forward slashes with a single slash
548 * @param string $path The path to normalise
549 * @return string
550 */
551 private function normalisePath($path) {
552 $path = preg_replace('#/+#', '/', trim($path, '/'));
553 return $path;
554 }
555
556 /**
557 * Encode the path, then replace encoded slashes
558 * with literal forward slash characters
559 * @param string $path The path to encode
560 * @return string
561 */
562 private function encodePath($path) {
563 // in APIv1, encoding was needed because parameters were passed as part of the URL; this is no longer done in our APIv2 SDK; hence, all that we now do here is normalise.
564 return $this->normalisePath($path);
565 }
566
567 public function metaData($path = null, $rev = null, $limit = 10000, $hash = false, $list = true, $deleted = false) {
568 $call = '2/files/get_metadata' ;
569 $params = array(
570 'path' => '/' . $this->normalisePath($path),
571 'api_v2' => true
572 );
573
574 return $this->fetch('POST', self::API_URL_V2, $call, $params);
575 }
576
577 public function putFile($file, $path = '', $overwrite = true) {
578 if (!file_exists($file)) {
579 // Throw an Exception if the file does not exist
580 throw new Exception('Local file ' . $file . ' does not exist');
581 }
582 $filesize = iwp_mmb_get_file_size($file);
583 if ($filesize >= 157286400) {
584 $output = $this->chunked_upload_single_call_new($file, $path,$overwrite);
585 return $output;
586
587 }else{
588 $handle = @fopen($file, 'r');
589 //Set the file content to $this->InFile
590 $this->OAuth->setInFile(fread($handle, filesize($file)));
591 fclose($handle);
592
593 $filename = (!empty($filename) && is_string($filename)) ? $filename : basename($file);
594 $path = '/' . $this->encodePath($path .'/'. $filename);
595 $params = array(
596 'path' => $path,
597 'mute' => true,
598 'mode' => 'overwrite',
599 'api_v2' => true,
600 'content_upload' => true
601 );
602 $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload', $params);
603 return $response;
604 }
605
606 }
607
608 public function chunked_upload_single_call_new($file, $path = '',$overwrite=true){
609 $file = str_replace("\\", "/",$file);
610 if (!is_readable($file) or !is_file($file))
611 throw new Exception("Error: File \"$file\" is not readable or doesn't exist.");
612 $file_handle=fopen($file,'r');
613 $uploadID=null;
614 $offset=0;
615 $ProgressFunction=null;
616 while ($data=fread($file_handle, (1024*1024*30))) { //1024*1024*30 = 30MB
617 $firstCommit = (0 == $offset);
618 iwp_mmb_auto_print('dropbox_chucked_upload');
619 $this->OAuth->setInFile($data);
620
621 if ($firstCommit) {
622 $params = array(
623 'close' => false,
624 'api_v2' => true,
625 'content_upload' => true
626 );
627 $response = $this->fetch('POST', self::CONTENT_URL_V2, 'files/upload_session/start', $params);
628 $firstCommit = false;
629
630 } else {
631 $params = array(
632 'cursor' => array(
633 'session_id' => $uploadID,
634 // If you send it as a string, Dropbox will be unhappy
635 'offset' => (int)$offset
636 ),
637 'api_v2' => true,
638 'content_upload' => true
639 );
640 $response = $this->append_upload($params, false);
641 }
642
643 // On subsequent chunks, use the upload ID returned by the previous request
644 if (isset($response['body']->session_id)) {
645 $uploadID = $response['body']->session_id;
646 }
647
648 /*
649 API v2 no longer returns the offset, we need to manually work this out. So check that there are no errors and update the offset as well as calling the callback method.
650 */
651 if (!isset($response['body']->error)) {
652 $offset = ftell($file_handle);
653 $output['response']= $response;
654 if($isCommit ==false){
655
656 $output['offset']= $offset;
657 $output['upload_id']= $uploadID;
658 }
659 $this->OAuth->setInFile(null);
660 }
661 fseek($file_handle, $offset);
662 }
663 fclose($file_handle);
664 $filename = (is_string($filename)) ? $filename : basename($file);
665 $params = array(
666 'cursor' => array(
667 'session_id' => $uploadID,
668 'offset' => (int)$offset
669 ),
670 'commit' => array(
671 'path' => '/' . $this->encodePath($path .'/'. $filename),
672 'mode' => 'overwrite'
673 ),
674 'api_v2' => true,
675 'content_upload' => true
676 );
677 $response = $this->append_upload($params, true);
678
679 return $response;
680 }
681 }
682