Completed
Branch dev (d5d70c)
by Raffael
11:00
created

Nodes::postName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 3
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\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...
18
use Balloon\Filesystem;
19
use Balloon\Filesystem\DeltaAttributeDecorator;
20
use Balloon\Filesystem\EventAttributeDecorator;
21
use Balloon\Filesystem\Node\AttributeDecorator as NodeAttributeDecorator;
22
use Balloon\Filesystem\Node\Collection;
23
use Balloon\Filesystem\Node\File;
24
use Balloon\Filesystem\Node\NodeInterface;
25
use Balloon\Helper;
26
use Balloon\Server;
27
use Balloon\Server\User;
28
use Closure;
29
use Generator;
30
use Micro\Http\Response;
31
use MongoDB\BSON\UTCDateTime;
32
use Psr\Log\LoggerInterface;
33
use ZipStream\ZipStream;
34
35
class Nodes extends Controller
36
{
37
    /**
38
     * Filesystem.
39
     *
40
     * @var Filesystem
41
     */
42
    protected $fs;
43
44
    /**
45
     * LoggerInterface.
46
     *
47
     * @var LoggerInterface
48
     */
49
    protected $logger;
50
51
    /**
52
     * Server.
53
     *
54
     * @var Server
55
     */
56
    protected $server;
57
58
    /**
59
     * User.
60
     *
61
     * @var User
62
     */
63
    protected $user;
64
65
    /**
66
     * Node attribute decorator.
67
     *
68
     * @var NodeAttributeDecorator
69
     */
70
    protected $node_decorator;
71
72
    /**
73
     * Initialize.
74
     *
75
     * @param Server                 $server
76
     * @param NodeAttributeDecorator $decorator
77
     * @param LoggerInterface        $logger
78
     */
79
    public function __construct(Server $server, NodeAttributeDecorator $decorator, LoggerInterface $logger)
80
    {
81
        $this->fs = $server->getFilesystem();
82
        $this->user = $server->getIdentity();
83
        $this->server = $server;
84
        $this->node_decorator = $decorator;
85
        $this->logger = $logger;
86
    }
87
88
    /**
89
     * @api {head} /api/v2/node/:id Node exists?
90
     * @apiVersion 2.0.0
91
     * @apiName head
92
     * @apiGroup Node
93
     * @apiPermission none
94
     * @apiDescription Check if a node exists. Per default deleted nodes are ignore which means it will
95
     *  return a 404 if a deleted node is requested. You can change this behaviour via the deleted parameter.
96
     * @apiUse _getNode
97
     *
98
     * @apiExample (cURL) example:
99
     * curl -XHEAD "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686"
100
     * curl -XHEAD "https://SERVER/api/v2/node/544627ed3c58891f058b4686"
101
     * curl -XHEAD "https://SERVER/api/v2/node?p=/absolute/path/to/my/node"
102
     *
103
     * @apiParam (GET Parameter) {number} [deleted=0] Wherever include deleted node or not, possible values:</br>
104
     * - 0 Exclude deleted</br>
105
     * - 1 Only deleted</br>
106
     * - 2 Include deleted</br>
107
     *
108
     * @apiSuccessExample {json} Success-Response (Node does exist):
109
     * HTTP/1.1 200 OK
110
     *
111
     * @apiSuccessExample {json} Success-Response (Node does not exist):
112
     * HTTP/1.1 404 Not Found
113
     *
114
     * @param string $id
115
     * @param string $p
116
     * @param int    $deleted
117
     *
118
     * @return Response
119
     */
120
    public function head(?string $id = null, ?string $p = null, int $deleted = 0): Response
121
    {
122
        try {
123
            $result = $this->_getNode($id, $p, null, false, false, $deleted);
124
125
            $response = (new Response())
126
                ->setHeader('Content-Length', (string) $result->getSize())
127
                ->setHeader('Content-Type', $result->getContentType())
128
                ->setCode(200);
129
130
            return $response;
131
        } catch (\Exception $e) {
132
            return (new Response())->setCode(404);
133
        }
134
    }
135
136
    /**
137
     * @api {post} /api/v2/node/:id/undelete Restore node
138
     * @apiVersion 2.0.0
139
     * @apiName postUndelete
140
     * @apiGroup Node
141
     * @apiPermission none
142
     * @apiDescription Undelete (Restore from trash) a single node or multiple ones.
143
     * @apiUse _getNodes
144
     * @apiUse _conflictNode
145
     * @apiUse _multiError
146
     * @apiUse _writeAction
147
     *
148
     * @apiExample (cURL) example:
149
     * curl -XPOST "https://SERVER/api/v2/node/undelete?id[]=544627ed3c58891f058b4686&id[]=544627ed3c58891f058b46865&pretty"
150
     * curl -XPOST "https://SERVER/api/v2/node/undelete?id=544627ed3c58891f058b4686?pretty"
151
     * curl -XPOST "https://SERVER/api/v2/node/544627ed3c58891f058b4686/undelete?conflict=2"
152
     * curl -XPOST "https://SERVER/api/v2/node/undelete?p=/absolute/path/to/my/node&conflict=0&move=1&destid=544627ed3c58891f058b46889"
153
     *
154
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
155
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
156
     *
157
     * @apiSuccessExample {json} Success-Response (conflict=1):
158
     * HTTP/1.1 200 OK
159
     * {
160
     *      "id": "544627ed3c58891f058b4686",
161
     *      "name": "renamed (xy23)"
162
     * }
163
     *
164
     * @param array|string $id
165
     * @param array|string $p
166
     * @param bool         $move
167
     * @param string       $destid
168
     * @param string       $destp
169
     * @param int          $conflict
170
     */
171
    public function postUndelete(
172
        $id = null,
173
        $p = null,
174
        bool $move = false,
175
        ?string $destid = null,
176
        ?string $destp = null,
177
        int $conflict = 0
178
    ): Response {
179
        $parent = null;
180
        if (true === $move) {
181
            try {
182
                $parent = $this->_getNode($destid, $destp, 'Collection', false, true);
183
            } catch (Exception\NotFound $e) {
184
                throw new Exception\NotFound(
185
                    'destination collection was not found or is not a collection',
186
                    Exception\NotFound::DESTINATION_NOT_FOUND
187
                );
188
            }
189
        }
190
191
        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 172 can also be of type null; however, Balloon\App\Api\v2\Nodes::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 173 can also be of type null; however, Balloon\App\Api\v2\Nodes::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...
192
            if (true === $move) {
193
                $node = $node->setParent($parent, $conflict);
194
            }
195
196
            if ($node->isDeleted()) {
197
                $node->undelete($conflict);
198
            }
199
200
            return [
201
                'code' => 200,
202
                'data' => $this->node_decorator->decorate($node),
203
            ];
204
        });
205
    }
206
207
    /**
208
     * @api {get} /api/v2/node/:id/content Download stream
209
     * @apiVersion 2.0.0
210
     * @apiName getContent
211
     * @apiGroup Node
212
     * @apiPermission none
213
     * @apiDescription Download node contents. Collections are zipped during streaming.
214
     * @apiUse _getNode
215
     *
216
     * @apiParam (GET Parameter) {number} [offset=0] Read stream from a specific offset in bytes
217
     * @apiParam (GET Parameter) {number} [length=0] Read stream until a specific limit in bytes
218
     * @apiParam (GET Parameter) {string} [encode] Can be set to base64 to encode content as base64.
219
     * @apiParam (GET Parameter) {boolean} [download=false] Force download file (Content-Disposition: attachment HTTP header)
220
     *
221
     * @apiExample (cURL) example:
222
     * curl -XGET "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686" > myfile.txt
223
     * curl -XGET "https://SERVER/api/v2/node/544627ed3c58891f058b4686" > myfile.txt
224
     * curl -XGET "https://SERVER/api/v2/node?p=/absolute/path/to/my/collection" > folder.zip
225
     *
226
     * @apiSuccessExample {string} Success-Response (encode=base64):
227
     * HTTP/1.1 200 OK
228
     *
229
     * @apiSuccessExample {binary} Success-Response:
230
     * HTTP/1.1 200 OK
231
     *
232
     * @apiErrorExample {json} Error-Response (Invalid offset):
233
     * HTTP/1.1 400 Bad Request
234
     * {
235
     *      "status": 400,
236
     *      "data": {
237
     *          "error": "Balloon\\Exception\\Conflict",
238
     *          "message": "invalid offset requested",
239
     *          "code": 277
240
     *      }
241
     * }
242
     *
243
     * @param array|string $id
244
     * @param array|string $p
245
     * @param int          $offset
246
     * @param int          $legnth
0 ignored issues
show
Bug introduced by
There is no parameter named $legnth. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
247
     * @param string       $encode
248
     * @param bool         $download
249
     * @param string       $name
250
     */
251
    public function getContent(
252
        $id = null,
253
        $p = null,
254
        int $offset = 0,
255
        int $length = 0,
256
        ?string $encode = null,
257
        bool $download = false,
258
        string $name = 'selected'
259
    ): ?Response {
260
        if (is_array($id) || is_array($p)) {
261
            return $this->combine($id, $p, $name);
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id 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...
Bug introduced by
It seems like $p defined by parameter $p on line 253 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...
262
        }
263
264
        $node = $this->_getNode($id, $p);
265
        if ($node instanceof Collection) {
266
            return $node->getZip();
267
        }
268
269
        $response = new Response();
270
271
        if (true === $download) {
272
            $response->setHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\''.rawurlencode($node->getName()));
273
            $response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
274
            $response->setHeader('Content-Type', 'application/octet-stream');
275
            $response->setHeader('Content-Length', (string) $node->getSize());
276
            $response->setHeader('Content-Transfer-Encoding', 'binary');
277
        } else {
278
            $response->setHeader('Content-Disposition', 'inline; filename*=UTF-8\'\''.rawurlencode($node->getName()));
279
            $response->setHeader('Content-Type', $node->getContentType());
280
        }
281
282
        return $response
283
          ->setOutputFormat(null)
284
          ->setBody(function () use ($node, $encode, $offset, $length) {
285
              $stream = $node->get();
286
              $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...
287
288
              if (null === $stream) {
289
                  echo '';
290
291
                  return;
292
              }
293
294
              if (0 !== $offset) {
295
                  if (fseek($stream, $offset) === -1) {
296
                      throw new Exception\Conflict(
297
                        'invalid offset requested',
298
                        Exception\Conflict::INVALID_OFFSET
299
                    );
300
                  }
301
              }
302
303
              $read = 0;
304
              if ('base64' === $encode) {
305
                  header('Content-Encoding: base64');
306
                  while (!feof($stream)) {
307
                      if (0 !== $length && $read + 8192 > $length) {
308
                          echo base64_encode(fread($stream, $length - $read));
309
                          exit();
310
                      }
311
312
                      echo base64_encode(fread($stream, 8192));
313
                      $read += 8192;
314
                  }
315
              } else {
316
                  while (!feof($stream)) {
317
                      if (0 !== $length && $read + 8192 > $length) {
318
                          echo fread($stream, $length - $read);
319
                          exit();
320
                      }
321
322
                      echo fread($stream, 8192);
323
                      $read += 8192;
324
                  }
325
326
                  exit();
327
              }
328
          });
329
    }
330
331
    /**
332
     * @api {post} /api/v2/node/:id/readonly Set readonly
333
     * @apiVersion 2.0.0
334
     * @apiName postReadonly
335
     * @apiGroup Node
336
     * @apiPermission none
337
     * @apiDescription Set (or unset) node as readonly
338
     * @apiUse _getNodes
339
     * @apiUse _multiError
340
     * @apiUse _writeAction
341
     *
342
     * @apiExample (cURL) example:
343
     * curl -XPOST "https://SERVER/api/v2/node/readonly?id[]=544627ed3c58891f058b4686&id[]=544627ed3c58891f058b46865&readonly=1"
344
     * curl -XPOST "https://SERVER/api/v2/node/544627ed3c58891f058b4686/readonly?readonly=0"
345
     * curl -XPOST "https://SERVER/api/v2/node/readonly?p=/absolute/path/to/my/node"
346
     *
347
     * @apiParam (GET Parameter) {bool} [readonly=true] Set readonly to false to make node writeable again
348
     *
349
     * @apiSuccessExample {json} Success-Response:
350
     * HTTP/1.1 204 No Content
351
     *
352
     * @param array|string $id
353
     * @param array|string $p
354
     *
355
     * @return Response
356
     */
357
    public function postReadonly($id = null, $p = null, bool $readonly = true): Response
358
    {
359
        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 357 can also be of type null; however, Balloon\App\Api\v2\Nodes::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 357 can also be of type null; however, Balloon\App\Api\v2\Nodes::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...
360
            $node->setReadonly($readonly);
361
362
            return ['code' => 204];
363
        });
364
    }
365
366
    /**
367
     * @apiDefine _nodeAttributes
368
     *
369
     * @apiSuccess (200 OK) {string} id Unique node id
370
     * @apiSuccess (200 OK) {string} name Name
371
     * @apiSuccess (200 OK) {string} hash MD5 content checksum (file only)
372
     * @apiSuccess (200 OK) {object} meta Extended meta attributes
373
     * @apiSuccess (200 OK) {string} meta.description UTF-8 Text Description
374
     * @apiSuccess (200 OK) {string} meta.color Color Tag (HEX) (Like: #000000)
375
     * @apiSuccess (200 OK) {string} meta.author Author
376
     * @apiSuccess (200 OK) {string} meta.mail Mail contact address
377
     * @apiSuccess (200 OK) {string} meta.license License
378
     * @apiSuccess (200 OK) {string} meta.copyright Copyright string
379
     * @apiSuccess (200 OK) {string[]} meta.tags Search Tags
380
     * @apiSuccess (200 OK) {number} size Size in bytes (file only), number of children if collection
381
     * @apiSuccess (200 OK) {string} mime Mime type
382
     * @apiSuccess (200 OK) {boolean} sharelink Is node shared?
383
     * @apiSuccess (200 OK) {number} version File version (file only)
384
     * @apiSuccess (200 OK) {mixed} deleted Is boolean false if not deleted, if deleted it contains a deleted timestamp
385
     * @apiSuccess (200 OK) {string} deleted ISO8601 timestamp, only set if node is deleted
386
     * @apiSuccess (200 OK) {string} changed ISO8601 timestamp
387
     * @apiSuccess (200 OK) {string} created ISO8601 timestamp
388
     * @apiSuccess (200 OK) {string} destroy ISO8601 timestamp, only set if node has a destroy timestamp set
389
     * @apiSuccess (200 OK) {boolean} share Node is shared
390
     * @apiSuccess (200 OK) {boolean} directory Is true if the node is a collection
391
     * @apiSuccess (200 OK) {string} access Access permission for the authenticated user (d/r/rw/m)
392
     * @apiSuccess (200 OK) {object} shareowner Share owner
393
     * @apiSuccess (200 OK) {object} parent Parent node
394
     * @apiSuccess (200 OK) {string} path Absolute node path
395
     * @apiSuccess (200 OK) {string} filter Node is filtered (collection only)
396
     * @apiSuccess (200 OK) {boolean} readonly Readonly
397
     *
398
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes
399
     *
400
     * @param null|mixed $id
401
     * @param null|mixed $p
402
     */
403
404
    /**
405
     * @api {get} /api/v2/node/:id Get attributes
406
     * @apiVersion 2.0.0
407
     * @apiName get
408
     * @apiGroup Node
409
     * @apiPermission none
410
     * @apiDescription Get attributes from one or multiple nodes
411
     * @apiUse _getNode
412
     * @apiUse _nodeAttributes
413
     *
414
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default only a bunch of attributes would be returned, if you
415
     * need other attributes you have to request them (for example "path")
416
     *
417
     * @apiExample (cURL) example:
418
     * curl -XGET "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686&pretty"
419
     * curl -XGET "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted&pretty"
420
     * curl -XGET "https://SERVER/api/v2/node/544627ed3c58891f058b4686?pretty"
421
     * curl -XGET "https://SERVER/api/v2/node?p=/absolute/path/to/my/node&pretty"
422
     *
423
     * @apiSuccessExample {json} Success-Response:
424
     * HTTP/1.1 200 OK
425
     * {
426
     *      "id": "544627ed3c58891f058b4686",
427
     *      "name": "api.php",
428
     *      "hash": "a77f23ed800fd7a600a8c2cfe8cc370b",
429
     *      "meta": {
430
     *          "license": "GPLv3"
431
     *      },
432
     *      "size": 178,
433
     *      "mime": "text\/plain",
434
     *      "sharelink": true,
435
     *      "version": 1,
436
     *      "changed": "2007-08-31T16:47+00:00",
437
     *      "created": "2007-08-31T16:47+00:00",
438
     *      "share": false,
439
     *      "directory": false
440
     * }
441
     *
442
     * @param array|string $id
443
     * @param array|string $p
444
     * @param array        $attributes
445
     *
446
     * @return Response
447
     */
448
    public function get($id = null, $p = null, array $attributes = []): Response
449
    {
450
        if (is_array($id) || is_array($p)) {
451
            $nodes = [];
452
            foreach ($this->_getNodes($id, $p) as $node) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 448 can also be of type array; however, Balloon\App\Api\v2\Nodes::_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 448 can also be of type array; however, Balloon\App\Api\v2\Nodes::_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...
453
                $nodes[] = $this->node_decorator->decorate($node, $attributes);
454
            }
455
456
            return (new Response())->setCode(200)->setBody($nodes);
457
        }
458
459
        $result = $this->node_decorator->decorate($this->_getNode($id, $p), $attributes);
460
461
        return (new Response())->setCode(200)->setBody($result);
462
    }
463
464
    /**
465
     * @api {get} /api/v2/node/:id/parents Get parent nodes
466
     * @apiVersion 2.0.0
467
     * @apiName getParents
468
     * @apiGroup Node
469
     * @apiPermission none
470
     * @apiDescription Get system attributes of all parent nodes. The hirarchy of all parent nodes is ordered in a
471
     * single level array beginning with the collection on the highest level.
472
     * @apiUse _getNode
473
     * @apiUse _nodeAttributes
474
     *
475
     * @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)
476
     *
477
     * @apiExample (cURL) example:
478
     * curl -XGET "https://SERVER/api/v2/node/parents?id=544627ed3c58891f058b4686&pretty"
479
     * curl -XGET "https://SERVER/api/v2/node/parents?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted&pretty"
480
     * curl -XGET "https://SERVER/api/v2/node/544627ed3c58891f058b4686/parents?pretty&self=1"
481
     * curl -XGET "https://SERVER/api/v2/node/parents?p=/absolute/path/to/my/node&self=1"
482
     *
483
     * @apiSuccessExample {json} Success-Response:
484
     * HTTP/1.1 200 OK
485
     * [
486
     *  {
487
     *      "id": "544627ed3c58891f058bbbaa",
488
     *      "name": "rootdir",
489
     *      "meta": {},
490
     *      "size": 1,
491
     *      "mime": "inode/directory",
492
     *      "created": "2007-08-31T16:47+00:00",
493
     *      "changed": "2007-08-31T16:47+00:00",
494
     *      "destroy": "2020-08-31T16:47+00:00",
495
     *      "share": false,
496
     *      "directory": true
497
     *  },
498
     *  {
499
     *      "id": "544627ed3c58891f058b46cc",
500
     *      "name": "parentdir",
501
     *      "meta": {},
502
     *      "size": 3,
503
     *      "mime": "inode/directory",
504
     *      "created": "2007-08-31T16:47+00:00",
505
     *      "changed": "2007-08-31T16:47+00:00",
506
     *      "share": false,
507
     *      "directory": true
508
     *  }
509
     * ]
510
     *
511
     * @param string $id
512
     * @param string $p
513
     * @param array  $attributes
514
     *
515
     * @return Response
516
     */
