GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Client::getTemporaryLink()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Spatie\Dropbox;
4
5
use Exception;
6
use GrahamCampbell\GuzzleFactory\GuzzleFactory;
7
use GuzzleHttp\Client as GuzzleClient;
8
use GuzzleHttp\Exception\ClientException;
9
use GuzzleHttp\Exception\RequestException;
10
use GuzzleHttp\Psr7;
11
use GuzzleHttp\Psr7\PumpStream;
12
use GuzzleHttp\Psr7\StreamWrapper;
13
use Psr\Http\Message\ResponseInterface;
14
use Psr\Http\Message\StreamInterface;
15
use Spatie\Dropbox\Exceptions\BadRequest;
16
17
class Client
18
{
19
    const THUMBNAIL_FORMAT_JPEG = 'jpeg';
20
    const THUMBNAIL_FORMAT_PNG = 'png';
21
22
    const THUMBNAIL_SIZE_XS = 'w32h32';
23
    const THUMBNAIL_SIZE_S = 'w64h64';
24
    const THUMBNAIL_SIZE_M = 'w128h128';
25
    const THUMBNAIL_SIZE_L = 'w640h480';
26
    const THUMBNAIL_SIZE_XL = 'w1024h768';
27
28
    const MAX_CHUNK_SIZE = 1024 * 1024 * 150;
29
30
    const UPLOAD_SESSION_START = 0;
31
    const UPLOAD_SESSION_APPEND = 1;
32
33
    /** @var string */
34
    protected $accessToken;
35
36
    /** @var string */
37
    protected $teamMemberId;
38
39
    /** @var string */
40
    protected $appKey;
41
42
    /** @var string */
43
    protected $appSecret;
44
45
    /** @var \GuzzleHttp\Client */
46
    protected $client;
47
48
    /** @var int */
49
    protected $maxChunkSize;
50
51
    /** @var int */
52
    protected $maxUploadChunkRetries;
53
54
    /**
55
     * @param string|array|null $accessTokenOrAppCredentials
56
     * @param GuzzleClient|null $client
57
     * @param int $maxChunkSize Set max chunk size per request (determines when to switch from "one shot upload" to upload session and defines chunk size for uploads via session).
58
     * @param int $maxUploadChunkRetries How many times to retry an upload session start or append after RequestException.
59
     * @param string $teamMemberID The team member ID to be specified for Dropbox business accounts
0 ignored issues
show
Documentation introduced by
There is no parameter named $teamMemberID. Did you maybe mean $teamMemberId?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
60
     */
61
    public function __construct($accessTokenOrAppCredentials = null, GuzzleClient $client = null, int $maxChunkSize = self::MAX_CHUNK_SIZE, int $maxUploadChunkRetries = 0, string $teamMemberId = null)
62
    {
63
        if (is_array($accessTokenOrAppCredentials)) {
64
            [$this->appKey, $this->appSecret] = $accessTokenOrAppCredentials;
65
        }
66
        if (is_string($accessTokenOrAppCredentials)) {
67
            $this->accessToken = $accessTokenOrAppCredentials;
68
        }
69
70
        if ($teamMemberId !== null) {
71
            $this->teamMemberId = $teamMemberId;
72
        }
73
74
        $this->client = $client ?? new GuzzleClient(['handler' => GuzzleFactory::handler()]);
75
76
        $this->maxChunkSize = ($maxChunkSize < self::MAX_CHUNK_SIZE ? ($maxChunkSize > 1 ? $maxChunkSize : 1) : self::MAX_CHUNK_SIZE);
77
        $this->maxUploadChunkRetries = $maxUploadChunkRetries;
78
    }
79
80
    /**
81
     * Copy a file or folder to a different location in the user's Dropbox.
82
     *
83
     * If the source path is a folder all its contents will be copied.
84
     *
85
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-copy_v2
86
     */
87
    public function copy(string $fromPath, string $toPath): array
88
    {
89
        $parameters = [
90
            'from_path' => $this->normalizePath($fromPath),
91
            'to_path' => $this->normalizePath($toPath),
92
        ];
93
94
        return $this->rpcEndpointRequest('files/copy_v2', $parameters);
95
    }
96
97
    /**
98
     * Create a folder at a given path.
99
     *
100
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-create_folder
101
     */
102
    public function createFolder(string $path): array
103
    {
104
        $parameters = [
105
            'path' => $this->normalizePath($path),
106
        ];
107
108
        $object = $this->rpcEndpointRequest('files/create_folder', $parameters);
109
110
        $object['.tag'] = 'folder';
111
112
        return $object;
113
    }
114
115
    /**
116
     * Create a shared link with custom settings.
117
     *
118
     * If no settings are given then the default visibility is RequestedVisibility.public.
119
     * The resolved visibility, though, may depend on other aspects such as team and
120
     * shared folder settings). Only for paid users.
121
     *
122
     * @link https://www.dropbox.com/developers/documentation/http/documentation#sharing-create_shared_link_with_settings
123
     */
124
    public function createSharedLinkWithSettings(string $path, array $settings = []): array
125
    {
126
        $parameters = [
127
            'path' => $this->normalizePath($path),
128
        ];
129
130
        if (count($settings)) {
131
            $parameters = array_merge(compact('settings'), $parameters);
132
        }
133
134
        return $this->rpcEndpointRequest('sharing/create_shared_link_with_settings', $parameters);
135
    }
136
137
    /**
138
     * Search a file or folder in the user's Dropbox.
139
     *
140
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-search
141
     */
142
    public function search(string $query, bool $includeHighlights = false): array
143
    {
144
        $parameters = [
145
            'query' => $query,
146
            'include_highlights' => $includeHighlights,
147
        ];
148
149
        return $this->rpcEndpointRequest('files/search_v2', $parameters);
150
    }
151
152
    /**
153
     * List shared links.
154
     *
155
     * For empty path returns a list of all shared links. For non-empty path
156
     * returns a list of all shared links with access to the given path.
157
     *
158
     * If direct_only is set true, only direct links to the path will be returned, otherwise
159
     * it may return link to the path itself and parent folders as described on docs.
160
     *
161
     * @link https://www.dropbox.com/developers/documentation/http/documentation#sharing-list_shared_links
162
     */
163
    public function listSharedLinks(string $path = null, bool $direct_only = false, string $cursor = null): array
164
    {
165
        $parameters = [
166
            'path' => $path ? $this->normalizePath($path) : null,
167
            'cursor' => $cursor,
168
            'direct_only' => $direct_only,
169
        ];
170
171
        $body = $this->rpcEndpointRequest('sharing/list_shared_links', $parameters);
172
173
        return $body['links'];
174
    }
175
176
    /**
177
     * Delete the file or folder at a given path.
178
     *
179
     * If the path is a folder, all its contents will be deleted too.
180
     * A successful response indicates that the file or folder was deleted.
181
     *
182
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-delete
183
     */
184
    public function delete(string $path): array
185
    {
186
        $parameters = [
187
            'path' => $this->normalizePath($path),
188
        ];
189
190
        return $this->rpcEndpointRequest('files/delete', $parameters);
191
    }
192
193
    /**
194
     * Download a file from a user's Dropbox.
195
     *
196
     * @param string $path
197
     *
198
     * @return resource
199
     *
200
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-download
201
     */
202
    public function download(string $path)
203
    {
204
        $arguments = [
205
            'path' => $this->normalizePath($path),
206
        ];
207
208
        $response = $this->contentEndpointRequest('files/download', $arguments);
209
210
        return StreamWrapper::getResource($response->getBody());
211
    }
212
213
    /**
214
     * Download a folder from the user's Dropbox, as a zip file.
215
     * The folder must be less than 20 GB in size and have fewer than 10,000 total files.
216
     * The input cannot be a single file. Any single file must be less than 4GB in size.
217
     *
218
     * @param string $path
219
     *
220
     * @return resource
221
     *
222
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-download_zip
223
     */
224
    public function downloadZip(string $path)
225
    {
226
        $arguments = [
227
            'path' => $this->normalizePath($path),
228
        ];
229
230
        $response = $this->contentEndpointRequest('files/download_zip', $arguments);
231
232
        return StreamWrapper::getResource($response->getBody());
233
    }
234
235
    /**
236
     * Returns the metadata for a file or folder.
237
     *
238
     * Note: Metadata for the root folder is unsupported.
239
     *
240
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_metadata
241
     */
242
    public function getMetadata(string $path): array
243
    {
244
        $parameters = [
245
            'path' => $this->normalizePath($path),
246
        ];
247
248
        return $this->rpcEndpointRequest('files/get_metadata', $parameters);
249
    }
250
251
    /**
252
     * Get a temporary link to stream content of a file.
253
     *
254
     * This link will expire in four hours and afterwards you will get 410 Gone.
255
     * Content-Type of the link is determined automatically by the file's mime type.
256
     *
257
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_temporary_link
258
     */
259
    public function getTemporaryLink(string $path): string
260
    {
261
        $parameters = [
262
            'path' => $this->normalizePath($path),
263
        ];
264
265
        $body = $this->rpcEndpointRequest('files/get_temporary_link', $parameters);
266
267
        return $body['link'];
268
    }
269
270
    /**
271
     * Get a thumbnail for an image.
272
     *
273
     * This method currently supports files with the following file extensions:
274
     * jpg, jpeg, png, tiff, tif, gif and bmp.
275
     *
276
     * Photos that are larger than 20MB in size won't be converted to a thumbnail.
277
     *
278
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_thumbnail
279
     */
280
    public function getThumbnail(string $path, string $format = 'jpeg', string $size = 'w64h64'): string
281
    {
282
        $arguments = [
283
            'path' => $this->normalizePath($path),
284
            'format' => $format,
285
            'size' => $size,
286
        ];
287
288
        $response = $this->contentEndpointRequest('files/get_thumbnail', $arguments);
289
290
        return (string) $response->getBody();
291
    }
292
293
    /**
294
     * Starts returning the contents of a folder.
295
     *
296
     * If the result's ListFolderResult.has_more field is true, call
297
     * list_folder/continue with the returned ListFolderResult.cursor to retrieve more entries.
298
     *
299
     * Note: auth.RateLimitError may be returned if multiple list_folder or list_folder/continue calls
300
     * with same parameters are made simultaneously by same API app for same user. If your app implements
301
     * retry logic, please hold off the retry until the previous request finishes.
302
     *
303
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder
304
     */
305
    public function listFolder(string $path = '', bool $recursive = false): array
306
    {
307
        $parameters = [
308
            'path' => $this->normalizePath($path),
309
            'recursive' => $recursive,
310
        ];
311
312
        return $this->rpcEndpointRequest('files/list_folder', $parameters);
313
    }
314
315
    /**
316
     * Once a cursor has been retrieved from list_folder, use this to paginate through all files and
317
     * retrieve updates to the folder, following the same rules as documented for list_folder.
318
     *
319
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder-continue
320
     */
321
    public function listFolderContinue(string $cursor = ''): array
322
    {
323
        return $this->rpcEndpointRequest('files/list_folder/continue', compact('cursor'));
324
    }
325
326
    /**
327
     * Move a file or folder to a different location in the user's Dropbox.
328
     *
329
     * If the source path is a folder all its contents will be moved.
330
     *
331
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-move_v2
332
     */
333
    public function move(string $fromPath, string $toPath): array
334
    {
335
        $parameters = [
336
            'from_path' => $this->normalizePath($fromPath),
337
            'to_path' => $this->normalizePath($toPath),
338
        ];
339
340
        return $this->rpcEndpointRequest('files/move_v2', $parameters);
341
    }
342
343
    /**
344
     * The file should be uploaded in chunks if it size exceeds the 150 MB threshold
345
     * or if the resource size could not be determined (eg. a popen() stream).
346
     *
347
     * @param string|resource $contents
348
     *
349
     * @return bool
350
     */
351
    protected function shouldUploadChunked($contents): bool
352
    {
353
        $size = is_string($contents) ? strlen($contents) : fstat($contents)['size'];
354
355
        if ($this->isPipe($contents)) {
356
            return true;
357
        }
358
359
        if ($size === null) {
360
            return true;
361
        }
362
363
        return $size > $this->maxChunkSize;
364
    }
365
366
    /**
367
     * Check if the contents is a pipe stream (not seekable, no size defined).
368
     *
369
     * @param string|resource $contents
370
     *
371
     * @return bool
372
     */
373
    protected function isPipe($contents): bool
374
    {
375
        return is_resource($contents) ? (fstat($contents)['mode'] & 010000) != 0 : false;
376
    }
377
378
    /**
379
     * Create a new file with the contents provided in the request.
380
     *
381
     * Do not use this to upload a file larger than 150 MB. Instead, create an upload session with upload_session/start.
382
     *
383
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload
384
     *
385
     * @param string $path
386
     * @param string|resource $contents
387
     * @param string $mode
388
     *
389
     * @return array
390
     */
391
    public function upload(string $path, $contents, $mode = 'add'): array
392
    {
393
        if ($this->shouldUploadChunked($contents)) {
394
            return $this->uploadChunked($path, $contents, $mode);
395
        }
396
397
        $arguments = [
398
            'path' => $this->normalizePath($path),
399
            'mode' => $mode,
400
        ];
401
402
        $response = $this->contentEndpointRequest('files/upload', $arguments, $contents);
403
404
        $metadata = json_decode($response->getBody(), true);
405
406
        $metadata['.tag'] = 'file';
407
408
        return $metadata;
409
    }
410
411
    /**
412
     * Upload file split in chunks. This allows uploading large files, since
413
     * Dropbox API v2 limits the content size to 150MB.
414
     *
415
     * The chunk size will affect directly the memory usage, so be careful.
416
     * Large chunks tends to speed up the upload, while smaller optimizes memory usage.
417
     *
418
     * @param string $path
419
     * @param string|resource $contents
420
     * @param string $mode
421
     * @param int|null $chunkSize
422
     *
423
     * @return array
424
     */
425
    public function uploadChunked(string $path, $contents, $mode = 'add', $chunkSize = null): array
426
    {
427
        if ($chunkSize === null || $chunkSize > $this->maxChunkSize) {
428
            $chunkSize = $this->maxChunkSize;
429
        }
430
431
        $stream = $this->getStream($contents);
432
433
        $cursor = $this->uploadChunk(self::UPLOAD_SESSION_START, $stream, $chunkSize, null);
0 ignored issues
show
Bug introduced by
It seems like $stream defined by $this->getStream($contents) on line 431 can also be of type object<GuzzleHttp\Psr7\PumpStream>; however, Spatie\Dropbox\Client::uploadChunk() does only seem to accept object<GuzzleHttp\Psr7\Stream>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
434
435
        while (! $stream->eof()) {
436
            $cursor = $this->uploadChunk(self::UPLOAD_SESSION_APPEND, $stream, $chunkSize, $cursor);
437
        }
438
439
        return $this->uploadSessionFinish('', $cursor, $path, $mode);
440
    }
441
442
    /**
443
     * @param int $type
444
     * @param Psr7\Stream $stream
445
     * @param int $chunkSize
446
     * @param \Spatie\Dropbox\UploadSessionCursor|null $cursor
447
     * @return \Spatie\Dropbox\UploadSessionCursor
448
     * @throws Exception
449
     */
450
    protected function uploadChunk($type, &$stream, $chunkSize, $cursor = null): UploadSessionCursor
451
    {
452
        $maximumTries = $stream->isSeekable() ? $this->maxUploadChunkRetries : 0;
453
        $pos = $stream->tell();
454
455
        $tries = 0;
456
457
        tryUpload:
458
        try {
459
            $tries++;
460
461
            $chunkStream = new Psr7\LimitStream($stream, $chunkSize, $stream->tell());
462
463
            if ($type === self::UPLOAD_SESSION_START) {
464
                return $this->uploadSessionStart($chunkStream);
465
            }
466
467
            if ($type === self::UPLOAD_SESSION_APPEND && $cursor !== null) {
468
                return $this->uploadSessionAppend($chunkStream, $cursor);
469
            }
470
471
            throw new Exception('Invalid type');
472
        } catch (RequestException $exception) {
473
            if ($tries < $maximumTries) {
474
                // rewind
475
                $stream->seek($pos, SEEK_SET);
476
                goto tryUpload;
477
            }
478
            throw $exception;
479
        }
480
    }
481
482
    /**
483
     * Upload sessions allow you to upload a single file in one or more requests,
484
     * for example where the size of the file is greater than 150 MB.
485
     * This call starts a new upload session with the given data.
486
     *
487
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-start
488
     *
489
     * @param string|StreamInterface $contents
490
     * @param bool $close
491
     *
492
     * @return UploadSessionCursor
493
     */
494
    public function uploadSessionStart($contents, bool $close = false): UploadSessionCursor
495
    {
496
        $arguments = compact('close');
497
498
        $response = json_decode(
499
            $this->contentEndpointRequest('files/upload_session/start', $arguments, $contents)->getBody(),
500
            true
501
        );
502
503
        return new UploadSessionCursor($response['session_id'], ($contents instanceof StreamInterface ? $contents->tell() : strlen($contents)));
504
    }
505
506
    /**
507
     * Append more data to an upload session.
508
     * When the parameter close is set, this call will close the session.
509
     * A single request should not upload more than 150 MB.
510
     *
511
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-append_v2
512
     *
513
     * @param string|StreamInterface $contents
514
     * @param UploadSessionCursor $cursor
515
     * @param bool $close
516
     *
517
     * @return \Spatie\Dropbox\UploadSessionCursor
518
     */
519
    public function uploadSessionAppend($contents, UploadSessionCursor $cursor, bool $close = false): UploadSessionCursor
520
    {
521
        $arguments = compact('cursor', 'close');
522
523
        $pos = $contents instanceof StreamInterface ? $contents->tell() : 0;
524
        $this->contentEndpointRequest('files/upload_session/append_v2', $arguments, $contents);
525
526
        $cursor->offset += $contents instanceof StreamInterface ? ($contents->tell() - $pos) : strlen($contents);
527
528
        return $cursor;
529
    }
530
531
    /**
532
     * Finish an upload session and save the uploaded data to the given file path.
533
     * A single request should not upload more than 150 MB.
534
     *
535
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-finish
536
     *
537
     * @param string|StreamInterface $contents
538
     * @param \Spatie\Dropbox\UploadSessionCursor $cursor
539
     * @param string $path
540
     * @param string|array $mode
541
     * @param bool $autorename
542
     * @param bool $mute
543
     *
544
     * @return array
545
     */
546
    public function uploadSessionFinish($contents, UploadSessionCursor $cursor, string $path, $mode = 'add', $autorename = false, $mute = false): array
547
    {
548
        $arguments = compact('cursor');
549
        $arguments['commit'] = compact('path', 'mode', 'autorename', 'mute');
550
551
        $response = $this->contentEndpointRequest(
552
            'files/upload_session/finish',
553
            $arguments,
554
            ($contents == '') ? null : $contents
555
        );
556
557
        $metadata = json_decode($response->getBody(), true);
558
559
        $metadata['.tag'] = 'file';
560
561
        return $metadata;
562
    }
563
564
    /**
565
     * Get Account Info for current authenticated user.
566
     *
567
     * @link https://www.dropbox.com/developers/documentation/http/documentation#users-get_current_account
568
     *
569
     * @return array
570
     */
571
    public function getAccountInfo(): array
572
    {
573
        return $this->rpcEndpointRequest('users/get_current_account');
574
    }
575
576
    /**
577
     * Revoke current access token.
578
     *
579
     * @link https://www.dropbox.com/developers/documentation/http/documentation#auth-token-revoke
580
     */
581
    public function revokeToken(): void
582
    {
583
        $this->rpcEndpointRequest('auth/token/revoke');
584
    }
585
586
    protected function normalizePath(string $path): string
587
    {
588
        if (preg_match("/^id:.*|^rev:.*|^(ns:[0-9]+(\/.*)?)/", $path) === 1) {
589
            return $path;
590
        }
591
592
        $path = trim($path, '/');
593
594
        return ($path === '') ? '' : '/'.$path;
595
    }
596
597
    protected function getEndpointUrl(string $subdomain, string $endpoint): string
598
    {
599
        if (count($parts = explode('::', $endpoint)) === 2) {
600
            [$subdomain, $endpoint] = $parts;
601
        }
602
603
        return "https://{$subdomain}.dropboxapi.com/2/{$endpoint}";
604
    }
605
606
    /**
607
     * @param string $endpoint
608
     * @param array $arguments
609
     * @param string|resource|StreamInterface $body
610
     *
611
     * @return \Psr\Http\Message\ResponseInterface
612
     *
613
     * @throws \Exception
614
     */
615
    public function contentEndpointRequest(string $endpoint, array $arguments, $body = ''): ResponseInterface
616
    {
617
        $headers = ['Dropbox-API-Arg' => json_encode($arguments)];
618
619
        if ($body !== '') {
620
            $headers['Content-Type'] = 'application/octet-stream';
621
        }
622
623
        try {
624
            $response = $this->client->post($this->getEndpointUrl('content', $endpoint), [
625
                'headers' => $this->getHeaders($headers),
626
                'body' => $body,
627
            ]);
628
        } catch (ClientException $exception) {
629
            throw $this->determineException($exception);
630
        }
631
632
        return $response;
633
    }
634
635
    public function rpcEndpointRequest(string $endpoint, array $parameters = null): array
636
    {
637
        try {
638
            $options = ['headers' => $this->getHeaders()];
639
640
            if ($parameters) {
641
                $options['json'] = $parameters;
642
            }
643
644
            $response = $this->client->post($this->getEndpointUrl('api', $endpoint), $options);
645
        } catch (ClientException $exception) {
646
            throw $this->determineException($exception);
647
        }
648
649
        $response = json_decode($response->getBody(), true);
650
651
        return $response ?? [];
652
    }
653
654
    protected function determineException(ClientException $exception): Exception
655
    {
656
        if (in_array($exception->getResponse()->getStatusCode(), [400, 409])) {
657
            return new BadRequest($exception->getResponse());
658
        }
659
660
        return $exception;
661
    }
662
663
    /**
664
     * @param $contents
665
     *
666
     * @return \GuzzleHttp\Psr7\PumpStream|\GuzzleHttp\Psr7\Stream
667
     */
668
    protected function getStream($contents)
669
    {
670
        if ($this->isPipe($contents)) {
671
            /* @var resource $contents */
672
            return new PumpStream(function ($length) use ($contents) {
673
                $data = fread($contents, $length);
674
                if (strlen($data) === 0) {
675
                    return false;
676
                }
677
678
                return $data;
679
            });
680
        }
681
682
        return Psr7\stream_for($contents);
683
    }
684
685
    /**
686
     * Get the access token.
687
     */
688
    public function getAccessToken(): string
689
    {
690
        return $this->accessToken;
691
    }
692
693
    /**
694
     * Set the access token.
695
     */
696
    public function setAccessToken(string $accessToken): self
697
    {
698
        $this->accessToken = $accessToken;
699
700
        return $this;
701
    }
702
703
    /**
704
     * Get the HTTP headers.
705
     */
706
    protected function getHeaders(array $headers = []): array
707
    {
708
        $auth = [];
709
        if ($this->accessToken || ($this->appKey && $this->appSecret)) {
710
            $auth = $this->accessToken ? $this->getHeadersForBearerToken() : $this->getHeadersForCredentials();
711
        }
712
713
        if ($this->teamMemberId) {
714
            $auth = array_merge(
715
                $auth,
716
                [
717
                    'Dropbox-API-Select-User' => $this->teamMemberId,
718
                ]
719
            );
720
        }
721
722
        return array_merge($auth, $headers);
723
    }
724
725
    /**
726
     * @return array
727
     */
728
    protected function getHeadersForBearerToken()
729
    {
730
        return [
731
            'Authorization' => "Bearer {$this->accessToken}",
732
        ];
733
    }
734
735
    /**
736
     * @return array
737
     */
738
    protected function getHeadersForCredentials()
739
    {
740
        return [
741
            'Authorization' => 'Basic '.base64_encode("{$this->appKey}:{$this->appSecret}"),
742
        ];
743
    }
744
}
745