Passed
Pull Request — master (#19)
by
unknown
10:48
created

Client::uploadFileStreamed()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 52
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 24
c 1
b 0
f 0
dl 0
loc 52
ccs 14
cts 14
cp 1
rs 9.2248
cc 5
nc 4
nop 7
crap 5

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Kapersoft\Sharefile;
4
5
use Exception;
6
use GuzzleHttp\Client as GuzzleClient;
7
use GuzzleHttp\Exception\ClientException;
8
use GuzzleHttp\Handler\MockHandler;
9
use GuzzleHttp\HandlerStack;
10
use GuzzleHttp\Psr7;
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(
69
        string $hostname,
70 75
        string $client_id,
71
        string $client_secret,
72 69
        string $username,
73 3
        string $password,
74
        $handler = null
75
    ) {
76 66
        $response = $this->authenticate($hostname, $client_id, $client_secret, $username, $password, $handler);
77 66
78
        if (!isset($response['access_token']) || !isset($response['subdomain'])) {
79 66
            throw new Exception("Incorrect response from Authentication: 'access_token' or 'subdomain' is missing.");
80
        }
81 66
82
        $this->token = $response;
83
        $this->client = new GuzzleClient(
84
            [
85 66
                'handler' => $handler,
86
                'headers' => [
87
                    'Authorization' => "Bearer {$this->token['access_token']}",
88
                ],
89
            ]
90
        );
91
    }
92
93
    /**
94
     * ShareFile authentication using username/password.
95
     *
96
     * @param  string  $hostname  ShareFile hostname
97
     * @param  string  $client_id  OAuth2 client_id
98
     * @param  string  $client_secret  OAuth2 client_secret
99
     * @param  string  $username  ShareFile username
100
     * @param  string  $password  ShareFile password
101 75
     * @param  MockHandler|HandlerStack  $handler  Guzzle Handler
102
     *
103 75
     * @return array
104
     * @throws Exception
105
     *
106 75
     */
107 75
    protected function authenticate(
108 75
        string $hostname,
109 75
        string $client_id,
110 75
        string $client_secret,
111
        string $username,
112
        string $password,
113
        $handler = null
114 75
    ): array {
115 75
        $uri = "https://{$hostname}/oauth/token";
116 75
117 75
        $parameters = [
118
            'grant_type'    => 'password',
119 3
            'client_id'     => $client_id,
120 3
            'client_secret' => $client_secret,
121
            'username'      => $username,
122
            'password'      => $password,
123 72
        ];
124 69
125
        try {
126 3
            $client = new GuzzleClient(['handler' => $handler]);
127
            $response = $client->post(
128
                $uri,
129
                ['form_params' => $parameters]
130
            );
131
        } catch (ClientException $exception) {
132
            throw $exception;
133
        }
134
135
        if ($response->getStatusCode() == '200') {
136
            return json_decode($response->getBody(), true);
137 3
        } else {
138
            throw new Exception('Authentication error', $response->getStatusCode());
139 3
        }
140
    }
141
142
    /**
143
     * Get user details.
144
     *
145
     * @param  string  $userId  ShareFile user id (optional)
146
     *
147
     * @return array
148
     */
149
    public function getUser(string $userId = ''): array
150
    {
151
        return $this->get("Users({$userId})");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get('Users('.$userId.')') could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
152 6
    }
153
154 6
    /**
155
     * Create a folder.
156 6
     *
157
     * @param  string  $parentId  Id of the parent folder
158
     * @param  string  $name  Name
159
     * @param  string  $description  Description
160
     * @param  bool  $overwrite  Overwrite folder
161
     *
162 6
     * @return array
163 6
     */
164
    public function createFolder(
165
        string $parentId,
166 6
        string $name,
167
        string $description = '',
168
        bool $overwrite = false
169
    ): array {
170
        $parameters = $this->buildHttpQuery(
171
            [
172
                'overwrite'   => $overwrite,
173
                'passthrough' => false,
174
            ]
175
        );
176
177 6
        $data = [
178
            'name'        => $name,
179 6
            'description' => $description,
180
        ];
181 6
182
        return $this->post("Items({$parentId})/Folder?{$parameters}", $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post('Item...r?'.$parameters, $data) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
183
    }
184
185
    /**
186
     * Get Folder/File using Id.
187
     *
188
     * @param  string  $itemId  Item id
189
     * @param  bool  $getChildren  Include children
190
     *
191
     * @return array
192 3
     */
193
    public function getItemById(string $itemId, bool $getChildren = false): array
194 3
    {
195 3
        $parameters = $getChildren === true ? '$expand=Children' : '';
196
197
        return $this->get("Items({$itemId})?{$parameters}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get('Items...temId.')?'.$parameters) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
198
    }
199
200
    /**
201
     * Get Folder/File using path.
202
     *
203
     * @param  string  $path  Path
204
     * @param  string  $itemId  Id of the root folder (optional)
205
     *
206
     * @return array
207
     */
208 3
    public function getItemByPath(string $path, string $itemId = ''): array
209
    {
210 3
        if (empty($itemId)) {
211
            return $this->get("Items/ByPath?Path={$path}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get('Items/ByPath?Path='.$path) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
212
        } else {
213
            return $this->get("Items({$itemId})/ByPath?Path={$path}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get('Items...')/ByPath?Path='.$path) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
214
        }
215
    }
216
217
    /**
218
     * Get breadcrumps of an item.
219
     *
220
     * @param  string  $itemId  Item Id
221
     *
222 6
     * @return array
223
     */
224 6
    public function getItemBreadcrumps(string $itemId): array
225
    {
226 6
        return $this->get("Items({$itemId})/Breadcrumbs");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get('Items...itemId.')/Breadcrumbs') could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
227 6
    }
228
229
    /**
230
     * Copy an item.
231 6
     *
232
     * @param  string  $targetId  Id of the target folder
233
     * @param  string  $itemId  Id of the copied item
234
     * @param  bool  $overwrite  Indicates whether items with the same name will be overwritten or not (optional)
235
     *
236
     * @return array
237
     */
238
    public function copyItem(string $targetId, string $itemId, bool $overwrite = false): array
239
    {
240
        $parameters = $this->buildHttpQuery(
241
            [
242
                'targetid'  => $targetId,
243
                'overwrite' => $overwrite,
244 3
            ]
245
        );
246 3
247
        return $this->post("Items({$itemId})/Copy?{$parameters}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post('Item....')/Copy?'.$parameters) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
248 3
    }
249 3
250
    /**
251
     * Update an item.
252
     *
253 3
     * @param  string  $itemId  Id of the item
254
     * @param  array  $data  New data
255
     * @param  bool  $forceSync  Indicates whether operation is to be executed synchronously (optional)
256
     * @param  bool  $notify  Indicates whether an email should be sent to users subscribed to Upload Notifications (optional)
257
     *
258
     * @return array
259
     */
260
    public function updateItem(string $itemId, array $data, bool $forceSync = true, bool $notify = true): array
261
    {
262
        $parameters = $this->buildHttpQuery(
263
            [
264
                'forceSync' => $forceSync,
265 3
                'notify'    => $notify,
266
            ]
267 3
        );
268
269 3
        return $this->patch("Items({$itemId})?{$parameters}", $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->patch('Ite...)?'.$parameters, $data) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
270 3
    }
271
272
    /**
273
     * Delete an item.
274 3
     *
275
     * @param  string  $itemId  Item id
276
     * @param  bool  $singleversion  True it will delete only the specified version rather than all sibling files with the same filename (optional)
277
     * @param  bool  $forceSync  True will block the operation from taking place asynchronously (optional)
278
     *
279
     * @return string
280
     */
281
    public function deleteItem(string $itemId, bool $singleversion = false, bool $forceSync = false): string
282
    {
283
        $parameters = $this->buildHttpQuery(
284
            [
285 3
                'singleversion' => $singleversion,
286
                'forceSync'     => $forceSync,
287 3
            ]
288
        );
289 3
290
        return $this->delete("Items({$itemId})?{$parameters}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->delete('It...temId.')?'.$parameters) 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...
291
    }
292
293
    /**
294 3
     * Get temporary download URL for an item.
295
     *
296
     * @param  string  $itemId  Item id
297
     * @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)
298
     *
299
     * @return array
300
     */
301
    public function getItemDownloadUrl(string $itemId, bool $includeallversions = false): array
302
    {
303
        $parameters = $this->buildHttpQuery(
304
            [
305 3
                'includeallversions' => $includeallversions,
306
                'redirect'           => false,
307 3
            ]
308
        );
309 3
310
        return $this->get("Items({$itemId})/Download?{$parameters}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get('Items...Download?'.$parameters) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
311
    }
312
313
    /**
314 3
     * Get contents of and item.
315
     *
316
     * @param  string  $itemId  Item id
317
     * @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)
318
     *
319
     * @return mixed
320
     */
321
    public function getItemContents(string $itemId, bool $includeallversions = false)
322
    {
323
        $parameters = $this->buildHttpQuery(
324
            [
325
                'includeallversions' => $includeallversions,
326
                'redirect'           => true,
327
            ]
328
        );
329
330
        return $this->get("Items({$itemId})/Download?{$parameters}");
331 9
    }
332
333 9
    /**
334
     * Get the Chunk Uri to start a file-upload.
335 9
     *
336 9
     * @param  string  $method  Upload method (Standard or Streamed)
337 9
     * @param  string  $filename  Name of file
338 9
     * @param  string  $folderId  Id of the parent folder
339
     * @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)
340
     * @param  bool  $overwrite  Indicates whether items with the same name will be overwritten or not (optional)
341 9
     * @param  bool  $notify  Indicates whether users will be notified of this upload - based on folder preferences (optional)
342 9
     * @param  bool  $raw  Send contents contents directly in the POST body (=true) or send contents in MIME format (=false) (optional)
343 9
     * @param  resource  $stream  Resource stream of the contents (optional)
344 9
     *
345
     * @return array
346 9
     */
347 9
    public function getChunkUri(
348 9
        string $method,
349 9
        string $filename,
350
        string $folderId,
351
        bool $unzip = false,
352
        $overwrite = true,
353 9
        bool $notify = true,
354
        bool $raw = false,
355
        $stream = null
356
    ): array {
357
        $parameters = $this->buildHttpQuery(
358
            [
359
                'method'                => $method,
360
                'raw'                   => $raw,
361
                'fileName'              => basename($filename),
362
                'fileSize'              => $stream == null ? filesize($filename) : fstat($stream)['size'],
363
                'canResume'             => false,
364
                'startOver'             => false,
365
                'unzip'                 => $unzip,
366
                'tool'                  => 'apiv3',
367 3
                'overwrite'             => $overwrite,
368
                'title'                 => basename($filename),
369 3
                'isSend'                => false,
370
                'responseFormat'        => 'json',
371 3
                'notify'                => $notify,
372 3
                'clientCreatedDateUTC'  => $stream == null ? filectime($filename) : fstat($stream)['ctime'],
373 3
                'clientModifiedDateUTC' => $stream == null ? filemtime($filename) : fstat($stream)['mtime'],
374
            ]
375
        );
376
377 3
        return $this->post("Items({$folderId})/Upload?{$parameters}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post('Item...)/Upload?'.$parameters) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
378 3
    }
379
380
    /**
381
     * Upload a file using a single HTTP POST.
382
     *
383
     * @param  string  $filename  Name of file
384 3
     * @param  string  $folderId  Id of the parent folder
385
     * @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)
386
     * @param  bool  $overwrite  Indicates whether items with the same name will be overwritten or not (optional)
387
     * @param  bool  $notify  Indicates whether users will be notified of this upload - based on folder preferences (optional)
388
     *
389
     * @return string
390
     */
391
    public function uploadFileStandard(
392
        string $filename,
393
        string $folderId,
394
        bool $unzip = false,
395
        bool $overwrite = true,
396
        bool $notify = true
397
    ): string {
398
        $chunkUri = $this->getChunkUri('standard', $filename, $folderId, $unzip, $overwrite, $notify);
399
400 3
        $response = $this->client->request(
401
            'POST',
402 3
            $chunkUri['ChunkUri'],
403 3
            [
404
                'multipart' => [
405
                    [
406
                        'name'     => 'File1',
407 3
                        'contents' => fopen($filename, 'r'),
408 3
                    ],
409 3
                ],
410
            ]
411
        );
412 3
413 3
        return (string) $response->getBody();
414 3
    }
415
416 3
    /**
417 3
     * Upload a file using multiple HTTP POSTs.
418 3
     *
419
     * @param  mixed  $stream  Stream resource
420
     * @param  string  $folderId  Id of the parent folder
421
     * @param  string  $filename  Filename (optional)
422 3
     * @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)
423
     * @param  bool  $overwrite  Indicates whether items with the same name will be overwritten or not (optional)
424 3
     * @param  bool  $notify  Indicates whether users will be notified of this upload - based on folder preferences (optional)
425
     * @param  int  $chunkSize  Maximum size of the individual HTTP posts in bytes
426
     *
427
     * @return string
428
     */
429 3
    public function uploadFileStreamed(
430 3
        $stream,
431
        string $folderId,
432
        string $filename = null,
433
        bool $unzip = false,
434 3
        bool $overwrite = true,
435
        bool $notify = true,
436 3
        int $chunkSize = null
437 3
    ): string {
438 3
        $filename = $filename ?? stream_get_meta_data($stream)['uri'];
439 3
        if (empty($filename)) {
440
            return 'Error: no filename';
441
        }
442
443
        $chunkUri = $this->getChunkUri('streamed', $filename, $folderId, $unzip, $overwrite, $notify, true, $stream);
444 3
        $chunkSize = $chunkSize ?? SELF::DEFAULT_CHUNK_SIZE;
445
        $index = 0;
446
447
        // First Chunk
448
        $data = $this->readChunk($stream, $chunkSize);
449
        while (!((strlen($data) < $chunkSize) || feof($stream))) {
450
            $parameters = $this->buildHttpQuery(
451
                [
452
                    'index'      => $index,
453
                    'byteOffset' => $index * $chunkSize,
454
                    'hash'       => md5($data),
455 3
                ]
456
            );
457 3
458
            $response = $this->uploadChunk("{$chunkUri['ChunkUri']}&{$parameters}", $data);
459 3
460
            if ($response != 'true') {
461
                return $response;
462
            }
463
464 3
            // Next chunk
465
            $index++;
466
            $data = $this->readChunk($stream, $chunkSize);
467
        }
468
469
        // Final chunk
470
        $parameters = $this->buildHttpQuery(
471
            [
472
                'index'      => $index,
473
                'byteOffset' => $index * $chunkSize,
474 3
                'hash'       => md5($data),
475
                'filehash'   => Psr7\hash(Psr7\stream_for($stream), 'md5'),
0 ignored issues
show
Bug introduced by
The function stream_for was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

475
                'filehash'   => Psr7\hash(/** @scrutinizer ignore-call */ Psr7\stream_for($stream), 'md5'),
Loading history...
476 3
                'finish'     => true,
477
            ]
478
        );
479
480
        return $this->uploadChunk("{$chunkUri['ChunkUri']}&{$parameters}", $data);
481
    }
482
483
    /**
484
     * Get Thumbnail of an item.
485
     *
486
     * @param  string  $itemId  Item id
487 3
     * @param  int  $size  Thumbnail size: THUMBNAIL_SIZE_M or THUMBNAIL_SIZE_L (optional)
488
     *
489 3
     * @return array
490
     */
491 3
    public function getThumbnailUrl(string $itemId, int $size = 75): array
492
    {
493
        $parameters = $this->buildHttpQuery(
494
            [
495
                'size'     => $size,
496 3
                'redirect' => false,
497
            ]
498
        );
499
500
        return $this->get("Items({$itemId})/Thumbnail?{$parameters}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get('Items...humbnail?'.$parameters) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
501
    }
502
503
    /**
504
     * Get browser link for an item.
505
     *
506
     * @param  string  $itemId  Item id
507 6
     *
508
     * @return array
509 6
     */
510 3
    public function getWebAppLink(string $itemId): array
511
    {
512 3
        return $this->post("Items({$itemId})/WebAppLink");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post('Item...$itemId.')/WebAppLink') could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
513
    }
514
515
    /**
516
     * Share Share for external user.
517
     *
518
     * @param  array  $options  Share options
519
     * @param  bool  $notify  Indicates whether user will be notified if item is downloaded (optional)
520
     *
521
     * @return array
522
     */
523 63
    public function createShare(array $options, $notify = false): array
524
    {
525 63
        $parameters = $this->buildHttpQuery(
526
            [
527
                'notify' => $notify,
528
                'direct' => true,
529
            ]
530
        );
531
532
        return $this->post("Shares?{$parameters}", $options);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post('Shar....$parameters, $options) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
533
    }
534
535
    /**
536
     * Get AccessControl List for an item.
537
     *
538
     * @param  string  $itemId  Id of an item
539 63
     * @param  string  $userId  Id of an user
540
     *
541 63
     * @return array
542 63
     */
543
    public function getItemAccessControls(string $itemId, string $userId = ''): array
544
    {
545 63
        if (!empty($userId)) {
546
            return $this->get("AccessControls(principalid={$userId},itemid={$itemId})");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get('Acces...',itemid='.$itemId.')') could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
547
        } else {
548
            return $this->get("Items({$itemId})/AccessControls");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get('Items...mId.')/AccessControls') could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
549
        }
550 63
    }
551
552 63
    /**
553
     * Build API uri.
554
     *
555
     * @param  string  $endpoint  API endpoint
556
     *
557
     * @return string
558
     */
559
    protected function buildUri(string $endpoint): string
560
    {
561
        return "https://{$this->token['subdomain']}.sf-api.com/sf/v3/{$endpoint}";
562 30
    }
563
564 30
    /**
565
     * Make a request to the API.
566
     *
567
     * @param  string  $method  HTTP Method
568
     * @param  string  $endpoint  API endpoint
569
     * @param  mixed|string|array  $json  POST body (optional)
570
     *
571
     * @return mixed
572
     * @throws Exception
573
     *
574
     */
575 27
    protected function request(string $method, string $endpoint, $json = null)
576
    {
577 27
        $uri = $this->buildUri($endpoint);
578
        $options = $json != null ? ['json' => $json] : [];
579
580
        try {
581
            $response = $this->client->request($method, $uri, $options);
582
        } catch (ClientException $exception) {
583
            throw $this->determineException($exception);
584
        }
585
586
        $body = (string) $response->getBody();
587
588 3
        return $this->jsonValidator($body) ? json_decode($body, true) : $body;
589
    }
590 3
591
    /**
592
     * Shorthand for GET-request.
593
     *
594
     * @param  string  $endpoint  API endpoint
595
     *
596
     * @return mixed
597
     */
598
    protected function get(string $endpoint)
599
    {
600 3
        return $this->request('GET', $endpoint);
601
    }
602 3
603
    /**
604
     * Shorthand for POST-request.
605
     *
606
     * @param  string  $endpoint  API endpoint
607
     * @param  mixed|string|array  $json  POST body (optional)
608
     *
609
     * @return mixed
610
     */
611
    protected function post(string $endpoint, $json = null)
612
    {
613 3
        return $this->request('POST', $endpoint, $json);
614
    }
615 3
616 3
    /**
617 3
     * Shorthand for PATCH-request.
618
     *
619
     * @param  string  $endpoint  API endpoint
620 3
     * @param  mixed|string|array  $json  POST body (optional)
621 3
     *
622
     * @return mixed
623 3
     */
624
    protected function patch(string $endpoint, $json = null)
625
    {
626
        return $this->request('PATCH', $endpoint, $json);
627 3
    }
628
629
    /**
630
     * Shorthand for DELETE-request.
631
     *
632
     * @param  string  $endpoint  API endpoint
633
     *
634
     * @return string|array
635
     */
636
    protected function delete(string $endpoint)
637
    {
638
        return $this->request('DELETE', $endpoint);
639
    }
640
641 3
    /**
642
     * Upload a chunk of data using HTTP POST body.
643 3
     *
644 3
     * @param  string  $uri  Upload URI
645 3
     * @param  string  $data  Contents to upload
646 3
     *
647
     * @return string|array
648
     */
649 3
    protected function uploadChunk($uri, $data)
650 3
    {
651
        $response = $this->client->request(
652
            'POST',
653 3
            $uri,
654
            [
655
                'headers' => [
656
                    'Content-Length' => strlen($data),
657
                    'Content-Type'   => 'application/octet-stream',
658
                ],
659
                'body'    => $data,
660
            ]
661
        );
662
663
        return (string) $response->getBody();
664
    }
665
666
    /**
667
     * Sometimes fread() returns less than the request number of bytes (for example, when reading
668
     * from network streams).  This function repeatedly calls fread until the requested number of
669
     * bytes have been read or we've reached EOF.
670
     *
671
     * @param  resource  $stream
672
     * @param  int  $chunkSize
673
     *
674
     * @return string
675
     * @throws Exception
676
     */
677
    protected function readChunk($stream, int $chunkSize)
678
    {
679 39
        $chunk = '';
680
        while (!feof($stream) && $chunkSize > 0) {
681 39
            $part = fread($stream, $chunkSize);
682 39
            if ($part === false) {
683 39
                throw new Exception('Error reading from $stream.');
684 39
            }
685 18
            $chunk .= $part;
686
            $chunkSize -= strlen($part);
687
        }
688 39
689 39
        return $chunk;
690 39
    }
691
692
    /**
693
     * Handle ClientException.
694
     *
695
     * @param  ClientException  $exception  ClientException
696
     *
697
     * @return Exception
698
     */
699
    protected function determineException(ClientException $exception): Exception
700
    {
701
        if (in_array($exception->getResponse()->getStatusCode(), [400, 403, 404, 409])) {
702 63
            return new BadRequest($exception->getResponse());
0 ignored issues
show
Bug introduced by
It seems like $exception->getResponse() can also be of type null; however, parameter $response of Kapersoft\Sharefile\Exce...dRequest::__construct() does only seem to accept Psr\Http\Message\ResponseInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

702
            return new BadRequest(/** @scrutinizer ignore-type */ $exception->getResponse());
Loading history...
703
        }
704 63
705 60
        return $exception;
706
    }
707 60
708
    /**
709
     * Build HTTP query.
710 3
     *
711
     * @param  array  $parameters  Query parameters
712
     *
713
     * @return string
714
     */
715
    protected function buildHttpQuery(array $parameters): string
716
    {
717
        return http_build_query(
718
            array_map(
719
                function ($parameter) {
720
                    if (!is_bool($parameter)) {
721
                        return $parameter;
722
                    }
723
724
                    return $parameter ? 'true' : 'false';
725
                },
726
                $parameters
727
            )
728
        );
729
    }
730
731
    /**
732
     * Validate JSON.
733
     *
734
     * @param  mixed  $data  JSON variable
735
     *
736
     * @return bool
737
     */
738
    protected function jsonValidator($data = null): bool
739
    {
740
        if (!empty($data)) {
741
            @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

741
            /** @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...
742
743
            return json_last_error() === JSON_ERROR_NONE;
744
        }
745
746
        return false;
747
    }
748
}
749