517
    public function getParents(?string $id = null, ?string $p = null, array $attributes = [], bool $self = false): Response
518
    {
519
        $request = $this->_getNode($id, $p);
520
        $parents = $request->getParents();
521
        $result = [];
522
523
        if (true === $self && $request instanceof Collection) {
524
            $result[] = $this->node_decorator->decorate($request, $attributes);
525
        }
526
527
        foreach ($parents as $node) {
528
            $result[] = $this->node_decorator->decorate($node, $attributes);
529
        }
530
531
        return (new Response())->setCode(200)->setBody($result);
532
    }
533
534
    /**
535
     * @api {patch} /api/v2/node/:id/meta Change meta attributes
536
     * @apiVersion 2.0.0
537
     * @apiName patchMeta
538
     * @apiGroup Node
539
     * @apiPermission none
540
     * @apiDescription Change meta attributes of a node
541
     * @apiUse _getNodes
542
     * @apiUse _multiError
543
     *
544
     * @apiParam (GET Parameter) {string} [attributes.description] UTF-8 Text Description - Can contain anything as long as it is a string
545
     * @apiParam (GET Parameter) {string} [attributes.color] Color Tag - Can contain anything as long as it is a string
546
     * @apiParam (GET Parameter) {string} [attributes.author] Author - Can contain anything as long as it is a string
547
     * @apiParam (GET Parameter) {string} [attributes.mail] Mail contact address - Can contain anything as long as it is a string
548
     * @apiParam (GET Parameter) {string} [attributes.license] License - Can contain anything as long as it is a string
549
     * @apiParam (GET Parameter) {string} [attributes.copyright] Copyright string - Can contain anything as long as it is a string
550
     * @apiParam (GET Parameter) {string[]} [attributes.tags] Tags - Must be an array full of strings
551
     *
552
     * @apiExample (cURL) example:
553
     * curl -XPOST "https://SERVER/api/v2/node/meta-attributes?id=544627ed3c58891f058b4686&author=peter.meier"
554
     * curl -XPOST "https://SERVER/api/v2/node/544627ed3c58891f058b4686/meta-attributes?author=example"
555
     * curl -XPOST "https://SERVER/api/v2/node/meta-attributes?p=/absolute/path/to/my/node?license=GPL-3.0"
556
     *
557
     * @apiSuccessExample {json} Success-Response:
558
     * HTTP/1.1 204 No Content
559
     *
560
     * @param array|string $id
561
     * @param array|string $p
562
     *
563
     * @return Response
564
     */
565
    public function patchMeta(array $attributes, ?string $id = null, ?string $p = null): Response
566
    {
567
        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 565 can also be of type null; however, Balloon\App\Api\v2\Nodes::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 565 can also be of type null; however, Balloon\App\Api\v2\Nodes::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...
568
            $node->setMetaAttributes($attributes);
569
570
            return ['code' => 204];
571
        });
572
    }
573
574
    /**
575
     * @api {post} /api/v2/node/:id/name Rename node
576
     * @apiVersion 2.0.0
577
     * @apiName postName
578
     * @apiGroup Node
579
     * @apiPermission none
580
     * @apiDescription Rename a node. The characters (\ < > : " / * ? |) (without the "()") are not allowed to use within a node name.
581
     * @apiUse _getNode
582
     * @apiUse _writeAction
583
     *
584
     * @apiParam (GET Parameter) {string} [name] The new name of the node
585
     * @apiError (Error 400) Exception name contains invalid characters
586
     *
587
     * @apiExample (cURL) example:
588
     * curl -XPOST "https://SERVER/api/v2/node/name?id=544627ed3c58891f058b4686&name=newname.txt"
589
     * curl -XPOST "https://SERVER/api/v2/node/544627ed3c58891f058b4677/name?name=newdir"
590
     * curl -XPOST "https://SERVER/api/v2/node/name?p=/absolute/path/to/my/node&name=newname.txt"
591
     *
592
     * @apiSuccessExample {json} Success-Response:
593
     * HTTP/1.1 200 OK
594
     *
595
     * @param string $id
596
     * @param string $p
597
     * @param string $name
598
     *
599
     * @return Response
600
     */
601
    public function postName(string $name, ?string $id = null, ?string $p = null): Response
602
    {
603
        $node = $this->_getNode($id, $p);
604
        $node->setName($name);
605
        $result = $this->node_decorator->decorate($node);
606
607
        return (new Response())->setCode(200)->setBody($result);
608
    }
609
610
    /**
611
     * @api {post} /api/v2/node/:id/clone Clone node
612
     * @apiVersion 2.0.0
613
     * @apiName postClone
614
     * @apiGroup Node
615
     * @apiPermission none
616
     * @apiDescription Clone a node
617
     * @apiUse _getNode
618
     * @apiUse _conflictNode
619
     * @apiUse _multiError
620
     * @apiUse _writeAction
621
     *
622
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
623
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
624
     *
625
     * @apiExample (cURL) example:
626
     * curl -XPOST "https://SERVER/api/v2/node/clone?id=544627ed3c58891f058b4686&dest=544627ed3c58891f058b4676"
627
     * curl -XPOST "https://SERVER/api/v2/node/544627ed3c58891f058b4686/clone?dest=544627ed3c58891f058b4676&conflict=2"
628
     * curl -XPOST "https://SERVER/api/v2/node/clone?p=/absolute/path/to/my/node&conflict=0&destp=/new/parent"
629
     *
630
     * @apiSuccessExample {json} Success-Response:
631
     * HTTP/1.1 201 Created
632
     *
633
     * @apiSuccessExample {json} Success-Response:
634
     * HTTP/1.1 200 OK
635
     *
636
     * @param array|string $id
637
     * @param array|string $id
638
     * @param array|string $p
639
     * @param string       $destid
640
     * @param string       $destp
641
     * @param int          $conflict
642
     *
643
     * @return Response
644
     */
