Completed
Branch dev (bc6e47)
by Raffael
02:23
created

Node::postClone()   C

Complexity

Conditions 7
Paths 8

Size

Total Lines 47
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 47
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 33
nc 8
nop 5
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Balloon
7
 *
8
 * @author      Raffael Sahli <[email protected]>
9
 * @copyright   Copryright (c) 2012-2017 gyselroth GmbH (https://gyselroth.com)
10
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
11
 */
12
13
namespace Balloon\Api\v1;
14
15
use Balloon\Api\Controller;
16
use Balloon\Api\v1\Collection as ApiCollection;
17
use Balloon\Api\v1\File as ApiFile;
18
use Balloon\Exception;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Balloon\Api\v1\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...
19
use Balloon\Filesystem;
20
use Balloon\Filesystem\Node\AbstractNode;
21
use Balloon\Filesystem\Node\AttributeDecorator;
22
use Balloon\Filesystem\Node\Collection;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Balloon\Api\v1\Collection.

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
23
use Balloon\Filesystem\Node\File;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Balloon\Api\v1\File.

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
24
use Balloon\Filesystem\Node\NodeInterface;
25
use Balloon\Helper;
26
use Balloon\Server;
27
use Balloon\Server\User;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Balloon\Api\v1\User.

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
28
use Generator;
29
use Micro\Http\Response;
30
use MongoDB\BSON\UTCDateTime;
31
use PHPZip\Zip\Stream\ZipStream;
32
use Psr\Log\LoggerInterface;
33
34
class Node 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
     * Decorator.
66
     *
67
     * @var AttributeDecorator
68
     */
69
    protected $decorator;
70
71
    /**
72
     * Initialize.
73
     *
74
     * @param Server             $server
75
     * @param AttributeDecorator $decorator
76
     * @param LoggerInterface    $logger
77
     */
78
    public function __construct(Server $server, AttributeDecorator $decorator, LoggerInterface $logger)
79
    {
80
        $this->fs = $server->getFilesystem();
81
        $this->user = $server->getIdentity();
82
        $this->server = $server;
83
        $this->decorator = $decorator;
84
        $this->logger = $logger;
85
    }
86
87
    /**
88
     * @api {head} /api/v1/node?id=:id Node exists?
89
     * @apiVersion 1.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/v1/node?id=544627ed3c58891f058b4686"
99
     * curl -XHEAD "https://SERVER/api/v1/node/544627ed3c58891f058b4686"
100
     * curl -XHEAD "https://SERVER/api/v1/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/v1/node/undelete?id=:id Undelete node
137
     * @apiVersion 1.0.0
138
     * @apiName postUndelete
139
     * @apiGroup Node
140
     * @apiPermission none
141
     * @apiDescription Undelete (Apiore 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/v1/node/undelete?id[]=544627ed3c58891f058b4686&id[]=544627ed3c58891f058b46865&pretty"
149
     * curl -XPOST "https://SERVER/api/v1/node/undelete?id=544627ed3c58891f058b4686?pretty"
150
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4686/undelete?conflict=2"
151
     * curl -XPOST "https://SERVER/api/v1/node/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:
157
     * HTTP/1.1 204 No Content
158
     *
159
     * @apiSuccessExample {json} Success-Response (conflict=1):
160
     * HTTP/1.1 200 OK
161
     * {
162
     *      "status":200,
163
     *      "data": "renamed (xy23)"
164
     *      }
165
     * }
166
     *
167
     * @param array|string $id
168
     * @param array|string $p
169
     * @param bool         $move
170
     * @param string       $destid
171
     * @param string       $destp
172
     * @param int          $conflict
173
     */
174
    public function postUndelete(
175
        $id = null,
176
        $p = null,
177
        bool $move = false,
178
        ?string $destid = null,
179
        ?string $destp = null,
180
        int $conflict = 0
181
    ): Response {
182
        $parent = null;
183
184
        if (true === $move) {
185
            try {
186
                $parent = $this->_getNode($destid, $destp, 'Collection', false, true);
187
            } catch (Exception\NotFound $e) {
188
                throw new Exception\NotFound(
189
                    'destination collection was not found or is not a collection',
190
                    Exception\NotFound::DESTINATION_NOT_FOUND
191
                );
192
            }
193
        }
194
195
        if (is_array($id) || is_array($p)) {
196
            $failures = [];
197
            foreach ($this->_getNodes($id, $p) as $node) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 175 can also be of type array; however, Balloon\Api\v1\Node::_getNodes() does only seem to accept string|null, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
198
                try {
199
                    if (true === $move) {
200
                        $node = $node->setParent($parent, $conflict);
201
                    }
202
203
                    if ($node->isDeleted()) {
204
                        $node->undelete($conflict);
205
                    }
206
                } catch (\Exception $e) {
207
                    $failures[] = [
208
                        'id' => (string) $node->getId(),
209
                        'name' => $node->getName(),
210
                        'error' => get_class($e),
211
                        'message' => $e->getMessage(),
212
                        'code' => $e->getCode(),
213
                    ];
214
215
                    $this->logger->debug('failed undelete node in multi node request ['.$node->getId().']', [
216
                        'category' => get_class($this),
217
                        'exception' => $e,
218
                    ]);
219
                }
220
            }
221
222
            if (empty($failures)) {
223
                return (new Response())->setCode(204);
224
            }
225
226
            return (new Response())->setCode(400)->setBody($failures);
227
        }
228
        $node = $this->_getNode($id, $p);
229
230
        if (true === $move) {
231
            $node = $node->setParent($parent, $conflict);
232
        }
233
234
        if ($node->isDeleted()) {
235
            $result = $node->undelete($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...
236
        }
237
238
        if (true === $move && NodeInterface::CONFLICT_RENAME === $conflict) {
239
            return (new Response())->setCode(200)->setBody($node->getName());
0 ignored issues
show
Bug introduced by
Consider using $node->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
240
        }
241
242
        return (new Response())->setCode(204);
243
    }
244
245
    /**
246
     * @api {get} /api/v1/node?id=:id Download stream
247
     * @apiVersion 1.0.0
248
     * @apiName get
249
     * @apiGroup Node
250
     * @apiPermission none
251
     * @apiDescription Download node contents. Collections (Folder) are converted into
252
     * a zip file in realtime.
253
     * @apiUse _getNode
254
     *
255
     * @apiParam (GET Parameter) {number} [offset=0] Get content from a specific offset in bytes
256
     * @apiParam (GET Parameter) {number} [length=0] Get content with until length in bytes reached
257
     * @apiParam (GET Parameter) {string} [encode] Can be set to base64 to encode content as base64.
258
     * @apiParam (GET Parameter) {boolean} [download=false] Force download file (Content-Disposition: attachment HTTP header)
259
     *
260
     * @apiExample (cURL) example:
261
     * curl -XGET "https://SERVER/api/v1/node?id=544627ed3c58891f058b4686" > myfile.txt
262
     * curl -XGET "https://SERVER/api/v1/node/544627ed3c58891f058b4686" > myfile.txt
263
     * curl -XGET "https://SERVER/api/v1/node?p=/absolute/path/to/my/collection" > folder.zip
264
     *
265
     * @apiSuccessExample {string} Success-Response (encode=base64):
266
     * HTTP/1.1 200 OK
267
     *
268
     * @apiSuccessExample {binary} Success-Response:
269
     * HTTP/1.1 200 OK
270
     *
271
     * @apiErrorExample {json} Error-Response (Invalid offset):
272
     * HTTP/1.1 400 Bad Request
273
     * {
274
     *      "status": 400,
275
     *      "data": {
276
     *          "error": "Balloon\\Exception\\Conflict",
277
     *          "message": "invalid offset requested",
278
     *          "code": 277
279
     *      }
280
     * }
281
     *
282
     * @param array|string $id
283
     * @param array|string $p
284
     * @param int          $offset
285
     * @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...
286
     * @param string       $encode
287
     * @param bool         $download
288
     * @param string       $name
289
     */
290
    public function get(
291
        $id = null,
292
        $p = null,
293
        int $offset = 0,
294
        int $length = 0,
295
        ?string $encode = null,
296
        bool $download = false,
297
        string $name = 'selected'
298
    ): ?Response {
299
        if (is_array($id) || is_array($p)) {
300
            return $this->combine($id, $p, $name);
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 291 can also be of type array; however, Balloon\Api\v1\Node::combine() does only seem to accept string|null, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
301
        }
302
303
        $node = $this->_getNode($id, $p);
304
        if ($node instanceof Collection) {
305
            return (new Response())->setBody(function () use ($node) {
306
                $node->getZip();
307
            });
308
        }
309
310
        $response = new Response();
311
312
        if (true === $download) {
313
            $response->setHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\''.rawurlencode($name));
314
            $response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
315
            $response->setHeader('Content-Type', 'application/octet-stream');
316
            $response->setHeader('Content-Length', (string) $node->getSize());
317
            $response->setHeader('Content-Transfer-Encoding', 'binary');
318
        } else {
319
            $response->setHeader('Content-Disposition', 'inline; filename*=UTF-8\'\''.rawurlencode($name));
320
        }
321
322
        return (new Response())
323
          ->setOutputFormat(null)
324
          ->setBody(function () use ($node, $encode, $offset, $length) {
325
              $mime = $node->getContentType();
326
              $stream = $node->get();
327
              $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...
328
329
              if (null === $stream) {
330
                  return;
331
              }
332
333
              if (0 !== $offset) {
334
                  if (fseek($stream, $offset) === -1) {
335
                      throw new Exception\Conflict(
336
                        'invalid offset requested',
337
                        Exception\Conflict::INVALID_OFFSET
338
                    );
339
                  }
340
              }
341
342
              $read = 0;
343
              header('Content-Type: '.$mime.'');
344
              if ('base64' === $encode) {
345
                  header('Content-Encoding: base64');
346
                  while (!feof($stream)) {
347
                      if (0 !== $length && $read + 8192 > $length) {
348
                          echo base64_encode(fread($stream, $length - $read));
349
                          exit();
350
                      }
351
352
                      echo base64_encode(fread($stream, 8192));
353
                      $read += 8192;
354
                  }
355
              } else {
356
                  while (!feof($stream)) {
357
                      if (0 !== $length && $read + 8192 > $length) {
358
                          echo fread($stream, $length - $read);
359
                          exit();
360
                      }
361
362
                      echo fread($stream, 8192);
363
                      $read += 8192;
364
                  }
365
              }
366
          });
367
    }
