Client   F
last analyzed

Complexity

Total Complexity 54

Size/Duplication

Total Lines 690
Duplicated Lines 10.29 %

Test Coverage

Coverage 94.41%

Importance

Changes 0
Metric Value
wmc 54
dl 71
loc 690
ccs 169
cts 179
cp 0.9441
rs 1.1714
c 0
b 0
f 0

30 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 3
A getItemBreadcrumps() 0 3 1
A deleteItem() 10 10 1
A getItemContents() 10 10 1
A getItemById() 0 5 2
A uploadFileStandard() 0 18 1
A getItemByPath() 6 6 2
A copyItem() 0 10 1
A getItemDownloadUrl() 10 10 1
B getChunkUri() 0 23 4
B uploadFileStreamed() 0 45 5
B authenticate() 0 26 3
A getUser() 0 3 1
A createFolder() 0 15 1
A updateItem() 10 10 1
A readChunk() 0 13 4
A getThumbnailUrl() 10 10 1
A buildUri() 0 3 1
A delete() 0 3 1
A uploadChunk() 0 15 1
A request() 0 14 4
A getItemAccessControls() 6 6 2
A get() 0 3 1
A patch() 0 3 1
A determineException() 0 7 2
A createShare() 0 10 1
A post() 0 3 1
A getWebAppLink() 0 3 1
A buildHttpQuery() 0 12 3
A jsonValidator() 0 9 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Client often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Client, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Kapersoft\ShareFile;
4
5
use Exception;
6
use GuzzleHttp\Psr7;
7
use GuzzleHttp\HandlerStack;
8
use GuzzleHttp\Handler\MockHandler;
9
use GuzzleHttp\Client as GuzzleClient;
10
use GuzzleHttp\Exception\ClientException;
11
use Kapersoft\Sharefile\Exceptions\BadRequest;
12
13
/**
14
 * Class Client.
15
 *
16
 * @author   Jan Willem Kaper <[email protected]>
17
 * @license  MIT (see License.txt)
18
 *
19
 * @link     http://github.com/kapersoft/sharefile-api
20
 */
