Passed
Push — master ( 3833e1...4b4cd8 )
by Jan Willem
02:47
created

Client::delete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1.037

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 3
cp 0.6667
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
crap 1.037
1
<?php
2
3
namespace Kapersoft\ShareFile;
4
5
use Exception;
6
use GuzzleHttp\HandlerStack;
7
use GuzzleHttp\Handler\MockHandler;
8
use GuzzleHttp\Client as GuzzleClient;
9
use GuzzleHttp\Exception\ClientException;
10
use Kapersoft\Sharefile\Exceptions\BadRequest;
11
12
/**
13
 * Class Client.
14
 *
15
 * @author   Jan Willem Kaper <[email protected]>
16
 * @license  MIT (see License.txt)
17
 *
18
 * @link     http://github.com/kapersoft/sharefile-api
19
 */
20
class Client
21
{
22
    /**
23
     * ShareFile token.
24
     *
25
     * @var array
26
     */
27
    public $token;
28
29
    /**
30
     * Guzzle Client.
31
     *
32
     * @var \GuzzleHttp\Client
33
     */
34
    public $client;
35
36
    /**
37
     * Thumbnail size.
38
     */
39
    const THUMBNAIL_SIZE_M = 75;
40
    const THUMBNAIL_SIZE_L = 600;
41
42
    /*
43
     * ShareFile Folder
44
     */
45
    const FOLDER_TOP = 'top';
46
    const FOLDER_HOME = 'home';
47
    const FOLDER_FAVORITES = 'favorites';
48
    const FOLDER_ALLSHARED = 'allshared';
49
50
    /**
51
     * Client constructor.
52
     *
53
     * @param string                   $hostname      ShareFile hostname
54
     * @param string                   $client_id     OAuth2 client_id
55
     * @param string                   $client_secret OAuth2 client_secret
56
     * @param string                   $username      ShareFile username
57
     * @param string                   $password      ShareFile password
58
     * @param MockHandler|HandlerStack $handler       Guzzle Handler
59
     *
60
     * @throws Exception
61
     */
62 48
    public function __construct(string $hostname, string $client_id, string $client_secret, string $username, string $password, $handler = null)
63
    {
64 48
        $response = $this->authenticate($hostname, $client_id, $client_secret, $username, $password, $handler);
65
66 44
        if (! isset($response['access_token']) || ! isset($response['subdomain'])) {
67 2
            throw new Exception("Incorrect response from Authentication: 'access_token' or 'subdomain' is missing.");
68
        }
69
70 42
        $this->token = $response;
71 42
        $this->client = new GuzzleClient(
72
            [
73 42
                'handler' => $handler,
74
                'headers' => [
75 42
                    'Authorization' => "Bearer {$this->token['access_token']}",
76
                ],
77
            ]
78
        );
79 42
    }
80
81
    /**
82
     * ShareFile authentication using username/password.
83
     *
84
     * @param string                   $hostname      ShareFile hostname
85
     * @param string                   $client_id     OAuth2 client_id
86
     * @param string                   $client_secret OAuth2 client_secret
87
     * @param string                   $username      ShareFile username
88
     * @param string                   $password      ShareFile password
89
     * @param MockHandler|HandlerStack $handler       Guzzle Handler
90
     *
91
     * @throws Exception
92
     *
93
     * @return array
94
     */
95 48
    protected function authenticate(string $hostname, string $client_id, string $client_secret, string $username, string $password, $handler = null):array
96
    {
97 48
        $uri = "https://{$hostname}/oauth/token";
98
99
        $parameters = [
100 48
            'grant_type'    => 'password',
101 48
            'client_id'     => $client_id,
102 48
            'client_secret' => $client_secret,
103 48
            'username'      => $username,
104 48
            'password'      => $password,
105
        ];
106
107
        try {
108 48
            $client = new GuzzleClient(['handler' => $handler]);
109 48
            $response = $client->post(
110 48
                $uri,
111 48
                ['form_params' => $parameters]
112
            );
113 2
        } catch (ClientException $exception) {
114 2
            throw $exception;
115
        }
116
117 46
        if ($response->getStatusCode() == '200') {
118 44
            return json_decode($response->getBody(), true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return json_decode($response->getBody(), true) returns the type mixed which includes types incompatible with the type-hinted return array.
Loading history...
119
        } else {
120 2
            throw new Exception('Authentication error', $response->getStatusCode());
121
        }
122
    }
123
124
    /**
125
     * Get user details.
126
     *
127
     * @param string $userId ShareFile user id (optional)
128
     *
129
     * @return array
130
     */
131 2
    public function getUser(string $userId = ''):array
132
    {
133 2
        return $this->get("Users({$userId})");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get(EncapsedNode) returns the type string which is incompatible with the type-hinted return array.
Loading history...
134
    }
135
136
    /**
137
     * Create a folder.
138
     *
139
     * @param string $parentId    Id of the parent folder
140
     * @param string $name        Name
141
     * @param string $description Description
142
     * @param bool   $overwrite   Overwrite folder
143
     *
144
     * @return array
145
     */
146 4
    public function createFolder(string $parentId, string $name, string $description = '', bool $overwrite = false):array
147
    {
148 4
        $parameters = $this->buildHttpQuery(
149
            [
150 4
                'overwrite'   => $overwrite,
151
                'passthrough' => false,
152
            ]
153
        );
154
155
        $data = [
156 4
            'name'        => $name,
157 4
            'description' => $description,
158
        ];
159
160 4
        return $this->post("Items({$parentId})/Folder?{$parameters}", $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(EncapsedNode, $data) returns the type string which is incompatible with the type-hinted return array.
Loading history...
161
    }
162
163
    /**
164
     * Get Folder/File using Id.
165
     *
166
     * @param string $itemId      Item id
167
     * @param bool   $getChildren Include children
168
     *
169
     * @return array
170
     */
171 4
    public function getItemById(string $itemId, bool $getChildren = false):array
172
    {
173 4
        $parameters = $getChildren === true ? '$expand=Children' : '';
174
175 4
        return $this->get("Items({$itemId})?{$parameters}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get(EncapsedNode) returns the type string which is incompatible with the type-hinted return array.
Loading history...
176
    }
177
178
    /**
179
     * Get Folder/File using path.
180
     *
181
     * @param string $path   Path
182
     * @param string $itemId Id of the root folder (optional)
183
     *
184
     * @return array
185
     */
186 2 View Code Duplication
    public function getItemByPath(string $path, string $itemId = ''):array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
187
    {
188 2
        if (empty($itemId)) {
189 2
            return $this->get("Items/ByPath?Path={$path}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get(EncapsedNode) returns the type string which is incompatible with the type-hinted return array.
Loading history...
190
        } else {
191
            return $this->get("Items({$itemId})/ByPath?Path={$path}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get(EncapsedNode) returns the type string which is incompatible with the type-hinted return array.
Loading history...
192
        }
193
    }
194
195
    /**
196
     * Get breadcrumps of an item.
197
     *
198
     * @param string $itemId Item Id
199
     *
200
     * @return array
201
     */
202 2
    public function getItemBreadcrumps(string $itemId):array
203
    {
204 2
        return $this->get("Items({$itemId})/Breadcrumbs");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get(EncapsedNode) returns the type string which is incompatible with the type-hinted return array.
Loading history...
205
    }
206
207
    /**
208
     * Copy an item.
209
     *
210
     * @param string $targetId  Id of the target folder
211
     * @param string $itemId    Id of the copied item
212
     * @param bool   $overwrite Indicates whether items with the same name will be overwritten or not (optional)
213
     *
214
     * @return array
215
     */
216 4
    public function copyItem(string $targetId, string $itemId, bool $overwrite = false):array
217
    {
218 4
        $parameters = $this->buildHttpQuery(
219
            [
220 4
                'targetid'  => $targetId,
221 4
                'overwrite' => $overwrite,
222
            ]
223
        );
224
225 4
        return $this->post("Items({$itemId})/Copy?{$parameters}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(EncapsedNode) returns the type string which is incompatible with the type-hinted return array.
Loading history...
226
    }
227
228
    /**
229
     * Update an item.
230
     *
231
     * @param string $itemId    Id of the item
232
     * @param array  $data      New data
233
     * @param bool   $forceSync Indicates whether operation is to be executed synchronously (optional)
234
     * @param bool   $notify    Indicates whether an email should be sent to users subscribed to Upload Notifications (optional)
235
     *
236
     * @return array
237
     */
238 2 View Code Duplication
    public function updateItem(string $itemId, array $data, bool $forceSync = true, bool $notify = true):array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
239
    {
240 2
        $parameters = $this->buildHttpQuery(
241
            [
242 2
                'forceSync' => $forceSync,
243 2
                'notify'    => $notify,
244
            ]
245
        );
246
247 2
        return $this->patch("Items({$itemId})?{$parameters}", $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->patch(EncapsedNode, $data) returns the type string which is incompatible with the type-hinted return array.
Loading history...
248
    }
249
250
    /**
251
     * Delete an item.
252
     *
253
     * @param string $itemId        Item id
254
     * @param bool   $singleversion True it will delete only the specified version rather than all sibling files with the same filename (optional)
255
     * @param bool   $forceSync     True will block the operation from taking place asynchronously (optional)
256
     *
257
     * @return string
258
     */
259 2 View Code Duplication
    public function deleteItem(string $itemId, bool $singleversion = false, bool $forceSync = false):string
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
260
    {
261 2
        $parameters = $this->buildHttpQuery(
262
            [
263 2
                'singleversion' => $singleversion,
264 2
                'forceSync'     => $forceSync,
265
            ]
266
        );
267
268 2
        return $this->delete("Items({$itemId})?{$parameters}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->delete(EncapsedNode) could return the type array which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
269
    }
270
271
    /**
272
     * Get temporary download URL for an item.
273
     *
274
     * @param string $itemId             Item id
275
     * @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)
276
     *
277
     * @return array
278
     */
279 2 View Code Duplication
    public function getItemDownloadUrl(string $itemId, bool $includeallversions = false):array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
280
    {
281 2
        $parameters = $this->buildHttpQuery(
282
            [
283 2
                'includeallversions' => $includeallversions,
284
                'redirect'           => false,
285
            ]
286
        );
287
288 2
        return $this->get("Items({$itemId})/Download?{$parameters}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get(EncapsedNode) returns the type string which is incompatible with the type-hinted return array.
Loading history...
289
    }
290
291
    /**
292
     * Get contents of and item.
293
     *
294
     * @param string $itemId             Item id
295
     * @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)
296
     *
297
     * @return mixed
298
     */
299 2 View Code Duplication
    public function getItemContents(string $itemId, bool $includeallversions = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
300
    {
301 2
        $parameters = $this->buildHttpQuery(
302
            [
303 2
                'includeallversions' => $includeallversions,
304
                'redirect'           => true,
305
            ]
306
        );
307
308 2
        return $this->get("Items({$itemId})/Download?{$parameters}");
309
    }
310
311
    /**
312
     * Get the Chunk Uri to start a file-upload.
313
     *
314
     * @param string $method    Upload method (Standard or Streamed)
315
     * @param string $filename  Name of file
316
     * @param string $folderId  Id of the parent folder
317
     * @param bool   $unzip     Inidicates 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)
318
     * @param bool   $overwrite Indicates whether items with the same name will be overwritten or not (optional)
319
     * @param bool   $notify    Indicates whether users will be notified of this upload - based on folder preferences (optional)
320
     *
321
     * @return array
322
     */
323 4
    public function getChunkUri(string $method, string $filename, string $folderId, bool $unzip = false, $overwrite = true, bool $notify = true):array
324
    {
325 4
        $parameters = $this->buildHttpQuery(
326
            [
327 4
                'method'                => $method,
328
                'raw'                   => false,
329 4
                'fileName'              => basename($filename),
330 4
                'fileSize'              => filesize($filename),
331
                'canResume'             => false,
332
                'startOver'             => false,
333 4
                'unzip'                 => $unzip,
334 4
                'tool'                  => 'apiv3',
335 4
                'overwrite'             => $overwrite,
336 4
                'title'                 => basename($filename),
337
                'isSend'                => false,
338 4
                'responseFormat'        => 'json',
339 4
                'notify'                => $notify,
340 4
                'clientCreatedDateUTC'  => filectime($filename),
341 4
                'clientModifiedDateUTC' => filemtime($filename),
342
            ]
343
        );
344
345 4
        return $this->post("Items({$folderId})/Upload?{$parameters}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(EncapsedNode) returns the type string which is incompatible with the type-hinted return array.
Loading history...
346
    }
347
348
    /**
349
     * Upload a file using a single HTTP POST.
350
     *
351
     * @param string $filename  Name of file
352
     * @param string $folderId  Id of the parent folder
353
     * @param bool   $unzip     Inidicates 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)
354
     * @param bool   $overwrite Indicates whether items with the same name will be overwritten or not (optional)
355
     * @param bool   $notify    Indicates whether users will be notified of this upload - based on folder preferences (optional)
356
     *
357
     * @return string
358
     */
359 2
    public function uploadFileStandard(string $filename, string $folderId, bool $unzip = false, bool $overwrite = true, bool $notify = true):string
360
    {
361 2
        $chunkUri = $this->getChunkUri('standard', $filename, $folderId, $unzip, $overwrite, $notify);
362
363 2
        $response = $this->client->request(
364 2
            'POST',
365 2
            $chunkUri['ChunkUri'],
366
            [
367
                'multipart' => [
368
                    [
369 2
                        'name'     => 'File1',
370 2
                        'contents' => fopen($filename, 'r'),
371
                    ],
372
                ],
373
            ]
374
        );
375
376 2
        return (string) $response->getBody();
377
    }
378
379
    /**
380
     * Get Thumbnail of an item.
381
     *
382
     * @param string $itemId Item id
383
     * @param int    $size   Thumbnail size: THUMBNAIL_SIZE_M or THUMBNAIL_SIZE_L (optional)
384
     *
385
     * @return array
386
     */
387 2 View Code Duplication
    public function getThumbnailUrl(string $itemId, int $size = 75):array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
388
    {
389 2
        $parameters = $this->buildHttpQuery(
390
            [
391 2
                'size'     => $size,
392
                'redirect' => false,
393
            ]
394
        );
395
396 2
        return $this->get("Items({$itemId})/Thumbnail?{$parameters}");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get(EncapsedNode) returns the type string which is incompatible with the type-hinted return array.
Loading history...
397
    }
398
399
    /**
400
     * Get browser link for an item.
401
     *
402
     * @param string $itemId Item id
403
     *
404
     * @return array
405
     */
406 2
    public function getWebAppLink(string $itemId):array
407
    {
408 2
        return $this->post("Items({$itemId})/WebAppLink");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(EncapsedNode) returns the type string which is incompatible with the type-hinted return array.
Loading history...
409
    }
410
411
    /**
412
     * Share Share for external user.
413
     *
414
     * @param array $options Share options
415
     * @param bool  $notify  Indicates whether user will be notified if item is downloaded (optional)
416
     *
417
     * @return array
418
     */
419 2
    public function createShare(array $options, $notify = false):array
420
    {
421 2
        $parameters = $this->buildHttpQuery(
422
            [
423 2
                'notify' => $notify,
424
                'direct' => true,
425
            ]
426
        );
427
428 2
        return $this->post("Shares?{$parameters}", $options);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(EncapsedNode, $options) returns the type string which is incompatible with the type-hinted return array.
Loading history...
429
    }
430
431
    /**
432
     * Get AccessControl List for an item.
433
     *
434
     * @param string $itemId Id of an item
435
     * @param string $userId Id of an user
436
     *
437
     * @return array
438
     */
439 4 View Code Duplication
    public function getItemAccessControls(string $itemId, string $userId = ''):array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
440
    {
441 4
        if (! empty($userId)) {
442 2
            return $this->get("AccessControls(principalid={$userId},itemid={$itemId})");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get(EncapsedNode) returns the type string which is incompatible with the type-hinted return array.
Loading history...
443
        } else {
444 2
            return $this->get("Items({$itemId})/AccessControls");
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get(EncapsedNode) returns the type string which is incompatible with the type-hinted return array.
Loading history...
445
        }
446
    }
447
448
    /**
449
     * Build API uri.
450
     *
451
     * @param string $endpoint API endpoint
452
     *
453
     * @return string
454
     */
455 40
    protected function buildUri(string $endpoint): string
456
    {
457 40
        return  "https://{$this->token['subdomain']}.sf-api.com/sf/v3/{$endpoint}";
458
    }
459
460
    /**
461
     * Make a request to the API.
462
     *
463
     * @param string             $method   HTTP Method
464
     * @param string             $endpoint API endpoint
465
     * @param mixed|string|array $json     POST body (optional)
466
     *
467
     * @throws Exception
468
     *
469
     * @return mixed
470
     */
471 40
    protected function request(string $method, string $endpoint, $json = null)
472
    {
473 40
        $uri = $this->buildUri($endpoint);
474 40
        $options = $json != null ? ['json' => $json] : [];
475
476
        try {
477 40
            $response = $this->client->request($method, $uri, $options);
478
        } catch (ClientException $exception) {
479
            throw $this->determineException($exception);
480
        }
481
482 40
        $body = (string) $response->getBody();
483
484 40
        return $this->jsonValidator($body) ? json_decode($body, true) : $body;
485
    }
486
487
    /**
488
     * Shorthand for GET-request.
489
     *
490
     * @param string $endpoint API endpoint
491
     *
492
     * @return mixed
493
     */
494 20
    protected function get(string $endpoint)
495
    {
496 20
        return $this->request('GET', $endpoint);
497
    }
498
499
    /**
500
     * Shorthand for POST-request.
501
     *
502
     * @param string             $endpoint API endpoint
503
     * @param mixed|string|array $json     POST body (optional)
504
     *
505
     * @return mixed
506
     */
507 16
    protected function post(string $endpoint, $json = null)
508
    {
509 16
        return $this->request('POST', $endpoint, $json);
510
    }
511
512
    /**
513
     * Shorthand for PATCH-request.
514
     *
515
     * @param string             $endpoint API endpoint
516
     * @param mixed|string|array $json     POST body (optional)
517
     *
518
     * @return mixed
519
     */
520 2
    protected function patch(string $endpoint, $json = null)
521
    {
522 2
        return $this->request('PATCH', $endpoint, $json);
523
    }
524
525
    /**
526
     * Shorthand for DELETE-request.
527
     *
528
     * @param string $endpoint API endpoint
529
     *
530
     * @return string|array
531
     */
532 2
    protected function delete(string $endpoint)
533
    {
534 2
        return $this->request('DELETE', $endpoint);
535
    }
536
537
    /**
538
     * Handle ClientException.
539
     *
540
     * @param ClientException $exception ClientException
541
     *
542
     * @return Exception
543
     */
544
    protected function determineException(ClientException $exception): Exception
545
    {
546
        if (in_array($exception->getResponse()->getStatusCode(), [400, 403, 404, 409])) {
547
            return new BadRequest($exception->getResponse());
548
        }
549
550
        return $exception;
551
    }
552
553
    /**
554
     * Build HTTP query.
555
     *
556
     * @param array $parameters Query parameters
557
     *
558
     * @return string
559
     */
560 24
    protected function buildHttpQuery(array $parameters):string
561
    {
562 24
        return http_build_query(
563 24
            array_map(
564 24
                function ($parameter) {
565 24
                    if (! is_bool($parameter)) {
566 10
                        return $parameter;
567
                    }
568
569 24
                    return $parameter ? 'true' : 'false';
570 24
                },
571 24
                $parameters
572
            )
573
        );
574
    }
575
576
    /**
577
     * Validate JSON.
578
     *
579
     * @param mixed $data JSON variable
580
     *
581
     * @return bool
582
     */
583 40
    protected function jsonValidator($data = null):bool
584
    {
585 40
        if (! empty($data)) {
586 38
            @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

586
            /** @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...
587
588 38
            return json_last_error() === JSON_ERROR_NONE;
589
        }
590
591 2
        return false;
592
    }
593
}
594