368
369
    /**
370
     * @api {post} /api/v1/node/readonly?id=:id Mark node as readonly
371
     * @apiVersion 1.0.0
372
     * @apiName postReadonly
373
     * @apiGroup Node
374
     * @apiPermission none
375
     * @apiDescription Mark (or unmark) node as readonly
376
     * @apiUse _getNodes
377
     * @apiUse _multiError
378
     * @apiUse _writeAction
379
     *
380
     * @apiExample (cURL) example:
381
     * curl -XPOST "https://SERVER/api/v1/node/readonly?id[]=544627ed3c58891f058b4686&id[]=544627ed3c58891f058b46865&readonly=1"
382
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4686/readonly?readonly=0"
383
     * curl -XPOST "https://SERVER/api/v1/node/readonly?p=/absolute/path/to/my/node"
384
     *
385
     * @apiParam (GET Parameter) {bool} [readonly=true] Set readonly to false to make node writeable again
386
     *
387
     * @apiSuccessExample {json} Success-Response:
388
     * HTTP/1.1 204 No Content
389
     *
390
     * @param array|string $id
391
     * @param array|string $p
392
     *
393
     * @return Response
394
     */
395
    public function postReadonly($id = null, $p = null, bool $readonly = true): Response
396
    {
397
        if (is_array($id) || is_array($p)) {
398
            $failures = [];
399
            foreach ($this->_getNodes($id, $p) as $node) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 395 can also be of type array; however, Balloon\Api\v1\Node::_getNodes() does only seem to accept string|null, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
400
                try {
401
                    $node->setReadonly($readonly);
402
                } catch (\Exception $e) {
403
                    $failures[] = [
404
                        'id' => (string) $node->getId(),
405
                        'name' => $node->getName(),
406
                        'error' => get_class($e),
407
                        'message' => $e->getMessage(),
408
                        'code' => $e->getCode(),
409
                    ];
410
411
                    $this->logger->debug('failed set readonly node in multi node request ['.$node->getId().']', [
412
                        'category' => get_class($this),
413
                        'exception' => $e,
414
                    ]);
415
                }
416
            }
417
418
            if (empty($failures)) {
419
                return (new Response())->setCode(204);
420
            }
421
422
            return (new Response())->setCode(400)->setBody($failures);
423
        }
424
        $result = $this->_getNode($id, $p)->setReadonly($readonly);
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...
425
426
        return (new Response())->setCode(204);
427
    }
428
429
    /**
430
     * @apiDefine _nodeAttributes
431
     *
432
     * @apiSuccess (200 OK) {number} status Status Code
433
     * @apiSuccess (200 OK) {object} data Attributes
434
     * @apiSuccess (200 OK) {string} data.id Unique node id
435
     * @apiSuccess (200 OK) {string} data.name Name
436
     * @apiSuccess (200 OK) {string} data.hash MD5 content checksum (file node only)
437
     * @apiSuccess (200 OK) {object} data.meta Extended meta attributes
438
     * @apiSuccess (200 OK) {string} data.meta.description UTF-8 Text Description
439
     * @apiSuccess (200 OK) {string} data.meta.color Color Tag (HEX) (Like: #000000)
440
     * @apiSuccess (200 OK) {string} data.meta.author Author
441
     * @apiSuccess (200 OK) {string} data.meta.mail Mail contact address
442
     * @apiSuccess (200 OK) {string} data.meta.license License
443
     * @apiSuccess (200 OK) {string} data.meta.copyright Copyright string
444
     * @apiSuccess (200 OK) {string[]} data.meta.tags Search Tags
445
     * @apiSuccess (200 OK) {number} data.size Size in bytes (Only file node), number of children if collection
446
     * @apiSuccess (200 OK) {string} data.mime Mime type
447
     * @apiSuccess (200 OK) {boolean} data.sharelink Is node shared?
448
     * @apiSuccess (200 OK) {number} data.version File version (file node only)
449
     * @apiSuccess (200 OK) {mixed} data.deleted Is boolean false if not deleted, if deleted it contains a deleted timestamp
450
     * @apiSuccess (200 OK) {number} data.deleted.sec Unix timestamp
451
     * @apiSuccess (200 OK) {number} data.deleted.usec Additional Microsecconds to Unix timestamp
452
     * @apiSuccess (200 OK) {object} data.changed Changed timestamp
453
     * @apiSuccess (200 OK) {number} data.changed.sec Unix timestamp
454
     * @apiSuccess (200 OK) {number} data.changed.usec Additional Microsecconds to Unix timestamp
455
     * @apiSuccess (200 OK) {object} data.created Created timestamp
456
     * @apiSuccess (200 OK) {number} data.created.sec Unix timestamp
457
     * @apiSuccess (200 OK) {number} data.created.usec Additional Microsecconds to Unix timestamp
458
     * @apiSuccess (200 OK) {boolean} data.share Node is shared
459
     * @apiSuccess (200 OK) {boolean} data.directory Is node a collection or a file?
460
     *
461
     * @apiSuccess (200 OK - additional attributes) {string} data.thumbnail Id of preview (file node only)
462
     * @apiSuccess (200 OK - additional attributes) {string} data.access Access if node is shared, one of r/rw/w
463
     * @apiSuccess (200 OK - additional attributes) {string} data.shareowner Username of the share owner
464
     * @apiSuccess (200 OK - additional attributes) {string} data.parent ID of the parent node
465
     * @apiSuccess (200 OK - additional attributes) {string} data.path Absolute node path
466
     * @apiSuccess (200 OK - additional attributes) {boolean} data.filtered Node is filtered (usually only a collection)
467
     * @apiSuccess (200 OK - additional attributes) {boolean} data.readonly Node is readonly
468
     *
469
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default not all attributes would be returned
470
     *
471
     * @param null|mixed $id
472
     * @param null|mixed $p
473
     */
