Test Failed
Pull Request — master (#13)
by
unknown
14:35
created

Client::authenticate()   B

Complexity

Conditions 3
Paths 1

Size

Total Lines 26
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 16
cts 16
cp 1
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 18
nc 1
nop 6
crap 3
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\Exception\ClientException;
10
use Kapersoft\Sharefile\Exceptions\BadRequest;
11
use Slacker775\OAuth2\Client\Provider\ShareFile as AuthProvider;
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
     *
32
     * @var AbstractProvider
0 ignored issues
show
Bug introduced by
The type Kapersoft\ShareFile\AbstractProvider was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
33
     */
34
    protected $authProvider;
35
36
    /**
37
     *
38
     * @var AccessToken
0 ignored issues
show
Bug introduced by
The type Kapersoft\ShareFile\AccessToken was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
39
     */
40
    protected $accessToken;
41
42
    /**
43
     *
44
     * @var array
45
     */
46
    protected $options;
47
48
    /**
49
     * Thumbnail size.
50
     */
51
    const THUMBNAIL_SIZE_M = 75;
52
    const THUMBNAIL_SIZE_L = 600;
53
54
    /*
55
     * ShareFile Folder
56
     */
57
    const FOLDER_TOP = 'top';
58
    const FOLDER_HOME = 'home';
59
    const FOLDER_FAVORITES = 'favorites';
60
    const FOLDER_ALLSHARED = 'allshared';
61
62
    /*
63
    * Default Chunk Size for uploading files
64
    */
65
    const DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024; // 8 megabytes
66
67
    /**
68 75
     * Client constructor.
69
     *
70 75
     * @param string                   $hostname      ShareFile hostname
71
     * @param string                   $client_id     OAuth2 client_id
72 69
     * @param string                   $client_secret OAuth2 client_secret
73 3
     * @param string                   $username      ShareFile username
74
     * @param string                   $password      ShareFile password
75
     * @param MockHandler|HandlerStack $handler       Guzzle Handler
76 66
     *
77 66
     * @throws Exception
78
     */
79 66
    public function __construct(string $hostname, string $client_id, string $client_secret, string $username, string $password, $handler = null)
0 ignored issues
show
Unused Code introduced by
The parameter $handler is not used and could be removed. ( Ignorable by Annotation )

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

79
    public function __construct(string $hostname, string $client_id, string $client_secret, string $username, string $password, /** @scrutinizer ignore-unused */ $handler = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
80
    {
81 66
        $this->authProvider = new AuthProvider([
0 ignored issues
show
Documentation Bug introduced by
It seems like new Slacker775\OAuth2\Cl...et' => $client_secret)) of type Slacker775\OAuth2\Client\Provider\ShareFile is incompatible with the declared type Kapersoft\ShareFile\AbstractProvider of property $authProvider.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
82
            'clientId' => $client_id,
83
            'clientSecret' => $client_secret
84
        ]);
85 66
86
        $this->options = [
87
            'username' => $username,
88
            'password' => $password,
89
            'baseUrl' => $hostname,
90
        ];
91
92
    }
93
94
    /**
95
     * Get user details.
96
     *
97
     * @param string $userId ShareFile user id (optional)
98
     *
99
     * @return array
100
     */
101 75
    public function getUser(string $userId = ''):array
102
    {
103 75
        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...
104
    }
105
106 75
    /**
107 75
     * Create a folder.
108 75
     *
109 75
     * @param string $parentId    Id of the parent folder
110 75
     * @param string $name        Name
111
     * @param string $description Description
112
     * @param bool   $overwrite   Overwrite folder
113
     *
114 75
     * @return array
115 75
     */
116 75
    public function createFolder(string $parentId, string $name, string $description = '', bool $overwrite = false):array
117 75
    {
118
        $parameters = $this->buildHttpQuery(
119 3
            [
120 3
                'overwrite'   => $overwrite,
121
                'passthrough' => false,
122
            ]
123 72
        );
124 69
125
        $data = [
126 3
            'name'        => $name,
127
            'description' => $description,
128
        ];
129
130
        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...
131
    }
132
133
    /**
134
     * Get Folder/File using Id.
135
     *
136
     * @param string $itemId      Item id
137 3
     * @param bool   $getChildren Include children
138
     *
139 3
     * @return array
140
     */
141
    public function getItemById(string $itemId, bool $getChildren = false):array
142
    {
143
        $parameters = $getChildren === true ? '$expand=Children' : '';
144
145
        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...
146
    }
147
148
    /**
149
     * Get Folder/File using path.
150
     *
151
     * @param string $path   Path
152 6
     * @param string $itemId Id of the root folder (optional)
153
     *
154 6
     * @return array
155
     */
156 6
    public function getItemByPath(string $path, string $itemId = ''):array
157
    {
158
        if (empty($itemId)) {
159
            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...
160
        } else {
161
            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...
162 6
        }
163 6
    }
164
165
    /**
166 6
     * Get breadcrumps of an item.
167
     *
168
     * @param string $itemId Item Id
169
     *
170
     * @return array
171
     */
172
    public function getItemBreadcrumps(string $itemId):array
173
    {
174
        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...
175
    }
176
177 6
    /**
178
     * Copy an item.
179 6
     *
180
     * @param string $targetId  Id of the target folder
181 6
     * @param string $itemId    Id of the copied item
182
     * @param bool   $overwrite Indicates whether items with the same name will be overwritten or not (optional)
183
     *
184
     * @return array
185
     */
186
    public function copyItem(string $targetId, string $itemId, bool $overwrite = false):array
187
    {
188
        $parameters = $this->buildHttpQuery(
189
            [
190
                'targetid'  => $targetId,
191
                'overwrite' => $overwrite,
192 3
            ]
193
        );
194 3
195 3
        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...
196
    }
197
198
    /**
199
     * Update an item.
200
     *
201
     * @param string $itemId    Id of the item
202
     * @param array  $data      New data
203
     * @param bool   $forceSync Indicates whether operation is to be executed synchronously (optional)
204
     * @param bool   $notify    Indicates whether an email should be sent to users subscribed to Upload Notifications (optional)
205
     *
206
     * @return array
207
     */
208 3
    public function updateItem(string $itemId, array $data, bool $forceSync = true, bool $notify = true):array
209
    {
210 3
        $parameters = $this->buildHttpQuery(
211
            [
212
                'forceSync' => $forceSync,
213
                'notify'    => $notify,
214
            ]
215
        );
216
217
        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...
218
    }
219
220
    /**
221
     * Delete an item.
222 6
     *
223
     * @param string $itemId        Item id
224 6
     * @param bool   $singleversion True it will delete only the specified version rather than all sibling files with the same filename (optional)
225
     * @param bool   $forceSync     True will block the operation from taking place asynchronously (optional)
226 6
     *
227 6
     * @return string
228
     */
229
    public function deleteItem(string $itemId, bool $singleversion = false, bool $forceSync = false):string
230
    {
231 6
        $parameters = $this->buildHttpQuery(
232
            [
233
                'singleversion' => $singleversion,
234
                'forceSync'     => $forceSync,
235
            ]
236
        );
237
238
        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...
239
    }
240
241
    /**
242
     * Get temporary download URL for an item.
243
     *
244 3
     * @param string $itemId             Item id
245
     * @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)
246 3
     *
247
     * @return array
248 3
     */
249 3
    public function getItemDownloadUrl(string $itemId, bool $includeallversions = false):array
250
    {
251
        $parameters = $this->buildHttpQuery(
252
            [
253 3
                'includeallversions' => $includeallversions,
254
                'redirect'           => false,
255
            ]
256
        );
257
258
        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...
259
    }
260
261
    /**
262
     * Get contents of and item.
263
     *
264
     * @param string $itemId             Item id
265 3
     * @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)
266
     *
267 3
     * @return mixed
268
     */
269 3
    public function getItemContents(string $itemId, bool $includeallversions = false)
270 3
    {
271
        $parameters = $this->buildHttpQuery(
272
            [
273
                'includeallversions' => $includeallversions,
274 3
                'redirect'           => true,
275
            ]
276
        );
277
278
        return $this->get("Items({$itemId})/Download?{$parameters}");
279
    }
280
281
    /**
282
     * Get the Chunk Uri to start a file-upload.
283
     *
284
     * @param string   $method    Upload method (Standard or Streamed)
285 3
     * @param string   $filename  Name of file
286
     * @param string   $folderId  Id of the parent folder
287 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)
288
     * @param bool     $overwrite Indicates whether items with the same name will be overwritten or not (optional)
289 3
     * @param bool     $notify    Indicates whether users will be notified of this upload - based on folder preferences (optional)
290
     * @param bool     $raw       Send contents contents directly in the POST body (=true) or send contents in MIME format (=false) (optional)
291
     * @param resource $stream    Resource stream of the contents (optional)
292
     *
293
     * @return array
294 3
     */
295
    public function getChunkUri(string $method, string $filename, string $folderId, bool $unzip = false, $overwrite = true, bool $notify = true, bool $raw = false, $stream = null):array
296
    {
297
        $parameters = $this->buildHttpQuery(
298
            [
299
                'method'                => $method,
300
                'raw'                   => $raw,
301
                'fileName'              => basename($filename),
302
                'fileSize'              => $stream == null ? filesize($filename) : fstat($stream)['size'],
303
                'canResume'             => false,
304
                'startOver'             => false,
305 3
                'unzip'                 => $unzip,
306
                'tool'                  => 'apiv3',
307 3
                'overwrite'             => $overwrite,
308
                'title'                 => basename($filename),
309 3
                'isSend'                => false,
310
                'responseFormat'        => 'json',
311
                'notify'                => $notify,
312
                'clientCreatedDateUTC'  => $stream == null ? filectime($filename) : fstat($stream)['ctime'],
313
                'clientModifiedDateUTC' => $stream == null ? filemtime($filename) : fstat($stream)['mtime'],
314 3
            ]
315
        );
316
317
        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...
318
    }
319
320
    /**
321
     * Upload a file using a single HTTP POST.
322
     *
323
     * @param string $filename  Name of file
324
     * @param string $folderId  Id of the parent folder
325
     * @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)
326
     * @param bool   $overwrite Indicates whether items with the same name will be overwritten or not (optional)
327
     * @param bool   $notify    Indicates whether users will be notified of this upload - based on folder preferences (optional)
328
     *
329
     * @return string
330
     */
331 9
    public function uploadFileStandard(string $filename, string $folderId, bool $unzip = false, bool $overwrite = true, bool $notify = true):string
332
    {
333 9
        $chunkUri = $this->getChunkUri('standard', $filename, $folderId, $unzip, $overwrite, $notify);
334
335 9
        $request = $this->authProvider->getAuthenticatedRequest(
336 9
            'POST',
337 9
            $chunkUri['ChunkUri'],
338 9
            $this->accessToken,
339
            [
340
                'multipart' => [
341 9
                    [
342 9
                        'name'     => 'File1',
343 9
                        'contents' => fopen($filename, 'r'),
344 9
                    ],
345
                ],
346 9
            ]
347 9
        );
348 9
        $response = $this->authProvider->getResponse($request);
349 9
350
        return (string) $response->getBody();
351
    }
352
353 9
    /**
354
     * Upload a file using multiple HTTP POSTs.
355
     *
356
     * @param mixed    $stream    Stream resource
357
     * @param string   $folderId  Id of the parent folder
358
     * @param string   $filename  Filename (optional)
359
     * @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)
360
     * @param bool     $overwrite Indicates whether items with the same name will be overwritten or not (optional)
361
     * @param bool     $notify    Indicates whether users will be notified of this upload - based on folder preferences (optional)
362
     * @param int      $chunkSize Maximum size of the individual HTTP posts in bytes
363
     *
364
     * @return string
365
     */
366
    public function uploadFileStreamed($stream, string $folderId, string $filename = null, bool $unzip = false, bool $overwrite = true, bool $notify = true, int $chunkSize = null):string
367 3
    {
368
        $filename = $filename ?? stream_get_meta_data($stream)['uri'];
369 3
        if (empty($filename)) {
370
            return 'Error: no filename';
371 3
        }
372 3
373 3
        $chunkUri = $this->getChunkUri('streamed', $filename, $folderId, $unzip, $overwrite, $notify, true, $stream);
374
        $chunkSize = $chunkSize ?? SELF::DEFAULT_CHUNK_SIZE;
375
        $index = 0;
376
377 3
        // First Chunk
378 3
        $data = $this->readChunk($stream, $chunkSize);
379
        while (! ((strlen($data) < $chunkSize) || feof($stream))) {
380
            $parameters = $this->buildHttpQuery(
381
                [
382
                    'index'      => $index,
383
                    'byteOffset' => $index * $chunkSize,
384 3
                    'hash'       => md5($data),
385
                ]
386
            );
387
388
            $response = $this->uploadChunk("{$chunkUri['ChunkUri']}&{$parameters}", $data);
389
390
            if ($response != 'true') {
391
                return $response;
392
            }
393
394
            // Next chunk
395
            $index++;
396
            $data = $this->readChunk($stream, $chunkSize);
397
        }
398
399
        // Final chunk
400 3
        $parameters = $this->buildHttpQuery(
401
            [
402 3
                'index'      => $index,
403 3
                'byteOffset' => $index * $chunkSize,
404
                'hash'       => md5($data),
405
                '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

405
                'filehash'   => Psr7\hash(/** @scrutinizer ignore-call */ Psr7\stream_for($stream), 'md5'),
Loading history...
406
                'finish'    => true,
407 3
            ]
408 3
        );
409 3
410
        return $this->uploadChunk("{$chunkUri['ChunkUri']}&{$parameters}", $data);
411
    }
412 3
413 3
    /**
414 3
     * Get Thumbnail of an item.
415
     *
416 3
     * @param string $itemId Item id
417 3
     * @param int    $size   Thumbnail size: THUMBNAIL_SIZE_M or THUMBNAIL_SIZE_L (optional)
418 3
     *
419
     * @return array
420
     */
421
    public function getThumbnailUrl(string $itemId, int $size = 75):array
422 3
    {
423
        $parameters = $this->buildHttpQuery(
424 3
            [
425
                'size'     => $size,
426
                'redirect' => false,
427
            ]
428
        );
429 3
430 3
        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...
431
    }
432
433
    /**
434 3
     * Get browser link for an item.
435
     *
436 3
     * @param string $itemId Item id
437 3
     *
438 3
     * @return array
439 3
     */
440
    public function getWebAppLink(string $itemId):array
441
    {
442
        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...
443
    }
444 3
445
    /**
446
     * Share Share for external user.
447
     *
448
     * @param array $options Share options
449
     * @param bool  $notify  Indicates whether user will be notified if item is downloaded (optional)
450
     *
451
     * @return array
452
     */
453
    public function createShare(array $options, $notify = false):array
454
    {
455 3
        $parameters = $this->buildHttpQuery(
456
            [
457 3
                'notify' => $notify,
458
                'direct' => true,
459 3
            ]
460
        );
461
462
        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...
463
    }
464 3
465
    /**
466
     * Get AccessControl List for an item.
467
     *
468
     * @param string $itemId Id of an item
469
     * @param string $userId Id of an user
470
     *
471
     * @return array
472
     */
473
    public function getItemAccessControls(string $itemId, string $userId = ''):array
474 3
    {
475
        if (! empty($userId)) {
476 3
            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...
477
        } else {
478
            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...
479
        }
480
    }
481
482
    /**
483
     * Build API uri.
484
     *
485
     * @param string $endpoint API endpoint
486
     *
487 3
     * @return string
488
     */
489 3
    protected function buildUri(string $endpoint): string
490
    {
491 3
        return  "https://{$this->accessToken->getValues()['subdomain']}.sf-api.com/sf/v3/{$endpoint}";
492
    }
493
494
    /**
495
     * Make a request to the API.
496 3
     *
497
     * @param string             $method   HTTP Method
498
     * @param string             $endpoint API endpoint
499
     * @param mixed|string|array $json     POST body (optional)
500
     *
501
     * @throws Exception
502
     *
503
     * @return mixed
504
     */
505
    protected function request(string $method, string $endpoint, $json = null)
506
    {
507 6
        if (is_null($this->accessToken)) {
508
            $this->accessToken = $this->authProvider->getAccessToken('password', [
509 6
                'username' => $this->options['username'],
510 3
                'password' => $this->options['password'],
511
                'baseUrl' => $this->options['baseUrl']
512 3
            ]);
513
        }
514
515
        $uri = $this->buildUri($endpoint);
516
        $options = $json != null ? ['json' => $json] : [];
517
518
        try {
519
            $request = $this->authProvider->getAuthenticatedRequest($method, $uri, $this->accessToken, $options);
520
            $response = $this->authProvider->getResponse($request);
521
        } catch (ClientException $exception) {
522
            throw $this->determineException($exception);
523 63
        }
524
525 63
        $body = (string) $response->getBody();
526
527
        return $this->jsonValidator($body) ? json_decode($body, true) : $body;
528
    }
529
530
    /**
531
     * Shorthand for GET-request.
532
     *
533
     * @param string $endpoint API endpoint
534
     *
535
     * @return mixed
536
     */
537
    protected function get(string $endpoint)
538
    {
539 63
        return $this->request('GET', $endpoint);
540
    }
541 63
542 63
    /**
543
     * Shorthand for POST-request.
544
     *
545 63
     * @param string             $endpoint API endpoint
546
     * @param mixed|string|array $json     POST body (optional)
547
     *
548
     * @return mixed
549
     */
550 63
    protected function post(string $endpoint, $json = null)
551
    {
552 63
        return $this->request('POST', $endpoint, $json);
553
    }
554
555
    /**
556
     * Shorthand for PATCH-request.
557
     *
558
     * @param string             $endpoint API endpoint
559
     * @param mixed|string|array $json     POST body (optional)
560
     *
561
     * @return mixed
562 30
     */
563
    protected function patch(string $endpoint, $json = null)
564 30
    {
565
        return $this->request('PATCH', $endpoint, $json);
566
    }
567
568
    /**
569
     * Shorthand for DELETE-request.
570
     *
571
     * @param string $endpoint API endpoint
572
     *
573
     * @return string|array
574
     */
575 27
    protected function delete(string $endpoint)
576
    {
577 27
        return $this->request('DELETE', $endpoint);
578
    }
579
580
    /**
581
     * Upload a chunk of data using HTTP POST body.
582
     *
583
     * @param string $uri  Upload URI
584
     * @param string $data Contents to upload
585
     *
586
     * @return string|array
587
     */
588 3
    protected function uploadChunk($uri, $data)
589
    {
590 3
        $request = $this->authProvider->getAuthenticatedRequest(
591
            'POST',
592
            $uri,
593
            $this->accessToken,
594
            [
595
                'headers' => [
596
                    'Content-Length' => strlen($data),
597
                    'Content-Type'   => 'application/octet-stream',
598
                ],
599
                'body' => $data,
600 3
            ]
601
        );
602 3
        $response = $this->authProvider->getResponse($request);
603
604
        return (string) $response->getBody();
605
    }
606
607
    /**
608
     * Sometimes fread() returns less than the request number of bytes (for example, when reading
609
     * from network streams).  This function repeatedly calls fread until the requested number of
610
     * bytes have been read or we've reached EOF.
611
     *
612
     * @param resource $stream
613 3
     * @param int      $chunkSize
614
     *
615 3
     * @throws Exception
616 3
     * @return string
617 3
     */
618
    protected function readChunk($stream, int $chunkSize)
619
    {
620 3
        $chunk = '';
621 3
        while (! feof($stream) && $chunkSize > 0) {
622
            $part = fread($stream, $chunkSize);
623 3
            if ($part === false) {
624
                throw new Exception('Error reading from $stream.');
625
            }
626
            $chunk .= $part;
627 3
            $chunkSize -= strlen($part);
628
        }
629
630
        return $chunk;
631
    }
632
633
    /**
634
     * Handle ClientException.
635
     *
636
     * @param ClientException $exception ClientException
637
     *
638
     * @return Exception
639
     */
640
    protected function determineException(ClientException $exception): Exception
641 3
    {
642
        if (in_array($exception->getResponse()->getStatusCode(), [400, 403, 404, 409])) {
643 3
            return new BadRequest($exception->getResponse());
644 3
        }
645 3
646 3
        return $exception;
647
    }
648
649 3
    /**
650 3
     * Build HTTP query.
651
     *
652
     * @param array $parameters Query parameters
653 3
     *
654
     * @return string
655
     */
656
    protected function buildHttpQuery(array $parameters):string
657
    {
658
        return http_build_query(
659
            array_map(
660
                function ($parameter) {
661
                    if (! is_bool($parameter)) {
662
                        return $parameter;
663
                    }
664
665
                    return $parameter ? 'true' : 'false';
666
                },
667
                $parameters
668
            )
669
        );
670
    }
671
672
    /**
673
     * Validate JSON.
674
     *
675
     * @param mixed $data JSON variable
676
     *
677
     * @return bool
678
     */
679 39
    protected function jsonValidator($data = null):bool
680
    {
681 39
        if (! empty($data)) {
682 39
            @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

682
            /** @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...
683 39
684 39
            return json_last_error() === JSON_ERROR_NONE;
685 18
        }
686
687
        return false;
688 39
    }
689
}
690