21
class Client
22
{
23
    /**
24
     * ShareFile token.
25
     *
26
     * @var array
27
     */
28
    public $token;
29
30
    /**
31
     * Guzzle Client.
32
     *
33
     * @var \GuzzleHttp\Client
34
     */
35
    public $client;
36
37
    /**
38
     * Thumbnail size.
39
     */
40
    const THUMBNAIL_SIZE_M = 75;
41
    const THUMBNAIL_SIZE_L = 600;
42
43
    /*
44
     * ShareFile Folder
45
     */
46
    const FOLDER_TOP = 'top';
47
    const FOLDER_HOME = 'home';
48
    const FOLDER_FAVORITES = 'favorites';
49
    const FOLDER_ALLSHARED = 'allshared';
50
51
    /*
52
    * Default Chunk Size for uploading files
53
    */
54
    const DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024; // 8 megabytes
55
56
    /**
57
     * Client constructor.
58
     *
59
     * @param string                   $hostname      ShareFile hostname
60
     * @param string                   $client_id     OAuth2 client_id
61
     * @param string                   $client_secret OAuth2 client_secret
62
     * @param string                   $username      ShareFile username
63
     * @param string                   $password      ShareFile password
64
     * @param MockHandler|HandlerStack $handler       Guzzle Handler
65
     *
66
     * @throws Exception
67
     */
68 75
    public function __construct(string $hostname, string $client_id, string $client_secret, string $username, string $password, $handler = null)
69
    {
70 75
        $response = $this->authenticate($hostname, $client_id, $client_secret, $username, $password, $handler);
71
72 69
        if (! isset($response['access_token']) || ! isset($response['subdomain'])) {
73 3
            throw new Exception("Incorrect response from Authentication: 'access_token' or 'subdomain' is missing.");
74
        }
75
76 66
        $this->token = $response;
77 66
        $this->client = new GuzzleClient(
78
            [
79 66
                'handler' => $handler,
80
                'headers' => [
81 66
                    'Authorization' => "Bearer {$this->token['access_token']}",
82
                ],
83
            ]
84
        );
85 66
    }
86
87
    /**
88
     * ShareFile authentication using username/password.
89
     *
90
     * @param string                   $hostname      ShareFile hostname
91
     * @param string                   $client_id     OAuth2 client_id
92
     * @param string                   $client_secret OAuth2 client_secret
93
     * @param string                   $username      ShareFile username
94
     * @param string                   $password      ShareFile password
95
     * @param MockHandler|HandlerStack $handler       Guzzle Handler
96
     *
97
     * @throws Exception
98
     *
99
     * @return array
100
     */
101 75
    protected function authenticate(string $hostname, string $client_id, string $client_secret, string $username, string $password, $handler = null):array
102
    {
103 75
        $uri = "https://{$hostname}/oauth/token";
104
105
        $parameters = [
106 75
            'grant_type'    => 'password',
107 75
            'client_id'     => $client_id,
108 75
            'client_secret' => $client_secret,
109 75
            'username'      => $username,
110 75
            'password'      => $password,
111
        ];
112
113
        try {
114 75
            $client = new GuzzleClient(['handler' => $handler]);
115 75
            $response = $client->post(
116 75
                $uri,
117 75
                ['form_params' => $parameters]
118
            );
119 3
        } catch (ClientException $exception) {
120 3
            throw $exception;
121
        }
122
123 72
        if ($response->getStatusCode() == '200') {
124 69
            return json_decode($response->getBody(), true);
125
        } else {
126 3
            throw new Exception('Authentication error', $response->getStatusCode());
127
        }
128
    }
129
130
    /**
131
     * Get user details.
132
     *
133
     * @param string $userId ShareFile user id (optional)
134
     *
135
     * @return array
136
     */
137 3
    public function getUser(string $userId = ''):array
138
    {
139 3
        return $this->get("Users({$userId})");
140
    }
141
142
    /**
143
     * Create a folder.
144
     *
145
     * @param string $parentId    Id of the parent folder
146
     * @param string $name        Name
147
     * @param string $description Description
148
     * @param bool   $overwrite   Overwrite folder
149
     *
150
     * @return array
151
     */
152 6
    public function createFolder(string $parentId, string $name, string $description = '', bool $overwrite = false):array
153
    {
154 6
        $parameters = $this->buildHttpQuery(
155
            [
156 6
                'overwrite'   => $overwrite,
157
                'passthrough' => false,
158
            ]
159
        );
160
161
        $data = [
162 6
            'name'        => $name,
163 6
            'description' => $description,
164
        ];
165
166 6
        return $this->post("Items({$parentId})/Folder?{$parameters}", $data);
167
    }
168
169
    /**
170
     * Get Folder/File using Id.
171
     *
172
     * @param string $itemId      Item id
173
     * @param bool   $getChildren Include children
174
     *
175
     * @return array
176
     */
177 6
    public function getItemById(string $itemId, bool $getChildren = false):array
178
    {
179 6
        $parameters = $getChildren === true ? '$expand=Children' : '';
180
181 6
        return $this->get("Items({$itemId})?{$parameters}");
182
    }
183
184
    /**
185
     * Get Folder/File using path.
186
     *
187
     * @param string $path   Path
188
     * @param string $itemId Id of the root folder (optional)
189
     *
190
     * @return array
191
     */
192 3 View Code Duplication
    public function getItemByPath(string $path, string $itemId = ''):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...
193
    {
194 3
        if (empty($itemId)) {
195 3
            return $this->get("Items/ByPath?Path={$path}");
196
        } else {
197
            return $this->get("Items({$itemId})/ByPath?Path={$path}");
198
        }
199
    }
200
201
    /**
202
     * Get breadcrumps of an item.
203
     *
204
     * @param string $itemId Item Id
205
     *
206
     * @return array
207
     */
208 3
    public function getItemBreadcrumps(string $itemId):array
209
    {
210 3
        return $this->get("Items({$itemId})/Breadcrumbs");
211
    }
212
213
    /**
214
     * Copy an item.
215
     *
216
     * @param string $targetId  Id of the target folder
217
     * @param string $itemId    Id of the copied item
218
     * @param bool   $overwrite Indicates whether items with the same name will be overwritten or not (optional)
219
     *
220
     * @return array
221
     */
222 6
    public function copyItem(string $targetId, string $itemId, bool $overwrite = false):array
223
    {
224 6
        $parameters = $this->buildHttpQuery(
225
            [
226 6
                'targetid'  => $targetId,
227 6
                'overwrite' => $overwrite,
228
            ]
229
        );
230
231 6
        return $this->post("Items({$itemId})/Copy?{$parameters}");
232
    }
233
234
    /**
235
     * Update an item.
236
     *
237
     * @param string $itemId    Id of the item
238
     * @param array  $data      New data
239
     * @param bool   $forceSync Indicates whether operation is to be executed synchronously (optional)
240
     * @param bool   $notify    Indicates whether an email should be sent to users subscribed to Upload Notifications (optional)
241
     *
242
     * @return array
243
     */
244 3 View Code Duplication
    public function updateItem(string $itemId, array $data, bool $forceSync = true, bool $notify = true):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...
245
    {
246 3
        $parameters = $this->buildHttpQuery(
247
            [
248 3
                'forceSync' => $forceSync,
249 3
                'notify'    => $notify,
250
            ]
251
        );
252
253 3
        return $this->patch("Items({$itemId})?{$parameters}", $data);
254
    }
255
256
    /**
257
     * Delete an item.
258
     *
259
     * @param string $itemId        Item id
260
     * @param bool   $singleversion True it will delete only the specified version rather than all sibling files with the same filename (optional)
261
     * @param bool   $forceSync     True will block the operation from taking place asynchronously (optional)
262
     *
263
     * @return string
264
     */
265 3 View Code Duplication
    public function deleteItem(string $itemId, bool $singleversion = false, bool $forceSync = false):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...
266
    {
267 3
        $parameters = $this->buildHttpQuery(
268
            [
269 3
                'singleversion' => $singleversion,
270 3
                'forceSync'     => $forceSync,
271
            ]
272
        );
273
274 3
        return $this->delete("Items({$itemId})?{$parameters}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->delete(EncapsedNode) could return the type array which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
275
    }
276
277
    /**
278
     * Get temporary download URL for an item.
279
     *
280
     * @param string $itemId             Item id
281
     * @param bool   $includeallversions For folder downloads only, includes old versions of files in the folder in the zip when true, current versions only when false (default)
282
     *
283
     * @return array
284
     */
285 3 View Code Duplication
    public function getItemDownloadUrl(string $itemId, bool $includeallversions = 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...
286
    {
287 3
        $parameters = $this->buildHttpQuery(
288
            [
289 3
                'includeallversions' => $includeallversions,
290
                'redirect'           => false,
291
            ]
292
        );
293
294 3
        return $this->get("Items({$itemId})/Download?{$parameters}");
295
    }
296
297
    /**
298
     * Get contents of and item.
299
     *
300
     * @param string $itemId             Item id
301
     * @param bool   $includeallversions $includeallversions For folder downloads only, includes old versions of files in the folder in the zip when true, current versions only when false (default)
302
     *
303
     * @return mixed
304
     */
305 3 View Code Duplication
    public function getItemContents(string $itemId, bool $includeallversions = false)
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...
306
    {
307 3
        $parameters = $this->buildHttpQuery(
308
            [
309 3
                'includeallversions' => $includeallversions,
310
                'redirect'           => true,
311
            ]
312
        );
313
314 3
        return $this->get("Items({$itemId})/Download?{$parameters}");
315
    }
316
317
    /**
318
     * Get the Chunk Uri to start a file-upload.
319
     *
320
     * @param string   $method    Upload method (Standard or Streamed)
321
     * @param string   $filename  Name of file
322
     * @param string   $folderId  Id of the parent folder
323
     * @param bool     $unzip     Indicates that the upload is a Zip file, and contents must be extracted at the end of upload. The resulting files and directories will be placed in the target folder. If set to false, the ZIP file is uploaded as a single file. Default is false (optional)
324
     * @param bool     $overwrite Indicates whether items with the same name will be overwritten or not (optional)
325
     * @param bool     $notify    Indicates whether users will be notified of this upload - based on folder preferences (optional)
326
     * @param bool     $raw       Send contents contents directly in the POST body (=true) or send contents in MIME format (=false) (optional)
327
     * @param resource $stream    Resource stream of the contents (optional)
328
     *
329
     * @return array
330
     */
331 9
    public function getChunkUri(string $method, string $filename, string $folderId, bool $unzip = false, $overwrite = true, bool $notify = true, bool $raw = false, $stream = null):array
332
    {
333 9
        $parameters = $this->buildHttpQuery(
334
            [
335 9
                'method'                => $method,
336 9
                'raw'                   => $raw,
337 9
                'fileName'              => basename($filename),
338 9
                'fileSize'              => $stream == null ? filesize($filename) : fstat($stream)['size'],
339
                'canResume'             => false,
340
                'startOver'             => false,
341 9
                'unzip'                 => $unzip,
342 9
                'tool'                  => 'apiv3',
343 9
                'overwrite'             => $overwrite,
344 9
                'title'                 => basename($filename),
345
                'isSend'                => false,
346 9
                'responseFormat'        => 'json',
347 9
                'notify'                => $notify,
348 9
                'clientCreatedDateUTC'  => $stream == null ? filectime($filename) : fstat($stream)['ctime'],
349 9
                'clientModifiedDateUTC' => $stream == null ? filemtime($filename) : fstat($stream)['mtime'],
350
            ]
351
        );
352
353 9
        return $this->post("Items({$folderId})/Upload?{$parameters}");
354
    }
355
356
    /**
357
     * Upload a file using a single HTTP POST.
358
     *
359
     * @param string $filename  Name of file
360
     * @param string $folderId  Id of the parent folder
361
     * @param bool   $unzip     Indicates that the upload is a Zip file, and contents must be extracted at the end of upload. The resulting files and directories will be placed in the target folder. If set to false, the ZIP file is uploaded as a single file. Default is false (optional)
362
     * @param bool   $overwrite Indicates whether items with the same name will be overwritten or not (optional)
363
     * @param bool   $notify    Indicates whether users will be notified of this upload - based on folder preferences (optional)
364
     *
365
     * @return string
366
     */
367 3
    public function uploadFileStandard(string $filename, string $folderId, bool $unzip = false, bool $overwrite = true, bool $notify = true):string
368
    {
369 3
        $chunkUri = $this->getChunkUri('standard', $filename, $folderId, $unzip, $overwrite, $notify);
370
371 3
        $response = $this->client->request(
372 3
            'POST',
373 3
            $chunkUri['ChunkUri'],
374
            [
375
                'multipart' => [
376
                    [
377 3
                        'name'     => 'File1',
378 3
                        'contents' => fopen($filename, 'r'),
379
                    ],
380
                ],
381
            ]
382
        );
383
384 3
        return (string) $response->getBody();
385
    }
386
387
    /**
388
     * Upload a file using multiple HTTP POSTs.
389
     *
390
     * @param mixed    $stream    Stream resource
391
     * @param string   $folderId  Id of the parent folder
392
     * @param string   $filename  Filename (optional)
393
     * @param bool     $unzip     Indicates that the upload is a Zip file, and contents must be extracted at the end of upload. The resulting files and directories will be placed in the target folder. If set to false, the ZIP file is uploaded as a single file. Default is false (optional)
394
     * @param bool     $overwrite Indicates whether items with the same name will be overwritten or not (optional)
395
     * @param bool     $notify    Indicates whether users will be notified of this upload - based on folder preferences (optional)
396
     * @param int      $chunkSize Maximum size of the individual HTTP posts in bytes
397
     *
398
     * @return string
399
     */
400 3
    public function uploadFileStreamed($stream, string $folderId, string $filename = null, bool $unzip = false, bool $overwrite = true, bool $notify = true, int $chunkSize = null):string
401
    {
402 3
        $filename = $filename ?? stream_get_meta_data($stream)['uri'];
403 3
        if (empty($filename)) {
404
            return 'Error: no filename';
405
        }
406
407 3
        $chunkUri = $this->getChunkUri('streamed', $filename, $folderId, $unzip, $overwrite, $notify, true, $stream);
408 3
        $chunkSize = $chunkSize ?? SELF::DEFAULT_CHUNK_SIZE;
409 3
        $index = 0;
410
411
        // First Chunk
412 3
        $data = $this->readChunk($stream, $chunkSize);
413 3
        while (! ((strlen($data) < $chunkSize) || feof($stream))) {
414 3
            $parameters = $this->buildHttpQuery(
415
                [
416 3
                    'index'      => $index,
417 3
                    'byteOffset' => $index * $chunkSize,
418 3
                    'hash'       => md5($data),
419
                ]
420
            );
421
422 3
            $response = $this->uploadChunk("{$chunkUri['ChunkUri']}&{$parameters}", $data);
423
424 3
            if ($response != 'true') {
425
                return $response;
426
            }
427
428
            // Next chunk
429 3
            $index++;
430 3
            $data = $this->readChunk($stream, $chunkSize);
431
        }
432
433
        // Final chunk
434 3
        $parameters = $this->buildHttpQuery(
435
            [
436 3
                'index'      => $index,
437 3
                'byteOffset' => $index * $chunkSize,
438 3
                'hash'       => md5($data),
439 3
                'filehash'   => Psr7\hash(Psr7\stream_for($stream), 'md5'),
440
                'finish'    => true,
441
            ]
442
        );
443
444 3
        return $this->uploadChunk("{$chunkUri['ChunkUri']}&{$parameters}", $data);
445
    }
446
447
    /**
448
     * Get Thumbnail of an item.
449
     *
450
     * @param string $itemId Item id
451
     * @param int    $size   Thumbnail size: THUMBNAIL_SIZE_M or THUMBNAIL_SIZE_L (optional)
452
     *
453
     * @return array
454
     */
455 3 View Code Duplication
    public function getThumbnailUrl(string $itemId, int $size = 75):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...
456
    {
457 3
        $parameters = $this->buildHttpQuery(
458
            [
459 3
                'size'     => $size,
460
                'redirect' => false,
461
            ]
462
        );
463
464 3
        return $this->get("Items({$itemId})/Thumbnail?{$parameters}");
465
    }
466
467
    /**
468
     * Get browser link for an item.
469
     *
470
     * @param string $itemId Item id
471
     *
472
     * @return array
473
     */
474 3
    public function getWebAppLink(string $itemId):array
475
    {
476 3
        return $this->post("Items({$itemId})/WebAppLink");
477
    }
478
479
    /**
480
     * Share Share for external user.
481
     *
482
     * @param array $options Share options
483
     * @param bool  $notify  Indicates whether user will be notified if item is downloaded (optional)
484
     *
485
     * @return array
486
     */
487 3
    public function createShare(array $options, $notify = false):array
488
    {
489 3
        $parameters = $this->buildHttpQuery(
490
            [
491 3
                'notify' => $notify,
492
                'direct' => true,
493
            ]
494
        );
495
496 3
        return $this->post("Shares?{$parameters}", $options);
497
    }
498
499
    /**
500
     * Get AccessControl List for an item.
501
     *
502
     * @param string $itemId Id of an item
503
     * @param string $userId Id of an user
504
     *
505
     * @return array
506
     */
507 6 View Code Duplication
    public function getItemAccessControls(string $itemId, string $userId = ''):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...
508
    {
509 6
        if (! empty($userId)) {
510 3
            return $this->get("AccessControls(principalid={$userId},itemid={$itemId})");
511
        } else {
512 3
            return $this->get("Items({$itemId})/AccessControls");
513
        }
514
    }
515
516
    /**
517
     * Build API uri.
518
     *
519
     * @param string $endpoint API endpoint
520
     *
521
     * @return string
522
     */
523 63
    protected function buildUri(string $endpoint): string
524
    {
525 63
        return  "https://{$this->token['subdomain']}.sf-api.com/sf/v3/{$endpoint}";
526
    }
527
528
    /**
529
     * Make a request to the API.
530
     *
531
     * @param string             $method   HTTP Method
532
     * @param string             $endpoint API endpoint
533
     * @param mixed|string|array $json     POST body (optional)
534
     *
535
     * @throws Exception
536
     *
537
     * @return mixed
538
     */
539 63
    protected function request(string $method, string $endpoint, $json = null)
540
    {
541 63
        $uri = $this->buildUri($endpoint);
542 63
        $options = $json != null ? ['json' => $json] : [];
543
544
        try {
545 63
            $response = $this->client->request($method, $uri, $options);
546
        } catch (ClientException $exception) {
547
            throw $this->determineException($exception);
548
        }
549
550 63
        $body = (string) $response->getBody();
551
552 63
        return $this->jsonValidator($body) ? json_decode($body, true) : $body;
553
    }
554
555
    /**
556
     * Shorthand for GET-request.
557
     *
558
     * @param string $endpoint API endpoint
559
     *
560
     * @return mixed
561
     */
562 30
    protected function get(string $endpoint)
563
    {
564 30
        return $this->request('GET', $endpoint);
565
    }
566
567
    /**
568
     * Shorthand for POST-request.
569
     *
570
     * @param string             $endpoint API endpoint
571
     * @param mixed|string|array $json     POST body (optional)
572
     *
573
     * @return mixed
574
     */
575 27
    protected function post(string $endpoint, $json = null)
576
    {
577 27
        return $this->request('POST', $endpoint, $json);
578
    }
579
580
    /**
581
     * Shorthand for PATCH-request.
582
     *
583
     * @param string             $endpoint API endpoint
584
     * @param mixed|string|array $json     POST body (optional)
585
     *
586
     * @return mixed
587
     */
588 3
    protected function patch(string $endpoint, $json = null)
589
    {
590 3
        return $this->request('PATCH', $endpoint, $json);
591
    }
592
593
    /**
594
     * Shorthand for DELETE-request.
595
     *
596
     * @param string $endpoint API endpoint
597
     *
598
     * @return string|array
599
     */
600 3
    protected function delete(string $endpoint)
601
    {
602 3
        return $this->request('DELETE', $endpoint);
603
    }
604
605
    /**
606
     * Upload a chunk of data using HTTP POST body.
607
     *
608
     * @param string $uri  Upload URI
609
     * @param string $data Contents to upload
610
     *
611
     * @return string|array
612
     */
613 3
    protected function uploadChunk($uri, $data)
614
    {
615 3
        $response = $this->client->request(
616 3
            'POST',
617 3
            $uri,
618
            [
619
                'headers' => [
620 3
                    'Content-Length' => strlen($data),
621 3
                    'Content-Type'   => 'application/octet-stream',
622
                ],
623 3
                'body' => $data,
624
            ]
625
        );
626
627 3
        return (string) $response->getBody();
628
    }
629
630
    /**
631
     * Sometimes fread() returns less than the request number of bytes (for example, when reading
632
     * from network streams).  This function repeatedly calls fread until the requested number of
633
     * bytes have been read or we've reached EOF.
634
     *
635
     * @param resource $stream
636
     * @param int      $chunkSize
637
     *
638
     * @throws Exception
639
     * @return string
640
     */
641 3
    protected function readChunk($stream, int $chunkSize)
642
    {
643 3
        $chunk = '';
644 3
        while (! feof($stream) && $chunkSize > 0) {
645 3
            $part = fread($stream, $chunkSize);
646 3
            if ($part === false) {
647
                throw new Exception('Error reading from $stream.');
648
            }
649 3
            $chunk .= $part;
650 3
            $chunkSize -= strlen($part);
651
        }
652
653 3
        return $chunk;
654
    }
655
656
    /**
657
     * Handle ClientException.
658
     *
659
     * @param ClientException $exception ClientException
660
     *
661
     * @return Exception
662
     */
663
    protected function determineException(ClientException $exception): Exception
664
    {
665
        if (in_array($exception->getResponse()->getStatusCode(), [400, 403, 404, 409])) {
666
            return new BadRequest($exception->getResponse());
667
        }
668
669
        return $exception;
670
    }
671
672
    /**
673
     * Build HTTP query.
674
     *
675
     * @param array $parameters Query parameters
676
     *
677
     * @return string
678
     */
679 39
    protected function buildHttpQuery(array $parameters):string
680
    {
681 39
        return http_build_query(
682 39
            array_map(
683 39
                function ($parameter) {
684 39
                    if (! is_bool($parameter)) {
685 18
                        return $parameter;
686
                    }
687
688 39
                    return $parameter ? 'true' : 'false';
689 39
                },
690 39
                $parameters
691
            )
692
        );
693
    }
694
695
    /**
696
     * Validate JSON.
697
     *
698
     * @param mixed $data JSON variable
699
     *
700
     * @return bool
701
     */
702 63
    protected function jsonValidator($data = null):bool
703
    {
704 63
        if (! empty($data)) {
705 60
            @json_decode($data);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for json_decode(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

705
            /** @scrutinizer ignore-unhandled */ @json_decode($data);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
706
707 60
            return json_last_error() === JSON_ERROR_NONE;
708
        }
709
710 3
        return false;
711
    }
712
}
713