Completed
Branch dev (276354)
by Raffael
15:43
created

Nodes::_getNode()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 8.7972
c 0
b 0
f 0
cc 4
eloc 17
nc 4
nop 6
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2018 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\v2\Collections as ApiCollection;
16
use Balloon\App\Api\v2\Files as ApiFile;
17
use Balloon\AttributeDecorator\Pager;
18
use Balloon\Filesystem;
19
use Balloon\Filesystem\DeltaAttributeDecorator;
20
use Balloon\Filesystem\EventAttributeDecorator;
21
use Balloon\Filesystem\Exception;
0 ignored issues
show
Bug introduced by
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...
22
use Balloon\Filesystem\Node\AttributeDecorator as NodeAttributeDecorator;
23
use Balloon\Filesystem\Node\Collection;
24
use Balloon\Filesystem\Node\File;
25
use Balloon\Filesystem\Node\NodeInterface;
26
use Balloon\Helper;
27
use Balloon\Server;
28
use Balloon\Server\User;
29
use Micro\Http\Response;
30
use MongoDB\BSON\UTCDateTime;
31
use Psr\Log\LoggerInterface;
32
use ZipStream\ZipStream;
33
34
class Nodes extends Controller
35
{
36
    /**
37
     * Filesystem.
38
     *
39
     * @var Filesystem
40
     */
41
    protected $fs;
42
43
    /**
44
     * LoggerInterface.
45
     *
46
     * @var LoggerInterface
47
     */
48
    protected $logger;
49
50
    /**
51
     * Server.
52
     *
53
     * @var Server
54
     */
55
    protected $server;
56
57
    /**
58
     * User.
59
     *
60
     * @var User
61
     */
62
    protected $user;
63
64
    /**
65
     * Node attribute decorator.
66
     *
67
     * @var NodeAttributeDecorator
68
     */
69
    protected $node_decorator;
70
71
    /**
72
     * Initialize.
73
     *
74
     * @param Server                 $server
75
     * @param NodeAttributeDecorator $decorator
76
     * @param LoggerInterface        $logger
77
     */
78
    public function __construct(Server $server, NodeAttributeDecorator $decorator, LoggerInterface $logger)
79
    {
80
        $this->fs = $server->getFilesystem();
81
        $this->user = $server->getIdentity();
82
        $this->server = $server;
83
        $this->node_decorator = $decorator;
84
        $this->logger = $logger;
85
    }
86
87
    /**
88
     * @api {head} /api/v2/nodes/:id Node exists?
89
     * @apiVersion 2.0.0
90
     * @apiName head
91
     * @apiGroup Node
92
     * @apiPermission none
93
     * @apiDescription Check if a node exists. Per default deleted nodes are ignore which means it will
94
     *  return a 404 if a deleted node is requested. You can change this behaviour via the deleted parameter.
95
     * @apiUse _getNode
96
     *
97
     * @apiExample (cURL) example:
98
     * curl -XHEAD "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686"
99
     * curl -XHEAD "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686"
100
     * curl -XHEAD "https://SERVER/api/v2/node?p=/absolute/path/to/my/node"
101
     *
102
     * @apiParam (GET Parameter) {number} [deleted=0] Wherever include deleted node or not, possible values:</br>
103
     * - 0 Exclude deleted</br>
104
     * - 1 Only deleted</br>
105
     * - 2 Include deleted</br>
106
     *
107
     * @apiSuccessExample {json} Success-Response (Node does exist):
108
     * HTTP/1.1 200 OK
109
     *
110
     * @apiSuccessExample {json} Success-Response (Node does not exist):
111
     * HTTP/1.1 404 Not Found
112
     *
113
     * @param string $id
114
     * @param string $p
115
     * @param int    $deleted
116
     *
117
     * @return Response
118
     */
119
    public function head(?string $id = null, ?string $p = null, int $deleted = 0): Response
120
    {
121
        try {
122
            $result = $this->_getNode($id, $p, null, false, false, $deleted);
123
124
            $response = (new Response())
125
                ->setHeader('Content-Length', (string) $result->getSize())
126
                ->setHeader('Content-Type', $result->getContentType())
127
                ->setCode(200);
128
129
            return $response;
130
        } catch (\Exception $e) {
131
            return (new Response())->setCode(404);
132
        }
133
    }
134
135
    /**
136
     * @api {post} /api/v2/nodes/:id/undelete Restore node
137
     * @apiVersion 2.0.0
138
     * @apiName postUndelete
139
     * @apiGroup Node
140
     * @apiPermission none
141
     * @apiDescription Undelete (Restore from trash) a single node or multiple ones.
142
     * @apiUse _getNodes
143
     * @apiUse _conflictNode
144
     * @apiUse _multiError
145
     * @apiUse _writeAction
146
     *
147
     * @apiExample (cURL) example:
148
     * curl -XPOST "https://SERVER/api/v2/nodes/undelete?id[]=544627ed3c58891f058b4686&id[]=544627ed3c58891f058b46865&pretty"
149
     * curl -XPOST "https://SERVER/api/v2/nodes/undelete?id=544627ed3c58891f058b4686?pretty"
150
     * curl -XPOST "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/undelete?conflict=2"
151
     * curl -XPOST "https://SERVER/api/v2/nodes/undelete?p=/absolute/path/to/my/node&conflict=0&move=1&destid=544627ed3c58891f058b46889"
152
     *
153
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
154
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
155
     *
156
     * @apiSuccessExample {json} Success-Response (conflict=1):
157
     * HTTP/1.1 200 OK
158
     * {
159
     *      "id": "544627ed3c58891f058b4686",
160
     *      "name": "renamed (xy23)"
161
     * }
162
     *
163
     * @param array|string $id
164
     * @param array|string $p
165
     * @param bool         $move
166
     * @param string       $destid
167
     * @param string       $destp
168
     * @param int          $conflict
169
     */
170
    public function postUndelete(
171
        $id = null,
172
        $p = null,
173
        bool $move = false,
174
        ?string $destid = null,
175
        ?string $destp = null,
176
        int $conflict = 0
177
    ): Response {
178
        $parent = null;
179
        if (true === $move) {
180
            try {
181
                $parent = $this->_getNode($destid, $destp, 'Collection', false, true);
182
            } catch (Exception\NotFound $e) {
183
                throw new Exception\NotFound(
184
                    'destination collection was not found or is not a collection',
185
                    Exception\NotFound::DESTINATION_NOT_FOUND
186
                );
187
            }
188
        }
189
190
        return $this->bulk($id, $p, function ($node) use ($parent, $conflict, $move) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 171 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...
Bug introduced by
It seems like $p defined by parameter $p on line 172 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...
191
            if (true === $move) {
192
                $node = $node->setParent($parent, $conflict);
193
            }
194
195
            if ($node->isDeleted()) {
196
                $node->undelete($conflict);
197
            }
198
199
            return [
200
                'code' => 200,
201
                'data' => $this->node_decorator->decorate($node),
202
            ];
203
        });
204
    }
