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

src/app/Balloon.App.Api/v1/Node.php (29 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\v1;
13
14
use Balloon\App\Api\Controller;
15
use Balloon\App\Api\v1\AttributeDecorator\DeltaDecorator;
16
use Balloon\App\Api\v1\AttributeDecorator\EventDecorator;
17
use Balloon\App\Api\v1\AttributeDecorator\NodeDecorator;
18
use Balloon\App\Api\v1\Collection as ApiCollection;
19
use Balloon\App\Api\v1\File as ApiFile;
20
use Balloon\Filesystem;
21
use Balloon\Filesystem\Exception;
22
use Balloon\Filesystem\Node\Collection;
0 ignored issues
show
This use statement conflicts with another class in this namespace, Balloon\App\Api\v1\Collection.

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\File;
0 ignored issues
show
This use statement conflicts with another class in this namespace, Balloon\App\Api\v1\File.

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...
24
use Balloon\Filesystem\Node\NodeInterface;
25
use Balloon\Helper;
26
use Balloon\Server;
27
use Balloon\Server\User;
0 ignored issues
show
This use statement conflicts with another class in this namespace, Balloon\App\Api\v1\User.

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...
28
use Closure;
29
use Generator;
30
use Micro\Http\Response;
31
use MongoDB\BSON\UTCDateTime;
32
use MongoDB\Database;
33
use Psr\Log\LoggerInterface;
34
use ZipStream\ZipStream;
35
36
class Node extends Controller
37
{
38
    /**
39
     * Filesystem.
40
     *
41
     * @var Filesystem
42
     */
43
    protected $fs;
44
45
    /**
46
     * LoggerInterface.
47
     *
48
     * @var LoggerInterface
49
     */
50
    protected $logger;
51
52
    /**
53
     * Server.
54
     *
55
     * @var Server
56
     */
57
    protected $server;
58
59
    /**
60
     * User.
61
     *
62
     * @var User
63
     */
64
    protected $user;
65
66
    /**
67
     * Decorator.
68
     *
69
     * @var NodeDecorator
70
     */
71
    protected $node_decorator;
72
73
    /**
74
     * Database.
75
     *
76
     * @var Database
77
     */
78
    protected $db;
79
80
    /**
81
     * Initialize.
82
     */
83
    public function __construct(Server $server, NodeDecorator $decorator, LoggerInterface $logger, Database $db)
84
    {
85
        $this->fs = $server->getFilesystem();
86
        $this->user = $server->getIdentity();
87
        $this->server = $server;
88
        $this->node_decorator = $decorator;
89
        $this->logger = $logger;
90
        $this->db = $db;
91
    }
92
93
    /**
94
     * @api {head} /api/v1/node?id=:id Node exists?
95
     * @apiVersion 1.0.0
96
     * @apiName head
97
     * @apiGroup Node
98
     * @apiPermission none
99
     * @apiDescription Check if a node exists. Per default deleted nodes are ignore which means it will
100
     *  return a 404 if a deleted node is requested. You can change this behaviour via the deleted parameter.
101
     * @apiUse _getNode
102
     *
103
     * @apiExample (cURL) example:
104
     * curl -XHEAD "https://SERVER/api/v1/node?id=544627ed3c58891f058b4686"
105
     * curl -XHEAD "https://SERVER/api/v1/node/544627ed3c58891f058b4686"
106
     * curl -XHEAD "https://SERVER/api/v1/node?p=/absolute/path/to/my/node"
107
     *
108
     * @apiParam (GET Parameter) {number} [deleted=0] Wherever include deleted node or not, possible values:</br>
109
     * - 0 Exclude deleted</br>
110
     * - 1 Only deleted</br>
111
     * - 2 Include deleted</br>
112
     *
113
     * @apiSuccessExample {json} Success-Response (Node does exist):
114
     * HTTP/1.1 200 OK
115
     *
116
     * @apiSuccessExample {json} Success-Response (Node does not exist):
117
     * HTTP/1.1 404 Not Found
118
     *
119
     * @param string $id
120
     * @param string $p
121
     */
122
    public function head(?string $id = null, ?string $p = null, int $deleted = 0): Response
123
    {
124
        try {
125
            $result = $this->_getNode($id, $p, null, false, false, $deleted);
126
127
            $response = (new Response())
128
                ->setHeader('Content-Length', (string) $result->getSize())
129
                ->setHeader('Content-Type', $result->getContentType())
130
                ->setCode(200);
131
132
            return $response;
133
        } catch (\Exception $e) {
134
            return (new Response())->setCode(404);
135
        }
136
    }
137
138
    /**
139
     * @api {post} /api/v1/node/undelete?id=:id Undelete node
140
     * @apiVersion 1.0.0
141
     * @apiName postUndelete
142
     * @apiGroup Node
143
     * @apiPermission none
144
     * @apiDescription Undelete (Apiore from trash) a single node or multiple ones.
145
     * @apiUse _getNodes
146
     * @apiUse _conflictNode
147
     * @apiUse _multiError
148
     * @apiUse _writeAction
149
     *
150
     * @apiExample (cURL) example:
151
     * curl -XPOST "https://SERVER/api/v1/node/undelete?id[]=544627ed3c58891f058b4686&id[]=544627ed3c58891f058b46865&pretty"
152
     * curl -XPOST "https://SERVER/api/v1/node/undelete?id=544627ed3c58891f058b4686?pretty"
153
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4686/undelete?conflict=2"
154
     * curl -XPOST "https://SERVER/api/v1/node/undelete?p=/absolute/path/to/my/node&conflict=0&move=1&destid=544627ed3c58891f058b46889"
155
     *
156
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
157
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
158
     *
159
     * @apiSuccessExample {json} Success-Response:
160
     * HTTP/1.1 204 No Content
161
     *
162
     * @apiSuccessExample {json} Success-Response (conflict=1):
163
     * HTTP/1.1 200 OK
164
     * {
165
     *      "status":200,
166
     *      "data": "renamed (xy23)"
167
     *      }
168
     * }
169
     *
170
     * @param array|string $id
171
     * @param array|string $p
172
     * @param string       $destid
173
     * @param string       $destp
174
     */
175
    public function postUndelete(
176
        $id = null,
177
        $p = null,
178
        bool $move = false,
179
        ?string $destid = null,
180
        ?string $destp = null,
181
        int $conflict = 0
182
    ): Response {
183
        $parent = null;
184
        if (true === $move) {
185
            try {
186
                $parent = $this->_getNode($destid, $destp, 'Collection', false, true);
187
            } catch (Exception\NotFound $e) {
188
                throw new Exception\NotFound(
189
                    'destination collection was not found or is not a collection',
190
                    Exception\NotFound::DESTINATION_NOT_FOUND
191
                );
192
            }
193
        }
194
195
        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 176 can also be of type null; however, Balloon\App\Api\v1\Node::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 177 can also be of type null; however, Balloon\App\Api\v1\Node::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...
196
            if (true === $move) {
197
                $node = $node->setParent($parent, $conflict);
198
            }
199
200
            if ($node->isDeleted()) {
201
                $node->undelete($conflict);
202
            }
203
204
            if (true === $move && NodeInterface::CONFLICT_RENAME === $conflict) {
205
                return [
206
                    'status' => 200,
207
                    'data' => [
208
                    ],
209
                ];
210
            }
211
212
            return ['status' => 204];
213
        });
214
    }
