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
Push — master ( f59937...fd804a )
by Freek
25s queued 11s
created

Client::downloadZip()   A

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 \GuzzleHttp\Client */
37
    protected $client;
38
39
    /** @var int */
40
    protected $maxChunkSize;
41
42
    /** @var int */
43
    protected $maxUploadChunkRetries;
44
45
    /**
46
     * @param string $accessToken
47
     * @param GuzzleClient|null $client
48
     * @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).
49
     * @param int $maxUploadChunkRetries How many times to retry an upload session start or append after RequestException.
50
     */
51
    public function __construct(string $accessToken, GuzzleClient $client = null, int $maxChunkSize = self::MAX_CHUNK_SIZE, int $maxUploadChunkRetries = 0)
52
    {
53
        $this->accessToken = $accessToken;
54
55
        $this->client = $client ?? new GuzzleClient(['handler' => GuzzleFactory::handler()]);
56
57
        $this->maxChunkSize = ($maxChunkSize < self::MAX_CHUNK_SIZE ? ($maxChunkSize > 1 ? $maxChunkSize : 1) : self::MAX_CHUNK_SIZE);
58
        $this->maxUploadChunkRetries = $maxUploadChunkRetries;
59
    }
60
61
    /**
62
     * Copy a file or folder to a different location in the user's Dropbox.
63
     *
64
     * If the source path is a folder all its contents will be copied.
65
     *
66
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-copy_v2
67
     */
68
    public function copy(string $fromPath, string $toPath): array
69
    {
70
        $parameters = [
71
            'from_path' => $this->normalizePath($fromPath),
72
            'to_path' => $this->normalizePath($toPath),
73
        ];
74
75
        return $this->rpcEndpointRequest('files/copy_v2', $parameters);
76
    }
77
78
    /**
79
     * Create a folder at a given path.
80
     *
81
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-create_folder
82
     */
83
    public function createFolder(string $path): array
84
    {
85
        $parameters = [
86
            'path' => $this->normalizePath($path),
87
        ];
88
89
        $object = $this->rpcEndpointRequest('files/create_folder', $parameters);
90
91
        $object['.tag'] = 'folder';
92
93
        return $object;
94
    }
95
96
    /**
97
     * Create a shared link with custom settings.
98
     *
99
     * If no settings are given then the default visibility is RequestedVisibility.public.
100
     * The resolved visibility, though, may depend on other aspects such as team and
101
     * shared folder settings). Only for paid users.
102
     *
103
     * @link https://www.dropbox.com/developers/documentation/http/documentation#sharing-create_shared_link_with_settings
104
     */
105
    public function createSharedLinkWithSettings(string $path, array $settings = [])
106
    {
107
        $parameters = [
108
            'path' => $this->normalizePath($path),
109
        ];
110
111
        if (count($settings)) {
112
            $parameters = array_merge(compact('settings'), $parameters);
113
        }
114
115
        return $this->rpcEndpointRequest('sharing/create_shared_link_with_settings', $parameters);
116
    }
117
118
    /**
119
     * Search a file or folder in the user's Dropbox.
120
     *
121
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-search
122
     */
123
    public function search(string $query, bool $includeHighlights = false)
124
    {
125
        $parameters = [
126
            'query' => $query,
127
            'include_highlights' => $includeHighlights,
128
        ];
129
130
        return $this->rpcEndpointRequest('files/search_v2', $parameters);
131
    }
132
133
    /**
134
     * List shared links.
135
     *
136
     * For empty path returns a list of all shared links. For non-empty path
137
     * returns a list of all shared links with access to the given path.
138
     *
139
     * If direct_only is set true, only direct links to the path will be returned, otherwise
140
     * it may return link to the path itself and parent folders as described on docs.
141
     *
142
     * @link https://www.dropbox.com/developers/documentation/http/documentation#sharing-list_shared_links
143
     */
144
    public function listSharedLinks(string $path = null, bool $direct_only = false, string $cursor = null): array
145
    {
146
        $parameters = [
147
            'path' => $path ? $this->normalizePath($path) : null,
148
            'cursor' => $cursor,
149
            'direct_only' => $direct_only,
150
        ];
151
152
        $body = $this->rpcEndpointRequest('sharing/list_shared_links', $parameters);
153
154
        return $body['links'];
155
    }