474
475
    /**
476
     * @api {get} /api/v1/node/attributes?id=:id Get attributes
477
     * @apiVersion 1.0.0
478
     * @apiName getAttributes
479
     * @apiGroup Node
480
     * @apiPermission none
481
     * @apiDescription Get attributes from one or multiple nodes
482
     * @apiUse _getNode
483
     * @apiUse _nodeAttributes
484
     *
485
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default only a bunch of attributes would be returned, if you
486
     * need other attributes you have to request them (for example "path")
487
     *
488
     * @apiExample (cURL) example:
489
     * curl -XGET "https://SERVER/api/v1/node/attributes?id=544627ed3c58891f058b4686&pretty"
490
     * curl -XGET "https://SERVER/api/v1/node/attributes?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted&pretty"
491
     * curl -XGET "https://SERVER/api/v1/node/544627ed3c58891f058b4686/attributes?pretty"
492
     * curl -XGET "https://SERVER/api/v1/node/attributes?p=/absolute/path/to/my/node&pretty"
493
     *
494
     * @apiSuccessExample {json} Success-Response:
495
     * HTTP/1.1 200 OK
496
     * {
497
     *     "status": 200,
498
     *     "data": {
499
     *          "id": "544627ed3c58891f058b4686",
500
     *          "name": "api.php",
501
     *          "hash": "a77f23ed800fd7a600a8c2cfe8cc370b",
502
     *          "meta": {
503
     *              "license": "GPLv3"
504
     *          },
505
     *          "size": 178,
506
     *          "mime": "text\/plain",
507
     *          "sharelink": true,
508
     *          "version": 1,
509
     *          "deleted": false,
510
     *          "changed": {
511
     *              "sec": 1413883885,
512
     *              "usec": 869000
513
     *          },
514
     *          "created": {
515
     *              "sec": 1413883885,
516
     *              "usec": 869000
517
     *          },
518
     *          "share": false,
519
     *          "directory": false
520
     *      }
521
     * }
522
     *
523
     * @param array|string $id
524
     * @param array|string $p
525
     * @param array        $attributes
526
     *
527
     * @return Response
528
     */
529
    public function getAttributes($id = null, $p = null, array $attributes = []): Response
530
    {
531
        if (is_array($id) || is_array($p)) {
532
            $nodes = [];
533
            foreach ($this->_getNodes($id, $p) as $node) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 529 can also be of type array; however, Balloon\Api\v1\Node::_getNodes() does only seem to accept string|null, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
534
                $nodes[] = Helper::escape($this->decorator->decorate($node, $attributes));
535
            }
536
537
            return (new Response())->setCode(200)->setBody($nodes);
538
        }
539
        $result = Helper::escape(
540
                $this->decorator->decorate($this->_getNode($id, $p), $attributes)
541
            );
542
543
        return (new Response())->setCode(200)->setBody($result);
544
    }
545
546
    /**
547
     * @api {get} /api/v1/node/parent?id=:id Get parent node
548
     * @apiVersion 1.0.0
549
     * @apiName getParent
550
     * @apiGroup Node
551
     * @apiPermission none
552
     * @apiDescription Get system attributes of the parent node
553
     * @apiUse _getNode
554
     * @apiUse _nodeAttributes
555
     *
556
     * @apiExample (cURL) example:
557
     * curl -XGET "https://SERVER/api/v1/node/parent?id=544627ed3c58891f058b4686&pretty"
558
     * curl -XGET "https://SERVER/api/v1/node/parent?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted?pretty"
559
     * curl -XGET "https://SERVER/api/v1/node/544627ed3c58891f058b4686/parent?pretty"
560
     * curl -XGET "https://SERVER/api/v1/node/parent?p=/absolute/path/to/my/node&pretty"
561
     *
562
     * @apiSuccessExample {json} Success-Response:
563
     * HTTP/1.1 200 OK
564
     * {
565
     *     "status": 200,
566
     *     "data": {
567
     *          "id": "544627ed3c58891f058b46cc",
568
     *          "name": "exampledir",
569
     *          "meta": {},
570
     *          "size": 3,
571
     *          "mime": "inode\/directory",
572
     *          "deleted": false,
573
     *          "changed": {
574
     *              "sec": 1413883885,
575
     *              "usec": 869000
576
     *          },
577
     *          "created": {
578
     *              "sec": 1413883885,
579
     *              "usec": 869000
580
     *          },
581
     *          "share": false,
582
     *          "directory": true
583
     *      }
584
     * }
585
     *
586
     * @param string $id
587
     * @param string $p
588
     * @param array  $attributes
589
     *
590
     * @return Response
591
     */
592
    public function getParent(?string $id = null, ?string $p = null, array $attributes = []): Response
593
    {
594
        $result = Helper::escape(
595
            $this->decorator->decorate($this->_getNode($id, $p)->getParent(), $attributes)
596
        );
597
598
        return (new Response())->setCode(200)->setBody($result);
599
    }
600
601
    /**
602
     * @api {get} /api/v1/node/parents?id=:id Get parent nodes
603
     * @apiVersion 1.0.0
604
     * @apiName getParents
605
     * @apiGroup Node
606
     * @apiPermission none
607
     * @apiDescription Get system attributes of all parent nodes. The hirarchy of all parent nodes is ordered in a
608
     * single level array beginning with the collection on the highest level.
609
     * @apiUse _getNode
610
     * @apiUse _nodeAttributes
611
     * @apiSuccess (200 OK) {object[]} data Nodes
612
     *
613
     * @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)
614
     *
615
     * @apiExample (cURL) example:
616
     * curl -XGET "https://SERVER/api/v1/node/parents?id=544627ed3c58891f058b4686&pretty"
617
     * curl -XGET "https://SERVER/api/v1/node/parents?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted&pretty"
618
     * curl -XGET "https://SERVER/api/v1/node/544627ed3c58891f058b4686/parents?pretty&self=1"
619
     * curl -XGET "https://SERVER/api/v1/node/parents?p=/absolute/path/to/my/node&self=1"
620
     *
621
     * @apiSuccessExample {json} Success-Response:
622
     * HTTP/1.1 200 OK
623
     * {
624
     *     "status": 200,
625
     *     "data": [
626
     * {
627
     *              "id": "544627ed3c58891f058bbbaa",
628
     *              "name": "rootdir",
629
     *              "meta": {},
630
     *              "size": 1,
631
     *              "mime": "inode\/directory",
632
     *              "deleted": false,
633
     *              "changed": {
634
     *                  "sec": 1413883880,
635
     *                  "usec": 869001
636
     *              },
637
     *              },
638
     *              "created": {
639
     *                  "sec": 1413883880,
640
     *                  "usec": 869001
641
     *              },
642
     *              "share": false,
643
     *              "directory": true
644
     *          },
645
     *          {
646
     *              "id": "544627ed3c58891f058b46cc",
647
     *              "name": "parentdir",
648
     *              "meta": {},
649
     *              "size": 3,
650
     *              "mime": "inode\/directory",
651
     *              "deleted": false,
652
     *              "changed": {
653
     *                  "sec": 1413883885,
654
     *                  "usec": 869000
655
     *              },
656
     *              "created": {
657
     *                  "sec": 1413883885,
658
     *                  "usec": 869000
659
     *              },
660
     *              "share": false,
661
     *              "directory": true
662
     *          }
663
     *      ]
664
     * }
665
     *
666
     * @param string $id
667
     * @param string $p
668
     * @param array  $attributes
669
     *
670
     * @return Response
671
     */