205
206
    /**
207
     * @api {get} /api/v2/nodes/:id/content Download stream
208
     * @apiVersion 2.0.0
209
     * @apiName getContent
210
     * @apiGroup Node
211
     * @apiPermission none
212
     * @apiDescription Download node contents. Collections are zipped during streaming.
213
     * @apiUse _getNode
214
     *
215
     * @apiParam (GET Parameter) {number} [offset=0] Read stream from a specific offset in bytes
216
     * @apiParam (GET Parameter) {number} [limit=0] Read stream until a specific limit in bytes
217
     * @apiParam (GET Parameter) {string} [encode] Can be set to base64 to encode content as base64.
218
     * @apiParam (GET Parameter) {boolean} [download=false] Force download file (Content-Disposition: attachment HTTP header)
219
     *
220
     * @apiExample (cURL) example:
221
     * curl -XGET "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686" > myfile.txt
222
     * curl -XGET "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686" > myfile.txt
223
     * curl -XGET "https://SERVER/api/v2/node?p=/absolute/path/to/my/collection" > folder.zip
224
     *
225
     * @apiSuccessExample {string} Success-Response (encode=base64):
226
     * HTTP/1.1 200 OK
227
     *
228
     * @apiSuccessExample {binary} Success-Response:
229
     * HTTP/1.1 200 OK
230
     *
231
     * @apiErrorExample {json} Error-Response (Invalid offset):
232
     * HTTP/1.1 400 Bad Request
233
     * {
234
     *      "status": 400,
235
     *      "data": {
236
     *          "error": "Balloon\\Exception\\Conflict",
237
     *          "message": "invalid offset requested",
238
     *          "code": 277
239
     *      }
240
     * }
241
     *
242
     * @param array|string $id
243
     * @param array|string $p
244
     * @param int          $offset
245
     * @param int          $limit
246
     * @param string       $encode
247
     * @param bool         $download
248
     * @param string       $name
249
     */
250
    public function getContent(
251
        $id = null,
252
        $p = null,
253
        int $offset = 0,
254
        int $limit = 0,
255
        ?string $encode = null,
256
        bool $download = false,
257
        string $name = 'selected'
258
    ): ?Response {
259
        if (is_array($id) || is_array($p)) {
260
            return $this->combine($id, $p, $name);
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 251 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...
Bug introduced by
It seems like $p defined by parameter $p on line 252 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...
261
        }
262
263
        $node = $this->_getNode($id, $p);
264
        if ($node instanceof Collection) {
265
            return $node->getZip();
266
        }
267
268
        $response = new Response();
269
270
        if (true === $download) {
271
            $response->setHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\''.rawurlencode($node->getName()));
272
            $response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
273
            $response->setHeader('Content-Type', 'application/octet-stream');
274
            $response->setHeader('Content-Length', (string) $node->getSize());
275
            $response->setHeader('Content-Transfer-Encoding', 'binary');
276
        } else {
277
            $response->setHeader('Content-Disposition', 'inline; filename*=UTF-8\'\''.rawurlencode($node->getName()));
278
            $response->setHeader('Content-Type', $node->getContentType());
279
        }
280
281
        return $response
282
          ->setOutputFormat(null)
283
          ->setBody(function () use ($node, $encode, $offset, $limit) {
284
              $stream = $node->get();
285
              $name = $node->getName();
0 ignored issues
show
Unused Code introduced by
$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...
286
287
              if (null === $stream) {
288
                  echo '';
289
290
                  return;
291
              }
292
293
              if (0 !== $offset) {
294
                  if (fseek($stream, $offset) === -1) {
295
                      throw new Exception\Conflict(
296
                        'invalid offset requested',
297
                        Exception\Conflict::INVALID_OFFSET
298
                    );
299
                  }
300
              }
301
302
              $read = 0;
303
              if ('base64' === $encode) {
304
                  header('Content-Encoding: base64');
305
                  while (!feof($stream)) {
306
                      if (0 !== $limit && $read + 8192 > $limit) {
307
                          echo base64_encode(fread($stream, $limit - $read));
308
                          exit();
309
                      }
310
311
                      echo base64_encode(fread($stream, 8192));
312
                      $read += 8192;
313
                  }
314
              } else {
315
                  while (!feof($stream)) {
316
                      if (0 !== $limit && $read + 8192 > $limit) {
317
                          echo fread($stream, $limit - $read);
318
                          exit();
319
                      }
320
321
                      echo fread($stream, 8192);
322
                      $read += 8192;
323
                  }
324
325
                  exit();
326
              }
327
          });
328
    }
329
330
    /**
331
     * @api {post} /api/v2/nodes/:id/readonly Set readonly
332
     * @apiVersion 2.0.0
333
     * @apiName postReadonly
334
     * @apiGroup Node
335
     * @apiPermission none
336
     * @apiDescription Set (or unset) node as readonly
337
     * @apiUse _getNodes
338
     * @apiUse _multiError
339
     * @apiUse _writeAction
340
     *
341
     * @apiExample (cURL) example:
342
     * curl -XPOST "https://SERVER/api/v2/nodes/readonly?id[]=544627ed3c58891f058b4686&id[]=544627ed3c58891f058b46865&readonly=1"
343
     * curl -XPOST "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/readonly?readonly=0"
344
     * curl -XPOST "https://SERVER/api/v2/nodes/readonly?p=/absolute/path/to/my/node"
345
     *
346
     * @apiParam (GET Parameter) {bool} [readonly=true] Set readonly to false to make node writeable again
347
     *
348
     * @apiSuccessExample {json} Success-Response:
349
     * HTTP/1.1 200 OK
350
     *
351
     * @param array|string $id
352
     * @param array|string $p
353
     *
354
     * @return Response
355
     */