156
157
    /**
158
     * Delete the file or folder at a given path.
159
     *
160
     * If the path is a folder, all its contents will be deleted too.
161
     * A successful response indicates that the file or folder was deleted.
162
     *
163
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-delete
164
     */
165
    public function delete(string $path): array
166
    {
167
        $parameters = [
168
            'path' => $this->normalizePath($path),
169
        ];
170
171
        return $this->rpcEndpointRequest('files/delete', $parameters);
172
    }
173
174
    /**
175
     * Download a file from a user's Dropbox.
176
     *
177
     * @param string $path
178
     *
179
     * @return resource
180
     *
181
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-download
182
     */
183
    public function download(string $path)
184
    {
185
        $arguments = [
186
            'path' => $this->normalizePath($path),
187
        ];
188
189
        $response = $this->contentEndpointRequest('files/download', $arguments);
190
191
        return StreamWrapper::getResource($response->getBody());
192
    }
193
194
    /**
195
     * Download a folder from the user's Dropbox, as a zip file.
196
     * The folder must be less than 20 GB in size and have fewer than 10,000 total files.
197
     * The input cannot be a single file. Any single file must be less than 4GB in size.
198
     *
199
     * @param string $path
200
     *
201
     * @return resource
202
     *
203
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-download_zip
204
     */
205
    public function downloadZip(string $path)
206
    {
207
        $arguments = [
208
            'path' => $this->normalizePath($path),
209
        ];
210
211
        $response = $this->contentEndpointRequest('files/download_zip', $arguments);
212
213
        return StreamWrapper::getResource($response->getBody());
214
    }
215
216
    /**
217
     * Returns the metadata for a file or folder.
218
     *
219
     * Note: Metadata for the root folder is unsupported.
220
     *
221
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_metadata
222
     */
223
    public function getMetadata(string $path): array
224
    {
225
        $parameters = [
226
            'path' => $this->normalizePath($path),
227
        ];
228
229
        return $this->rpcEndpointRequest('files/get_metadata', $parameters);
230
    }
231
232
    /**
233
     * Get a temporary link to stream content of a file.
234
     *
235
     * This link will expire in four hours and afterwards you will get 410 Gone.
236
     * Content-Type of the link is determined automatically by the file's mime type.
237
     *
238
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_temporary_link
239
     */
240
    public function getTemporaryLink(string $path): string
241
    {
242
        $parameters = [
243
            'path' => $this->normalizePath($path),
244
        ];
245
246
        $body = $this->rpcEndpointRequest('files/get_temporary_link', $parameters);
247
248
        return $body['link'];
249
    }
250
251
    /**
252
     * Get a thumbnail for an image.
253
     *
254
     * This method currently supports files with the following file extensions:
255
     * jpg, jpeg, png, tiff, tif, gif and bmp.
256
     *
257
     * Photos that are larger than 20MB in size won't be converted to a thumbnail.
258
     *
259
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_thumbnail
260
     */
261
    public function getThumbnail(string $path, string $format = 'jpeg', string $size = 'w64h64'): string
262
    {
263
        $arguments = [
264
            'path' => $this->normalizePath($path),
265
            'format' => $format,
266
            'size' => $size,
267
        ];
268
269
        $response = $this->contentEndpointRequest('files/get_thumbnail', $arguments);
270
271
        return (string) $response->getBody();
272
    }
273
274
    /**
275
     * Starts returning the contents of a folder.
276
     *
277
     * If the result's ListFolderResult.has_more field is true, call
278
     * list_folder/continue with the returned ListFolderResult.cursor to retrieve more entries.
279
     *
280
     * Note: auth.RateLimitError may be returned if multiple list_folder or list_folder/continue calls
281
     * with same parameters are made simultaneously by same API app for same user. If your app implements
282
     * retry logic, please hold off the retry until the previous request finishes.
283
     *
284
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder
285
     */
286
    public function listFolder(string $path = '', bool $recursive = false): array
287
    {
288
        $parameters = [
289
            'path' => $this->normalizePath($path),
290
            'recursive' => $recursive,
291
        ];
292
293
        return $this->rpcEndpointRequest('files/list_folder', $parameters);
294
    }