645
    public function postClone(
646
        $id = null,
647
        $p = null,
648
        ?string $destid = null,
649
        ?string $destp = null,
650
        int $conflict = 0
651
    ): Response {
652
        try {
653
            $parent = $this->_getNode($destid, $destp, Collection::class, false, true);
654
        } catch (Exception\NotFound $e) {
655
            throw new Exception\NotFound(
656
                'destination collection was not found or is not a collection',
657
                Exception\NotFound::DESTINATION_NOT_FOUND
658
            );
659
        }
660
661
        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 646 can also be of type null; however, Balloon\App\Api\v2\Nodes::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 647 can also be of type null; however, Balloon\App\Api\v2\Nodes::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...
662
            $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...
663
            $result = $node->copyTo($parent, $conflict);
664
665
            return [
666
                'code' => $parent == $result ? 200 : 201,
667
                'data' => $this->node_decorator->decorate($result),
668
            ];
669
        });
670
    }
671
672
    /**
673
     * @api {post} /api/v2/node/:id/move Move node
674
     * @apiVersion 2.0.0
675
     * @apiName postMove
676
     * @apiGroup Node
677
     * @apiPermission none
678
     * @apiDescription Move node
679
     * @apiUse _getNodes
680
     * @apiUse _conflictNode
681
     * @apiUse _multiError
682
     * @apiUse _writeAction
683
     *
684
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
685
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
686
     *
687
     * @apiExample (cURL) example:
688
     * curl -XPOST "https://SERVER/api/v2/node/move?id=544627ed3c58891f058b4686?destid=544627ed3c58891f058b4655"
689
     * curl -XPOST "https://SERVER/api/v2/node/544627ed3c58891f058b4686/move?destid=544627ed3c58891f058b4655"
690
     * curl -XPOST "https://SERVER/api/v2/node/move?p=/absolute/path/to/my/node&destp=/new/parent&conflict=1
691
     *
692
     * @apiSuccessExample {json} Success-Response:
693
     * HTTP/1.1 204 No Content
694
     *
695
     * @apiSuccessExample {json} Success-Response (conflict=1):
696
     * HTTP/1.1 200 OK
697
     * {
698
     *      "status":200,
699
     *      "data": "renamed (xy23)"
700
     * }
701
     *
702
     * @param array|string $id
703
     * @param array|string $p
704
     * @param string       $destid
705
     * @param string       $destp
706
     * @param int          $conflict
707
     *
708
     * @return Response
709
     */
710
    public function postMove(
711
        $id = null,
712
        $p = null,
713
        ?string $destid = null,
714
        ?string $destp = null,
715
        int $conflict = 0
716
    ): Response {
717
        try {
718
            $parent = $this->_getNode($destid, $destp, Collection::class, false, true);
719
        } catch (Exception\NotFound $e) {
720
            throw new Exception\NotFound(
721
                'destination collection was not found or is not a collection',
722
                Exception\NotFound::DESTINATION_NOT_FOUND
723
            );
724
        }
725
726
        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 711 can also be of type null; however, Balloon\App\Api\v2\Nodes::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 712 can also be of type null; however, Balloon\App\Api\v2\Nodes::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...
727
            $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...
728
729
            return [
730
                'code' => 200,
731
                'data' => $this->node_decorator->decorate($node),
732
            ];
733
        });
734
    }
735
736
    /**
737
     * @api {delete} /api/v2/node?id=:id Delete node
738
     * @apiVersion 2.0.0
739
     * @apiName delete
740
     * @apiGroup Node
741
     * @apiPermission none
742
     * @apiDescription Delete node
743
     * @apiUse _getNodes
744
     * @apiUse _multiError
745
     * @apiUse _writeAction
746
     *
747
     * @apiParam (GET Parameter) {boolean} [force=false] Force flag need to be set to delete a node from trash (node must have the deleted flag)
748
     * @apiParam (GET Parameter) {boolean} [ignore_flag=false] If both ignore_flag and force_flag were set, the node will be deleted completely
749
     * @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
750
     *
751
     * @apiExample (cURL) example:
752
     * curl -XDELETE "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686"
753
     * curl -XDELETE "https://SERVER/api/v2/node/544627ed3c58891f058b4686?force=1&ignore_flag=1"
754
     * curl -XDELETE "https://SERVER/api/v2/node?p=/absolute/path/to/my/node"
755
     *
756
     * @apiSuccessExample {json} Success-Response:
757
     * HTTP/1.1 204 No Content
758
     *
759
     * @param array|string $id
760
     * @param array|string $p
761
     * @param bool         $force
762
     * @param bool         $ignore_flag
763
     * @param int          $at
764
     *
765
     * @return Response
766
     */
767
    public function delete(
768
        $id = null,
769
        $p = null,
770
        bool $force = false,
771
        bool $ignore_flag = false,
772
        ?string $at = null
773
    ): Response {
774
        $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...
775
776
        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...
777
            $at = $this->_verifyAttributes(['destroy' => $at])['destroy'];
778
        }
779
780
        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 768 can also be of type null; however, Balloon\App\Api\v2\Nodes::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 769 can also be of type null; however, Balloon\App\Api\v2\Nodes::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...
781
            if (null === $at) {
782
                $node->delete($force && $node->isDeleted() || $force && $ignore_flag);
783
            } else {
784
                if ('0' === $at) {
785
                    $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...
786
                }
787
                $node->setDestroyable($at);
788
            }
789
790
            return [
791
                'code' => 204,
792
            ];
793
        });
794
    }
795
796
    /**
797
     * @api {get} /api/v2/node/query Custom query
798
     * @apiVersion 2.0.0
799
     * @apiName getQuery
800
     * @apiGroup Node
801
     * @apiPermission none
802
     * @apiDescription A custom query is similar requet to children. You do not have to provide any parent node (id or p)
803
     * 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
804
     * (search) but does not use the search engine like GET /node/search does. You can also create a persistent query collection, just look at
805
     * 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.
806
     * 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.
807
     * @apiUse _nodeAttributes
808
     *
809
     * @apiExample (cURL) example:
810
     * curl -XGET https://SERVER/api/v2/node/query?{%22filter%22:{%22shared%22:true,%22reference%22:{%22$exists%22:0}}}
811
     *
812
     * @apiParam (GET Parameter) {string[]} [attributes] Filter node attributes
813
     * @apiParam (GET Parameter) {string[]} [filter] Filter nodes
814
     * @apiParam (GET Parameter) {number} [deleted=0] Wherever include deleted nodes or not, possible values:</br>
815
     * - 0 Exclude deleted</br>
816
     * - 1 Only deleted</br>
817
     * - 2 Include deleted</br>
818
     *
819
     * @apiSuccess (200 OK) {object[]} - List of nodes
820
     * @apiSuccessExample {json} Success-Response:
821
     * HTTP/1.1 200 OK
822
     * {
823
     *      "status":200,
824
     *      "data": [{..}, {...}] //Shorted
825
     * }
826
     *
827
     * @param int   $deleted
828
     * @param array $filter
829
     * @param array $attributes
830
     *
831
     * @return Response
832
     */
