Passed
Push — master ( 37cafd...a8b392 )
by
unknown
07:18
created

SeafileApi::createNewDirectory()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 9
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 13
rs 9.9666
1
<?php declare(strict_types=1);
2
3
namespace Datamate\SeafileApi;
4
5
use CURLFile;
6
use CurlHandle;
7
use Datamate\SeafileApi\Exception\UnexpectedJsonTextResponseException as JsonDecodeException;
8
use DateTimeImmutable;
9
use DateTimeInterface;
10
use JsonException;
11
use Datamate\SeafileApi\Exception\ConnectionException;
12
use Datamate\SeafileApi\Exception\InvalidResponseException;
13
use Datamate\SeafileApi\Exception\InvalidArgumentException;
14
use function count;
15
use function gettype;
16
use function strtr;
17
18
/**
19
 * Seafile API
20
 *
21
 * Interact with the Seafile API with the curl extension.
22
 *
23
 * @link https://manual.seafile.com/develop/web_api_v2.1.html
24
 */
25
final class SeafileApi
26
{
27
    public const USER_PREFIX_AUTH_TOKEN = '@auth:token:';
28
29
    public const TYPE_DIR = 'dir';
30
    public const TYPE_FILE = 'file';
31
    public const TYPE_REPO = 'repo';
32
    public const TYPE_SREPO = 'srepo';
33
    public const TYPE_GREPO = 'grepo';
34
35
    public const TYPES = self::TYPES_FILE + self::TYPES_DIR_LIKE;
36
    public const TYPES_DIR_LIKE = self::TYPES_DIR + self::TYPES_REPO;
37
    public const TYPES_DIR = [self::TYPE_DIR => self::TYPE_DIR];
38
    public const TYPES_FILE = [self::TYPE_FILE => self::TYPE_FILE];
39
    public const TYPES_REPO = [self::TYPE_REPO => self::TYPE_REPO, self::TYPE_SREPO => self::TYPE_SREPO, self::TYPE_GREPO => self::TYPE_GREPO];
40
41
    /**
42
     * @const string
43
     */
44
    public const STRING_SUCCESS = 'success';
45
46
    /**
47
     * Error codes
48
     */
49
    public const ERROR_CODE_FEATURES = 802;
50
    public const ERROR_CODE_NO_CURL = 803;
51
    public const ERROR_CODE_FILE_IO = 808;
52
53
    /**
54
     * default curl options
55
     */
56
    private const CURL_OPTION_DEFAULTS = [
57
        CURLOPT_AUTOREFERER => true,
58
        CURLOPT_TIMEOUT => 10,
59
        CURLOPT_RETURNTRANSFER => true,
60
        CURLOPT_FOLLOWLOCATION => false,
61
    ];
62
63
    /**
64
     * @var array shared curl options between all requests
65
     */
66
    private array $curlSharedOptions = self::CURL_OPTION_DEFAULTS;
67
68
    /**
69
     * jsonDecode accept flags
70
     *
71
     * @see jsonDecode
72
     */
73
    private const JSON_DECODE_ACCEPT_MASK = 31;                         # 1 1111 accept bitmask (five bits with the msb flags)
74
    private const JSON_DECODE_ACCEPT_JSON = 16;                         # 1 0000 JSON text
75
    private const JSON_DECODE_ACCEPT_DEFAULT = 23;                      # 1 0111 default: string, array or object
76
    private const JSON_DECODE_ACCEPT_OBJECT = 17;                       # 1 0001 object
77
    private const JSON_DECODE_ACCEPT_ARRAY = 18;                        # 1 0010 array
78
    private const JSON_DECODE_ACCEPT_STRING = 20;                       # 1 0100 string
79
    private const JSON_DECODE_ACCEPT_ARRAY_OF_OBJECTS = 24;             # 1 1000 array with only objects (incl. none)
80
    private const JSON_DECODE_ACCEPT_ARRAY_SINGLE_OBJECT = 25;          # 1 1001 array with one single object, return that item
81
    private const JSON_DECODE_ACCEPT_ARRAY_SINGLE_OBJECT_NULLABLE = 26; # 1 1010 array with one single object, return that item, or empty array, return null
82
    private const JSON_DECODE_ACCEPT_SUCCESS_STRING = 28;               # 1 1100 string "success"
83
    private const JSON_DECODE_ACCEPT_SUCCESS_OBJECT = 29;               # 1 1101 object with single "success" property and value true
84
85
    /**
86
     * @const string ASCII upper-case characters part of a hexit
87
     */
88
    private const HEX_ALPHA_UPPER = 'ABCDEF';
89
90
    /**
91
     * @const string ASCII lower-case characters part of a hexit
92
     */
93
    private const HEX_ALPHA_LOWER = 'abcdef';
94
95
    /**
96
     * @var string Server base URL
97
     */
98
    private string $baseurl;
99
100
    /**
101
     * @var string Username
102
     */
103
    private string $user;
104
105
    /**
106
     * @var string Password
107
     */
108
    private string $pass;
109
110
    private ?CurlHandle $handle = null;
111
112
    /**
113
     * @var string
114
     */
115
    private string $token = '';
116
117
    /**
118
     * constructor
119
     *
120
     * @param string $baseurl
121
     * @param string $user
122
     * @param string $pass
123
     * @param string|null $otp
124
     */
125
    public function __construct(string $baseurl, string $user, string $pass, string $otp = null)
126
    {
127
        if (!(function_exists('curl_version'))) {
128
            throw new ConnectionException('PHP-CURL not installed', self::ERROR_CODE_NO_CURL);
129
        }
130
131
        $this->baseurl = $baseurl;
132
        $this->user = $user;
133
        $this->pass = $pass;
134
        // trigger_error(sprintf("ctor: %s:%s", $user, $pass), E_USER_NOTICE);
135
136
        $this->setTokenByUsernameAndPassword($otp);
137
    }
138
139
    /**
140
     * ping (with authentication)
141
     *
142
     * @link https://download.seafile.com/published/web-api/home.md#user-content-Quick%20Start
143
     *
144
     * @return string "pong"
145
     */
146
    public function ping(): string
147
    {
148
        return $this->jsonDecode(
149
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

149
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
150
                "$this->baseurl/api2/ping/",
151
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
152
            ),
153
            self::JSON_DECODE_ACCEPT_STRING,
154
        );
155
    }
156
157
    /**
158
     * get server version
159
     *
160
     * @throws Exception
161
     * @return string
162
     */
163
    public function getServerVersion(): string
164
    {
165
        $serverInfo = $this->getServerInformation();
166
        if (
167
            !is_string($serverInfo->version ?? null)
168
            || !is_array($serverInfo->features ?? null)
169
        ) {
170
            throw new InvalidResponseException('We could not retrieve list of server features.', self::ERROR_CODE_FEATURES);
171
        }
172
173
        $isSeafilePro = in_array('seafile-pro', $serverInfo->features, true);
174
        $edition = $isSeafilePro ? 'Professional' : 'Community';
175
176
        return "$serverInfo->version ($edition)";
177
    }
178
179
    /**
180
     * get server information
181
     *
182
     * @link https://download.seafile.com/published/web-api/v2.1/server-info.md#user-content-Get%20Server%20Information
183
     *
184
     * @throws Exception
185
     * @return object
186
     */
187
    public function getServerInformation(): object
188
    {
189
        return $this->jsonDecode(
190
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

190
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
191
                "$this->baseurl/api2/server-info/",
192
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
193
            ),
194
            self::JSON_DECODE_ACCEPT_OBJECT,
195
        );
196
    }
197
198
    /**
199
     * check account info
200
     *
201
     * @link https://download.seafile.com/published/web-api/v2.1/account.md#user-content-Check%20Account%20Info
202
     * @return object
203
     */
204
    public function checkAccountInfo(): object
205
    {
206
        return $this->jsonDecode(
207
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

207
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
208
                "$this->baseurl/api2/account/info/",
209
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]]
210
            ),