215
216
    /**
217
     * @api {get} /api/v1/node?id=:id Download stream
218
     * @apiVersion 1.0.0
219
     * @apiName get
220
     * @apiGroup Node
221
     * @apiPermission none
222
     * @apiDescription Download node contents. Collections (Folder) are converted into
223
     * a zip file in realtime.
224
     * @apiUse _getNode
225
     *
226
     * @apiParam (GET Parameter) {number} [offset=0] Get content from a specific offset in bytes
227
     * @apiParam (GET Parameter) {number} [length=0] Get content with until length in bytes reached
228
     * @apiParam (GET Parameter) {string} [encode] Can be set to base64 to encode content as base64.
229
     * @apiParam (GET Parameter) {boolean} [download=false] Force download file (Content-Disposition: attachment HTTP header)
230
     *
231
     * @apiExample (cURL) example:
232
     * curl -XGET "https://SERVER/api/v1/node?id=544627ed3c58891f058b4686" > myfile.txt
233
     * curl -XGET "https://SERVER/api/v1/node/544627ed3c58891f058b4686" > myfile.txt
234
     * curl -XGET "https://SERVER/api/v1/node?p=/absolute/path/to/my/collection" > folder.zip
235
     *
236
     * @apiSuccessExample {string} Success-Response (encode=base64):
237
     * HTTP/1.1 200 OK
238
     *
239
     * @apiSuccessExample {binary} Success-Response:
240
     * HTTP/1.1 200 OK
241
     *
242
     * @apiErrorExample {json} Error-Response (Invalid offset):
243
     * HTTP/1.1 400 Bad Request
244
     * {
245
     *      "status": 400,
246
     *      "data": {
247
     *          "error": "Balloon\\Exception\\Conflict",
248
     *          "message": "invalid offset requested",
249
     *          "code": 277
250
     *      }
251
     * }
252
     *
253
     * @param array|string $id
254
     * @param array|string $p
255
     * @param string       $encode
256
     */
257
    public function get(
258
        $id = null,
259
        $p = null,
260
        int $offset = 0,
261
        int $length = 0,
262
        ?string $encode = null,
263
        bool $download = false,
264
        string $name = 'selected'
265
    ): ?Response {
266
        if (is_array($id) || is_array($p)) {
267
            return $this->combine($id, $p, $name);
0 ignored issues
show
It seems like $id defined by parameter $id on line 258 can also be of type array; however, Balloon\App\Api\v1\Node::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 259 can also be of type array; however, Balloon\App\Api\v1\Node::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...
268
        }
269
270
        $node = $this->_getNode($id, $p);
271
        if ($node instanceof Collection) {
272
            return (new Response())->setBody(function () use ($node) {
273
                $node->getZip();
274
            });
275
        }
276
277
        $response = new Response();
278
279
        if (true === $download) {
280
            $response->setHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\''.rawurlencode($name));
281
            $response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
282
            $response->setHeader('Content-Type', 'application/octet-stream');
283
            $response->setHeader('Content-Length', (string) $node->getSize());
284
            $response->setHeader('Content-Transfer-Encoding', 'binary');
285
        } else {
286
            $response->setHeader('Content-Disposition', 'inline; filename*=UTF-8\'\''.rawurlencode($name));
287
        }
288
289
        return $response->setOutputFormat(null)
290
          ->setBody(function () use ($node, $encode, $offset, $length) {
291
              $mime = $node->getContentType();
292
              $stream = $node->get();
293
              $name = $node->getName();
0 ignored issues
show
$name 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...
294
295
              if (null === $stream) {
296
                  return;
297
              }
298
299
              if (0 !== $offset) {
300
                  if (fseek($stream, $offset) === -1) {
301
                      throw new Exception\Conflict(
302
                        'invalid offset requested',
303
                        Exception\Conflict::INVALID_OFFSET
304
                    );
305
                  }
306
              }
307
308
              $read = 0;
309
              header('Content-Type: '.$mime.'');
310
              if ('base64' === $encode) {
311
                  header('Content-Encoding: base64');
312
                  while (!feof($stream)) {
313
                      if (0 !== $length && $read + 8192 > $length) {
314
                          echo base64_encode(fread($stream, $length - $read));
315
                          exit();
316
                      }
317
318
                      echo base64_encode(fread($stream, 8192));
319
                      $read += 8192;
320
                  }
321
              } else {
322
                  while (!feof($stream)) {
323
                      if (0 !== $length && $read + 8192 > $length) {
324
                          echo fread($stream, $length - $read);
325
                          exit();
326
                      }
327
328
                      echo fread($stream, 8192);
329
                      $read += 8192;
330
                  }
331
              }
332
          });
333
    }
334
335
    /**
336
     * @api {post} /api/v1/node/readonly?id=:id Mark node as readonly
337
     * @apiVersion 1.0.0
338
     * @apiName postReadonly
339
     * @apiGroup Node
340
     * @apiPermission none
341
     * @apiDescription Mark (or unmark) node as readonly
342
     * @apiUse _getNodes
343
     * @apiUse _multiError
344
     * @apiUse _writeAction
345
     *
346
     * @apiExample (cURL) example:
347
     * curl -XPOST "https://SERVER/api/v1/node/readonly?id[]=544627ed3c58891f058b4686&id[]=544627ed3c58891f058b46865&readonly=1"
348
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4686/readonly?readonly=0"
349
     * curl -XPOST "https://SERVER/api/v1/node/readonly?p=/absolute/path/to/my/node"
350
     *
351
     * @apiParam (GET Parameter) {bool} [readonly=true] Set readonly to false to make node writeable again
352
     *
353
     * @apiSuccessExample {json} Success-Response:
354
     * HTTP/1.1 204 No Content
355
     *
356
     * @param array|string $id
357
     * @param array|string $p
358
     */
359
    public function postReadonly($id = null, $p = null, bool $readonly = true): Response
360
    {
361
        return $this->bulk($id, $p, function ($node) use ($readonly) {
0 ignored issues
show
It seems like $id defined by parameter $id on line 359 can also be of type null; however, Balloon\App\Api\v1\Node::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 359 can also be of type null; however, Balloon\App\Api\v1\Node::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...
362
            $node->setReadonly($readonly);
363
364
            return ['status' => 204];
365
        });
366
    }