356
    public function postReadonly($id = null, $p = null, bool $readonly = true): Response
357
    {
358
        return $this->bulk($id, $p, function ($node) use ($readonly) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 356 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...
Bug introduced by
It seems like $p defined by parameter $p on line 356 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...
359
            $node->setReadonly($readonly);
360
361
            return [
362
                'code' => 200,
363
                'data' => $this->node_decorator->decorate($node),
364
            ];
365
        });
366
    }
367
368
    /**
369
     * @apiDefine _nodeAttributes
370
     *
371
     * @apiSuccess (200 OK) {string} id Unique node id
372
     * @apiSuccess (200 OK) {string} name Name
373
     * @apiSuccess (200 OK) {string} hash MD5 content checksum (file only)
374
     * @apiSuccess (200 OK) {object} meta Extended meta attributes
375
     * @apiSuccess (200 OK) {string} meta.description UTF-8 Text Description
376
     * @apiSuccess (200 OK) {string} meta.color Color Tag (HEX) (Like: #000000)
377
     * @apiSuccess (200 OK) {string} meta.author Author
378
     * @apiSuccess (200 OK) {string} meta.mail Mail contact address
379
     * @apiSuccess (200 OK) {string} meta.license License
380
     * @apiSuccess (200 OK) {string} meta.copyright Copyright string
381
     * @apiSuccess (200 OK) {string[]} meta.tags Search Tags
382
     * @apiSuccess (200 OK) {number} size Size in bytes (file only), number of children if collection
383
     * @apiSuccess (200 OK) {string} mime Mime type
384
     * @apiSuccess (200 OK) {boolean} sharelink Is node shared?
385
     * @apiSuccess (200 OK) {number} version File version (file only)
386
     * @apiSuccess (200 OK) {mixed} deleted Is boolean false if not deleted, if deleted it contains a deleted timestamp
387
     * @apiSuccess (200 OK) {string} deleted ISO8601 timestamp, only set if node is deleted
388
     * @apiSuccess (200 OK) {string} changed ISO8601 timestamp
389
     * @apiSuccess (200 OK) {string} created ISO8601 timestamp
390
     * @apiSuccess (200 OK) {string} destroy ISO8601 timestamp, only set if node has a destroy timestamp set
391
     * @apiSuccess (200 OK) {boolean} share Node is shared
392
     * @apiSuccess (200 OK) {boolean} directory Is true if the node is a collection
393
     * @apiSuccess (200 OK) {string} access Access permission for the authenticated user (d/r/rw/m)
394
     * @apiSuccess (200 OK) {object} shareowner Share owner
395
     * @apiSuccess (200 OK) {object} parent Parent node
396
     * @apiSuccess (200 OK) {string} path Absolute node path
397
     * @apiSuccess (200 OK) {string} filter Node is filtered (collection only)
398
     * @apiSuccess (200 OK) {boolean} readonly Readonly
399
     *
400
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes
401
     *
402
     * @param null|mixed $id
403
     * @param null|mixed $p
404
     */
405
406
    /**
407
     * @api {get} /api/v2/nodes/:id Get attributes
408
     * @apiVersion 2.0.0
409
     * @apiName get
410
     * @apiGroup Node
411
     * @apiPermission none
412
     * @apiDescription Get attributes from one or multiple nodes
413
     * @apiUse _getNode
414
     * @apiUse _nodeAttributes
415
     *
416
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default only a bunch of attributes would be returned, if you
417
     * need other attributes you have to request them (for example "path")
418
     *
419
     * @apiExample (cURL) example:
420
     * curl -XGET "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686&pretty"
421
     * curl -XGET "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted&pretty"
422
     * curl -XGET "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686?pretty"
423
     * curl -XGET "https://SERVER/api/v2/node?p=/absolute/path/to/my/node&pretty"
424
     *
425
     * @apiSuccessExample {json} Success-Response:
426
     * HTTP/1.1 200 OK
427
     * {
428
     *      "id": "544627ed3c58891f058b4686",
429
     *      "name": "api.php",
430
     *      "hash": "a77f23ed800fd7a600a8c2cfe8cc370b",
431
     *      "meta": {
432
     *          "license": "GPLv3"
433
     *      },
434
     *      "size": 178,
435
     *      "mime": "text\/plain",
436
     *      "sharelink": true,
437
     *      "version": 1,
438
     *      "changed": "2007-08-31T16:47+00:00",
439
     *      "created": "2007-08-31T16:47+00:00",
440
     *      "share": false,
441
     *      "directory": false
442
     * }
443
     *
444
     * @param array|string $id
445
     * @param array|string $p
446
     * @param array        $attributes
447
     * @param int          $offset
448
     * @param int          $limit
449
     *
450
     * @return Response
451
     */
452
    public function get($id = null, $p = null, int $deleted = 0, array $query = [], array $attributes = [], int $offset = 0, int $limit = 20): Response
453
    {
454
        if (is_array($id) || is_array($p)) {
455
            $nodes = [];
456
            foreach ($this->_getNodes($id, $p) as $node) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 452 can also be of type array; however, Balloon\App\Api\Controller::_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...
Bug introduced by
It seems like $p defined by parameter $p on line 452 can also be of type array; however, Balloon\App\Api\Controller::_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...
457
                $nodes[] = $this->node_decorator->decorate($node, $attributes);
458
            }
459
460
            return (new Response())->setCode(200)->setBody($nodes);
461
        }
