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

Node   F

Complexity

Total Complexity 103

Size/Duplication

Total Lines 1424
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 0
Metric Value
wmc 103
lcom 1
dl 0
loc 1424
rs 0.6314
c 0
b 0
f 0
cbo 16

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A head() 0 15 2
C postUndelete() 0 40 7
C get() 0 78 15
A postReadonly() 0 8 1
A getAttributes() 0 21 4
A getParents() 0 19 4
A postMetaAttributes() 0 8 1
A postName() 0 6 1
B postClone() 0 25 2
B postMove() 0 30 3
C delete() 0 28 8
A getQuery() 0 15 2
B getTrash() 0 23 5
B getDelta() 0 29 5
A getEventLog() 0 19 4
A getLastCursor() 0 15 3
C bulk() 0 52 9
B _getNode() 0 23 4
A _getNodes() 0 21 4
A combine() 0 17 3
C _verifyAttributes() 0 56 15

How to fix   Complexity   

Complex Class

Complex classes like Node often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Node, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2018 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Balloon\App\Api\v1;
13
14
use Balloon\App\Api\Controller;
15
use Balloon\App\Api\v1\AttributeDecorator\DeltaDecorator;
16
use Balloon\App\Api\v1\AttributeDecorator\EventDecorator;
17
use Balloon\App\Api\v1\AttributeDecorator\NodeDecorator;
18
use Balloon\App\Api\v1\Collection as ApiCollection;
19
use Balloon\App\Api\v1\File as ApiFile;
20
use Balloon\Exception;
21
use Balloon\Filesystem;
22
use Balloon\Filesystem\Node\Collection;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Balloon\App\Api\v1\Collection.

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
272
        }
273
274
        $node = $this->_getNode($id, $p);
275
        if ($node instanceof Collection) {
276
            return (new Response())->setBody(function () use ($node) {
277
                $node->getZip();
278
            });
279
        }
280
281
        $response = new Response();
282
283
        if (true === $download) {
284
            $response->setHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\''.rawurlencode($name));
285
            $response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
286
            $response->setHeader('Content-Type', 'application/octet-stream');
287
            $response->setHeader('Content-Length', (string) $node->getSize());
288
            $response->setHeader('Content-Transfer-Encoding', 'binary');
289
        } else {
290
            $response->setHeader('Content-Disposition', 'inline; filename*=UTF-8\'\''.rawurlencode($name));
291
        }
292
293
        return (new Response())
294
          ->setOutputFormat(null)
295
          ->setBody(function () use ($node, $encode, $offset, $length) {
296
              $mime = $node->getContentType();
297
              $stream = $node->get();
298
              $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...
299
300
              if (null === $stream) {
301
                  return;
302
              }
303
304
              if (0 !== $offset) {
305
                  if (fseek($stream, $offset) === -1) {
306
                      throw new Exception\Conflict(
307
                        'invalid offset requested',
308
                        Exception\Conflict::INVALID_OFFSET
309
                    );
310
                  }
311
              }
312
313
              $read = 0;
314
              header('Content-Type: '.$mime.'');
315
              if ('base64' === $encode) {
316
                  header('Content-Encoding: base64');
317
                  while (!feof($stream)) {
318
                      if (0 !== $length && $read + 8192 > $length) {
319
                          echo base64_encode(fread($stream, $length - $read));
320
                          exit();
321
                      }
322
323
                      echo base64_encode(fread($stream, 8192));
324
                      $read += 8192;
325
                  }
326
              } else {
327
                  while (!feof($stream)) {
328
                      if (0 !== $length && $read + 8192 > $length) {
329
                          echo fread($stream, $length - $read);
330
                          exit();
331
                      }
332
333
                      echo fread($stream, 8192);
334
                      $read += 8192;
335
                  }
336
              }
337
          });
338
    }
339
340
    /**
341
     * @api {post} /api/v1/node/readonly?id=:id Mark node as readonly
342
     * @apiVersion 1.0.0
343
     * @apiName postReadonly
344
     * @apiGroup Node
345
     * @apiPermission none
346
     * @apiDescription Mark (or unmark) node as readonly
347
     * @apiUse _getNodes
348
     * @apiUse _multiError
349
     * @apiUse _writeAction
350
     *
351
     * @apiExample (cURL) example:
352
     * curl -XPOST "https://SERVER/api/v1/node/readonly?id[]=544627ed3c58891f058b4686&id[]=544627ed3c58891f058b46865&readonly=1"
353
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4686/readonly?readonly=0"
354
     * curl -XPOST "https://SERVER/api/v1/node/readonly?p=/absolute/path/to/my/node"
355
     *
356
     * @apiParam (GET Parameter) {bool} [readonly=true] Set readonly to false to make node writeable again
357
     *
358
     * @apiSuccessExample {json} Success-Response:
359
     * HTTP/1.1 204 No Content
360
     *
361
     * @param array|string $id
362
     * @param array|string $p
363
     *
364
     * @return Response
365
     */
366
    public function postReadonly($id = null, $p = null, bool $readonly = true): Response
367
    {
368
        return $this->bulk($id, $p, function ($node) use ($readonly) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 366 can also be of type null; however, Balloon\App\Api\v1\Node::bulk() does only seem to accept array|string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
369
            $node->setReadonly($readonly);
370
371
            return ['code' => 204];
372
        });
373
    }