367
368
    /**
369
     * @apiDefine _nodeAttributes
370
     *
371
     * @apiSuccess (200 OK) {number} status Status Code
372
     * @apiSuccess (200 OK) {object} data Attributes
373
     * @apiSuccess (200 OK) {string} data.id Unique node id
374
     * @apiSuccess (200 OK) {string} data.name Name
375
     * @apiSuccess (200 OK) {string} data.hash MD5 content checksum (file node only)
376
     * @apiSuccess (200 OK) {object} data.meta Extended meta attributes
377
     * @apiSuccess (200 OK) {string} data.meta.description UTF-8 Text Description
378
     * @apiSuccess (200 OK) {string} data.meta.color Color Tag (HEX) (Like: #000000)
379
     * @apiSuccess (200 OK) {string} data.meta.author Author
380
     * @apiSuccess (200 OK) {string} data.meta.mail Mail contact address
381
     * @apiSuccess (200 OK) {string} data.meta.license License
382
     * @apiSuccess (200 OK) {string} data.meta.copyright Copyright string
383
     * @apiSuccess (200 OK) {string[]} data.meta.tags Search Tags
384
     * @apiSuccess (200 OK) {number} data.size Size in bytes (Only file node), number of children if collection
385
     * @apiSuccess (200 OK) {string} data.mime Mime type
386
     * @apiSuccess (200 OK) {boolean} data.sharelink Is node shared?
387
     * @apiSuccess (200 OK) {number} data.version File version (file node only)
388
     * @apiSuccess (200 OK) {mixed} data.deleted Is boolean false if not deleted, if deleted it contains a deleted timestamp
389
     * @apiSuccess (200 OK) {number} data.deleted.sec Unix timestamp
390
     * @apiSuccess (200 OK) {number} data.deleted.usec Additional Microsecconds to Unix timestamp
391
     * @apiSuccess (200 OK) {object} data.changed Changed timestamp
392
     * @apiSuccess (200 OK) {number} data.changed.sec Unix timestamp
393
     * @apiSuccess (200 OK) {number} data.changed.usec Additional Microsecconds to Unix timestamp
394
     * @apiSuccess (200 OK) {object} data.created Created timestamp
395
     * @apiSuccess (200 OK) {number} data.created.sec Unix timestamp
396
     * @apiSuccess (200 OK) {number} data.created.usec Additional Microsecconds to Unix timestamp
397
     * @apiSuccess (200 OK) {boolean} data.share Node is shared
398
     * @apiSuccess (200 OK) {boolean} data.directory Is node a collection or a file?
399
     *
400
     * @apiSuccess (200 OK - additional attributes) {string} data.thumbnail Id of preview (file node only)
401
     * @apiSuccess (200 OK - additional attributes) {string} data.access Access if node is shared, one of r/rw/w
402
     * @apiSuccess (200 OK - additional attributes) {string} data.shareowner Username of the share owner
403
     * @apiSuccess (200 OK - additional attributes) {string} data.parent ID of the parent node
404
     * @apiSuccess (200 OK - additional attributes) {string} data.path Absolute node path
405
     * @apiSuccess (200 OK - additional attributes) {boolean} data.filtered Node is filtered (usually only a collection)
406
     * @apiSuccess (200 OK - additional attributes) {boolean} data.readonly Node is readonly
407
     *
408
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default not all attributes would be returned
409
     *
410
     * @param null|mixed $id
411
     * @param null|mixed $p
412
     */
413
414
    /**
415
     * @api {get} /api/v1/node/attributes?id=:id Get attributes
416
     * @apiVersion 1.0.0
417
     * @apiName getAttributes
418
     * @apiGroup Node
419
     * @apiPermission none
420
     * @apiDescription Get attributes from one or multiple nodes
421
     * @apiUse _getNode
422
     * @apiUse _nodeAttributes
423
     *
424
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default only a bunch of attributes would be returned, if you
425
     * need other attributes you have to request them (for example "path")
426
     *
427
     * @apiExample (cURL) example:
428
     * curl -XGET "https://SERVER/api/v1/node/attributes?id=544627ed3c58891f058b4686&pretty"
429
     * curl -XGET "https://SERVER/api/v1/node/attributes?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted&pretty"
430
     * curl -XGET "https://SERVER/api/v1/node/544627ed3c58891f058b4686/attributes?pretty"
431
     * curl -XGET "https://SERVER/api/v1/node/attributes?p=/absolute/path/to/my/node&pretty"
432
     *
433
     * @apiSuccessExample {json} Success-Response:
434
     * HTTP/1.1 200 OK
435
     * {
436
     *     "status": 200,
437
     *     "data": {
438
     *          "id": "544627ed3c58891f058b4686",
439
     *          "name": "api.php",
440
     *          "hash": "a77f23ed800fd7a600a8c2cfe8cc370b",
441
     *          "meta": {
442
     *              "license": "GPLv3"
443
     *          },
444
     *          "size": 178,
445
     *          "mime": "text\/plain",
446
     *          "sharelink": true,
447
     *          "version": 1,
448
     *          "deleted": false,
449
     *          "changed": {
450
     *              "sec": 1413883885,
451
     *              "usec": 869000
452
     *          },
453
     *          "created": {
454
     *              "sec": 1413883885,
455
     *              "usec": 869000
456
     *          },
457
     *          "share": false,
458
     *          "directory": false
459
     *      }
460
     * }
461
     *
462
     * @param array|string $id
463
     * @param array|string $p
464
     */
465
    public function getAttributes($id = null, $p = null, array $attributes = []): Response
466
    {
467
        if (is_array($id) || is_array($p)) {
468
            $nodes = [];
469
            foreach ($this->_getNodes($id, $p) as $node) {
0 ignored issues
show
It seems like $id defined by parameter $id on line 465 can also be of type array; however, Balloon\App\Api\v1\Node::_getNodes() 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 465 can also be of type array; however, Balloon\App\Api\v1\Node::_getNodes() 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...
470
                $nodes[] = $this->node_decorator->decorate($node, $attributes);
471
            }
472
473
            return (new Response())->setCode(200)->setBody([
474
                'status' => 200,
475
                'data' => $nodes,
476
            ]);
477
        }
478
479
        $result = $this->node_decorator->decorate($this->_getNode($id, $p), $attributes);
480
481
        return (new Response())->setCode(200)->setBody([
482
            'status' => 200,
483
            'data' => $result,
484
        ]);
485
    }
486
487
    /**
488
     * @api {get} /api/v1/node/parents?id=:id Get parent nodes
489
     * @apiVersion 1.0.0
490
     * @apiName getParents
491
     * @apiGroup Node
492
     * @apiPermission none
493
     * @apiDescription Get system attributes of all parent nodes. The hirarchy of all parent nodes is ordered in a
494
     * single level array beginning with the collection on the highest level.
495
     * @apiUse _getNode
496
     * @apiUse _nodeAttributes
497
     * @apiSuccess (200 OK) {object[]} data Nodes
498
     *
499
     * @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)
500
     *
501
     * @apiExample (cURL) example:
502
     * curl -XGET "https://SERVER/api/v1/node/parents?id=544627ed3c58891f058b4686&pretty"
503
     * curl -XGET "https://SERVER/api/v1/node/parents?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted&pretty"
504
     * curl -XGET "https://SERVER/api/v1/node/544627ed3c58891f058b4686/parents?pretty&self=1"
505
     * curl -XGET "https://SERVER/api/v1/node/parents?p=/absolute/path/to/my/node&self=1"
506
     *
507
     * @apiSuccessExample {json} Success-Response:
508
     * HTTP/1.1 200 OK
509
     * {
510
     *     "status": 200,
511
     *     "data": [
512
     * {
513
     *              "id": "544627ed3c58891f058bbbaa",
514
     *              "name": "rootdir",
515
     *              "meta": {},
516
     *              "size": 1,
517
     *              "mime": "inode\/directory",
518
     *              "deleted": false,
519
     *              "changed": {
520
     *                  "sec": 1413883880,
521
     *                  "usec": 869001
522
     *              },
523
     *              },
524
     *              "created": {
525
     *                  "sec": 1413883880,
526
     *                  "usec": 869001
527
     *              },
528
     *              "share": false,
529
     *              "directory": true
530
     *          },
531
     *          {
532
     *              "id": "544627ed3c58891f058b46cc",
533
     *              "name": "parentdir",
534
     *              "meta": {},
535
     *              "size": 3,
536
     *              "mime": "inode\/directory",
537
     *              "deleted": false,
538
     *              "changed": {
539
     *                  "sec": 1413883885,
540
     *                  "usec": 869000
541
     *              },
542
     *              "created": {
543
     *                  "sec": 1413883885,
544
     *                  "usec": 869000
545
     *              },
546
     *              "share": false,
547
     *              "directory": true
548
     *          }
549
     *      ]
550
     * }
551
     *
552
     * @param string $id
553
     * @param string $p
554
     */