462
        if ($id === null && $p === null) {
463
            if ($this instanceof ApiFile) {
464
                $query['directory'] = false;
465
                $uri = '/api/v2/files';
466
            } elseif ($this instanceof ApiCollection) {
467
                $query['directory'] = true;
468
                $uri = '/api/v2/collections';
469
            } else {
470
                $uri = '/api/v2/nodes';
471
            }
472
473
            $nodes = $this->fs->findNodesByFilterUser($deleted, $query, $offset, $limit);
474
            $pager = new Pager($this->node_decorator, $nodes, $attributes, $offset, $limit, $uri);
0 ignored issues
show
Documentation introduced by
$nodes is of type object<Generator>, but the function expects a object<Balloon\AttributeDecorator\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
475
            $result = $pager->paging();
476
477
            return (new Response())->setCode(200)->setBody($result);
478
        }
479
480
        $result = $this->node_decorator->decorate($this->_getNode($id, $p), $attributes);
481
482
        return (new Response())->setCode(200)->setBody($result);
483
    }
484
485
    /**
486
     * @api {get} /api/v2/nodes/:id/parents Get parent nodes
487
     * @apiVersion 2.0.0
488
     * @apiName getParents
489
     * @apiGroup Node
490
     * @apiPermission none
491
     * @apiDescription Get system attributes of all parent nodes. The hirarchy of all parent nodes is ordered in a
492
     * single level array beginning with the collection on the highest level.
493
     * @apiUse _getNode
494
     * @apiUse _nodeAttributes
495
     *
496
     * @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)
497
     *
498
     * @apiExample (cURL) example:
499
     * curl -XGET "https://SERVER/api/v2/nodes/parents?id=544627ed3c58891f058b4686&pretty"
500
     * curl -XGET "https://SERVER/api/v2/nodes/parents?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted&pretty"
501
     * curl -XGET "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/parents?pretty&self=1"
502
     * curl -XGET "https://SERVER/api/v2/nodes/parents?p=/absolute/path/to/my/node&self=1"
503
     *
504
     * @apiSuccessExample {json} Success-Response:
505
     * HTTP/1.1 200 OK
506
     * [
507
     *  {
508
     *      "id": "544627ed3c58891f058bbbaa",
509
     *      "name": "rootdir",
510
     *      "meta": {},
511
     *      "size": 1,
512
     *      "mime": "inode/directory",
513
     *      "created": "2007-08-31T16:47+00:00",
514
     *      "changed": "2007-08-31T16:47+00:00",
515
     *      "destroy": "2020-08-31T16:47+00:00",
516
     *      "share": false,
517
     *      "directory": true
518
     *  },
519
     *  {
520
     *      "id": "544627ed3c58891f058b46cc",
521
     *      "name": "parentdir",
522
     *      "meta": {},
523
     *      "size": 3,
524
     *      "mime": "inode/directory",
525
     *      "created": "2007-08-31T16:47+00:00",
526
     *      "changed": "2007-08-31T16:47+00:00",
527
     *      "share": false,
528
     *      "directory": true
529
     *  }
530
     * ]
531
     *
532
     * @param string $id
533
     * @param string $p
534
     * @param array  $attributes
535
     * @param bool   $self
536
     *
537
     * @return Response
538
     */
539
    public function getParents(?string $id = null, ?string $p = null, array $attributes = [], bool $self = false): Response
540
    {
541
        $result = [];
542
        $request = $this->_getNode($id, $p);
543
        $parents = $request->getParents();
544
545
        if (true === $self && $request instanceof Collection) {
546
            $result[] = $this->node_decorator->decorate($request, $attributes);
547
        }
548
549
        foreach ($parents as $node) {
550
            $result[] = $this->node_decorator->decorate($node, $attributes);
551
        }
552
553
        return (new Response())->setCode(200)->setBody($result);
554
    }
555
556
    /**
557
     * @api {patch} /api/v2/nodes/:id/meta Change meta attributes
558
     * @apiVersion 2.0.0
559
     * @apiName patchMeta
560
     * @apiGroup Node
561
     * @apiPermission none
562
     * @apiDescription Change meta attributes of a node
563
     * @apiUse _getNodes
564
     * @apiUse _multiError
565
     *
566
     * @apiParam (GET Parameter) {string} [attributes.description] UTF-8 Text Description - Can contain anything as long as it is a string
567
     * @apiParam (GET Parameter) {string} [attributes.color] Color Tag - Can contain anything as long as it is a string
568
     * @apiParam (GET Parameter) {string} [attributes.author] Author - Can contain anything as long as it is a string
569
     * @apiParam (GET Parameter) {string} [attributes.mail] Mail contact address - Can contain anything as long as it is a string
570
     * @apiParam (GET Parameter) {string} [attributes.license] License - Can contain anything as long as it is a string
571
     * @apiParam (GET Parameter) {string} [attributes.copyright] Copyright string - Can contain anything as long as it is a string
572
     * @apiParam (GET Parameter) {string[]} [attributes.tags] Tags - Must be an array full of strings
573
     *
574
     * @apiExample (cURL) example:
575
     * curl -XPOST "https://SERVER/api/v2/nodes/meta-attributes?id=544627ed3c58891f058b4686&author=peter.meier"
576
     * curl -XPOST "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/meta-attributes?author=example"
577
     * curl -XPOST "https://SERVER/api/v2/nodes/meta-attributes?p=/absolute/path/to/my/node?license=GPL-3.0"
578
     *
579
     * @apiSuccessExample {json} Success-Response:
580
     * HTTP/1.1 200
581
     *
582
     * @param array|string $id
583
     * @param array|string $p
584
     *
585
     * @return Response
586
     */
587
    public function patchMeta(array $attributes, ?string $id = null, ?string $p = null): Response
588
    {
589
        return $this->bulk($id, $p, function ($node) use ($attributes) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 587 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...
Bug introduced by
It seems like $p defined by parameter $p on line 587 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...
590
            $node->setMetaAttributes($attributes);
591
592
            return [
593
                'code' => 200,
594
                'data' => $this->node_decorator->decorate($node),
595
            ];
596
        });
597
    }