374
375
    /**
376
     * @apiDefine _nodeAttributes
377
     *
378
     * @apiSuccess (200 OK) {number} status Status Code
379
     * @apiSuccess (200 OK) {object} data Attributes
380
     * @apiSuccess (200 OK) {string} data.id Unique node id
381
     * @apiSuccess (200 OK) {string} data.name Name
382
     * @apiSuccess (200 OK) {string} data.hash MD5 content checksum (file node only)
383
     * @apiSuccess (200 OK) {object} data.meta Extended meta attributes
384
     * @apiSuccess (200 OK) {string} data.meta.description UTF-8 Text Description
385
     * @apiSuccess (200 OK) {string} data.meta.color Color Tag (HEX) (Like: #000000)
386
     * @apiSuccess (200 OK) {string} data.meta.author Author
387
     * @apiSuccess (200 OK) {string} data.meta.mail Mail contact address
388
     * @apiSuccess (200 OK) {string} data.meta.license License
389
     * @apiSuccess (200 OK) {string} data.meta.copyright Copyright string
390
     * @apiSuccess (200 OK) {string[]} data.meta.tags Search Tags
391
     * @apiSuccess (200 OK) {number} data.size Size in bytes (Only file node), number of children if collection
392
     * @apiSuccess (200 OK) {string} data.mime Mime type
393
     * @apiSuccess (200 OK) {boolean} data.sharelink Is node shared?
394
     * @apiSuccess (200 OK) {number} data.version File version (file node only)
395
     * @apiSuccess (200 OK) {mixed} data.deleted Is boolean false if not deleted, if deleted it contains a deleted timestamp
396
     * @apiSuccess (200 OK) {number} data.deleted.sec Unix timestamp
397
     * @apiSuccess (200 OK) {number} data.deleted.usec Additional Microsecconds to Unix timestamp
398
     * @apiSuccess (200 OK) {object} data.changed Changed timestamp
399
     * @apiSuccess (200 OK) {number} data.changed.sec Unix timestamp
400
     * @apiSuccess (200 OK) {number} data.changed.usec Additional Microsecconds to Unix timestamp
401
     * @apiSuccess (200 OK) {object} data.created Created timestamp
402
     * @apiSuccess (200 OK) {number} data.created.sec Unix timestamp
403
     * @apiSuccess (200 OK) {number} data.created.usec Additional Microsecconds to Unix timestamp
404
     * @apiSuccess (200 OK) {boolean} data.share Node is shared
405
     * @apiSuccess (200 OK) {boolean} data.directory Is node a collection or a file?
406
     *
407
     * @apiSuccess (200 OK - additional attributes) {string} data.thumbnail Id of preview (file node only)
408
     * @apiSuccess (200 OK - additional attributes) {string} data.access Access if node is shared, one of r/rw/w
409
     * @apiSuccess (200 OK - additional attributes) {string} data.shareowner Username of the share owner
410
     * @apiSuccess (200 OK - additional attributes) {string} data.parent ID of the parent node
411
     * @apiSuccess (200 OK - additional attributes) {string} data.path Absolute node path
412
     * @apiSuccess (200 OK - additional attributes) {boolean} data.filtered Node is filtered (usually only a collection)
413
     * @apiSuccess (200 OK - additional attributes) {boolean} data.readonly Node is readonly
414
     *
415
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default not all attributes would be returned
416
     *
417
     * @param null|mixed $id
418
     * @param null|mixed $p
419
     */
420
421
    /**
422
     * @api {get} /api/v1/node/attributes?id=:id Get attributes
423
     * @apiVersion 1.0.0
424
     * @apiName getAttributes
425
     * @apiGroup Node
426
     * @apiPermission none
427
     * @apiDescription Get attributes from one or multiple nodes
428
     * @apiUse _getNode
429
     * @apiUse _nodeAttributes
430
     *
431
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default only a bunch of attributes would be returned, if you
432
     * need other attributes you have to request them (for example "path")
433
     *
434
     * @apiExample (cURL) example:
435
     * curl -XGET "https://SERVER/api/v1/node/attributes?id=544627ed3c58891f058b4686&pretty"
436
     * curl -XGET "https://SERVER/api/v1/node/attributes?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted&pretty"
437
     * curl -XGET "https://SERVER/api/v1/node/544627ed3c58891f058b4686/attributes?pretty"
438
     * curl -XGET "https://SERVER/api/v1/node/attributes?p=/absolute/path/to/my/node&pretty"
439
     *
440
     * @apiSuccessExample {json} Success-Response:
441
     * HTTP/1.1 200 OK
442
     * {
443
     *     "status": 200,
444
     *     "data": {
445
     *          "id": "544627ed3c58891f058b4686",
446
     *          "name": "api.php",
447
     *          "hash": "a77f23ed800fd7a600a8c2cfe8cc370b",
448
     *          "meta": {
449
     *              "license": "GPLv3"
450
     *          },
451
     *          "size": 178,
452
     *          "mime": "text\/plain",
453
     *          "sharelink": true,
454
     *          "version": 1,
455
     *          "deleted": false,
456
     *          "changed": {
457
     *              "sec": 1413883885,
458
     *              "usec": 869000
459
     *          },
460
     *          "created": {
461
     *              "sec": 1413883885,
462
     *              "usec": 869000
463
     *          },
464
     *          "share": false,
465
     *          "directory": false
466
     *      }
467
     * }
468
     *
469
     * @param array|string $id
470
     * @param array|string $p
471
     * @param array        $attributes
472
     *
473
     * @return Response
474
     */
475
    public function getAttributes($id = null, $p = null, array $attributes = []): Response
476
    {
477
        if (is_array($id) || is_array($p)) {
478
            $nodes = [];
479
            foreach ($this->_getNodes($id, $p) as $node) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 475 can also be of type array; however, Balloon\App\Api\v1\Node::_getNodes() does only seem to accept string|null, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
480
                $nodes[] = $this->node_decorator->decorate($node, $attributes);
481
            }
482
483
            return (new Response())->setCode(200)->setBody([
484
                'code' => 200,
485
                'data' => $nodes,
486
            ]);
487
        }
488
489
        $result = $this->node_decorator->decorate($this->_getNode($id, $p), $attributes);
490
491
        return (new Response())->setCode(200)->setBody([
492
            'code' => 200,
493
            'data' => $result,
494
        ]);
495
    }