833
    public function getQuery(int $deleted = 0, array $filter = [], array $attributes = []): Response
834
    {
835
        $children = [];
836
        $nodes = $this->fs->findNodesByFilterUser($deleted, $filter);
837
838
        foreach ($nodes as $node) {
839
            $child = $this->node_decorator->decorate($node, $attributes);
840
            $children[] = $child;
841
        }
842
843
        return (new Response())->setCode(200)->setBody($children);
844
    }
845
846
    /**
847
     * @api {get} /api/v2/node/trash Get trash
848
     * @apiName getTrash
849
     * @apiVersion 2.0.0
850
     * @apiGroup Node
851
     * @apiPermission none
852
     * @apiDescription A similar endpoint to /api/v2/node/query filer={'deleted': {$type: 9}] but instead returning all deleted
853
     * nodes (including children which are deleted as well) this enpoint only returns the first deleted node from every subtree)
854
     * @apiUse _nodeAttributes
855
     *
856
     * @apiExample (cURL) example:
857
     * curl -XGET https://SERVER/api/v2/node/trash?pretty
858
     *
859
     * @apiParam (GET Parameter) {string[]} [attributes] Filter node attributes
860
     *
861
     * @apiSuccess (200 OK) {object[]} - List of deleted nodes
862
     * @apiSuccessExample {json} Success-Response:
863
     * HTTP/1.1 200 OK
864
     * [
865
     *  {
866
     *  }
867
     * ]
868
     *
869
     * @param array $attributes
870
     *
871
     * @return Response
872
     */
873
    public function getTrash(array $attributes = []): Response
874
    {
875
        $children = [];
876
        $nodes = $this->fs->findNodesByFilterUser(NodeInterface::DELETED_ONLY, ['deleted' => ['$type' => 9]]);
877
878
        foreach ($nodes as $node) {
879
            try {
880
                $parent = $node->getParent();
881
                if (null !== $parent && $parent->isDeleted()) {
882
                    continue;
883
                }
884
            } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
885
            }
886
887
            $child = $this->node_decorator->decorate($node, $attributes);
888
            $children[] = $child;
889
        }
890
891
        return (new Response())->setCode(200)->setBody(array_values($children));
892
    }
893
894
    /**
895
     * @api {get} /api/v2/node/delta Get delta
896
     * @apiVersion 2.0.0
897
     * @apiName getDelta
898
     * @apiGroup Node
899
     * @apiPermission none
900
     * @apiUse _getNode
901
     *
902
     * @apiDescription Use this method to request a delta feed with all changes on the server (or) a snapshot of the server state.
903
     * 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.
904
     * If has_more is TRUE you need to request /delta immediatly again to
905
     * receive the next bunch of deltas. If has_more is FALSE you should wait at least 120s seconds before any further requests to the
906
     * 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).
907
     * 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
908
     * without a cursor. reset could be TRUE if there was an account maintenance or a simialar case.
909
     * You can request a different limit as well but be aware that the number of nodes could be slighty different from your requested limit.
910
     * If requested with parameter id or p the delta gets generated recursively from the node given.
911
     *
912
     * @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
913
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default not all attributes would be returned
914
     * @apiParam (GET Parameter) {string} [cursor=null] Set a cursor to rquest next nodes within delta processing
915
     *
916
     * @apiExample (cURL) example:
917
     * curl -XGET "https://SERVER/api/v2/node/delta?pretty"
918
     *
919
     * @apiSuccess (200 OK) {boolean} reset If true the local state needs to be reseted, is alway TRUE during
920
     * the first request to /delta without a cursor or in special cases like server or account maintenance
921
     * @apiSuccess (200 OK) {string} cursor The cursor needs to be stored and reused to request further deltas
922
     * @apiSuccess (200 OK) {boolean} has_more If has_more is TRUE /delta can be requested immediatly after the last request
923
     * to receive further delta. If it is FALSE we should wait at least 120 seconds before any further delta requests to the api endpoint
924
     * @apiSuccess (200 OK) {object[]} nodes Node list to process
925
     * @apiSuccessExample {json} Success-Response:
926
     * HTTP/1.1 200 OK
927
     * {
928
     *      "reset": false,
929
     *      "cursor": "aW5pdGlhbHwxMDB8NTc1YTlhMGIzYzU4ODkwNTE0OGI0NTZifDU3NWE5YTBiM2M1ODg5MDUxNDhiNDU2Yw==",
930
     *      "has_more": false,
931
     *       "nodes": [
932
     *          {
933
     *              "id": "581afa783c5889ad7c8b4572",
934
     *              "deleted": " 2008-08-31T16:47+00:00",
935
     *              "changed": "2007-08-31T16:47+00:00",
936
     *              "path": "\/AAA\/AL",
937
     *              "directory": true
938
     *          },
939
     *          {
940
     *              "id": "581afa783c5889ad7c8b3dcf",
941
     *              "created": "2007-08-31T16:47+00:00",
942
     *              "changed": "2007-09-28T12:33+00:00",
943
     *              "path": "\/AL",
944
     *              "directory": true
945
     *          }
946
     *      ]
947
     * }
948
     *
949
     * @param DeltaAttributeDecorator $delta_decorator
950
     * @param string                  $id
951
     * @param string                  $p
952
     * @param string                  $cursor
953
     * @param int                     $limit
954
     * @param array                   $attributes
955
     *
956
     * @return Response
957
     */
958
    public function getDelta(
959
        DeltaAttributeDecorator $delta_decorator,
960
        ?string $id = null,
961
        ?string $p = null,
962
        ?string $cursor = null,
963
        int $limit = 250,
964
        array $attributes = []
965
    ): Response {
966
        if (null !== $id || null !== $p) {
967
            $node = $this->_getNode($id, $p);
968
        } else {
969
            $node = null;
970
        }
971
972
        $result = $this->fs->getDelta()->getDeltaFeed($cursor, $limit, $node);
973
        foreach ($result['nodes'] as &$node) {
974
            if ($node instanceof NodeInterface) {
975
                $node = $this->node_decorator->decorate($node, $attributes);
976
            } else {
977
                $node = $delta_decorator->decorate($node, $attributes);
978
            }
979
        }
980
981
        return (new Response())->setCode(200)->setBody($result);
982
    }