598
599
    /**
600
     * @api {post} /api/v2/nodes/:id/name Rename node
601
     * @apiVersion 2.0.0
602
     * @apiName postName
603
     * @apiGroup Node
604
     * @apiPermission none
605
     * @apiDescription Rename a node. The characters (\ < > : " / * ? |) (without the "()") are not allowed to use within a node name.
606
     * @apiUse _getNode
607
     * @apiUse _writeAction
608
     *
609
     * @apiParam (GET Parameter) {string} [name] The new name of the node
610
     * @apiError (Error 400) Exception name contains invalid characters
611
     *
612
     * @apiExample (cURL) example:
613
     * curl -XPOST "https://SERVER/api/v2/nodes/name?id=544627ed3c58891f058b4686&name=newname.txt"
614
     * curl -XPOST "https://SERVER/api/v2/nodes/544627ed3c58891f058b4677/name?name=newdir"
615
     * curl -XPOST "https://SERVER/api/v2/nodes/name?p=/absolute/path/to/my/node&name=newname.txt"
616
     *
617
     * @apiSuccessExample {json} Success-Response:
618
     * HTTP/1.1 200 OK
619
     *
620
     * @param string $id
621
     * @param string $p
622
     * @param string $name
623
     *
624
     * @return Response
625
     */
626
    public function postName(string $name, ?string $id = null, ?string $p = null): Response
627
    {
628
        $node = $this->_getNode($id, $p);
629
        $node->setName($name);
630
        $result = $this->node_decorator->decorate($node);
631
632
        return (new Response())->setCode(200)->setBody($result);
633
    }
634
635
    /**
636
     * @api {post} /api/v2/nodes/:id/clone Clone node
637
     * @apiVersion 2.0.0
638
     * @apiName postClone
639
     * @apiGroup Node
640
     * @apiPermission none
641
     * @apiDescription Clone a node
642
     * @apiUse _getNode
643
     * @apiUse _conflictNode
644
     * @apiUse _multiError
645
     * @apiUse _writeAction
646
     *
647
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
648
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
649
     *
650
     * @apiExample (cURL) example:
651
     * curl -XPOST "https://SERVER/api/v2/nodes/clone?id=544627ed3c58891f058b4686&dest=544627ed3c58891f058b4676"
652
     * curl -XPOST "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/clone?dest=544627ed3c58891f058b4676&conflict=2"
653
     * curl -XPOST "https://SERVER/api/v2/nodes/clone?p=/absolute/path/to/my/node&conflict=0&destp=/new/parent"
654
     *
655
     * @apiSuccessExample {json} Success-Response:
656
     * HTTP/1.1 201 Created
657
     *
658
     * @apiSuccessExample {json} Success-Response:
659
     * HTTP/1.1 200 OK
660
     *
661
     * @param array|string $id
662
     * @param array|string $id
663
     * @param array|string $p
664
     * @param string       $destid
665
     * @param string       $destp
666
     * @param int          $conflict
667
     *
668
     * @return Response
669
     */
670
    public function postClone(
671
        $id = null,
672
        $p = null,
673
        ?string $destid = null,
674
        ?string $destp = null,
675
        int $conflict = 0
676
    ): Response {
677
        try {
678
            $parent = $this->_getNode($destid, $destp, Collection::class, false, true);
679
        } catch (Exception\NotFound $e) {
680
            throw new Exception\NotFound(
681
                'destination collection was not found or is not a collection',
682
                Exception\NotFound::DESTINATION_NOT_FOUND
683
            );
684
        }
685
686
        return $this->bulk($id, $p, function ($node) use ($parent, $conflict) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 671 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...
Bug introduced by
It seems like $p defined by parameter $p on line 672 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...
687
            $parent = $node->getParent();
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $parent, 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...
688
            $result = $node->copyTo($parent, $conflict);
689
690
            return [
691
                'code' => $parent == $result ? 200 : 201,
692
                'data' => $this->node_decorator->decorate($result),
693
            ];
694
        });
695
    }
696
697
    /**
698
     * @api {post} /api/v2/nodes/:id/move Move node
699
     * @apiVersion 2.0.0
700
     * @apiName postMove
701
     * @apiGroup Node
702
     * @apiPermission none
703
     * @apiDescription Move node
704
     * @apiUse _getNodes
705
     * @apiUse _conflictNode
706
     * @apiUse _multiError
707
     * @apiUse _writeAction
708
     *
709
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
710
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
711
     *
712
     * @apiExample (cURL) example:
713
     * curl -XPOST "https://SERVER/api/v2/nodes/move?id=544627ed3c58891f058b4686?destid=544627ed3c58891f058b4655"
714
     * curl -XPOST "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/move?destid=544627ed3c58891f058b4655"
715
     * curl -XPOST "https://SERVER/api/v2/nodes/move?p=/absolute/path/to/my/node&destp=/new/parent&conflict=1
716
     *
717
     * @apiSuccessExample {json} Success-Response:
718
     * HTTP/1.1 204 No Content
719
     *
720
     * @apiSuccessExample {json} Success-Response (conflict=1):
721
     * HTTP/1.1 200 OK
722
     * {
723
     *      "status":200,
724
     *      "data": "renamed (xy23)"
725
     * }
726
     *
727
     * @param array|string $id
728
     * @param array|string $p
729
     * @param string       $destid
730
     * @param string       $destp
731
     * @param int          $conflict
732
     *
733
     * @return Response
734
     */
735
    public function postMove(
736
        $id = null,
737
        $p = null,
738
        ?string $destid = null,
739
        ?string $destp = null,
740
        int $conflict = 0
741
    ): Response {
742
        try {
743
            $parent = $this->_getNode($destid, $destp, Collection::class, false, true);
744
        } catch (Exception\NotFound $e) {
745
            throw new Exception\NotFound(
746
                'destination collection was not found or is not a collection',
747
                Exception\NotFound::DESTINATION_NOT_FOUND
748
            );
749
        }
750
751
        return $this->bulk($id, $p, function ($node) use ($parent, $conflict) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 736 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...
Bug introduced by
It seems like $p defined by parameter $p on line 737 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...
752
            $result = $node->setParent($parent, $conflict);
0 ignored issues
show
Unused Code introduced by
$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...
753
754
            return [
755
                'code' => 200,
756
                'data' => $this->node_decorator->decorate($node),
757
            ];
758
        });
759
    }