496
497
    /**
498
     * @api {get} /api/v1/node/parents?id=:id Get parent nodes
499
     * @apiVersion 1.0.0
500
     * @apiName getParents
501
     * @apiGroup Node
502
     * @apiPermission none
503
     * @apiDescription Get system attributes of all parent nodes. The hirarchy of all parent nodes is ordered in a
504
     * single level array beginning with the collection on the highest level.
505
     * @apiUse _getNode
506
     * @apiUse _nodeAttributes
507
     * @apiSuccess (200 OK) {object[]} data Nodes
508
     *
509
     * @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)
510
     *
511
     * @apiExample (cURL) example:
512
     * curl -XGET "https://SERVER/api/v1/node/parents?id=544627ed3c58891f058b4686&pretty"
513
     * curl -XGET "https://SERVER/api/v1/node/parents?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted&pretty"
514
     * curl -XGET "https://SERVER/api/v1/node/544627ed3c58891f058b4686/parents?pretty&self=1"
515
     * curl -XGET "https://SERVER/api/v1/node/parents?p=/absolute/path/to/my/node&self=1"
516
     *
517
     * @apiSuccessExample {json} Success-Response:
518
     * HTTP/1.1 200 OK
519
     * {
520
     *     "status": 200,
521
     *     "data": [
522
     * {
523
     *              "id": "544627ed3c58891f058bbbaa",
524
     *              "name": "rootdir",
525
     *              "meta": {},
526
     *              "size": 1,
527
     *              "mime": "inode\/directory",
528
     *              "deleted": false,
529
     *              "changed": {
530
     *                  "sec": 1413883880,
531
     *                  "usec": 869001
532
     *              },
533
     *              },
534
     *              "created": {
535
     *                  "sec": 1413883880,
536
     *                  "usec": 869001
537
     *              },
538
     *              "share": false,
539
     *              "directory": true
540
     *          },
541
     *          {
542
     *              "id": "544627ed3c58891f058b46cc",
543
     *              "name": "parentdir",
544
     *              "meta": {},
545
     *              "size": 3,
546
     *              "mime": "inode\/directory",
547
     *              "deleted": false,
548
     *              "changed": {
549
     *                  "sec": 1413883885,
550
     *                  "usec": 869000
551
     *              },
552
     *              "created": {
553
     *                  "sec": 1413883885,
554
     *                  "usec": 869000
555
     *              },
556
     *              "share": false,
557
     *              "directory": true
558
     *          }
559
     *      ]
560
     * }
561
     *
562
     * @param string $id
563
     * @param string $p
564
     * @param array  $attributes
565
     *
566
     * @return Response
567
     */
568
    public function getParents(?string $id = null, ?string $p = null, array $attributes = [], bool $self = false): Response
569
    {
570
        $request = $this->_getNode($id, $p);
571
        $parents = $request->getParents();
572
        $result = [];
573
574
        if (true === $self && $request instanceof Collection) {
575
            $result[] = $this->node_decorator->decorate($request, $attributes);
576
        }
577
578
        foreach ($parents as $node) {
579
            $result[] = $this->node_decorator->decorate($node, $attributes);
580
        }
581
582
        return (new Response())->setCode(200)->setBody([
583
            'code' => 200,
584
            'data' => $result,
585
        ]);
586
    }
587
588
    /**
589
     * @api {post} /api/v1/node/meta-attributes?id=:id Change meta attributes
590
     * @apiVersion 1.0.0
591
     * @apiName postMetaAttributes
592
     * @apiGroup Node
593
     * @apiPermission none
594
     * @apiDescription Change meta attributes of a node
595
     * @apiUse _getNodes
596
     * @apiUse _multiError
597
     *
598
     * @apiParam (GET Parameter) {string} [attributes.description] UTF-8 Text Description - Can contain anything as long as it is a string
599
     * @apiParam (GET Parameter) {string} [attributes.color] Color Tag - Can contain anything as long as it is a string
600
     * @apiParam (GET Parameter) {string} [attributes.author] Author - Can contain anything as long as it is a string
601
     * @apiParam (GET Parameter) {string} [attributes.mail] Mail contact address - Can contain anything as long as it is a string
602
     * @apiParam (GET Parameter) {string} [attributes.license] License - Can contain anything as long as it is a string
603
     * @apiParam (GET Parameter) {string} [attributes.copyright] Copyright string - Can contain anything as long as it is a string
604
     * @apiParam (GET Parameter) {string[]} [attributes.tags] Tags - Must be an array full of strings
605
     *
606
     * @apiExample (cURL) example:
607
     * curl -XPOST "https://SERVER/api/v1/node/meta-attributes?id=544627ed3c58891f058b4686&author=peter.meier"
608
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4686/meta-attributes?author=example"
609
     * curl -XPOST "https://SERVER/api/v1/node/meta-attributes?p=/absolute/path/to/my/node?license=GPL-3.0"
610
     *
611
     * @apiSuccessExample {json} Success-Response:
612
     * HTTP/1.1 204 No Content
613
     *
614
     * @param array|string $id
615
     * @param array|string $p
616
     *
617
     * @return Response
618
     */
619
    public function postMetaAttributes(array $attributes, ?string $id = null, ?string $p = null): Response
620
    {
621
        return $this->bulk($id, $p, function ($node) use ($attributes) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 619 can also be of type null; however, Balloon\App\Api\v1\Node::bulk() does only seem to accept array|string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
622
            $node->setMetaAttributes($attributes);
623
624
            return ['code' => 204];
625
        });
626
    }
627
628
    /**
629
     * @api {post} /api/v1/node/name?id=:id Rename node
630
     * @apiVersion 1.0.0
631
     * @apiName postName
632
     * @apiGroup Node
633
     * @apiPermission none
634
     * @apiDescription Rename a node. The characters (\ < > : " / * ? |) (without the "()") are not allowed to use within a node name.
635
     * @apiUse _getNode
636
     * @apiUse _writeAction
637
     *
638
     * @apiParam (GET Parameter) {string} [name] The new name of the node
639
     * @apiError (Error 400) Exception name contains invalid characters
640
     *
641
     * @apiExample (cURL) example:
642
     * curl -XPOST "https://SERVER/api/v1/node/name?id=544627ed3c58891f058b4686&name=newname.txt"
643
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4677/name?name=newdir"
644
     * curl -XPOST "https://SERVER/api/v1/node/name?p=/absolute/path/to/my/node&name=newname.txt"
645
     *
646
     * @apiSuccessExample {json} Success-Response:
647
     * HTTP/1.1 204 No Content
648
     *
649
     * @param string $id
650
     * @param string $p
651
     * @param string $name
652
     *
653
     * @return Response
654
     */
655
    public function postName(string $name, ?string $id = null, ?string $p = null): Response
656
    {
657
        $this->_getNode($id, $p)->setName($name);
658
659
        return (new Response())->setCode(204);
660
    }