555
    public function getParents(?string $id = null, ?string $p = null, array $attributes = [], bool $self = false): Response
556
    {
557
        $request = $this->_getNode($id, $p);
558
        $parents = $request->getParents();
559
        $result = [];
560
561
        if (true === $self && $request instanceof Collection) {
562
            $result[] = $this->node_decorator->decorate($request, $attributes);
563
        }
564
565
        foreach ($parents as $node) {
566
            $result[] = $this->node_decorator->decorate($node, $attributes);
567
        }
568
569
        return (new Response())->setCode(200)->setBody([
570
            'status' => 200,
571
            'data' => $result,
572
        ]);
573
    }
574
575
    /**
576
     * @api {post} /api/v1/node/meta-attributes?id=:id Change meta attributes
577
     * @apiVersion 1.0.0
578
     * @apiName postMetaAttributes
579
     * @apiGroup Node
580
     * @apiPermission none
581
     * @apiDescription Change meta attributes of a node
582
     * @apiUse _getNodes
583
     * @apiUse _multiError
584
     *
585
     * @apiParam (GET Parameter) {string} [attributes.description] UTF-8 Text Description - Can contain anything as long as it is a string
586
     * @apiParam (GET Parameter) {string} [attributes.color] Color Tag - Can contain anything as long as it is a string
587
     * @apiParam (GET Parameter) {string} [attributes.author] Author - Can contain anything as long as it is a string
588
     * @apiParam (GET Parameter) {string} [attributes.mail] Mail contact address - Can contain anything as long as it is a string
589
     * @apiParam (GET Parameter) {string} [attributes.license] License - Can contain anything as long as it is a string
590
     * @apiParam (GET Parameter) {string} [attributes.copyright] Copyright string - Can contain anything as long as it is a string
591
     * @apiParam (GET Parameter) {string[]} [attributes.tags] Tags - Must be an array full of strings
592
     *
593
     * @apiExample (cURL) example:
594
     * curl -XPOST "https://SERVER/api/v1/node/meta-attributes?id=544627ed3c58891f058b4686&author=peter.meier"
595
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4686/meta-attributes?author=example"
596
     * curl -XPOST "https://SERVER/api/v1/node/meta-attributes?p=/absolute/path/to/my/node?license=GPL-3.0"
597
     *
598
     * @apiSuccessExample {json} Success-Response:
599
     * HTTP/1.1 204 No Content
600
     *
601
     * @param array|string $id
602
     * @param array|string $p
603
     */
604
    public function postMetaAttributes(?string $id = null, ?string $p = null): Response
605
    {
606
        return $this->bulk($id, $p, function ($node) {
0 ignored issues
show
It seems like $id defined by parameter $id on line 604 can also be of type null; however, Balloon\App\Api\v1\Node::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 604 can also be of type null; however, Balloon\App\Api\v1\Node::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...
607
            $node->setMetaAttributes($_POST);
608
609
            return ['status' => 204];
610
        });
611
    }
612
613
    /**
614
     * @api {post} /api/v1/node/name?id=:id Rename node
615
     * @apiVersion 1.0.0
616
     * @apiName postName
617
     * @apiGroup Node
618
     * @apiPermission none
619
     * @apiDescription Rename a node. The characters (\ < > : " / * ? |) (without the "()") are not allowed to use within a node name.
620
     * @apiUse _getNode
621
     * @apiUse _writeAction
622
     *
623
     * @apiParam (GET Parameter) {string} [name] The new name of the node
624
     * @apiError (Error 400) Exception name contains invalid characters
625
     *
626
     * @apiExample (cURL) example:
627
     * curl -XPOST "https://SERVER/api/v1/node/name?id=544627ed3c58891f058b4686&name=newname.txt"
628
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4677/name?name=newdir"
629
     * curl -XPOST "https://SERVER/api/v1/node/name?p=/absolute/path/to/my/node&name=newname.txt"
630
     *
631
     * @apiSuccessExample {json} Success-Response:
632
     * HTTP/1.1 204 No Content
633
     *
634
     * @param string $id
635
     * @param string $p
636
     */
637
    public function postName(string $name, ?string $id = null, ?string $p = null): Response
638
    {
639
        $this->_getNode($id, $p)->setName($name);
640
641
        return (new Response())->setCode(204);
642
    }
643
644
    /**
645
     * @api {post} /api/v1/node/clone?id=:id Clone node
646
     * @apiVersion 1.0.0
647
     * @apiName postClone
648
     * @apiGroup Node
649
     * @apiPermission none
650
     * @apiDescription Clone a node
651
     * @apiUse _getNode
652
     * @apiUse _conflictNode
653
     * @apiUse _multiError
654
     * @apiUse _writeAction
655
     *
656
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
657
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
658
     *
659
     * @apiExample (cURL) example:
660
     * curl -XPOST "https://SERVER/api/v1/node/clone?id=544627ed3c58891f058b4686&dest=544627ed3c58891f058b4676"
661
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4686/clone?dest=544627ed3c58891f058b4676&conflict=2"
662
     * curl -XPOST "https://SERVER/api/v1/node/clone?p=/absolute/path/to/my/node&conflict=0&destp=/new/parent"
663
     *
664
     * @apiSuccessExample {json} Success-Response:
665
     * HTTP/1.1 204 No Content
666
     *
667
     * @param array|string $id
668
     * @param array|string $p
669
     * @param string       $destid
670
     * @param string       $destp
671
     */
672
    public function postClone(
673
        $id = null,
674
        $p = null,
675
        ?string $destid = null,
676
        ?string $destp = null,
677
        int $conflict = 0
678
    ): Response {
679
        try {
680
            $parent = $this->_getNode($destid, $destp, Collection::class, false, true);
681
        } catch (Exception\NotFound $e) {
682
            throw new Exception\NotFound(
683
                'destination collection was not found or is not a collection',
684
                Exception\NotFound::DESTINATION_NOT_FOUND
685
            );
686
        }
687
688
        return $this->bulk($id, $p, function ($node) use ($parent, $conflict) {
0 ignored issues
show
It seems like $id defined by parameter $id on line 673 can also be of type null; however, Balloon\App\Api\v1\Node::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 674 can also be of type null; however, Balloon\App\Api\v1\Node::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...
689
            $result = $node->copyTo($parent, $conflict);
690
691
            return [
692
                'status' => 201,
693
                'data' => $result,
694
            ];
695
        });
696
    }