760
761
    /**
762
     * @api {delete} /api/v2/nodes/:id Delete node
763
     * @apiVersion 2.0.0
764
     * @apiName delete
765
     * @apiGroup Node
766
     * @apiPermission none
767
     * @apiDescription Delete node
768
     * @apiUse _getNodes
769
     * @apiUse _multiError
770
     * @apiUse _writeAction
771
     *
772
     * @apiParam (GET Parameter) {boolean} [force=false] Force flag need to be set to delete a node from trash (node must have the deleted flag)
773
     * @apiParam (GET Parameter) {boolean} [ignore_flag=false] If both ignore_flag and force_flag were set, the node will be deleted completely
774
     * @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
775
     *
776
     * @apiExample (cURL) example:
777
     * curl -XDELETE "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686"
778
     * curl -XDELETE "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686?force=1&ignore_flag=1"
779
     * curl -XDELETE "https://SERVER/api/v2/node?p=/absolute/path/to/my/node"
780
     *
781
     * @apiSuccessExample {json} Success-Response:
782
     * HTTP/1.1 204 No Content
783
     *
784
     * @param array|string $id
785
     * @param array|string $p
786
     * @param bool         $force
787
     * @param bool         $ignore_flag
788
     * @param int          $at
789
     *
790
     * @return Response
791
     */
792
    public function delete(
793
        $id = null,
794
        $p = null,
795
        bool $force = false,
796
        bool $ignore_flag = false,
797
        ?string $at = null
798
    ): Response {
799
        $failures = [];
0 ignored issues
show
Unused Code introduced by
$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...
800
801
        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...
802
            $at = $this->_verifyAttributes(['destroy' => $at])['destroy'];
803
        }
804
805
        return $this->bulk($id, $p, function ($node) use ($force, $ignore_flag, $at) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 793 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...
Bug introduced by
It seems like $p defined by parameter $p on line 794 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...
806
            if (null === $at) {
807
                $node->delete($force && $node->isDeleted() || $force && $ignore_flag);
808
            } else {
809
                if ('0' === $at) {
810
                    $at = null;
0 ignored issues
show
Bug introduced by
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...
811
                }
812
                $node->setDestroyable($at);
813
            }
814
815
            return [
816
                'code' => 204,
817
            ];
818
        });
819
    }
820
821
    /**
822
     * @api {get} /api/v2/nodes/trash Get trash
823
     * @apiName getTrash
824
     * @apiVersion 2.0.0
825
     * @apiGroup Node
826
     * @apiPermission none
827
     * @apiDescription A similar endpoint to /api/v2/nodes/query filer={'deleted': {$type: 9}] but instead returning all deleted
828
     * nodes (including children which are deleted as well) this enpoint only returns the first deleted node from every subtree)
829
     * @apiUse _nodeAttributes
830
     *
831
     * @apiExample (cURL) example:
832
     * curl -XGET https://SERVER/api/v2/nodes/trash?pretty
833
     *
834
     * @apiParam (GET Parameter) {string[]} [attributes] Filter node attributes
835
     *
836
     * @apiSuccess (200 OK) {object[]} - List of deleted nodes
837
     * @apiSuccessExample {json} Success-Response:
838
     * HTTP/1.1 200 OK
839
     * [
840
     *  {
841
     *  }
842
     * ]
843
     *
844
     * @param array $attributes
845
     * @param int   $offset
846
     * @param int   $limit
847
     *
848
     * @return Response
849
     */
850
    public function getTrash(array $attributes = [], int $offset = 0, int $limit = 20): Response
851
    {
852
        $children = [];
853
        $nodes = $this->fs->findNodesByFilterUser(NodeInterface::DELETED_ONLY, ['deleted' => ['$type' => 9]], $offset, $limit);
854
855
        foreach ($nodes as $node) {
856
            try {
857
                $parent = $node->getParent();
858
                if (null !== $parent && $parent->isDeleted()) {
859
                    continue;
860
                }
861
            } catch (\Exception $e) {
862
                //skip exception
863
            }
864
865
            $children[] = $node;
866
        }
867
868
        if ($this instanceof ApiFile) {
869
            $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...
870
            $uri = '/api/v2/files';
871
        } elseif ($this instanceof ApiCollection) {
872
            $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...
873
            $uri = '/api/v2/collections';
874
        } else {
875
            $uri = '/api/v2/nodes';
876
        }
877
878
        $pager = new Pager($this->node_decorator, $children, $attributes, $offset, $limit, $uri, $nodes->getReturn());
0 ignored issues
show
Documentation introduced by
$children is of type array, but the function expects a object<Balloon\AttributeDecorator\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
879
        $result = $pager->paging();
880
881
        return (new Response())->setCode(200)->setBody($result);
882
    }
883
884
    /**
885
     * @api {get} /api/v2/nodes/delta Get delta
886
     * @apiVersion 2.0.0
887
     * @apiName getDelta
888
     * @apiGroup Node
889
     * @apiPermission none
890
     * @apiUse _getNode
891
     *
892
     * @apiDescription Use this method to request a delta feed with all changes on the server (or) a snapshot of the server state.
893
     * 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.
894
     * If has_more is TRUE you need to request /delta immediatly again to
895
     * receive the next bunch of deltas. If has_more is FALSE you should wait at least 120s seconds before any further requests to the
896
     * 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).
897
     * 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
898
     * without a cursor. reset could be TRUE if there was an account maintenance or a simialar case.
899
     * You can request a different limit as well but be aware that the number of nodes could be slighty different from your requested limit.
900
     * If requested with parameter id or p the delta gets generated recursively from the node given.
901
     *
902
     * @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
903
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default not all attributes would be returned
904
     * @apiParam (GET Parameter) {string} [cursor=null] Set a cursor to rquest next nodes within delta processing
905
     *
906
     * @apiExample (cURL) example:
907
     * curl -XGET "https://SERVER/api/v2/nodes/delta?pretty"
908
     *
909
     * @apiSuccess (200 OK) {boolean} reset If true the local state needs to be reseted, is alway TRUE during
910
     * the first request to /delta without a cursor or in special cases like server or account maintenance
911
     * @apiSuccess (200 OK) {string} cursor The cursor needs to be stored and reused to request further deltas
912
     * @apiSuccess (200 OK) {boolean} has_more If has_more is TRUE /delta can be requested immediatly after the last request
913
     * to receive further delta. If it is FALSE we should wait at least 120 seconds before any further delta requests to the api endpoint
914
     * @apiSuccess (200 OK) {object[]} nodes Node list to process
915
     * @apiSuccessExample {json} Success-Response:
916
     * HTTP/1.1 200 OK
917
     * {
918
     *      "reset": false,
919
     *      "cursor": "aW5pdGlhbHwxMDB8NTc1YTlhMGIzYzU4ODkwNTE0OGI0NTZifDU3NWE5YTBiM2M1ODg5MDUxNDhiNDU2Yw==",
920
     *      "has_more": false,
921
     *       "nodes": [
922
     *          {
923
     *              "id": "581afa783c5889ad7c8b4572",
924
     *              "deleted": " 2008-08-31T16:47+00:00",
925
     *              "changed": "2007-08-31T16:47+00:00",
926
     *              "path": "\/AAA\/AL",
927
     *              "directory": true
928
     *          },
929
     *          {
930
     *              "id": "581afa783c5889ad7c8b3dcf",
931
     *              "created": "2007-08-31T16:47+00:00",
932
     *              "changed": "2007-09-28T12:33+00:00",
933
     *              "path": "\/AL",
934
     *              "directory": true
935
     *          }
936
     *      ]
937
     * }
938
     *
939
     * @param DeltaAttributeDecorator $delta_decorator
940
     * @param string                  $id
941
     * @param string                  $p
942
     * @param string                  $cursor
943
     * @param int                     $limit
944
     * @param array                   $attributes
945
     *
946
     * @return Response
947
     */