661
662
    /**
663
     * @api {post} /api/v1/node/clone?id=:id Clone node
664
     * @apiVersion 1.0.0
665
     * @apiName postClone
666
     * @apiGroup Node
667
     * @apiPermission none
668
     * @apiDescription Clone a node
669
     * @apiUse _getNode
670
     * @apiUse _conflictNode
671
     * @apiUse _multiError
672
     * @apiUse _writeAction
673
     *
674
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
675
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
676
     *
677
     * @apiExample (cURL) example:
678
     * curl -XPOST "https://SERVER/api/v1/node/clone?id=544627ed3c58891f058b4686&dest=544627ed3c58891f058b4676"
679
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4686/clone?dest=544627ed3c58891f058b4676&conflict=2"
680
     * curl -XPOST "https://SERVER/api/v1/node/clone?p=/absolute/path/to/my/node&conflict=0&destp=/new/parent"
681
     *
682
     * @apiSuccessExample {json} Success-Response:
683
     * HTTP/1.1 204 No Content
684
     *
685
     * @param array|string $id
686
     * @param array|string $p
687
     * @param string       $destid
688
     * @param string       $destp
689
     * @param int          $conflict
690
     *
691
     * @return Response
692
     */
693
    public function postClone(
694
        $id = null,
695
        $p = null,
696
        ?string $destid = null,
697
        ?string $destp = null,
698
        int $conflict = 0
699
    ): Response {
700
        try {
701
            $parent = $this->_getNode($destid, $destp, Collection::class, false, true);
702
        } catch (Exception\NotFound $e) {
703
            throw new Exception\NotFound(
704
                'destination collection was not found or is not a collection',
705
                Exception\NotFound::DESTINATION_NOT_FOUND
706
            );
707
        }
708
709
        return $this->bulk($id, $p, function ($node) use ($parent, $conflict) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 694 can also be of type null; however, Balloon\App\Api\v1\Node::bulk() does only seem to accept array|string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
710
            $result = $node->copyTo($parent, $conflict);
711
712
            return [
713
                'code' => 201,
714
                'data' => $result,
715
            ];
716
        });
717
    }
718
719
    /**
720
     * @api {post} /api/v1/node/move?id=:id Move node
721
     * @apiVersion 1.0.0
722
     * @apiName postMove
723
     * @apiGroup Node
724
     * @apiPermission none
725
     * @apiDescription Move node
726
     * @apiUse _getNodes
727
     * @apiUse _conflictNode
728
     * @apiUse _multiError
729
     * @apiUse _writeAction
730
     *
731
     * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given.
732
     * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given.
733
     *
734
     * @apiExample (cURL) example:
735
     * curl -XPOST "https://SERVER/api/v1/node/move?id=544627ed3c58891f058b4686?destid=544627ed3c58891f058b4655"
736
     * curl -XPOST "https://SERVER/api/v1/node/544627ed3c58891f058b4686/move?destid=544627ed3c58891f058b4655"
737
     * curl -XPOST "https://SERVER/api/v1/node/move?p=/absolute/path/to/my/node&destp=/new/parent&conflict=1
738
     *
739
     * @apiSuccessExample {json} Success-Response:
740
     * HTTP/1.1 204 No Content
741
     *
742
     * @apiSuccessExample {json} Success-Response (conflict=1):
743
     * HTTP/1.1 200 OK
744
     * {
745
     *      "status":200,
746
     *      "data": "renamed (xy23)"
747
     * }
748
     *
749
     * @param array|string $id
750
     * @param array|string $p
751
     * @param string       $destid
752
     * @param string       $destp
753
     * @param int          $conflict
754
     *
755
     * @return Response
756
     */
757
    public function postMove(
758
        $id = null,
759
        $p = null,
760
        ?string $destid = null,
761
        ?string $destp = null,
762
        int $conflict = 0
763
    ): Response {
764
        try {
765
            $parent = $this->_getNode($destid, $destp, Collection::class, false, true);
766
        } catch (Exception\NotFound $e) {
767
            throw new Exception\NotFound(
768
                'destination collection was not found or is not a collection',
769
                Exception\NotFound::DESTINATION_NOT_FOUND
770
            );
771
        }
772
773
        return $this->bulk($id, $p, function ($node) use ($parent, $conflict) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 758 can also be of type null; however, Balloon\App\Api\v1\Node::bulk() does only seem to accept array|string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
774
            $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...
775
            if (NodeInterface::CONFLICT_RENAME === $conflict) {
776
                return [
777
                    'code' => 200,
778
                    'data' => $node->getName(),
779
                ];
780
            }
781
782
            return [
783
                'code' => 204,
784
            ];
785
        });
786
    }
787
788
    /**
789
     * @api {delete} /api/v1/node?id=:id Delete node
790
     * @apiVersion 1.0.0
791
     * @apiName delete
792
     * @apiGroup Node
793
     * @apiPermission none
794
     * @apiDescription Delete node
795
     * @apiUse _getNodes
796
     * @apiUse _multiError
797
     * @apiUse _writeAction
798
     *
799
     * @apiParam (GET Parameter) {boolean} [force=false] Force flag need to be set to delete a node from trash (node must have the deleted flag)
800
     * @apiParam (GET Parameter) {boolean} [ignore_flag=false] If both ignore_flag and force_flag were set, the node will be deleted completely
801
     * @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
802
     *
803
     * @apiExample (cURL) example:
804
     * curl -XDELETE "https://SERVER/api/v1/node?id=544627ed3c58891f058b4686"
805
     * curl -XDELETE "https://SERVER/api/v1/node/544627ed3c58891f058b4686?force=1&ignore_flag=1"
806
     * curl -XDELETE "https://SERVER/api/v1/node?p=/absolute/path/to/my/node"
807
     *
808
     * @apiSuccessExample {json} Success-Response:
809
     * HTTP/1.1 204 No Content
810
     *
811
     * @param array|string $id
812
     * @param array|string $p
813
     * @param bool         $force
814
     * @param bool         $ignore_flag
815
     * @param int          $at
816
     *
817
     * @return Response
818
     */
819
    public function delete(
820
        $id = null,
821
        $p = null,
822
        bool $force = false,
823
        bool $ignore_flag = false,
824
        ?string $at = null
825
    ): Response {
826
        $failures = [];
0 ignored issues
show
Unused Code introduced by
$failures is not used, you could remove the assignment.

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

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

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

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

Loading history...
827
828
        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...
829
            $at = $this->_verifyAttributes(['destroy' => $at])['destroy'];
830
        }