697
698
    /**
699
     * @api {post} /api/v1/node/move?id=:id Move node
700
     * @apiVersion 1.0.0
701
     * @apiName postMove
702
     * @apiGroup Node
703
     * @apiPermission none
704
     * @apiDescription Move node
705
     * @apiUse _getNodes
706
     * @apiUse _conflictNode
707
     * @apiUse _multiError
708
     * @apiUse _writeAction
709
     *
710
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
711
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
712
     *
713
     * @apiExample (cURL) example:
714
     * curl -XPOST "https://SERVER/api/v1/node/move?id=544627ed3c58891f058b4686?destid=544627ed3c58891f058b4655"
715
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4686/move?destid=544627ed3c58891f058b4655"
716
     * curl -XPOST "https://SERVER/api/v1/node/move?p=/absolute/path/to/my/node&destp=/new/parent&conflict=1
717
     *
718
     * @apiSuccessExample {json} Success-Response:
719
     * HTTP/1.1 204 No Content
720
     *
721
     * @apiSuccessExample {json} Success-Response (conflict=1):
722
     * HTTP/1.1 200 OK
723
     * {
724
     *      "status":200,
725
     *      "data": "renamed (xy23)"
726
     * }
727
     *
728
     * @param array|string $id
729
     * @param array|string $p
730
     * @param string       $destid
731
     * @param string       $destp
732
     */
733
    public function postMove(
734
        $id = null,
735
        $p = null,
736
        ?string $destid = null,
737
        ?string $destp = null,
738
        int $conflict = 0
739
    ): Response {
740
        try {
741
            $parent = $this->_getNode($destid, $destp, Collection::class, false, true);
742
        } catch (Exception\NotFound $e) {
743
            throw new Exception\NotFound(
744
                'destination collection was not found or is not a collection',
745
                Exception\NotFound::DESTINATION_NOT_FOUND
746
            );
747
        }
748
749
        return $this->bulk($id, $p, function ($node) use ($parent, $conflict) {
0 ignored issues
show
It seems like $id defined by parameter $id on line 734 can also be of type null; however, Balloon\App\Api\v1\Node::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 735 can also be of type null; however, Balloon\App\Api\v1\Node::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...
750
            $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...
751
            if (NodeInterface::CONFLICT_RENAME === $conflict) {
752
                return [
753
                    'status' => 200,
754
                    'data' => $node->getName(),
755
                ];
756
            }
757
758
            return [
759
                'status' => 204,
760
            ];
761
        });
762
    }
763
764
    /**
765
     * @api {delete} /api/v1/node?id=:id Delete node
766
     * @apiVersion 1.0.0
767
     * @apiName delete
768
     * @apiGroup Node
769
     * @apiPermission none
770
     * @apiDescription Delete node
771
     * @apiUse _getNodes
772
     * @apiUse _multiError
773
     * @apiUse _writeAction
774
     *
775
     * @apiParam (GET Parameter) {boolean} [force=false] Force flag need to be set to delete a node from trash (node must have the deleted flag)
776
     * @apiParam (GET Parameter) {boolean} [ignore_flag=false] If both ignore_flag and force_flag were set, the node will be deleted completely
777
     * @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
778
     *
779
     * @apiExample (cURL) example:
780
     * curl -XDELETE "https://SERVER/api/v1/node?id=544627ed3c58891f058b4686"
781
     * curl -XDELETE "https://SERVER/api/v1/node/544627ed3c58891f058b4686?force=1&ignore_flag=1"
782
     * curl -XDELETE "https://SERVER/api/v1/node?p=/absolute/path/to/my/node"
783
     *
784
     * @apiSuccessExample {json} Success-Response:
785
     * HTTP/1.1 204 No Content
786
     *
787
     * @param array|string $id
788
     * @param array|string $p
789
     * @param int          $at
790
     */
791
    public function delete(
792
        $id = null,
793
        $p = null,
794
        bool $force = false,
795
        bool $ignore_flag = false,
796
        ?string $at = null
797
    ): Response {
798
        $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...
799
800
        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...
801
            $at = $this->_verifyAttributes(['destroy' => $at])['destroy'];
802
        }
803
804
        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 792 can also be of type null; however, Balloon\App\Api\v1\Node::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 793 can also be of type null; however, Balloon\App\Api\v1\Node::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...
805
            if (null === $at) {
806
                $node->delete($force && $node->isDeleted() || $force && $ignore_flag);
807
            } else {
808
                if ('0' === $at) {
809
                    $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...
810
                }
811
                $node->setDestroyable($at);
812
            }
813
814
            return [
815
                'status' => 204,
816
            ];
817
        });
818
    }
819
820
    /**
821
     * @api {get} /api/v1/node/query Custom query
822
     * @apiVersion 1.0.0
823
     * @apiName getQuery
824
     * @apiGroup Node
825
     * @apiPermission none
826
     * @apiDescription A custom query is similar requet to children. You do not have to provide any parent node (id or p)
827
     * but you have to provide a filter therefore you can collect any nodes which do match the provided filter. It is a form of a search
828
     * (search) but does not use the search engine like GET /node/search does. You can also create a persistent query collection, just look at
829
     * POST /collection, there you can attach a filter option to the attributes paramater which would be the same as a custom query but just persistent.
830
     * Since query parameters can only be strings and you perhaps would like to filter other data types, you have to send json as parameter to the server.
831
     * @apiUse _nodeAttributes
832
     *
833
     * @apiExample (cURL) example:
834
     * curl -XGET https://SERVER/api/v1/node/query?{%22filter%22:{%22shared%22:true,%22reference%22:{%22$exists%22:0}}}
835
     *
836
     * @apiParam (GET Parameter) {string[]} [attributes] Filter node attributes
837
     * @apiParam (GET Parameter) {string[]} [filter] Filter nodes
838
     * @apiParam (GET Parameter) {number} [deleted=0] Wherever include deleted nodes or not, possible values:</br>
839
     * - 0 Exclude deleted</br>
840
     * - 1 Only deleted</br>
841
     * - 2 Include deleted</br>
842
     *
843
     * @apiSuccess (200 OK) {number} status Status Code
844
     * @apiSuccess (200 OK) {object[]} data Children
845
     * @apiSuccessExample {json} Success-Response:
846
     * HTTP/1.1 200 OK
847
     * {
848
     *      "status":200,
849
     *      "data": [{..}, {...}] //Shorted
850
     * }
851
     */
852
    public function getQuery(int $deleted = 0, array $filter = [], array $attributes = []): Response
853
    {
854
        $children = [];
855
        $nodes = $this->fs->findNodesByFilterUser($deleted, $filter);
856
857
        foreach ($nodes as $node) {
858
            $child = $this->node_decorator->decorate($node, $attributes);
859
            $children[] = $child;
860
        }
861
862
        return (new Response())->setCode(200)->setBody([
863
            'status' => 200,
864
            'data' => $children,
865
        ]);
866
    }