295
296
    /**
297
     * Once a cursor has been retrieved from list_folder, use this to paginate through all files and
298
     * retrieve updates to the folder, following the same rules as documented for list_folder.
299
     *
300
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder-continue
301
     */
302
    public function listFolderContinue(string $cursor = ''): array
303
    {
304
        return $this->rpcEndpointRequest('files/list_folder/continue', compact('cursor'));
305
    }
306
307
    /**
308
     * Move a file or folder to a different location in the user's Dropbox.
309
     *
310
     * If the source path is a folder all its contents will be moved.
311
     *
312
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-move_v2
313
     */
314
    public function move(string $fromPath, string $toPath): array
315
    {
316
        $parameters = [
317
            'from_path' => $this->normalizePath($fromPath),
318
            'to_path' => $this->normalizePath($toPath),
319
        ];
320
321
        return $this->rpcEndpointRequest('files/move_v2', $parameters);
322
    }
323
324
    /**
325
     * The file should be uploaded in chunks if it size exceeds the 150 MB threshold
326
     * or if the resource size could not be determined (eg. a popen() stream).
327
     *
328
     * @param string|resource $contents
329
     *
330
     * @return bool
331
     */
332
    protected function shouldUploadChunked($contents): bool
333
    {
334
        $size = is_string($contents) ? strlen($contents) : fstat($contents)['size'];
335
336
        if ($this->isPipe($contents)) {
337
            return true;
338
        }
339
340
        if ($size === null) {
341
            return true;
342
        }
343
344
        return $size > $this->maxChunkSize;
345
    }
346
347
    /**
348
     * Check if the contents is a pipe stream (not seekable, no size defined).
349
     *
350
     * @param string|resource $contents
351
     *
352
     * @return bool
353
     */
354
    protected function isPipe($contents): bool
355
    {
356
        return is_resource($contents) ? (fstat($contents)['mode'] & 010000) != 0 : false;
357
    }
358
359
    /**
360
     * Create a new file with the contents provided in the request.
361
     *
362
     * Do not use this to upload a file larger than 150 MB. Instead, create an upload session with upload_session/start.
363
     *
364
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload
365
     *
366
     * @param string $path
367
     * @param string|resource $contents
368
     * @param string $mode
369
     *
370
     * @return array
371
     */
372
    public function upload(string $path, $contents, $mode = 'add'): array
373
    {
374
        if ($this->shouldUploadChunked($contents)) {
375
            return $this->uploadChunked($path, $contents, $mode);
376
        }
377
378
        $arguments = [
379
            'path' => $this->normalizePath($path),
380
            'mode' => $mode,
381
        ];
382
383
        $response = $this->contentEndpointRequest('files/upload', $arguments, $contents);
384
385
        $metadata = json_decode($response->getBody(), true);
386
387
        $metadata['.tag'] = 'file';
388
389
        return $metadata;
390
    }
391
392
    /**
393
     * Upload file split in chunks. This allows uploading large files, since
394
     * Dropbox API v2 limits the content size to 150MB.
395
     *
396
     * The chunk size will affect directly the memory usage, so be careful.
397
     * Large chunks tends to speed up the upload, while smaller optimizes memory usage.
398
     *
399
     * @param string $path
400
     * @param string|resource $contents
401
     * @param string $mode
402
     * @param int $chunkSize
403
     *
404
     * @return array
405
     */
406
    public function uploadChunked(string $path, $contents, $mode = 'add', $chunkSize = null): array
407
    {
408
        if ($chunkSize === null || $chunkSize > $this->maxChunkSize) {
409
            $chunkSize = $this->maxChunkSize;
410
        }
411
412
        $stream = $this->getStream($contents);
413
414
        $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 412 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...
415
416
        while (! $stream->eof()) {
417
            $cursor = $this->uploadChunk(self::UPLOAD_SESSION_APPEND, $stream, $chunkSize, $cursor);
418
        }
419
420
        return $this->uploadSessionFinish('', $cursor, $path, $mode);
421
    }
422
423
    /**
424
     * @param int $type
425
     * @param Psr7\Stream $stream
426
     * @param int $chunkSize
427
     * @param \Spatie\Dropbox\UploadSessionCursor|null $cursor
428
     * @return \Spatie\Dropbox\UploadSessionCursor
429
     * @throws Exception
430
     */