948
    public function getDelta(
949
        DeltaAttributeDecorator $delta_decorator,
950
        ?string $id = null,
951
        ?string $p = null,
952
        ?string $cursor = null,
953
        int $limit = 250,
954
        array $attributes = []
955
    ): Response {
956
        if (null !== $id || null !== $p) {
957
            $node = $this->_getNode($id, $p);
958
        } else {
959
            $node = null;
960
        }
961
962
        $result = $this->fs->getDelta()->getDeltaFeed($cursor, $limit, $node);
963
        foreach ($result['nodes'] as &$node) {
964
            if ($node instanceof NodeInterface) {
965
                $node = $this->node_decorator->decorate($node, $attributes);
966
            } else {
967
                $node = $delta_decorator->decorate($node, $attributes);
968
            }
969
        }
970
971
        return (new Response())->setCode(200)->setBody($result);
972
    }
973
974
    /**
975
     * @api {get} /api/v2/nodes/:id/event-log Event log
976
     * @apiVersion 2.0.0
977
     * @apiName getEventLog
978
     * @apiGroup Node
979
     * @apiPermission none
980
     * @apiUse _getNode
981
     * @apiDescription Get detailed event log
982
     * Request all modifications which are made by the user himself or share members.
983
     * Possible operations are the follwing:
984
     * - deleteCollectionReference
985
     * - deleteCollectionShare
986
     * - deleteCollection
987
     * - addCollection
988
     * - addFile
989
     * - addCollectionShare
990
     * - addCollectionReference
991
     * - undeleteFile
992
     * - undeleteCollectionReference
993
     * - undeleteCollectionShare
994
     * - restoreFile
995
     * - renameFile
996
     * - renameCollection
997
     * - renameCollectionShare
998
     * - renameCollectionRFeference
999
     * - copyFile
1000
     * - copyCollection
1001
     * - copyCollectionShare
1002
     * - copyCollectionRFeference
1003
     * - moveFile
1004
     * - moveCollection
1005
     * - moveCollectionReference
1006
     * - moveCollectionShare
1007
     *
1008
     * @apiExample (cURL) example:
1009
     * curl -XGET "https://SERVER/api/v2/nodes/event-log?pretty"
1010
     * curl -XGET "https://SERVER/api/v2/nodes/event-log?id=544627ed3c58891f058b4686&pretty"
1011
     * curl -XGET "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/event-log?pretty&limit=10"
1012
     * curl -XGET "https://SERVER/api/v2/nodes/event-log?p=/absolute/path/to/my/node&pretty"
1013
     *
1014
     * @apiParam (GET Parameter) {number} [limit=100] Sets limit of events to be returned
1015
     * @apiParam (GET Parameter) {number} [skip=0] How many events are skiped (useful for paging)
1016
     *
1017
     * @apiSuccess (200 OK) {object[]} - List of events
1018
     * @apiSuccess (200 OK) {number} -.event Event ID
1019
     * @apiSuccess (200 OK) {object} -.timestamp ISO8601 timestamp when the event occured
1020
     * @apiSuccess (200 OK) {string} -.operation event operation (like addCollection, deleteFile, ...)
1021
     * @apiSuccess (200 OK) {object} -.parent Parent node object at the time of the event
1022
     * @apiSuccess (200 OK) {object} -.previous Previous state of actual data which has been modified during an event, can contain either version, name or parent
1023
     * @apiSuccess (200 OK) {number} -.previous.version Version at the time before the event
1024
     * @apiSuccess (200 OK) {string} -.previous.name Name at the time before the event
1025
     * @apiSuccess (200 OK) {object} -.previous.parent Parent node object at the time before the event
1026
     * @apiSuccess (200 OK) {object} -.share shared collection object at the time of the event (If the node was part of a share)
1027
     * @apiSuccess (200 OK) {string} -.name Name of the node at the time of the event
1028
     * @apiSuccess (200 OK) {object} -.node Current node object
1029
     * @apiSuccess (200 OK) {object} -.user User who executed an event
1030
     *
1031
     * @apiSuccessExample {json} Success-Response:
1032
     * HTTP/1.1 200 OK
1033
     * [
1034
     *  {
1035
     *      "id": "57628e523c5889026f8b4570",
1036
     *      "timestamp": " 2018-01-02T13:22+00:00",
1037
     *      "operation": "restoreFile",
1038
     *      "name": "file.txt",
1039
     *      "previous": {
1040
     *          "version": 16
1041
     *      },
1042
     *      "node": {
1043
     *          "id": "558c0b273c588963078b457a",
1044
     *          "name": "3dddsceheckfile.txt",
1045
     *          "deleted": false
1046
     *      },
1047
     *      "parent": null,
1048
     *      "user": {
1049
     *          "id": "54354cb63c58891f058b457f",
1050
     *          "username": "example"
1051
     *      }
1052
     *  }
1053
     * ]
1054
     *
1055
     * @param EventAttributeDecorator $event_decorator
1056
     * @param string                  $id
1057
     * @param string                  $p
1058
     * @param int                     $offset
1059
     * @param int                     $limit
1060
     *
1061
     * @return Response
1062
     */
