Completed
Push — master ( 287393...7ff50d )
by Raffael
18:27 queued 14:12
created

src/app/Balloon.App.Api/v2/Nodes.php (23 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2019 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Balloon\App\Api\v2;
13
14
use Balloon\App\Api\Controller;
15
use Balloon\App\Api\Helper as ApiHelper;
16
use Balloon\App\Api\v2\Collections as ApiCollection;
17
use Balloon\App\Api\v2\Files as ApiFile;
18
use Balloon\AttributeDecorator\Pager;
19
use Balloon\Filesystem;
20
use Balloon\Filesystem\DeltaAttributeDecorator;
21
use Balloon\Filesystem\EventAttributeDecorator;
22
use Balloon\Filesystem\Exception;
0 ignored issues
show
This use statement conflicts with another class in this namespace, Balloon\App\Api\v2\Exception.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
23
use Balloon\Filesystem\Node\AttributeDecorator as NodeAttributeDecorator;
24
use Balloon\Filesystem\Node\Collection;
25
use Balloon\Filesystem\Node\File;
26
use Balloon\Filesystem\Node\NodeInterface;
27
use Balloon\Helper;
28
use Balloon\Server;
29
use Balloon\Server\User;
30
use Micro\Http\Response;
31
use function MongoDB\BSON\fromJSON;
32
use function MongoDB\BSON\toPHP;
33
use MongoDB\BSON\UTCDateTime;
34
use Psr\Log\LoggerInterface;
35
use ZipStream\ZipStream;
36
37
class Nodes extends Controller
38
{
39
    /**
40
     * Filesystem.
41
     *
42
     * @var Filesystem
43
     */
44
    protected $fs;
45
46
    /**
47
     * LoggerInterface.
48
     *
49
     * @var LoggerInterface
50
     */
51
    protected $logger;
52
53
    /**
54
     * Server.
55
     *
56
     * @var Server
57
     */
58
    protected $server;
59
60
    /**
61
     * User.
62
     *
63
     * @var User
64
     */
65
    protected $user;
66
67
    /**
68
     * Node attribute decorator.
69
     *
70
     * @var NodeAttributeDecorator
71
     */
72
    protected $node_decorator;
73
74
    /**
75
     * Initialize.
76
     */
77
    public function __construct(Server $server, NodeAttributeDecorator $decorator, LoggerInterface $logger)
78
    {
79
        $this->fs = $server->getFilesystem();
80
        $this->user = $server->getIdentity();
81
        $this->server = $server;
82
        $this->node_decorator = $decorator;
83
        $this->logger = $logger;
84
    }
85
86
    /**
87
     * @api {head} /api/v2/nodes/:id Node exists?
88
     * @apiVersion 2.0.0
89
     * @apiName head
90
     * @apiGroup Node
91
     * @apiPermission none
92
     * @apiDescription Check if a node exists. Per default deleted nodes are ignore which means it will
93
     *  return a 404 if a deleted node is requested. You can change this behaviour via the deleted parameter.
94
     * @apiUse _getNode
95
     *
96
     * @apiExample (cURL) example:
97
     * curl -XHEAD "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686"
98
     * curl -XHEAD "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686"
99
     * curl -XHEAD "https://SERVER/api/v2/node?p=/absolute/path/to/my/node"
100
     *
101
     * @apiParam (GET Parameter) {number} [deleted=0] Wherever include deleted node or not, possible values:</br>
102
     * - 0 Exclude deleted</br>
103
     * - 1 Only deleted</br>
104
     * - 2 Include deleted</br>
105
     *
106
     * @apiSuccessExample {json} Success-Response (Node does exist):
107
     * HTTP/1.1 200 OK
108
     *
109
     * @apiSuccessExample {json} Success-Response (Node does not exist):
110
     * HTTP/1.1 404 Not Found
111
     *
112
     * @param string $id
113
     * @param string $p
114
     */
115
    public function head(?string $id = null, ?string $p = null, int $deleted = 0): Response
116
    {
117
        try {
118
            $result = $this->_getNode($id, $p, null, false, false, $deleted);
119
120
            $response = (new Response())
121
                ->setHeader('Content-Length', (string) $result->getSize())
122
                ->setHeader('Content-Type', $result->getContentType())
123
                ->setCode(200);
124
125
            return $response;
126
        } catch (\Exception $e) {
127
            return (new Response())->setCode(404);
128
        }
129
    }
130
131
    /**
132
     * @api {post} /api/v2/nodes/:id/undelete Restore node
133
     * @apiVersion 2.0.0
134
     * @apiName postUndelete
135
     * @apiGroup Node
136
     * @apiPermission none
137
     * @apiDescription Undelete (Restore from trash) a single node or multiple ones.
138
     * @apiUse _getNodes
139
     * @apiUse _conflictNode
140
     * @apiUse _multiError
141
     * @apiUse _writeAction
142
     *
143
     * @apiExample (cURL) example:
144
     * curl -XPOST "https://SERVER/api/v2/nodes/undelete?id[]=544627ed3c58891f058b4686&id[]=544627ed3c58891f058b46865&pretty"
145
     * curl -XPOST "https://SERVER/api/v2/nodes/undelete?id=544627ed3c58891f058b4686?pretty"
146
     * curl -XPOST "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/undelete?conflict=2"
147
     * curl -XPOST "https://SERVER/api/v2/nodes/undelete?p=/absolute/path/to/my/node&conflict=0&move=1&destid=544627ed3c58891f058b46889"
148
     *
149
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
150
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
151
     *
152
     * @apiSuccessExample {json} Success-Response (conflict=1):
153
     * HTTP/1.1 200 OK
154
     * {
155
     *      "id": "544627ed3c58891f058b4686",
156
     *      "name": "renamed (xy23)"
157
     * }
158
     *
159
     * @param array|string $id
160
     * @param array|string $p
161
     * @param string       $destid
162
     * @param string       $destp
163
     */
164
    public function postUndelete(
165
        $id = null,
166
        $p = null,
167
        bool $move = false,
168
        ?string $destid = null,
169
        ?string $destp = null,
170
        int $conflict = 0
171
    ): Response {
172
        $parent = null;
173
        if (true === $move) {
174
            try {
175
                $parent = $this->_getNode($destid, $destp, 'Collection', false, true);
176
            } catch (Exception\NotFound $e) {
177
                throw new Exception\NotFound(
178
                    'destination collection was not found or is not a collection',
179
                    Exception\NotFound::DESTINATION_NOT_FOUND
180
                );
181
            }
182
        }
183
184
        return $this->bulk($id, $p, function ($node) use ($parent, $conflict, $move) {
0 ignored issues
show
It seems like $id defined by parameter $id on line 165 can also be of type null; however, Balloon\App\Api\Controller::bulk() does only seem to accept array|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $p defined by parameter $p on line 166 can also be of type null; however, Balloon\App\Api\Controller::bulk() does only seem to accept array|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
185
            if (true === $move) {
186
                $node = $node->setParent($parent, $conflict);
187
            }
188
189
            $node->undelete($conflict);
190
191
            return [
192
                'code' => 200,
193
                'data' => $this->node_decorator->decorate($node),
194
            ];
195
        });
196
    }
197
198
    /**
199
     * @api {get} /api/v2/nodes/:id/content Download stream
200
     * @apiVersion 2.0.0
201
     * @apiName getContent
202
     * @apiGroup Node
203
     * @apiPermission none
204
     * @apiDescription Download node contents. Collections are zipped during streaming.
205
     * @apiUse _getNode
206
     * @apiParam (GET Parameter) {boolean} [download=false] Force download file (Content-Disposition: attachment HTTP header)
207
     *
208
     * @apiExample (cURL) example:
209
     * curl -XGET "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686" > myfile.txt
210
     * curl -XGET "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686" > myfile.txt
211
     * curl -XGET "https://SERVER/api/v2/node?p=/absolute/path/to/my/collection" > folder.zip
212
     *
213
     * @apiSuccessExample {binary} Success-Response:
214
     * HTTP/1.1 200 OK
215
     *
216
     * @apiErrorExample {json} Error-Response (Invalid offset):
217
     * HTTP/1.1 400 Bad Request
218
     * {
219
     *      "status": 400,
220
     *      "data": {
221
     *          "error": "Balloon\\Exception\\Conflict",
222
     *          "message": "invalid offset requested",
223
     *          "code": 277
224
     *      }
225
     * }
226
     *
227
     * @param array|string $id
228
     * @param array|string $p
229
     */
230
    public function getContent(
231
        $id = null,
232
        $p = null,
233
        bool $download = false,
234
        string $name = 'selected'
235
    ): ?Response {
236
        if (is_array($id) || is_array($p)) {
237
            return $this->combine($id, $p, $name);
0 ignored issues
show
It seems like $id defined by parameter $id on line 231 can also be of type array; however, Balloon\App\Api\v2\Nodes::combine() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $p defined by parameter $p on line 232 can also be of type array; however, Balloon\App\Api\v2\Nodes::combine() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
238
        }
239
240
        $node = $this->_getNode($id, $p);
241
        if ($node instanceof Collection) {
242
            return $node->getZip();
243
        }
244
245
        $response = new Response();
246
247
        return ApiHelper::streamContent($response, $node, $download);
248
    }
249
250
    /**
251
     * @apiDefine _nodeAttributes
252
     *
253
     * @apiSuccess (200 OK) {string} id Unique node id
254
     * @apiSuccess (200 OK) {string} name Name
255
     * @apiSuccess (200 OK) {string} hash MD5 content checksum (file only)
256
     * @apiSuccess (200 OK) {object} meta Extended meta attributes
257
     * @apiSuccess (200 OK) {string} meta.description UTF-8 Text Description
258
     * @apiSuccess (200 OK) {string} meta.color Color Tag (HEX) (Like: #000000)
259
     * @apiSuccess (200 OK) {string} meta.author Author
260
     * @apiSuccess (200 OK) {string} meta.mail Mail contact address
261
     * @apiSuccess (200 OK) {string} meta.license License
262
     * @apiSuccess (200 OK) {string} meta.copyright Copyright string
263
     * @apiSuccess (200 OK) {string[]} meta.tags Search Tags
264
     * @apiSuccess (200 OK) {number} size Size in bytes (file only), number of children if collection
265
     * @apiSuccess (200 OK) {string} mime Mime type
266
     * @apiSuccess (200 OK) {boolean} sharelink Is node shared?
267
     * @apiSuccess (200 OK) {number} version File version (file only)
268
     * @apiSuccess (200 OK) {mixed} deleted Is boolean false if not deleted, if deleted it contains a deleted timestamp
269
     * @apiSuccess (200 OK) {string} deleted ISO8601 timestamp, only set if node is deleted
270
     * @apiSuccess (200 OK) {string} changed ISO8601 timestamp
271
     * @apiSuccess (200 OK) {string} created ISO8601 timestamp
272
     * @apiSuccess (200 OK) {string} destroy ISO8601 timestamp, only set if node has a destroy timestamp set
273
     * @apiSuccess (200 OK) {boolean} share Node is shared
274
     * @apiSuccess (200 OK) {boolean} directory Is true if the node is a collection
275
     * @apiSuccess (200 OK) {string} access Access permission for the authenticated user (d/r/rw/m)
276
     * @apiSuccess (200 OK) {object} shareowner Share owner
277
     * @apiSuccess (200 OK) {object} parent Parent node
278
     * @apiSuccess (200 OK) {string} path Absolute node path
279
     * @apiSuccess (200 OK) {string} filter Node is filtered (collection only)
280
     * @apiSuccess (200 OK) {boolean} readonly Readonly
281
     *
282
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes
283
     *
284
     * @param null|mixed $id
285
     * @param null|mixed $p
286
     * @param null|mixed $query
287
     */
288
289
    /**
290
     * @api {get} /api/v2/nodes/:id Get attributes
291
     * @apiVersion 2.0.0
292
     * @apiName get
293
     * @apiGroup Node
294
     * @apiPermission none
295
     * @apiDescription Get attributes from one or multiple nodes
296
     * @apiUse _getNode
297
     * @apiUse _nodeAttributes
298
     *
299
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default only a bunch of attributes would be returned, if you
300
     * need other attributes you have to request them (for example "path")
301
     *
302
     * @apiExample (cURL) example:
303
     * curl -XGET "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686&pretty"
304
     * curl -XGET "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted&pretty"
305
     * curl -XGET "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686?pretty"
306
     * curl -XGET "https://SERVER/api/v2/node?p=/absolute/path/to/my/node&pretty"
307
     *
308
     * @apiSuccessExample {json} Success-Response:
309
     * HTTP/1.1 200 OK
310
     * {
311
     *      "id": "544627ed3c58891f058b4686",
312
     *      "name": "api.php",
313
     *      "hash": "a77f23ed800fd7a600a8c2cfe8cc370b",
314
     *      "meta": {
315
     *          "license": "GPLv3"
316
     *      },
317
     *      "size": 178,
318
     *      "mime": "text\/plain",
319
     *      "sharelink": true,
320
     *      "version": 1,
321
     *      "changed": "2007-08-31T16:47+00:00",
322
     *      "created": "2007-08-31T16:47+00:00",
323
     *      "share": false,
324
     *      "directory": false
325
     * }
326
     *
327
     * @param array|string $id
328
     * @param array|string $p
329
     */
330
    public function get($id = null, $p = null, int $deleted = 0, $query = null, array $attributes = [], int $offset = 0, int $limit = 20): Response
331
    {
332
        if ($id === null && $p === null) {
333
            if ($query === null) {
334
                $query = [];
335
            } elseif (is_string($query)) {
336
                $query = toPHP(fromJSON($query), [
337
                    'root' => 'array',
338
                    'document' => 'array',
339
                    'array' => 'array',
340
                ]);
341
            }
342
343
            if ($this instanceof ApiFile) {
344
                $query['directory'] = false;
345
                $uri = '/api/v2/files';
346
            } elseif ($this instanceof ApiCollection) {
347
                $query['directory'] = true;
348
                $uri = '/api/v2/collections';
349
            } else {
350
                $uri = '/api/v2/nodes';
351
            }
352
353
            $nodes = $this->fs->findNodesByFilterUser($deleted, $query, $offset, $limit);
354
            $pager = new Pager($this->node_decorator, $nodes, $attributes, $offset, $limit, $uri);
355
            $result = $pager->paging();
356
357
            return (new Response())->setCode(200)->setBody($result);
358
        }
359
360
        return $this->bulk($id, $p, function ($node) use ($attributes) {
0 ignored issues
show
It seems like $id defined by parameter $id on line 330 can also be of type null; however, Balloon\App\Api\Controller::bulk() does only seem to accept array|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $p defined by parameter $p on line 330 can also be of type null; however, Balloon\App\Api\Controller::bulk() does only seem to accept array|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
361
            return [
362
                'code' => 200,
363
                'data' => $this->node_decorator->decorate($node, $attributes),
364
            ];
365
        });
366
    }
367
368
    /**
369
     * @api {get} /api/v2/nodes/:id/parents Get parent nodes
370
     * @apiVersion 2.0.0
371
     * @apiName getParents
372
     * @apiGroup Node
373
     * @apiPermission none
374
     * @apiDescription Get system attributes of all parent nodes. The hirarchy of all parent nodes is ordered in a
375
     * single level array beginning with the collection on the highest level.
376
     * @apiUse _getNode
377
     * @apiUse _nodeAttributes
378
     *
379
     * @apiParam (GET Parameter) {boolean} [self=true] Include requested collection itself at the end of the list (Will be ignored if the requested node is a file)
380
     *
381
     * @apiExample (cURL) example:
382
     * curl -XGET "https://SERVER/api/v2/nodes/parents?id=544627ed3c58891f058b4686&pretty"
383
     * curl -XGET "https://SERVER/api/v2/nodes/parents?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted&pretty"
384
     * curl -XGET "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/parents?pretty&self=1"
385
     * curl -XGET "https://SERVER/api/v2/nodes/parents?p=/absolute/path/to/my/node&self=1"
386
     *
387
     * @apiSuccessExample {json} Success-Response:
388
     * HTTP/1.1 200 OK
389
     * [
390
     *  {
391
     *      "id": "544627ed3c58891f058bbbaa",
392
     *      "name": "rootdir",
393
     *      "meta": {},
394
     *      "size": 1,
395
     *      "mime": "inode/directory",
396
     *      "created": "2007-08-31T16:47+00:00",
397
     *      "changed": "2007-08-31T16:47+00:00",
398
     *      "destroy": "2020-08-31T16:47+00:00",
399
     *      "share": false,
400
     *      "directory": true
401
     *  },
402
     *  {
403
     *      "id": "544627ed3c58891f058b46cc",
404
     *      "name": "parentdir",
405
     *      "meta": {},
406
     *      "size": 3,
407
     *      "mime": "inode/directory",
408
     *      "created": "2007-08-31T16:47+00:00",
409
     *      "changed": "2007-08-31T16:47+00:00",
410
     *      "share": false,
411
     *      "directory": true
412
     *  }
413
     * ]
414
     *
415
     * @param string $id
416
     * @param string $p
417
     */
418
    public function getParents(?string $id = null, ?string $p = null, array $attributes = [], bool $self = false): Response
419
    {
420
        $result = [];
421
        $request = $this->_getNode($id, $p);
422
        $parents = $request->getParents();
423
424
        if (true === $self && $request instanceof Collection) {
425
            $result[] = $this->node_decorator->decorate($request, $attributes);
426
        }
427
428
        foreach ($parents as $node) {
429
            $result[] = $this->node_decorator->decorate($node, $attributes);
430
        }
431
432
        return (new Response())->setCode(200)->setBody($result);
433
    }
434
435
    /**
436
     * @api {patch} /api/v2/nodes/:id Change attributes
437
     * @apiVersion 2.0.0
438
     * @apiName patch
439
     * @apiGroup Node
440
     * @apiPermission none
441
     * @apiDescription Change attributes
442
     * @apiUse _getNodes
443
     * @apiUse _multiError
444
     *
445
     * @apiParam (GET Parameter) {string} [name] Rename node, the characters (\ < > : " / * ? |) (without the "()") are not allowed to use within a node name.
446
     * @apiParam (GET Parameter) {boolean} [readonly] Mark node as readonly
447
     * @apiParam (GET Parameter) {object} [filter] Custom collection filter (Collection only)
448
     * @apiParam (GET Parameter) {string} [meta.description] UTF-8 Text Description - Can contain anything as long as it is a string
449
     * @apiParam (GET Parameter) {string} [meta.color] Color Tag - Can contain anything as long as it is a string
450
     * @apiParam (GET Parameter) {string} [meta.author] Author - Can contain anything as long as it is a string
451
     * @apiParam (GET Parameter) {string} [meta.mail] Mail contact address - Can contain anything as long as it is a string
452
     * @apiParam (GET Parameter) {string} [meta.license] License - Can contain anything as long as it is a string
453
     * @apiParam (GET Parameter) {string} [meta.copyright] Copyright string - Can contain anything as long as it is a string
454
     * @apiParam (GET Parameter) {string[]} [meta.tags] Tags - Must be an array full of strings
455
     *
456
     * @apiExample (cURL) example:
457
     * curl -XPATCH "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686?name=example"
458
     *
459
     * @apiSuccessExample {json} Success-Response:
460
     * HTTP/1.1 200
461
     *
462
     * @param array|string $id
463
     * @param array|string $p
464
     */
465
    public function patch(?string $name = null, ?array $meta = null, ?bool $readonly = null, ?array $filter = null, ?array $acl = null, ?string $id = null, ?string $p = null): Response
466
    {
467
        $attributes = compact('name', 'meta', 'readonly', 'filter', 'acl');
468
        $attributes = array_filter($attributes, function ($attribute) {return !is_null($attribute); });
469
470
        return $this->bulk($id, $p, function ($node) use ($attributes) {
0 ignored issues
show
It seems like $id defined by parameter $id on line 465 can also be of type null; however, Balloon\App\Api\Controller::bulk() does only seem to accept array|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $p defined by parameter $p on line 465 can also be of type null; however, Balloon\App\Api\Controller::bulk() does only seem to accept array|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
471
            foreach ($attributes as $attribute => $value) {
472
                switch ($attribute) {
473
                    case 'name':
474
                        $node->setName($value);
475
476
                    break;
477
                    case 'meta':
478
                        $node->setMetaAttributes($value);
479
480
                    break;
481
                    case 'readonly':
482
                        $node->setReadonly($value);
483
484
                    break;
485
                    case 'filter':
486
                        if ($node instanceof Collection) {
487
                            $node->setFilter($value);
488
                        }
489
490
                    break;
491
                    case 'acl':
492
                        $node->setAcl($value);
493
494
                    break;
495
                }
496
            }
497
498
            return [
499
                'code' => 200,
500
                'data' => $this->node_decorator->decorate($node),
501
            ];
502
        });
503
    }
504
505
    /**
506
     * @api {post} /api/v2/nodes/:id/clone Clone node
507
     * @apiVersion 2.0.0
508
     * @apiName postClone
509
     * @apiGroup Node
510
     * @apiPermission none
511
     * @apiDescription Clone a node
512
     * @apiUse _getNode
513
     * @apiUse _conflictNode
514
     * @apiUse _multiError
515
     * @apiUse _writeAction
516
     *
517
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
518
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
519
     *
520
     * @apiExample (cURL) example:
521
     * curl -XPOST "https://SERVER/api/v2/nodes/clone?id=544627ed3c58891f058b4686&dest=544627ed3c58891f058b4676"
522
     * curl -XPOST "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/clone?dest=544627ed3c58891f058b4676&conflict=2"
523
     * curl -XPOST "https://SERVER/api/v2/nodes/clone?p=/absolute/path/to/my/node&conflict=0&destp=/new/parent"
524
     *
525
     * @apiSuccessExample {json} Success-Response:
526
     * HTTP/1.1 201 Created
527
     *
528
     * @apiSuccessExample {json} Success-Response:
529
     * HTTP/1.1 200 OK
530
     *
531
     * @param array|string $id
532
     * @param array|string $id
533
     * @param array|string $p
534
     * @param string       $destid
535
     * @param string       $destp
536
     */
537
    public function postClone(
538
        $id = null,
539
        $p = null,
540
        ?string $destid = null,
541
        ?string $destp = null,
542
        int $conflict = 0
543
    ): Response {
544
        try {
545
            $parent = $this->_getNode($destid, $destp, Collection::class, false, true);
546
        } catch (Exception\NotFound $e) {
547
            throw new Exception\NotFound(
548
                'destination collection was not found or is not a collection',
549
                Exception\NotFound::DESTINATION_NOT_FOUND
550
            );
551
        }
552
553
        return $this->bulk($id, $p, function ($node) use ($parent, $conflict) {
0 ignored issues
show
It seems like $id defined by parameter $id on line 538 can also be of type null; however, Balloon\App\Api\Controller::bulk() does only seem to accept array|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $p defined by parameter $p on line 539 can also be of type null; however, Balloon\App\Api\Controller::bulk() does only seem to accept array|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
554
            $result = $node->copyTo($parent, $conflict);
555
556
            return [
557
                'code' => $parent == $result ? 200 : 201,
558
                'data' => $this->node_decorator->decorate($result),
559
            ];
560
        });
561
    }
562
563
    /**
564
     * @api {post} /api/v2/nodes/:id/move Move node
565
     * @apiVersion 2.0.0
566
     * @apiName postMove
567
     * @apiGroup Node
568
     * @apiPermission none
569
     * @apiDescription Move node
570
     * @apiUse _getNodes
571
     * @apiUse _conflictNode
572
     * @apiUse _multiError
573
     * @apiUse _writeAction
574
     *
575
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
576
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
577
     *
578
     * @apiExample (cURL) example:
579
     * curl -XPOST "https://SERVER/api/v2/nodes/move?id=544627ed3c58891f058b4686?destid=544627ed3c58891f058b4655"
580
     * curl -XPOST "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/move?destid=544627ed3c58891f058b4655"
581
     * curl -XPOST "https://SERVER/api/v2/nodes/move?p=/absolute/path/to/my/node&destp=/new/parent&conflict=1
582
     *
583
     * @apiSuccessExample {json} Success-Response:
584
     * HTTP/1.1 204 No Content
585
     *
586
     * @apiSuccessExample {json} Success-Response (conflict=1):
587
     * HTTP/1.1 200 OK
588
     * {
589
     *      "status":200,
590
     *      "data": "renamed (xy23)"
591
     * }
592
     *
593
     * @param array|string $id
594
     * @param array|string $p
595
     * @param string       $destid
596
     * @param string       $destp
597
     */
598
    public function postMove(
599
        $id = null,
600
        $p = null,
601
        ?string $destid = null,
602
        ?string $destp = null,
603
        int $conflict = 0
604
    ): Response {
605
        try {
606
            $parent = $this->_getNode($destid, $destp, Collection::class, false, true);
607
        } catch (Exception\NotFound $e) {
608
            throw new Exception\NotFound(
609
                'destination collection was not found or is not a collection',
610
                Exception\NotFound::DESTINATION_NOT_FOUND
611
            );
612
        }
613
614
        return $this->bulk($id, $p, function ($node) use ($parent, $conflict) {
0 ignored issues
show
It seems like $id defined by parameter $id on line 599 can also be of type null; however, Balloon\App\Api\Controller::bulk() does only seem to accept array|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $p defined by parameter $p on line 600 can also be of type null; however, Balloon\App\Api\Controller::bulk() does only seem to accept array|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
615
            $result = $node->setParent($parent, $conflict);
0 ignored issues
show
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
616
617
            return [
618
                'code' => 200,
619
                'data' => $this->node_decorator->decorate($node),
620
            ];
621
        });
622
    }
623
624
    /**
625
     * @api {delete} /api/v2/nodes/:id Delete node
626
     * @apiVersion 2.0.0
627
     * @apiName delete
628
     * @apiGroup Node
629
     * @apiPermission none
630
     * @apiDescription Delete node
631
     * @apiUse _getNodes
632
     * @apiUse _multiError
633
     * @apiUse _writeAction
634
     *
635
     * @apiParam (GET Parameter) {boolean} [force=false] Force flag need to be set to delete a node from trash (node must have the deleted flag)
636
     * @apiParam (GET Parameter) {boolean} [ignore_flag=false] If both ignore_flag and force_flag were set, the node will be deleted completely
637
     * @apiParam (GET Parameter) {number} [at] Has to be a valid unix timestamp if so the node will destroy itself at this specified time instead immediatly
638
     *
639
     * @apiExample (cURL) example:
640
     * curl -XDELETE "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686"
641
     * curl -XDELETE "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686?force=1&ignore_flag=1"
642
     * curl -XDELETE "https://SERVER/api/v2/node?p=/absolute/path/to/my/node"
643
     *
644
     * @apiSuccessExample {json} Success-Response:
645
     * HTTP/1.1 204 No Content
646
     *
647
     * @param array|string $id
648
     * @param array|string $p
649
     * @param int          $at
650
     */
651
    public function delete(
652
        $id = null,
653
        $p = null,
654
        bool $force = false,
655
        bool $ignore_flag = false,
656
        ?string $at = null
657
    ): Response {
658
        $failures = [];
0 ignored issues
show
$failures is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
659
660
        if (null !== $at && '0' !== $at) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of '0' (string) and $at (integer) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
661
            $at = $this->_verifyAttributes(['destroy' => $at])['destroy'];
662
        }
663
664
        return $this->bulk($id, $p, function ($node) use ($force, $ignore_flag, $at) {
0 ignored issues
show
It seems like $id defined by parameter $id on line 652 can also be of type null; however, Balloon\App\Api\Controller::bulk() does only seem to accept array|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $p defined by parameter $p on line 653 can also be of type null; however, Balloon\App\Api\Controller::bulk() does only seem to accept array|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
665
            if (null === $at) {
666
                $node->delete($force && $node->isDeleted() || $force && $ignore_flag);
667
            } else {
668
                if ('0' === $at) {
669
                    $at = null;
0 ignored issues
show
Consider using a different name than the imported variable $at, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
670
                }
671
                $node->setDestroyable($at);
672
            }
673
674
            return [
675
                'code' => 204,
676
            ];
677
        });
678
    }
679
680
    /**
681
     * @api {get} /api/v2/nodes/trash Get trash
682
     * @apiName getTrash
683
     * @apiVersion 2.0.0
684
     * @apiGroup Node
685
     * @apiPermission none
686
     * @apiDescription A similar endpoint to /api/v2/nodes/query filer={'deleted': {$type: 9}] but instead returning all deleted
687
     * nodes (including children which are deleted as well) this enpoint only returns the first deleted node from every subtree)
688
     * @apiUse _nodeAttributes
689
     *
690
     * @apiExample (cURL) example:
691
     * curl -XGET https://SERVER/api/v2/nodes/trash?pretty
692
     *
693
     * @apiParam (GET Parameter) {string[]} [attributes] Filter node attributes
694
     *
695
     * @apiSuccess (200 OK) {object[]} - List of deleted nodes
696
     * @apiSuccessExample {json} Success-Response:
697
     * HTTP/1.1 200 OK
698
     * [
699
     *  {
700
     *  }
701
     * ]
702
     */
703
    public function getTrash(array $attributes = [], int $offset = 0, int $limit = 20): Response
704
    {
705
        $children = [];
706
        $nodes = $this->fs->findNodesByFilterUser(NodeInterface::DELETED_ONLY, ['deleted' => ['$type' => 9]], $offset, $limit);
707
708
        foreach ($nodes as $node) {
709
            try {
710
                $parent = $node->getParent();
711
                if (null !== $parent && $parent->isDeleted()) {
712
                    continue;
713
                }
714
            } catch (\Exception $e) {
715
                //skip exception
716
            }
717
718
            $children[] = $node;
719
        }
720
721
        if ($this instanceof ApiFile) {
722
            $query['directory'] = false;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$query was never initialized. Although not strictly required by PHP, it is generally a good practice to add $query = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
723
            $uri = '/api/v2/files';
724
        } elseif ($this instanceof ApiCollection) {
725
            $query['directory'] = true;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$query was never initialized. Although not strictly required by PHP, it is generally a good practice to add $query = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
726
            $uri = '/api/v2/collections';
727
        } else {
728
            $uri = '/api/v2/nodes';
729
        }
730
731
        $pager = new Pager($this->node_decorator, $children, $attributes, $offset, $limit, $uri, $nodes->getReturn());
732
        $result = $pager->paging();
733
734
        return (new Response())->setCode(200)->setBody($result);
735
    }
736
737
    /**
738
     * @api {get} /api/v2/nodes/delta Get delta
739
     * @apiVersion 2.0.0
740
     * @apiName getDelta
741
     * @apiGroup Node
742
     * @apiPermission none
743
     * @apiUse _getNode
744
     *
745
     * @apiDescription Use this method to request a delta feed with all changes on the server (or) a snapshot of the server state.
746
     * since the state of the submited cursor. If no cursor was submited the server will create one which can than be used to request any further deltas.
747
     * If has_more is TRUE you need to request /delta immediatly again to
748
     * receive the next bunch of deltas. If has_more is FALSE you should wait at least 120s seconds before any further requests to the
749
     * api endpoint. You can also specify additional node attributes with the $attributes paramter or request the delta feed only for a specific node (see Get Attributes for that).
750
     * If reset is TRUE you have to clean your local state because you will receive a snapshot of the server state, it is the same as calling the /delta endpoint
751
     * without a cursor. reset could be TRUE if there was an account maintenance or a simialar case.
752
     * You can request a different limit as well but be aware that the number of nodes could be slighty different from your requested limit.
753
     * If requested with parameter id or p the delta gets generated recursively from the node given.
754
     *
755
     * @apiParam (GET Parameter) {number} [limit=250] Limit the number of delta entries, if too low you have to call this endpoint more often since has_more would be true more often
756
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default not all attributes would be returned
757
     * @apiParam (GET Parameter) {string} [cursor=null] Set a cursor to rquest next nodes within delta processing
758
     *
759
     * @apiExample (cURL) example:
760
     * curl -XGET "https://SERVER/api/v2/nodes/delta?pretty"
761
     *
762
     * @apiSuccess (200 OK) {boolean} reset If true the local state needs to be reseted, is alway TRUE during
763
     * the first request to /delta without a cursor or in special cases like server or account maintenance
764
     * @apiSuccess (200 OK) {string} cursor The cursor needs to be stored and reused to request further deltas
765
     * @apiSuccess (200 OK) {boolean} has_more If has_more is TRUE /delta can be requested immediatly after the last request
766
     * to receive further delta. If it is FALSE we should wait at least 120 seconds before any further delta requests to the api endpoint
767
     * @apiSuccess (200 OK) {object[]} nodes Node list to process
768
     * @apiSuccessExample {json} Success-Response:
769
     * HTTP/1.1 200 OK
770
     * {
771
     *      "reset": false,
772
     *      "cursor": "aW5pdGlhbHwxMDB8NTc1YTlhMGIzYzU4ODkwNTE0OGI0NTZifDU3NWE5YTBiM2M1ODg5MDUxNDhiNDU2Yw==",
773
     *      "has_more": false,
774
     *       "nodes": [
775
     *          {
776
     *              "id": "581afa783c5889ad7c8b4572",
777
     *              "deleted": " 2008-08-31T16:47+00:00",
778
     *              "changed": "2007-08-31T16:47+00:00",
779
     *              "path": "\/AAA\/AL",
780
     *              "directory": true
781
     *          },
782
     *          {
783
     *              "id": "581afa783c5889ad7c8b3dcf",
784
     *              "created": "2007-08-31T16:47+00:00",
785
     *              "changed": "2007-09-28T12:33+00:00",
786
     *              "path": "\/AL",
787
     *              "directory": true
788
     *          }
789
     *      ]
790
     * }
791
     *
792
     * @param string $id
793
     * @param string $p
794
     * @param string $cursor
795
     */
796
    public function getDelta(
797
        DeltaAttributeDecorator $delta_decorator,
798
        ?string $id = null,
799
        ?string $p = null,
800
        ?string $cursor = null,
801
        int $limit = 250,
802
        array $attributes = []
803
    ): Response {
804
        if (null !== $id || null !== $p) {
805
            $node = $this->_getNode($id, $p);
806
        } else {
807
            $node = null;
808
        }
809
810
        $result = $this->fs->getDelta()->getDeltaFeed($cursor, $limit, $node);
811
        foreach ($result['nodes'] as &$node) {
812
            if ($node instanceof NodeInterface) {
813
                $node = $this->node_decorator->decorate($node, $attributes);
814
            } else {
815
                $node = $delta_decorator->decorate($node, $attributes);
816
            }
817
        }
818
819
        return (new Response())->setCode(200)->setBody($result);
820
    }
821
822
    /**
823
     * @api {get} /api/v2/nodes/:id/event-log Event log
824
     * @apiVersion 2.0.0
825
     * @apiName getEventLog
826
     * @apiGroup Node
827
     * @apiPermission none
828
     * @apiUse _getNode
829
     * @apiDescription Get detailed event log
830
     * Request all modifications which are made by the user himself or share members.
831
     * Possible operations are the follwing:
832
     * - deleteCollectionReference
833
     * - deleteCollectionShare
834
     * - deleteCollection
835
     * - addCollection
836
     * - addFile
837
     * - addCollectionShare
838
     * - addCollectionReference
839
     * - undeleteFile
840
     * - undeleteCollectionReference
841
     * - undeleteCollectionShare
842
     * - restoreFile
843
     * - renameFile
844
     * - renameCollection
845
     * - renameCollectionShare
846
     * - renameCollectionRFeference
847
     * - copyFile
848
     * - copyCollection
849
     * - copyCollectionShare
850
     * - copyCollectionRFeference
851
     * - moveFile
852
     * - moveCollection
853
     * - moveCollectionReference
854
     * - moveCollectionShare
855
     *
856
     * @apiExample (cURL) example:
857
     * curl -XGET "https://SERVER/api/v2/nodes/event-log?pretty"
858
     * curl -XGET "https://SERVER/api/v2/nodes/event-log?id=544627ed3c58891f058b4686&pretty"
859
     * curl -XGET "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/event-log?pretty&limit=10"
860
     * curl -XGET "https://SERVER/api/v2/nodes/event-log?p=/absolute/path/to/my/node&pretty"
861
     *
862
     * @apiParam (GET Parameter) {number} [limit=100] Sets limit of events to be returned
863
     * @apiParam (GET Parameter) {number} [skip=0] How many events are skiped (useful for paging)
864
     *
865
     * @apiSuccess (200 OK) {object[]} - List of events
866
     * @apiSuccess (200 OK) {number} -.event Event ID
867
     * @apiSuccess (200 OK) {object} -.timestamp ISO8601 timestamp when the event occured
868
     * @apiSuccess (200 OK) {string} -.operation event operation (like addCollection, deleteFile, ...)
869
     * @apiSuccess (200 OK) {object} -.parent Parent node object at the time of the event
870
     * @apiSuccess (200 OK) {object} -.previous Previous state of actual data which has been modified during an event, can contain either version, name or parent
871
     * @apiSuccess (200 OK) {number} -.previous.version Version at the time before the event
872
     * @apiSuccess (200 OK) {string} -.previous.name Name at the time before the event
873
     * @apiSuccess (200 OK) {object} -.previous.parent Parent node object at the time before the event
874
     * @apiSuccess (200 OK) {object} -.share shared collection object at the time of the event (If the node was part of a share)
875
     * @apiSuccess (200 OK) {string} -.name Name of the node at the time of the event
876
     * @apiSuccess (200 OK) {object} -.node Current node object
877
     * @apiSuccess (200 OK) {object} -.user User who executed an event
878
     *
879
     * @apiSuccessExample {json} Success-Response:
880
     * HTTP/1.1 200 OK
881
     * [
882
     *  {
883
     *      "id": "57628e523c5889026f8b4570",
884
     *      "timestamp": " 2018-01-02T13:22+00:00",
885
     *      "operation": "restoreFile",
886
     *      "name": "file.txt",
887
     *      "previous": {
888
     *          "version": 16
889
     *      },
890
     *      "node": {
891
     *          "id": "558c0b273c588963078b457a",
892
     *          "name": "3dddsceheckfile.txt",
893
     *          "deleted": false
894
     *      },
895
     *      "parent": null,
896
     *      "user": {
897
     *          "id": "54354cb63c58891f058b457f",
898
     *          "username": "example"
899
     *      }
900
     *  }
901
     * ]
902
     *
903
     * @param string $id
904
     * @param string $p
905
     */
906
    public function getEventLog(EventAttributeDecorator $event_decorator, ?string $id = null, ?string $p = null, ?array $attributes = [], int $offset = 0, int $limit = 20): Response
907
    {
908
        if (null !== $id || null !== $p) {
909
            $node = $this->_getNode($id, $p);
910
            $uri = '/api/v2/nodes/'.$node->getId().'/event-log';
911
        } else {
912
            $node = null;
913
            $uri = '/api/v2/nodes/event-log';
914
        }
915
916
        $result = $this->fs->getDelta()->getEventLog($limit, $offset, $node, $total);
917
        $pager = new Pager($event_decorator, $result, $attributes, $offset, $limit, $uri, $total);
918
919
        return (new Response())->setCode(200)->setBody($pager->paging());
920
    }
921
922
    /**
923
     * @api {get} /api/v2/nodes/last-cursor Get last Cursor
924
     * @apiVersion 2.0.0
925
     * @apiName geLastCursor
926
     * @apiGroup Node
927
     * @apiUse _getNode
928
     * @apiPermission none
929
     * @apiDescription Use this method to request the latest cursor if you only need to now
930
     * if there are changes on the server. This method will not return any other data than the
931
     * newest cursor. To request a feed with all deltas request /delta.
932
     *
933
     * @apiExample (cURL) example:
934
     * curl -XGET "https://SERVER/api/v2/nodes/last-cursor?pretty"
935
     *
936
     * @apiSuccess (200 OK) {string} cursor v2 cursor
937
     * @apiSuccessExample {json} Success-Response:
938
     * HTTP/1.1 200 OK
939
     * "aW5pdGlhbHwxMDB8NTc1YTlhMGIzYzU4ODkwNTE0OGI0NTZifDU3NWE5YTBiM2M1ODg5MDUxNDhiNDU2Yw=="
940
     *
941
     * @param string $id
942
     * @param string $p
943
     */
944
    public function getLastCursor(?string $id = null, ?string $p = null): Response
945
    {
946
        if (null !== $id || null !== $p) {
947
            $node = $this->_getNode($id, $p);
0 ignored issues
show
$node is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
948
        } else {
949
            $node = null;
0 ignored issues
show
$node is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
950
        }
951
952
        $result = $this->fs->getDelta()->getLastCursor();
953
954
        return (new Response())->setCode(200)->setBody($result);
955
    }
956
957
    /**
958
     * Merge multiple nodes into one zip archive.
959
     *
960
     * @param string $id
961
     * @param string $path
962
     */
963
    protected function combine($id = null, $path = null, string $name = 'selected')
964
    {
965
        $archive = new ZipStream($name.'.zip');
966
967
        foreach ($this->_getNodes($id, $path) as $node) {
968
            try {
969
                $node->zip($archive);
970
                //json_decode($stored, true),
971
            } catch (\Exception $e) {
972
                $this->logger->debug('failed zip node in multi node request ['.$node->getId().']', [
973
                   'category' => get_class($this),
974
                   'exception' => $e,
975
               ]);
976
            }
977
        }
978
979
        $archive->finish();
980
    }
981
982
    /**
983
     * Check custom node attributes which have to be written.
984
     */
985
    protected function _verifyAttributes(array $attributes): array
986
    {
987
        $valid_attributes = [
988
            'changed',
989
            'destroy',
990
            'created',
991
            'meta',
992
            'readonly',
993
            'acl',
994
        ];
995
996
        if ($this instanceof ApiCollection) {
997
            $valid_attributes += ['filter', 'mount'];
998
        }
999
1000
        $check = array_merge(array_flip($valid_attributes), $attributes);
1001
1002
        if ($this instanceof ApiCollection && count($check) > 8) {
1003
            throw new Exception\InvalidArgument('Only changed, created, destroy timestamp, acl, filter, mount, readonly and/or meta attributes may be overwritten');
1004
        }
1005
        if ($this instanceof ApiFile && count($check) > 6) {
1006
            throw new Exception\InvalidArgument('Only changed, created, destroy timestamp, acl, readonly and/or meta attributes may be overwritten');
1007
        }
1008
1009
        foreach ($attributes as $attribute => $value) {
1010
            switch ($attribute) {
1011
                case 'filter':
1012
                    if (!is_array($value)) {
1013
                        throw new Exception\InvalidArgument($attribute.' must be an array');
1014
                    }
1015
1016
                    $attributes['filter'] = json_encode($value);
1017
1018
                break;
1019
                case 'destroy':
1020
                    if (!Helper::isValidTimestamp($value)) {
1021
                        throw new Exception\InvalidArgument($attribute.' timestamp must be valid unix timestamp');
1022
                    }
1023
                    $attributes[$attribute] = new UTCDateTime($value.'000');
1024
1025
                break;
1026
                case 'changed':
1027
                case 'created':
1028
                    if (!Helper::isValidTimestamp($value)) {
1029
                        throw new Exception\InvalidArgument($attribute.' timestamp must be valid unix timestamp');
1030
                    }
1031
                    if ((int) $value > time()) {
1032
                        throw new Exception\InvalidArgument($attribute.' timestamp can not be set greater than the server time');
1033
                    }
1034
                    $attributes[$attribute] = new UTCDateTime($value.'000');
1035
1036
                break;
1037
                case 'readonly':
1038
                    $attributes['readonly'] = (bool) $attributes['readonly'];
1039
1040
                break;
1041
            }
1042
        }
1043
1044
        return $attributes;
1045
    }
1046
}
1047