867
868
    /**
869
     * @api {get} /api/v1/node/trash Get trash
870
     * @apiName getTrash
871
     * @apiVersion 1.0.0
872
     * @apiGroup Node
873
     * @apiPermission none
874
     * @apiDescription A similar endpoint to /api/v1/node/query filer={'deleted': {$type: 9}] but instead returning all deleted
875
     * nodes (including children which are deleted as well) this enpoint only returns the first deleted node from every subtree)
876
     * @apiUse _nodeAttributes
877
     *
878
     * @apiExample (cURL) example:
879
     * curl -XGET https://SERVER/api/v1/node/trash?pretty
880
     *
881
     * @apiParam (GET Parameter) {string[]} [attributes] Filter node attributes
882
     *
883
     * @apiSuccess (200 OK) {number} status Status Code
884
     * @apiSuccess (200 OK) {object[]} data Children
885
     * @apiSuccessExample {json} Success-Response:
886
     * HTTP/1.1 200 OK
887
     * {
888
     *      "status":200,
889
     *      "data": [{..}, {...}] //Shorted
890
     * }
891
     */
892
    public function getTrash(array $attributes = []): Response
893
    {
894
        $children = [];
895
        $nodes = $this->fs->findNodesByFilterUser(NodeInterface::DELETED_ONLY, ['deleted' => ['$type' => 9]]);
896
897
        foreach ($nodes as $node) {
898
            try {
899
                $parent = $node->getParent();
900
                if (null !== $parent && $parent->isDeleted()) {
901
                    continue;
902
                }
903
            } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
904
            }
905
906
            $child = $this->node_decorator->decorate($node, $attributes);
907
            $children[] = $child;
908
        }
909
910
        return (new Response())->setCode(200)->setBody([
911
            'status' => 200,
912
            'data' => array_values($children),
913
        ]);
914
    }
915
916
    /**
917
     * @api {get} /api/v1/node/delta Get delta
918
     * @apiVersion 1.0.0
919
     * @apiName getDelta
920
     * @apiGroup Node
921
     * @apiPermission none
922
     * @apiUse _getNode
923
     *
924
     * @apiDescription Use this method to request a delta feed with all changes on the server (or) a snapshot of the server state.
925
     * 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.
926
     * If has_more is TRUE you need to request /delta immediatly again to
927
     * receive the next bunch of deltas. If has_more is FALSE you should wait at least 120s seconds before any further requests to the
928
     * 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).
929
     * 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
930
     * without a cursor. reset could be TRUE if there was an account maintenance or a simialar case.
931
     * You can request a different limit as well but be aware that the number of nodes could be slighty different from your requested limit.
932
     * If requested with parameter id or p the delta gets generated recursively from the node given.
933
     *
934
     * @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
935
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default not all attributes would be returned
936
     * @apiParam (GET Parameter) {string} [cursor=null] Set a cursor to rquest next nodes within delta processing
937
     *
938
     * @apiExample (cURL) example:
939
     * curl -XGET "https://SERVER/api/v1/node/delta?pretty"
940
     *
941
     * @apiSuccess (200 OK) {number} status Status Code
942
     * @apiSuccess (200 OK) {object} data Delta feed
943
     * @apiSuccess (200 OK) {boolean} data.reset If true the local state needs to be reseted, is alway TRUE during
944
     * the first request to /delta without a cursor or in special cases like server or account maintenance
945
     * @apiSuccess (200 OK) {string} data.cursor The cursor needs to be stored and reused to request further deltas
946
     * @apiSuccess (200 OK) {boolean} data.has_more If has_more is TRUE /delta can be requested immediatly after the last request
947
     * to receive further delta. If it is FALSE we should wait at least 120 seconds before any further delta requests to the api endpoint
948
     * @apiSuccess (200 OK) {object[]} data.nodes Node list to process
949
     * @apiSuccess (200 OK) {string} data.nodes.id Node ID
950
     * @apiSuccess (200 OK) {string} data.nodes.deleted Is node deleted?
951
     * @apiSuccess (200 OK) {object} data.nodes.changed Changed timestamp
952
     * @apiSuccess (200 OK) {number} data.nodes.changed.sec Unix timestamp
953
     * @apiSuccess (200 OK) {number} data.nodes.changed.usec Additional Microsecconds to Unix timestamp
954
     * @apiSuccess (200 OK) {object} data.nodes.created Created timestamp (If data.nodes[].deleted is TRUE, created will be NULL)
955
     * @apiSuccess (200 OK) {number} data.nodes.created.sec Unix timestamp
956
     * @apiSuccess (200 OK) {number} data.nodes.created.usec Additional Microsecconds to Unix timestamp
957
     * @apiSuccess (200 OK) {string} data.nodes.path The full absolute path to the node
958
     * @apiSuccess (200 OK) {string} data.nodes.directory Is true if node is a directory
959
     * @apiSuccessExample {json} Success-Response:
960
     * HTTP/1.1 200 OK
961
     * {
962
     *      "status": 200,
963
     *      "data": {
964
     *          "reset": false,
965
     *          "cursor": "aW5pdGlhbHwxMDB8NTc1YTlhMGIzYzU4ODkwNTE0OGI0NTZifDU3NWE5YTBiM2M1ODg5MDUxNDhiNDU2Yw==",
966
     *          "has_more": false,
967
     *          "nodes": [
968
     *             {
969
     *                  "id": "581afa783c5889ad7c8b4572",
970
     *                  "deleted": true,
971
     *                  "created": null,
972
     *                  "changed": {
973
     *                      "sec": 1478163064,
974
     *                      "usec": 31.0.0
975
     *                  },
976
     *                  "path": "\/AAA\/AL",
977
     *                  "directory": true
978
     *              },
979
     *              {
980
     *                  "id": "581afa783c5889ad7c8b3dcf",
981
     *                  "deleted": false,
982
     *                  "created": {
983
     *                      "sec": 1478163048,
984
     *                      "usec": 101000
985
     *                  },
986
     *                  "changed": {
987
     *                      "sec": 1478163048,
988
     *                      "usec": 101000
989
     *                  },
990
     *                  "path": "\/AL",
991
     *                  "directory": true
992
     *              }
993
     *          ]
994
     *      }
995
     * }
996
     *
997
     * @param string $id
998
     * @param string $p
999
     * @param string $cursor
1000
     */
1001
    public function getDelta(
1002
        DeltaDecorator $delta_decorator,
1003
        ?string $id = null,
1004
        ?string $p = null,
1005
        ?string $cursor = null,
1006
        int $limit = 250,
1007
        array $attributes = []
1008
    ): Response {
1009
        if (null !== $id || null !== $p) {
1010
            $node = $this->_getNode($id, $p);
1011
        } else {
1012
            $node = null;
1013
        }
1014
1015
        $result = $this->fs->getDelta()->getDeltaFeed($cursor, $limit, $node);
1016
1017
        $default = ['id', 'deleted', 'created', 'changed', 'path', 'directory'];
1018
        $attributes = array_merge($default, $attributes);
1019
1020
        foreach ($result['nodes'] as &$node) {
1021
            if ($node instanceof NodeInterface) {
1022
                $node = $this->node_decorator->decorate($node, $attributes);
1023
            } else {
1024
                $node = $delta_decorator->decorate($node, $attributes);
1025
            }
1026
        }
1027
1028
        return (new Response())->setCode(200)->setBody([
1029
            'status' => 200,
1030
            'data' => $result,
1031
        ]);
1032
    }