672
    public function getParents(?string $id = null, ?string $p = null, array $attributes = [], bool $self = false): Response
673
    {
674
        $request = $this->_getNode($id, $p);
675
        $parents = $request->getParents();
676
        $result = [];
677
678
        if (true === $self && $request instanceof Collection) {
679
            $result[] = $this->decorator->decorate($request, $attributes);
680
        }
681
682
        foreach ($parents as $node) {
683
            $result[] = $this->decorator->decorate($node, $attributes);
684
        }
685
686
        $result = Helper::escape($result);
687
688
        return (new Response())->setCode(200)->setBody($result);
689
    }
690
691
    /**
692
     * @api {post} /api/v1/node/meta-attributes?id=:id Write meta attributes
693
     * @apiVersion 1.0.0
694
     * @apiName postMetaAttributes
695
     * @apiGroup Node
696
     * @apiPermission none
697
     * @apiDescription Get meta attributes of a node
698
     * @apiUse _getNodes
699
     * @apiUse _multiError
700
     *
701
     * @apiParam (POST Parameter) {string} [description] UTF-8 Text Description
702
     * @apiParam (POST Parameter) {string} [color] Color Tag (HEX) (Like: #000000)
703
     * @apiParam (POST Parameter) {string} [author] Author
704
     * @apiParam (POST Parameter) {string} [mail] Mail contact address
705
     * @apiParam (POST Parameter) {string} [license] License
706
     * @apiParam (POST Parameter) {string} [opyright] Copyright string
707
     * @apiParam (POST Parameter) {string[]} [tags] Search Tags
708
     *
709
     * @apiExample (cURL) example:
710
     * curl -XPOST -d author=peter.mier -d license="GPLv2" "https://SERVER/api/v1/node/meta-attributes?id=544627ed3c58891f058b4686"
711
     * curl -XPOST -d author=authorname "https://SERVER/api/v1/node/544627ed3c58891f058b4686/meta-attributes"
712
     * curl -XPOST -d license="GPLv3" "https://SERVER/api/v1/node/meta-attributes?p=/absolute/path/to/my/node"
713
     *
714
     * @apiSuccessExample {json} Success-Response:
715
     * HTTP/1.1 204 No Content
716
     *
717
     * @param array|string $id
718
     * @param array|string $p
719
     *
720
     * @return Response
721
     */
722
    public function postMetaAttributes(?string $id = null, ?string $p = null): Response
723
    {
724
        if (is_array($id) || is_array($p)) {
725
            $failures = [];
726
            foreach ($this->_getNodes($id, $p) as $node) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 722 can also be of type array; however, Balloon\Api\v1\Node::_getNodes() does only seem to accept string|null, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
727
                try {
728
                    $node->setMetaAttribute(Helper::filter($_POST));
729
                } catch (\Exception $e) {
730
                    $failures[] = [
731
                        'id' => (string) $node->getId(),
732
                        'name' => $node->getName(),
733
                        'error' => get_class($e),
734
                        'message' => $e->getMessage(),
735
                        'code' => $e->getCode(),
736
                    ];
737
738
                    $this->logger->debug('failed set metta attributes on node in multi node request ['.$node->getId().']', [
739
                        'category' => get_class($this),
740
                        'exception' => $e,
741
                    ]);
742
                }
743
            }
744
745
            if (empty($failures)) {
746
                return (new Response())->setCode(204);
747
            }
748
749
            return (new Response())->setCode(400)->setBody($failures);
750
        }
751
752
        $this->_getNode($id, $p)->setMetaAttribute(Helper::filter($_POST));
753
754
        return (new Response())->setCode(204);
755
    }
756
757
    /**
758
     * @api {post} /api/v1/node/name?id=:id Rename node
759
     * @apiVersion 1.0.0
760
     * @apiName postName
761
     * @apiGroup Node
762
     * @apiPermission none
763
     * @apiDescription Rename a node. The characters (\ < > : " / * ? |) (without the "()") are not allowed to use within a node name.
764
     * @apiUse _getNode
765
     * @apiUse _writeAction
766
     *
767
     * @apiParam (GET Parameter) {string} [name] The new name of the node
768
     * @apiError (Error 400) Exception name contains invalid characters
769
     *
770
     * @apiExample (cURL) example:
771
     * curl -XPOST "https://SERVER/api/v1/node/name?id=544627ed3c58891f058b4686&name=newname.txt"
772
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4677/name?name=newdir"
773
     * curl -XPOST "https://SERVER/api/v1/node/name?p=/absolute/path/to/my/node&name=newname.txt"
774
     *
775
     * @apiSuccessExample {json} Success-Response:
776
     * HTTP/1.1 204 No Content
777
     *
778
     * @param string $id
779
     * @param string $p
780
     * @param string $name
781
     *
782
     * @return Response
783
     */
784
    public function postName(string $name, ?string $id = null, ?string $p = null): Response
785
    {
786
        $this->_getNode($id, $p)->setName($name);
787
788
        return (new Response())->setCode(204);
789
    }
790
791
    /**
792
     * @api {post} /api/v1/node/clone?id=:id Clone node
793
     * @apiVersion 1.0.0
794
     * @apiName postClone
795
     * @apiGroup Node
796
     * @apiPermission none
797
     * @apiDescription Clone a node
798
     * @apiUse _getNode
799
     * @apiUse _conflictNode
800
     * @apiUse _multiError
801
     * @apiUse _writeAction
802
     *
803
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
804
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
805
     *
806
     * @apiExample (cURL) example:
807
     * curl -XPOST "https://SERVER/api/v1/node/clone?id=544627ed3c58891f058b4686&dest=544627ed3c58891f058b4676"
808
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4686/clone?dest=544627ed3c58891f058b4676&conflict=2"
809
     * curl -XPOST "https://SERVER/api/v1/node/clone?p=/absolute/path/to/my/node&conflict=0&destp=/new/parent"
810
     *
811
     * @apiSuccessExample {json} Success-Response:
812
     * HTTP/1.1 204 No Content
813
     *
814
     * @param array|string $id
815
     * @param array|string $p
816
     * @param string       $destid
817
     * @param string       $destp
818
     * @param int          $conflict
819
     *
820
     * @return Response
821
     */