983
984
    /**
985
     * @api {get} /api/v2/node/:id/event-log Event log
986
     * @apiVersion 2.0.0
987
     * @apiName getEventLog
988
     * @apiGroup Node
989
     * @apiPermission none
990
     * @apiUse _getNode
991
     * @apiDescription Get detailed event log
992
     * Request all modifications which are made by the user himself or share members.
993
     * Possible operations are the follwing:
994
     * - deleteCollectionReference
995
     * - deleteCollectionShare
996
     * - deleteCollection
997
     * - addCollection
998
     * - addFile
999
     * - addCollectionShare
1000
     * - addCollectionReference
1001
     * - undeleteFile
1002
     * - undeleteCollectionReference
1003
     * - undeleteCollectionShare
1004
     * - restoreFile
1005
     * - renameFile
1006
     * - renameCollection
1007
     * - renameCollectionShare
1008
     * - renameCollectionRFeference
1009
     * - copyFile
1010
     * - copyCollection
1011
     * - copyCollectionShare
1012
     * - copyCollectionRFeference
1013
     * - moveFile
1014
     * - moveCollection
1015
     * - moveCollectionReference
1016
     * - moveCollectionShare
1017
     *
1018
     * @apiExample (cURL) example:
1019
     * curl -XGET "https://SERVER/api/v2/node/event-log?pretty"
1020
     * curl -XGET "https://SERVER/api/v2/node/event-log?id=544627ed3c58891f058b4686&pretty"
1021
     * curl -XGET "https://SERVER/api/v2/node/544627ed3c58891f058b4686/event-log?pretty&limit=10"
1022
     * curl -XGET "https://SERVER/api/v2/node/event-log?p=/absolute/path/to/my/node&pretty"
1023
     *
1024
     * @apiParam (GET Parameter) {number} [limit=100] Sets limit of events to be returned
1025
     * @apiParam (GET Parameter) {number} [skip=0] How many events are skiped (useful for paging)
1026
     *
1027
     * @apiSuccess (200 OK) {object[]} - List of events
1028
     * @apiSuccess (200 OK) {number} -.event Event ID
1029
     * @apiSuccess (200 OK) {object} -.timestamp ISO8601 timestamp when the event occured
1030
     * @apiSuccess (200 OK) {string} -.operation event operation (like addCollection, deleteFile, ...)
1031
     * @apiSuccess (200 OK) {object} -.parent Parent node object at the time of the event
1032
     * @apiSuccess (200 OK) {object} -.previous Previous state of actual data which has been modified during an event, can contain either version, name or parent
1033
     * @apiSuccess (200 OK) {number} -.previous.version Version at the time before the event
1034
     * @apiSuccess (200 OK) {string} -.previous.name Name at the time before the event
1035
     * @apiSuccess (200 OK) {object} -.previous.parent Parent node object at the time before the event
1036
     * @apiSuccess (200 OK) {object} -.share shared collection object at the time of the event (If the node was part of a share)
1037
     * @apiSuccess (200 OK) {string} -.name Name of the node at the time of the event
1038
     * @apiSuccess (200 OK) {object} -.node Current node object
1039
     * @apiSuccess (200 OK) {object} -.user User who executed an event
1040
     *
1041
     * @apiSuccessExample {json} Success-Response:
1042
     * HTTP/1.1 200 OK
1043
     * [
1044
     *  {
1045
     *      "id": "57628e523c5889026f8b4570",
1046
     *      "timestamp": " 2018-01-02T13:22+00:00",
1047
     *      "operation": "restoreFile",
1048
     *      "name": "file.txt",
1049
     *      "previous": {
1050
     *          "version": 16
1051
     *      },
1052
     *      "node": {
1053
     *          "id": "558c0b273c588963078b457a",
1054
     *          "name": "3dddsceheckfile.txt",
1055
     *          "deleted": false
1056
     *      },
1057
     *      "parent": null,
1058
     *      "user": {
1059
     *          "id": "54354cb63c58891f058b457f",
1060
     *          "username": "example"
1061
     *      }
1062
     *  }
1063
     * ]
1064
     *
1065
     * @param EventAttributeDecorator $event_decorator
1066
     * @param string                  $id
1067
     * @param string                  $p
1068
     * @param int                     $skip
1069
     * @param int                     $limit
1070
     *
1071
     * @return Response
1072
     */
1073
    public function getEventLog(EventAttributeDecorator $event_decorator, ?string $id = null, ?string $p = null, int $skip = 0, int $limit = 100): Response
1074
    {
1075
        if (null !== $id || null !== $p) {
1076
            $node = $this->_getNode($id, $p);
1077
        } else {
1078
            $node = null;
1079
        }
1080
1081
        $result = $this->fs->getDelta()->getEventLog($limit, $skip, $node);
1082
        $body = [];
1083
        foreach ($result as $event) {
1084
            $body[] = $event_decorator->decorate($event);
1085
        }
1086
1087
        return (new Response())->setCode(200)->setBody($body);
1088
    }
1089
1090
    /**
1091
     * @api {get} /api/v2/node/last-cursor Get last Cursor
1092
     * @apiVersion 2.0.0
1093
     * @apiName geLastCursor
1094
     * @apiGroup Node
1095
     * @apiUse _getNode
1096
     * @apiPermission none
1097
     * @apiDescription Use this method to request the latest cursor if you only need to now
1098
     * if there are changes on the server. This method will not return any other data than the
1099
     * newest cursor. To request a feed with all deltas request /delta.
1100
     *
1101
     * @apiExample (cURL) example:
1102
     * curl -XGET "https://SERVER/api/v2/node/last-cursor?pretty"
1103
     *
1104
     * @apiSuccess (200 OK) {string} cursor v2 cursor
1105
     * @apiSuccessExample {json} Success-Response:
1106
     * HTTP/1.1 200 OK
1107
     * "aW5pdGlhbHwxMDB8NTc1YTlhMGIzYzU4ODkwNTE0OGI0NTZifDU3NWE5YTBiM2M1ODg5MDUxNDhiNDU2Yw=="
1108
     *
1109
     * @param string $id
1110
     * @param string $p
1111
     *
1112
     * @return Response
1113
     */
1114
    public function getLastCursor(?string $id = null, ?string $p = null): Response
1115
    {
1116
        if (null !== $id || null !== $p) {
1117
            $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...
1118
        } else {
1119
            $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...
1120
        }
1121
1122
        $result = $this->fs->getDelta()->getLastCursor();
1123
1124
        return (new Response())->setCode(200)->setBody($result);
1125
    }
1126
1127
    /**
1128
     * Do bulk operations.
1129
     *
1130
     * @param array|string $id
1131
     * @param array|string $p
1132
     * @param Closure      $action
1133
     */