831
832
        return $this->bulk($id, $p, function ($node) use ($force, $ignore_flag, $at) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 820 can also be of type null; however, Balloon\App\Api\v1\Node::bulk() does only seem to accept array|string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
833
            if (null === $at) {
834
                $node->delete($force && $node->isDeleted() || $force && $ignore_flag);
835
            } else {
836
                if ('0' === $at) {
837
                    $at = null;
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $at, or did you forget to import by reference?

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

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

Change not visible in outer-scope

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

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

Change visible in outer-scope

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

$callable();
var_dump($x); // integer(2)
Loading history...
838
                }
839
                $node->setDestroyable($at);
840
            }
841
842
            return [
843
                'code' => 204,
844
            ];
845
        });
846
    }
847
848
    /**
849
     * @api {get} /api/v1/node/query Custom query
850
     * @apiVersion 1.0.0
851
     * @apiName getQuery
852
     * @apiGroup Node
853
     * @apiPermission none
854
     * @apiDescription A custom query is similar requet to children. You do not have to provide any parent node (id or p)
855
     * 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
856
     * (search) but does not use the search engine like GET /node/search does. You can also create a persistent query collection, just look at
857
     * 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.
858
     * 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.
859
     * @apiUse _nodeAttributes
860
     *
861
     * @apiExample (cURL) example:
862
     * curl -XGET https://SERVER/api/v1/node/query?{%22filter%22:{%22shared%22:true,%22reference%22:{%22$exists%22:0}}}
863
     *
864
     * @apiParam (GET Parameter) {string[]} [attributes] Filter node attributes
865
     * @apiParam (GET Parameter) {string[]} [filter] Filter nodes
866
     * @apiParam (GET Parameter) {number} [deleted=0] Wherever include deleted nodes or not, possible values:</br>
867
     * - 0 Exclude deleted</br>
868
     * - 1 Only deleted</br>
869
     * - 2 Include deleted</br>
870
     *
871
     * @apiSuccess (200 OK) {number} status Status Code
872
     * @apiSuccess (200 OK) {object[]} data Children
873
     * @apiSuccessExample {json} Success-Response:
874
     * HTTP/1.1 200 OK
875
     * {
876
     *      "status":200,
877
     *      "data": [{..}, {...}] //Shorted
878
     * }
879
     *
880
     * @param int   $deleted
881
     * @param array $filter
882
     * @param array $attributes
883
     *
884
     * @return Response
885
     */
886
    public function getQuery(int $deleted = 0, array $filter = [], array $attributes = []): Response
887
    {
888
        $children = [];
889
        $nodes = $this->fs->findNodesByFilterUser($deleted, $filter);
890
891
        foreach ($nodes as $node) {
892
            $child = $this->node_decorator->decorate($node, $attributes);
893
            $children[] = $child;
894
        }
895
896
        return (new Response())->setCode(200)->setBody([
897
            'code' => 200,
898
            'data' => $children,
899
        ]);
900
    }
901
902
    /**
903
     * @api {get} /api/v1/node/trash Get trash
904
     * @apiName getTrash
905
     * @apiVersion 1.0.0
906
     * @apiGroup Node
907
     * @apiPermission none
908
     * @apiDescription A similar endpoint to /api/v1/node/query filer={'deleted': {$type: 9}] but instead returning all deleted
909
     * nodes (including children which are deleted as well) this enpoint only returns the first deleted node from every subtree)
910
     * @apiUse _nodeAttributes
911
     *
912
     * @apiExample (cURL) example:
913
     * curl -XGET https://SERVER/api/v1/node/trash?pretty
914
     *
915
     * @apiParam (GET Parameter) {string[]} [attributes] Filter node attributes
916
     *
917
     * @apiSuccess (200 OK) {number} status Status Code
918
     * @apiSuccess (200 OK) {object[]} data Children
919
     * @apiSuccessExample {json} Success-Response:
920
     * HTTP/1.1 200 OK
921
     * {
922
     *      "status":200,
923
     *      "data": [{..}, {...}] //Shorted
924
     * }
925
     *
926
     * @param array $attributes
927
     *
928
     * @return Response
929
     */
930
    public function getTrash(array $attributes = []): Response
931
    {
932
        $children = [];
933
        $nodes = $this->fs->findNodesByFilterUser(NodeInterface::DELETED_ONLY, ['deleted' => ['$type' => 9]]);
934
935
        foreach ($nodes as $node) {
936
            try {
937
                $parent = $node->getParent();
938
                if (null !== $parent && $parent->isDeleted()) {
939
                    continue;
940
                }
941
            } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
942
            }
943
944
            $child = $this->node_decorator->decorate($node, $attributes);
945
            $children[] = $child;
946
        }
947
948
        return (new Response())->setCode(200)->setBody([
949
            'code' => 200,
950
            'data' => array_values($children),
951
        ]);
952
    }