211
            self::JSON_DECODE_ACCEPT_OBJECT,
212
        );
213
    }
214
215
    /**
216
     * list seafile server libraries
217
     *
218
     * that are all libraries a user can access.
219
     *
220
     * @link https://download.seafile.com/published/web-api/v2.1/libraries.md#user-content-List%20Libraries
221
     *
222
     * @throws Exception
223
     * @return object[]
224
     */
225
    public function listLibraries(): array
226
    {
227
        return $this->listLibrariesCached(true);
228
    }
229
230
    /**
231
     * get default library
232
     *
233
     * @link https://download.seafile.com/published/web-api/v2.1/libraries.md#user-content-Get%20Default%20Library
234
     *
235
     * @return object{exists: bool, repo_id: string}
236
     */
237
    public function getDefaultLibrary(): object
238
    {
239
        return $this->jsonDecode(
240
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

240
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
241
                "$this->baseurl/api2/default-repo/",
242
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
243
            ),
244
            self::JSON_DECODE_ACCEPT_OBJECT,
245
        );
246
    }
247
248
    /**
249
     * create a library
250
     *
251
     * @link https://download.seafile.com/published/web-api/v2.1/libraries.md#user-content-Create%20Library
252
     *
253
     * @param string $name of library
254
     * @return object
255
     */
256
    public function createLibrary(string $name): object
