Completed
Push — master ( 287393...7ff50d )
by Raffael
18:27 queued 14:12
created

src/lib/Filesystem/Node/Collection.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

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
It seems like $this->getShareId() can be null; however, findRawNode() does not accept null, maybe add an additional type check?

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:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
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