953
954
    /**
955
     * @api {get} /api/v1/node/delta Get delta
956
     * @apiVersion 1.0.0
957
     * @apiName getDelta
958
     * @apiGroup Node
959
     * @apiPermission none
960
     * @apiUse _getNode
961
     *
962
     * @apiDescription Use this method to request a delta feed with all changes on the server (or) a snapshot of the server state.
963
     * 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.
964
     * If has_more is TRUE you need to request /delta immediatly again to
965
     * receive the next bunch of deltas. If has_more is FALSE you should wait at least 120s seconds before any further requests to the
966
     * 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).
967
     * 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
968
     * without a cursor. reset could be TRUE if there was an account maintenance or a simialar case.
969
     * You can request a different limit as well but be aware that the number of nodes could be slighty different from your requested limit.
970
     * If requested with parameter id or p the delta gets generated recursively from the node given.
971
     *
972
     * @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
973
     * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default not all attributes would be returned
974
     * @apiParam (GET Parameter) {string} [cursor=null] Set a cursor to rquest next nodes within delta processing
975
     *
976
     * @apiExample (cURL) example:
977
     * curl -XGET "https://SERVER/api/v1/node/delta?pretty"
978
     *
979
     * @apiSuccess (200 OK) {number} status Status Code
980
     * @apiSuccess (200 OK) {object} data Delta feed
981
     * @apiSuccess (200 OK) {boolean} data.reset If true the local state needs to be reseted, is alway TRUE during
982
     * the first request to /delta without a cursor or in special cases like server or account maintenance
983
     * @apiSuccess (200 OK) {string} data.cursor The cursor needs to be stored and reused to request further deltas
984
     * @apiSuccess (200 OK) {boolean} data.has_more If has_more is TRUE /delta can be requested immediatly after the last request
985
     * to receive further delta. If it is FALSE we should wait at least 120 seconds before any further delta requests to the api endpoint
986
     * @apiSuccess (200 OK) {object[]} data.nodes Node list to process
987
     * @apiSuccess (200 OK) {string} data.nodes.id Node ID
988
     * @apiSuccess (200 OK) {string} data.nodes.deleted Is node deleted?
989
     * @apiSuccess (200 OK) {object} data.nodes.changed Changed timestamp
990
     * @apiSuccess (200 OK) {number} data.nodes.changed.sec Unix timestamp
991
     * @apiSuccess (200 OK) {number} data.nodes.changed.usec Additional Microsecconds to Unix timestamp
992
     * @apiSuccess (200 OK) {object} data.nodes.created Created timestamp (If data.nodes[].deleted is TRUE, created will be NULL)
993
     * @apiSuccess (200 OK) {number} data.nodes.created.sec Unix timestamp
994
     * @apiSuccess (200 OK) {number} data.nodes.created.usec Additional Microsecconds to Unix timestamp
995
     * @apiSuccess (200 OK) {string} data.nodes.path The full absolute path to the node
996
     * @apiSuccess (200 OK) {string} data.nodes.directory Is true if node is a directory
997
     * @apiSuccessExample {json} Success-Response:
998
     * HTTP/1.1 200 OK
999
     * {
1000
     *      "status": 200,
1001
     *      "data": {
1002
     *          "reset": false,
1003
     *          "cursor": "aW5pdGlhbHwxMDB8NTc1YTlhMGIzYzU4ODkwNTE0OGI0NTZifDU3NWE5YTBiM2M1ODg5MDUxNDhiNDU2Yw==",
1004
     *          "has_more": false,
1005
     *          "nodes": [
1006
     *             {
1007
     *                  "id": "581afa783c5889ad7c8b4572",
1008
     *                  "deleted": true,
1009
     *                  "created": null,
1010
     *                  "changed": {
1011
     *                      "sec": 1478163064,
1012
     *                      "usec": 31.0.0
1013
     *                  },
1014
     *                  "path": "\/AAA\/AL",
1015
     *                  "directory": true
1016
     *              },
1017
     *              {
1018
     *                  "id": "581afa783c5889ad7c8b3dcf",
1019
     *                  "deleted": false,
1020
     *                  "created": {
1021
     *                      "sec": 1478163048,
1022
     *                      "usec": 101000
1023
     *                  },
1024
     *                  "changed": {
1025
     *                      "sec": 1478163048,
1026
     *                      "usec": 101000
1027
     *                  },
1028
     *                  "path": "\/AL",
1029
     *                  "directory": true
1030
     *              }
1031
     *          ]
1032
     *      }
1033
     * }
1034
     *
1035
     * @param DeltaDecorator $delta_decorator
1036
     * @param string         $id
1037
     * @param string         $p
1038
     * @param string         $cursor
1039
     * @param int            $limit
1040
     * @param array          $attributes
1041
     *
1042
     * @return Response
1043
     */
1044
    public function getDelta(
1045
        DeltaDecorator $delta_decorator,
1046
        ?string $id = null,
1047
        ?string $p = null,
1048
        ?string $cursor = null,
1049
        int $limit = 250,
1050
        array $attributes = []
1051
    ): Response {
1052
        if (null !== $id || null !== $p) {
1053
            $node = $this->_getNode($id, $p);
1054
        } else {
1055
            $node = null;
1056
        }
1057
1058
        $result = $this->fs->getDelta()->getDeltaFeed($cursor, $limit, $node);
1059
1060
        foreach ($result['nodes'] as &$node) {
1061
            if ($node instanceof NodeInterface) {
1062
                $node = $this->node_decorator->decorate($node, $attributes);
1063
            } else {
1064
                $node = $delta_decorator->decorate($node, $attributes);
1065
            }
1066
        }
1067
1068
        return (new Response())->setCode(200)->setBody([
1069
            'code' => 200,
1070
            'data' => $result,
1071
        ]);
1072
    }
1073
1074
    /**
1075
     * @api {get} /api/v1/node/event-log?id=:id Event log
1076
     * @apiVersion 1.0.0
1077
     * @apiName getEventLog
1078
     * @apiGroup Node
1079
     * @apiPermission none
1080
     * @apiUse _getNode
1081
     * @apiDescription Get detailed event log
1082
     * Request all modifications which are made by the user himself or share members.
1083
     * Possible operations are the follwing:
1084
     * - deleteCollectionReference
1085
     * - deleteCollectionShare
1086
     * - deleteCollection
1087
     * - addCollection
1088
     * - addFile
1089
     * - addCollectionShare
1090
     * - addCollectionReference
1091
     * - undeleteFile
1092
     * - undeleteCollectionReference
1093
     * - undeleteCollectionShare
1094
     * - restoreFile
1095
     * - renameFile
1096
     * - renameCollection
1097
     * - renameCollectionShare
1098
     * - renameCollectionRFeference
1099
     * - copyFile
1100
     * - copyCollection
1101
     * - copyCollectionShare
1102
     * - copyCollectionRFeference
1103
     * - moveFile
1104
     * - moveCollection
1105
     * - moveCollectionReference
1106
     * - moveCollectionShare
1107
     *
1108
     * @apiExample (cURL) example:
1109
     * curl -XGET "https://SERVER/api/v1/node/event-log?pretty"
1110
     * curl -XGET "https://SERVER/api/v1/node/event-log?id=544627ed3c58891f058b4686&pretty"
1111
     * curl -XGET "https://SERVER/api/v1/node/544627ed3c58891f058b4686/event-log?pretty&limit=10"
1112
     * curl -XGET "https://SERVER/api/v1/node/event-log?p=/absolute/path/to/my/node&pretty"
1113
     *
1114
     * @apiParam (GET Parameter) {number} [limit=100] Sets limit of events to be returned
1115
     * @apiParam (GET Parameter) {number} [skip=0] How many events are skiped (useful for paging)
1116
     *
1117
     * @apiSuccess (200 OK) {number} status Status Code
1118
     * @apiSuccess (200 OK) {object[]} data Events
1119
     * @apiSuccess (200 OK) {number} data.event Event ID
1120
     * @apiSuccess (200 OK) {object} data.timestamp event timestamp
1121
     * @apiSuccess (200 OK) {number} data.timestamp.sec Event timestamp timestamp in Unix time
1122
     * @apiSuccess (200 OK) {number} data.timestamp.usec Additional microseconds to changed Unix timestamp
1123
     * @apiSuccess (200 OK) {string} data.operation event operation (like addCollection, deleteFile, ...)
1124
     * @apiSuccess (200 OK) {string} data.parent ID of the parent node at the time of the event
1125
     * @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
1126
     * @apiSuccess (200 OK) {number} data.previous.version Version at the time before the event
1127
     * @apiSuccess (200 OK) {string} data.previous.name Name at the time before the event
1128
     * @apiSuccess (200 OK) {string} data.previous.parent Parent node at the time before the event
1129
     * @apiSuccess (200 OK) {string} data.share If of the shared folder at the time of the event
1130
     * @apiSuccess (200 OK) {string} data.name Name of the node at the time of the event
1131
     * @apiSuccess (200 OK) {object} data.node Current data of the node (Not from the time of the event!)
1132
     * @apiSuccess (200 OK) {boolean} data.node.deleted True if the node is deleted, false otherwise
1133
     * @apiSuccess (200 OK) {string} data.node.id Actual ID of the node
1134
     * @apiSuccess (200 OK) {string} data.node.name Current name of the node
1135
     * @apiSuccess (200 OK) {object} data.user Data which contains information about the user who executed an event
1136
     * @apiSuccess (200 OK) {string} data.user.id Actual user ID
1137
     * @apiSuccess (200 OK) {string} data.user.username Current username of executed event
1138
     *
1139
     * @apiSuccessExample {json} Success-Response:
1140
     * HTTP/1.1 200 OK
1141
     * {
1142
     *      "status": 200,
1143
     *      "data": [
1144
     *          {
1145
     *              "event": "57628e523c5889026f8b4570",
1146
     *              "timestamp": {
1147
     *                  "sec": 1466076753,
1148
     *                  "usec": 988000
1149
     *              },
1150
     *              "operation": "restoreFile",
1151
     *              "name": "file.txt",
1152
     *              "previous": {
1153
     *                  "version": 16
1154
     *              },
1155
     *              "node": {
1156
     *                  "id": "558c0b273c588963078b457a",
1157
     *                  "name": "3dddsceheckfile.txt",
1158
     *                  "deleted": false
1159
     *              },
1160
     *              "parent": null,
1161
     *              "user": {
1162
     *                  "id": "54354cb63c58891f058b457f",
1163
     *                  "username": "gradmin.bzu"
1164
     *              },
1165
     *              "share": null
1166
     *          }
1167
     *      ]
1168
     * }
1169
     *
1170
     * @param EventDecorator $event_decorator
1171
     * @param string         $id
1172
     * @param string         $p
1173
     * @param int            $skip
1174
     * @param int            $limit
1175
     *
1176
     * @return Response
1177
     */
1178
    public function getEventLog(EventDecorator $event_decorator, ?string $id = null, ?string $p = null, int $skip = 0, int $limit = 100): Response
1179
    {
1180
        if (null !== $id || null !== $p) {
1181
            $node = $this->_getNode($id, $p);
1182
        } else {
1183
            $node = null;
1184
        }
1185
1186
        $result = $this->fs->getDelta()->getEventLog($limit, $skip, $node);
1187
        $body = [];
1188
        foreach ($result as $event) {
1189
            $body[] = $event_decorator->decorate($event);
1190
        }
1191
1192
        return (new Response())->setCode(200)->setBody([
1193
            'code' => 200,
1194
            'data' => $body,
1195
        ]);
1196
    }
1197
1198
    /**
1199
     * @api {get} /api/v1/node/last-cursor Get last Cursor
1200
     * @apiVersion 1.0.0
1201
     * @apiName geLastCursor
1202
     * @apiGroup Node
1203
     * @apiUse _getNode
1204
     * @apiPermission none
1205
     * @apiDescription Use this method to request the latest cursor if you only need to now
1206
     * if there are changes on the server. This method will not return any other data than the
1207
     * newest cursor. To request a feed with all deltas request /delta.
1208
     *
1209
     * @apiExample (cURL) example:
1210
     * curl -XGET "https://SERVER/api/v1/node/last-cursor?pretty"
1211
     *
1212
     * @apiSuccess (200 OK) {number} status Status Code
1213
     * @apiSuccess (200 OK) {string} data Newest cursor
1214
     * @apiSuccessExample {json} Success-Response:
1215
     * HTTP/1.1 200 OK
1216
     * {
1217
     *      "status": 200,
1218
     *      "data": "aW5pdGlhbHwxMDB8NTc1YTlhMGIzYzU4ODkwNTE0OGI0NTZifDU3NWE5YTBiM2M1ODg5MDUxNDhiNDU2Yw=="
1219
     * }
1220
     *
1221
     * @param string $id
1222
     * @param string $p
1223
     *
1224
     * @return Response
1225
     */
1226
    public function getLastCursor(?string $id = null, ?string $p = null): Response
1227
    {
1228
        if (null !== $id || null !== $p) {
1229
            $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...
1230
        } else {
1231
            $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...
1232
        }
1233
1234
        $result = $this->fs->getDelta()->getLastCursor();
1235
1236
        return (new Response())->setCode(200)->setBody([
1237
            'code' => 200,
1238
            'data' => $result,
1239
        ]);
1240
    }
1241
1242
    /**
1243
     * Do bulk operations.
1244
     *
1245
     * @param array|string $id
1246
     * @param array|string $p
1247
     * @param Closure      $action
1248
     */
1249
    protected function bulk($id, $p, Closure $action): Response