822
    public function postClone(
823
        $id = null,
824
        $p = null,
825
        ?string $destid = null,
826
        ?string $destp = null,
827
        int $conflict = 0
828
    ): Response {
829
        try {
830
            $parent = $this->_getNode($destid, $destp, Collection::class, false, true);
831
        } catch (Exception\NotFound $e) {
832
            throw new Exception\NotFound(
833
                'destination collection was not found or is not a collection',
834
                Exception\NotFound::DESTINATION_NOT_FOUND
835
            );
836
        }
837
838
        if (is_array($id) || is_array($p)) {
839
            $failures = [];
840
            foreach ($this->_getNodes($id, $p) as $node) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 823 can also be of type array; however, Balloon\Api\v1\Node::_getNodes() does only seem to accept string|null, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
841
                try {
842
                    $node->copyTo($parent, $conflict);
843
                } catch (\Exception $e) {
844
                    $failures[] = [
845
                        'id' => (string) $node->getId(),
846
                        'name' => $node->getName(),
847
                        'error' => get_class($e),
848
                        'message' => $e->getMessage(),
849
                        'code' => $e->getCode(),
850
                    ];
851
852
                    $this->logger->debug('failed clone node in multi node request ['.$node->getId().']', [
853
                        'category' => get_class($this),
854
                        'exception' => $e,
855
                    ]);
856
                }
857
            }
858
859
            if (empty($failures)) {
860
                return (new Response())->setCode(204);
861
            }
862
863
            return(new Response())->setCode(400)->setBody($failures);
864
        }
865
        $result = $this->_getNode($id, $p)->copyTo($parent, $conflict);
866
867
        return (new Response())->setCode(201)->setBody((string) $result->getId());
868
    }
869
870
    /**
871
     * @api {post} /api/v1/node/move?id=:id Move node
872
     * @apiVersion 1.0.0
873
     * @apiName postMove
874
     * @apiGroup Node
875
     * @apiPermission none
876
     * @apiDescription Move node
877
     * @apiUse _getNodes
878
     * @apiUse _conflictNode
879
     * @apiUse _multiError
880
     * @apiUse _writeAction
881
     *
882
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
883
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
884
     *
885
     * @apiExample (cURL) example:
886
     * curl -XPOST "https://SERVER/api/v1/node/move?id=544627ed3c58891f058b4686?destid=544627ed3c58891f058b4655"
887
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4686/move?destid=544627ed3c58891f058b4655"
888
     * curl -XPOST "https://SERVER/api/v1/node/move?p=/absolute/path/to/my/node&destp=/new/parent&conflict=1
889
     *
890
     * @apiSuccessExample {json} Success-Response:
891
     * HTTP/1.1 204 No Content
892
     *
893
     * @apiSuccessExample {json} Success-Response (conflict=1):
894
     * HTTP/1.1 200 OK
895
     * {
896
     *      "status":200,
897
     *      "data": "renamed (xy23)"
898
     * }
899
     *
900
     * @param array|string $id
901
     * @param array|string $p
902
     * @param string       $destid
903
     * @param string       $destp
904
     * @param int          $conflict
905
     *
906
     * @return Response
907
     */
908
    public function postMove(
909
        $id = null,
910
        $p = null,
911
        ?string $destid = null,
912
        ?string $destp = null,
913
        int $conflict = 0
914
    ): Response {
915
        try {
916
            $parent = $this->_getNode($destid, $destp, Collection::class, false, true);
917
        } catch (Exception\NotFound $e) {
918
            throw new Exception\NotFound(
919
                'destination collection was not found or is not a collection',
920
                Exception\NotFound::DESTINATION_NOT_FOUND
921
            );
922
        }
923
924
        if (is_array($id) || is_array($p)) {
925
            $failures = [];
926
            foreach ($this->_getNodes($id, $p) as $node) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 909 can also be of type array; however, Balloon\Api\v1\Node::_getNodes() does only seem to accept string|null, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
927
                try {
928
                    $node->setParent($parent, $conflict);
929
                } catch (\Exception $e) {
930
                    $failures[] = [
931
                        'id' => (string) $node->getId(),
932
                        'name' => $node->getName(),
933
                        'error' => get_class($e),
934
                        'message' => $e->getMessage(),
935
                        'code' => $e->getCode(),
936
                    ];
937
938
                    $this->logger->debug('failed move node in multi node request ['.$node->getId().']', [
939
                        'category' => get_class($this),
940
                        'exception' => $e,
941
                    ]);
942
                }
943
            }
944
945
            if (empty($failures)) {
946
                return (new Response())->setCode(204);
947
            }
948
949
            return (new Response())->setCode(400)->setBody($failures);
950
        }
951
        $node = $this->_getNode($id, $p);
952
        $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...
953
954
        if (NodeInterface::CONFLICT_RENAME === $conflict) {
955
            return (new Response())->setCode(200)->setBody($node->getName());
0 ignored issues
show
Bug introduced by
Consider using $node->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
956
        }
957
958
        return (new Response())->setCode(204);
959
    }
960
961
    /**
962
     * @api {delete} /api/v1/node?id=:id Delete node
963
     * @apiVersion 1.0.0
964
     * @apiName delete
965
     * @apiGroup Node
966
     * @apiPermission none
967
     * @apiDescription Delete node
968
     * @apiUse _getNodes
969
     * @apiUse _multiError
970
     * @apiUse _writeAction
971
     *
972
     * @apiParam (GET Parameter) {boolean} [force=false] Force flag need to be set to delete a node from trash (node must have the deleted flag)
973
     * @apiParam (GET Parameter) {boolean} [ignore_flag=false] If both ignore_flag and force_flag were set, the node will be deleted completely
974
     * @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
975
     *
976
     * @apiExample (cURL) example:
977
     * curl -XDELETE "https://SERVER/api/v1/node?id=544627ed3c58891f058b4686"
978
     * curl -XDELETE "https://SERVER/api/v1/node/544627ed3c58891f058b4686?force=1&ignore_flag=1"
979
     * curl -XDELETE "https://SERVER/api/v1/node?p=/absolute/path/to/my/node"
980
     *
981
     * @apiSuccessExample {json} Success-Response:
982
     * HTTP/1.1 204 No Content
983
     *
984
     * @param array|string $id
985
     * @param array|string $p
986
     * @param bool         $force
987
     * @param bool         $ignore_flag
988
     * @param int          $at
989
     *
990
     * @return Response
991
     */
992
    public function delete(
993
        $id = null,
994
        $p = null,
995
        bool $force = false,
996
        bool $ignore_flag = false,
997
        ?string $at = null
998
    ): Response {
999
        $failures = [];
1000
1001
        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...
1002
            $at = $this->_verifyAttributes(['destroy' => $at])['destroy'];
1003
        }
1004
1005
        if (is_array($id) || is_array($p)) {
1006
            foreach ($this->_getNodes($id, $p) as $node) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 993 can also be of type array; however, Balloon\Api\v1\Node::_getNodes() does only seem to accept string|null, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
1007
                try {
1008
                    if (null === $at) {
1009
                        $node->delete($force && $node->isDeleted() || $force && $ignore_flag);
1010
                    } else {
1011
                        if ('0' === $at) {
1012
                            $at = null;
1013
                        }
1014
                        $node->setDestroyable($at);
1015
                    }
1016
                } catch (\Exception $e) {
1017
                    $failures[] = [
1018
                        'id' => (string) $node->getId(),
1019
                        'name' => $node->getName(),
1020
                        'error' => get_class($e),
1021
                        'message' => $e->getMessage(),
1022
                        'code' => $e->getCode(),
1023
                    ];
1024
1025
                    $this->logger->debug('failed delete node in multi node request ['.$node->getId().']', [
1026
                        'category' => get_class($this),
1027
                        'exception' => $e,
1028
                    ]);
1029
                }
1030
            }
1031
1032
            if (empty($failures)) {
1033
                return (new Response())->setCode(204);
1034
            }
1035
1036
            return (new Response())->setcode(400)->setBody($failures);
1037
        }
1038
        if (null === $at) {
1039
            $result = $this->_getNode($id, $p)->delete($force);
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...
1040
        } else {
1041
            if ('0' === $at) {
1042
                $at = null;
1043
            }
1044
1045
            $result = $this->_getNode($id, $p)->setDestroyable($at);
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...
1046
        }
1047
1048
        return (new Response())->setCode(204);
1049
    }
1050
1051
    /**
1052
     * @api {get} /api/v1/node/query Custom query
1053
     * @apiVersion 1.0.0
1054
     * @apiName getQuery
1055
     * @apiGroup Node
1056
     * @apiPermission none
1057
     * @apiDescription A custom query is similar requet to children. You do not have to provide any parent node (id or p)
1058
     * 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
1059
     * (search) but does not use the search engine like GET /node/search does. You can also create a persistent query collection, just look at
1060
     * 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.
1061
     * 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.
1062
     * @apiUse _nodeAttributes
1063
     *
1064
     * @apiExample (cURL) example:
1065
     * curl -XGET https://SERVER/api/v1/node/query?{%22filter%22:{%22shared%22:true,%22reference%22:{%22$exists%22:0}}}
1066
     *
1067
     * @apiParam (GET Parameter) {string[]} [attributes] Filter node attributes
1068
     * @apiParam (GET Parameter) {string[]} [filter] Filter nodes
1069
     * @apiParam (GET Parameter) {number} [deleted=0] Wherever include deleted nodes or not, possible values:</br>
1070
     * - 0 Exclude deleted</br>
1071
     * - 1 Only deleted</br>
1072
     * - 2 Include deleted</br>
1073
     *
1074
     * @apiSuccess (200 OK) {number} status Status Code
1075
     * @apiSuccess (200 OK) {object[]} data Children
1076
     * @apiSuccessExample {json} Success-Response:
1077
     * HTTP/1.1 200 OK
1078
     * {
1079
     *      "status":200,
1080
     *      "data": [{..}, {...}] //Shorted
1081
     * }
1082
     *
1083
     * @param int   $deleted
1084
     * @param array $filter
1085
     * @param array $attributes
1086
     *
1087
     * @return Response
1088
     */
1089
    public function getQuery(int $deleted = 0, array $filter = [], array $attributes = []): Response
1090
    {
1091
        $children = [];
1092
        $nodes = $this->fs->findNodesByFilterUser($deleted, $filter);
1093
1094
        foreach ($nodes as $node) {
1095
            $child = Helper::escape($node->getAttributes($attributes));
1096
            $children[] = $child;
1097
        }
1098
1099
        return (new Response())->setCode(200)->setBody($children);
1100
    }
1101
1102
    /**
1103
     * @api {get} /api/v1/node/trash Get trash
1104
     * @apiName getTrash
1105
     * @apiVersion 1.0.0
1106
     * @apiGroup Node
1107
     * @apiPermission none
1108
     * @apiDescription A similar endpoint to /api/v1/node/query filer={'deleted': {$type: 9}] but instead returning all deleted
1109
     * nodes (including children which are deleted as well) this enpoint only returns the first deleted node from every subtree)
1110
     * @apiUse _nodeAttributes
1111
     *
1112
     * @apiExample (cURL) example:
1113
     * curl -XGET https://SERVER/api/v1/node/trash?pretty
1114
     *
1115
     * @apiParam (GET Parameter) {string[]} [attributes] Filter node attributes
1116
     *
1117
     * @apiSuccess (200 OK) {number} status Status Code
1118
     * @apiSuccess (200 OK) {object[]} data Children
1119
     * @apiSuccessExample {json} Success-Response:
1120
     * HTTP/1.1 200 OK
1121
     * {
1122
     *      "status":200,
1123
     *      "data": [{..}, {...}] //Shorted
1124
     * }
1125
     *
1126
     * @param array $attributes
1127
     *
1128
     * @return Response
1129
     */
1130
    public function getTrash(array $attributes = []): Response
1131
    {
1132
        $children = [];
1133
        $nodes = $this->fs->findNodesByFilterUser(NodeInterface::DELETED_ONLY, ['deleted' => ['$type' => 9]]);
1134
1135
        foreach ($nodes as $node) {
1136
            try {
1137
                $parent = $node->getParent();
1138
                if (null !== $parent && $parent->isDeleted()) {
1139
                    continue;
1140
                }
1141
            } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1142
            }
1143
1144
            $child = Helper::escape($node->getAttributes($attributes));
1145
            $children[] = $child;
1146
        }
1147
1148
        return (new Response())->setCode(200)->setBody(array_values($children));
1149
    }
1150
1151
    /**
1152
     * @api {get} /api/v1/node/delta Get delta
1153
     * @apiVersion 1.0.0
1154
     * @apiName getDelta
1155
     * @apiGroup Node
1156
     * @apiPermission none
1157
     * @apiUse _getNode
1158
     *
1159
     * @apiDescription Use this method to request a delta feed with all changes on the server (or) a snapshot of the server state.
1160
     * 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.
1161
     * If has_more is TRUE you need to request /delta immediatly again to
1162
     * receive the next bunch of deltas. If has_more is FALSE you should wait at least 120s seconds before any further requests to the
1163
     * 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).
1164
     * 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
1165
     * without a cursor. reset could be TRUE if there was an account maintenance or a simialar case.
1166
     * You can request a different limit as well but be aware that the number of nodes could be slighty different from your requested limit.
1167
     * If requested with parameter id or p the delta gets generated recursively from the node given.
1168
     *
1169
     * @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
1170
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default not all attributes would be returned
1171
     * @apiParam (GET Parameter) {string} [cursor=null] Set a cursor to rquest next nodes within delta processing
1172
     *
1173
     * @apiExample (cURL) example:
1174
     * curl -XGET "https://SERVER/api/v1/node/delta?pretty"
1175
     *
1176
     * @apiSuccess (200 OK) {number} status Status Code
1177
     * @apiSuccess (200 OK) {object} data Delta feed
1178
     * @apiSuccess (200 OK) {boolean} data.reset If true the local state needs to be reseted, is alway TRUE during
1179
     * the first request to /delta without a cursor or in special cases like server or account maintenance
1180
     * @apiSuccess (200 OK) {string} data.cursor The cursor needs to be stored and reused to request further deltas
1181
     * @apiSuccess (200 OK) {boolean} data.has_more If has_more is TRUE /delta can be requested immediatly after the last request
1182
     * to receive further delta. If it is FALSE we should wait at least 120 seconds before any further delta requests to the api endpoint
1183
     * @apiSuccess (200 OK) {object[]} data.nodes Node list to process
1184
     * @apiSuccess (200 OK) {string} data.nodes.id Node ID
1185
     * @apiSuccess (200 OK) {string} data.nodes.deleted Is node deleted?
1186
     * @apiSuccess (200 OK) {object} data.nodes.changed Changed timestamp
1187
     * @apiSuccess (200 OK) {number} data.nodes.changed.sec Unix timestamp
1188
     * @apiSuccess (200 OK) {number} data.nodes.changed.usec Additional Microsecconds to Unix timestamp
1189
     * @apiSuccess (200 OK) {object} data.nodes.created Created timestamp (If data.nodes[].deleted is TRUE, created will be NULL)
1190
     * @apiSuccess (200 OK) {number} data.nodes.created.sec Unix timestamp
1191
     * @apiSuccess (200 OK) {number} data.nodes.created.usec Additional Microsecconds to Unix timestamp
1192
     * @apiSuccess (200 OK) {string} data.nodes.path The full absolute path to the node
1193
     * @apiSuccess (200 OK) {string} data.nodes.directory Is true if node is a directory
1194
     * @apiSuccessExample {json} Success-Response:
1195
     * HTTP/1.1 200 OK
1196
     * {
1197
     *      "status": 200,
1198
     *      "data": {
1199
     *          "reset": false,
1200
     *          "cursor": "aW5pdGlhbHwxMDB8NTc1YTlhMGIzYzU4ODkwNTE0OGI0NTZifDU3NWE5YTBiM2M1ODg5MDUxNDhiNDU2Yw==",
1201
     *          "has_more": false,
1202
     *          "nodes": [
1203
     *             {
1204
     *                  "id": "581afa783c5889ad7c8b4572",
1205
     *                  "deleted": true,
1206
     *                  "created": null,
1207
     *                  "changed": {
1208
     *                      "sec": 1478163064,
1209
     *                      "usec": 317000
1210
     *                  },
1211
     *                  "path": "\/AAA\/AL",
1212
     *                  "directory": true
1213
     *              },
1214
     *              {
1215
     *                  "id": "581afa783c5889ad7c8b3dcf",
1216
     *                  "deleted": false,
1217
     *                  "created": {
1218
     *                      "sec": 1478163048,
1219
     *                      "usec": 101000
1220
     *                  },
1221
     *                  "changed": {
1222
     *                      "sec": 1478163048,
1223
     *                      "usec": 101000
1224
     *                  },
1225
     *                  "path": "\/AL",
1226
     *                  "directory": true
1227
     *              }
1228
     *          ]
1229
     *      }
1230
     * }
1231
     *
1232
     * @param string $id
1233
     * @param string $p
1234
     * @param string $cursor
1235
     * @param int    $limit
1236
     * @param array  $attributes
1237
     *
1238
     * @return Response
1239
     */
