PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / 1.6.9
JetBackup – Backup, Restore & Migrate v1.6.9
3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / com / lib / Dropbox / Client.php
backup / com / lib / Dropbox Last commit date
Exception 8 years ago WebAuthException 8 years ago certs 8 years ago AppInfo.php 8 years ago AppInfoLoadException.php 8 years ago ArrayEntryStore.php 8 years ago AuthBase.php 8 years ago AuthInfo.php 8 years ago AuthInfoLoadException.php 8 years ago Checker.php 8 years ago Client.php 8 years ago Curl.php 8 years ago CurlStreamRelay.php 8 years ago DeserializeException.php 8 years ago DropboxMetadataHeaderCatcher.php 8 years ago Exception.php 8 years ago Host.php 8 years ago HttpResponse.php 8 years ago OAuth1AccessToken.php 8 years ago OAuth1Upgrader.php 8 years ago Path.php 8 years ago RequestUtil.php 8 years ago RootCertificates.php 8 years ago SSLTester.php 8 years ago Security.php 8 years ago StreamReadException.php 8 years ago Util.php 8 years ago ValueStore.php 8 years ago WebAuth.php 3 years ago WebAuthBase.php 3 years ago WebAuthNoRedirect.php 8 years ago WriteMode.php 8 years ago autoload.php 8 years ago strict.php 8 years ago
Client.php
1701 lines
1 <?php
2 namespace Dropbox;
3
4 /**
5 * The class used to make most Dropbox API calls. You can use this once you've gotten an
6 * {@link AccessToken} via {@link WebAuth}.
7 *
8 * This class is stateless so it can be shared/reused.
9 */
10 class Client
11 {
12 /**
13 * The access token used by this client to make authenticated API calls. You can get an
14 * access token via {@link WebAuth}.
15 *
16 * @return AccessToken
17 */
18 function getAccessToken() { return $this->accessToken; }
19
20 /** @var AccessToken */
21 private $accessToken;
22
23 /**
24 * An identifier for the API client, typically of the form "Name/Version".
25 * This is used to set the HTTP <code>User-Agent</code> header when making API requests.
26 * Example: <code>"PhotoEditServer/1.3"</code>
27 *
28 * If you're the author a higher-level library on top of the basic SDK, and the
29 * "Photo Edit" app's server code is using your library to access Dropbox, you should append
30 * your library's name and version to form the full identifier. For example,
31 * if your library is called "File Picker", you might set this field to:
32 * <code>"PhotoEditServer/1.3 FilePicker/0.1-beta"</code>
33 *
34 * The exact format of the <code>User-Agent</code> header is described in
35 * <a href="http://tools.ietf.org/html/rfc2616#section-3.8">section 3.8 of the HTTP specification</a>.
36 *
37 * Note that underlying HTTP client may append other things to the <code>User-Agent</code>, such as
38 * the name of the library being used to actually make the HTTP request (such as cURL).
39 *
40 * @return string
41 */
42 function getClientIdentifier() { return $this->clientIdentifier; }
43
44 /** @var string */
45 private $clientIdentifier;
46
47 /**
48 * The locale of the user of your application. Some API calls return localized
49 * data and error messages; this "user locale" setting determines which locale
50 * the server should use to localize those strings.
51 *
52 * @return null|string
53 */
54 function getUserLocale() { return $this->userLocale; }
55
56 /** @var null|string */
57 private $userLocale;
58
59 /**
60 * The {@link Host} object that determines the hostnames we make requests to.
61 *
62 * @return Host
63 */
64 function getHost() { return $this->host; }
65
66 /**
67 * Constructor.
68 *
69 * @param string $accessToken
70 * See {@link getAccessToken()}
71 * @param string $clientIdentifier
72 * See {@link getClientIdentifier()}
73 * @param null|string $userLocale
74 * See {@link getUserLocale()}
75 */
76 function __construct($accessToken, $clientIdentifier, $userLocale = null)
77 {
78 self::checkAccessTokenArg("accessToken", $accessToken);
79 self::checkClientIdentifierArg("clientIdentifier", $clientIdentifier);
80 Checker::argStringNonEmptyOrNull("userLocale", $userLocale);
81
82 $this->accessToken = $accessToken;
83 $this->clientIdentifier = $clientIdentifier;
84 $this->userLocale = $userLocale;
85
86 // The $host parameter is sort of internal. We don't include it in the param list because
87 // we don't want it to be included in the documentation. Use PHP arg list hacks to get at
88 // it.
89 $host = null;
90 if (\func_num_args() == 4) {
91 $host = \func_get_arg(3);
92 Host::checkArgOrNull("host", $host);
93 }
94 if ($host === null) {
95 $host = Host::getDefault();
96 }
97 $this->host = $host;
98
99 // These fields are redundant, but it makes these values a little more convenient
100 // to access.
101 $this->apiHost = $host->getApi();
102 $this->contentHost = $host->getContent();
103 }
104
105 /** @var string */
106 private $apiHost;
107 /** @var string */
108 public $contentHost;
109
110 /**
111 * Given a <code>$base</code> path for an API endpoint (for example, "/files"), append
112 * a Dropbox API file path to the end of that URL. Special characters in the file will
113 * be encoded properly.
114 *
115 * This is for endpoints like "/files" takes the path on the URL and not as a separate
116 * query or POST parameter.
117 *
118 * @param string $base
119 * @param string $path
120 * @return string
121 */
122 function appendFilePath($base, $path)
123 {
124 return $base . "/auto/" . rawurlencode(substr($path, 1));
125 }
126
127 /**
128 * Make an API call to disable the access token that you constructed this <code>Client</code>
129 * with. After calling this, API calls made with this <code>Client</code> will fail.
130 *
131 * See <a href="https://www.dropbox.com/developers/core/docs#disable-token">/disable_access_token</a>.
132 *
133 * @throws Exception
134 */
135 // function disableAccessToken()
136 // {
137 // $response = $this->doPost($this->apiHost, "1/disable_access_token");
138 // if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
139 // }
140
141 /*Dropbox api 2*/
142 function disableAccessToken()
143 {
144 $response = $this->doPost($this->apiHost, "2/auth/token/revoke");
145 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
146 }
147
148 /**
149 * Make an API call to get basic account and quota information.
150 *
151 * <code>
152 * $client = ...
153 * $accountInfo = $client->getAccountInfo();
154 * print_r($accountInfo);
155 * </code>
156 *
157 * @return array
158 * See <a href="https://www.dropbox.com/developers/core/docs#account-info">/account/info</a>.
159 *
160 * @throws Exception
161 */
162 // function getAccountInfo()
163 // {
164 // $response = $this->doGet($this->apiHost, "1/account/info");
165 // if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
166 // return RequestUtil::parseResponseJson($response->body);
167 // }
168
169 /*Dropbox api 2*/
170 function getAccountInfo()
171 {
172 $response = $this->doPost(
173 $this->apiHost,
174 "2/users/get_current_account",
175 null,
176 "Content-Type: application/json"
177 );
178 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
179 return RequestUtil::parseResponseJson($response->body);
180 }
181
182 /**
183 * Downloads a file from Dropbox. The file's contents are written to the
184 * given <code>$outStream</code> and the file's metadata is returned.
185 *
186 * <code>
187 * $client = ...;
188 * $fd = fopen("./Frog.jpeg", "wb");
189 * $metadata = $client->getFile("/Photos/Frog.jpeg", $fd);
190 * fclose($fd);
191 * print_r($metadata);
192 * </code>
193 *
194 * @param string $path
195 * The path to the file on Dropbox (UTF-8).
196 *
197 * @param resource $outStream
198 * If the file exists, the file contents will be written to this stream.
199 *
200 * @param string|null $rev
201 * If you want the latest revision of the file at the given path, pass in <code>null</code>.
202 * If you want a specific version of a file, pass in value of the file metadata's "rev" field.
203 *
204 * @return null|array
205 * The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
206 * object</a> for the file at the given $path and $rev, or <code>null</code> if the file
207 * doesn't exist,
208 *
209 * @throws Exception
210 */
211 function getFile($path, $outStream, $rev = null)
212 {
213 Path::checkArgNonRoot("path", $path);
214 Checker::argResource("outStream", $outStream);
215 Checker::argStringNonEmptyOrNull("rev", $rev);
216
217 $url = $this->buildUrlForGetOrPut(
218 $this->contentHost,
219 $this->appendFilePath("1/files", $path),
220 array("rev" => $rev));
221
222 $curl = $this->mkCurl($url);
223 $metadataCatcher = new DropboxMetadataHeaderCatcher($curl->handle);
224 $streamRelay = new CurlStreamRelay($curl->handle, $outStream);
225
226 $response = $curl->exec();
227
228 if ($response->statusCode === 404) return null;
229
230 if ($response->statusCode !== 200) {
231 $response->body = $streamRelay->getErrorBody();
232 throw RequestUtil::unexpectedStatus($response);
233 }
234
235 return $metadataCatcher->getMetadata();
236 }
237
238 /**
239 * Calling 'uploadFile' with <code>$numBytes</code> less than this value, will cause this SDK
240 * to use the standard /files_put endpoint. When <code>$numBytes</code> is greater than this
241 * value, we'll use the /chunked_upload endpoint.
242 *
243 * @var int
244 */
245 private static $AUTO_CHUNKED_UPLOAD_THRESHOLD = 9863168; // 8 MB
246
247 /**
248 * @var int
249 */
250 private static $DEFAULT_CHUNK_SIZE = 4194304; // 4 MB
251
252 /**
253 * Creates a file on Dropbox, using the data from <code>$inStream</code> for the file contents.
254 *
255 * <code>
256 * use \Dropbox as dbx;
257 * $client = ...;
258 * $fd = fopen("./frog.jpeg", "rb");
259 * $md1 = $client->uploadFile("/Photos/Frog.jpeg",
260 * dbx\WriteMode::add(), $fd);
261 * fclose($fd);
262 * print_r($md1);
263 * $rev = $md1["rev"];
264 *
265 * // Re-upload with WriteMode::update(...), which will overwrite the
266 * // file if it hasn't been modified from our original upload.
267 * $fd = fopen("./frog-new.jpeg", "rb");
268 * $md2 = $client->uploadFile("/Photos/Frog.jpeg",
269 * dbx\WriteMode::update($rev), $fd);
270 * fclose($fd);
271 * print_r($md2);
272 * </code>
273 *
274 * @param string $path
275 * The Dropbox path to save the file to (UTF-8).
276 *
277 * @param WriteMode $writeMode
278 * What to do if there's already a file at the given path.
279 *
280 * @param resource $inStream
281 * The data to use for the file contents.
282 *
283 * @param int|null $numBytes
284 * You can pass in <code>null</code> if you don't know. If you do provide the size, we can
285 * perform a slightly more efficient upload (fewer network round-trips) for files smaller
286 * than 8 MB.
287 *
288 * @return mixed
289 * The <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
290 * object</a> for the newly-added file.
291 *
292 * @throws Exception
293 */
294 function uploadFile($path, $writeMode, $inStream, $numBytes = null)
295 {
296 Path::checkArgNonRoot("path", $path);
297 WriteMode::checkArg("writeMode", $writeMode);
298 Checker::argResource("inStream", $inStream);
299 Checker::argNatOrNull("numBytes", $numBytes);
300
301 // If we don't know how many bytes are coming, we have to use chunked upload.
302 // If $numBytes is large, we elect to use chunked upload.
303 // In all other cases, use regular upload.
304 if ($numBytes === null || $numBytes > self::$AUTO_CHUNKED_UPLOAD_THRESHOLD) {
305 $metadata = $this->_uploadFileChunked($path, $writeMode, $inStream, $numBytes,
306 self::$DEFAULT_CHUNK_SIZE);
307 } else {
308 $metadata = $this->_uploadFile($path, $writeMode,
309 function(Curl $curl) use ($inStream, $numBytes) {
310 $curl->set(CURLOPT_PUT, true);
311 $curl->set(CURLOPT_INFILE, $inStream);
312 $curl->set(CURLOPT_INFILESIZE, $numBytes);
313 });
314 }
315
316 return $metadata;
317 }
318
319 /**
320 * Creates a file on Dropbox, using the given $data string as the file contents.
321 *
322 * <code>
323 * use \Dropbox as dbx;
324 * $client = ...;
325 * $md = $client->uploadFileFromString("/Grocery List.txt",
326 * dbx\WriteMode::add(),
327 * "1. Coke\n2. Popcorn\n3. Toothpaste\n");
328 * print_r($md);
329 * </code>
330 *
331 * @param string $path
332 * The Dropbox path to save the file to (UTF-8).
333 *
334 * @param WriteMode $writeMode
335 * What to do if there's already a file at the given path.
336 *
337 * @param string $data
338 * The data to use for the contents of the file.
339 *
340 * @return mixed
341 * The <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
342 * object</a> for the newly-added file.
343 *
344 * @throws Exception
345 */
346 function uploadFileFromString($path, $writeMode, $data)
347 {
348 Path::checkArgNonRoot("path", $path);
349 WriteMode::checkArg("writeMode", $writeMode);
350 Checker::argString("data", $data);
351
352 return $this->_uploadFile($path, $writeMode, function(Curl $curl) use ($data) {
353 $curl->set(CURLOPT_CUSTOMREQUEST, "PUT");
354 $curl->set(CURLOPT_POSTFIELDS, $data);
355 $curl->addHeader("Content-Type: application/octet-stream");
356 });
357 }
358
359 /**
360 * Creates a file on Dropbox, using the data from $inStream as the file contents.
361 *
362 * This version of <code>uploadFile</code> splits uploads the file ~4MB chunks at a time and
363 * will retry a few times if one chunk fails to upload. Uses {@link chunkedUploadStart()},
364 * {@link chunkedUploadContinue()}, and {@link chunkedUploadFinish()}.
365 *
366 * @param string $path
367 * The Dropbox path to save the file to (UTF-8).
368 *
369 * @param WriteMode $writeMode
370 * What to do if there's already a file at the given path.
371 *
372 * @param resource $inStream
373 * The data to use for the file contents.
374 *
375 * @param int|null $numBytes
376 * The number of bytes available from $inStream.
377 * You can pass in <code>null</code> if you don't know.
378 *
379 * @param int|null $chunkSize
380 * The number of bytes to upload in each chunk. You can omit this (or pass in
381 * <code>null</code> and the library will use a reasonable default.
382 *
383 * @return mixed
384 * The <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
385 * object</a> for the newly-added file.
386 *
387 * @throws Exception
388 */
389 function uploadFileChunked($path, $writeMode, $inStream, $numBytes = null, $chunkSize = null)
390 {
391 if ($chunkSize === null) {
392 $chunkSize = self::$DEFAULT_CHUNK_SIZE;
393 }
394
395 Path::checkArgNonRoot("path", $path);
396 WriteMode::checkArg("writeMode", $writeMode);
397 Checker::argResource("inStream", $inStream);
398 Checker::argNatOrNull("numBytes", $numBytes);
399 Checker::argIntPositive("chunkSize", $chunkSize);
400
401 return $this->_uploadFileChunked($path, $writeMode, $inStream, $numBytes, $chunkSize);
402 }
403
404 /**
405 * @param string $path
406 *
407 * @param WriteMode $writeMode
408 * What to do if there's already a file at the given path (UTF-8).
409 *
410 * @param resource $inStream
411 * The source of data to upload.
412 *
413 * @param int|null $numBytes
414 * You can pass in <code>null</code>. But if you know how many bytes you expect, pass in
415 * that value and this function will do a sanity check at the end to make sure the number of
416 * bytes read from $inStream matches up.
417 *
418 * @param int $chunkSize
419 *
420 * @return array
421 * The <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
422 * object</a> for the newly-added file.
423 */
424 private function _uploadFileChunked($path, $writeMode, $inStream, $numBytes, $chunkSize)
425 {
426 Path::checkArg("path", $path);
427 WriteMode::checkArg("writeMode", $writeMode);
428 Checker::argResource("inStream", $inStream);
429 Checker::argNatOrNull("numBytes", $numBytes);
430 Checker::argNat("chunkSize", $chunkSize);
431
432 // NOTE: This function performs 3 retries on every call. This is maybe not the right
433 // layer to make retry decisions. It's also awkward because none of the other calls
434 // perform retries.
435
436 assert($chunkSize > 0);
437
438 $data = self::readFully($inStream, $chunkSize);
439 $len = strlen($data);
440
441 $client = $this;
442 $uploadId = RequestUtil::runWithRetry(3, function() use ($data, $client) {
443 return $client->chunkedUploadStart($data);
444 });
445
446 $byteOffset = $len;
447
448 while (!feof($inStream)) {
449 $data = self::readFully($inStream, $chunkSize);
450 $len = strlen($data);
451
452 while (true) {
453 $r = RequestUtil::runWithRetry(3,
454 function() use ($client, $uploadId, $byteOffset, $data) {
455 return $client->chunkedUploadContinue($uploadId, $byteOffset, $data);
456 });
457
458 if ($r === true) { // Chunk got uploaded!
459 $byteOffset += $len;
460 break;
461 }
462 if ($r === false) { // Server didn't recognize our upload ID
463 // This is very unlikely since we're uploading all the chunks in sequence.
464 throw new Exception_BadResponse("Server forgot our uploadId");
465 }
466
467 // Otherwise, the server is at a different byte offset from us.
468 $serverByteOffset = $r;
469 assert($serverByteOffset !== $byteOffset); // chunkedUploadContinue ensures this.
470 // An earlier byte offset means the server has lost data we sent earlier.
471 if ($serverByteOffset < $byteOffset) throw new Exception_BadResponse(
472 "Server is at an ealier byte offset: us=$byteOffset, server=$serverByteOffset");
473 $diff = $serverByteOffset - $byteOffset;
474 // If the server is past where we think it could possibly be, something went wrong.
475 if ($diff > $len) throw new Exception_BadResponse(
476 "Server is more than a chunk ahead: us=$byteOffset, server=$serverByteOffset");
477 // The normal case is that the server is a bit further along than us because of a
478 // partially-uploaded chunk. Finish it off.
479 $byteOffset += $diff;
480 if ($diff === $len) break; // If the server is at the end, we're done.
481 $data = substr($data, $diff);
482 }
483 }
484
485 if ($numBytes !== null && $byteOffset !== $numBytes) throw new \InvalidArgumentException(
486 "You passed numBytes=$numBytes but the stream had $byteOffset bytes.");
487
488 $metadata = RequestUtil::runWithRetry(3,
489 function() use ($client, $uploadId, $path, $writeMode) {
490 return $client->chunkedUploadFinish($uploadId, $path, $writeMode);
491 });
492
493 return $metadata;
494 }
495
496 /**
497 * Sometimes fread() returns less than the request number of bytes (for example, when reading
498 * from network streams). This function repeatedly calls fread until the requested number of
499 * bytes have been read or we've reached EOF.
500 *
501 * @param resource $inStream
502 * @param int $numBytes
503 * @throws StreamReadException
504 * @return string
505 */
506 private static function readFully($inStream, $numBytes)
507 {
508 Checker::argNat("numBytes", $numBytes);
509
510 $full = '';
511 $bytesRemaining = $numBytes;
512 while (!feof($inStream) && $bytesRemaining > 0) {
513 $part = fread($inStream, $bytesRemaining);
514 if ($part === false) throw new StreamReadException("Error reading from \$inStream.");
515 $full .= $part;
516 $bytesRemaining -= strlen($part);
517 }
518 return $full;
519 }
520
521 /**
522 * @param string $path
523 * @param WriteMode $writeMode
524 * @param callable $curlConfigClosure
525 * @return array
526 */
527 private function _uploadFile($path, $writeMode, $curlConfigClosure)
528 {
529 Path::checkArg("path", $path);
530 WriteMode::checkArg("writeMode", $writeMode);
531 Checker::argCallable("curlConfigClosure", $curlConfigClosure);
532
533 $url = $this->buildUrlForGetOrPut(
534 $this->contentHost,
535 $this->appendFilePath("1/files_put", $path),
536 $writeMode->getExtraParams());
537
538 $curl = $this->mkCurl($url);
539
540 $curlConfigClosure($curl);
541
542 $curl->set(CURLOPT_RETURNTRANSFER, true);
543 $response = $curl->exec();
544
545 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
546
547 return RequestUtil::parseResponseJson($response->body);
548 }
549
550 /**
551 * Start a new chunked upload session and upload the first chunk of data.
552 *
553 * @param string $data
554 * The data to start off the chunked upload session.
555 *
556 * @return array
557 * A pair of <code>(string $uploadId, int $byteOffset)</code>. <code>$uploadId</code>
558 * is a unique identifier for this chunked upload session. You pass this in to
559 * {@link chunkedUploadContinue} and {@link chuunkedUploadFinish}. <code>$byteOffset</code>
560 * is the number of bytes that were successfully uploaded.
561 *
562 * @throws Exception
563 */
564 function chunkedUploadStart($data)
565 {
566 Checker::argString("data", $data);
567
568 $response = $this->_chunkedUpload(array(), $data);
569
570 if ($response->statusCode === 404) {
571 throw new Exception_BadResponse("Got a 404, but we didn't send up an 'session_id'");
572 }
573
574 $correction = self::_chunkedUploadCheckForOffsetCorrection($response);
575 if ($correction !== null) throw new Exception_BadResponse(
576 "Got an offset-correcting 400 response, but we didn't send an offset");
577
578 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
579
580 $uploadId = self::_chunkedUploadParse200Response($response->body);
581 $len = strlen($data);
582
583 return $uploadId;
584 }
585
586 /**
587 * Append another chunk data to a previously-started chunked upload session.
588 *
589 * @param string $uploadId
590 * The unique identifier for the chunked upload session. This is obtained via
591 * {@link chunkedUploadStart}.
592 *
593 * @param int $byteOffset
594 * The number of bytes you think you've already uploaded to the given chunked upload
595 * session. The server will append the new chunk of data after that point.
596 *
597 * @param string $data
598 * The data to append to the existing chunked upload session.
599 *
600 * @return int|bool
601 * If <code>false</code>, it means the server didn't know about the given
602 * <code>$uploadId</code>. This may be because the chunked upload session has expired
603 * (they last around 24 hours).
604 * If <code>true</code>, the chunk was successfully uploaded. If an integer, it means
605 * you and the server don't agree on the current <code>$byteOffset</code>. The returned
606 * integer is the server's internal byte offset for the chunked upload session. You need
607 * to adjust your input to match.
608 *
609 * @throws Exception
610 */
611 // function chunkedUploadContinue($uploadId, $byteOffset, $data)
612 // {
613 // Checker::argStringNonEmpty("uploadId", $uploadId);
614 // Checker::argNat("byteOffset", $byteOffset);
615 // Checker::argString("data", $data);
616
617 // $response = $this->_chunkedUpload(
618 // array("upload_id" => $uploadId, "offset" => $byteOffset), $data);
619
620 // if ($response->statusCode === 404) {
621 // // The server doesn't know our upload ID. Maybe it expired?
622 // return false;
623 // }
624
625 // $correction = self::_chunkedUploadCheckForOffsetCorrection($response);
626 // if ($correction !== null) {
627 // list($correctedUploadId, $correctedByteOffset) = $correction;
628 // if ($correctedUploadId !== $uploadId) throw new Exception_BadResponse(
629 // "Corrective 400 upload_id mismatch: us=".
630 // Util::q($uploadId)." server=".Util::q($correctedUploadId));
631 // if ($correctedByteOffset === $byteOffset) throw new Exception_BadResponse(
632 // "Corrective 400 offset is the same as ours: $byteOffset");
633 // return $correctedByteOffset;
634 // }
635
636 // if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
637 // list($retUploadId, $retByteOffset) = self::_chunkedUploadParse200Response($response->body);
638
639 // $nextByteOffset = $byteOffset + strlen($data);
640 // if ($uploadId !== $retUploadId) throw new Exception_BadResponse(
641 // "upload_id mismatch: us=".Util::q($uploadId) .", server=".Util::q($uploadId));
642 // if ($nextByteOffset !== $retByteOffset) throw new Exception_BadResponse(
643 // "next-offset mismatch: us=$nextByteOffset, server=$retByteOffset");
644
645 // return true;
646 // }
647
648 /*Dropbox api 2*/
649 function chunkedUploadContinue($uploadId, $byteOffset, $data)
650 {
651 Checker::argStringNonEmpty("session_id", $uploadId);
652 Checker::argNat("byteOffset", $byteOffset);
653 Checker::argString("data", $data);
654
655 $response = $this->_chunkedUploadContinue(
656 array(
657 "cursor" => array(
658 "session_id" => $uploadId,
659 "offset" => $byteOffset
660 ),
661 "close" => false
662 ),
663 $data
664 );
665
666 if ($response->statusCode === 404) {
667 // The server doesn't know our upload ID. Maybe it expired?
668 return false;
669 }
670
671 return true;
672 }
673
674 protected function _chunkedUploadContinue($params, $data)
675 {
676 $url = "https://content.dropboxapi.com/2/files/upload_session/append_v2";
677
678 $curl = $this->mkCurl($url);
679
680 $curl->set(CURLOPT_CUSTOMREQUEST, "POST");
681 $curl->set(CURLOPT_POSTFIELDS, $data);
682 $curl->addHeader("Content-Type: application/octet-stream");
683 $curl->addHeader("Dropbox-API-Arg: ".json_encode($params));
684 $curl->set(CURLOPT_RETURNTRANSFER, true);
685
686 return $curl->exec();
687 }
688
689 /**
690 * @param string $body
691 * @return array
692 */
693 private static function _chunkedUploadParse200Response($body)
694 {
695 $j = RequestUtil::parseResponseJson($body);
696 $uploadId = self::getField($j, "session_id");
697 return $uploadId;
698 }
699
700 /**
701 * @param HttpResponse $response
702 * @return array|null
703 */
704 private static function _chunkedUploadCheckForOffsetCorrection($response)
705 {
706 if ($response->statusCode !== 400) return null;
707 $j = json_decode($response->body, true, 10);
708 if ($j === null) return null;
709 if (!array_key_exists("session_id", $j)) return null;
710 $uploadId = $j["session_id"];
711 return $uploadId;
712 }
713
714 /**
715 * Creates a file on Dropbox using the accumulated contents of the given chunked upload session.
716 *
717 * See <a href="https://www.dropbox.com/developers/core/docs#commit-chunked-upload">/commit_chunked_upload</a>.
718 *
719 * @param string $uploadId
720 * The unique identifier for the chunked upload session. This is obtained via
721 * {@link chunkedUploadStart}.
722 *
723 * @param string $path
724 * The Dropbox path to save the file to ($path).
725 *
726 * @param WriteMode $writeMode
727 * What to do if there's already a file at the given path.
728 *
729 * @return array|null
730 * If <code>null</code>, it means the Dropbox server wasn't aware of the
731 * <code>$uploadId</code> you gave it.
732 * Otherwise, you get back the
733 * <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
734 * for the newly-created file.
735 *
736 * @throws Exception
737 */
738 // function chunkedUploadFinish($uploadId, $path, $writeMode)
739 // {
740 // Checker::argStringNonEmpty("uploadId", $uploadId);
741 // Path::checkArgNonRoot("path", $path);
742 // WriteMode::checkArg("writeMode", $writeMode);
743
744 // $params = array_merge(array("upload_id" => $uploadId), $writeMode->getExtraParams());
745
746 // $response = $this->doPost(
747 // $this->contentHost,
748 // $this->appendFilePath("1/commit_chunked_upload", $path),
749 // $params);
750
751 // if ($response->statusCode === 404) return null;
752 // if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
753
754 // return RequestUtil::parseResponseJson($response->body);
755 // }
756
757 /*Dropbox api 2*/
758 function chunkedUploadFinish($uploadId, $path, $offset)
759 {
760 Checker::argStringNonEmpty("session_id", $uploadId);
761 Path::checkArgNonRoot("path", $path);
762
763 $url = "https://content.dropboxapi.com/2/files/upload_session/finish";
764 $params = array(
765 "cursor" => array(
766 "session_id" => $uploadId,
767 "offset" => $offset
768 ),
769 "commit" => array(
770 "path" => $path,
771 "mode" => "add",
772 "autorename" => false,
773 "mute" => false
774 )
775 );
776
777 $curl = $this->mkCurl($url);
778
779 $curl->set(CURLOPT_CUSTOMREQUEST, "POST");
780 $curl->addHeader("Content-Type: application/octet-stream");
781 $curl->addHeader("Dropbox-API-Arg: ".json_encode($params));
782 $curl->set(CURLOPT_RETURNTRANSFER, true);
783
784 $response = $curl->exec();
785
786 if ($response->statusCode === 404) return null;
787 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
788
789 return RequestUtil::parseResponseJson($response->body);
790 }
791
792 /**
793 * @param array $params
794 * @param string $data
795 * @return HttpResponse
796 */
797 // protected function _chunkedUpload($params, $data)
798 // // Marked 'protected' so I can override it in testing.
799 // {
800 // $url = $this->buildUrlForGetOrPut(
801 // $this->contentHost, "1/chunked_upload", $params);
802
803 // $curl = $this->mkCurl($url);
804
805 // // We can't use CURLOPT_PUT because it wants a stream, but we already have $data in memory.
806 // $curl->set(CURLOPT_CUSTOMREQUEST, "PUT");
807 // $curl->set(CURLOPT_POSTFIELDS, $data);
808 // $curl->addHeader("Content-Type: application/octet-stream");
809
810 // $curl->set(CURLOPT_RETURNTRANSFER, true);
811 // return $curl->exec();
812 // }
813
814 /*Dropbox api 2*/
815 // Marked 'protected' so I can override it in testing.
816 protected function _chunkedUpload($params, $data)
817 {
818 $url = "https://content.dropboxapi.com/2/files/upload_session/start";
819
820 $curl = $this->mkCurl($url);
821
822 $curl->set(CURLOPT_CUSTOMREQUEST, "POST");
823 $curl->set(CURLOPT_POSTFIELDS, $data);
824 $curl->addHeader("Content-Type: application/octet-stream");
825
826 $curl->set(CURLOPT_RETURNTRANSFER, true);
827 $response = $curl->exec();
828
829 return $response;
830 }
831
832 /**
833 * Returns the metadata for whatever file or folder is at the given path.
834 *
835 * <code>
836 * $client = ...;
837 * $md = $client->getMetadata("/Photos/Frog.jpeg");
838 * print_r($md);
839 * </code>
840 *
841 * @param string $path
842 * The Dropbox path to a file or folder (UTF-8).
843 *
844 * @return array|null
845 * If there is a file or folder at the given path, you'll get back the
846 * <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
847 * for that file or folder. If not, you'll get back <code>null</code>.
848 *
849 * @throws Exception
850 */
851 function getMetadata($path)
852 {
853 Path::checkArg("path", $path);
854
855 return $this->_getMetadata($path, array("list" => "false"));
856 }
857
858 /**
859 * Returns the metadata for whatever file or folder is at the given path and, if it's a folder,
860 * also include the metadata for all the immediate children of that folder.
861 *
862 * <code>
863 * $client = ...;
864 * $md = $client->getMetadataWithChildren("/Photos");
865 * print_r($md);
866 * </code>
867 *
868 * @param string $path
869 * The Dropbox path to a file or folder (UTF-8).
870 *
871 * @return array|null
872 * If there is a file or folder at the given path, you'll get back the
873 * <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
874 * for that file or folder, along with all immediate children if it's a folder. If not,
875 * you'll get back <code>null</code>.
876 *
877 * @throws Exception
878 */
879 // function getMetadataWithChildren($path)
880 // {
881 // Path::checkArg("path", $path);
882
883 // return $this->_getMetadata($path, array("list" => "true", "file_limit" => "25000"));
884 // }
885
886 /*Dropbox api 2*/
887 function getMetadataWithChildren($path)
888 {
889 Path::checkArg("path", $path);
890
891 $params = array(
892 "path" => $path,
893 "recursive" => false,
894 "include_media_info" => false,
895 "include_deleted" => false,
896 "include_has_explicit_shared_members" => false
897 );
898 return $this->_getMetadata($params);
899 }
900
901 /**
902 * @param string $path
903 * @param array $params
904 * @return array
905 */
906 // private function _getMetadata($path, $params)
907 // {
908 // $response = $this->doGet(
909 // $this->apiHost,
910 // $this->appendFilePath("1/metadata", $path),
911 // $params);
912
913 // if ($response->statusCode === 404) return null;
914 // if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
915
916 // $metadata = RequestUtil::parseResponseJson($response->body);
917 // if (array_key_exists("is_deleted", $metadata) && $metadata["is_deleted"]) return null;
918 // return $metadata;
919 // }
920
921 /*Dropbox api 2*/
922 private function _getMetadata($params)
923 {
924 $response = $this->doPost($this->apiHost, "2/files/list_folder", $params, "Content-Type: application/json");
925
926 if ($response->statusCode === 404) return null;
927 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
928
929 $metadata = RequestUtil::parseResponseJson($response->body);
930 if (array_key_exists("is_deleted", $metadata) && $metadata["is_deleted"]) return null;
931 return $metadata;
932 }
933 /**
934 * If you've previously retrieved the metadata for a folder and its children, this method will
935 * retrieve updated metadata only if something has changed. This is more efficient than
936 * calling {@link getMetadataWithChildren} if you have a cache of previous results.
937 *
938 * <code>
939 * $client = ...;
940 * $md = $client->getMetadataWithChildren("/Photos");
941 * print_r($md);
942 * assert($md["is_dir"], "expecting \"/Photos\" to be a folder");
943 *
944 * sleep(10);
945 *
946 * // Now see if anything changed...
947 * list($changed, $new_md) = $client->getMetadataWithChildrenIfChanged(
948 * "/Photos", $md["hash"]);
949 * if ($changed) {
950 * echo "Folder changed.\n";
951 * print_r($new_md);
952 * } else {
953 * echo "Folder didn't change.\n";
954 * }
955 * </code>
956 *
957 * @param string $path
958 * The Dropbox path to a folder (UTF-8).
959 *
960 * @param string $previousFolderHash
961 * The "hash" field from the previously retrieved folder metadata.
962 *
963 * @return array
964 * A <code>list(boolean $changed, array $metadata)</code>. If the metadata hasn't changed,
965 * you'll get <code>list(false, null)</code>. If the metadata of the folder or any of its
966 * children has changed, you'll get <code>list(true, $newMetadata)</code>. $metadata is a
967 * <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>.
968 *
969 * @throws Exception
970 */
971 function getMetadataWithChildrenIfChanged($path, $previousFolderHash)
972 {
973 Path::checkArg("path", $path);
974 Checker::argStringNonEmpty("previousFolderHash", $previousFolderHash);
975
976 $params = array("list" => "true", "file_limit" => "25000", "hash" => $previousFolderHash);
977
978 $response = $this->doGet(
979 $this->apiHost,
980 $this->appendFilePath("1/metadata", $path),
981 $params);
982
983 if ($response->statusCode === 304) return array(false, null);
984 if ($response->statusCode === 404) return array(true, null);
985 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
986
987 $metadata = RequestUtil::parseResponseJson($response->body);
988 if (array_key_exists("is_deleted", $metadata) && $metadata["is_deleted"]) {
989 return array(true, null);
990 }
991 return array(true, $metadata);
992 }
993
994 /**
995 * A way of letting you keep up with changes to files and folders in a user's Dropbox.
996 *
997 * @param string|null $cursor
998 * If this is the first time you're calling this, pass in <code>null</code>. Otherwise,
999 * pass in whatever cursor was returned by the previous call.
1000 *
1001 * @param string|null $pathPrefix
1002 * If <code>null</code>, you'll get results for the entire folder (either the user's
1003 * entire Dropbox or your App Folder). If you set <code>$path_prefix</code> to
1004 * "/Photos/Vacation", you'll only get results for that path and any files and folders
1005 * under it.
1006 *
1007 * @return array
1008 * A <a href="https://www.dropbox.com/developers/core/docs#delta">delta page</a>, which
1009 * contains a list of changes to apply along with a new "cursor" that should be passed into
1010 * future <code>getDelta</code> calls. If the "reset" field is <code>true</code>, you
1011 * should clear your local state before applying the changes. If the "has_more" field is
1012 * <code>true</code>, call <code>getDelta</code> immediately to get more results, otherwise
1013 * wait a while (at least 5 minutes) before calling <code>getDelta</code> again.
1014 *
1015 * @throws Exception
1016 */
1017 function getDelta($cursor = null, $pathPrefix = null)
1018 {
1019 Checker::argStringNonEmptyOrNull("cursor", $cursor);
1020 Path::checkArgOrNull("pathPrefix", $pathPrefix);
1021
1022 $response = $this->doPost($this->apiHost, "1/delta", array(
1023 "cursor" => $cursor,
1024 "path_prefix" => $pathPrefix));
1025
1026 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1027
1028 return RequestUtil::parseResponseJson($response->body);
1029 }
1030
1031 /**
1032 * Gets the metadata for all the file revisions (up to a limit) for a given path.
1033 *
1034 * See <a href="https://www.dropbox.com/developers/core/docs#revisions">/revisions</a>.
1035 *
1036 * @param string path
1037 * The Dropbox path that you want file revision metadata for (UTF-8).
1038 *
1039 * @param int|null limit
1040 * The maximum number of revisions to return.
1041 *
1042 * @return array|null
1043 * A list of <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
1044 * objects</a>, one for each file revision. The later revisions appear first in the list.
1045 * If <code>null</code>, then there were too many revisions at that path.
1046 *
1047 * @throws Exception
1048 */
1049 function getRevisions($path, $limit = null)
1050 {
1051 Path::checkArgNonRoot("path", $path);
1052 Checker::argIntPositiveOrNull("limit", $limit);
1053
1054 $response = $this->doGet(
1055 $this->apiHost,
1056 $this->appendFilePath("1/revisions", $path),
1057 array("rev_limit" => $limit));
1058
1059 if ($response->statusCode === 406) return null;
1060 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1061
1062 return RequestUtil::parseResponseJson($response->body);
1063 }
1064
1065 /**
1066 * Takes a copy of the file at the given revision and saves it over the current copy. This
1067 * will create a new revision, but the file contents will match the revision you specified.
1068 *
1069 * See <a href="https://www.dropbox.com/developers/core/docs#restore">/restore</a>.
1070 *
1071 * @param string $path
1072 * The Dropbox path of the file to restore (UTF-8).
1073 *
1074 * @param string $rev
1075 * The revision to restore the contents to.
1076 *
1077 * @return mixed
1078 * The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
1079 * object</a>
1080 *
1081 * @throws Exception
1082 */
1083 function restoreFile($path, $rev)
1084 {
1085 Path::checkArgNonRoot("path", $path);
1086 Checker::argStringNonEmpty("rev", $rev);
1087
1088 $response = $this->doPost(
1089 $this->apiHost,
1090 $this->appendFilePath("1/restore", $path),
1091 array("rev" => $rev));
1092
1093 if ($response->statusCode === 404) return null;
1094 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1095
1096 return RequestUtil::parseResponseJson($response->body);
1097 }
1098
1099 /**
1100 * Returns metadata for all files and folders whose filename matches the query string.
1101 *
1102 * See <a href="https://www.dropbox.com/developers/core/docs#search">/search</a>.
1103 *
1104 * @param string $basePath
1105 * The path to limit the search to (UTF-8). Pass in "/" to search everything.
1106 *
1107 * @param string $query
1108 * A space-separated list of substrings to search for. A file matches only if it contains
1109 * all the substrings.
1110 *
1111 * @param int|null $limit
1112 * The maximum number of results to return.
1113 *
1114 * @param bool $includeDeleted
1115 * Whether to include deleted files in the results.
1116 *
1117 * @return mixed
1118 * A list of <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
1119 * objects</a> of files that match the search query.
1120 *
1121 * @throws Exception
1122 */
1123 // function searchFileNames($basePath, $query, $limit = null, $includeDeleted = false)
1124 // {
1125 // Path::checkArg("basePath", $basePath);
1126 // Checker::argStringNonEmpty("query", $query);
1127 // Checker::argNatOrNull("limit", $limit);
1128 // Checker::argBool("includeDeleted", $includeDeleted);
1129
1130 // $response = $this->doPost(
1131 // $this->apiHost,
1132 // $this->appendFilePath("1/search", $basePath),
1133 // array(
1134 // "query" => $query,
1135 // "file_limit" => $limit,
1136 // "include_deleted" => $includeDeleted,
1137 // ));
1138
1139 // if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1140
1141 // return RequestUtil::parseResponseJson($response->body);
1142 // }
1143
1144 /*Dropbox api 2*/
1145 function searchFileNames($basePath, $query, $limit = null, $includeDeleted = false)
1146 {
1147 Path::checkArg("basePath", $basePath);
1148 Checker::argStringNonEmpty("query", $query);
1149 Checker::argNatOrNull("limit", $limit);
1150 Checker::argBool("includeDeleted", $includeDeleted);
1151
1152 $response = $this->doPost(
1153 $this->apiHost,
1154 "2/files/search",
1155 array(
1156 "path" => $basePath,
1157 "query" => $query,
1158 "start" => 0,
1159 "max_results" => 200,
1160 "mode" => "filename"
1161 ),
1162 "Content-Type: application/json"
1163 );
1164
1165 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1166
1167 return RequestUtil::parseResponseJson($response->body);
1168 }
1169
1170 /**
1171 * Creates and returns a public link to a file or folder's "preview page". This link can be
1172 * used without authentication. The preview page may contain a thumbnail or some other
1173 * preview of the file, along with a download link to download the actual file.
1174 *
1175 * See <a href="https://www.dropbox.com/developers/core/docs#shares">/shares</a>.
1176 *
1177 * @param string $path
1178 * The Dropbox path to the file or folder you want to create a shareable link to (UTF-8).
1179 *
1180 * @return string
1181 * The URL of the preview page.
1182 *
1183 * @throws Exception
1184 */
1185 function createShareableLink($path)
1186 {
1187 Path::checkArg("path", $path);
1188
1189 $response = $this->doPost(
1190 $this->apiHost,
1191 $this->appendFilePath("1/shares", $path),
1192 array(
1193 "short_url" => "false",
1194 ));
1195
1196 if ($response->statusCode === 404) return null;
1197 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1198
1199 $j = RequestUtil::parseResponseJson($response->body);
1200 return self::getField($j, "url");
1201 }
1202
1203 /**
1204 * Creates and returns a direct link to a file. This link can be used without authentication.
1205 * This link will expire in a few hours.
1206 *
1207 * See <a href="https://www.dropbox.com/developers/core/docs#media">/media</a>.
1208 *
1209 * @param string $path
1210 * The Dropbox path to a file or folder (UTF-8).
1211 *
1212 * @return array
1213 * A <code>list(string $url, \DateTime $expires)</code> where <code>$url</code> is a direct
1214 * link to the requested file and <code>$expires</code> is a standard PHP
1215 * <code>\DateTime</code> representing when <code>$url</code> will stop working.
1216 *
1217 * @throws Exception
1218 */
1219 function createTemporaryDirectLink($path)
1220 {
1221 Path::checkArgNonRoot("path", $path);
1222
1223 $response = $this->doPost(
1224 $this->apiHost,
1225 $this->appendFilePath("1/media", $path));
1226
1227 if ($response->statusCode === 404) return null;
1228 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1229
1230 $j = RequestUtil::parseResponseJson($response->body);
1231 $url = self::getField($j, "url");
1232 $expires = self::parseDateTime(self::getField($j, "expires"));
1233 return array($url, $expires);
1234 }
1235
1236 /**
1237 * Creates and returns a "copy ref" to a file. A copy ref can be used to copy a file across
1238 * different Dropbox accounts without downloading and re-uploading.
1239 *
1240 * For example: Create a <code>Client</code> using the access token from one account and call
1241 * <code>createCopyRef</code>. Then, create a <code>Client</code> using the access token for
1242 * another account and call <code>copyFromCopyRef</code> using the copy ref. (You need to use
1243 * the same app key both times.)
1244 *
1245 * See <a href="https://www.dropbox.com/developers/core/docs#copy_ref">/copy_ref</a>.
1246 *
1247 * @param string path
1248 * The Dropbox path of the file or folder you want to create a copy ref for (UTF-8).
1249 *
1250 * @return string
1251 * The copy ref (just a string that you keep track of).
1252 *
1253 * @throws Exception
1254 */
1255 function createCopyRef($path)
1256 {
1257 Path::checkArg("path", $path);
1258
1259 $response = $this->doGet(
1260 $this->apiHost,
1261 $this->appendFilePath("1/copy_ref", $path));
1262
1263 if ($response->statusCode === 404) return null;
1264 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1265
1266 $j = RequestUtil::parseResponseJson($response->body);
1267 return self::getField($j, "copy_ref");
1268 }
1269
1270 /**
1271 * Gets a thumbnail image representation of the file at the given path.
1272 *
1273 * See <a href="https://www.dropbox.com/developers/core/docs#thumbnails">/thumbnails</a>.
1274 *
1275 * @param string $path
1276 * The path to the file you want a thumbnail for (UTF-8).
1277 *
1278 * @param string $format
1279 * One of the two image formats: "jpeg" or "png".
1280 *
1281 * @param string $size
1282 * One of the predefined image size names, as a string:
1283 * <ul>
1284 * <li>"xs" - 32x32</li>
1285 * <li>"s" - 64x64</li>
1286 * <li>"m" - 128x128</li>
1287 * <li>"l" - 640x480</li>
1288 * <li>"xl" - 1024x768</li>
1289 * </ul>
1290 *
1291 * @return array|null
1292 * If the file exists, you'll get <code>list(array $metadata, string $data)</code> where
1293 * <code>$metadata</code> is the file's
1294 * <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
1295 * and $data is the raw data for the thumbnail image. If the file doesn't exist, you'll
1296 * get <code>null</code>.
1297 *
1298 * @throws Exception
1299 */
1300 function getThumbnail($path, $format, $size)
1301 {
1302 Path::checkArgNonRoot("path", $path);
1303 Checker::argString("format", $format);
1304 Checker::argString("size", $size);
1305 if (!in_array($format, array("jpeg", "png"))) {
1306 throw new \InvalidArgumentException("Invalid 'format': ".Util::q($format));
1307 }
1308 if (!in_array($size, array("xs", "s", "m", "l", "xl"))) {
1309 throw new \InvalidArgumentException("Invalid 'size': ".Util::q($format));
1310 }
1311
1312 $url = $this->buildUrlForGetOrPut(
1313 $this->contentHost,
1314 $this->appendFilePath("1/thumbnails", $path),
1315 array("size" => $size, "format" => $format));
1316
1317 $curl = $this->mkCurl($url);
1318 $metadataCatcher = new DropboxMetadataHeaderCatcher($curl->handle);
1319
1320 $curl->set(CURLOPT_RETURNTRANSFER, true);
1321 $response = $curl->exec();
1322
1323 if ($response->statusCode === 404) return null;
1324 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1325
1326 $metadata = $metadataCatcher->getMetadata();
1327 return array($metadata, $response->body);
1328 }
1329
1330 /**
1331 * Copies a file or folder to a new location
1332 *
1333 * See <a href="https://www.dropbox.com/developers/core/docs#fileops-copy">/fileops/copy</a>.
1334 *
1335 * @param string $fromPath
1336 * The Dropbox path of the file or folder you want to copy (UTF-8).
1337 *
1338 * @param string $toPath
1339 * The destination Dropbox path (UTF-8).
1340 *
1341 * @return mixed
1342 * The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
1343 * object</a> for the new file or folder.
1344 *
1345 * @throws Exception
1346 */
1347 function copy($fromPath, $toPath)
1348 {
1349 Path::checkArg("fromPath", $fromPath);
1350 Path::checkArgNonRoot("toPath", $toPath);
1351
1352 $response = $this->doPost(
1353 $this->apiHost,
1354 "1/fileops/copy",
1355 array(
1356 "root" => "auto",
1357 "from_path" => $fromPath,
1358 "to_path" => $toPath,
1359 ));
1360
1361 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1362
1363 return RequestUtil::parseResponseJson($response->body);
1364 }
1365
1366 /**
1367 * Creates a file or folder based on an existing copy ref (possibly from a different Dropbox
1368 * account).
1369 *
1370 * See <a href="https://www.dropbox.com/developers/core/docs#fileops-copy">/fileops/copy</a>.
1371 *
1372 * @param string $copyRef
1373 * A copy ref obtained via the {@link createCopyRef()} call.
1374 *
1375 * @param string $toPath
1376 * The Dropbox path you want to copy the file or folder to (UTF-8).
1377 *
1378 * @return mixed
1379 * The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
1380 * object</a> for the new file or folder.
1381 *
1382 * @throws Exception
1383 */
1384 function copyFromCopyRef($copyRef, $toPath)
1385 {
1386 Checker::argStringNonEmpty("copyRef", $copyRef);
1387 Path::checkArgNonRoot("toPath", $toPath);
1388
1389 $response = $this->doPost(
1390 $this->apiHost,
1391 "1/fileops/copy",
1392 array(
1393 "root" => "auto",
1394 "from_copy_ref" => $copyRef,
1395 "to_path" => $toPath,
1396 )
1397 );
1398
1399 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1400
1401 return RequestUtil::parseResponseJson($response->body);
1402 }
1403
1404 /**
1405 * Creates a folder.
1406 *
1407 * See <a href="https://www.dropbox.com/developers/core/docs#fileops-create-folder">/fileops/create_folder</a>.
1408 *
1409 * @param string $path
1410 * The Dropbox path at which to create the folder (UTF-8).
1411 *
1412 * @return array|null
1413 * If successful, you'll get back the
1414 * <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
1415 * for the newly-created folder. If not successful, you'll get <code>null</code>.
1416 *
1417 * @throws Exception
1418 */
1419 // function createFolder($path)
1420 // {
1421 // Path::checkArgNonRoot("path", $path);
1422
1423 // $response = $this->doPost(
1424 // $this->apiHost,
1425 // "1/fileops/create_folder",
1426 // array(
1427 // "root" => "auto",
1428 // "path" => $path,
1429 // ));
1430
1431 // if ($response->statusCode === 403) return null;
1432 // if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1433
1434 // return RequestUtil::parseResponseJson($response->body);
1435 // }
1436
1437 /*Dropbox api 2*/
1438 function createFolder($path)
1439 {
1440 Path::checkArgNonRoot("path", $path);
1441
1442 $response = $this->doPost(
1443 $this->apiHost,
1444 "2/files/create_folder",
1445 array(
1446 "autorename" => false,
1447 "path" => $path,
1448 ),
1449 "Content-Type: application/json"
1450 );
1451
1452 if ($response->statusCode == 409) {
1453 return;
1454 }
1455
1456 if ($response->statusCode === 403) return null;
1457 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1458
1459 return RequestUtil::parseResponseJson($response->body);
1460 }
1461 /**
1462 * Deletes a file or folder
1463 *
1464 * See <a href="https://www.dropbox.com/developers/core/docs#fileops-delete">/fileops/delete</a>.
1465 *
1466 * @param string $path
1467 * The Dropbox path of the file or folder to delete (UTF-8).
1468 *
1469 * @return mixed
1470 * The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
1471 * object</a> for the deleted file or folder.
1472 *
1473 * @throws Exception
1474 */
1475 // function delete($path)
1476 // {
1477 // Path::checkArgNonRoot("path", $path);
1478
1479 // $response = $this->doPost(
1480 // $this->apiHost,
1481 // "1/fileops/delete",
1482 // array(
1483 // "root" => "auto",
1484 // "path" => $path,
1485 // ));
1486
1487 // if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1488
1489 // return RequestUtil::parseResponseJson($response->body);
1490 // }
1491
1492 /*Dropbox api 2*/
1493 function delete($path)
1494 {
1495 Path::checkArgNonRoot("path", $path);
1496
1497 $response = $this->doPost(
1498 $this->apiHost,
1499 "2/files/delete",
1500 array(
1501 "path" => $path
1502 ),
1503 "Content-Type: application/json"
1504 );
1505
1506 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1507
1508 return RequestUtil::parseResponseJson($response->body);
1509 }
1510
1511 /**
1512 * Moves a file or folder to a new location.
1513 *
1514 * See <a href="https://www.dropbox.com/developers/core/docs#fileops-move">/fileops/move</a>.
1515 *
1516 * @param string $fromPath
1517 * The source Dropbox path (UTF-8).
1518 *
1519 * @param string $toPath
1520 * The destination Dropbox path (UTF-8).
1521 *
1522 * @return mixed
1523 * The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
1524 * object</a> for the destination file or folder.
1525 *
1526 * @throws Exception
1527 */
1528 function move($fromPath, $toPath)
1529 {
1530 Path::checkArgNonRoot("fromPath", $fromPath);
1531 Path::checkArgNonRoot("toPath", $toPath);
1532
1533 $response = $this->doPost(
1534 $this->apiHost,
1535 "1/fileops/move",
1536 array(
1537 "root" => "auto",
1538 "from_path" => $fromPath,
1539 "to_path" => $toPath,
1540 ));
1541
1542 if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1543
1544 return RequestUtil::parseResponseJson($response->body);
1545 }
1546
1547 /**
1548 * Build a URL for making a GET or PUT request. Will add the "locale"
1549 * parameter.
1550 *
1551 * @param string $host
1552 * Either the "API" or "API content" hostname from {@link getHost()}.
1553 * @param string $path
1554 * The "path" part of the URL. For example, "/account/info".
1555 * @param array|null $params
1556 * URL parameters. For POST requests, do not put the parameters here.
1557 * Include them in the request body instead.
1558 *
1559 * @return string
1560 */
1561 function buildUrlForGetOrPut($host, $path, $params = null)
1562 {
1563 return RequestUtil::buildUrlForGetOrPut($this->userLocale, $host, $path, $params);
1564 }
1565
1566 /**
1567 * Perform an OAuth-2-authorized GET request to the Dropbox API. Will automatically
1568 * fill in "User-Agent" and "locale" as well.
1569 *
1570 * @param string $host
1571 * Either the "API" or "API content" hostname from {@link getHost()}.
1572 * @param string $path
1573 * The "path" part of the URL. For example, "/account/info".
1574 * @param array|null $params
1575 * GET parameters.
1576 * @return HttpResponse
1577 *
1578 * @throws Exception
1579 */
1580 function doGet($host, $path, $params = null)
1581 {
1582 Checker::argString("host", $host);
1583 Checker::argString("path", $path);
1584 return RequestUtil::doGet($this->clientIdentifier, $this->accessToken, $this->userLocale,
1585 $host, $path, $params);
1586 }
1587
1588 /**
1589 * Perform an OAuth-2-authorized POST request to the Dropbox API. Will automatically
1590 * fill in "User-Agent" and "locale" as well.
1591 *
1592 * @param string $host
1593 * Either the "API" or "API content" hostname from {@link getHost()}.
1594 * @param string $path
1595 * The "path" part of the URL. For example, "/commit_chunked_upload".
1596 * @param array|null $params
1597 * POST parameters.
1598 * @return HttpResponse
1599 *
1600 * @throws Exception
1601 */
1602 function doPost($host, $path, $params = null, $headers = null)
1603 {
1604 Checker::argString("host", $host);
1605 Checker::argString("path", $path);
1606 return RequestUtil::doPost($this->clientIdentifier, $this->accessToken, $this->userLocale, $host, $path, $params, $headers);
1607 }
1608
1609 /**
1610 * Create a {@link Curl} object that is pre-configured with {@link getClientIdentifier()},
1611 * and the proper OAuth 2 "Authorization" header.
1612 *
1613 * @param string $url
1614 * Generate this URL using {@link buildUrl()}.
1615 *
1616 * @return Curl
1617 */
1618 function mkCurl($url)
1619 {
1620 return RequestUtil::mkCurlWithOAuth($this->clientIdentifier, $url, $this->accessToken);
1621 }
1622
1623 /**
1624 * Parses date/time strings returned by the Dropbox API. The Dropbox API returns date/times
1625 * formatted like: <code>"Sat, 21 Aug 2010 22:31:20 +0000"</code>.
1626 *
1627 * @param string $apiDateTimeString
1628 * A date/time string returned by the API.
1629 *
1630 * @return \DateTime
1631 * A standard PHP <code>\DateTime</code> instance.
1632 *
1633 * @throws Exception_BadResponse
1634 * Thrown if <code>$apiDateTimeString</code> isn't correctly formatted.
1635 */
1636 static function parseDateTime($apiDateTimeString)
1637 {
1638 $dt = \DateTime::createFromFormat(self::$dateTimeFormat, $apiDateTimeString);
1639 if ($dt === false) throw new Exception_BadResponse(
1640 "Bad date/time from server: ".Util::q($apiDateTimeString));
1641 return $dt;
1642 }
1643
1644 private static $dateTimeFormat = "D, d M Y H:i:s T";
1645
1646 /**
1647 * @internal
1648 */
1649 static function getField($j, $fieldName)
1650 {
1651 if (!array_key_exists($fieldName, $j)) throw new Exception_BadResponse(
1652 "missing field \"$fieldName\" in ".Util::q($j));
1653 return $j[$fieldName];
1654 }
1655
1656 /**
1657 * Given an OAuth 2 access token, returns <code>null</code> if it is well-formed (though
1658 * not necessarily valid). Otherwise, returns a string describing what's wrong with it.
1659 *
1660 * @param string $s
1661 *
1662 * @return string
1663 */
1664 static function getAccessTokenError($s)
1665 {
1666 if ($s === null) return "can't be null";
1667 if (strlen($s) === 0) return "can't be empty";
1668 if (preg_match('@[^-=_~/A-Za-z0-9\.\+]@', $s) === 1) return "contains invalid character";
1669 return null;
1670 }
1671
1672 /**
1673 * @internal
1674 */
1675 static function checkAccessTokenArg($argName, $accessToken)
1676 {
1677 $error = self::getAccessTokenError($accessToken);
1678 if ($error !== null) throw new \InvalidArgumentException("'$argName' invalid: $error");
1679 }
1680
1681 /**
1682 * @internal
1683 */
1684 static function getClientIdentifierError($s)
1685 {
1686 if ($s === null) return "can't be null";
1687 if (strlen($s) === 0) return "can't be empty";
1688 if (preg_match('@[\x00-\x1f\x7f]@', $s) === 1) return "contains control character";
1689 return null;
1690 }
1691
1692 /**
1693 * @internal
1694 */
1695 static function checkClientIdentifierArg($argName, $accessToken)
1696 {
1697 $error = self::getClientIdentifierError($accessToken);
1698 if ($error !== null) throw new \InvalidArgumentException("'$argName' invalid: $error");
1699 }
1700 }
1701