1063
    public function getEventLog(EventAttributeDecorator $event_decorator, ?string $id = null, ?string $p = null, ?array $attributes = [], int $offset = 0, int $limit = 20): Response
1064
    {
1065
        if (null !== $id || null !== $p) {
1066
            $node = $this->_getNode($id, $p);
1067
            $uri = '/api/v2/nodes/'.$node->getId().'/event-log';
1068
        } else {
1069
            $node = null;
1070
            $uri = '/api/v2/nodes/event-log';
1071
        }
1072
1073
        $result = $this->fs->getDelta()->getEventLog($limit, $offset, $node, $total);
1074
        $pager = new Pager($event_decorator, $result, $attributes, $offset, $limit, $uri, $total);
1075
1076
        return (new Response())->setCode(200)->setBody($pager->paging());
1077
    }
1078
1079
    /**
1080
     * @api {get} /api/v2/nodes/last-cursor Get last Cursor
1081
     * @apiVersion 2.0.0
1082
     * @apiName geLastCursor
1083
     * @apiGroup Node
1084
     * @apiUse _getNode
1085
     * @apiPermission none
1086
     * @apiDescription Use this method to request the latest cursor if you only need to now
1087
     * if there are changes on the server. This method will not return any other data than the
1088
     * newest cursor. To request a feed with all deltas request /delta.
1089
     *
1090
     * @apiExample (cURL) example:
1091
     * curl -XGET "https://SERVER/api/v2/nodes/last-cursor?pretty"
1092
     *
1093
     * @apiSuccess (200 OK) {string} cursor v2 cursor
1094
     * @apiSuccessExample {json} Success-Response:
1095
     * HTTP/1.1 200 OK
1096
     * "aW5pdGlhbHwxMDB8NTc1YTlhMGIzYzU4ODkwNTE0OGI0NTZifDU3NWE5YTBiM2M1ODg5MDUxNDhiNDU2Yw=="
1097
     *
1098
     * @param string $id
1099
     * @param string $p
1100
     *
1101
     * @return Response
1102
     */
1103
    public function getLastCursor(?string $id = null, ?string $p = null): Response
1104
    {
1105
        if (null !== $id || null !== $p) {
1106
            $node = $this->_getNode($id, $p);
0 ignored issues
show
Unused Code introduced by
$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...
1107
        } else {
1108
            $node = null;
0 ignored issues
show
Unused Code introduced by
$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...
1109
        }
1110
1111
        $result = $this->fs->getDelta()->getLastCursor();
1112
1113
        return (new Response())->setCode(200)->setBody($result);
1114
    }
1115
1116
    /**
1117
     * Merge multiple nodes into one zip archive.
1118
     *
1119
     * @param string $id
1120
     * @param string $path
1121
     * @param string $name
1122
     */
1123
    protected function combine($id = null, $path = null, string $name = 'selected')
1124
    {
1125
        $archive = new ZipStream($name.'.zip');
1126
1127
        foreach ($this->_getNodes($id, $path) as $node) {
1128
            try {
1129
                $node->zip($archive);
1130
            } catch (\Exception $e) {
1131
                $this->logger->debug('failed zip node in multi node request ['.$node->getId().']', [
1132
                   'category' => get_class($this),
1133
                   'exception' => $e,
1134
               ]);
1135
            }
1136
        }
1137
1138
        $archive->finalize();
0 ignored issues
show
Bug introduced by
The method finalize() does not seem to exist on object<ZipStream\ZipStream>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1139
    }
1140
1141
    /**
1142
     * Check custom node attributes which have to be written.
1143
     *
1144
     * @param array $attributes
1145
     *
1146
     * @return array
1147
     */
1148
    protected function _verifyAttributes(array $attributes): array
1149
    {
1150
        $valid_attributes = [
1151
            'changed',
1152
            'destroy',
1153
            'created',
1154
            'meta',
1155
            'readonly',
1156
        ];
1157
1158
        if ($this instanceof ApiCollection) {
1159
            $valid_attributes[] = 'filter';
1160
        }
1161
1162
        $check = array_merge(array_flip($valid_attributes), $attributes);
1163
1164
        if ($this instanceof ApiCollection && count($check) > 6) {
1165
            throw new Exception\InvalidArgument('Only changed, created, destroy timestamp, filter, readonly and/or meta attributes may be overwritten');
1166
        }
1167
        if ($this instanceof ApiFile && count($check) > 5) {
1168
            throw new Exception\InvalidArgument('Only changed, created, destroy timestamp, readonly and/or meta attributes may be overwritten');
1169
        }
1170
1171
        foreach ($attributes as $attribute => $value) {
1172
            switch ($attribute) {
1173
                case 'filter':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1174
                    $attributes['filter'] = json_encode((array) $attributes['filter']);
1175
1176
                break;
1177
                case 'destroy':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1178
                    if (!Helper::isValidTimestamp($value)) {
1179
                        throw new Exception\InvalidArgument($attribute.' Changed timestamp must be valid unix timestamp');
1180
                    }
1181
                    $attributes[$attribute] = new UTCDateTime($value.'000');
1182
1183
                break;
1184
                case 'changed':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1185
                case 'created':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1186
                    if (!Helper::isValidTimestamp($value)) {
1187
                        throw new Exception\InvalidArgument($attribute.' Changed timestamp must be valid unix timestamp');
1188
                    }
1189
                    if ((int) $value > time()) {
1190
                        throw new Exception\InvalidArgument($attribute.' timestamp can not be set greater than the server time');
1191
                    }
1192
                    $attributes[$attribute] = new UTCDateTime($value.'000');
1193
1194
                break;
1195
                case 'readonly':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1196
                    $attributes['readonly'] = (bool) $attributes['readonly'];
1197
1198
                break;
1199
            }
1200
        }
1201
1202
        return $attributes;
1203
    }
1204
}
1205