1240
    public function getDelta(
1241
        ?string $id = null,
1242
        ?string $p = null,
1243
        ?string $cursor = null,
1244
        int $limit = 250,
1245
        array $attributes = []
1246
    ): Response {
1247
        if (null !== $id || null !== $p) {
1248
            $node = $this->_getNode($id, $p);
1249
        } else {
1250
            $node = null;
1251
        }
1252
1253
        $result = $this->fs->getDelta()->getDeltaFeed($cursor, $limit, $attributes, $node);
1254
1255
        return (new Response())->setCode(200)->setBody($result);
1256
    }
1257
1258
    /**
1259
     * @api {get} /api/v1/node/event-log?id=:id Event log
1260
     * @apiVersion 1.0.0
1261
     * @apiName getEventLog
1262
     * @apiGroup Node
1263
     * @apiPermission none
1264
     * @apiUse _getNode
1265
     * @apiDescription Get detailed event log
1266
     * Request all modifications which are made by the user himself or share members.
1267
     * Possible operations are the follwing:
1268
     * - deleteCollectionReference
1269
     * - deleteCollectionShare
1270
     * - deleteCollection
1271
     * - addCollection
1272
     * - addFile
1273
     * - addCollectionShare
1274
     * - addCollectionReference
1275
     * - undeleteFile
1276
     * - undeleteCollectionReference
1277
     * - undeleteCollectionShare
1278
     * - restoreFile
1279
     * - renameFile
1280
     * - renameCollection
1281
     * - renameCollectionShare
1282
     * - renameCollectionRFeference
1283
     * - copyFile
1284
     * - copyCollection
1285
     * - copyCollectionShare
1286
     * - copyCollectionRFeference
1287
     * - moveFile
1288
     * - moveCollection
1289
     * - moveCollectionReference
1290
     * - moveCollectionShare
1291
     *
1292
     * @apiExample (cURL) example:
1293
     * curl -XGET "https://SERVER/api/v1/node/event-log?pretty"
1294
     * curl -XGET "https://SERVER/api/v1/node/event-log?id=544627ed3c58891f058b4686&pretty"
1295
     * curl -XGET "https://SERVER/api/v1/node/544627ed3c58891f058b4686/event-log?pretty&limit=10"
1296
     * curl -XGET "https://SERVER/api/v1/node/event-log?p=/absolute/path/to/my/node&pretty"
1297
     *
1298
     * @apiParam (GET Parameter) {number} [limit=100] Sets limit of events to be returned
1299
     * @apiParam (GET Parameter) {number} [skip=0] How many events are skiped (useful for paging)
1300
     *
1301
     * @apiSuccess (200 OK) {number} status Status Code
1302
     * @apiSuccess (200 OK) {object[]} data Events
1303
     * @apiSuccess (200 OK) {number} data.event Event ID
1304
     * @apiSuccess (200 OK) {object} data.timestamp event timestamp
1305
     * @apiSuccess (200 OK) {number} data.timestamp.sec Event timestamp timestamp in Unix time
1306
     * @apiSuccess (200 OK) {number} data.timestamp.usec Additional microseconds to changed Unix timestamp
1307
     * @apiSuccess (200 OK) {string} data.operation event operation (like addCollection, deleteFile, ...)
1308
     * @apiSuccess (200 OK) {string} data.parent ID of the parent node at the time of the event
1309
     * @apiSuccess (200 OK) {object} data.previous Previous state of actual data which has been modified during an event, can contain either version, name or parent
1310
     * @apiSuccess (200 OK) {number} data.previous.version Version at the time before the event
1311
     * @apiSuccess (200 OK) {string} data.previous.name Name at the time before the event
1312
     * @apiSuccess (200 OK) {string} data.previous.parent Parent node at the time before the event
1313
     * @apiSuccess (200 OK) {string} data.share If of the shared folder at the time of the event
1314
     * @apiSuccess (200 OK) {string} data.name Name of the node at the time of the event
1315
     * @apiSuccess (200 OK) {object} data.node Current data of the node (Not from the time of the event!)
1316
     * @apiSuccess (200 OK) {boolean} data.node.deleted True if the node is deleted, false otherwise
1317
     * @apiSuccess (200 OK) {string} data.node.id Actual ID of the node
1318
     * @apiSuccess (200 OK) {string} data.node.name Current name of the node
1319
     * @apiSuccess (200 OK) {object} data.user Data which contains information about the user who executed an event
1320
     * @apiSuccess (200 OK) {string} data.user.id Actual user ID
1321
     * @apiSuccess (200 OK) {string} data.user.username Current username of executed event
1322
     *
1323
     * @apiSuccessExample {json} Success-Response:
1324
     * HTTP/1.1 200 OK
1325
     * {
1326
     *      "status": 200,
1327
     *      "data": [
1328
     *          {
1329
     *              "event": "57628e523c5889026f8b4570",
1330
     *              "timestamp": {
1331
     *                  "sec": 1466076753,
1332
     *                  "usec": 988000
1333
     *              },
1334
     *              "operation": "restoreFile",
1335
     *              "name": "file.txt",
1336
     *              "previous": {
1337
     *                  "version": 16
1338
     *              },
1339
     *              "node": {
1340
     *                  "id": "558c0b273c588963078b457a",
1341
     *                  "name": "3dddsceheckfile.txt",
1342
     *                  "deleted": false
1343
     *              },
1344
     *              "parent": null,
1345
     *              "user": {
1346
     *                  "id": "54354cb63c58891f058b457f",
1347
     *                  "username": "gradmin.bzu"
1348
     *              },
1349
     *              "share": null
1350
     *          }
1351
     *      ]
1352
     * }
1353
     *
1354
     * @param string $id
1355
     * @param string $p
1356
     * @param int    $skip
1357
     * @param int    $limit
1358
     *
1359
     * @return Response
1360
     */