1250
    {
1251
        if (is_array($id) || is_array($p)) {
1252
            $errors = [];
1253
            $body = [];
1254
1255
            foreach ($this->_getNodes($id, $p) as $node) {
0 ignored issues
show
Bug introduced by
It seems like $id defined by parameter $id on line 1249 can also be of type array; however, Balloon\App\Api\v1\Node::_getNodes() does only seem to accept string|null, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

An additional type check may prevent trouble.

Loading history...
1256
                try {
1257
                    $body[] = $action->call($this, $node);
1258
                } catch (\Exception $e) {
1259
                    $errors[] = [
1260
                        'error' => get_class($e),
1261
                        'message' => $e->getMessage(),
1262
                        'code' => $e->getCode(),
1263
                    ];
1264
                }
1265
            }
1266
1267
            if (!empty($errors)) {
1268
                return (new Response())->setCode(400)->setBody([
1269
                    'code' => 400,
1270
                    'data' => $errors,
1271
                ]);
1272
            }
1273
            if (empty($body)) {
1274
                return (new Response())->setCode(204);
1275
            }
1276
            $body = array_shift($body);
1277
            $response = (new Response())->setCode($body['code']);
1278
1279
            if (isset($body['data'])) {
1280
                $response->setBody([
1281
                    'code' => $body['code'],
1282
                    'data' => $body['data'],
1283
                ]);
1284
            }
1285
1286
            return $response;
1287
        }
1288
1289
        $body = $action->call($this, $this->_getNode($id, $p));
1290
        $response = (new Response())->setCode($body['code']);
1291
1292
        if (isset($body['data'])) {
1293
            $response->setBody([
1294
                'code' => $body['code'],
1295
                'data' => $body['data'],
1296
            ]);
1297
        }
1298
1299
        return $response;
1300
    }
1301
1302
    /**
1303
     * Get node.
1304
     *
1305
     * @param string $id
1306
     * @param string $path
1307
     * @param string $class      Force set node type
1308
     * @param bool   $multiple   Allow $id to be an array
1309
     * @param bool   $allow_root Allow instance of root collection
1310
     * @param bool   $deleted    How to handle deleted node
1311
     *
1312
     * @return NodeInterface
1313
     */
1314
    protected function _getNode(
1315
        ?string $id = null,
1316
        ?string $path = null,
1317
        ?string $class = null,
1318
        bool $multiple = false,
1319
        bool $allow_root = false,
1320
        int $deleted = 2
1321
    ): NodeInterface {
1322
        if (null === $class) {
1323
            switch (get_class($this)) {
1324
                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...
1325
                    $class = File::class;
1326
1327
                break;
1328
                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...
1329
                    $class = Collection::class;
1330
1331
                break;
1332
            }
1333
        }
1334
1335
        return $this->fs->getNode($id, $path, $class, $multiple, $allow_root, $deleted);
1336
    }
1337
1338
    /**
1339
     * Get nodes.
1340
     *
1341
     * @param string $id
1342
     * @param string $path
1343
     * @param string $class   Force set node type
1344
     * @param bool   $deleted How to handle deleted node
1345
     *
1346
     * @return Generator
1347
     */
1348
    protected function _getNodes(
1349
        $id = null,
1350
        $path = null,
1351
        ?string $class = null,
1352
        int $deleted = 2
1353
    ): Generator {
1354
        if (null === $class) {
1355
            switch (get_class($this)) {
1356
                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...
1357
                    $class = File::class;
1358
1359
                break;
1360
                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...
1361
                    $class = Collection::class;
1362
1363
                break;
1364
            }
1365
        }
1366
1367
        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 1349 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 1350 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...
1368
    }
1369
1370
    /**
1371
     * Merge multiple nodes into one zip archive.
1372
     *
1373
     * @param string $id
1374
     * @param string $path
1375
     * @param string $name
1376
     */
1377
    protected function combine($id = null, $path = null, string $name = 'selected')
1378
    {
1379
        $archive = new ZipStream($name.'.zip');
1380
1381
        foreach ($this->_getNodes($id, $path) as $node) {
1382
            try {
1383
                $node->zip($archive);
1384
            } catch (\Exception $e) {
1385
                $this->logger->debug('failed zip node in multi node request ['.$node->getId().']', [
1386
                   'category' => get_class($this),
1387
                   'exception' => $e,
1388
               ]);
1389
            }
1390
        }
1391
1392
        $archive->finalize();
0 ignored issues
show
Bug introduced by
The method finalize() does not seem to exist on object<ZipStream\ZipStream>.

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

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

Loading history...
1393
    }
1394
1395
    /**
1396
     * Check custom node attributes which have to be written.
1397
     *
1398
     * @param array $attributes
1399
     *
1400
     * @return array
1401
     */
1402
    protected function _verifyAttributes(array $attributes): array
1403
    {
1404
        $valid_attributes = [
1405
            'changed',
1406
            'destroy',
1407
            'created',
1408
            'meta',
1409
            'readonly',
1410
        ];
1411
1412
        if ($this instanceof ApiCollection) {
1413
            $valid_attributes[] = 'filter';
1414
        }
1415
1416
        $check = array_merge(array_flip($valid_attributes), $attributes);
1417
1418
        if ($this instanceof ApiCollection && count($check) > 6) {
1419
            throw new Exception\InvalidArgument('Only changed, created, destroy timestamp, filter, readonly and/or meta attributes may be overwritten');
1420
        }
1421
        if ($this instanceof ApiFile && count($check) > 5) {
1422
            throw new Exception\InvalidArgument('Only changed, created, destroy timestamp, readonly and/or meta attributes may be overwritten');
1423
        }
1424
1425
        foreach ($attributes as $attribute => $value) {
1426
            switch ($attribute) {
1427
                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...
1428
                    $attributes['filter'] = json_encode((array) $attributes['filter']);
1429
1430
                break;
1431
                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...
1432
                    if (!Helper::isValidTimestamp($value)) {
1433
                        throw new Exception\InvalidArgument($attribute.' Changed timestamp must be valid unix timestamp');
1434
                    }
1435
                    $attributes[$attribute] = new UTCDateTime($value.'000');
1436
1437
                break;
1438
                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...
1439
                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...
1440
                    if (!Helper::isValidTimestamp($value)) {
1441
                        throw new Exception\InvalidArgument($attribute.' Changed timestamp must be valid unix timestamp');
1442
                    }
1443
                    if ((int) $value > time()) {
1444
                        throw new Exception\InvalidArgument($attribute.' timestamp can not be set greater than the server time');
1445
                    }
1446
                    $attributes[$attribute] = new UTCDateTime($value.'000');
1447
1448
                break;
1449
                case 'readonly':
1450
                    $attributes['readonly'] = (bool) $attributes['readonly'];
1451
1452
                break;
1453
            }
1454
        }
1455
1456
        return $attributes;
1457
    }
1458
}
1459