1033
1034
    /**
1035
     * @api {get} /api/v1/node/event-log?id=:id Event log
1036
     * @apiVersion 1.0.0
1037
     * @apiName getEventLog
1038
     * @apiGroup Node
1039
     * @apiPermission none
1040
     * @apiUse _getNode
1041
     * @apiDescription Get detailed event log
1042
     * Request all modifications which are made by the user himself or share members.
1043
     * Possible operations are the follwing:
1044
     * - deleteCollectionReference
1045
     * - deleteCollectionShare
1046
     * - deleteCollection
1047
     * - addCollection
1048
     * - addFile
1049
     * - addCollectionShare
1050
     * - addCollectionReference
1051
     * - undeleteFile
1052
     * - undeleteCollectionReference
1053
     * - undeleteCollectionShare
1054
     * - restoreFile
1055
     * - renameFile
1056
     * - renameCollection
1057
     * - renameCollectionShare
1058
     * - renameCollectionRFeference
1059
     * - copyFile
1060
     * - copyCollection
1061
     * - copyCollectionShare
1062
     * - copyCollectionRFeference
1063
     * - moveFile
1064
     * - moveCollection
1065
     * - moveCollectionReference
1066
     * - moveCollectionShare
1067
     *
1068
     * @apiExample (cURL) example:
1069
     * curl -XGET "https://SERVER/api/v1/node/event-log?pretty"
1070
     * curl -XGET "https://SERVER/api/v1/node/event-log?id=544627ed3c58891f058b4686&pretty"
1071
     * curl -XGET "https://SERVER/api/v1/node/544627ed3c58891f058b4686/event-log?pretty&limit=10"
1072
     * curl -XGET "https://SERVER/api/v1/node/event-log?p=/absolute/path/to/my/node&pretty"
1073
     *
1074
     * @apiParam (GET Parameter) {number} [limit=100] Sets limit of events to be returned
1075
     * @apiParam (GET Parameter) {number} [skip=0] How many events are skiped (useful for paging)
1076
     *
1077
     * @apiSuccess (200 OK) {number} status Status Code
1078
     * @apiSuccess (200 OK) {object[]} data Events
1079
     * @apiSuccess (200 OK) {number} data.event Event ID
1080
     * @apiSuccess (200 OK) {object} data.timestamp event timestamp
1081
     * @apiSuccess (200 OK) {number} data.timestamp.sec Event timestamp timestamp in Unix time
1082
     * @apiSuccess (200 OK) {number} data.timestamp.usec Additional microseconds to changed Unix timestamp
1083
     * @apiSuccess (200 OK) {string} data.operation event operation (like addCollection, deleteFile, ...)
1084
     * @apiSuccess (200 OK) {string} data.parent ID of the parent node at the time of the event
1085
     * @apiSuccess (200 OK) {object} data.previous Previous state of actual data which has been modified during an event, can contain either version, name or parent
1086
     * @apiSuccess (200 OK) {number} data.previous.version Version at the time before the event
1087
     * @apiSuccess (200 OK) {string} data.previous.name Name at the time before the event
1088
     * @apiSuccess (200 OK) {string} data.previous.parent Parent node at the time before the event
1089
     * @apiSuccess (200 OK) {string} data.share If of the shared folder at the time of the event
1090
     * @apiSuccess (200 OK) {string} data.name Name of the node at the time of the event
1091
     * @apiSuccess (200 OK) {object} data.node Current data of the node (Not from the time of the event!)
1092
     * @apiSuccess (200 OK) {boolean} data.node.deleted True if the node is deleted, false otherwise
1093
     * @apiSuccess (200 OK) {string} data.node.id Actual ID of the node
1094
     * @apiSuccess (200 OK) {string} data.node.name Current name of the node
1095
     * @apiSuccess (200 OK) {object} data.user Data which contains information about the user who executed an event
1096
     * @apiSuccess (200 OK) {string} data.user.id Actual user ID
1097
     * @apiSuccess (200 OK) {string} data.user.username Current username of executed event
1098
     *
1099
     * @apiSuccessExample {json} Success-Response:
1100
     * HTTP/1.1 200 OK
1101
     * {
1102
     *      "status": 200,
1103
     *      "data": [
1104
     *          {
1105
     *              "event": "57628e523c5889026f8b4570",
1106
     *              "timestamp": {
1107
     *                  "sec": 1466076753,
1108
     *                  "usec": 988000
1109
     *              },
1110
     *              "operation": "restoreFile",
1111
     *              "name": "file.txt",
1112
     *              "previous": {
1113
     *                  "version": 16
1114
     *              },
1115
     *              "node": {
1116
     *                  "id": "558c0b273c588963078b457a",
1117
     *                  "name": "3dddsceheckfile.txt",
1118
     *                  "deleted": false
1119
     *              },
1120
     *              "parent": null,
1121
     *              "user": {
1122
     *                  "id": "54354cb63c58891f058b457f",
1123
     *                  "username": "gradmin.bzu"
1124
     *              },
1125
     *              "share": null
1126
     *          }
1127
     *      ]
1128
     * }
1129
     *
1130
     * @param string $id
1131
     * @param string $p
1132
     */
1133
    public function getEventLog(EventDecorator $event_decorator, ?string $id = null, ?string $p = null, int $skip = 0, int $limit = 100): Response
1134
    {
1135
        if (null !== $id || null !== $p) {
1136
            $node = $this->_getNode($id, $p);
1137
        } else {
1138
            $node = null;
1139
        }
1140
1141
        $result = $this->fs->getDelta()->getEventLog($limit, $skip, $node);
1142
        $body = [];
1143
        foreach ($result as $event) {
1144
            $body[] = $event_decorator->decorate($event);
1145
        }
1146
1147
        return (new Response())->setCode(200)->setBody([
1148
            'status' => 200,
1149
            'data' => $body,
1150
        ]);
1151
    }
1152
1153
    /**
1154
     * @api {get} /api/v1/node/last-cursor Get last Cursor
1155
     * @apiVersion 1.0.0
1156
     * @apiName geLastCursor
1157
     * @apiGroup Node
1158
     * @apiUse _getNode
1159
     * @apiPermission none
1160
     * @apiDescription Use this method to request the latest cursor if you only need to now
1161
     * if there are changes on the server. This method will not return any other data than the
1162
     * newest cursor. To request a feed with all deltas request /delta.
1163
     *
1164
     * @apiExample (cURL) example:
1165
     * curl -XGET "https://SERVER/api/v1/node/last-cursor?pretty"
1166
     *
1167
     * @apiSuccess (200 OK) {number} status Status Code
1168
     * @apiSuccess (200 OK) {string} data Newest cursor
1169
     * @apiSuccessExample {json} Success-Response:
1170
     * HTTP/1.1 200 OK
1171
     * {
1172
     *      "status": 200,
1173
     *      "data": "aW5pdGlhbHwxMDB8NTc1YTlhMGIzYzU4ODkwNTE0OGI0NTZifDU3NWE5YTBiM2M1ODg5MDUxNDhiNDU2Yw=="
1174
     * }
1175
     *
1176
     * @param string $id
1177
     * @param string $p
1178
     */
1179
    public function getLastCursor(?string $id = null, ?string $p = null): Response