1361
    public function getEventLog(?string $id = null, ?string $p = null, int $skip = 0, int $limit = 100): Response
1362
    {
1363
        if (null !== $id || null !== $p) {
1364
            $node = $this->_getNode($id, $p);
1365
        } else {
1366
            $node = null;
1367
        }
1368
1369
        $result = $this->fs->getDelta()->getEventLog($limit, $skip, $node);
1370
1371
        return (new Response())->setCode(200)->setBody($result);
1372
    }
1373
1374
    /**
1375
     * @api {get} /api/v1/node/last-cursor Get last Cursor
1376
     * @apiVersion 1.0.0
1377
     * @apiName geLastCursor
1378
     * @apiGroup Node
1379
     * @apiUse _getNode
1380
     * @apiPermission none
1381
     * @apiDescription Use this method to request the latest cursor if you only need to now
1382
     * if there are changes on the server. This method will not return any other data than the
1383
     * newest cursor. To request a feed with all deltas request /delta.
1384
     *
1385
     * @apiExample (cURL) example:
1386
     * curl -XGET "https://SERVER/api/v1/node/last-cursor?pretty"
1387
     *
1388
     * @apiSuccess (200 OK) {number} status Status Code
1389
     * @apiSuccess (200 OK) {string} data Newest cursor
1390
     * @apiSuccessExample {json} Success-Response:
1391
     * HTTP/1.1 200 OK
1392
     * {
1393
     *      "status": 200,
1394
     *      "data": "aW5pdGlhbHwxMDB8NTc1YTlhMGIzYzU4ODkwNTE0OGI0NTZifDU3NWE5YTBiM2M1ODg5MDUxNDhiNDU2Yw=="
1395
     * }
1396
     *
1397
     * @param string $id
1398
     * @param string $p
1399
     *
1400
     * @return Response
1401
     */
1402
    public function getLastCursor(?string $id = null, ?string $p = null): Response
1403
    {
1404
        if (null !== $id || null !== $p) {
1405
            $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...
1406
        } else {
1407
            $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...
1408
        }
1409
1410
        $result = $this->fs->getDelta()->getLastCursor();
1411
1412
        return (new Response())->setCode(200)->setBody($result);
1413
    }
1414
1415
    /**
1416
     * Get node.
1417
     *
1418
     * @param string $id
1419
     * @param string $path
1420
     * @param string $class      Force set node type
1421
     * @param bool   $multiple   Allow $id to be an array
1422
     * @param bool   $allow_root Allow instance of root collection
1423
     * @param bool   $deleted    How to handle deleted node
1424
     *
1425
     * @return NodeInterface
1426
     */
1427
    protected function _getNode(
1428
        ?string $id = null,
1429
        ?string $path = null,
1430
        ?string $class = null,
1431
        bool $multiple = false,
1432
        bool $allow_root = false,
1433
        int $deleted = 2
1434
    ): NodeInterface {
1435
        if (null === $class) {
1436
            switch (get_class($this)) {
1437
                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...
1438
                    $class = File::class;
1439
1440
                break;
1441
                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...
1442
                    $class = Collection::class;
1443
1444
                break;
1445
            }
1446
        }
1447
1448
        return $this->fs->getNode($id, $path, $class, $multiple, $allow_root, $deleted);
1449
    }
1450
1451
    /**
1452
     * Get nodes.
1453
     *
1454
     * @param string $id
1455
     * @param string $path
1456
     * @param string $class   Force set node type
1457
     * @param bool   $deleted How to handle deleted node
1458
     *
1459
     * @return Generator
1460
     */
1461
    protected function _getNodes(
1462
        $id = null,
1463
        $path = null,
1464
        ?string $class = null,
1465
        int $deleted = 2
1466
    ): Generator {
1467
        if (null === $class) {
1468
            switch (get_class($this)) {
1469
                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...
1470
                    $class = File::class;
1471
1472
                break;
1473
                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...
1474
                    $class = Collection::class;
1475
1476
                break;
1477
            }
1478
        }
1479
1480
        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 1462 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 1463 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...
1481
    }
1482
1483
    /**
1484
     * Merge multiple nodes into one zip archive.
1485
     *
1486
     * @param string $id
1487
     * @param string $path
1488
     * @param string $name
1489
     */
1490
    protected function combine($id = null, $path = null, string $name = 'selected')
1491
    {
1492
        $temp = $this->server->getTempDir().DIRECTORY_SEPARATOR.'zip';
1493
        if (!file_exists($temp)) {
1494
            mkdir($temp, 0700, true);
1495
        }
1496
1497
        ZipStream::$temp = $temp;
1498
        $archive = new ZipStream($name.'.zip', 'application/zip', $name.'.zip');
1499
1500
        foreach ($this->_getNodes($id, $path) as $node) {
1501
            try {
1502
                $node->zip($archive);
1503
            } catch (\Exception $e) {
1504
                $this->logger->debug('failed zip node in multi node request ['.$node->getId().']', [
1505
                   'category' => get_class($this),
1506
                   'exception' => $e,
1507
               ]);
1508
            }
1509
        }
1510
1511
        $archive->finalize();
1512
    }
1513
1514
    /**
1515
     * Check custom node attributes which have to be written.
1516
     *
1517
     * @param array $attributes
1518
     *
1519
     * @return array
1520
     */
1521
    protected function _verifyAttributes(array $attributes): array
1522
    {
1523
        $valid_attributes = [
1524
            'changed',
1525
            'destroy',
1526
            'created',
1527
            'meta',
1528
            'readonly',
1529
        ];
1530
1531
        if ($this instanceof ApiCollection) {
1532
            $valid_attributes[] = 'filter';
1533
        }
1534
1535
        $check = array_merge(array_flip($valid_attributes), $attributes);
1536
1537
        if ($this instanceof ApiCollection && count($check) > 6) {
1538
            throw new Exception\InvalidArgument('Only changed, created, destroy timestamp, filter, readonly and/or meta attributes may be overwritten');
1539
        }
1540
        if ($this instanceof ApiFile && count($check) > 5) {
1541
            throw new Exception\InvalidArgument('Only changed, created, destroy timestamp, readonly and/or meta attributes may be overwritten');
1542
        }
1543
1544
        foreach ($attributes as $attribute => $value) {
1545
            switch ($attribute) {
1546
                case 'meta':
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...
1547
                    $attributes['meta'] = AbstractNode::validateMetaAttribute($attributes['meta']);
1548
1549
                break;
1550
                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...
1551
                    $attributes['filter'] = json_encode((array) $attributes['filter']);
1552
1553
                break;
1554
                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...
1555
                    if (!Helper::isValidTimestamp($value)) {
1556
                        throw new Exception\InvalidArgument($attribute.' Changed timestamp must be valid unix timestamp');
1557
                    }
1558
                    $attributes[$attribute] = new UTCDateTime($value.'000');
1559
1560
                break;
1561
                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...
1562
                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...
1563
                    if (!Helper::isValidTimestamp($value)) {
1564
                        throw new Exception\InvalidArgument($attribute.' Changed timestamp must be valid unix timestamp');
1565
                    }
1566
                    if ((int) $value > time()) {
1567
                        throw new Exception\InvalidArgument($attribute.' timestamp can not be set greater than the server time');
1568
                    }
1569
                    $attributes[$attribute] = new UTCDateTime($value.'000');
1570
1571
                break;
1572
                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...
1573
                    $attributes['readonly'] = (bool) $attributes['readonly'];
1574
1575
                break;
1576
            }
1577
        }
1578
1579
        return $attributes;
1580
    }
1581
}
1582