257
    {
258
        $name = trim($name, "/");
259
260
        return $this->jsonDecode(
261
            $this->post(
0 ignored issues
show
Bug introduced by
It seems like $this->post($this->baseu...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

261
            /** @scrutinizer ignore-type */ $this->post(
Loading history...
262
                "$this->baseurl/api2/repos/",
263
                ['name' => $name],
264
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
265
            ),
266
            self::JSON_DECODE_ACCEPT_OBJECT,
267
        );
268
    }
269
270
    /**
271
     * name a library
272
     *
273
     * @link https://download.seafile.com/published/web-api/v2.1/libraries.md#user-content-Rename%20Library
274
     */
275
    public function nameLibrary(string $lib, string $name): void
276
    {
277
        $lib = $this->verifyLib($lib);
278
        $fields = ['repo_name' => $name];
279
        $_ = $this->jsonDecode(
280
            $this->post(
0 ignored issues
show
Bug introduced by
It seems like $this->post($this->baseu...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

280
            /** @scrutinizer ignore-type */ $this->post(
Loading history...
281
                "$this->baseurl/api2/repos/$lib/?op=rename",
282
                $fields,
283
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
284
            ),
285
            self::JSON_DECODE_ACCEPT_SUCCESS_STRING,
286
        );
287
        unset($_);
288
    }
289
290
    /**
291
     * rename a library
292
     *
293
     * do nothing if the library can not be found
294
     *
295
     * @link https://download.seafile.com/published/web-api/v2.1/libraries.md#user-content-Rename%20Library
296
     *
297
     * @param string $oldName
298
     * @param string $newName
299
     * @return void
300
     */
301
    public function renameLibrary(string $oldName, string $newName): void
302
    {
303
        $lib = $this->getLibraryIdByLibraryName($oldName);
304
        if (null === $lib) {
305
            return; // no library to rename
306
        }
307
308
        $this->nameLibrary($lib, $newName);
309
    }
310
311
    /**
312
     * delete a library by name
313
     *
314
     * @link https://download.seafile.com/published/web-api/v2.1/libraries.md#user-content-Delete%20Library
315
     *
316
     * @param string $name
317
     * @return void
318
     */
319
    public function deleteLibraryByName(string $name): void
320
    {
321
        $id = $this->getLibraryIdByLibraryName($name);
322
        if (null === $id) {
323
            return; // library already gone
324
        }
325
326
        $this->deleteLibraryById($id);
327
    }
328
329
    /**
330
     * delete a library by id
331
     *
332
     * @link https://download.seafile.com/published/web-api/v2.1/libraries.md#user-content-Delete%20Library
333
     *
334
     * @param string $id
335
     * @return void
336
     */
337
    public function deleteLibraryById(string $id): void
338
    {
339
        $lib = $this->verifyLib($id);
340
341
        $_ = $this->jsonDecode(
342
            $this->delete(
0 ignored issues
show
Bug introduced by
It seems like $this->delete($this->bas...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

342
            /** @scrutinizer ignore-type */ $this->delete(
Loading history...
343
                "$this->baseurl/api2/repos/$lib/",
344
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
345
            ),
346
            self::JSON_DECODE_ACCEPT_SUCCESS_STRING,
347
        );
348
        unset($_);
349
    }
350
351
    /**
352
     * get library info array from path
353
     *
354
     * @param string $libNamedPath with the library name as first component
355
     * @throws Exception
356
     * @throws InvalidResponseException
357
     * @return object with 'id' and 'name' of library, both NULL if not found
358
     */
359
    public function getLibraryFromPath(string $libNamedPath): object
360
    {
361
        $libraries = $this->listLibrariesCached();
362
        $libraries = array_column($libraries, null, 'name');
363
364
        $name = explode('/', ltrim($this->normalizePath($libNamedPath), '/'), 2)[0];
365
366
        return (object)[
367
            'id' => $libraries[$name]->id ?? null,
368
            'name' => $libraries[$name]->name ?? null,
369
        ];
370
    }
371
372
    /**
373
     * list all share links
374
     *
375
     * all folder/file download share links in all libraries created by user.
376
     *
377
     * @link https://download.seafile.com/published/web-api/v2.1/share-links.md#user-content-List%20all%20Share%20Links
378
     *
379
     * @return object[]
380
     * @throws Exception
381
     * @throws InvalidResponseException
382
     * @noinspection PhpUnused
383
     */
384
    public function listAllShareLinks(): array
385
    {
386
        return $this->jsonDecode(
387
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

387
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
388
                "$this->baseurl/api/v2.1/share-links/",
389
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
390
            ),
391
            self::JSON_DECODE_ACCEPT_ARRAY_OF_OBJECTS,
392
        );
393
    }
394
395
    /**
396
     * Create Share Link
397
     *
398
     * @link https://download.seafile.com/published/web-api/v2.1/share-links.md#user-content-Create%20Share%20Link
399
     *
400
     * @param string $lib
401
     * @param string $path
402
     * @param ?string $password [optional]
403
     * @param ?int|DateTimeInterface $expire [optional] number of days to expire (int) or DateTime to expire
404
     * @param ?array $permissions [optional] see seafile api docs
405
     * @return object
406
     * @throws Exception
407
     * @throws InvalidArgumentException
408
     * @throws InvalidResponseException
409
     */
410
    public function createShareLink(string $lib, string $path, string $password = null, $expire = null, array $permissions = null): object
411
    {
412
        $lib = $this->verifyLib($lib);
413
        $path = $this->normalizePath($path);
414
415
        $fields = [
416
            'repo_id' => $lib,
417
            'path' => $path,
418
        ];
419
        if (null !== $password) {
420
            $fields['password'] = $password;
421
        }
422
        if (null !== $expire) {
423
            $expireTime = $expire;
424
            if (is_int($expire)) {
425
                $expireDays = max(1, min(365, (int)$expire));
426
                $expireTime = (new DateTimeImmutable())->add(
427
                    new \DateInterval("P{$expireDays}D")
428
                );
429
            }
430
            if (!$expireTime instanceof DateTimeInterface) {
431
                throw new InvalidArgumentException('Expire type mismatch: ' . gettype($expireTime));
432
            }
433
            $fields['expiration_time'] = $expireTime->format(\DATE_ATOM);
434
        }
435
        if (null !== $permissions) {
436
            try {
437
                $fields['permissions'] = json_encode($permissions, JSON_THROW_ON_ERROR);
438
            } /** @noinspection PhpMultipleClassDeclarationsInspection */ catch (JsonException $ex) {
439
                throw new InvalidArgumentException('permissions');
440
            }
441
        }
442
443
        return $this->jsonDecode(
444
            $this->post(
0 ignored issues
show
Bug introduced by
It seems like $this->post($this->baseu...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

444
            /** @scrutinizer ignore-type */ $this->post(
Loading history...
445
                "$this->baseurl/api/v2.1/share-links/",
446
                $fields,
447
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
448
            ),
449
            self::JSON_DECODE_ACCEPT_OBJECT,
450
        );
451
    }
452
453
    /**
454
     * List Share Links of a Library
455
     *
456
     * @link https://download.seafile.com/published/web-api/v2.1/share-links.md#user-content-List%20all%20Share%20Links
457
     *
458
     * @param ?string $lib [optional] library id (guid), default/null for all libraries
459
     * @throws Exception
460
     * @return object[]
461
     */
462
    public function listShareLinksOfALibrary(string $lib = null): array
463
    {
464
        $lib = $this->verifyLib($lib ?? '', true);
465
466
        return $this->jsonDecode(
467
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

467
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
468
                "$this->baseurl/api/v2.1/share-links/?repo_id=$lib",
469
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
470
            ),
471
            self::JSON_DECODE_ACCEPT_ARRAY_OF_OBJECTS,
472
        );
473
    }
474
475
    /**
476
     * check password (of a share link by token)
477
     *
478
     * @link https://download.seafile.com/published/web-api/v2.1-admin/share-links.md#user-content-Check%20Password
479
     *
480
     * @param string $token of share link
481
     * @param string $password in plain
482
     * @return object
483
     * @throws Exception
484
     * @throws InvalidResponseException
485
     * @noinspection PhpUnused
486
     */
487
    public function checkShareLinkPassword(string $token, string $password): object
488
    {
489
        $tokenEncoded = rawurlencode($token);
490
491
        return $this->jsonDecode(
492
            $this->post(
0 ignored issues
show
Bug introduced by
It seems like $this->post($this->baseu...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

492
            /** @scrutinizer ignore-type */ $this->post(
Loading history...
493
                "$this->baseurl/api/v2.1/admin/share-links/$tokenEncoded/check-password/",
494
                ['password' => $password],
495
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
496
            ),
497
            self::JSON_DECODE_ACCEPT_SUCCESS_OBJECT,
498
        );
499
    }
500
501
    /**
502
     * share Link of a folder (or file)
503
     *
504
     * @link https://download.seafile.com/published/web-api/v2.1/share-links.md#user-content-List%20Share%20Link%20of%20a%20Folder%20(File)
505
     *
506
     * @param string $lib
507
     * @param string $path
508
     * @return object the share link
509
     * @throws Exception
510
     * @throws InvalidArgumentException
511
     * @throws InvalidResponseException
512
     * @noinspection PhpUnused
513
     */
514
    public function listShareLinksOfAFolder(string $lib, string $path): ?object
515
    {
516
        $lib = $this->verifyLib($lib);
517
        $path = $this->normalizePath($path);
518
        $pathEncoded = rawurlencode($path);
519
520
        return $this->jsonDecode(
521
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

521
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
522
                "$this->baseurl/api/v2.1/share-links/?repo_id=$lib&path=$pathEncoded",
523
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
524
            ),
525
            self::JSON_DECODE_ACCEPT_ARRAY_SINGLE_OBJECT_NULLABLE,
526
        );
527
    }
528
529
    /**
530
     * Delete Share Link
531
     *
532
     * @link https://download.seafile.com/published/web-api/v2.1/share-links.md#user-content-Delete%20Share%20Link
533
     *
534
     * @param string $token
535
     * @throws Exception
536
     * @return object success {"success": true}
537
     */
538
    public function deleteShareLink(string $token): object
539
    {
540
        $token = $this->verifyToken($token);
541
542
        return $this->jsonDecode(
543
            $this->delete(
0 ignored issues
show
Bug introduced by
It seems like $this->delete($this->bas...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

543
            /** @scrutinizer ignore-type */ $this->delete(
Loading history...
544
                "$this->baseurl/api/v2.1/share-links/$token/",
545
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
546
            ),
547
            self::JSON_DECODE_ACCEPT_SUCCESS_OBJECT,
548
        );
549
    }
550
551
    /**
552
     * search user
553
     *
554
     * @link https://download.seafile.com/published/web-api/v2.1/user-search.md#user-content-Search%20User
555
     *
556
     * @param string $search
557
     * @throws Exception
558
     * @return object
559
     */
560
    public function searchUser(string $search): object
561
    {
562
        $searchEncoded = rawurlencode($search);
563
564
        return $this->jsonDecode(
565
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...ken ' . $this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

565
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
566
                "$this->baseurl/api2/search-user/?q=$searchEncoded",
567
                [CURLOPT_HTTPHEADER => ['Authorization: Token ' . $this->token]],
568
            ),
569
            self::JSON_DECODE_ACCEPT_OBJECT,
570
        );
571
    }
572
573
    /**
574
     * list groups for user sharing
575
     *
576
     * @throws Exception
577
     * @throws InvalidResponseException
578
     * @return array|object|string
579
     * @link (undocumented)
580
     *
581
     */
582
    public function shareableGroups(): array
583
    {
584
        return $this->jsonDecode(
585
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

585
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
586
                "$this->baseurl/api/v2.1/shareable-groups/",
587
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
588
            ),
589
            self::JSON_DECODE_ACCEPT_ARRAY_OF_OBJECTS,
590
        );
591
    }
592
593
    /**
594
     * Share a Library to Group
595
     *
596
     * @link https://download.seafile.com/published/web-api/v2.1/share.md#user-content-Share%20a%20Library%20to%20Group
597
     *
598
     * @param string $lib
599
     * @param string $path
600
     * @param int|int[] $group
601
     * @param string|null $permission [optional] r, rw, admin (default: r)
602
     * @throws Exception
603
     * @throws InvalidArgumentException
604
     * @return array
605
     */
606
    public function shareLibraryPathToGroup(string $lib, string $path, $group, string $permission = null)
607
    {
608
        $lib = $this->verifyLib($lib);
609
        $path = $this->normalizePath($path);
610
        $pathEncoded = rawurlencode($path);
611
612
        $fields = [
613
            'share_type' => 'group',
614
            'group_id' => $group,
615
            'permission' => $permission ?? 'r',
616
        ];
617
        return $this->jsonDecode(
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->jsonDecode...N_DECODE_ACCEPT_OBJECT) also could return the type object|string which is incompatible with the documented return type array.
Loading history...
618
            $this->put(
0 ignored issues
show
Bug introduced by
It seems like $this->put($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

618
            /** @scrutinizer ignore-type */ $this->put(
Loading history...
619
                "$this->baseurl/api2/repos/$lib/dir/shared_items/?p=$pathEncoded",
620
                $fields,
621
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
622
            ),
623
            self::JSON_DECODE_ACCEPT_OBJECT,
624
        );
625
    }
626
627
    /**
628
     * Share a Library to User
629
     *
630
     * @link https://download.seafile.com/published/web-api/v2.1/share.md#user-content-Share%20a%20Library%20to%20User
631
     *
632
     * @param string $lib
633
     * @param string $path
634
     * @param string $user
635
     * @param string|null $permission [optional] r, rw, admin (default: r)
636
     * @throws Exception
637
     * @throws InvalidArgumentException
638
     * @return array
639
     */
640
    public function shareLibraryPathToUser(string $lib, string $path, string $user, string $permission = null)
641
    {
642
        $lib = $this->verifyLib($lib);
643
        $path = $this->normalizePath($path);
644
        $pathEncoded = rawurlencode($path);
645
646
        $fields = [
647
            'share_type' => 'user',
648
            'username' => $user,
649
            'permission' => $permission ?? 'r',
650
        ];
651
        return $this->jsonDecode(
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->jsonDecode...N_DECODE_ACCEPT_OBJECT) also could return the type object|string which is incompatible with the documented return type array.
Loading history...
652
            $this->put(
0 ignored issues
show
Bug introduced by
It seems like $this->put($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

652
            /** @scrutinizer ignore-type */ $this->put(
Loading history...
653
                "$this->baseurl/api2/repos/$lib/dir/shared_items/?p=$pathEncoded",
654
                $fields,
655
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
656
            ),
657
            // either array of objects -or- failure object
658
            self::JSON_DECODE_ACCEPT_ARRAY | self::JSON_DECODE_ACCEPT_OBJECT,
659
        );
660
    }
661
662
    /**
663
     * Unshare a Library to Group
664
     *
665
     * @link https://download.seafile.com/published/web-api/v2.1/share.md#user-content-Unshare%20a%20Library%20from%20Group
666
     *
667
     * @param string $lib
668
     * @param string $path
669
     * @param int $group
670
     * @throws Exception
671
     * @throws InvalidArgumentException
672
     * @return object
673
     */
674
    public function unshareLibraryPathToGroup(string $lib, string $path, int $group): object
675
    {
676
        $lib = $this->verifyLib($lib);
677
        $path = $this->normalizePath($path);
678
        $pathEncoded = rawurlencode($path);
679
680
        return $this->jsonDecode(
681
            $this->delete(
0 ignored issues
show
Bug introduced by
It seems like $this->delete($this->bas...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

681
            /** @scrutinizer ignore-type */ $this->delete(
Loading history...
682
                "$this->baseurl/api2/repos/$lib/dir/shared_items/?p=$pathEncoded&share_type=group&group_id=$group",
683
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
684
            ),
685
            self::JSON_DECODE_ACCEPT_SUCCESS_OBJECT,
686
        );
687
    }
688
689
    /**
690
     * Unshare a Library to User
691
     *
692
     * @link https://download.seafile.com/published/web-api/v2.1/share.md#user-content-Unshare%20a%20Library%20from%20User
693
     *
694
     * @param string $lib
695
     * @param string $path
696
     * @param string|string[] $user
697
     * @throws Exception
698
     * @throws InvalidArgumentException
699
     * @return object
700
     */
701
    public function unshareLibraryPathToUser(string $lib, string $path, $user): object
702
    {
703
        $lib = $this->verifyLib($lib);
704
        $path = $this->normalizePath($path);
705
        $pathEncoded = rawurlencode($path);
706
        $userEncoded = rawurlencode($user);
0 ignored issues
show
Bug introduced by
It seems like $user can also be of type string[]; however, parameter $string of rawurlencode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

706
        $userEncoded = rawurlencode(/** @scrutinizer ignore-type */ $user);
Loading history...
707
708
        return $this->jsonDecode(
709
            $this->delete(
0 ignored issues
show
Bug introduced by
It seems like $this->delete($this->bas...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

709
            /** @scrutinizer ignore-type */ $this->delete(
Loading history...
710
                "$this->baseurl/api2/repos/$lib/dir/shared_items/?p=$pathEncoded&share_type=user&username=$userEncoded",
711
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
712
            ),
713
            self::JSON_DECODE_ACCEPT_SUCCESS_OBJECT,
714
        );
715
    }
716
717
    /**
718
     * list user shares for a library path
719
     *
720
     * @link https://download.seafile.com/published/web-api/v2.1/share.md#user-content-List%20Shared%20Users%20of%20a%20Library
721
     *
722
     * @param string $lib
723
     * @param string $path
724
     * @throws Exception
725
     * @throws InvalidArgumentException
726
     * @throws InvalidResponseException
727
     * @return array<int, object>
728
     */
729
    public function listSharesOfLibraryPath(string $lib, string $path): array
730
    {
731
        $lib = $this->verifyLib($lib);
732
        $path = $this->normalizePath($path);
733
        $pathEncoded = rawurlencode($path);
734
735
        return $this->jsonDecode(
736
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

736
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
737
                "$this->baseurl/api2/repos/$lib/dir/shared_items/?p=$pathEncoded",
738
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
739
            ),
740
            self::JSON_DECODE_ACCEPT_ARRAY_OF_OBJECTS,
741
        );
742
    }
743
744
    /**
745
     * create a new directory
746
     *
747
     * @link https://download.seafile.com/published/web-api/v2.1/directories.md#user-content-Create%20New%20Directory
748
     *
749
     * @param string $lib library id (guid)
750
     * @param string $path of the directory to create (e.g.: "/path/to/new-directory", leading and trailing slashes can be omitted)
751
     * @throws InvalidArgumentException|Exception
752
     * @return string|object the common "success" or the object with error_msg property
753
     */
754
    public function createNewDirectory(string $lib, string $path)
755
    {
756
        $lib = $this->verifyLib($lib);
757
        $path = $this->normalizePath($path);
758
        $pathEncoded = rawurlencode($path);
759
760
        return $this->jsonDecode(
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->jsonDecode...N_DECODE_ACCEPT_OBJECT) also could return the type array which is incompatible with the documented return type object|string.
Loading history...
761
            $this->post(
0 ignored issues
show
Bug introduced by
It seems like $this->post($this->baseu...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

761
            /** @scrutinizer ignore-type */ $this->post(
Loading history...
762
                "$this->baseurl/api2/repos/$lib/dir/?p=$pathEncoded",
763
                ['operation' => 'mkdir'],
764
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
765
            ),
766
            self::JSON_DECODE_ACCEPT_STRING | self::JSON_DECODE_ACCEPT_OBJECT,
767
        );
768
    }
769
770
    /**
771
     * delete a file
772
     *
773
     * @link https://download.seafile.com/published/web-api/v2.1/file.md#user-content-Delete%20File
774
     *
775
     * @param string $lib library id (guid)
776
     * @param string $path of the fle to delete (e.g.: "/path/to/file-to-delete", leading and trailing slashes can be omitted)
777
     * @throws InvalidArgumentException|Exception
778
     * @return string|object the common "success" or the known object with error_msg property
779
     */
780
    public function deleteFile(string $lib, string $path)
781
    {
782
        $lib = $this->verifyLib($lib);
783
        $path = $this->normalizePath($path);
784
        $pathEncoded = rawurlencode($path);
785
786
        return $this->jsonDecode(
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->jsonDecode...N_DECODE_ACCEPT_OBJECT) also could return the type array which is incompatible with the documented return type object|string.
Loading history...
787
            $this->delete(
0 ignored issues
show
Bug introduced by
It seems like $this->delete($this->bas...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

787
            /** @scrutinizer ignore-type */ $this->delete(
Loading history...
788
                "$this->baseurl/api2/repos/$lib/file/?p=$pathEncoded",
789
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
790
            ),
791
            self::JSON_DECODE_ACCEPT_STRING | self::JSON_DECODE_ACCEPT_OBJECT,
792
        );
793
    }
794
795
    /**
796
     * get a file download URL
797
     *
798
     * @link https://download.seafile.com/published/web-api/v2.1/file.md#user-content-Download%20File
799
     *
800
     * @param string $lib
801
     * @param string $path
802
     * @return string download URL (http/s)
803
     * @throws InvalidArgumentException|Exception
804
     */
805
    public function downloadFile(string $lib, string $path): string
806
    {
807
        $lib = $this->verifyLib($lib);
808
        $path = $this->normalizePath($path);
809
        $pathEncoded = rawurlencode($path);
810
811
        return $this->jsonDecode(
812
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

812
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
813
                "$this->baseurl/api2/repos/$lib/file/?p=$pathEncoded",
814
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
815
            ),
816
            self::JSON_DECODE_ACCEPT_STRING,
817
        );
818
    }
819
820
    /**
821
     * download a file
822
     *
823
     * get file contents of a file in a library
824
     *
825
     * @param string $lib
826
     * @param string $path
827
     * @throws InvalidArgumentException|Exception
828
     * @return string|false on failure
829
     */
830
    public function downloadFileAsBuffer(string $lib, string $path)
831
    {
832
        $url = $this->downloadFile($lib, $path);
833
834
        return $this->get($url);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get($url) also could return the type true which is incompatible with the documented return type false|string.
Loading history...
835
    }
836
837
    /**
838
     * download a file to a local file
839
     *
840
     * @param string $lib
841
     * @param string $path
842
     * @param string $localPath path to a file - existing or not - on the local file-system
843
     * @throws InvalidArgumentException|Exception
844
     * @return bool success/failure
845
     */
846
    public function downloadFileToFile(string $lib, string $path, string $localPath): bool
847
    {
848
        $handle = fopen($localPath, 'wb');
849
        if (false === $handle) {
850
            throw new Exception('failed to open local path for writing', self::ERROR_CODE_FILE_IO);
851
        }
852
853
        try {
854
            $result = $this->downloadFileToStream($lib, $path, $handle);
855
        } finally {
856
            $close = fclose($handle);
857
        }
858
859
        if (!$close) {
860
            throw new Exception('failed to close local path handle', self::ERROR_CODE_FILE_IO);
861
        }
862
863
        return $result;
864
    }
865
866
    /**
867
     * download a file to a stream handle
868
     *
869
     * @param string $lib
870
     * @param string $path
871
     * @param resource $handle stream handle
872
     * @throws InvalidArgumentException|Exception
873
     * @return bool success/failure
874
     */
875
    public function downloadFileToStream(string $lib, string $path, $handle): bool
876
    {
877
        $url = $this->downloadFile($lib, $path);
878
879
        return $this->get($url, [CURLOPT_RETURNTRANSFER => true, CURLOPT_FILE => $handle]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get($url, ...RLOPT_FILE => $handle)) could return the type string which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
880
    }
881
882
    /**
883
     * list items in directory
884
     *
885
     * @link https://download.seafile.com/published/web-api/v2.1/directories.md#user-content-List%20Items%20in%20Directory
886
     *
887
     * @param string $lib
888
     * @param string $path
889
     * @return array
890
     * @throws Exception
891
     * @throws InvalidArgumentException
892
     * @throws InvalidResponseException
893
     */
894
    public function listItemsInDirectory(string $lib, string $path): array
895
    {
896
        $lib = $this->verifyLib($lib);
897
        $path = $this->normalizePath($path);
898
        $pathEncoded = rawurlencode($path);
899
900
        $result = $this->jsonDecode(
901
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...ken ' . $this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

901
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
902
                "$this->baseurl/api2/repos/$lib/dir/?p=$pathEncoded",
903
                [CURLOPT_HTTPHEADER => ['Authorization: Token ' . $this->token]],
904
            ),
905
        );
906
907
        if (is_object($result)) {
908
            // likely a folder not found.
909
            $result = [];
910
        }
911
912
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result 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...
913
    }
914
915
    /**
916
     * move a file
917
     *
918
     * @link https://download.seafile.com/published/web-api/v2.1/file.md#user-content-Move%20File
919
     *
920
     * @param string $lib
921
     * @param string $path
922
     * @param string $dstLib
923
     * @param string $dstDir
924
     * @return object
925
     * @throws InvalidArgumentException|Exception
926
     */
927
    public function moveFile(string $lib, string $path, string $dstLib, string $dstDir): object
928
    {
929
        $lib = $this->verifyLib($lib);
930
        $path = $this->normalizePath($path);
931
        $pathEncoded = rawurlencode($path);
932
        $dstLib = $this->verifyLib($dstLib);
933
        $dstDir = $this->normalizePath($dstDir);
934
935
        return $this->jsonDecode(
936
            $this->post(
0 ignored issues
show
Bug introduced by
It seems like $this->post($this->baseu...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

936
            /** @scrutinizer ignore-type */ $this->post(
Loading history...
937
                "$this->baseurl/api2/repos/$lib/file/?p=$pathEncoded",
938
                ['operation' => 'move', 'dst_repo' => $dstLib, 'dst_dir' => $dstDir],
939
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
940
            ),
941
            self::JSON_DECODE_ACCEPT_OBJECT,
942
        );
943
    }
944
945
    /**
946
     * rename a file
947
     *
948
     * @link https://download.seafile.com/published/web-api/v2.1/file.md#user-content-Move%20File
949
     *
950
     * @param string $lib library id (guid)
951
     * @param string $path of the file to rename (e.g. "/path/to/file-to-rename")
952
     * @param string $newName new basename for the basename of $path (e.g. "new-file-name")
953
     * @return string|object the common "success" or the known object with error_msg property
954
     * @throws Exception
955
     */
956
    public function renameFile(string $lib, string $path, string $newName)
957
    {
958
        $lib = $this->verifyLib($lib);
959
        $path = $this->normalizePath($path);
960
        $pathEncoded = rawurlencode($path);
961
962
        return $this->jsonDecode(
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->jsonDecode...N_DECODE_ACCEPT_OBJECT) also could return the type array which is incompatible with the documented return type object|string.
Loading history...
963
            $this->post(
0 ignored issues
show
Bug introduced by
It seems like $this->post($this->baseu...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

963
            /** @scrutinizer ignore-type */ $this->post(
Loading history...
964
                "$this->baseurl/api2/repos/$lib/file/?p=$pathEncoded",
965
                ['operation' => 'rename', 'newname' => $newName],
966
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
967
            ),
968
            self::JSON_DECODE_ACCEPT_STRING | self::JSON_DECODE_ACCEPT_OBJECT,
969
        );
970
    }
971
972
    /**
973
     * simplified file upload routine for string buffer
974
     *
975
     * @param string $lib
976
     * @param string $path
977
     * @param string $buffer
978
     * @throws InvalidArgumentException
979
     * @throws Exception
980
     * @return object
981
     */
982
    public function uploadBuffer(string $lib, string $path, string $buffer): object
983
    {
984
        $lib = $this->verifyLib($lib);
985
        $path = $this->normalizePath($path);
986
987
        $parentDir = dirname($path);
988
        $uploadLink = $this->uploadGetLink($lib, $parentDir);
989
        $fileName = basename($path);
990
991
        return $this->uploadFileBuffer($uploadLink, $parentDir, '', $buffer, $fileName);
992
    }
993
994
    /**
995
     * simplified file upload routine for standard file
996
     *
997
     * @param string $lib
998
     * @param string $path path in seafile to upload the file as
999
     * @param string $file path of file to upload
1000
     * @throws InvalidArgumentException
1001
     * @throws Exception
1002
     * @return object
1003
     */
1004
    public function uploadFile(string $lib, string $path, string $file): object
1005
    {
1006
        $lib = $this->verifyLib($lib);
1007
        $path = $this->normalizePath($path);
1008
        if (!is_file($file) && !is_readable($file)) {
1009
            throw new InvalidArgumentException(sprintf('Not a readable file: %s', $file));
1010
        }
1011
1012
        $parentDir = dirname($path);
1013
        $uploadLink = $this->uploadGetLink($lib, $parentDir);
1014
        $fileName = basename($path);
1015
1016
        return $this->uploadFilePath($uploadLink, $parentDir, '', $file, $fileName);
1017
    }
1018
1019
    /**
1020
     * upload string buffer as a file
1021
     *
1022
     * same as {@see SeafileApi::uploadFile()} with the option to upload without a
1023
     * concrete file on the system. the temporary file to upload is created
1024
     * from the string buffer.
1025
     *
1026
     * @param string $uploadLink from {@see uploadGetLink}
1027
     * @param string $parentDir the parent directory to upload the file to
1028
     * @param string $relativePath the name of the file, subdirectories possible (e.g. uploading a folder)
1029
     * @param string $buffer file contents to upload as string (not the file-name)
1030
     * @param string $fileName to use as basename for the data
1031
     * @param bool $replace
1032
     * @throws Exception
1033
     * @return object
1034
     */
1035
    public function uploadFileBuffer(
1036
        string $uploadLink, string $parentDir, string $relativePath,
1037
        string $buffer, string $fileName = 'upload.dat', bool $replace = false
1038
    ): object
1039
    {
1040
        $tmpHandle = tmpfile();
1041
        if (false === $tmpHandle) {
1042
            throw new Exception('Upload data rejected: Unable to open temporary stream.');
1043
        }
1044
1045
        $meta = stream_get_meta_data($tmpHandle);
1046
        $tmpFile = $meta['uri'];
1047
        if (!is_file($tmpFile)) {
1048
            fclose($tmpHandle);
1049
            throw new Exception('Upload data rejected: No file with temporary stream.');
1050
        }
1051
1052
        $bytes = fwrite($tmpHandle, $buffer);
1053
        if (false === $bytes) {
1054
            fclose($tmpHandle);
1055
            throw new Exception('Upload data rejected: Failed to write to temporary stream.');
1056
        }
1057
1058
        $diff = strlen($buffer) - $bytes;
1059
        if ($diff !== 0) {
1060
            fclose($tmpHandle);
1061
            throw new Exception(sprintf("Upload data rejected: Unexpected difference writing to temporary stream: %d bytes", $diff));
1062
        }
1063
1064
        $result = rewind($tmpHandle);
1065
        if (false === $result) {
1066
            fclose($tmpHandle);
1067
            throw new Exception('Upload data rejected: Failed to rewind temporary stream.');
1068
        }
1069
1070
        $result = $this->uploadFilePath($uploadLink, $parentDir, $relativePath, $tmpFile, $fileName, $replace);
1071
        fclose($tmpHandle);
1072
1073
        return $result;
1074
    }
1075
1076
    /**
1077
     * upload file
1078
     *
1079
     * @link https://download.seafile.com/published/web-api/v2.1/file-upload.md#user-content-Upload%20File
1080
     *
1081
     * @param string $uploadLink from {@see uploadGetLink}
1082
     * @param string $parentDir the parent directory to upload a file to
1083
     * @param string $relativePath to place the file in under $uploadPath (can include subdirectories)
1084
     * @param string $path path of the file to upload
1085
     * @param ?string $fileName to use as basename for the file (the name used in Seafile)
1086
     * @param bool $replace
1087
     * @return object
1088
     * @throws Exception
1089
     */
1090
    public function uploadFilePath(
1091
        string $uploadLink, string $parentDir, string $relativePath,
1092
        string $path, string $fileName = null, bool $replace = false
1093
    ): object
1094
    {
1095
        $parentDir = $this->normalizePath($parentDir);
1096
        $relativePath = ltrim('/', $this->normalizePath($relativePath));
1097
        $fileName = $fileName ?? basename($path);
1098
1099
        $fields = [
1100
            'file' => new CURLFile($path, 'application/octet-stream', $fileName),
1101
            'parent_dir' => $parentDir,
1102
            'relative_path' => $relativePath,
1103
            'replace' => $replace ? '1' : '0',
1104
        ];
1105
1106
        return $this->jsonDecode(
1107
            $this->post(
0 ignored issues
show
Bug introduced by
It seems like $this->post($uploadLink....Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1107
            /** @scrutinizer ignore-type */ $this->post(
Loading history...
1108
                "$uploadLink?ret-json=1",
1109
                $fields,
1110
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
1111
            ),
1112
            self::JSON_DECODE_ACCEPT_ARRAY_SINGLE_OBJECT,
1113
        );
1114
    }
1115
1116
    /**
1117
     * get file upload link
1118
     *
1119
     * @link https://download.seafile.com/published/web-api/v2.1/file-upload.md#user-content-Get%20Upload%20Link
1120
     *
1121
     * @param string $lib
1122
     * @param string $uploadDir the directory to upload file(s) to
1123
     * @return string upload link (https?://...)
1124
     * @throws InvalidArgumentException|Exception
1125
     */
1126
    public function uploadGetLink(string $lib, string $uploadDir): string
1127
    {
1128
        $lib = $this->verifyLib($lib);
1129
        $uploadDir = $this->normalizePath($uploadDir);
1130
        $pathEncoded = rawurlencode($uploadDir);
1131
1132
        return $this->jsonDecode(
1133
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1133
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
1134
                "$this->baseurl/api2/repos/$lib/upload-link/?p=$pathEncoded",
1135
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
1136
            ),
1137
            self::JSON_DECODE_ACCEPT_STRING,
1138
        );
1139
    }
1140
1141
    public function generateUserAuthToken(string $email): object
1142
    {
1143
        return $this->jsonDecode(
1144
            $this->post(
0 ignored issues
show
Bug introduced by
It seems like $this->post($this->baseu...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1144
            /** @scrutinizer ignore-type */ $this->post(
Loading history...
1145
                "$this->baseurl/api/v2.1/admin/generate-user-auth-token/",
1146
                ['email' => $email],
1147
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
1148
            ),
1149
            self::JSON_DECODE_ACCEPT_OBJECT,
1150
        );
1151
    }
1152
1153
    public function getUserActivity(string $email, int $page = 1, int $perPage = 25): object
1154
    {
1155
        return $this->jsonDecode(
1156
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1156
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
1157
                "$this->baseurl/api/v2.1/admin/user-activities/?user=$email&page=$page&per_page=$perPage",
1158
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
1159
            ),
1160
            self::JSON_DECODE_ACCEPT_OBJECT,
1161
        );
1162
    }
1163
1164
    /**
1165
     * set authorization token
1166
     *
1167
     * @param string $token
1168
     * @return void
1169
     */
1170
    public function setToken(string $token): void
1171
    {
1172
        $this->token = $token;
1173
    }
1174
1175
    public function listDevices(): array
1176
    {
1177
        return $this->jsonDecode(
1178
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1178
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
1179
                "$this->baseurl/api2/devices/",
1180
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
1181
            ),
1182
        );
1183
    }
1184
1185
    public function listStarredItems(): object
1186
    {
1187
        return $this->jsonDecode(
1188
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1188
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
1189
                "$this->baseurl/api/v2.1/starred-items/",
1190
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
1191
            ),
1192
        );
1193
    }
1194
1195
    public function listGroups(): object
1196
    {
1197
        return $this->jsonDecode(
1198
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1198
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
1199
                "$this->baseurl/api2/groups/",
1200
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
1201
            ),
1202
        );
1203
    }
1204
1205
    public function listInvitations(): array
1206
    {
1207
        return $this->jsonDecode(
1208
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1208
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
1209
                "$this->baseurl/api/v2.1/invitations/",
1210
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
1211
            ),
1212
        );
1213
    }
1214
1215
    /**
1216
     * @param string $start e.g. date('Y-m-d', time() - 7776000 ); // 3 months
1217
     * @param string $end e.g. date('Y-m-d', time());
1218
     *
1219
     * @return array
1220
     */
1221
    public function getLoginLog(string $start, string $end): array
1222
    {
1223
        $start = rawurlencode($start);
1224
        $end = rawurlencode($end);
1225
1226
        return $this->jsonDecode(
1227
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1227
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
1228
                "$this->baseurl/api/v2.1/admin/logs/login/?start=$start&end=$end",
1229
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
1230
            ),
1231
        );
1232
    }
1233
1234
    public function listUploadLinks(): array
1235
    {
1236
        return $this->jsonDecode(
1237
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1237
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
1238
                "$this->baseurl/api/v2.1/upload-links/",
1239
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
1240
            ),
1241
        );
1242
    }
1243
1244
    public function listRepoApiTokens(string $lib): object
1245
    {
1246
        $lib = $this->verifyLib($lib);
1247
        return $this->jsonDecode(
1248
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1248
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
1249
                "$this->baseurl/api/v2.1/repos/$lib/repo-api-tokens/",
1250
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
1251
            ),
1252
        );
1253
    }
1254
1255
    /**
1256
     * internal api implementation to get library by name
1257
     *
1258
     * @param string $name
1259
     * @throws Exception
1260
     * @return ?string id (guid) of the library, null if library does not exist
1261
     */
1262
    private function getLibraryIdByLibraryName(string $name): ?string
1263
    {
1264
        $name = explode('/', $this->normalizePath($name), 2)[1];
1265
1266
        $libraries = $this->listLibrariesCached();
1267
        $libraries = array_column($libraries, null, 'name');
1268
        return $libraries[$name]->id ?? null;
1269
    }
1270
1271
    /**
1272
     * List libraries a user can access.
1273
     *
1274
     * internal api implementation for {@see listLibrariesCached()} and {@see listLibraries()}
1275
     *
1276
     * @link https://download.seafile.com/published/web-api/v2.1/libraries.md#user-content-List%20Libraries
1277
     *
1278
     * @throws Exception
1279
     * @return object[]
1280
     */
1281
    private function listLibrariesDo(): array
1282
    {
1283
        return $this->jsonDecode(
1284
            $this->get(
0 ignored issues
show
Bug introduced by
It seems like $this->get($this->baseur...Token '.$this->token))) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1284
            /** @scrutinizer ignore-type */ $this->get(
Loading history...
1285
                "$this->baseurl/api2/repos/",
1286
                [CURLOPT_HTTPHEADER => ["Authorization: Token $this->token"]],
1287
            ),
1288
            self::JSON_DECODE_ACCEPT_ARRAY_OF_OBJECTS,
1289
        );
1290
    }
1291
1292
    /**
1293
     * like {@see listLibraries()} but cached.
1294
     *
1295
     * @param bool $invalidate
1296
     * @throws Exception
1297
     * @throws InvalidResponseException
1298
     * @return void
1299
     */
1300
    private function listLibrariesCached(bool $invalidate = false): array
1301
    {
1302
        static $librariesCache;
1303
1304
        return $librariesCache = ($invalidate ? null : $librariesCache) ?? $this->listLibrariesDo();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $librariesCache =...this->listLibrariesDo() also could return the type array which is incompatible with the documented return type void.
Loading history...
1305
    }
1306
1307
    /**
1308
     * normalize path
1309
     *
1310
     * normalizes the path component separator <slash> "/" <U002F> U+002F SOLIDUS
1311
     * with first character being the slash, no consecutive slashes within the
1312
     * path and no terminating slash.
1313
     *
1314
     * @param string $path
1315
     * @return string
1316
     */
1317
    private function normalizePath(string $path): string
1318
    {
1319
        $buffer = rtrim($path, '/');
1320
        $buffer = preg_replace('~/{2,}~', '/', $buffer);
1321
        '' === $buffer && $buffer = '/';
1322
        '/' === $buffer[0] || $buffer = "/$buffer";
1323
1324
        return $buffer;
1325
    }
1326
1327
    /**
1328
     * verify library id
1329
     *
1330
     * verifies the format of a library id. can be used in URLs
1331
     * afterwards. case normalization to lowercase.
1332
     *
1333
     * example library id strings:
1334
     *  - 21b941c2-5411-4372-a514-00b62ab99ef2 (from the docs)
1335
     *  - 79144b25-f772-42b6-a1c0-60e6359f5884 (from a server)
1336
     *
1337
     * @param string $lib
1338
     * @param bool $allowEmpty
1339
     * @return string library id
1340
     */
1341
    private function verifyLib(string $lib, bool $allowEmpty = false): string
1342
    {
1343
        if ($allowEmpty && ('' === $lib)) {
1344
            return $lib;
1345
        }
1346
1347
        $buffer = strtr($lib, self::HEX_ALPHA_UPPER, self::HEX_ALPHA_LOWER);
1348
        $format = '%04x%04x-%04x-%04x-%04x-%04x%04x%04x';
1349
        $values = sscanf($buffer, $format);
1350
        $result = vsprintf($format, $values);
0 ignored issues
show
Bug introduced by
It seems like $values can also be of type integer and null; however, parameter $values of vsprintf() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

1350
        $result = vsprintf($format, /** @scrutinizer ignore-type */ $values);
Loading history...
1351
1352
        if ($buffer !== $result) {
1353
            throw new InvalidArgumentException(sprintf('Not a library id: "%s"', $lib));
1354
        }
1355
1356
        return $result;
1357
    }
1358
1359
    /**
1360
     * verify share link token
1361
     *
1362
     * verifies the format of a share link token. can be used in URLs
1363
     * afterwards. case normalization to lowercase.
1364
     *
1365
     * @param string $token e.g. "0a29ff44dc0b4b56be74"
1366
     * @return string
1367
     */
1368
    private function verifyToken(string $token): string
1369
    {
1370
        $buffer = strtr($token, self::HEX_ALPHA_UPPER, self::HEX_ALPHA_LOWER);
1371
        $format = '%04x%04x%04x%04x%04x';
1372
        $values = sscanf($buffer, $format);
1373
        $result = vsprintf($format, $values);
0 ignored issues
show
Bug introduced by
It seems like $values can also be of type integer and null; however, parameter $values of vsprintf() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

1373
        $result = vsprintf($format, /** @scrutinizer ignore-type */ $values);
Loading history...
1374
1375
        if ($buffer !== $result) {
1376
            throw new InvalidArgumentException(sprintf('Not a token: "%s"', $token));
1377
        }
1378
1379
        return $result;
1380
    }
1381
1382
    /**
1383
     * authenticate class against seafile api
1384
     *
1385
     * @link https://download.seafile.com/published/web-api/home.md#user-content-Quick%20Start
1386
     *
1387
     * @param string|null $otp (optional) Seafile OTP (if user uses OTP access)
1388
     * @return void
1389
     */
1390
    private function setTokenByUsernameAndPassword(string $otp = null): void
1391
    {
1392
        // @auth:token:<email> : password is auth token
1393
        if (0 === strpos($this->user, $needle = self::USER_PREFIX_AUTH_TOKEN)) {
1394
            $this->user = \substr($this->user, strlen($needle)) ?: '';
1395
            $this->token = $this->pass;
1396
            if ('pong' !== $this->ping()) {
1397
                throw new ConnectionException('token authentication failure');
1398
            }
1399
            return;
1400
        }
1401
1402
        $data = $this->jsonDecode(
1403
            $this->post(
0 ignored issues
show
Bug introduced by
It seems like $this->post($this->baseu...TP: '.$otp)) : array()) can also be of type true; however, parameter $jsonText of Datamate\SeafileApi\SeafileApi::jsonDecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1403
            /** @scrutinizer ignore-type */ $this->post(
Loading history...
1404
                "$this->baseurl/api2/auth-token/",
1405
                ['username' => $this->user, 'password' => $this->pass],
1406
                $otp ? [CURLOPT_HTTPHEADER => ["X-SEAFILE-OTP: $otp"]] : [],
1407
            ),
1408
            self::JSON_DECODE_ACCEPT_OBJECT,
1409
        );
1410
        $this->token = (string)$data->token;
1411
    }
1412
1413
    /**
1414
     * http request with get method
1415
     *
1416
     * @param string $url
1417
     * @param array $curlOptions
1418
     * @return bool|string
1419
     */
1420
    private function get(string $url, array $curlOptions = [])
1421
    {
1422
        $curlOptions += $this->curlSharedOptions;
1423
1424
        return $this->curlExec($url, $curlOptions);
1425
    }
1426
1427
    /**
1428
     * http request with post method
1429
     *
1430
     * @param string $url
1431
     * @param array $fields
1432
     * @param array $curlOptions
1433
     * @return bool|string
1434
     */
1435
    private function post(string $url, array $fields = [], array $curlOptions = [])
1436
    {
1437
        $curlOptions += $this->curlSharedOptions;
1438
        $curlOptions[CURLOPT_POST] = true;
1439
        $curlOptions[CURLOPT_POSTFIELDS] = $fields;
1440
1441
        return $this->curlExec($url, $curlOptions);
1442
    }
1443
1444
    /**
1445
     * http request with put method
1446
     *
1447
     * @param string $url
1448
     * @param array $fields
1449
     * @param array $curlOptions
1450
     * @return bool|string
1451
     */
1452
    public function put(string $url, array $fields = [], array $curlOptions = [])
1453
    {
1454
        $curlOptions += $this->curlSharedOptions;
1455
        $curlOptions[CURLOPT_CUSTOMREQUEST] = 'PUT';
1456
        $curlOptions[CURLOPT_POSTFIELDS] = $fields;
1457
1458
        return $this->curlExec($url, $curlOptions);
1459
    }
1460
1461
    /**
1462
     * http request with delete method
1463
     *
1464
     * @param string $url
1465
     * @param array $curlOptions
1466
     * @return bool|string
1467
     */
1468
    public function delete(string $url, array $curlOptions = [])
1469
    {
1470
1471
        $curlOptions += $this->curlSharedOptions;
1472
        $curlOptions[CURLOPT_CUSTOMREQUEST] = 'DELETE';
1473
1474
        return $this->curlExec($url, $curlOptions);
1475
    }
1476
1477
    /**
1478
     * json decode handler
1479
     *
1480
     * decode json with structural acceptance
1481
     *
1482
     * @param string $jsonText
1483
     * @param int $flags decode accept flag
1484
     * @throws InvalidResponseException
1485
     * @return string|object|array
1486
     */
1487
    private function jsonDecode(string $jsonText, int $flags = self::JSON_DECODE_ACCEPT_DEFAULT)
1488
    {
1489
        $accept = $flags & self::JSON_DECODE_ACCEPT_MASK;
1490
        if (0 === $accept) {
1491
            return $jsonText;
1492
        }
1493
1494
        try {
1495
            $result = json_decode($jsonText, false, 512, JSON_THROW_ON_ERROR);
1496
        } /** @noinspection PhpMultipleClassDeclarationsInspection */ catch (JsonException $e) {
1497
            throw JsonDecodeException::create(sprintf('json decode error of %s', JsonDecodeException::shorten($jsonText)), $jsonText, $e);
1498
        }
1499
1500
        if (self::JSON_DECODE_ACCEPT_JSON === $accept) {
1501
            return $result;
1502
        }
1503
1504
        if (self::JSON_DECODE_ACCEPT_ARRAY_OF_OBJECTS === $accept) {
1505
            if (is_array($result) && $result === array_filter($result, 'is_object')) {
1506
                return $result;
1507
            }
1508
            throw JsonDecodeException::create(sprintf('json decode accept %5d error [%s] of %s', decbin($accept), gettype($result), JsonDecodeException::shorten($jsonText)), $jsonText);
1509
        }
1510
1511
        if (self::JSON_DECODE_ACCEPT_ARRAY_SINGLE_OBJECT_NULLABLE === $accept) {
1512
            if (is_array($result)
1513
                && (
1514
                    (count($result) === 1 && is_object($result[0] ?? null))
1515
                    || (count($result) === 0)
1516
                )
1517
            ) {
1518
                return $result[0] ?? null;
1519
            }
1520
            throw JsonDecodeException::create(sprintf('json decode accept %5d error [%s] of %s', decbin($accept), gettype($result), JsonDecodeException::shorten($jsonText)), $jsonText);
1521
        }
1522
1523
        if (self::JSON_DECODE_ACCEPT_ARRAY_SINGLE_OBJECT === $accept) {
1524
            if (is_array($result) && is_object($result[0] ?? null) && count($result) === 1) {
1525
                return $result[0];
1526
            }
1527
            throw JsonDecodeException::create(sprintf('json decode accept %5d error [%s] of %s', decbin($accept), gettype($result), JsonDecodeException::shorten($jsonText)), $jsonText);
1528
        }
1529
1530
        if (self::JSON_DECODE_ACCEPT_SUCCESS_OBJECT === $accept) {
1531
            if (is_object($result) && (array)$result === ['success' => true]) {
1532
                return $result;
1533
            }
1534
            throw JsonDecodeException::create(sprintf('json decode accept %5d error [%s] of %s', decbin($accept), gettype($result), JsonDecodeException::shorten($jsonText)), $jsonText);
1535
        }
1536
1537
        if (self::JSON_DECODE_ACCEPT_SUCCESS_STRING === $accept) {
1538
            if (self::STRING_SUCCESS === $result) {
1539
                return $result;
1540
            }
1541
            throw JsonDecodeException::create(sprintf('json decode accept %5d error [%s] of %s', decbin($accept), gettype($result), JsonDecodeException::shorten($jsonText)), $jsonText);
1542
        }
1543
1544
        if (is_string($result) && (self::JSON_DECODE_ACCEPT_STRING !== ($accept & self::JSON_DECODE_ACCEPT_STRING))) {
1545
            throw JsonDecodeException::create(sprintf('json decode type %s not accepted; of %s', gettype($result), JsonDecodeException::shorten($jsonText)), $jsonText);
1546
        }
1547
1548
        if (is_array($result) && (self::JSON_DECODE_ACCEPT_ARRAY !== ($accept & self::JSON_DECODE_ACCEPT_ARRAY))) {
1549
            throw JsonDecodeException::create(sprintf('json decode type %s not accepted; of %s', gettype($result), JsonDecodeException::shorten($jsonText)), $jsonText);
1550
        }
1551
1552
        if (is_object($result) && (self::JSON_DECODE_ACCEPT_OBJECT !== ($accept & self::JSON_DECODE_ACCEPT_OBJECT))) {
1553
            throw JsonDecodeException::create(sprintf('json decode type %s not accepted; of %s', gettype($result), JsonDecodeException::shorten($jsonText)), $jsonText);
1554
        }
1555
1556
        return $result;
1557
    }
1558
1559
1560
    /**
1561
     * execute curl with url and options
1562
     *
1563
     * @param string $url
1564
     * @param array $options
1565
     * @return bool|string
1566
     */
1567
    private function curlExec(string $url, array $options)
1568
    {
1569
        $handle = curl_init($url);
1570
        if (!($handle instanceof CurlHandle)) {
1571
            throw new ConnectionException('Unable to initialise cURL session.', self::ERROR_CODE_NO_CURL);
1572
        }
1573
1574
        $this->handle = $handle;
1575
1576
        if (!curl_setopt_array($this->handle, $options)) {
1577
            throw new Exception("Error setting cURL request options.");
1578
        }
1579
        $result = curl_exec($this->handle);
1580
        $this->curlExecHandleResult($result);
1581
        curl_close($this->handle);
1582
1583
        return $result;
1584
    }
1585
1586
    /**
1587
     * internal handling of curl_exec() return
1588
     *
1589
     * {@see curlExec()}
1590
     *
1591
     * @param bool|string $curlResult return value from curl_exec();
1592
     * @throws ConnectionException
1593
     * @return void
1594
     */
1595
    private function curlExecHandleResult($curlResult): void
1596
    {
1597
        if (empty($curlResult)) {
1598
            throw new ConnectionException(curl_error($this->handle), -1);
1599
        }
1600
1601
        $code = (int)curl_getinfo($this->handle)['http_code'];
1602
1603
        $codeIsInErrorRange = $code >= 400 && $code <= 600;
1604
        $codeIsNotInNonErrorCodes = !in_array($code, [200, 201, 202, 203, 204, 205, 206, 207, 301], true);
1605
1606
        if ($codeIsInErrorRange || $codeIsNotInNonErrorCodes) {
1607
            ConnectionException::throwCurlResult($code, $curlResult);
1608
        }
1609
    }
1610
}
1611