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.
Completed
Pull Request — master (#21)
by
unknown
03:27
created

Client::listSharedLinks()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 3
1
<?php
2
3
namespace Spatie\Dropbox;
4
5
use Exception;
6
use GuzzleHttp\Psr7\StreamWrapper;
7
use GuzzleHttp\Client as GuzzleClient;
8
use Psr\Http\Message\ResponseInterface;
9
use GuzzleHttp\Exception\ClientException;
10
use Spatie\Dropbox\Exceptions\BadRequest;
11
12
class Client
13
{
14
    const THUMBNAIL_FORMAT_JPEG = 'jpeg';
15
    const THUMBNAIL_FORMAT_PNG = 'png';
16
17
    const THUMBNAIL_SIZE_XS = 'w32h32';
18
    const THUMBNAIL_SIZE_S = 'w64h64';
19
    const THUMBNAIL_SIZE_M = 'w128h128';
20
    const THUMBNAIL_SIZE_L = 'w640h480';
21
    const THUMBNAIL_SIZE_XL = 'w1024h768';
22
23
    /** @var string */
24
    protected $accessToken;
25
26
    /** @var \GuzzleHttp\Client */
27
    protected $client;
28
29
    public function __construct(string $accessToken, GuzzleClient $client = null)
30
    {
31
        $this->accessToken = $accessToken;
32
33
        $this->client = $client ?? new GuzzleClient([
34
                'headers' => [
35
                    'Authorization' => "Bearer {$this->accessToken}",
36
                ],
37
            ]);
38
    }
39
40
    /**
41
     * Copy a file or folder to a different location in the user's Dropbox.
42
     *
43
     * If the source path is a folder all its contents will be copied.
44
     *
45
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-copy
46
     */
47 View Code Duplication
    public function copy(string $fromPath, string $toPath): array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
48
    {
49
        $parameters = [
50
            'from_path' => $this->normalizePath($fromPath),
51
            'to_path' => $this->normalizePath($toPath),
52
        ];
53
54
        return $this->rpcEndpointRequest('files/copy', $parameters);
55
    }
56
57
    /**
58
     * Create a folder at a given path.
59
     *
60
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-create_folder
61
     */
62
    public function createFolder(string $path): array
63
    {
64
        $parameters = [
65
            'path' => $this->normalizePath($path),
66
        ];
67
68
        $object = $this->rpcEndpointRequest('files/create_folder', $parameters);
69
70
        $object['.tag'] = 'folder';
71
72
        return $object;
73
    }
74
75
    /**
76
     * Create a shared link with custom settings.
77
     *
78
     * If no settings are given then the default visibility is RequestedVisibility.public.
79
     * The resolved visibility, though, may depend on other aspects such as team and
80
     * shared folder settings). Only for paid users.
81
     *
82
     * @link https://www.dropbox.com/developers/documentation/http/documentation#sharing-create_shared_link_with_settings
83
     */
84 View Code Duplication
    public function createSharedLinkWithSettings(string $path, array $settings = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
85
    {
86
        $parameters = [
87
            'path' => $this->normalizePath($path),
88
            'settings' => $settings,
89
        ];
90
91
        return $this->rpcEndpointRequest('sharing/create_shared_link_with_settings', $parameters);
92
    }
93
94
    /**
95
     * List shared links.
96
     *
97
     * For empty path returns a list of all shared links. For non-empty path
98
     * returns a list of all shared links with access to the given path.
99
     *
100
     * If direct_only is set true, only direct links to the path will be returned, otherwise
101
     * it may return link to the path itself and parent folders as described on docs.
102
     *
103
     * @link https://www.dropbox.com/developers/documentation/http/documentation#sharing-list_shared_links
104
     */
105
    public function listSharedLinks(string $path = null, bool $direct_only = false, string $cursor = null): array
106
    {
107
        $parameters = [
108
            'path' => $path ? $this->normalizePath($path) : null,
109
            'cursor' => $cursor,
110
            'direct_only' => $direct_only,
111
        ];
112
113
        $body = $this->rpcEndpointRequest('sharing/list_shared_links', $parameters);
114
115
        return $body['links'];
116
    }
117
118
    /**
119
     * Delete the file or folder at a given path.
120
     *
121
     * If the path is a folder, all its contents will be deleted too.
122
     * A successful response indicates that the file or folder was deleted.
123
     *
124
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-delete
125
     */
126
    public function delete(string $path): array
127
    {
128
        $parameters = [
129
            'path' => $this->normalizePath($path),
130
        ];
131
132
        return $this->rpcEndpointRequest('files/delete', $parameters);
133
    }
134
135
    /**
136
     * Download a file from a user's Dropbox.
137
     *
138
     * @param string $path
139
     *
140
     * @return resource
141
     *
142
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-download
143
     */
144
    public function download(string $path)
145
    {
146
        $arguments = [
147
            'path' => $this->normalizePath($path),
148
        ];
149
150
        $response = $this->contentEndpointRequest('files/download', $arguments);
151
152
        return StreamWrapper::getResource($response->getBody());
153
    }
154
155
    /**
156
     * Returns the metadata for a file or folder.
157
     *
158
     * Note: Metadata for the root folder is unsupported.
159
     *
160
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_metadata
161
     */
162
    public function getMetadata(string $path): array
163
    {
164
        $parameters = [
165
            'path' => $this->normalizePath($path),
166
        ];
167
168
        return $this->rpcEndpointRequest('files/get_metadata', $parameters);
169
    }
170
171
    /**
172
     * Get a temporary link to stream content of a file.
173
     *
174
     * This link will expire in four hours and afterwards you will get 410 Gone.
175
     * Content-Type of the link is determined automatically by the file's mime type.
176
     *
177
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_temporary_link
178
     */
179 View Code Duplication
    public function getTemporaryLink(string $path): string
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
180
    {
181
        $parameters = [
182
            'path' => $this->normalizePath($path),
183
        ];
184
185
        $body = $this->rpcEndpointRequest('files/get_temporary_link', $parameters);
186
187
        return $body['link'];
188
    }
189
190
    /**
191
     * Get a thumbnail for an image.
192
     *
193
     * This method currently supports files with the following file extensions:
194
     * jpg, jpeg, png, tiff, tif, gif and bmp.
195
     *
196
     * Photos that are larger than 20MB in size won't be converted to a thumbnail.
197
     *
198
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_thumbnail
199
     */
200
    public function getThumbnail(string $path, string $format = 'jpeg', string $size = 'w64h64'): string
201
    {
202
        $arguments = [
203
            'path' => $this->normalizePath($path),
204
            'format' => $format,
205
            'size' => $size,
206
        ];
207
208
        $response = $this->contentEndpointRequest('files/get_thumbnail', $arguments);
209
210
        return (string) $response->getBody();
211
    }
212
213
    /**
214
     * Starts returning the contents of a folder.
215
     *
216
     * If the result's ListFolderResult.has_more field is true, call
217
     * list_folder/continue with the returned ListFolderResult.cursor to retrieve more entries.
218
     *
219
     * Note: auth.RateLimitError may be returned if multiple list_folder or list_folder/continue calls
220
     * with same parameters are made simultaneously by same API app for same user. If your app implements
221
     * retry logic, please hold off the retry until the previous request finishes.
222
     *
223
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder
224
     */
225 View Code Duplication
    public function listFolder(string $path = '', bool $recursive = false): array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
226
    {
227
        $parameters = [
228
            'path' => $this->normalizePath($path),
229
            'recursive' => $recursive,
230
        ];
231
232
        return $this->rpcEndpointRequest('files/list_folder', $parameters);
233
    }
234
235
    /**
236
     * Once a cursor has been retrieved from list_folder, use this to paginate through all files and
237
     * retrieve updates to the folder, following the same rules as documented for list_folder.
238
     *
239
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder-continue
240
     */
241
    public function listFolderContinue(string $cursor = ''): array
242
    {
243
        return $this->rpcEndpointRequest('files/list_folder/continue', compact('cursor'));
244
    }
245
246
    /**
247
     * Move a file or folder to a different location in the user's Dropbox.
248
     *
249
     * If the source path is a folder all its contents will be moved.
250
     *
251
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-move
252
     */
253 View Code Duplication
    public function move(string $fromPath, string $toPath): array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
254
    {
255
        $parameters = [
256
            'from_path' => $this->normalizePath($fromPath),
257
            'to_path' => $this->normalizePath($toPath),
258
        ];
259
260
        return $this->rpcEndpointRequest('files/move', $parameters);
261
    }
262
263
    /**
264
     * Create a new file with the contents provided in the request.
265
     *
266
     * Do not use this to upload a file larger than 150 MB. Instead, create an upload session with upload_session/start.
267
     *
268
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload
269
     *
270
     * @param string $path
271
     * @param string|resource $contents
272
     * @param string|array $mode
273
     *
274
     * @return array
275
     */
276
    public function upload(string $path, $contents, $mode = 'add'): array
277
    {
278
        $arguments = [
279
            'path' => $this->normalizePath($path),
280
            'mode' => $mode,
281
        ];
282
283
        $response = $this->contentEndpointRequest('files/upload', $arguments, $contents);
284
285
        $metadata = json_decode($response->getBody(), true);
286
287
        $metadata['.tag'] = 'file';
288
289
        return $metadata;
290
    }
291
292
    /**
293
     * Upload file split in chunks. This allows uploading large files, since
294
     * Dropbox API v2 limits the content size to 150MB.
295
     *
296
     * The chunk size will affect directly the memory usage, so be careful.
297
     * Large chunks tends to speed up the upload, while smaller optimizes memory usage.
298
     *
299
     * @param string          $path
300
     * @param string|resource $contents
301
     * @param int             $chunkSize
302
     * @param string          $mode
303
     *
304
     * @return array
305
     */
306
    public function uploadChunked(string $path, $contents, int $chunkSize, $mode = 'add'): array
307
    {
308
        $stream = $contents;
309
310
        // This method relies on resources, so we need to convert strings to resource
311
        if (is_string($contents)) {
312
            $stream = fopen('php://memory', 'r+');
313
            fwrite($stream, $contents);
314
            rewind($stream);
315
        }
316
317
        $data = self::readChunk($stream, $chunkSize);
0 ignored issues
show
Bug introduced by
It seems like $stream defined by $contents on line 308 can also be of type string; however, Spatie\Dropbox\Client::readChunk() does only seem to accept resource, 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...
318
        $cursor = null;
319
320
        while (! ((strlen($data) < $chunkSize) || feof($stream))) {
321
            // Start upload session on first iteration, then just append on subsequent iterations
322
            $cursor = isset($cursor) ? $this->uploadSessionAppend($data, $cursor) : $this->uploadSessionStart($data);
323
            $data = self::readChunk($stream, $chunkSize);
0 ignored issues
show
Bug introduced by
It seems like $stream defined by $contents on line 308 can also be of type string; however, Spatie\Dropbox\Client::readChunk() does only seem to accept resource, 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...
324
        }
325
326
        // If there's no cursor here, our stream is small enough to a single request
327
        if (! isset($cursor)) {
328
            $cursor = $this->uploadSessionStart($data);
329
            $data = '';
330
        }
331
332
        return $this->uploadSessionFinish($data, $cursor, $path, $mode);
333
    }
334
335
    /**
336
     * Upload sessions allow you to upload a single file in one or more requests,
337
     * for example where the size of the file is greater than 150 MB.
338
     * This call starts a new upload session with the given data.
339
     *
340
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-start
341
     *
342
     * @param string $contents
343
     * @param bool   $close
344
     *
345
     * @return UploadSessionCursor
346
     */
347
    public function uploadSessionStart($contents, bool $close = false): UploadSessionCursor
348
    {
349
        $arguments = compact('close');
350
351
        $response = json_decode(
352
            $this->contentEndpointRequest('files/upload_session/start', $arguments, $contents)->getBody(),
353
            true
354
        );
355
356
        return new UploadSessionCursor($response['session_id'], strlen($contents));
357
    }
358
359
    /**
360
     * Append more data to an upload session.
361
     * When the parameter close is set, this call will close the session.
362
     * A single request should not upload more than 150 MB.
363
     *
364
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-append_v2
365
     *
366
     * @param string              $contents
367
     * @param UploadSessionCursor $cursor
368
     * @param bool                $close
369
     *
370
     * @return \Spatie\Dropbox\UploadSessionCursor
371
     */
372
    public function uploadSessionAppend($contents, UploadSessionCursor $cursor, bool $close = false): UploadSessionCursor
373
    {
374
        $arguments = compact('cursor', 'close');
375
376
        $this->contentEndpointRequest('files/upload_session/append_v2', $arguments, $contents);
377
378
        $cursor->offset += strlen($contents);
379
380
        return $cursor;
381
    }
382
383
    /**
384
     * Finish an upload session and save the uploaded data to the given file path.
385
     * A single request should not upload more than 150 MB.
386
     *
387
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-finish
388
     *
389
     * @param string                              $contents
390
     * @param \Spatie\Dropbox\UploadSessionCursor $cursor
391
     * @param string                              $path
392
     * @param string|array                        $mode
393
     * @param bool                                $autorename
394
     * @param bool                                $mute
395
     *
396
     * @return array
397
     */
398
    public function uploadSessionFinish($contents, UploadSessionCursor $cursor, string $path, $mode = 'add', $autorename = false, $mute = false): array
399
    {
400
        $arguments = compact('cursor');
401
        $arguments['commit'] = compact('path', 'mode', 'autorename', 'mute');
402
403
        $response = $this->contentEndpointRequest(
404
            'files/upload_session/finish',
405
            $arguments,
406
            ($contents == '') ? null : $contents
407
        );
408
409
        return json_decode($response->getBody(), true);
410
    }
411
412
    /**
413
     * Sometimes fread() returns less than the request number of bytes (for example, when reading
414
     * from network streams).  This function repeatedly calls fread until the requested number of
415
     * bytes have been read or we've reached EOF.
416
     *
417
     * @param resource $stream
418
     * @param int      $chunkSize
419
     *
420
     * @throws Exception
421
     * @return string
422
     */
423
    protected static function readChunk($stream, int $chunkSize)
424
    {
425
        $chunk = '';
426
        while (! feof($stream) && $chunkSize > 0) {
427
            $part = fread($stream, $chunkSize);
428
            if ($part === false) {
429
                throw new Exception('Error reading from $stream.');
430
            }
431
            $chunk .= $part;
432
            $chunkSize -= strlen($part);
433
        }
434
435
        return $chunk;
436
    }
437
438
    /**
439
     * Get Account Info for current authenticated user.
440
     *
441
     * @link https://www.dropbox.com/developers/documentation/http/documentation#users-get_current_account
442
     *
443
     * @return array
444
     */
445
    public function getAccountInfo(): array
446
    {
447
        return $this->rpcEndpointRequest('users/get_current_account');
448
    }
449
450
    /**
451
     * Revoke current access token.
452
     *
453
     * @link https://www.dropbox.com/developers/documentation/http/documentation#auth-token-revoke
454
     */
455
    public function revokeToken()
456
    {
457
        $this->rpcEndpointRequest('auth/token/revoke');
458
    }
459
460
    protected function normalizePath(string $path): string
461
    {
462
        $path = trim($path, '/');
463
464
        return ($path === '') ? '' : '/'.$path;
465
    }
466
467
    /**
468
     * @param string $endpoint
469
     * @param array $arguments
470
     * @param string|resource $body
471
     *
472
     * @return \Psr\Http\Message\ResponseInterface
473
     *
474
     * @throws \Exception
475
     */
476
    public function contentEndpointRequest(string $endpoint, array $arguments, $body = ''): ResponseInterface
477
    {
478
        $headers['Dropbox-API-Arg'] = json_encode($arguments);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$headers was never initialized. Although not strictly required by PHP, it is generally a good practice to add $headers = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
479
480
        if ($body !== '') {
481
            $headers['Content-Type'] = 'application/octet-stream';
482
        }
483
484
        try {
485
            $response = $this->client->post("https://content.dropboxapi.com/2/{$endpoint}", [
486
                'headers' => $headers,
487
                'body' => $body,
488
            ]);
489
        } catch (ClientException $exception) {
490
            throw $this->determineException($exception);
491
        }
492
493
        return $response;
494
    }
495
496
    public function rpcEndpointRequest(string $endpoint, array $parameters = null): array
497
    {
498
        try {
499
            $options = [];
500
501
            if ($parameters) {
502
                $options['json'] = $parameters;
503
            }
504
505
            $response = $this->client->post("https://api.dropboxapi.com/2/{$endpoint}", $options);
506
        } catch (ClientException $exception) {
507
            throw $this->determineException($exception);
508
        }
509
510
        $response = json_decode($response->getBody(), true);
511
512
        return $response ?? [];
513
    }
514
515
    protected function determineException(ClientException $exception): Exception
516
    {
517
        if (in_array($exception->getResponse()->getStatusCode(), [400, 409])) {
518
            return new BadRequest($exception->getResponse());
0 ignored issues
show
Bug introduced by
It seems like $exception->getResponse() can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
519
        }
520
521
        return $exception;
522
    }
523
}
524