1134
    protected function bulk($id, $p, Closure $action): Response
1135
    {
1136
        if (is_array($id) || is_array($p)) {
1137
            $body = [];
1138
            foreach ($this->_getNodes($id, $p) as $node) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 1134 can also be of type array; however, Balloon\App\Api\v2\Nodes::_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 1134 can also be of type array; however, Balloon\App\Api\v2\Nodes::_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...
1139
                try {
1140
                    $body[(string) $node->getId()] = $action->call($this, $node);
1141
                } catch (\Exception $e) {
1142
                    $body[(string) $node->getId()] = [
1143
                        'code' => 400,
1144
                        'data' => [
1145
                            'error' => get_class($e),
1146
                            'message' => $e->getMessage(),
1147
                            'code' => $e->getCode(),
1148
                        ],
1149
                    ];
1150
                }
1151
            }
1152
1153
            return (new Response())->setCode(207)->setBody($body);
1154
        }
1155
1156
        $body = $action->call($this, $this->_getNode($id, $p));
1157
        $response = (new Response())->setCode($body['code']);
1158
1159
        if (isset($body['data'])) {
1160
            $response->setBody($body['data']);
1161
        }
1162
1163
        return $response;
1164
    }
1165
1166
    /**
1167
     * Get node.
1168
     *
1169
     * @param string $id
1170
     * @param string $path
1171
     * @param string $class      Force set node type
1172
     * @param bool   $multiple   Allow $id to be an array
1173
     * @param bool   $allow_root Allow instance of root collection
1174
     * @param bool   $deleted    How to handle deleted node
1175
     *
1176
     * @return NodeInterface
1177
     */
1178
    protected function _getNode(
1179
        ?string $id = null,
1180
        ?string $path = null,
1181
        ?string $class = null,
1182
        bool $multiple = false,
1183
        bool $allow_root = false,
1184
        int $deleted = 2
1185
    ): NodeInterface {
1186
        if (null === $class) {
1187
            switch (get_class($this)) {
1188
                case ApiFile::class:
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...
1189
                    $class = File::class;
1190
1191
                break;
1192
                case ApiCollection::class:
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...
1193
                    $class = Collection::class;
1194
1195
                break;
1196
            }
1197
        }
1198
1199
        return $this->fs->getNode($id, $path, $class, $multiple, $allow_root, $deleted);
1200
    }
1201
1202
    /**
1203
     * Get nodes.
1204
     *
1205
     * @param string $id
1206
     * @param string $path
1207
     * @param string $class   Force set node type
1208
     * @param bool   $deleted How to handle deleted node
1209
     *
1210
     * @return Generator
1211
     */
1212
    protected function _getNodes(
1213
        $id = null,
1214
        $path = null,
1215
        ?string $class = null,
1216
        int $deleted = 2
1217
    ): Generator {
1218
        if (null === $class) {
1219
            switch (get_class($this)) {
1220
                case ApiFile::class:
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...
1221
                    $class = File::class;
1222
1223
                break;
1224
                case ApiCollection::class:
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...
1225
                    $class = Collection::class;
1226
1227
                break;
1228
            }
1229
        }
1230
1231
        return $this->fs->getNodes($id, $path, $class, $deleted);
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 1213 can also be of type string; however, Balloon\Filesystem::getNodes() does only seem to accept array|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 $path defined by parameter $path on line 1214 can also be of type string; however, Balloon\Filesystem::getNodes() does only seem to accept array|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...
1232
    }
1233
1234
    /**
1235
     * Merge multiple nodes into one zip archive.
1236
     *
1237
     * @param string $id
1238
     * @param string $path
1239
     * @param string $name
1240
     */
1241
    protected function combine($id = null, $path = null, string $name = 'selected')
1242
    {
1243
        $archive = new ZipStream($name.'.zip');
1244
1245
        foreach ($this->_getNodes($id, $path) as $node) {
1246
            try {
1247
                $node->zip($archive);
1248
            } catch (\Exception $e) {
1249
                $this->logger->debug('failed zip node in multi node request ['.$node->getId().']', [
1250
                   'category' => get_class($this),
1251
                   'exception' => $e,
1252
               ]);
1253
            }
1254
        }
1255
1256
        $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...
1257
    }
1258
1259
    /**
1260
     * Check custom node attributes which have to be written.
1261
     *
1262
     * @param array $attributes
1263
     *
1264
     * @return array
1265
     */
1266
    protected function _verifyAttributes(array $attributes): array
1267
    {
1268
        $valid_attributes = [
1269
            'changed',
1270
            'destroy',
1271
            'created',
1272
            'meta',
1273
            'readonly',
1274
        ];
1275
1276
        if ($this instanceof ApiCollection) {
1277
            $valid_attributes[] = 'filter';
1278
        }
1279
1280
        $check = array_merge(array_flip($valid_attributes), $attributes);
1281
1282
        if ($this instanceof ApiCollection && count($check) > 6) {
1283
            throw new Exception\InvalidArgument('Only changed, created, destroy timestamp, filter, readonly and/or meta attributes may be overwritten');
1284
        }
1285
        if ($this instanceof ApiFile && count($check) > 5) {
1286
            throw new Exception\InvalidArgument('Only changed, created, destroy timestamp, readonly and/or meta attributes may be overwritten');
1287
        }
1288
1289
        foreach ($attributes as $attribute => $value) {
1290
            switch ($attribute) {
1291
                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...
1292
                    $attributes['filter'] = json_encode((array) $attributes['filter']);
1293
1294
                break;
1295
                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...
1296
                    if (!Helper::isValidTimestamp($value)) {
1297
                        throw new Exception\InvalidArgument($attribute.' Changed timestamp must be valid unix timestamp');
1298
                    }
1299
                    $attributes[$attribute] = new UTCDateTime($value.'000');
1300
1301
                break;
1302
                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...
1303
                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...
1304
                    if (!Helper::isValidTimestamp($value)) {
1305
                        throw new Exception\InvalidArgument($attribute.' Changed timestamp must be valid unix timestamp');
1306
                    }
1307
                    if ((int) $value > time()) {
1308
                        throw new Exception\InvalidArgument($attribute.' timestamp can not be set greater than the server time');
1309
                    }
1310
                    $attributes[$attribute] = new UTCDateTime($value.'000');
1311
1312
                break;
1313
                case 'readonly':
1314
                    $attributes['readonly'] = (bool) $attributes['readonly'];
1315
1316
                break;
1317
            }
1318
        }
1319
1320
        return $attributes;
1321
    }
1322
}
1323