Test Failed
Pull Request — master (#12)
by
unknown
10:48
created

Client::getItemDownloadUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

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

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