1180
    {
1181
        if (null !== $id || null !== $p) {
1182
            $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...
1183
        } else {
1184
            $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...
1185
        }
1186
1187
        $result = $this->fs->getDelta()->getLastCursor();
1188
1189
        return (new Response())->setCode(200)->setBody([
1190
            'status' => 200,
1191
            'data' => $result,
1192
        ]);
1193
    }
1194
1195
    /**
1196
     * Do bulk operations.
1197
     *
1198
     * @param array|string $id
1199
     * @param array|string $p
1200
     */
1201
    protected function bulk($id, $p, Closure $action): Response
1202
    {
1203
        if (is_array($id) || is_array($p)) {
1204
            $errors = [];
1205
            $body = [];
1206
1207
            foreach ($this->_getNodes($id, $p) as $node) {
0 ignored issues
show
It seems like $id defined by parameter $id on line 1201 can also be of type array; however, Balloon\App\Api\v1\Node::_getNodes() 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 1201 can also be of type array; however, Balloon\App\Api\v1\Node::_getNodes() 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...
1208
                try {
1209
                    $body[] = $action->call($this, $node);
1210
                } catch (\Exception $e) {
1211
                    $errors[] = [
1212
                        'error' => get_class($e),
1213
                        'message' => $e->getMessage(),
1214
                        'status' => $e->getCode(),
1215
                    ];
1216
                }
1217
            }
1218
1219
            if (!empty($errors)) {
1220
                return (new Response())->setCode(400)->setBody([
1221
                    'status' => 400,
1222
                    'data' => $errors,
1223
                ]);
1224
            }
1225
            if (empty($body)) {
1226
                return (new Response())->setCode(204);
1227
            }
1228
            $body = array_shift($body);
1229
            $response = (new Response())->setCode($body['status']);
1230
1231
            if (isset($body['data'])) {
1232
                $response->setBody([
1233
                    'status' => $body['status'],
1234
                    'data' => $body['data'],
1235
                ]);
1236
            }
1237
1238
            return $response;
1239
        }
1240
1241
        $body = $action->call($this, $this->_getNode($id, $p));
1242
        $response = (new Response())->setCode($body['status']);
1243
1244
        if (isset($body['data'])) {
1245
            $response->setBody([
1246
                'status' => $body['status'],
1247
                'data' => $body['data'],
1248
            ]);
1249
        }
1250
1251
        return $response;
1252
    }
1253
1254
    /**
1255
     * Get node.
1256
     *
1257
     * @param string $id
1258
     * @param string $path
1259
     * @param string $class      Force set node type
1260
     * @param bool   $multiple   Allow $id to be an array
1261
     * @param bool   $allow_root Allow instance of root collection
1262
     * @param bool   $deleted    How to handle deleted node
1263
     */
1264
    protected function _getNode(
1265
        ?string $id = null,
1266
        ?string $path = null,
1267
        ?string $class = null,
1268
        bool $multiple = false,
1269
        bool $allow_root = false,
1270
        int $deleted = 2
1271
    ): NodeInterface {
1272
        if (null === $class) {
1273
            switch (get_class($this)) {
1274
                case ApiFile::class:
1275
                    $class = File::class;
1276
1277
                break;
1278
                case ApiCollection::class:
1279
                    $class = Collection::class;
1280
1281
                break;
1282
            }
1283
        }
1284
1285
        return $this->fs->getNode($id, $path, $class, $multiple, $allow_root, $deleted);
1286
    }
1287
1288
    /**
1289
     * Get nodes.
1290
     *
1291
     * @param string $id
1292
     * @param string $path
1293
     * @param string $class   Force set node type
1294
     * @param bool   $deleted How to handle deleted node
1295
     */
1296
    protected function _getNodes(
1297
        $id = null,
1298
        $path = null,
1299
        ?string $class = null,
1300
        int $deleted = 2
1301
    ): Generator {
1302
        if (null === $class) {
1303
            switch (get_class($this)) {
1304
                case ApiFile::class:
1305
                    $class = File::class;
1306
1307
                break;
1308
                case ApiCollection::class:
1309
                    $class = Collection::class;
1310
1311
                break;
1312
            }
1313
        }
1314
1315
        return $this->fs->getNodes($id, $path, $class, $deleted);
1316
    }
1317
1318
    /**
1319
     * Merge multiple nodes into one zip archive.
1320
     *
1321
     * @param string $id
1322
     * @param string $path
1323
     */
1324
    protected function combine($id = null, $path = null, string $name = 'selected')
1325
    {
1326
        $archive = new ZipStream($name.'.zip');
1327
1328
        foreach ($this->_getNodes($id, $path) as $node) {
1329
            try {
1330
                $node->zip($archive);
1331
            } catch (\Exception $e) {
1332
                $this->logger->debug('failed zip node in multi node request ['.$node->getId().']', [
1333
                   'category' => get_class($this),
1334
                   'exception' => $e,
1335
               ]);
1336
            }
1337
        }
1338
1339
        $archive->finish();
1340
    }
1341
1342
    /**
1343
     * Check custom node attributes which have to be written.
1344
     */
1345
    protected function _verifyAttributes(array $attributes): array
1346
    {
1347
        $valid_attributes = [
1348
            'changed',
1349
            'destroy',
1350
            'created',
1351
            'meta',
1352
            'readonly',
1353
        ];
1354
1355
        if ($this instanceof ApiCollection) {
1356
            $valid_attributes[] = 'filter';
1357
        }
1358
1359
        $check = array_merge(array_flip($valid_attributes), $attributes);
1360
1361
        if ($this instanceof ApiCollection && count($check) > 6) {
1362
            throw new Exception\InvalidArgument('Only changed, created, destroy timestamp, filter, readonly and/or meta attributes may be overwritten');
1363
        }
1364
        if ($this instanceof ApiFile && count($check) > 5) {
1365
            throw new Exception\InvalidArgument('Only changed, created, destroy timestamp, readonly and/or meta attributes may be overwritten');
1366
        }
1367
1368
        foreach ($attributes as $attribute => $value) {
1369
            switch ($attribute) {
1370
                case 'filter':
1371
                    $attributes['filter'] = json_encode((array) $attributes['filter']);
1372
1373
                break;
1374
                case 'destroy':
1375
                    if (!Helper::isValidTimestamp($value)) {
1376
                        throw new Exception\InvalidArgument($attribute.' Changed timestamp must be valid unix timestamp');
1377
                    }
1378
                    $attributes[$attribute] = new UTCDateTime($value.'000');
1379
1380
                break;
1381
                case 'changed':
1382
                case 'created':
1383
                    if (!Helper::isValidTimestamp($value)) {
1384
                        throw new Exception\InvalidArgument($attribute.' Changed timestamp must be valid unix timestamp');
1385
                    }
1386
                    if ((int) $value > time()) {
1387
                        throw new Exception\InvalidArgument($attribute.' timestamp can not be set greater than the server time');
1388
                    }
1389
                    $attributes[$attribute] = new UTCDateTime($value.'000');
1390
1391
                break;
1392
                case 'readonly':
1393
                    $attributes['readonly'] = (bool) $attributes['readonly'];
1394
1395
                break;
1396
            }
1397
        }
1398
1399
        return $attributes;
1400
    }
1401
}
1402