Test Failed
Pull Request — master (#12)
by
unknown
14:04
created

Client::getThumbnailUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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

482
                'filehash'   => Psr7\hash(/** @scrutinizer ignore-call */ Psr7\stream_for($stream), 'md5'),
Loading history...
483
                'finish'    => true,
484
            ]
485
        );
486
487 3
        return $this->uploadChunk("{$chunkUri['ChunkUri']}&{$parameters}", $data);
488
    }
489 3
490
    /**
491 3
     * Get Thumbnail of an item.
492
     *
493
     * @param string $itemId Item id
494
     * @param int    $size   Thumbnail size: THUMBNAIL_SIZE_M or THUMBNAIL_SIZE_L (optional)
495
     *
496 3
     * @return array
497
     */
498
    public function getThumbnailUrl(string $itemId, int $size = 75):array
499
    {
500
        $parameters = $this->buildHttpQuery(
501
            [
502
                'size'     => $size,
503
                'redirect' => false,
504
            ]
505
        );
506
507 6
        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...
508
    }
509 6
510 3
    /**
511
     * Get browser link for an item.
512 3
     *
513
     * @param string $itemId Item id
514
     *
515
     * @return array
516
     */
517
    public function getWebAppLink(string $itemId):array
518
    {
519
        return $this->post("Items({$itemId})/WebAppLink");
520
    }
521
522
    /**
523 63
     * Share Share for external user.
524
     *
525 63
     * @param array $options Share options
526
     * @param bool  $notify  Indicates whether user will be notified if item is downloaded (optional)
527
     *
528
     * @return array
529
     */
530
    public function createShare(array $options, $notify = false):array
531
    {
532
        $parameters = $this->buildHttpQuery(
533
            [
534
                'notify' => $notify,
535
                'direct' => true,
536
            ]
537
        );
538
539 63
        return $this->post("Shares?{$parameters}", $options);
540
    }
541 63
542 63
    /**
543
     * Get AccessControl List for an item.
544
     *
545 63
     * @param string $itemId Id of an item
546
     * @param string $userId Id of an user
547
     *
548
     * @return array
549
     */
550 63
    public function getItemAccessControls(string $itemId, string $userId = ''):array
551
    {
552 63
        if (! empty($userId)) {
553
            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...
554
        } else {
555
            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...
556
        }
557
    }
558
559
    /**
560
     * Build API uri.
561
     *
562 30
     * @param string $endpoint API endpoint
563
     *
564 30
     * @return string
565
     */
566
    protected function buildUri(string $endpoint): string
567
    {
568
        return  "https://{$this->token['subdomain']}.sf-api.com/sf/v3/{$endpoint}";
569
    }
570
571
    /**
572
     * Make a request to the API.
573
     *
574
     * @param string             $method   HTTP Method
575 27
     * @param string             $endpoint API endpoint
576
     * @param mixed|string|array $json     POST body (optional)
577 27
     *
578
     * @throws Exception
579
     *
580
     * @return mixed
581
     */
582
    protected function request(string $method, string $endpoint, $json = null)
583
    {
584
        $uri = $this->buildUri($endpoint);
585
        $options = $json != null ? ['json' => $json] : [];
586
587
        try {
588 3
            $response = $this->client->request($method, $uri, $options);
589
        } catch (ClientException $exception) {
590 3
            throw $this->determineException($exception);
591
        }
592
593
        $body = (string) $response->getBody();
594
595
        return $this->jsonValidator($body) ? json_decode($body, true) : $body;
596
    }
597
598
    /**
599
     * Shorthand for GET-request.
600 3
     *
601
     * @param string $endpoint API endpoint
602 3
     *
603
     * @return mixed
604
     */
605
    protected function get(string $endpoint)
606
    {
607
        return $this->request('GET', $endpoint);
608
    }