431
    protected function uploadChunk($type, &$stream, $chunkSize, $cursor = null): UploadSessionCursor
432
    {
433
        $maximumTries = $stream->isSeekable() ? $this->maxUploadChunkRetries : 0;
434
        $pos = $stream->tell();
435
436
        $tries = 0;
437
438
        tryUpload:
439
        try {
440
            $tries++;
441
442
            $chunkStream = new Psr7\LimitStream($stream, $chunkSize, $stream->tell());
443
444
            if ($type === self::UPLOAD_SESSION_START) {
445
                return $this->uploadSessionStart($chunkStream);
446
            }
447
448
            if ($type === self::UPLOAD_SESSION_APPEND && $cursor !== null) {
449
                return $this->uploadSessionAppend($chunkStream, $cursor);
450
            }
451
452
            throw new Exception('Invalid type');
453
        } catch (RequestException $exception) {
454
            if ($tries < $maximumTries) {
455
                // rewind
456
                $stream->seek($pos, SEEK_SET);
457
                goto tryUpload;
458
            }
459
            throw $exception;
460
        }
461
    }
462
463
    /**
464
     * Upload sessions allow you to upload a single file in one or more requests,
465
     * for example where the size of the file is greater than 150 MB.
466
     * This call starts a new upload session with the given data.
467
     *
468
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-start
469
     *
470
     * @param string|StreamInterface $contents
471
     * @param bool $close
472
     *
473
     * @return UploadSessionCursor
474
     */
475
    public function uploadSessionStart($contents, bool $close = false): UploadSessionCursor
476
    {
477
        $arguments = compact('close');
478
479
        $response = json_decode(
480
            $this->contentEndpointRequest('files/upload_session/start', $arguments, $contents)->getBody(),
481
            true
482
        );
483
484
        return new UploadSessionCursor($response['session_id'], ($contents instanceof StreamInterface ? $contents->tell() : strlen($contents)));
485
    }
486
487
    /**
488
     * Append more data to an upload session.
489
     * When the parameter close is set, this call will close the session.
490
     * A single request should not upload more than 150 MB.
491
     *
492
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-append_v2
493
     *
494
     * @param string|StreamInterface $contents
495
     * @param UploadSessionCursor $cursor
496
     * @param bool $close
497
     *
498
     * @return \Spatie\Dropbox\UploadSessionCursor
499
     */
500
    public function uploadSessionAppend($contents, UploadSessionCursor $cursor, bool $close = false): UploadSessionCursor
501
    {
502
        $arguments = compact('cursor', 'close');
503
504
        $pos = $contents instanceof StreamInterface ? $contents->tell() : 0;
505
        $this->contentEndpointRequest('files/upload_session/append_v2', $arguments, $contents);
506
507
        $cursor->offset += $contents instanceof StreamInterface ? ($contents->tell() - $pos) : strlen($contents);
508
509
        return $cursor;
510
    }
511
512
    /**
513
     * Finish an upload session and save the uploaded data to the given file path.
514
     * A single request should not upload more than 150 MB.
515
     *
516
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-finish
517
     *
518
     * @param string|StreamInterface $contents
519
     * @param \Spatie\Dropbox\UploadSessionCursor $cursor
520
     * @param string $path
521
     * @param string|array $mode
522
     * @param bool $autorename
523
     * @param bool $mute
524
     *
525
     * @return array
526
     */
527
    public function uploadSessionFinish($contents, UploadSessionCursor $cursor, string $path, $mode = 'add', $autorename = false, $mute = false): array
528
    {
529
        $arguments = compact('cursor');
530
        $arguments['commit'] = compact('path', 'mode', 'autorename', 'mute');
531
532
        $response = $this->contentEndpointRequest(
533
            'files/upload_session/finish',
534
            $arguments,
535
            ($contents == '') ? null : $contents
536
        );
537
538
        $metadata = json_decode($response->getBody(), true);
539
540
        $metadata['.tag'] = 'file';
541
542
        return $metadata;
543
    }
