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\Filesystem\Node; |
||
13 | |||
14 | use Balloon\Filesystem; |
||
15 | use Balloon\Filesystem\Acl; |
||
16 | use Balloon\Filesystem\Acl\Exception\Forbidden as ForbiddenException; |
||
17 | use Balloon\Filesystem\Exception; |
||
18 | use Balloon\Filesystem\Storage\Adapter\AdapterInterface as StorageAdapterInterface; |
||
19 | use Balloon\Hook; |
||
20 | use Balloon\Server\User; |
||
21 | use Generator; |
||
22 | use MimeType\MimeType; |
||
23 | use MongoDB\BSON\ObjectId; |
||
24 | use MongoDB\BSON\Regex; |
||
25 | use MongoDB\BSON\UTCDateTime; |
||
26 | use Psr\Log\LoggerInterface; |
||
27 | use Sabre\DAV\IQuota; |
||
28 | |||
29 | class Collection extends AbstractNode implements IQuota |
||
30 | { |
||
31 | /** |
||
32 | * Root folder. |
||
33 | */ |
||
34 | const ROOT_FOLDER = '/'; |
||
35 | |||
36 | /** |
||
37 | * Share acl. |
||
38 | * |
||
39 | * @var array |
||
40 | */ |
||
41 | protected $acl = []; |
||
42 | |||
43 | /** |
||
44 | * Share name. |
||
45 | * |
||
46 | * @var string |
||
47 | */ |
||
48 | protected $share_name; |
||
49 | |||
50 | /** |
||
51 | * filter. |
||
52 | * |
||
53 | * @var string |
||
54 | */ |
||
55 | protected $filter; |
||
56 | |||
57 | /** |
||
58 | * Storage. |
||
59 | * |
||
60 | * @var StorageAdapterInterface |
||
61 | */ |
||
62 | protected $_storage; |
||
63 | |||
64 | /** |
||
65 | * Initialize. |
||
66 | */ |
||
67 | public function __construct(array $attributes, Filesystem $fs, LoggerInterface $logger, Hook $hook, Acl $acl, ?Collection $parent, StorageAdapterInterface $storage) |
||
68 | { |
||
69 | $this->_fs = $fs; |
||
70 | $this->_server = $fs->getServer(); |
||
71 | $this->_db = $fs->getDatabase(); |
||
72 | $this->_user = $fs->getUser(); |
||
73 | $this->_logger = $logger; |
||
74 | $this->_hook = $hook; |
||
75 | $this->_acl = $acl; |
||
76 | $this->_storage = $storage; |
||
77 | $this->_parent = $parent; |
||
78 | |||
79 | foreach ($attributes as $attr => $value) { |
||
80 | $this->{$attr} = $value; |
||
81 | } |
||
82 | |||
83 | $this->mime = 'inode/directory'; |
||
84 | $this->raw_attributes = $attributes; |
||
85 | } |
||
86 | |||
87 | /** |
||
88 | * Set storage adapter. |
||
89 | */ |
||
90 | public function setStorage(StorageAdapterInterface $adapter): self |
||
91 | { |
||
92 | $this->_storage = $adapter; |
||
93 | |||
94 | return $this; |
||
95 | } |
||
96 | |||
97 | /** |
||
98 | * Get storage adapter. |
||
99 | */ |
||
100 | public function getStorage(): StorageAdapterInterface |
||
101 | { |
||
102 | return $this->_storage; |
||
103 | } |
||
104 | |||
105 | /** |
||
106 | * Copy node with children. |
||
107 | */ |
||
108 | public function copyTo(self $parent, int $conflict = NodeInterface::CONFLICT_NOACTION, ?string $recursion = null, bool $recursion_first = true): NodeInterface |
||
109 | { |
||
110 | if (null === $recursion) { |
||
111 | $recursion_first = true; |
||
112 | $recursion = uniqid(); |
||
113 | } else { |
||
114 | $recursion_first = false; |
||
115 | } |
||
116 | |||
117 | $this->_hook->run( |
||
118 | 'preCopyCollection', |
||
119 | [$this, $parent, &$conflict, &$recursion, &$recursion_first] |
||
120 | ); |
||
121 | |||
122 | if (NodeInterface::CONFLICT_RENAME === $conflict && $parent->childExists($this->name)) { |
||
123 | $name = $this->getDuplicateName(); |
||
124 | } else { |
||
125 | $name = $this->name; |
||
126 | } |
||
127 | |||
128 | if ($this->_id === $parent->getId()) { |
||
129 | throw new Exception\Conflict( |
||
130 | 'can not copy node into itself', |
||
131 | Exception\Conflict::CANT_COPY_INTO_ITSELF |
||
132 | ); |
||
133 | } |
||
134 | |||
135 | if (NodeInterface::CONFLICT_MERGE === $conflict && $parent->childExists($this->name)) { |
||
136 | $new_parent = $parent->getChild($this->name); |
||
137 | |||
138 | if ($new_parent instanceof File) { |
||
139 | $new_parent = $this; |
||
140 | } |
||
141 | } else { |
||
142 | $new_parent = $parent->addDirectory($name, [ |
||
143 | 'created' => $this->created, |
||
144 | 'changed' => $this->changed, |
||
145 | 'filter' => $this->filter, |
||
146 | 'meta' => $this->meta, |
||
147 | ], NodeInterface::CONFLICT_NOACTION, true); |
||
148 | } |
||
149 | |||
150 | foreach ($this->getChildNodes(NodeInterface::DELETED_INCLUDE) as $child) { |
||
151 | $child->copyTo($new_parent, $conflict, $recursion, false); |
||
152 | } |
||
153 | |||
154 | $this->_hook->run( |
||
155 | 'postCopyCollection', |
||
156 | [$this, $parent, $new_parent, $conflict, $recursion, $recursion_first] |
||
157 | ); |
||
158 | |||
159 | return $new_parent; |
||
160 | } |
||
161 | |||
162 | /** |
||
163 | * Is mount. |
||
164 | */ |
||
165 | public function isMounted(): bool |
||
166 | { |
||
167 | return count($this->mount) > 0; |
||
168 | } |
||
169 | |||
170 | /** |
||
171 | * Get Share name. |
||
172 | */ |
||
173 | public function getShareName(): string |
||
174 | { |
||
175 | if ($this->isShare()) { |
||
176 | return $this->share_name; |
||
177 | } |
||
178 | |||
179 | return $this->_fs->findRawNode($this->getShareId())['share_name']; |
||
0 ignored issues
–
show
|
|||
180 | } |
||
181 | |||
182 | /** |
||
183 | * Get Attributes. |
||
184 | */ |
||
185 | public function getAttributes(): array |
||
186 | { |
||
187 | return [ |
||
188 | '_id' => $this->_id, |
||
189 | 'name' => $this->name, |
||
190 | 'shared' => $this->shared, |
||
191 | 'share_name' => $this->share_name, |
||
192 | 'acl' => $this->acl, |
||
193 | 'directory' => true, |
||
194 | 'reference' => $this->reference, |
||
195 | 'parent' => $this->parent, |
||
196 | 'app' => $this->app, |
||
197 | 'owner' => $this->owner, |
||
198 | 'meta' => $this->meta, |
||
199 | 'mime' => $this->mime, |
||
200 | 'filter' => $this->filter, |
||
201 | 'deleted' => $this->deleted, |
||
202 | 'changed' => $this->changed, |
||
203 | 'created' => $this->created, |
||
204 | 'destroy' => $this->destroy, |
||
205 | 'readonly' => $this->readonly, |
||
206 | 'mount' => $this->mount, |
||
207 | 'storage_reference' => $this->storage_reference, |
||
208 | 'storage' => $this->storage, |
||
209 | ]; |
||
210 | } |
||
211 | |||
212 | /** |
||
213 | * Set collection filter. |
||
214 | */ |
||
215 | public function setFilter(?array $filter = null): bool |
||
216 | { |
||
217 | $this->filter = json_encode($filter); |
||
218 | |||
219 | return $this->save('filter'); |
||
220 | } |
||
221 | |||
222 | /** |
||
223 | * Get collection. |
||
224 | */ |
||
225 | public function get(): void |
||
226 | { |
||
227 | $this->getZip(); |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * Fetch children items of this collection. |
||
232 | * |
||
233 | * Deleted: |
||
234 | * 0 - Exclude deleted |
||
235 | * 1 - Only deleted |
||
236 | * 2 - Include deleted |
||
237 | * |
||
238 | * @param int $offset |
||
239 | * @param int $limit |
||
240 | */ |
||
241 | public function getChildNodes(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = [], ?int $offset = null, ?int $limit = null): Generator |
||
242 | { |
||
243 | $filter = $this->getChildrenFilter($deleted, $filter); |
||
244 | |||
245 | return $this->_fs->findNodesByFilter($filter, $offset, $limit); |
||
246 | } |
||
247 | |||
248 | /** |
||
249 | * Fetch children items of this collection (as array). |
||
250 | * |
||
251 | * Deleted: |
||
252 | * 0 - Exclude deleted |
||
253 | * 1 - Only deleted |
||
254 | * 2 - Include deleted |
||
255 | */ |
||
256 | public function getChildren(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): array |
||
257 | { |
||
258 | return iterator_to_array($this->getChildNodes($deleted, $filter)); |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * Is custom filter node. |
||
263 | */ |
||
264 | public function isFiltered(): bool |
||
265 | { |
||
266 | return !empty($this->filter); |
||
267 | } |
||
268 | |||
269 | /** |
||
270 | * Get number of children. |
||
271 | */ |
||
272 | public function getSize(): int |
||
273 | { |
||
274 | return count($this->getChildren()); |
||
275 | } |
||
276 | |||
277 | /** |
||
278 | * Get real id (reference). |
||
279 | * |
||
280 | * @return ObjectId |
||
281 | */ |
||
282 | public function getRealId(): ?ObjectId |
||
283 | { |
||
284 | if (true === $this->shared && $this->isReference()) { |
||
285 | return $this->reference; |
||
286 | } |
||
287 | |||
288 | return $this->_id; |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * Get user quota information. |
||
293 | */ |
||
294 | public function getQuotaInfo(): array |
||
295 | { |
||
296 | $quota = $this->_user->getQuotaUsage(); |
||
297 | |||
298 | return [ |
||
299 | $quota['used'], |
||
300 | $quota['available'], |
||
301 | ]; |
||
302 | } |
||
303 | |||
304 | /** |
||
305 | * Fetch children items of this collection. |
||
306 | */ |
||
307 | public function getChild($name, int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): NodeInterface |
||
308 | { |
||
309 | $name = $this->checkName($name); |
||
310 | $filter = $this->getChildrenFilter($deleted, $filter); |
||
311 | $filter['name'] = new Regex('^'.preg_quote($name).'$', 'i'); |
||
312 | $node = $this->_db->storage->findOne($filter); |
||
313 | |||
314 | if (null === $node) { |
||
315 | throw new Exception\NotFound( |
||
316 | 'node called '.$name.' does not exists here', |
||
317 | Exception\NotFound::NODE_NOT_FOUND |
||
318 | ); |
||
319 | } |
||
320 | |||
321 | $this->_logger->debug('loaded node ['.$node['_id'].' from parent node ['.$this->getRealId().']', [ |
||
322 | 'category' => get_class($this), |
||
323 | ]); |
||
324 | |||
325 | return $this->_fs->initNode($node); |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * Delete node. |
||
330 | * |
||
331 | * Actually the node will not be deleted (Just set a delete flag), set $force=true to |
||
332 | * delete finally |
||
333 | */ |
||
334 | public function delete(bool $force = false, ?string $recursion = null, bool $recursion_first = true): bool |
||
335 | { |
||
336 | if (!$this->isReference() && !$this->_acl->isAllowed($this, 'w')) { |
||
337 | throw new ForbiddenException( |
||
338 | 'not allowed to delete node '.$this->name, |
||
339 | ForbiddenException::NOT_ALLOWED_TO_DELETE |
||
340 | ); |
||
341 | } |
||
342 | |||
343 | if (null === $recursion) { |
||
344 | $recursion_first = true; |
||
345 | $recursion = uniqid(); |
||
346 | } else { |
||
347 | $recursion_first = false; |
||
348 | } |
||
349 | |||
350 | $this->_hook->run( |
||
351 | 'preDeleteCollection', |
||
352 | [$this, &$force, &$recursion, &$recursion_first] |
||
353 | ); |
||
354 | |||
355 | if (true === $force) { |
||
356 | return $this->_forceDelete($recursion, $recursion_first); |
||
357 | } |
||
358 | |||
359 | $this->deleted = new UTCDateTime(); |
||
360 | $this->storage = $this->_parent->getStorage()->deleteCollection($this); |
||
361 | |||
362 | if (!$this->isReference() && !$this->isMounted() && !$this->isFiltered()) { |
||
363 | $this->doRecursiveAction(function ($node) use ($recursion) { |
||
364 | $node->delete(false, $recursion, false); |
||
365 | }, NodeInterface::DELETED_EXCLUDE); |
||
366 | } |
||
367 | |||
368 | if (null !== $this->_id) { |
||
369 | $result = $this->save([ |
||
370 | 'deleted', 'storage', |
||
371 | ], [], $recursion, false); |
||
372 | } else { |
||
373 | $result = true; |
||
374 | } |
||
375 | |||
376 | $this->_hook->run( |
||
377 | 'postDeleteCollection', |
||
378 | [$this, $force, $recursion, $recursion_first] |
||
379 | ); |
||
380 | |||
381 | return $result; |
||
382 | } |
||
383 | |||
384 | /** |
||
385 | * Check if this collection has child named $name. |
||
386 | * |
||
387 | * deleted: |
||
388 | * |
||
389 | * 0 - Exclude deleted |
||
390 | * 1 - Only deleted |
||
391 | * 2 - Include deleted |
||
392 | * |
||
393 | * @param string $name |
||
394 | * @param int $deleted |
||
395 | */ |
||
396 | public function childExists($name, $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): bool |
||
397 | { |
||
398 | $name = $this->checkName($name); |
||
399 | |||
400 | $find = [ |
||
401 | 'parent' => $this->getRealId(), |
||
402 | 'name' => new Regex('^'.preg_quote($name).'$', 'i'), |
||
403 | ]; |
||
404 | |||
405 | if (null !== $this->_user) { |
||
406 | $find['owner'] = $this->_user->getId(); |
||
407 | } |
||
408 | |||
409 | switch ($deleted) { |
||
410 | case NodeInterface::DELETED_EXCLUDE: |
||
411 | $find['deleted'] = false; |
||
412 | |||
413 | break; |
||
414 | case NodeInterface::DELETED_ONLY: |
||
415 | $find['deleted'] = ['$type' => 9]; |
||
416 | |||
417 | break; |
||
418 | } |
||
419 | |||
420 | $find = array_merge($filter, $find); |
||
421 | |||
422 | if ($this->isSpecial()) { |
||
423 | unset($find['owner']); |
||
424 | } |
||
425 | |||
426 | $node = $this->_db->storage->findOne($find); |
||
427 | |||
428 | return (bool) $node; |
||
429 | } |
||
430 | |||
431 | /** |
||
432 | * Share collection. |
||
433 | */ |
||
434 | public function share(array $acl, string $name): bool |
||
435 | { |
||
436 | if ($this->isShareMember()) { |
||
437 | throw new Exception('a sub node of a share can not be shared'); |
||
438 | } |
||
439 | |||
440 | $this->_acl->validateAcl($this->_server, $acl); |
||
441 | |||
442 | $action = [ |
||
443 | '$set' => [ |
||
444 | 'shared' => $this->getRealId(), |
||
445 | ], |
||
446 | ]; |
||
447 | |||
448 | $toset = $this->getChildrenRecursive($this->getRealId(), $shares); |
||
449 | |||
450 | if (!empty($shares)) { |
||
451 | throw new Exception('child folder contains a shared folder'); |
||
452 | } |
||
453 | |||
454 | $this->_db->storage->updateMany([ |
||
455 | '_id' => [ |
||
456 | '$in' => $toset, |
||
457 | ], |
||
458 | ], $action); |
||
459 | |||
460 | $this->_db->delta->updateMany([ |
||
461 | '_id' => [ |
||
462 | '$in' => $toset, |
||
463 | ], |
||
464 | ], $action); |
||
465 | |||
466 | if ($this->getRealId() === $this->_id) { |
||
467 | $this->acl = $acl; |
||
468 | $this->shared = true; |
||
469 | $this->share_name = $name; |
||
470 | $this->save(['acl', 'shared', 'share_name']); |
||
471 | } else { |
||
472 | $this->_db->storage->updateOne([ |
||
473 | '_id' => $this->getRealId(), |
||
474 | ], [ |
||
475 | '$set' => [ |
||
476 | 'share_name' => $name, |
||
477 | 'acl' => $acl, |
||
478 | ], |
||
479 | ]); |
||
480 | } |
||
481 | |||
482 | return true; |
||
483 | } |
||
484 | |||
485 | /** |
||
486 | * Unshare collection. |
||
487 | */ |
||
488 | public function unshare(): bool |
||
489 | { |
||
490 | if (!$this->_acl->isAllowed($this, 'm')) { |
||
491 | throw new ForbiddenException( |
||
492 | 'not allowed to share node', |
||
493 | ForbiddenException::NOT_ALLOWED_TO_MANAGE |
||
494 | ); |
||
495 | } |
||
496 | |||
497 | if (true !== $this->shared) { |
||
498 | throw new Exception\Conflict( |
||
499 | 'Can not unshare a none shared collection', |
||
500 | Exception\Conflict::NOT_SHARED |
||
501 | ); |
||
502 | } |
||
503 | |||
504 | $this->shared = false; |
||
505 | $this->share_name = null; |
||
506 | $this->acl = []; |
||
507 | $action = [ |
||
508 | '$set' => [ |
||
509 | 'owner' => $this->_user->getId(), |
||
510 | 'shared' => false, |
||
511 | ], |
||
512 | ]; |
||
513 | |||
514 | $toset = $this->getChildrenRecursive($this->getRealId(), $shares); |
||
515 | |||
516 | $this->_db->storage->updateMany([ |
||
517 | '_id' => [ |
||
518 | '$in' => $toset, |
||
519 | ], |
||
520 | ], $action); |
||
521 | |||
522 | $result = $this->save(['shared'], ['acl', 'share_name']); |
||
523 | |||
524 | return true; |
||
525 | } |
||
526 | |||
527 | /** |
||
528 | * Get children. |
||
529 | */ |
||
530 | public function getChildrenRecursive(?ObjectId $id = null, ?array &$shares = []): array |
||
531 | { |
||
532 | $list = []; |
||
533 | $result = $this->_db->storage->find([ |
||
534 | 'parent' => $id, |
||
535 | ], [ |
||
536 | '_id' => 1, |
||
537 | 'directory' => 1, |
||
538 | 'reference' => 1, |
||
539 | 'shared' => 1, |
||
540 | ]); |
||
541 | |||
542 | foreach ($result as $node) { |
||
543 | $list[] = $node['_id']; |
||
544 | |||
545 | if ($node['directory'] === true) { |
||
546 | if (isset($node['reference']) || isset($node['shared']) && true === $node['shared']) { |
||
547 | $shares[] = $node['_id']; |
||
548 | } |
||
549 | |||
550 | if (true === $node['directory'] && !isset($node['reference'])) { |
||
551 | $list = array_merge($list, $this->getChildrenRecursive($node['_id'], $shares)); |
||
552 | } |
||
553 | } |
||
554 | } |
||
555 | |||
556 | return $list; |
||
557 | } |
||
558 | |||
559 | /** |
||
560 | * Create new directory. |
||
561 | */ |
||
562 | public function addDirectory($name, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): self |
||
563 | { |
||
564 | if (!$this->_acl->isAllowed($this, 'w')) { |
||
565 | throw new ForbiddenException( |
||
566 | 'not allowed to create new node here', |
||
567 | ForbiddenException::NOT_ALLOWED_TO_CREATE |
||
568 | ); |
||
569 | } |
||
570 | |||
571 | $this->_hook->run('preCreateCollection', [$this, &$name, &$attributes, &$clone]); |
||
572 | |||
573 | if ($this->readonly) { |
||
574 | throw new Exception\Conflict( |
||
575 | 'node is set as readonly, it is not possible to add new sub nodes', |
||
576 | Exception\Conflict::READONLY |
||
577 | ); |
||
578 | } |
||
579 | |||
580 | $name = $this->checkName($name); |
||
581 | |||
582 | if ($this->childExists($name)) { |
||
583 | if (NodeInterface::CONFLICT_NOACTION === $conflict) { |
||
584 | throw new Exception\Conflict( |
||
585 | 'a node called '.$name.' does already exists in this collection', |
||
586 | Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS |
||
587 | ); |
||
588 | } |
||
589 | if (NodeInterface::CONFLICT_RENAME === $conflict) { |
||
590 | $name = $this->getDuplicateName($name); |
||
591 | } |
||
592 | } |
||
593 | |||
594 | if ($this->isDeleted()) { |
||
595 | throw new Exception\Conflict( |
||
596 | 'could not add node '.$name.' into a deleted parent collection', |
||
597 | Exception\Conflict::DELETED_PARENT |
||
598 | ); |
||
599 | } |
||
600 | |||
601 | $id = new ObjectId(); |
||
602 | |||
603 | try { |
||
604 | $meta = [ |
||
605 | '_id' => $id, |
||
606 | 'pointer' => $id, |
||
607 | 'name' => $name, |
||
608 | 'deleted' => false, |
||
609 | 'parent' => $this->getRealId(), |
||
610 | 'directory' => true, |
||
611 | 'created' => new UTCDateTime(), |
||
612 | 'changed' => new UTCDateTime(), |
||
613 | 'shared' => (true === $this->shared ? $this->getRealId() : $this->shared), |
||
614 | 'storage' => $this->_storage->createCollection($this, $name), |
||
615 | 'storage_reference' => $this->getMount(), |
||
616 | ]; |
||
617 | |||
618 | if (null !== $this->_user) { |
||
619 | $meta['owner'] = $this->_user->getId(); |
||
620 | } |
||
621 | |||
622 | $save = array_merge($meta, $attributes); |
||
623 | |||
624 | if (isset($save['acl'])) { |
||
625 | $this->validateAcl($save['acl']); |
||
626 | } |
||
627 | |||
628 | $result = $this->_db->storage->insertOne($save); |
||
629 | |||
630 | $this->_logger->info('added new collection ['.$save['_id'].'] under parent ['.$this->_id.']', [ |
||
631 | 'category' => get_class($this), |
||
632 | ]); |
||
633 | |||
634 | $this->changed = $save['changed']; |
||
635 | $this->save('changed'); |
||
636 | |||
637 | $new = $this->_fs->initNode($save); |
||
638 | $this->_hook->run('postCreateCollection', [$this, $new, $clone]); |
||
639 | |||
640 | return $new; |
||
641 | } catch (\Exception $e) { |
||
642 | $this->_logger->error('failed create new collection under parent ['.$this->_id.']', [ |
||
643 | 'category' => get_class($this), |
||
644 | 'exception' => $e, |
||
645 | ]); |
||
646 | |||
647 | throw $e; |
||
648 | } |
||
649 | } |
||
650 | |||
651 | /** |
||
652 | * Create new file as a child from this collection. |
||
653 | */ |
||
654 | public function addFile($name, ?ObjectId $session = null, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): File |
||
655 | { |
||
656 | if (!$this->_acl->isAllowed($this, 'w')) { |
||
657 | throw new ForbiddenException( |
||
658 | 'not allowed to create new node here', |
||
659 | ForbiddenException::NOT_ALLOWED_TO_CREATE |
||
660 | ); |
||
661 | } |
||
662 | |||
663 | $this->_hook->run('preCreateFile', [$this, &$name, &$attributes, &$clone]); |
||
664 | |||
665 | if ($this->readonly) { |
||
666 | throw new Exception\Conflict( |
||
667 | 'node is set as readonly, it is not possible to add new sub nodes', |
||
668 | Exception\Conflict::READONLY |
||
669 | ); |
||
670 | } |
||
671 | |||
672 | $name = $this->checkName($name); |
||
673 | |||
674 | if ($this->childExists($name)) { |
||
675 | if (NodeInterface::CONFLICT_NOACTION === $conflict) { |
||
676 | throw new Exception\Conflict( |
||
677 | 'a node called '.$name.' does already exists in this collection', |
||
678 | Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS |
||
679 | ); |
||
680 | } |
||
681 | if (NodeInterface::CONFLICT_RENAME === $conflict) { |
||
682 | $name = $this->getDuplicateName($name, File::class); |
||
683 | } |
||
684 | } |
||
685 | |||
686 | if ($this->isDeleted()) { |
||
687 | throw new Exception\Conflict( |
||
688 | 'could not add node '.$name.' into a deleted parent collection', |
||
689 | Exception\Conflict::DELETED_PARENT |
||
690 | ); |
||
691 | } |
||
692 | |||
693 | $id = new ObjectId(); |
||
694 | |||
695 | try { |
||
696 | $meta = [ |
||
697 | '_id' => $id, |
||
698 | 'pointer' => $id, |
||
699 | 'name' => $name, |
||
700 | 'deleted' => false, |
||
701 | 'parent' => $this->getRealId(), |
||
702 | 'directory' => false, |
||
703 | 'hash' => null, |
||
704 | 'mime' => MimeType::getType($name), |
||
705 | 'created' => new UTCDateTime(), |
||
706 | 'changed' => new UTCDateTime(), |
||
707 | 'version' => 0, |
||
708 | 'shared' => (true === $this->shared ? $this->getRealId() : $this->shared), |
||
709 | 'storage_reference' => $this->getMount(), |
||
710 | ]; |
||
711 | |||
712 | if (null !== $this->_user) { |
||
713 | $meta['owner'] = $this->_user->getId(); |
||
714 | } |
||
715 | |||
716 | $save = array_merge($meta, $attributes); |
||
717 | |||
718 | if (isset($save['acl'])) { |
||
719 | $this->validateAcl($save['acl']); |
||
720 | } |
||
721 | |||
722 | $result = $this->_db->storage->insertOne($save); |
||
723 | |||
724 | $this->_logger->info('added new file ['.$save['_id'].'] under parent ['.$this->_id.']', [ |
||
725 | 'category' => get_class($this), |
||
726 | ]); |
||
727 | |||
728 | $this->changed = $save['changed']; |
||
729 | $this->save('changed'); |
||
730 | |||
731 | $file = $this->_fs->initNode($save); |
||
732 | |||
733 | if ($session !== null) { |
||
734 | $file->setContent($session, $attributes); |
||
735 | } |
||
736 | |||
737 | $this->_hook->run('postCreateFile', [$this, $file, $clone]); |
||
738 | |||
739 | return $file; |
||
740 | } catch (\Exception $e) { |
||
741 | $this->_logger->error('failed add new file under parent ['.$this->_id.']', [ |
||
742 | 'category' => get_class($this), |
||
743 | 'exception' => $e, |
||
744 | ]); |
||
745 | |||
746 | throw $e; |
||
747 | } |
||
748 | } |
||
749 | |||
750 | /** |
||
751 | * Create new file wrapper |
||
752 | * (Sabe\DAV compatible method, elsewhere use addFile(). |
||
753 | * |
||
754 | * Sabre\DAV requires that createFile() returns the ETag instead the newly created file instance |
||
755 | * |
||
756 | * @param string $name |
||
757 | * @param string $data |
||
758 | */ |
||
759 | public function createFile($name, $data = null): string |
||
760 | { |
||
761 | $session = $this->_storage->storeTemporaryFile($data, $this->_user); |
||
762 | |||
763 | if ($this->childExists($name, NodeInterface::DELETED_INCLUDE, ['directory' => false])) { |
||
764 | $file = $this->getChild($name, NodeInterface::DELETED_INCLUDE, ['directory' => false]); |
||
765 | $file->setContent($session); |
||
766 | } else { |
||
767 | $file = $this->addFile($name, $session); |
||
768 | } |
||
769 | |||
770 | return $file->getETag(); |
||
771 | } |
||
772 | |||
773 | /** |
||
774 | * Create new directory wrapper |
||
775 | * (Sabe\DAV compatible method, elsewhere use addDirectory(). |
||
776 | * |
||
777 | * Sabre\DAV requires that createDirectory() returns void |
||
778 | * |
||
779 | * @param string $name |
||
780 | */ |
||
781 | public function createDirectory($name): void |
||
782 | { |
||
783 | $this->addDirectory($name); |
||
784 | } |
||
785 | |||
786 | /** |
||
787 | * Do recursive Action. |
||
788 | */ |
||
789 | public function doRecursiveAction(callable $callable, int $deleted = NodeInterface::DELETED_EXCLUDE): bool |
||
790 | { |
||
791 | $children = $this->getChildNodes($deleted, []); |
||
792 | |||
793 | foreach ($children as $child) { |
||
794 | $callable($child); |
||
795 | } |
||
796 | |||
797 | return true; |
||
798 | } |
||
799 | |||
800 | /** |
||
801 | * Validate acl. |
||
802 | */ |
||
803 | protected function validateAcl(array $acl): bool |
||
804 | { |
||
805 | if (!$this->_acl->isAllowed($this, 'm')) { |
||
806 | throw new ForbiddenException( |
||
807 | 'not allowed to set acl', |
||
808 | ForbiddenException::NOT_ALLOWED_TO_MANAGE |
||
809 | ); |
||
810 | } |
||
811 | |||
812 | if (!$this->isSpecial()) { |
||
813 | throw new Exception\Conflict('node acl may only be set on share member nodes', Exception\Conflict::NOT_SHARED); |
||
814 | } |
||
815 | |||
816 | $this->_acl->validateAcl($this->_server, $acl); |
||
817 | |||
818 | return true; |
||
819 | } |
||
820 | |||
821 | /** |
||
822 | * Get children query filter. |
||
823 | * |
||
824 | * Deleted: |
||
825 | * 0 - Exclude deleted |
||
826 | * 1 - Only deleted |
||
827 | * 2 - Include deleted |
||
828 | */ |
||
829 | protected function getChildrenFilter(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): array |
||
830 | { |
||
831 | $search = [ |
||
832 | 'parent' => $this->getRealId(), |
||
833 | ]; |
||
834 | |||
835 | if (NodeInterface::DELETED_EXCLUDE === $deleted) { |
||
836 | $search['deleted'] = false; |
||
837 | } elseif (NodeInterface::DELETED_ONLY === $deleted) { |
||
838 | $search['deleted'] = ['$type' => 9]; |
||
839 | } |
||
840 | |||
841 | $search = array_merge($filter, $search); |
||
842 | |||
843 | if ($this->shared) { |
||
844 | $search = [ |
||
845 | '$and' => [ |
||
846 | $search, |
||
847 | [ |
||
848 | '$or' => [ |
||
849 | ['shared' => $this->reference], |
||
850 | ['shared' => $this->shared], |
||
851 | ['shared' => $this->_id], |
||
852 | ], |
||
853 | ], |
||
854 | ], |
||
855 | ]; |
||
856 | } elseif (null !== $this->_user) { |
||
857 | $search['owner'] = $this->_user->getId(); |
||
858 | } |
||
859 | |||
860 | if ($this->filter !== null && $this->_user !== null) { |
||
861 | $include = isset($search['deleted']) ? ['deleted' => $search['deleted']] : []; |
||
862 | $stored_filter = ['$and' => [ |
||
863 | array_merge( |
||
864 | $include, |
||
865 | json_decode($this->filter, true), |
||
866 | $filter |
||
867 | ), |
||
868 | ['$or' => [ |
||
869 | ['owner' => $this->_user->getId()], |
||
870 | ['shared' => ['$in' => $this->_user->getShares()]], |
||
871 | ]], |
||
872 | ]]; |
||
873 | |||
874 | $search = ['$or' => [ |
||
875 | $search, |
||
876 | $stored_filter, |
||
877 | ]]; |
||
878 | } |
||
879 | |||
880 | return $search; |
||
881 | } |
||
882 | |||
883 | /** |
||
884 | * Completely remove node. |
||
885 | */ |
||
886 | protected function _forceDelete(?string $recursion = null, bool $recursion_first = true): bool |
||
887 | { |
||
888 | if (!$this->isReference() && !$this->isMounted()) { |
||
889 | $this->doRecursiveAction(function ($node) use ($recursion) { |
||
890 | $node->delete(true, $recursion, false); |
||
891 | }, NodeInterface::DELETED_INCLUDE); |
||
892 | } |
||
893 | |||
894 | try { |
||
895 | $this->_parent->getStorage()->forceDeleteCollection($this); |
||
896 | $result = $this->_db->storage->deleteOne(['_id' => $this->_id]); |
||
897 | |||
898 | if ($this->isShared()) { |
||
899 | $result = $this->_db->storage->deleteMany(['reference' => $this->_id]); |
||
900 | } |
||
901 | |||
902 | $this->_logger->info('force removed collection ['.$this->_id.']', [ |
||
903 | 'category' => get_class($this), |
||
904 | ]); |
||
905 | |||
906 | $this->_hook->run( |
||
907 | 'postDeleteCollection', |
||
908 | [$this, true, $recursion, $recursion_first] |
||
909 | ); |
||
910 | } catch (\Exception $e) { |
||
911 | $this->_logger->error('failed force remove collection ['.$this->_id.']', [ |
||
912 | 'category' => get_class($this), |
||
913 | 'exception' => $e, |
||
914 | ]); |
||
915 | |||
916 | throw $e; |
||
917 | } |
||
918 | |||
919 | return true; |
||
920 | } |
||
921 | } |
||
922 |
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: