Test Failed
Push — master ( 8c814b...380005 )
by Raffael
08:49
created

Collection::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 0
cts 15
cp 0
rs 9.6333
c 0
b 0
f 0
cc 2
nc 2
nop 7
crap 6
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 function MongoDB\BSON\fromJSON;
24
use MongoDB\BSON\ObjectId;
25
use MongoDB\BSON\Regex;
26
use function MongoDB\BSON\toPHP;
27
use MongoDB\BSON\UTCDateTime;
28
use Psr\Log\LoggerInterface;
29
use Sabre\DAV\IQuota;
30
31
class Collection extends AbstractNode implements IQuota
32
{
33
    /**
34
     * Root folder.
35
     */
36
    const ROOT_FOLDER = '/';
37
38
    /**
39
     * Share acl.
40
     *
41
     * @var array
42
     */
43
    protected $acl = [];
44
45
    /**
46
     * Share name.
47
     *
48
     * @var string
49
     */
50
    protected $share_name;
51
52
    /**
53
     * filter.
54
     *
55
     * @var string
56
     */
57
    protected $filter;
58
59
    /**
60
     * Storage.
61
     *
62
     * @var StorageAdapterInterface
63
     */
64
    protected $_storage;
65
66
    /**
67
     * Initialize.
68
     */
69
    public function __construct(array $attributes, Filesystem $fs, LoggerInterface $logger, Hook $hook, Acl $acl, ?Collection $parent, StorageAdapterInterface $storage)
70
    {
71
        $this->_fs = $fs;
72
        $this->_server = $fs->getServer();
73
        $this->_db = $fs->getDatabase();
74
        $this->_user = $fs->getUser();
75
        $this->_logger = $logger;
76
        $this->_hook = $hook;
77
        $this->_acl = $acl;
78
        $this->_storage = $storage;
79
        $this->_parent = $parent;
80
81
        foreach ($attributes as $attr => $value) {
82
            $this->{$attr} = $value;
83
        }
84
85
        $this->mime = 'inode/directory';
86
        $this->raw_attributes = $attributes;
87
    }
88
89
    /**
90
     * Set storage adapter.
91
     */
92
    public function setStorage(StorageAdapterInterface $adapter): self
93
    {
94
        $this->_storage = $adapter;
95
96
        return $this;
97
    }
98
99
    /**
100
     * Get storage adapter.
101
     */
102
    public function getStorage(): StorageAdapterInterface
103
    {
104
        return $this->_storage;
105
    }
106
107
    /**
108
     * Copy node with children.
109
     */
110
    public function copyTo(self $parent, int $conflict = NodeInterface::CONFLICT_NOACTION, ?string $recursion = null, bool $recursion_first = true, int $deleted = NodeInterface::DELETED_EXCLUDE): NodeInterface
111
    {
112
        if (null === $recursion) {
113
            $recursion_first = true;
114
            $recursion = uniqid();
115
        } else {
116
            $recursion_first = false;
117
        }
118
119
        $this->_hook->run(
120
            'preCopyCollection',
121
            [$this, $parent, &$conflict, &$recursion, &$recursion_first]
122
        );
123
124
        if (NodeInterface::CONFLICT_RENAME === $conflict && $parent->childExists($this->name)) {
125
            $name = $this->getDuplicateName();
126
        } else {
127
            $name = $this->name;
128
        }
129
130
        if ($this->_id === $parent->getId()) {
131
            throw new Exception\Conflict(
132
                'can not copy node into itself',
133
                Exception\Conflict::CANT_COPY_INTO_ITSELF
134
            );
135
        }
136
137
        if (NodeInterface::CONFLICT_MERGE === $conflict && $parent->childExists($this->name)) {
138
            $new_parent = $parent->getChild($this->name);
139
140
            if ($new_parent instanceof File) {
141
                $new_parent = $this;
142
            }
143
        } else {
144
            $new_parent = $parent->addDirectory($name, [
145
                'created' => $this->created,
146
                'changed' => $this->changed,
147
                'filter' => $this->filter,
148
                'meta' => $this->meta,
149
            ], NodeInterface::CONFLICT_NOACTION, true);
150
        }
151
152
        foreach ($this->getChildNodes($deleted) as $child) {
153
            $child->copyTo($new_parent, $conflict, $recursion, false, $deleted);
154
        }
155
156
        $this->_hook->run(
157
            'postCopyCollection',
158
            [$this, $parent, $new_parent, $conflict, $recursion, $recursion_first]
159
        );
160
161
        return $new_parent;
162
    }
163
164
    /**
165
     * Is mount.
166
     */
167
    public function isMounted(): bool
168
    {
169
        return count($this->mount) > 0;
170
    }
171
172
    /**
173
     * Get Share name.
174
     */
175
    public function getShareName(): string
176
    {
177
        if ($this->isShare()) {
178
            return $this->share_name;
179
        }
180
181
        return $this->_fs->findRawNode($this->getShareId())['share_name'];
0 ignored issues
show
Bug introduced by
It seems like $this->getShareId() targeting Balloon\Filesystem\Node\AbstractNode::getShareId() can also be of type boolean or null; however, Balloon\Filesystem::findRawNode() does only seem to accept object<MongoDB\BSON\ObjectId>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

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

An additional type check may prevent trouble.

Loading history...
182
    }
183
184
    /**
185
     * Get Attributes.
186
     */
187
    public function getAttributes(): array
188
    {
189
        return [
190
            '_id' => $this->_id,
191
            'name' => $this->name,
192
            'shared' => $this->shared,
193
            'share_name' => $this->share_name,
194
            'acl' => $this->acl,
195
            'lock' => $this->lock,
196
            'directory' => true,
197
            'reference' => $this->reference,
198
            'parent' => $this->parent,
199
            'app' => $this->app,
200
            'owner' => $this->owner,
201
            'meta' => $this->meta,
202
            'mime' => $this->mime,
203
            'filter' => $this->filter,
204
            'deleted' => $this->deleted,
205
            'changed' => $this->changed,
206
            'created' => $this->created,
207
            'destroy' => $this->destroy,
208
            'readonly' => $this->readonly,
209
            'mount' => $this->mount,
210
            'storage_reference' => $this->storage_reference,
211
            'storage' => $this->storage,
212
        ];
213
    }
214
215
    /**
216
     * Set collection filter.
217
     */
218
    public function setFilter(?array $filter = null): bool
219
    {
220
        $this->filter = json_encode($filter, JSON_THROW_ON_ERROR);
221
222
        return $this->save('filter');
223
    }
224
225
    /**
226
     * Get collection.
227
     */
228
    public function get(): void
229
    {
230
        $this->getZip();
231
    }
232
233
    /**
234
     * Fetch children items of this collection.
235
     *
236
     * Deleted:
237
     *  0 - Exclude deleted
238
     *  1 - Only deleted
239
     *  2 - Include deleted
240
     */
241
    public function getChildNodes(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = [], ?int $offset = null, ?int $limit = null, bool $recursive = false): Generator
242
    {
243
        $filter = $this->getChildrenFilter($deleted, $filter);
244
245
        if ($recursive === false) {
246
            return $this->_fs->findNodesByFilterRecursiveChildren($filter, $deleted, $offset, $limit, $this);
247
        }
248
249
        unset($filter['parent']);
250
251
        return $this->_fs->findNodesByFilterRecursive($this, $filter, $offset, $limit);
252
    }
253
254
    /**
255
     * Fetch children items of this collection (as array).
256
     *
257
     * Deleted:
258
     *  0 - Exclude deleted
259
     *  1 - Only deleted
260
     *  2 - Include deleted
261
     */
262
    public function getChildren(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): array
263
    {
264
        return iterator_to_array($this->getChildNodes($deleted, $filter));
265
    }
266
267
    /**
268
     * Is custom filter node.
269
     */
270
    public function isFiltered(): bool
271
    {
272
        return !empty($this->filter);
273
    }
274
275
    /**
276
     * Get number of children.
277
     */
278
    public function getSize(): int
279
    {
280
        if ($this->isFiltered() || $this->_acl->getAclPrivilege($this) === Acl::PRIVILEGE_WRITEPLUS) {
281
            return count($this->getChildren());
282
        }
283
284
        return $this->size;
285
    }
286
287
    /**
288
     * Get real id (reference).
289
     *
290
     * @return ObjectId
291
     */
292
    public function getRealId(): ?ObjectId
293
    {
294
        if (true === $this->shared && $this->isReference()) {
295
            return $this->reference;
296
        }
297
298
        return $this->_id;
299
    }
300
301
    /**
302
     * Get user quota information.
303
     */
304
    public function getQuotaInfo(): array
305
    {
306
        $quota = $this->_user->getQuotaUsage();
307
308
        return [
309
            $quota['used'],
310
            $quota['available'],
311
        ];
312
    }
313
314
    /**
315
     * Fetch children items of this collection.
316
     */
317
    public function getChild($name, int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): NodeInterface
318
    {
319
        $name = $this->checkName($name);
320
        $filter = $this->getChildrenFilter($deleted, $filter);
321
        $filter['name'] = new Regex('^'.preg_quote($name).'$', 'i');
322
323
        return $this->_fs->findOne($filter, $deleted, $this);
324
    }
325
326
    /**
327
     * Delete node.
328
     *
329
     * Actually the node will not be deleted (Just set a delete flag), set $force=true to
330
     * delete finally
331
     */
332
    public function delete(bool $force = false, ?string $recursion = null, bool $recursion_first = true): bool
333
    {
334
        if (!$this->isReference() && !$this->_acl->isAllowed($this, 'w')) {
335
            throw new ForbiddenException(
336
                'not allowed to delete node '.$this->name,
337
                ForbiddenException::NOT_ALLOWED_TO_DELETE
338
            );
339
        }
340
341
        if (null === $recursion) {
342
            $recursion_first = true;
343
            $recursion = uniqid();
344
        } else {
345
            $recursion_first = false;
346
        }
347
348
        $this->_hook->run(
349
            'preDeleteCollection',
350
            [$this, &$force, &$recursion, &$recursion_first]
351
        );
352
353
        if (true === $force) {
354
            return $this->_forceDelete($recursion, $recursion_first);
355
        }
356
357
        $this->deleted = new UTCDateTime();
358
        $this->storage = $this->_parent->getStorage()->deleteCollection($this);
359
360
        if (!$this->isReference() && !$this->isMounted() && !$this->isFiltered()) {
361
            $this->doRecursiveAction(function ($node) use ($recursion) {
362
                $node->delete(false, $recursion, false);
363
            }, NodeInterface::DELETED_EXCLUDE);
364
        }
365
366
        if (null !== $this->_id) {
367
            $result = $this->save([
368
                'deleted', 'storage',
369
            ], [], $recursion, false);
370
        } else {
371
            $result = true;
372
        }
373
374
        $this->_hook->run(
375
            'postDeleteCollection',
376
            [$this, $force, $recursion, $recursion_first]
377
        );
378
379
        return $result;
380
    }
381
382
    /**
383
     * Check if this collection has child named $name.
384
     *
385
     * deleted:
386
     *
387
     *  0 - Exclude deleted
388
     *  1 - Only deleted
389
     *  2 - Include deleted
390
     *
391
     * @param string $name
392
     * @param int    $deleted
393
     */
394
    public function childExists($name, $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): bool
395
    {
396
        $name = $this->checkName($name);
397
398
        $find = [
399
            'parent' => $this->getRealId(),
400
            'name' => new Regex('^'.preg_quote($name).'$', 'i'),
401
        ];
402
403
        if (null !== $this->_user) {
404
            $find['owner'] = $this->_user->getId();
405
        }
406
407
        switch ($deleted) {
408
            case NodeInterface::DELETED_EXCLUDE:
409
                $find['deleted'] = false;
410
411
                break;
412
            case NodeInterface::DELETED_ONLY:
413
                $find['deleted'] = ['$type' => 9];
414
415
                break;
416
        }
417
418
        $find = array_merge($filter, $find);
419
420
        if ($this->isSpecial()) {
421
            unset($find['owner']);
422
        }
423
424
        $node = $this->_db->storage->findOne($find);
425
426
        return (bool) $node;
427
    }
428
429
    /**
430
     * Share collection.
431
     */
432
    public function share(array $acl, string $name): bool
433
    {
434
        if ($this->isShareMember()) {
435
            throw new Exception('a sub node of a share can not be shared');
436
        }
437
438
        $this->checkName($name);
439
        $this->_acl->validateAcl($this->_server, $acl);
440
441
        $action = [
442
            '$set' => [
443
                'shared' => $this->getRealId(),
444
            ],
445
        ];
446
447
        $query = [
448
            '$or' => [
449
                ['reference' => ['exists' => true]],
450
                ['shared' => true],
451
            ],
452
        ];
453
454
        if (iterator_count($this->_fs->findNodesByFilterRecursive($this, $query, 0, 1)) !== 0) {
455
            throw new Exception\Conflict(
456
                'folder contains a shared folder',
457
                Exception\Conflict::NODE_CONTAINS_SHARED_NODE
458
            );
459
        }
460
461
        $toset = $this->_fs->findNodesByFilterRecursiveToArray($this);
462
        $this->_db->storage->updateMany([
463
            '_id' => [
464
                '$in' => $toset,
465
            ],
466
        ], $action);
467
468
        $this->_db->delta->updateMany([
469
            '_id' => [
470
                '$in' => $toset,
471
            ],
472
        ], $action);
473
474
        if ($this->getRealId() === $this->_id) {
475
            $this->acl = $acl;
476
            $this->shared = true;
477
            $this->share_name = $name;
478
            $this->save(['acl', 'shared', 'share_name']);
479
        } else {
480
            $this->_db->storage->updateOne([
481
                '_id' => $this->getRealId(),
482
            ], [
483
                '$set' => [
484
                    'share_name' => $name,
485
                    'acl' => $acl,
486
                ],
487
            ]);
488
        }
489
490
        return true;
491
    }
492
493
    /**
494
     * Unshare collection.
495
     */
496
    public function unshare(): Collection
497
    {
498
        if (!$this->_acl->isAllowed($this, 'm')) {
499
            throw new ForbiddenException(
500
                'not allowed to share node',
501
                ForbiddenException::NOT_ALLOWED_TO_MANAGE
502
            );
503
        }
504
505
        if (true !== $this->shared) {
506
            throw new Exception\Conflict(
507
                'Can not unshare a none shared collection',
508
                Exception\Conflict::NOT_SHARED
509
            );
510
        }
511
512
        $real = $this->getRealId();
513
514
        $action = [
515
            '$set' => [
516
                'owner' => $this->_fs->findRawNode($real)['owner'],
517
                'shared' => false,
518
            ],
519
        ];
520
521
        $this->shared = false;
522
        $this->share_name = null;
523
        $this->acl = [];
524
525
        $toset = $this->_fs->findNodesByFilterRecursiveToArray($this);
526
        $this->_db->storage->updateMany([
527
            '_id' => [
528
                '$in' => $toset,
529
            ],
530
        ], $action);
531
532
        if ($real === $this->_id) {
533
            $result = $this->save(['shared'], ['acl', 'share_name']);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

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

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

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

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

Loading history...
534
        } else {
535
            $this->_db->storage->updateOne([
536
                '_id' => $real,
537
            ], [
538
                '$set' => [
539
                    'share_name' => null,
540
                    'acl' => [],
541
                    'shared' => false,
542
                ],
543
            ]);
544
545
            $this->delete();
546
        }
547
548
        return $this;
549
    }
550
551
    /**
552
     * Create new directory.
553
     */
554
    public function addDirectory($name, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): self
555
    {
556
        if (!$this->_acl->isAllowed($this, 'w')) {
557
            throw new ForbiddenException(
558
                'not allowed to create new node here',
559
                ForbiddenException::NOT_ALLOWED_TO_CREATE
560
            );
561
        }
562
563
        $this->_hook->run('preCreateCollection', [$this, &$name, &$attributes, &$clone]);
564
        $name = $this->validateInsert($name, $conflict, Collection::class);
565
        $id = new ObjectId();
566
567
        if (isset($attributes['lock'])) {
568
            $attributes['lock'] = $this->prepareLock($attributes['lock']);
569
        }
570
571
        try {
572
            $meta = [
573
                '_id' => $id,
574
                'pointer' => $id,
575
                'name' => $name,
576
                'deleted' => false,
577
                'parent' => $this->getRealId(),
578
                'directory' => true,
579
                'created' => new UTCDateTime(),
580
                'changed' => new UTCDateTime(),
581
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
582
                'storage' => $this->_storage->createCollection($this, $name),
583
                'storage_reference' => $this->getMount(),
584
            ];
585
586
            if (null !== $this->_user) {
587
                $meta['owner'] = $this->_user->getId();
588
            }
589
590
            $save = array_merge($meta, $attributes);
591
592
            if (isset($save['filter'])) {
593
                $this->validateFilter($save['filter']);
594
            }
595
596
            if (isset($save['acl'])) {
597
                $this->validateAcl($save['acl']);
598
            }
599
600
            $result = $this->_db->storage->insertOne($save);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

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

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

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

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

Loading history...
601
602
            $this->_logger->info('added new collection ['.$save['_id'].'] under parent ['.$this->_id.']', [
603
                'category' => get_class($this),
604
            ]);
605
606
            $this->changed = $save['changed'];
607
            $this->save('changed');
608
609
            $new = $this->_fs->initNode($save, $this);
610
            $this->_hook->run('postCreateCollection', [$this, $new, $clone]);
611
612
            return $new;
613
        } catch (\Exception $e) {
614
            $this->_logger->error('failed create new collection under parent ['.$this->_id.']', [
615
                'category' => get_class($this),
616
                'exception' => $e,
617
            ]);
618
619
            throw $e;
620
        }
621
    }
622
623
    /**
624
     * Create new file as a child from this collection.
625
     */
626
    public function addFile($name, ?ObjectId $session = null, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): File
627
    {
628
        if (!$this->_acl->isAllowed($this, 'w')) {
629
            throw new ForbiddenException(
630
                'not allowed to create new node here',
631
                ForbiddenException::NOT_ALLOWED_TO_CREATE
632
            );
633
        }
634
635
        $this->_hook->run('preCreateFile', [$this, &$name, &$attributes, &$clone]);
636
        $name = $this->validateInsert($name, $conflict, File::class);
637
        $id = new ObjectId();
638
639
        if (isset($attributes['lock'])) {
640
            $attributes['lock'] = $this->prepareLock($attributes['lock']);
641
        }
642
643
        try {
644
            $meta = [
645
                '_id' => $id,
646
                'pointer' => $id,
647
                'name' => $name,
648
                'deleted' => false,
649
                'parent' => $this->getRealId(),
650
                'directory' => false,
651
                'hash' => null,
652
                'mime' => MimeType::getType($name),
653
                'created' => new UTCDateTime(),
654
                'changed' => new UTCDateTime(),
655
                'version' => 0,
656
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
657
                'storage_reference' => $this->getMount(),
658
            ];
659
660
            if (null !== $this->_user) {
661
                $meta['owner'] = $this->_user->getId();
662
            }
663
664
            $save = array_merge($meta, $attributes);
665
666
            if (isset($save['acl'])) {
667
                $this->validateAcl($save['acl']);
668
            }
669
670
            $result = $this->_db->storage->insertOne($save);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

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

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

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

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

Loading history...
671
672
            $this->_logger->info('added new file ['.$save['_id'].'] under parent ['.$this->_id.']', [
673
                'category' => get_class($this),
674
            ]);
675
676
            $this->changed = $save['changed'];
677
            $this->save('changed');
678
679
            $file = $this->_fs->initNode($save, $this);
680
681
            if ($session !== null) {
682
                $file->setContent($session, $attributes);
683
            }
684
685
            $this->_hook->run('postCreateFile', [$this, $file, $clone]);
686
687
            return $file;
688
        } catch (\Exception $e) {
689
            $this->_logger->error('failed add new file under parent ['.$this->_id.']', [
690
                'category' => get_class($this),
691
                'exception' => $e,
692
            ]);
693
694
            throw $e;
695
        }
696
    }
697
698
    /**
699
     * Create new file wrapper
700
     * (Sabe\DAV compatible method, elsewhere use addFile().
701
     *
702
     * Sabre\DAV requires that createFile() returns the ETag instead the newly created file instance
703
     *
704
     * @param string $name
705
     * @param string $data
706
     */
707
    public function createFile($name, $data = null): string
708
    {
709
        $session = $this->_storage->storeTemporaryFile($data, $this->_user);
710
711
        if ($this->childExists($name, NodeInterface::DELETED_INCLUDE, ['directory' => false])) {
712
            $file = $this->getChild($name, NodeInterface::DELETED_INCLUDE, ['directory' => false]);
713
            $file->setContent($session);
714
        } else {
715
            $file = $this->addFile($name, $session);
716
        }
717
718
        return $file->getETag();
719
    }
720
721
    /**
722
     * Create new directory wrapper
723
     * (Sabe\DAV compatible method, elsewhere use addDirectory().
724
     *
725
     * Sabre\DAV requires that createDirectory() returns void
726
     *
727
     * @param string $name
728
     */
729
    public function createDirectory($name): void
730
    {
731
        $this->addDirectory($name);
732
    }
733
734
    /**
735
     * Do recursive Action.
736
     */
737
    public function doRecursiveAction(callable $callable, int $deleted = NodeInterface::DELETED_EXCLUDE): bool
738
    {
739
        $children = $this->getChildNodes($deleted, []);
740
741
        foreach ($children as $child) {
742
            $callable($child);
743
        }
744
745
        return true;
746
    }
747
748
    /**
749
     * Validate insert.
750
     */
751
    public function validateInsert(string $name, int $conflict = NodeInterface::CONFLICT_NOACTION, string $type = Collection::class): string
752
    {
753
        if ($this->readonly) {
754
            throw new Exception\Conflict(
755
                'node is set as readonly, it is not possible to add new sub nodes',
756
                Exception\Conflict::READONLY
757
            );
758
        }
759
760
        if ($this->isFiltered()) {
761
            throw new Exception\Conflict(
762
                'could not add node '.$name.' into a filtered parent collection',
763
                Exception\Conflict::DYNAMIC_PARENT
764
            );
765
        }
766
767
        if ($this->isDeleted()) {
768
            throw new Exception\Conflict(
769
                'could not add node '.$name.' into a deleted parent collection',
770
                Exception\Conflict::DELETED_PARENT
771
            );
772
        }
773
774
        $name = $this->checkName($name);
775
776
        if ($this->childExists($name)) {
777
            if (NodeInterface::CONFLICT_NOACTION === $conflict) {
778
                throw new Exception\Conflict(
779
                    'a node called '.$name.' does already exists in this collection',
780
                    Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
781
                );
782
            }
783
            if (NodeInterface::CONFLICT_RENAME === $conflict) {
784
                $name = $this->getDuplicateName($name, $type);
785
            }
786
        }
787
788
        return $name;
789
    }
790
791
    /**
792
     * Validate filtered collection query.
793
     */
794
    protected function validateFilter(string $filter): bool
795
    {
796
        $filter = toPHP(fromJSON($filter), [
797
            'root' => 'array',
798
            'document' => 'array',
799
            'array' => 'array',
800
        ]);
801
802
        $this->_db->storage->findOne($filter);
803
804
        return true;
805
    }
806
807
    /**
808
     * Validate acl.
809
     */
810
    protected function validateAcl(array $acl): bool
811
    {
812
        if (!$this->_acl->isAllowed($this, 'm')) {
813
            throw new ForbiddenException(
814
                'not allowed to set acl',
815
                ForbiddenException::NOT_ALLOWED_TO_MANAGE
816
            );
817
        }
818
819
        if (!$this->isSpecial()) {
820
            throw new Exception\Conflict('node acl may only be set on share member nodes', Exception\Conflict::NOT_SHARED);
821
        }
822
823
        $this->_acl->validateAcl($this->_server, $acl);
824
825
        return true;
826
    }
827
828
    /**
829
     * Get children query filter.
830
     *
831
     * Deleted:
832
     *  0 - Exclude deleted
833
     *  1 - Only deleted
834
     *  2 - Include deleted
835
     */
836
    protected function getChildrenFilter(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): array
837
    {
838
        $search = $filter;
839
        $search['parent'] = $this->getRealId();
840
841
        if (NodeInterface::DELETED_EXCLUDE === $deleted) {
842
            $search['deleted'] = false;
843
        } elseif (NodeInterface::DELETED_ONLY === $deleted) {
844
            $search['deleted'] = ['$type' => 9];
845
        }
846
847
        if ($this->shared) {
848
            $search = [
849
                '$and' => [
850
                    $search,
851
                    [
852
                        '$or' => [
853
                            ['shared' => $this->reference],
854
                            ['shared' => $this->shared],
855
                            ['shared' => $this->_id],
856
                        ],
857
                    ],
858
                    [
859
                        '$or' => [
860
                            [
861
                                'acl' => ['$exists' => false],
862
                            ], [
863
                                'acl.id' => (string) $this->_user->getId(),
864
                                'acl.privilege' => ['$in' => ['m', 'rw', 'r', 'w', 'w+']],
865
                            ],
866
                        ],
867
                    ],
868
                ],
869
            ];
870
        } elseif (null !== $this->_user) {
871
            $search['owner'] = $this->_user->getId();
872
        }
873
874
        if ($this->filter !== null && $this->_user !== null) {
875
            $stored = toPHP(fromJSON($this->filter), [
876
                'root' => 'array',
877
                'document' => 'array',
878
                'array' => 'array',
879
            ]);
880
881
            $include = isset($search['deleted']) ? ['deleted' => $search['deleted']] : [];
882
            $stored_filter = ['$and' => [
883
                array_merge(
884
                    $include,
885
                    $stored,
886
                    $filter
887
                ),
888
                ['$or' => [
889
                    ['owner' => $this->_user->getId()],
890
                    ['shared' => ['$in' => $this->_user->getShares()]],
891
                ]],
892
                [
893
                    '_id' => ['$ne' => $this->_id],
894
                ],
895
            ]];
896
897
            $search = ['$or' => [
898
                $search,
899
                $stored_filter,
900
            ]];
901
        }
902
903
        return $search;
904
    }
905
906
    /**
907
     * Completely remove node.
908
     */
909
    protected function _forceDelete(?string $recursion = null, bool $recursion_first = true): bool
910
    {
911
        if (!$this->isReference() && !$this->isMounted() && !$this->isFiltered()) {
912
            $this->doRecursiveAction(function ($node) use ($recursion) {
913
                $node->delete(true, $recursion, false);
914
            }, NodeInterface::DELETED_INCLUDE);
915
        }
916
917
        try {
918
            $this->_parent->getStorage()->forceDeleteCollection($this);
919
            $result = $this->_db->storage->deleteOne(['_id' => $this->_id]);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

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

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

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

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

Loading history...
920
921
            if ($this->isShared()) {
922
                $result = $this->_db->storage->deleteMany(['reference' => $this->_id]);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

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

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

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

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

Loading history...
923
            }
924
925
            $this->_logger->info('force removed collection ['.$this->_id.']', [
926
                'category' => get_class($this),
927
            ]);
928
929
            $this->_hook->run(
930
                'postDeleteCollection',
931
                [$this, true, $recursion, $recursion_first]
932
            );
933
        } catch (\Exception $e) {
934
            $this->_logger->error('failed force remove collection ['.$this->_id.']', [
935
                'category' => get_class($this),
936
                'exception' => $e,
937
            ]);
938
939
            throw $e;
940
        }
941
942
        return true;
943
    }
944
}
945