Passed
Push — master ( 370327...67d1d9 )
by Jan Willem
05:51
created

Client::getUser()   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
 * @category GitHub_Repositories
16
 *
17
 * @author   Jan Willem Kaper <[email protected]>
18
 * @license  MIT (see License.txt)
19
 *
20
 * @link     http://github.com/kapersoft/sharefile-api
21
 */
22
class Client
23
{
24
    /**
25
     * ShareFile token.
26
     *
27
     * @var array
28
     */
29
    public $token;
30
31
    /**
32
     * Guzzle Client.
33
     *
34
     * @var \GuzzleHttp\Client
35
     */
36
    public $client;
37
38
    /**
39
     * Thumbnail size.
40
     */
41
    const THUMBNAIL_SIZE_M = 75;
42
    const THUMBNAIL_SIZE_L = 600;
43
44
    /*
45
     * ShareFile Folder
46
     */
47
    const FOLDER_TOP = 'top';
48
    const FOLDER_HOME = 'home';
49
    const FOLDER_FAVORITES = 'favorites';
50
    const FOLDER_ALLSHARED = 'allshared';
51
52
    /**
53
     * Client constructor.
54
     *
55
     * @param string                   $hostname      ShareFile hostname
56
     * @param string                   $client_id     OAuth2 client_id
57
     * @param string                   $client_secret OAuth2 client_secret
58
     * @param string                   $username      ShareFile username
59
     * @param string                   $password      ShareFile password
60
     * @param MockHandler|HandlerStack $handler       Guzzle Handler
61
     *
62
     * @throws Exception
63
     */
64 22
    public function __construct(string $hostname, string $client_id, string $client_secret, string $username, string $password, $handler = null)
65
    {
66 22
        $response = $this->authenticate($hostname, $client_id, $client_secret, $username, $password, $handler);
67
68 20
        if (! isset($response['access_token']) || ! isset($response['subdomain'])) {
69 1
            throw new Exception("Incorrect response from Authentication: 'access_token' or 'subdomain' is missing.");
70
        }
71
72 19
        $this->token = $response;
73 19
        $this->client = new GuzzleClient(
74
            [
75 19
                'handler' => $handler,
76
                'headers' => [
77 19
                    'Authorization' => "Bearer {$this->token['access_token']}",
78
                ],
79
            ]
80
        );
81 19
    }
82
83
    /**
84
     * ShareFile authentication using username/password.
85
     *
86
     * @param string                   $hostname      ShareFile hostname
87
     * @param string                   $client_id     OAuth2 client_id
88
     * @param string                   $client_secret OAuth2 client_secret
89
     * @param string                   $username      ShareFile username
90
     * @param string                   $password      ShareFile password
91
     * @param MockHandler|HandlerStack $handler       Guzzle Handler
92
     *
93
     * @throws Exception
94
     *
95
     * @return array
96
     */
97 22
    protected function authenticate(string $hostname, string $client_id, string $client_secret, string $username, string $password, $handler = null):array
98
    {
99 22
        $uri = "https://{$hostname}/oauth/token";
100
101
        $parameters = [
102 22
            'grant_type'    => 'password',
103 22
            'client_id'     => $client_id,
104 22
            'client_secret' => $client_secret,
105 22
            'username'      => $username,
106 22
            'password'      => $password,
107
        ];
108
109
        try {
110 22
            $client = new GuzzleClient(['handler' => $handler]);
111 22
            $response = $client->post(
112 22
                $uri,
113 22
                ['form_params' => $parameters]
114
            );
115 1
        } catch (ClientException $exception) {
116 1
            throw $exception;
117
        }
118
119 21
        if ($response->getStatusCode() == '200') {
120 20
            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...
121
        } else {
122 1
            throw new Exception('Authentication error', $response->getStatusCode());
123
        }
124
    }
125
126
    /**
127
     * Get user details.
128
     *
129
     * @param string $userId ShareFile user id (optional)
130
     *
131
     * @return array
132
     */
133 1
    public function getUser(string $userId = ''):array
134
    {
135 1
        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...
136
    }
137
138
    /**
139
     * Create a folder.
140
     *
141
     * @param string $parentId    Id of the parent folder
142
     * @param string $name        Name
143
     * @param string $description Description
144
     * @param bool   $overwrite   Overwrite folder
145
     *
146
     * @return array
147
     */
148 2
    public function createFolder(string $parentId, string $name, string $description = '', bool $overwrite = false):array
149
    {
150 2
        $parameters = $this->buildHttpQuery(
151
            [
152 2
                'overwrite'   => $overwrite,
153
                'passthrough' => false,
154
            ]
155
        );
156
157
        $data = [
158 2
            'name'        => $name,
159 2
            'description' => $description,
160
        ];
161
162 2
        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...
163
    }
164
165
    /**
166
     * Get Folder/File using Id.
167
     *
168
     * @param string $itemId      Item id
169
     * @param bool   $getChildren Include children
170
     *
171
     * @return array
172
     */
173 2
    public function getItemById(string $itemId, bool $getChildren = false):array
174
    {
175 2
        $parameters = $getChildren === true ? '$expand=Children' : '';
176
177 2
        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...
178
    }
179
180
    /**
181
     * Get Folder/File using path.
182
     *
183
     * @param string $path   Path
184
     * @param string $itemId Id of the root folder (optional)
185
     *
186
     * @return array
187
     */
188 1
    public function getItemByPath(string $path, string $itemId = ''):array
189
    {
190 1
        if (empty($itemId)) {
191 1
            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...
192
        } else {
193
            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...
194
        }
195
    }
196
197
    /**
198
     * Get breadcrumps of an item.
199
     *
200
     * @param string $itemId Item Id
201
     *
202
     * @return array
203
     */
204 1
    public function getItemBreadcrumps(string $itemId):array
205
    {
206 1
        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...
207
    }
208
209
    /**
210
     * Copy an item.
211
     *
212
     * @param string $targetId  Id of the target folder
213
     * @param string $itemId    Id of the copied item
214
     * @param bool   $overwrite Indicates whether items with the same name will be overwritten or not (optional)
215
     *
216
     * @return array
217
     */
218 2
    public function copyItem(string $targetId, string $itemId, bool $overwrite = false):array
219
    {
220 2
        $parameters = $this->buildHttpQuery(
221
            [
222 2
                'targetid'  => $targetId,
223 2
                'overwrite' => $overwrite,
224
            ]
225
        );
226
227 2
        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...
228
    }
229
230
    /**
231
     * Update an item.
232
     *
233
     * @param string $itemId    Id of the item
234
     * @param array  $data      New data
235
     * @param bool   $forceSync Indicates whether operation is to be executed synchronously (optional)
236
     * @param bool   $notify    Indicates whether an email should be sent to users subscribed to Upload Notifications (optional)
237
     *
238
     * @return array
239
     */
240 1 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...
241
    {
242 1
        $parameters = $this->buildHttpQuery(
243
            [
244 1
                'forceSync' => $forceSync,
245 1
                'notify'    => $notify,
246
            ]
247
        );
248
249 1
        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...
250
    }
251
252
    /**
253
     * Delete an item.
254
     *
255
     * @param string $itemId        Item id
256
     * @param bool   $singleversion True it will delete only the specified version rather than all sibling files with the same filename (optional)
257
     * @param bool   $forceSync     True will block the operation from taking place asynchronously (optional)
258
     *
259
     * @return string
260
     */
261 1 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...
262
    {
263 1
        $parameters = $this->buildHttpQuery(
264
            [
265 1
                'singleversion' => $singleversion,
266 1
                'forceSync'     => $forceSync,
267
            ]
268
        );
269
270 1
        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...
271
    }
272
273
    /**
274
     * Get temporary download URL for an item.
275
     *
276
     * @param string $itemId             Item id
277
     * @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)
278
     *
279
     * @return array
280
     */
281 1 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...
282
    {
283 1
        $parameters = $this->buildHttpQuery(
284
            [
285 1
                'includeallversions' => $includeallversions,
286
                'redirect'           => false,
287
            ]
288
        );
289
290 1
        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...
291
    }
292
293
    /**
294
     * Get contents of and item.
295
     *
296
     * @param string $itemId             Item id
297
     * @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)
298
     *
299
     * @return mixed
300
     */
301 1 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...
302
    {
303 1
        $parameters = $this->buildHttpQuery(
304
            [
305 1
                'includeallversions' => $includeallversions,
306
                'redirect'           => true,
307
            ]
308
        );
309
310 1
        return $this->get("Items({$itemId})/Download?{$parameters}");
311
    }
312
313
    /**
314
     * Get the Chunk Uri to start a file-upload.
315
     *
316
     * @param string $method    Upload method (Standard or Streamed)
317
     * @param string $filename  Name of file
318
     * @param string $folderId  Id of the parent folder
319
     * @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)
320
     * @param bool   $overwrite Indicates whether items with the same name will be overwritten or not (optional)
321
     * @param bool   $notify    Indicates whether users will be notified of this upload - based on folder preferences (optional)
322
     *
323
     * @return array
324
     */
325 2
    public function getChunkUri(string $method, string $filename, string $folderId, bool $unzip = false, $overwrite = true, bool $notify = true):array
326
    {
327 2
        $parameters = $this->buildHttpQuery(
328
            [
329 2
                'method'                => $method,
330
                'raw'                   => false,
331 2
                'fileName'              => basename($filename),
332 2
                'fileSize'              => filesize($filename),
333
                'canResume'             => false,
334
                'startOver'             => false,
335 2
                'unzip'                 => $unzip,
336 2
                'tool'                  => 'apiv3',
337 2
                'overwrite'             => $overwrite,
338 2
                'title'                 => basename($filename),
339
                'isSend'                => false,
340 2
                'responseFormat'        => 'json',
341 2
                'notify'                => $notify,
342 2
                'clientCreatedDateUTC'  => filectime($filename),
343 2
                'clientModifiedDateUTC' => filemtime($filename),
344
            ]
345
        );
346
347 2
        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...
348
    }
349
350
    /**
351
     * Upload a file using a single HTTP POST.
352
     *
353
     * @param string $filename  Name of file
354
     * @param string $folderId  Id of the parent folder
355
     * @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)
356
     * @param bool   $overwrite Indicates whether items with the same name will be overwritten or not (optional)
357
     * @param bool   $notify    Indicates whether users will be notified of this upload - based on folder preferences (optional)
358
     *
359
     * @return string
360
     */
361 1
    public function uploadFileStandard(string $filename, string $folderId, bool $unzip = false, bool $overwrite = true, bool $notify = true):string
362
    {
363 1
        $chunkUri = $this->getChunkUri('standard', $filename, $folderId, $unzip, $overwrite, $notify);
364
365 1
        $response = $this->client->request(
366 1
            'POST',
367 1
            $chunkUri['ChunkUri'],
368
            [
369
                'multipart' => [
370
                    [
371 1
                        'name'     => 'File1',
372 1
                        'contents' => fopen($filename, 'r'),
373
                    ],
374
                ],
375
            ]
376
        );
377
378 1
        return (string) $response->getBody();
379
    }
380
381
    /**
382
     * Get Thumbnail of an item.
383
     *
384
     * @param string $itemId Item id
385
     * @param int    $size   Thumbnail size: THUMBNAIL_SIZE_M or THUMBNAIL_SIZE_L (optional)
386
     *
387
     * @return array
388
     */
389 1 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...
390
    {
391 1
        $parameters = $this->buildHttpQuery(
392
            [
393 1
                'size'     => $size,
394
                'redirect' => false,
395
            ]
396
        );
397
398 1
        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...
399
    }
400
401
    /**
402
     * Get browser link for an item.
403
     *
404
     * @param string $itemId Item id
405
     *
406
     * @return array
407
     */
408 1
    public function getWebAppLink(string $itemId):array
409
    {
410 1
        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...
411
    }
412
413
    /**
414
     * Share Share for external user.
415
     *
416
     * @param array $options Share options
417
     * @param bool  $notify  Indicates whether user will be notified if item is downloaded (optional)
418
     *
419
     * @return array
420
     */
421 1
    public function createShare(array $options, $notify = false):array
422
    {
423 1
        $parameters = $this->buildHttpQuery(
424
            [
425 1
                'notify' => $notify,
426
                'direct' => true,
427
            ]
428
        );
429
430 1
        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...
431
    }
432
433
    /**
434
     * Build API uri.
435
     *
436
     * @param string $endpoint API endpoint
437
     *
438
     * @return string
439
     */
440 18
    protected function buildUri(string $endpoint): string
441
    {
442 18
        return  "https://{$this->token['subdomain']}.sf-api.com/sf/v3/{$endpoint}";
443
    }
444
445
    /**
446
     * Make a request to the API.
447
     *
448
     * @param string             $method   HTTP Method
449
     * @param string             $endpoint API endpoint
450
     * @param mixed|string|array $json     POST body (optional)
451
     *
452
     * @throws Exception
453
     *
454
     * @return mixed
455
     */
456 18
    protected function request(string $method, string $endpoint, $json = null)
457
    {
458 18
        $uri = $this->buildUri($endpoint);
459 18
        $options = $json != null ? ['json' => $json] : [];
460
461
        try {
462 18
            $response = $this->client->request($method, $uri, $options);
463
        } catch (ClientException $exception) {
464
            throw $this->determineException($exception);
465
        }
466
467 18
        $body = (string) $response->getBody();
468
469 18
        return $this->jsonValidator($body) ? json_decode($body, true) : $body;
470
    }
471
472
    /**
473
     * Shorthand for GET-request.
474
     *
475
     * @param string $endpoint API endpoint
476
     *
477
     * @return mixed
478
     */
479 8
    protected function get(string $endpoint)
480
    {
481 8
        return $this->request('GET', $endpoint);
482
    }
483
484
    /**
485
     * Shorthand for POST-request.
486
     *
487
     * @param string             $endpoint API endpoint
488
     * @param mixed|string|array $json     POST body (optional)
489
     *
490
     * @return mixed
491
     */
492 8
    protected function post(string $endpoint, $json = null)
493
    {
494 8
        return $this->request('POST', $endpoint, $json);
495
    }
496
497
    /**
498
     * Shorthand for PATCH-request.
499
     *
500
     * @param string             $endpoint API endpoint
501
     * @param mixed|string|array $json     POST body (optional)
502
     *
503
     * @return mixed
504
     */
505 1
    protected function patch(string $endpoint, $json = null)
506
    {
507 1
        return $this->request('PATCH', $endpoint, $json);
508
    }
509
510
    /**
511
     * Shorthand for DELETE-request.
512
     *
513
     * @param string $endpoint API endpoint
514
     *
515
     * @return string|array
516
     */
517 1
    protected function delete(string $endpoint)
518
    {
519 1
        return $this->request('DELETE', $endpoint);
520
    }
521
522
    /**
523
     * Handle ClientException.
524
     *
525
     * @param ClientException $exception ClientException
526
     *
527
     * @return Exception
528
     */
529
    protected function determineException(ClientException $exception): Exception
530
    {
531
        if (in_array($exception->getResponse()->getStatusCode(), [400, 409, 404])) {
532
            return new BadRequest($exception->getResponse());
533
        }
534
535
        return $exception;
536
    }
537
538
    /**
539
     * Build HTTP query.
540
     *
541
     * @param array $parameters Query parameters
542
     *
543
     * @return string
544
     */
545 12
    protected function buildHttpQuery(array $parameters):string
546
    {
547 12
        return http_build_query(
548 12
            array_map(
549 12
                function ($parameter) {
550 12
                    if (! is_bool($parameter)) {
551 5
                        return $parameter;
552
                    }
553
554 12
                    return $parameter ? 'true' : 'false';
555 12
                },
556 12
                $parameters
557
            )
558
        );
559
    }
560
561
    /**
562
     * Validate JSON.
563
     *
564
     * @param mixed $data JSON variable
565
     *
566
     * @return bool
567
     */
568 18
    protected function jsonValidator($data = null):bool
569
    {
570 18
        if (! empty($data)) {
571 17
            @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

571
            /** @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...
572
573 17
            return json_last_error() === JSON_ERROR_NONE;
574
        }
575
576 1
        return false;
577
    }
578
}
579