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 (#64)
by
unknown
01:14
created

Client::setHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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