Completed
Push — master ( 67d1d9...640e49 )
by Jan Willem
04:47
created

Client::getItemAccessControls()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 6
Ratio 100 %

Code Coverage

Tests 4
CRAP Score 2.1481

Importance

Changes 0
Metric Value
dl 6
loc 6
ccs 4
cts 6
cp 0.6667
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 2
crap 2.1481
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 24
    public function __construct(string $hostname, string $client_id, string $client_secret, string $username, string $password, $handler = null)
65
    {
66 24
        $response = $this->authenticate($hostname, $client_id, $client_secret, $username, $password, $handler);
67
68 22
        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 21
        $this->token = $response;
73 21
        $this->client = new GuzzleClient(
74
            [
75 21
                'handler' => $handler,
76
                'headers' => [
77 21
                    'Authorization' => "Bearer {$this->token['access_token']}",
78
                ],
79
            ]
80
        );
81 21
    }
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 24
    protected function authenticate(string $hostname, string $client_id, string $client_secret, string $username, string $password, $handler = null):array
98
    {
99 24
        $uri = "https://{$hostname}/oauth/token";
100
101
        $parameters = [
102 24
            'grant_type'    => 'password',
103 24
            'client_id'     => $client_id,
104 24
            'client_secret' => $client_secret,
105 24
            'username'      => $username,
106 24
            'password'      => $password,
107
        ];
108
109
        try {
110 24
            $client = new GuzzleClient(['handler' => $handler]);
111 24
            $response = $client->post(
112 24
                $uri,
113 24
                ['form_params' => $parameters]
114
            );
115 1
        } catch (ClientException $exception) {
116 1
            throw $exception;
117
        }
118
119 23
        if ($response->getStatusCode() == '200') {
120 22
            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 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...
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
     * Get AccessControl List for an item.
435
     *
436
     * @param string $itemId Id of an item
437
     * @param string $userId Id of an user
438
     *
439
     * @return array
440
     */
441 2 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...
442
    {
443 2
        if (!empty($userId)) {
444 1
            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...
445
        } else {
446 1
            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...
447
        }
448
    }
449
450
    /**
451
     * Build API uri.
452
     *
453
     * @param string $endpoint API endpoint
454
     *
455
     * @return string
456
     */
457 20
    protected function buildUri(string $endpoint): string
458
    {
459 20
        return  "https://{$this->token['subdomain']}.sf-api.com/sf/v3/{$endpoint}";
460
    }
461
462
    /**
463
     * Make a request to the API.
464
     *
465
     * @param string             $method   HTTP Method
466
     * @param string             $endpoint API endpoint
467
     * @param mixed|string|array $json     POST body (optional)
468
     *
469
     * @throws Exception
470
     *
471
     * @return mixed
472
     */
473 20
    protected function request(string $method, string $endpoint, $json = null)
474
    {
475 20
        $uri = $this->buildUri($endpoint);
476 20
        $options = $json != null ? ['json' => $json] : [];
477
478
        try {
479 20
            $response = $this->client->request($method, $uri, $options);
480
        } catch (ClientException $exception) {
481
            throw $this->determineException($exception);
482
        }
483
484 20
        $body = (string) $response->getBody();
485
486 20
        return $this->jsonValidator($body) ? json_decode($body, true) : $body;
487
    }
488
489
    /**
490
     * Shorthand for GET-request.
491
     *
492
     * @param string $endpoint API endpoint
493
     *
494
     * @return mixed
495
     */
496 10
    protected function get(string $endpoint)
497
    {
498 10
        return $this->request('GET', $endpoint);
499
    }
500
501
    /**
502
     * Shorthand for POST-request.
503
     *
504
     * @param string             $endpoint API endpoint
505
     * @param mixed|string|array $json     POST body (optional)
506
     *
507
     * @return mixed
508
     */
509 8
    protected function post(string $endpoint, $json = null)
510
    {
511 8
        return $this->request('POST', $endpoint, $json);
512
    }
513
514
    /**
515
     * Shorthand for PATCH-request.
516
     *
517
     * @param string             $endpoint API endpoint
518
     * @param mixed|string|array $json     POST body (optional)
519
     *
520
     * @return mixed
521
     */
522 1
    protected function patch(string $endpoint, $json = null)
523
    {
524 1
        return $this->request('PATCH', $endpoint, $json);
525
    }
526
527
    /**
528
     * Shorthand for DELETE-request.
529
     *
530
     * @param string $endpoint API endpoint
531
     *
532
     * @return string|array
533
     */
534 1
    protected function delete(string $endpoint)
535
    {
536 1
        return $this->request('DELETE', $endpoint);
537
    }
538
539
    /**
540
     * Handle ClientException.
541
     *
542
     * @param ClientException $exception ClientException
543
     *
544
     * @return Exception
545
     */
546
    protected function determineException(ClientException $exception): Exception
547
    {
548
        if (in_array($exception->getResponse()->getStatusCode(), [400, 409, 404])) {
549
            return new BadRequest($exception->getResponse());
550
        }
551
552
        return $exception;
553
    }
554
555
    /**
556
     * Build HTTP query.
557
     *
558
     * @param array $parameters Query parameters
559
     *
560
     * @return string
561
     */
562 12
    protected function buildHttpQuery(array $parameters):string
563
    {
564 12
        return http_build_query(
565 12
            array_map(
566 12
                function ($parameter) {
567 12
                    if (! is_bool($parameter)) {
568 5
                        return $parameter;
569
                    }
570
571 12
                    return $parameter ? 'true' : 'false';
572 12
                },
573 12
                $parameters
574
            )
575
        );
576
    }
577
578
    /**
579
     * Validate JSON.
580
     *
581
     * @param mixed $data JSON variable
582
     *
583
     * @return bool
584
     */
585 20
    protected function jsonValidator($data = null):bool
586
    {
587 20
        if (! empty($data)) {
588 19
            @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

588
            /** @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...
589
590 19
            return json_last_error() === JSON_ERROR_NONE;
591
        }
592
593 1
        return false;
594
    }
595
}
596