544
545
    /**
546
     * Get Account Info for current authenticated user.
547
     *
548
     * @link https://www.dropbox.com/developers/documentation/http/documentation#users-get_current_account
549
     *
550
     * @return array
551
     */
552
    public function getAccountInfo(): array
553
    {
554
        return $this->rpcEndpointRequest('users/get_current_account');
555
    }
556
557
    /**
558
     * Revoke current access token.
559
     *
560
     * @link https://www.dropbox.com/developers/documentation/http/documentation#auth-token-revoke
561
     */
562
    public function revokeToken()
563
    {
564
        $this->rpcEndpointRequest('auth/token/revoke');
565
    }
566
567
    protected function normalizePath(string $path): string
568
    {
569
        if (preg_match("/^id:.*|^rev:.*|^(ns:[0-9]+(\/.*)?)/", $path) === 1) {
570
            return $path;
571
        }
572
573
        $path = trim($path, '/');
574
575
        return ($path === '') ? '' : '/'.$path;
576
    }
577
578
    protected function getEndpointUrl(string $subdomain, string $endpoint): string
579
    {
580
        if (count($parts = explode('::', $endpoint)) === 2) {
581
            [$subdomain, $endpoint] = $parts;
582
        }
583
584
        return "https://{$subdomain}.dropboxapi.com/2/{$endpoint}";
585
    }
586
587
    /**
588
     * @param string $endpoint
589
     * @param array $arguments
590
     * @param string|resource|StreamInterface $body
591
     *
592
     * @return \Psr\Http\Message\ResponseInterface
593
     *
594
     * @throws \Exception
595
     */
596
    public function contentEndpointRequest(string $endpoint, array $arguments, $body = ''): ResponseInterface
597
    {
598
        $headers = ['Dropbox-API-Arg' => json_encode($arguments)];
599
600
        if ($body !== '') {
601
            $headers['Content-Type'] = 'application/octet-stream';
602
        }
603
604
        try {
605
            $response = $this->client->post($this->getEndpointUrl('content', $endpoint), [
606
                'headers' => $this->getHeaders($headers),
607
                'body' => $body,
608
            ]);
609
        } catch (ClientException $exception) {
610
            throw $this->determineException($exception);
611
        }
612
613
        return $response;
614
    }
615
616
    public function rpcEndpointRequest(string $endpoint, array $parameters = null): array
617
    {
618
        try {
619
            $options = ['headers' => $this->getHeaders()];
620
621
            if ($parameters) {
622
                $options['json'] = $parameters;
623
            }
624
625
            $response = $this->client->post($this->getEndpointUrl('api', $endpoint), $options);
626
        } catch (ClientException $exception) {
627
            throw $this->determineException($exception);
628
        }
629
630
        $response = json_decode($response->getBody(), true);
631
632
        return $response ?? [];
633
    }
634
635
    protected function determineException(ClientException $exception): Exception
636
    {
637
        if (in_array($exception->getResponse()->getStatusCode(), [400, 409])) {
638
            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...
639
        }
640
641
        return $exception;
642
    }
643
644
    /**
645
     * @param $contents
646
     *
647
     * @return \GuzzleHttp\Psr7\PumpStream|\GuzzleHttp\Psr7\Stream
648
     */
649
    protected function getStream($contents)
650
    {
651
        if ($this->isPipe($contents)) {
652
            /* @var resource $contents */
653
            return new PumpStream(function ($length) use ($contents) {
654
                $data = fread($contents, $length);
655
                if (strlen($data) === 0) {
656
                    return false;
657
                }
658
659
                return $data;
660
            });
661
        }
662
663
        return Psr7\stream_for($contents);
664
    }
665
666
    /**
667
     * Get the access token.
668
     */
669
    public function getAccessToken(): string
670
    {
671
        return $this->accessToken;
672
    }
673
674
    /**
675
     * Set the access token.
676
     */
677
    public function setAccessToken(string $accessToken): self
678
    {
679
        $this->accessToken = $accessToken;
680
681
        return $this;
682
    }
683
684
    /**
685
     * Get the HTTP headers.
686
     */
687
    protected function getHeaders(array $headers = []): array
688
    {
689
        return array_merge([
690
            'Authorization' => "Bearer {$this->accessToken}",
691
        ], $headers);
692
    }
693
}
694