These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | /** |
||
6 | * balloon |
||
7 | * |
||
8 | * @copyright Copryright (c) 2012-2019 gyselroth GmbH (https://gyselroth.com) |
||
9 | * @license GPL-3.0 https://opensource.org/licenses/GPL-3.0 |
||
10 | */ |
||
11 | |||
12 | namespace Balloon\App\Api\v2; |
||
13 | |||
14 | use Balloon\App\Api\Controller; |
||
15 | use Balloon\App\Api\Helper as ApiHelper; |
||
16 | use Balloon\App\Api\v2\Collections as ApiCollection; |
||
17 | use Balloon\App\Api\v2\Files as ApiFile; |
||
18 | use Balloon\AttributeDecorator\Pager; |
||
19 | use Balloon\Filesystem; |
||
20 | use Balloon\Filesystem\DeltaAttributeDecorator; |
||
21 | use Balloon\Filesystem\EventAttributeDecorator; |
||
22 | use Balloon\Filesystem\Exception; |
||
23 | use Balloon\Filesystem\Node\AttributeDecorator as NodeAttributeDecorator; |
||
24 | use Balloon\Filesystem\Node\Collection; |
||
25 | use Balloon\Filesystem\Node\File; |
||
26 | use Balloon\Filesystem\Node\NodeInterface; |
||
27 | use Balloon\Helper; |
||
28 | use Balloon\Server; |
||
29 | use Balloon\Server\User; |
||
30 | use Micro\Http\Response; |
||
31 | use function MongoDB\BSON\fromJSON; |
||
32 | use function MongoDB\BSON\toPHP; |
||
33 | use MongoDB\BSON\UTCDateTime; |
||
34 | use Psr\Log\LoggerInterface; |
||
35 | use ZipStream\ZipStream; |
||
36 | |||
37 | class Nodes extends Controller |
||
38 | { |
||
39 | /** |
||
40 | * Filesystem. |
||
41 | * |
||
42 | * @var Filesystem |
||
43 | */ |
||
44 | protected $fs; |
||
45 | |||
46 | /** |
||
47 | * LoggerInterface. |
||
48 | * |
||
49 | * @var LoggerInterface |
||
50 | */ |
||
51 | protected $logger; |
||
52 | |||
53 | /** |
||
54 | * Server. |
||
55 | * |
||
56 | * @var Server |
||
57 | */ |
||
58 | protected $server; |
||
59 | |||
60 | /** |
||
61 | * User. |
||
62 | * |
||
63 | * @var User |
||
64 | */ |
||
65 | protected $user; |
||
66 | |||
67 | /** |
||
68 | * Node attribute decorator. |
||
69 | * |
||
70 | * @var NodeAttributeDecorator |
||
71 | */ |
||
72 | protected $node_decorator; |
||
73 | |||
74 | /** |
||
75 | * Initialize. |
||
76 | */ |
||
77 | public function __construct(Server $server, NodeAttributeDecorator $decorator, LoggerInterface $logger) |
||
78 | { |
||
79 | $this->fs = $server->getFilesystem(); |
||
80 | $this->user = $server->getIdentity(); |
||
81 | $this->server = $server; |
||
82 | $this->node_decorator = $decorator; |
||
83 | $this->logger = $logger; |
||
84 | } |
||
85 | |||
86 | /** |
||
87 | * @api {head} /api/v2/nodes/:id Node exists? |
||
88 | * @apiVersion 2.0.0 |
||
89 | * @apiName head |
||
90 | * @apiGroup Node |
||
91 | * @apiPermission none |
||
92 | * @apiDescription Check if a node exists. Per default deleted nodes are ignore which means it will |
||
93 | * return a 404 if a deleted node is requested. You can change this behaviour via the deleted parameter. |
||
94 | * @apiUse _getNode |
||
95 | * |
||
96 | * @apiExample (cURL) example: |
||
97 | * curl -XHEAD "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686" |
||
98 | * curl -XHEAD "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686" |
||
99 | * curl -XHEAD "https://SERVER/api/v2/node?p=/absolute/path/to/my/node" |
||
100 | * |
||
101 | * @apiParam (GET Parameter) {number} [deleted=0] Wherever include deleted node or not, possible values:</br> |
||
102 | * - 0 Exclude deleted</br> |
||
103 | * - 1 Only deleted</br> |
||
104 | * - 2 Include deleted</br> |
||
105 | * |
||
106 | * @apiSuccessExample {json} Success-Response (Node does exist): |
||
107 | * HTTP/1.1 200 OK |
||
108 | * |
||
109 | * @apiSuccessExample {json} Success-Response (Node does not exist): |
||
110 | * HTTP/1.1 404 Not Found |
||
111 | * |
||
112 | * @param string $id |
||
113 | * @param string $p |
||
114 | */ |
||
115 | public function head(?string $id = null, ?string $p = null, int $deleted = 0): Response |
||
116 | { |
||
117 | try { |
||
118 | $result = $this->_getNode($id, $p, null, false, false, $deleted); |
||
119 | |||
120 | $response = (new Response()) |
||
121 | ->setHeader('Content-Length', (string) $result->getSize()) |
||
122 | ->setHeader('Content-Type', $result->getContentType()) |
||
123 | ->setCode(200); |
||
124 | |||
125 | return $response; |
||
126 | } catch (\Exception $e) { |
||
127 | return (new Response())->setCode(404); |
||
128 | } |
||
129 | } |
||
130 | |||
131 | /** |
||
132 | * @api {post} /api/v2/nodes/:id/undelete Restore node |
||
133 | * @apiVersion 2.0.0 |
||
134 | * @apiName postUndelete |
||
135 | * @apiGroup Node |
||
136 | * @apiPermission none |
||
137 | * @apiDescription Undelete (Restore from trash) a single node or multiple ones. |
||
138 | * @apiUse _getNodes |
||
139 | * @apiUse _conflictNode |
||
140 | * @apiUse _multiError |
||
141 | * @apiUse _writeAction |
||
142 | * |
||
143 | * @apiExample (cURL) example: |
||
144 | * curl -XPOST "https://SERVER/api/v2/nodes/undelete?id[]=544627ed3c58891f058b4686&id[]=544627ed3c58891f058b46865&pretty" |
||
145 | * curl -XPOST "https://SERVER/api/v2/nodes/undelete?id=544627ed3c58891f058b4686?pretty" |
||
146 | * curl -XPOST "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/undelete?conflict=2" |
||
147 | * curl -XPOST "https://SERVER/api/v2/nodes/undelete?p=/absolute/path/to/my/node&conflict=0&move=1&destid=544627ed3c58891f058b46889" |
||
148 | * |
||
149 | * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given. |
||
150 | * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given. |
||
151 | * |
||
152 | * @apiSuccessExample {json} Success-Response (conflict=1): |
||
153 | * HTTP/1.1 200 OK |
||
154 | * { |
||
155 | * "id": "544627ed3c58891f058b4686", |
||
156 | * "name": "renamed (xy23)" |
||
157 | * } |
||
158 | * |
||
159 | * @param array|string $id |
||
160 | * @param array|string $p |
||
161 | * @param string $destid |
||
162 | * @param string $destp |
||
163 | */ |
||
164 | public function postUndelete( |
||
165 | $id = null, |
||
166 | $p = null, |
||
167 | bool $move = false, |
||
168 | ?string $destid = null, |
||
169 | ?string $destp = null, |
||
170 | int $conflict = 0 |
||
171 | ): Response { |
||
172 | $parent = null; |
||
173 | if (true === $move) { |
||
174 | try { |
||
175 | $parent = $this->_getNode($destid, $destp, 'Collection', false, true); |
||
176 | } catch (Exception\NotFound $e) { |
||
177 | throw new Exception\NotFound( |
||
178 | 'destination collection was not found or is not a collection', |
||
179 | Exception\NotFound::DESTINATION_NOT_FOUND |
||
180 | ); |
||
181 | } |
||
182 | } |
||
183 | |||
184 | return $this->bulk($id, $p, function ($node) use ($parent, $conflict, $move) { |
||
185 | if (true === $move) { |
||
186 | $node = $node->setParent($parent, $conflict); |
||
187 | } |
||
188 | |||
189 | $node->undelete($conflict); |
||
190 | |||
191 | return [ |
||
192 | 'code' => 200, |
||
193 | 'data' => $this->node_decorator->decorate($node), |
||
194 | ]; |
||
195 | }); |
||
196 | } |
||
197 | |||
198 | /** |
||
199 | * @api {get} /api/v2/nodes/:id/content Download stream |
||
200 | * @apiVersion 2.0.0 |
||
201 | * @apiName getContent |
||
202 | * @apiGroup Node |
||
203 | * @apiPermission none |
||
204 | * @apiDescription Download node contents. Collections are zipped during streaming. |
||
205 | * @apiUse _getNode |
||
206 | * @apiParam (GET Parameter) {boolean} [download=false] Force download file (Content-Disposition: attachment HTTP header) |
||
207 | * |
||
208 | * @apiExample (cURL) example: |
||
209 | * curl -XGET "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686" > myfile.txt |
||
210 | * curl -XGET "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686" > myfile.txt |
||
211 | * curl -XGET "https://SERVER/api/v2/node?p=/absolute/path/to/my/collection" > folder.zip |
||
212 | * |
||
213 | * @apiSuccessExample {binary} Success-Response: |
||
214 | * HTTP/1.1 200 OK |
||
215 | * |
||
216 | * @apiErrorExample {json} Error-Response (Invalid offset): |
||
217 | * HTTP/1.1 400 Bad Request |
||
218 | * { |
||
219 | * "status": 400, |
||
220 | * "data": { |
||
221 | * "error": "Balloon\\Exception\\Conflict", |
||
222 | * "message": "invalid offset requested", |
||
223 | * "code": 277 |
||
224 | * } |
||
225 | * } |
||
226 | * |
||
227 | * @param array|string $id |
||
228 | * @param array|string $p |
||
229 | */ |
||
230 | public function getContent( |
||
231 | $id = null, |
||
232 | $p = null, |
||
233 | bool $download = false, |
||
234 | string $name = 'selected' |
||
235 | ): ?Response { |
||
236 | if (is_array($id) || is_array($p)) { |
||
237 | return $this->combine($id, $p, $name); |
||
238 | } |
||
239 | |||
240 | $node = $this->_getNode($id, $p); |
||
241 | if ($node instanceof Collection) { |
||
242 | return $node->getZip(); |
||
243 | } |
||
244 | |||
245 | $response = new Response(); |
||
246 | |||
247 | return ApiHelper::streamContent($response, $node, $download); |
||
248 | } |
||
249 | |||
250 | /** |
||
251 | * @apiDefine _nodeAttributes |
||
252 | * |
||
253 | * @apiSuccess (200 OK) {string} id Unique node id |
||
254 | * @apiSuccess (200 OK) {string} name Name |
||
255 | * @apiSuccess (200 OK) {string} hash MD5 content checksum (file only) |
||
256 | * @apiSuccess (200 OK) {object} meta Extended meta attributes |
||
257 | * @apiSuccess (200 OK) {string} meta.description UTF-8 Text Description |
||
258 | * @apiSuccess (200 OK) {string} meta.color Color Tag (HEX) (Like: #000000) |
||
259 | * @apiSuccess (200 OK) {string} meta.author Author |
||
260 | * @apiSuccess (200 OK) {string} meta.mail Mail contact address |
||
261 | * @apiSuccess (200 OK) {string} meta.license License |
||
262 | * @apiSuccess (200 OK) {string} meta.copyright Copyright string |
||
263 | * @apiSuccess (200 OK) {string[]} meta.tags Search Tags |
||
264 | * @apiSuccess (200 OK) {number} size Size in bytes (file only), number of children if collection |
||
265 | * @apiSuccess (200 OK) {string} mime Mime type |
||
266 | * @apiSuccess (200 OK) {boolean} sharelink Is node shared? |
||
267 | * @apiSuccess (200 OK) {number} version File version (file only) |
||
268 | * @apiSuccess (200 OK) {mixed} deleted Is boolean false if not deleted, if deleted it contains a deleted timestamp |
||
269 | * @apiSuccess (200 OK) {string} deleted ISO8601 timestamp, only set if node is deleted |
||
270 | * @apiSuccess (200 OK) {string} changed ISO8601 timestamp |
||
271 | * @apiSuccess (200 OK) {string} created ISO8601 timestamp |
||
272 | * @apiSuccess (200 OK) {string} destroy ISO8601 timestamp, only set if node has a destroy timestamp set |
||
273 | * @apiSuccess (200 OK) {boolean} share Node is shared |
||
274 | * @apiSuccess (200 OK) {boolean} directory Is true if the node is a collection |
||
275 | * @apiSuccess (200 OK) {string} access Access permission for the authenticated user (d/r/rw/m) |
||
276 | * @apiSuccess (200 OK) {object} shareowner Share owner |
||
277 | * @apiSuccess (200 OK) {object} parent Parent node |
||
278 | * @apiSuccess (200 OK) {string} path Absolute node path |
||
279 | * @apiSuccess (200 OK) {string} filter Node is filtered (collection only) |
||
280 | * @apiSuccess (200 OK) {boolean} readonly Readonly |
||
281 | * |
||
282 | * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes |
||
283 | * |
||
284 | * @param null|mixed $id |
||
285 | * @param null|mixed $p |
||
286 | * @param null|mixed $query |
||
287 | */ |
||
288 | |||
289 | /** |
||
290 | * @api {get} /api/v2/nodes/:id Get attributes |
||
291 | * @apiVersion 2.0.0 |
||
292 | * @apiName get |
||
293 | * @apiGroup Node |
||
294 | * @apiPermission none |
||
295 | * @apiDescription Get attributes from one or multiple nodes |
||
296 | * @apiUse _getNode |
||
297 | * @apiUse _nodeAttributes |
||
298 | * |
||
299 | * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default only a bunch of attributes would be returned, if you |
||
300 | * need other attributes you have to request them (for example "path") |
||
301 | * |
||
302 | * @apiExample (cURL) example: |
||
303 | * curl -XGET "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686&pretty" |
||
304 | * curl -XGET "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted&pretty" |
||
305 | * curl -XGET "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686?pretty" |
||
306 | * curl -XGET "https://SERVER/api/v2/node?p=/absolute/path/to/my/node&pretty" |
||
307 | * |
||
308 | * @apiSuccessExample {json} Success-Response: |
||
309 | * HTTP/1.1 200 OK |
||
310 | * { |
||
311 | * "id": "544627ed3c58891f058b4686", |
||
312 | * "name": "api.php", |
||
313 | * "hash": "a77f23ed800fd7a600a8c2cfe8cc370b", |
||
314 | * "meta": { |
||
315 | * "license": "GPLv3" |
||
316 | * }, |
||
317 | * "size": 178, |
||
318 | * "mime": "text\/plain", |
||
319 | * "sharelink": true, |
||
320 | * "version": 1, |
||
321 | * "changed": "2007-08-31T16:47+00:00", |
||
322 | * "created": "2007-08-31T16:47+00:00", |
||
323 | * "share": false, |
||
324 | * "directory": false |
||
325 | * } |
||
326 | * |
||
327 | * @param array|string $id |
||
328 | * @param array|string $p |
||
329 | */ |
||
330 | public function get($id = null, $p = null, int $deleted = 0, $query = null, array $attributes = [], int $offset = 0, int $limit = 20): Response |
||
331 | { |
||
332 | if ($id === null && $p === null) { |
||
333 | if ($query === null) { |
||
334 | $query = []; |
||
335 | } elseif (is_string($query)) { |
||
336 | $query = toPHP(fromJSON($query), [ |
||
337 | 'root' => 'array', |
||
338 | 'document' => 'array', |
||
339 | 'array' => 'array', |
||
340 | ]); |
||
341 | } |
||
342 | |||
343 | if ($this instanceof ApiFile) { |
||
344 | $query['directory'] = false; |
||
345 | $uri = '/api/v2/files'; |
||
346 | } elseif ($this instanceof ApiCollection) { |
||
347 | $query['directory'] = true; |
||
348 | $uri = '/api/v2/collections'; |
||
349 | } else { |
||
350 | $uri = '/api/v2/nodes'; |
||
351 | } |
||
352 | |||
353 | $nodes = $this->fs->findNodesByFilterUser($deleted, $query, $offset, $limit); |
||
354 | $pager = new Pager($this->node_decorator, $nodes, $attributes, $offset, $limit, $uri); |
||
355 | $result = $pager->paging(); |
||
356 | |||
357 | return (new Response())->setCode(200)->setBody($result); |
||
358 | } |
||
359 | |||
360 | return $this->bulk($id, $p, function ($node) use ($attributes) { |
||
361 | return [ |
||
362 | 'code' => 200, |
||
363 | 'data' => $this->node_decorator->decorate($node, $attributes), |
||
364 | ]; |
||
365 | }); |
||
366 | } |
||
367 | |||
368 | /** |
||
369 | * @api {get} /api/v2/nodes/:id/parents Get parent nodes |
||
370 | * @apiVersion 2.0.0 |
||
371 | * @apiName getParents |
||
372 | * @apiGroup Node |
||
373 | * @apiPermission none |
||
374 | * @apiDescription Get system attributes of all parent nodes. The hirarchy of all parent nodes is ordered in a |
||
375 | * single level array beginning with the collection on the highest level. |
||
376 | * @apiUse _getNode |
||
377 | * @apiUse _nodeAttributes |
||
378 | * |
||
379 | * @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) |
||
380 | * |
||
381 | * @apiExample (cURL) example: |
||
382 | * curl -XGET "https://SERVER/api/v2/nodes/parents?id=544627ed3c58891f058b4686&pretty" |
||
383 | * curl -XGET "https://SERVER/api/v2/nodes/parents?id=544627ed3c58891f058b4686&attributes[0]=name&attributes[1]=deleted&pretty" |
||
384 | * curl -XGET "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/parents?pretty&self=1" |
||
385 | * curl -XGET "https://SERVER/api/v2/nodes/parents?p=/absolute/path/to/my/node&self=1" |
||
386 | * |
||
387 | * @apiSuccessExample {json} Success-Response: |
||
388 | * HTTP/1.1 200 OK |
||
389 | * [ |
||
390 | * { |
||
391 | * "id": "544627ed3c58891f058bbbaa", |
||
392 | * "name": "rootdir", |
||
393 | * "meta": {}, |
||
394 | * "size": 1, |
||
395 | * "mime": "inode/directory", |
||
396 | * "created": "2007-08-31T16:47+00:00", |
||
397 | * "changed": "2007-08-31T16:47+00:00", |
||
398 | * "destroy": "2020-08-31T16:47+00:00", |
||
399 | * "share": false, |
||
400 | * "directory": true |
||
401 | * }, |
||
402 | * { |
||
403 | * "id": "544627ed3c58891f058b46cc", |
||
404 | * "name": "parentdir", |
||
405 | * "meta": {}, |
||
406 | * "size": 3, |
||
407 | * "mime": "inode/directory", |
||
408 | * "created": "2007-08-31T16:47+00:00", |
||
409 | * "changed": "2007-08-31T16:47+00:00", |
||
410 | * "share": false, |
||
411 | * "directory": true |
||
412 | * } |
||
413 | * ] |
||
414 | * |
||
415 | * @param string $id |
||
416 | * @param string $p |
||
417 | */ |
||
418 | public function getParents(?string $id = null, ?string $p = null, array $attributes = [], bool $self = false): Response |
||
419 | { |
||
420 | $result = []; |
||
421 | $request = $this->_getNode($id, $p); |
||
422 | $parents = $request->getParents(); |
||
423 | |||
424 | if (true === $self && $request instanceof Collection) { |
||
425 | $result[] = $this->node_decorator->decorate($request, $attributes); |
||
426 | } |
||
427 | |||
428 | foreach ($parents as $node) { |
||
429 | $result[] = $this->node_decorator->decorate($node, $attributes); |
||
430 | } |
||
431 | |||
432 | return (new Response())->setCode(200)->setBody($result); |
||
433 | } |
||
434 | |||
435 | /** |
||
436 | * @api {patch} /api/v2/nodes/:id Change attributes |
||
437 | * @apiVersion 2.0.0 |
||
438 | * @apiName patch |
||
439 | * @apiGroup Node |
||
440 | * @apiPermission none |
||
441 | * @apiDescription Change attributes |
||
442 | * @apiUse _getNodes |
||
443 | * @apiUse _multiError |
||
444 | * |
||
445 | * @apiParam (GET Parameter) {string} [name] Rename node, the characters (\ < > : " / * ? |) (without the "()") are not allowed to use within a node name. |
||
446 | * @apiParam (GET Parameter) {boolean} [readonly] Mark node as readonly |
||
447 | * @apiParam (GET Parameter) {object} [filter] Custom collection filter (Collection only) |
||
448 | * @apiParam (GET Parameter) {string} [meta.description] UTF-8 Text Description - Can contain anything as long as it is a string |
||
449 | * @apiParam (GET Parameter) {string} [meta.color] Color Tag - Can contain anything as long as it is a string |
||
450 | * @apiParam (GET Parameter) {string} [meta.author] Author - Can contain anything as long as it is a string |
||
451 | * @apiParam (GET Parameter) {string} [meta.mail] Mail contact address - Can contain anything as long as it is a string |
||
452 | * @apiParam (GET Parameter) {string} [meta.license] License - Can contain anything as long as it is a string |
||
453 | * @apiParam (GET Parameter) {string} [meta.copyright] Copyright string - Can contain anything as long as it is a string |
||
454 | * @apiParam (GET Parameter) {string[]} [meta.tags] Tags - Must be an array full of strings |
||
455 | * |
||
456 | * @apiExample (cURL) example: |
||
457 | * curl -XPATCH "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686?name=example" |
||
458 | * |
||
459 | * @apiSuccessExample {json} Success-Response: |
||
460 | * HTTP/1.1 200 |
||
461 | * |
||
462 | * @param array|string $id |
||
463 | * @param array|string $p |
||
464 | */ |
||
465 | public function patch(?string $name = null, ?array $meta = null, ?bool $readonly = null, ?array $filter = null, ?array $acl = null, ?string $id = null, ?string $p = null): Response |
||
466 | { |
||
467 | $attributes = compact('name', 'meta', 'readonly', 'filter', 'acl'); |
||
468 | $attributes = array_filter($attributes, function ($attribute) {return !is_null($attribute); }); |
||
469 | |||
470 | return $this->bulk($id, $p, function ($node) use ($attributes) { |
||
471 | foreach ($attributes as $attribute => $value) { |
||
472 | switch ($attribute) { |
||
473 | case 'name': |
||
474 | $node->setName($value); |
||
475 | |||
476 | break; |
||
477 | case 'meta': |
||
478 | $node->setMetaAttributes($value); |
||
479 | |||
480 | break; |
||
481 | case 'readonly': |
||
482 | $node->setReadonly($value); |
||
483 | |||
484 | break; |
||
485 | case 'filter': |
||
486 | if ($node instanceof Collection) { |
||
487 | $node->setFilter($value); |
||
488 | } |
||
489 | |||
490 | break; |
||
491 | case 'acl': |
||
492 | $node->setAcl($value); |
||
493 | |||
494 | break; |
||
495 | } |
||
496 | } |
||
497 | |||
498 | return [ |
||
499 | 'code' => 200, |
||
500 | 'data' => $this->node_decorator->decorate($node), |
||
501 | ]; |
||
502 | }); |
||
503 | } |
||
504 | |||
505 | /** |
||
506 | * @api {post} /api/v2/nodes/:id/clone Clone node |
||
507 | * @apiVersion 2.0.0 |
||
508 | * @apiName postClone |
||
509 | * @apiGroup Node |
||
510 | * @apiPermission none |
||
511 | * @apiDescription Clone a node |
||
512 | * @apiUse _getNode |
||
513 | * @apiUse _conflictNode |
||
514 | * @apiUse _multiError |
||
515 | * @apiUse _writeAction |
||
516 | * |
||
517 | * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given. |
||
518 | * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given. |
||
519 | * |
||
520 | * @apiExample (cURL) example: |
||
521 | * curl -XPOST "https://SERVER/api/v2/nodes/clone?id=544627ed3c58891f058b4686&dest=544627ed3c58891f058b4676" |
||
522 | * curl -XPOST "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/clone?dest=544627ed3c58891f058b4676&conflict=2" |
||
523 | * curl -XPOST "https://SERVER/api/v2/nodes/clone?p=/absolute/path/to/my/node&conflict=0&destp=/new/parent" |
||
524 | * |
||
525 | * @apiSuccessExample {json} Success-Response: |
||
526 | * HTTP/1.1 201 Created |
||
527 | * |
||
528 | * @apiSuccessExample {json} Success-Response: |
||
529 | * HTTP/1.1 200 OK |
||
530 | * |
||
531 | * @param array|string $id |
||
532 | * @param array|string $id |
||
533 | * @param array|string $p |
||
534 | * @param string $destid |
||
535 | * @param string $destp |
||
536 | */ |
||
537 | public function postClone( |
||
538 | $id = null, |
||
539 | $p = null, |
||
540 | ?string $destid = null, |
||
541 | ?string $destp = null, |
||
542 | int $conflict = 0 |
||
543 | ): Response { |
||
544 | try { |
||
545 | $parent = $this->_getNode($destid, $destp, Collection::class, false, true); |
||
546 | } catch (Exception\NotFound $e) { |
||
547 | throw new Exception\NotFound( |
||
548 | 'destination collection was not found or is not a collection', |
||
549 | Exception\NotFound::DESTINATION_NOT_FOUND |
||
550 | ); |
||
551 | } |
||
552 | |||
553 | return $this->bulk($id, $p, function ($node) use ($parent, $conflict) { |
||
554 | $result = $node->copyTo($parent, $conflict); |
||
555 | |||
556 | return [ |
||
557 | 'code' => $parent == $result ? 200 : 201, |
||
558 | 'data' => $this->node_decorator->decorate($result), |
||
559 | ]; |
||
560 | }); |
||
561 | } |
||
562 | |||
563 | /** |
||
564 | * @api {post} /api/v2/nodes/:id/move Move node |
||
565 | * @apiVersion 2.0.0 |
||
566 | * @apiName postMove |
||
567 | * @apiGroup Node |
||
568 | * @apiPermission none |
||
569 | * @apiDescription Move node |
||
570 | * @apiUse _getNodes |
||
571 | * @apiUse _conflictNode |
||
572 | * @apiUse _multiError |
||
573 | * @apiUse _writeAction |
||
574 | * |
||
575 | * @apiParam (GET Parameter) {string} [destid] Either destid or destp (path) of the new parent collection node must be given. |
||
576 | * @apiParam (GET Parameter) {string} [destp] Either destid or destp (path) of the new parent collection node must be given. |
||
577 | * |
||
578 | * @apiExample (cURL) example: |
||
579 | * curl -XPOST "https://SERVER/api/v2/nodes/move?id=544627ed3c58891f058b4686?destid=544627ed3c58891f058b4655" |
||
580 | * curl -XPOST "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/move?destid=544627ed3c58891f058b4655" |
||
581 | * curl -XPOST "https://SERVER/api/v2/nodes/move?p=/absolute/path/to/my/node&destp=/new/parent&conflict=1 |
||
582 | * |
||
583 | * @apiSuccessExample {json} Success-Response: |
||
584 | * HTTP/1.1 204 No Content |
||
585 | * |
||
586 | * @apiSuccessExample {json} Success-Response (conflict=1): |
||
587 | * HTTP/1.1 200 OK |
||
588 | * { |
||
589 | * "status":200, |
||
590 | * "data": "renamed (xy23)" |
||
591 | * } |
||
592 | * |
||
593 | * @param array|string $id |
||
594 | * @param array|string $p |
||
595 | * @param string $destid |
||
596 | * @param string $destp |
||
597 | */ |
||
598 | public function postMove( |
||
599 | $id = null, |
||
600 | $p = null, |
||
601 | ?string $destid = null, |
||
602 | ?string $destp = null, |
||
603 | int $conflict = 0 |
||
604 | ): Response { |
||
605 | try { |
||
606 | $parent = $this->_getNode($destid, $destp, Collection::class, false, true); |
||
607 | } catch (Exception\NotFound $e) { |
||
608 | throw new Exception\NotFound( |
||
609 | 'destination collection was not found or is not a collection', |
||
610 | Exception\NotFound::DESTINATION_NOT_FOUND |
||
611 | ); |
||
612 | } |
||
613 | |||
614 | return $this->bulk($id, $p, function ($node) use ($parent, $conflict) { |
||
615 | $result = $node->setParent($parent, $conflict); |
||
616 | |||
617 | return [ |
||
618 | 'code' => 200, |
||
619 | 'data' => $this->node_decorator->decorate($node), |
||
620 | ]; |
||
621 | }); |
||
622 | } |
||
623 | |||
624 | /** |
||
625 | * @api {delete} /api/v2/nodes/:id Delete node |
||
626 | * @apiVersion 2.0.0 |
||
627 | * @apiName delete |
||
628 | * @apiGroup Node |
||
629 | * @apiPermission none |
||
630 | * @apiDescription Delete node |
||
631 | * @apiUse _getNodes |
||
632 | * @apiUse _multiError |
||
633 | * @apiUse _writeAction |
||
634 | * |
||
635 | * @apiParam (GET Parameter) {boolean} [force=false] Force flag need to be set to delete a node from trash (node must have the deleted flag) |
||
636 | * @apiParam (GET Parameter) {boolean} [ignore_flag=false] If both ignore_flag and force_flag were set, the node will be deleted completely |
||
637 | * @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 |
||
638 | * |
||
639 | * @apiExample (cURL) example: |
||
640 | * curl -XDELETE "https://SERVER/api/v2/node?id=544627ed3c58891f058b4686" |
||
641 | * curl -XDELETE "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686?force=1&ignore_flag=1" |
||
642 | * curl -XDELETE "https://SERVER/api/v2/node?p=/absolute/path/to/my/node" |
||
643 | * |
||
644 | * @apiSuccessExample {json} Success-Response: |
||
645 | * HTTP/1.1 204 No Content |
||
646 | * |
||
647 | * @param array|string $id |
||
648 | * @param array|string $p |
||
649 | * @param int $at |
||
650 | */ |
||
651 | public function delete( |
||
652 | $id = null, |
||
653 | $p = null, |
||
654 | bool $force = false, |
||
655 | bool $ignore_flag = false, |
||
656 | ?string $at = null |
||
657 | ): Response { |
||
658 | $failures = []; |
||
659 | |||
660 | if (null !== $at && '0' !== $at) { |
||
661 | $at = $this->_verifyAttributes(['destroy' => $at])['destroy']; |
||
662 | } |
||
663 | |||
664 | return $this->bulk($id, $p, function ($node) use ($force, $ignore_flag, $at) { |
||
665 | if (null === $at) { |
||
666 | $node->delete($force && $node->isDeleted() || $force && $ignore_flag); |
||
667 | } else { |
||
668 | if ('0' === $at) { |
||
669 | $at = null; |
||
670 | } |
||
671 | $node->setDestroyable($at); |
||
672 | } |
||
673 | |||
674 | return [ |
||
675 | 'code' => 204, |
||
676 | ]; |
||
677 | }); |
||
678 | } |
||
679 | |||
680 | /** |
||
681 | * @api {get} /api/v2/nodes/trash Get trash |
||
682 | * @apiName getTrash |
||
683 | * @apiVersion 2.0.0 |
||
684 | * @apiGroup Node |
||
685 | * @apiPermission none |
||
686 | * @apiDescription A similar endpoint to /api/v2/nodes/query filer={'deleted': {$type: 9}] but instead returning all deleted |
||
687 | * nodes (including children which are deleted as well) this enpoint only returns the first deleted node from every subtree) |
||
688 | * @apiUse _nodeAttributes |
||
689 | * |
||
690 | * @apiExample (cURL) example: |
||
691 | * curl -XGET https://SERVER/api/v2/nodes/trash?pretty |
||
692 | * |
||
693 | * @apiParam (GET Parameter) {string[]} [attributes] Filter node attributes |
||
694 | * |
||
695 | * @apiSuccess (200 OK) {object[]} - List of deleted nodes |
||
696 | * @apiSuccessExample {json} Success-Response: |
||
697 | * HTTP/1.1 200 OK |
||
698 | * [ |
||
699 | * { |
||
700 | * } |
||
701 | * ] |
||
702 | */ |
||
703 | public function getTrash(array $attributes = [], int $offset = 0, int $limit = 20): Response |
||
704 | { |
||
705 | $children = []; |
||
706 | $nodes = $this->fs->findNodesByFilterUser(NodeInterface::DELETED_ONLY, ['deleted' => ['$type' => 9]], $offset, $limit); |
||
707 | |||
708 | foreach ($nodes as $node) { |
||
709 | try { |
||
710 | $parent = $node->getParent(); |
||
711 | if (null !== $parent && $parent->isDeleted()) { |
||
712 | continue; |
||
713 | } |
||
714 | } catch (\Exception $e) { |
||
715 | //skip exception |
||
716 | } |
||
717 | |||
718 | $children[] = $node; |
||
719 | } |
||
720 | |||
721 | if ($this instanceof ApiFile) { |
||
722 | $query['directory'] = false; |
||
0 ignored issues
–
show
|
|||
723 | $uri = '/api/v2/files'; |
||
724 | } elseif ($this instanceof ApiCollection) { |
||
725 | $query['directory'] = true; |
||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
$query was never initialized. Although not strictly required by PHP, it is generally a good practice to add $query = array(); before regardless.
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code. Let’s take a look at an example: foreach ($collection as $item) {
$myArray['foo'] = $item->getFoo();
if ($item->hasBar()) {
$myArray['bar'] = $item->getBar();
}
// do something with $myArray
}
As you can see in this example, the array This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.
Loading history...
|
|||
726 | $uri = '/api/v2/collections'; |
||
727 | } else { |
||
728 | $uri = '/api/v2/nodes'; |
||
729 | } |
||
730 | |||
731 | $pager = new Pager($this->node_decorator, $children, $attributes, $offset, $limit, $uri, $nodes->getReturn()); |
||
732 | $result = $pager->paging(); |
||
733 | |||
734 | return (new Response())->setCode(200)->setBody($result); |
||
735 | } |
||
736 | |||
737 | /** |
||
738 | * @api {get} /api/v2/nodes/delta Get delta |
||
739 | * @apiVersion 2.0.0 |
||
740 | * @apiName getDelta |
||
741 | * @apiGroup Node |
||
742 | * @apiPermission none |
||
743 | * @apiUse _getNode |
||
744 | * |
||
745 | * @apiDescription Use this method to request a delta feed with all changes on the server (or) a snapshot of the server state. |
||
746 | * 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. |
||
747 | * If has_more is TRUE you need to request /delta immediatly again to |
||
748 | * receive the next bunch of deltas. If has_more is FALSE you should wait at least 120s seconds before any further requests to the |
||
749 | * 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). |
||
750 | * 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 |
||
751 | * without a cursor. reset could be TRUE if there was an account maintenance or a simialar case. |
||
752 | * You can request a different limit as well but be aware that the number of nodes could be slighty different from your requested limit. |
||
753 | * If requested with parameter id or p the delta gets generated recursively from the node given. |
||
754 | * |
||
755 | * @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 |
||
756 | * @apiParam (GET Parameter) {string[]} [attributes] Filter attributes, per default not all attributes would be returned |
||
757 | * @apiParam (GET Parameter) {string} [cursor=null] Set a cursor to rquest next nodes within delta processing |
||
758 | * |
||
759 | * @apiExample (cURL) example: |
||
760 | * curl -XGET "https://SERVER/api/v2/nodes/delta?pretty" |
||
761 | * |
||
762 | * @apiSuccess (200 OK) {boolean} reset If true the local state needs to be reseted, is alway TRUE during |
||
763 | * the first request to /delta without a cursor or in special cases like server or account maintenance |
||
764 | * @apiSuccess (200 OK) {string} cursor The cursor needs to be stored and reused to request further deltas |
||
765 | * @apiSuccess (200 OK) {boolean} has_more If has_more is TRUE /delta can be requested immediatly after the last request |
||
766 | * to receive further delta. If it is FALSE we should wait at least 120 seconds before any further delta requests to the api endpoint |
||
767 | * @apiSuccess (200 OK) {object[]} nodes Node list to process |
||
768 | * @apiSuccessExample {json} Success-Response: |
||
769 | * HTTP/1.1 200 OK |
||
770 | * { |
||
771 | * "reset": false, |
||
772 | * "cursor": "aW5pdGlhbHwxMDB8NTc1YTlhMGIzYzU4ODkwNTE0OGI0NTZifDU3NWE5YTBiM2M1ODg5MDUxNDhiNDU2Yw==", |
||
773 | * "has_more": false, |
||
774 | * "nodes": [ |
||
775 | * { |
||
776 | * "id": "581afa783c5889ad7c8b4572", |
||
777 | * "deleted": " 2008-08-31T16:47+00:00", |
||
778 | * "changed": "2007-08-31T16:47+00:00", |
||
779 | * "path": "\/AAA\/AL", |
||
780 | * "directory": true |
||
781 | * }, |
||
782 | * { |
||
783 | * "id": "581afa783c5889ad7c8b3dcf", |
||
784 | * "created": "2007-08-31T16:47+00:00", |
||
785 | * "changed": "2007-09-28T12:33+00:00", |
||
786 | * "path": "\/AL", |
||
787 | * "directory": true |
||
788 | * } |
||
789 | * ] |
||
790 | * } |
||
791 | * |
||
792 | * @param string $id |
||
793 | * @param string $p |
||
794 | * @param string $cursor |
||
795 | */ |
||
796 | public function getDelta( |
||
797 | DeltaAttributeDecorator $delta_decorator, |
||
798 | ?string $id = null, |
||
799 | ?string $p = null, |
||
800 | ?string $cursor = null, |
||
801 | int $limit = 250, |
||
802 | array $attributes = [] |
||
803 | ): Response { |
||
804 | if (null !== $id || null !== $p) { |
||
805 | $node = $this->_getNode($id, $p); |
||
806 | } else { |
||
807 | $node = null; |
||
808 | } |
||
809 | |||
810 | $result = $this->fs->getDelta()->getDeltaFeed($cursor, $limit, $node); |
||
811 | foreach ($result['nodes'] as &$node) { |
||
812 | if ($node instanceof NodeInterface) { |
||
813 | $node = $this->node_decorator->decorate($node, $attributes); |
||
814 | } else { |
||
815 | $node = $delta_decorator->decorate($node, $attributes); |
||
816 | } |
||
817 | } |
||
818 | |||
819 | return (new Response())->setCode(200)->setBody($result); |
||
820 | } |
||
821 | |||
822 | /** |
||
823 | * @api {get} /api/v2/nodes/:id/event-log Event log |
||
824 | * @apiVersion 2.0.0 |
||
825 | * @apiName getEventLog |
||
826 | * @apiGroup Node |
||
827 | * @apiPermission none |
||
828 | * @apiUse _getNode |
||
829 | * @apiDescription Get detailed event log |
||
830 | * Request all modifications which are made by the user himself or share members. |
||
831 | * Possible operations are the follwing: |
||
832 | * - deleteCollectionReference |
||
833 | * - deleteCollectionShare |
||
834 | * - deleteCollection |
||
835 | * - addCollection |
||
836 | * - addFile |
||
837 | * - addCollectionShare |
||
838 | * - addCollectionReference |
||
839 | * - undeleteFile |
||
840 | * - undeleteCollectionReference |
||
841 | * - undeleteCollectionShare |
||
842 | * - restoreFile |
||
843 | * - renameFile |
||
844 | * - renameCollection |
||
845 | * - renameCollectionShare |
||
846 | * - renameCollectionRFeference |
||
847 | * - copyFile |
||
848 | * - copyCollection |
||
849 | * - copyCollectionShare |
||
850 | * - copyCollectionRFeference |
||
851 | * - moveFile |
||
852 | * - moveCollection |
||
853 | * - moveCollectionReference |
||
854 | * - moveCollectionShare |
||
855 | * |
||
856 | * @apiExample (cURL) example: |
||
857 | * curl -XGET "https://SERVER/api/v2/nodes/event-log?pretty" |
||
858 | * curl -XGET "https://SERVER/api/v2/nodes/event-log?id=544627ed3c58891f058b4686&pretty" |
||
859 | * curl -XGET "https://SERVER/api/v2/nodes/544627ed3c58891f058b4686/event-log?pretty&limit=10" |
||
860 | * curl -XGET "https://SERVER/api/v2/nodes/event-log?p=/absolute/path/to/my/node&pretty" |
||
861 | * |
||
862 | * @apiParam (GET Parameter) {number} [limit=100] Sets limit of events to be returned |
||
863 | * @apiParam (GET Parameter) {number} [skip=0] How many events are skiped (useful for paging) |
||
864 | * |
||
865 | * @apiSuccess (200 OK) {object[]} - List of events |
||
866 | * @apiSuccess (200 OK) {number} -.event Event ID |
||
867 | * @apiSuccess (200 OK) {object} -.timestamp ISO8601 timestamp when the event occured |
||
868 | * @apiSuccess (200 OK) {string} -.operation event operation (like addCollection, deleteFile, ...) |
||
869 | * @apiSuccess (200 OK) {object} -.parent Parent node object at the time of the event |
||
870 | * @apiSuccess (200 OK) {object} -.previous Previous state of actual data which has been modified during an event, can contain either version, name or parent |
||
871 | * @apiSuccess (200 OK) {number} -.previous.version Version at the time before the event |
||
872 | * @apiSuccess (200 OK) {string} -.previous.name Name at the time before the event |
||
873 | * @apiSuccess (200 OK) {object} -.previous.parent Parent node object at the time before the event |
||
874 | * @apiSuccess (200 OK) {object} -.share shared collection object at the time of the event (If the node was part of a share) |
||
875 | * @apiSuccess (200 OK) {string} -.name Name of the node at the time of the event |
||
876 | * @apiSuccess (200 OK) {object} -.node Current node object |
||
877 | * @apiSuccess (200 OK) {object} -.user User who executed an event |
||
878 | * |
||
879 | * @apiSuccessExample {json} Success-Response: |
||
880 | * HTTP/1.1 200 OK |
||
881 | * [ |
||
882 | * { |
||
883 | * "id": "57628e523c5889026f8b4570", |
||
884 | * "timestamp": " 2018-01-02T13:22+00:00", |
||
885 | * "operation": "restoreFile", |
||
886 | * "name": "file.txt", |
||
887 | * "previous": { |
||
888 | * "version": 16 |
||
889 | * }, |
||
890 | * "node": { |
||
891 | * "id": "558c0b273c588963078b457a", |
||
892 | * "name": "3dddsceheckfile.txt", |
||
893 | * "deleted": false |
||
894 | * }, |
||
895 | * "parent": null, |
||
896 | * "user": { |
||
897 | * "id": "54354cb63c58891f058b457f", |
||
898 | * "username": "example" |
||
899 | * } |
||
900 | * } |
||
901 | * ] |
||
902 | * |
||
903 | * @param string $id |
||
904 | * @param string $p |
||
905 | */ |
||
906 | public function getEventLog(EventAttributeDecorator $event_decorator, ?string $id = null, ?string $p = null, ?array $attributes = [], int $offset = 0, int $limit = 20): Response |
||
907 | { |
||
908 | if (null !== $id || null !== $p) { |
||
909 | $node = $this->_getNode($id, $p); |
||
910 | $uri = '/api/v2/nodes/'.$node->getId().'/event-log'; |
||
911 | } else { |
||
912 | $node = null; |
||
913 | $uri = '/api/v2/nodes/event-log'; |
||
914 | } |
||
915 | |||
916 | $result = $this->fs->getDelta()->getEventLog($limit, $offset, $node, $total); |
||
917 | $pager = new Pager($event_decorator, $result, $attributes, $offset, $limit, $uri, $total); |
||
918 | |||
919 | return (new Response())->setCode(200)->setBody($pager->paging()); |
||
920 | } |
||
921 | |||
922 | /** |
||
923 | * @api {get} /api/v2/nodes/last-cursor Get last Cursor |
||
924 | * @apiVersion 2.0.0 |
||
925 | * @apiName geLastCursor |
||
926 | * @apiGroup Node |
||
927 | * @apiUse _getNode |
||
928 | * @apiPermission none |
||
929 | * @apiDescription Use this method to request the latest cursor if you only need to now |
||
930 | * if there are changes on the server. This method will not return any other data than the |
||
931 | * newest cursor. To request a feed with all deltas request /delta. |
||
932 | * |
||
933 | * @apiExample (cURL) example: |
||
934 | * curl -XGET "https://SERVER/api/v2/nodes/last-cursor?pretty" |
||
935 | * |
||
936 | * @apiSuccess (200 OK) {string} cursor v2 cursor |
||
937 | * @apiSuccessExample {json} Success-Response: |
||
938 | * HTTP/1.1 200 OK |
||
939 | * "aW5pdGlhbHwxMDB8NTc1YTlhMGIzYzU4ODkwNTE0OGI0NTZifDU3NWE5YTBiM2M1ODg5MDUxNDhiNDU2Yw==" |
||
940 | * |
||
941 | * @param string $id |
||
942 | * @param string $p |
||
943 | */ |
||
944 | public function getLastCursor(?string $id = null, ?string $p = null): Response |
||
945 | { |
||
946 | if (null !== $id || null !== $p) { |
||
947 | $node = $this->_getNode($id, $p); |
||
948 | } else { |
||
949 | $node = null; |
||
950 | } |
||
951 | |||
952 | $result = $this->fs->getDelta()->getLastCursor(); |
||
953 | |||
954 | return (new Response())->setCode(200)->setBody($result); |
||
955 | } |
||
956 | |||
957 | /** |
||
958 | * Merge multiple nodes into one zip archive. |
||
959 | * |
||
960 | * @param string $id |
||
961 | * @param string $path |
||
962 | */ |
||
963 | protected function combine($id = null, $path = null, string $name = 'selected') |
||
964 | { |
||
965 | $archive = new ZipStream($name.'.zip'); |
||
966 | |||
967 | foreach ($this->_getNodes($id, $path) as $node) { |
||
968 | try { |
||
969 | $node->zip($archive); |
||
970 | //json_decode($stored, true), |
||
971 | } catch (\Exception $e) { |
||
972 | $this->logger->debug('failed zip node in multi node request ['.$node->getId().']', [ |
||
973 | 'category' => get_class($this), |
||
974 | 'exception' => $e, |
||
975 | ]); |
||
976 | } |
||
977 | } |
||
978 | |||
979 | $archive->finish(); |
||
980 | } |
||
981 | |||
982 | /** |
||
983 | * Check custom node attributes which have to be written. |
||
984 | */ |
||
985 | protected function _verifyAttributes(array $attributes): array |
||
986 | { |
||
987 | $valid_attributes = [ |
||
988 | 'changed', |
||
989 | 'destroy', |
||
990 | 'created', |
||
991 | 'meta', |
||
992 | 'readonly', |
||
993 | 'acl', |
||
994 | ]; |
||
995 | |||
996 | if ($this instanceof ApiCollection) { |
||
997 | $valid_attributes += ['filter', 'mount']; |
||
998 | } |
||
999 | |||
1000 | $check = array_merge(array_flip($valid_attributes), $attributes); |
||
1001 | |||
1002 | if ($this instanceof ApiCollection && count($check) > 8) { |
||
1003 | throw new Exception\InvalidArgument('Only changed, created, destroy timestamp, acl, filter, mount, readonly and/or meta attributes may be overwritten'); |
||
1004 | } |
||
1005 | if ($this instanceof ApiFile && count($check) > 6) { |
||
1006 | throw new Exception\InvalidArgument('Only changed, created, destroy timestamp, acl, readonly and/or meta attributes may be overwritten'); |
||
1007 | } |
||
1008 | |||
1009 | foreach ($attributes as $attribute => $value) { |
||
1010 | switch ($attribute) { |
||
1011 | case 'filter': |
||
1012 | if (!is_array($value)) { |
||
1013 | throw new Exception\InvalidArgument($attribute.' must be an array'); |
||
1014 | } |
||
1015 | |||
1016 | $attributes['filter'] = json_encode($value); |
||
1017 | |||
1018 | break; |
||
1019 | case 'destroy': |
||
1020 | if (!Helper::isValidTimestamp($value)) { |
||
1021 | throw new Exception\InvalidArgument($attribute.' timestamp must be valid unix timestamp'); |
||
1022 | } |
||
1023 | $attributes[$attribute] = new UTCDateTime($value.'000'); |
||
1024 | |||
1025 | break; |
||
1026 | case 'changed': |
||
1027 | case 'created': |
||
1028 | if (!Helper::isValidTimestamp($value)) { |
||
1029 | throw new Exception\InvalidArgument($attribute.' timestamp must be valid unix timestamp'); |
||
1030 | } |
||
1031 | if ((int) $value > time()) { |
||
1032 | throw new Exception\InvalidArgument($attribute.' timestamp can not be set greater than the server time'); |
||
1033 | } |
||
1034 | $attributes[$attribute] = new UTCDateTime($value.'000'); |
||
1035 | |||
1036 | break; |
||
1037 | case 'readonly': |
||
1038 | $attributes['readonly'] = (bool) $attributes['readonly']; |
||
1039 | |||
1040 | break; |
||
1041 | } |
||
1042 | } |
||
1043 | |||
1044 | return $attributes; |
||
1045 | } |
||
1046 | } |
||
1047 |
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.
Let’s take a look at an example:
As you can see in this example, the array
$myArray
is initialized the first time when the foreach loop is entered. You can also see that the value of thebar
key is only written conditionally; thus, its value might result from a previous iteration.This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.