609
610
    /**
611
     * Shorthand for POST-request.
612
     *
613 3
     * @param string             $endpoint API endpoint
614
     * @param mixed|string|array $json     POST body (optional)
615 3
     *
616 3
     * @return mixed
617 3
     */
618
    protected function post(string $endpoint, $json = null)
619
    {
620 3
        return $this->request('POST', $endpoint, $json);
621 3
    }
622
623 3
    /**
624
     * Shorthand for PATCH-request.
625
     *
626
     * @param string             $endpoint API endpoint
627 3
     * @param mixed|string|array $json     POST body (optional)
628
     *
629
     * @return mixed
630
     */
631
    protected function patch(string $endpoint, $json = null)
632
    {
633
        return $this->request('PATCH', $endpoint, $json);
634
    }
635
636
    /**
637
     * Shorthand for DELETE-request.
638
     *
639
     * @param string $endpoint API endpoint
640
     *
641 3
     * @return string|array
642
     */
643 3
    protected function delete(string $endpoint)
644 3
    {
645 3
        return $this->request('DELETE', $endpoint);
646 3
    }
647
648
    /**
649 3
     * Upload a chunk of data using HTTP POST body.
650 3
     *
651
     * @param string $uri  Upload URI
652
     * @param string $data Contents to upload
653 3
     *
654
     * @return string|array
655
     */
656
    protected function uploadChunk($uri, $data)
657
    {
658
        $response = $this->client->request(
659
            'POST',
660
            $uri,
661
            [
662
                'headers' => [
663
                    'Content-Length' => strlen($data),
664
                    'Content-Type'   => 'application/octet-stream',
665
                ],
666
                'body' => $data,
667
            ]
668
        );
669
670
        return (string) $response->getBody();
671
    }
672
673
    /**
674
     * Sometimes fread() returns less than the request number of bytes (for example, when reading
675
     * from network streams).  This function repeatedly calls fread until the requested number of
676
     * bytes have been read or we've reached EOF.
677
     *
678
     * @param resource $stream
679 39
     * @param int      $chunkSize
680
     *
681 39
     * @throws Exception
682 39
     * @return string
683 39
     */
684 39
    protected function readChunk($stream, int $chunkSize)
685 18
    {
686
        $chunk = '';
687
        while (! feof($stream) && $chunkSize > 0) {
688 39
            $part = fread($stream, $chunkSize);
689 39
            if ($part === false) {
690 39
                throw new Exception('Error reading from $stream.');
691
            }
692
            $chunk .= $part;
693
            $chunkSize -= strlen($part);
694
        }
695
696
        return $chunk;
697
    }
698
699
    /**
700
     * Handle ClientException.
701
     *
702 63
     * @param ClientException $exception ClientException
703
     *
704 63
     * @return Exception
705 60
     */
706
    protected function determineException(ClientException $exception): Exception
707 60
    {
708
        if (in_array($exception->getResponse()->getStatusCode(), [400, 403, 404, 409])) {
709
            return new BadRequest($exception->getResponse());
710 3
        }
711
712
        return $exception;
713
    }
714
715
    /**
716
     * Build HTTP query.
717
     *
718
     * @param array $parameters Query parameters
719
     *
720
     * @return string
721
     */
722
    protected function buildHttpQuery(array $parameters):string
723
    {
724
        return http_build_query(
725
            array_map(
726
                function ($parameter) {
727
                    if (! is_bool($parameter)) {
728
                        return $parameter;
729
                    }
730
731
                    return $parameter ? 'true' : 'false';
732
                },
733
                $parameters
734
            )
735
        );
736
    }
737
738
    /**
739
     * Validate JSON.
740
     *
741
     * @param mixed $data JSON variable
742
     *
743
     * @return bool
744
     */
745
    protected function jsonValidator($data = null):bool
746
    {
747
        if (! empty($data)) {
748
            @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

748
            /** @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...
749
750
            return json_last_error() === JSON_ERROR_NONE;
751
        }
752
753
        return false;
754
    }
755
}
756