Completed
Push — master ( 37faaa...541bbf )
by Raffael
10:18 queued 06:30
created

Collection::addDirectory()   B

Complexity

Conditions 7
Paths 137

Size

Total Lines 61

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 61
ccs 0
cts 31
cp 0
rs 7.6709
c 0
b 0
f 0
cc 7
crap 56
nc 137
nop 4

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Balloon\Session\Factory as SessionFactory;
22
use Balloon\Session\SessionInterface;
23
use Generator;
24
use MimeType\MimeType;
25
use function MongoDB\BSON\fromJSON;
26
use MongoDB\BSON\ObjectId;
27
use MongoDB\BSON\Regex;
28
use function MongoDB\BSON\toPHP;
29
use MongoDB\BSON\UTCDateTime;
30
use Psr\Log\LoggerInterface;
31
use Sabre\DAV\IQuota;
32
33
class Collection extends AbstractNode implements IQuota
34
{
35
    /**
36
     * Root folder.
37
     */
38
    const ROOT_FOLDER = '/';
39
40
    /**
41
     * Share acl.
42
     *
43
     * @var array
44
     */
45
    protected $acl = [];
46
47
    /**
48
     * Share name.
49
     *
50
     * @var string
51
     */
52
    protected $share_name;
53
54
    /**
55
     * filter.
56
     *
57
     * @var string
58
     */
59
    protected $filter;
60
61
    /**
62
     * Storage.
63
     *
64
     * @var StorageAdapterInterface
65
     */
66
    protected $_storage;
67
68
    /**
69
     * Initialize.
70
     */
71
    public function __construct(array $attributes, Filesystem $fs, LoggerInterface $logger, Hook $hook, Acl $acl, ?Collection $parent, StorageAdapterInterface $storage, SessionFactory $session_factory)
72
    {
73
        $this->_fs = $fs;
74
        $this->_server = $fs->getServer();
75
        $this->_db = $fs->getDatabase();
76
        $this->_user = $fs->getUser();
77
        $this->_logger = $logger;
78
        $this->_hook = $hook;
79
        $this->_acl = $acl;
80
        $this->_storage = $storage;
81
        $this->_parent = $parent;
82
        $this->_session_factory = $session_factory;
0 ignored issues
show
Documentation Bug introduced by
It seems like $session_factory of type object<Balloon\Session\Factory> is incompatible with the declared type object<Balloon\Filesystem\Node\SessionFactory> of property $_session_factory.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
83
84
        foreach ($attributes as $attr => $value) {
85
            $this->{$attr} = $value;
86
        }
87
88
        $this->mime = 'inode/directory';
89
        $this->raw_attributes = $attributes;
90
    }
91
92
    /**
93
     * Set storage adapter.
94
     */
95
    public function setStorage(StorageAdapterInterface $adapter): self
96
    {
97
        $this->_storage = $adapter;
98
99
        return $this;
100
    }
101
102
    /**
103
     * Get storage adapter.
104
     */
105
    public function getStorage(): StorageAdapterInterface
106
    {
107
        return $this->_storage;
108
    }
109
110
    /**
111
     * Copy node with children.
112
     */
113
    public function copyTo(self $parent, int $conflict = NodeInterface::CONFLICT_NOACTION, ?string $recursion = null, bool $recursion_first = true, int $deleted = NodeInterface::DELETED_EXCLUDE): NodeInterface
114
    {
115
        if (null === $recursion) {
116
            $recursion_first = true;
117
            $recursion = uniqid();
118
        } else {
119
            $recursion_first = false;
120
        }
121
122
        $this->_hook->run(
123
            'preCopyCollection',
124
            [$this, $parent, &$conflict, &$recursion, &$recursion_first]
125
        );
126
127
        if (NodeInterface::CONFLICT_RENAME === $conflict && $parent->childExists($this->name)) {
128
            $name = $this->getDuplicateName();
129
        } else {
130
            $name = $this->name;
131
        }
132
133
        if ($this->_id === $parent->getId()) {
134
            throw new Exception\Conflict('can not copy node into itself', Exception\Conflict::CANT_COPY_INTO_ITSELF);
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->getChildren($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 getChildren(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
     * Is custom filter node.
256
     */
257
    public function isFiltered(): bool
258
    {
259
        return !empty($this->filter);
260
    }
261
262
    /**
263
     * Get number of children.
264
     */
265
    public function getSize(): int
266
    {
267
        if ($this->isFiltered() || $this->_acl->getAclPrivilege($this) === Acl::PRIVILEGE_WRITEPLUS) {
268
            return count(iterator_to_array($this->getChildren()));
269
        }
270
271
        return $this->size;
272
    }
273
274
    /**
275
     * Get real id (reference).
276
     *
277
     * @return ObjectId
278
     */
279
    public function getRealId(): ?ObjectId
280
    {
281
        if (true === $this->shared && $this->isReference()) {
282
            return $this->reference;
283
        }
284
285
        return $this->_id;
286
    }
287
288
    /**
289
     * Get user quota information.
290
     */
291
    public function getQuotaInfo(): array
292
    {
293
        $quota = $this->_user->getQuotaUsage();
294
295
        return [
296
            $quota['used'],
297
            $quota['available'],
298
        ];
299
    }
300
301
    /**
302
     * Fetch children items of this collection.
303
     */
304
    public function getChild($name, int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): NodeInterface
305
    {
306
        $name = $this->checkName($name);
307
        $filter = $this->getChildrenFilter($deleted, $filter);
308
        $filter['name'] = new Regex('^'.preg_quote($name).'$', 'i');
309
310
        return $this->_fs->findOne($filter, $deleted, $this);
311
    }
312
313
    /**
314
     * Delete node.
315
     *
316
     * Actually the node will not be deleted (Just set a delete flag), set $force=true to
317
     * delete finally
318
     */
319
    public function delete(bool $force = false, ?string $recursion = null, bool $recursion_first = true): bool
320
    {
321
        if (!$this->isReference() && !$this->_acl->isAllowed($this, 'w')) {
322
            throw new ForbiddenException('not allowed to delete node '.$this->name, ForbiddenException::NOT_ALLOWED_TO_DELETE);
323
        }
324
325
        if (null === $recursion) {
326
            $recursion_first = true;
327
            $recursion = uniqid();
328
        } else {
329
            $recursion_first = false;
330
        }
331
332
        $this->_hook->run(
333
            'preDeleteCollection',
334
            [$this, &$force, &$recursion, &$recursion_first]
335
        );
336
337
        if (true === $force) {
338
            return $this->_forceDelete($recursion, $recursion_first);
339
        }
340
341
        $this->deleted = new UTCDateTime();
342
        $this->storage = $this->_parent->getStorage()->deleteCollection($this);
343
344
        if (!$this->isReference() && !$this->isMounted() && !$this->isFiltered()) {
345
            $this->doRecursiveAction(function ($node) use ($recursion) {
346
                $node->delete(false, $recursion, false);
347
            }, NodeInterface::DELETED_EXCLUDE);
348
        }
349
350
        if (null !== $this->_id) {
351
            $result = $this->save([
352
                'deleted', 'storage',
353
            ], [], $recursion, false);
354
        } else {
355
            $result = true;
356
        }
357
358
        $this->_hook->run(
359
            'postDeleteCollection',
360
            [$this, $force, $recursion, $recursion_first]
361
        );
362
363
        return $result;
364
    }
365
366
    /**
367
     * Check if this collection has child named $name.
368
     *
369
     * deleted:
370
     *
371
     *  0 - Exclude deleted
372
     *  1 - Only deleted
373
     *  2 - Include deleted
374
     *
375
     * @param string $name
376
     * @param int    $deleted
377
     */
378
    public function childExists($name, $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): bool
379
    {
380
        $name = $this->checkName($name);
381
382
        $find = [
383
            'parent' => $this->getRealId(),
384
            'name' => new Regex('^'.preg_quote($name).'$', 'i'),
385
        ];
386
387
        if (null !== $this->_user) {
388
            $find['owner'] = $this->_user->getId();
389
        }
390
391
        switch ($deleted) {
392
            case NodeInterface::DELETED_EXCLUDE:
393
                $find['deleted'] = false;
394
395
                break;
396
            case NodeInterface::DELETED_ONLY:
397
                $find['deleted'] = ['$type' => 9];
398
399
                break;
400
        }
401
402
        $find = array_merge($filter, $find);
403
404
        if ($this->isSpecial()) {
405
            unset($find['owner']);
406
        }
407
408
        $node = $this->_db->storage->findOne($find);
409
410
        return (bool) $node;
411
    }
412
413
    /**
414
     * Share collection.
415
     */
416
    public function share(array $acl, string $name): bool
417
    {
418
        if ($this->isShareMember()) {
419
            throw new Exception('a sub node of a share can not be shared');
420
        }
421
422
        $this->checkName($name);
423
        $this->_acl->validateAcl($this->_server, $acl);
424
425
        $action = [
426
            '$set' => [
427
                'shared' => $this->getRealId(),
428
            ],
429
        ];
430
431
        $query = [
432
            '$or' => [
433
                ['reference' => ['exists' => true]],
434
                ['shared' => true],
435
            ],
436
        ];
437
438
        if (iterator_count($this->_fs->findNodesByFilterRecursive($this, $query, 0, 1)) !== 0) {
439
            throw new Exception\Conflict('folder contains a shared folder', Exception\Conflict::NODE_CONTAINS_SHARED_NODE);
440
        }
441
442
        $toset = $this->_fs->findNodesByFilterRecursiveToArray($this);
443
        $this->_db->storage->updateMany([
444
            '_id' => [
445
                '$in' => $toset,
446
            ],
447
        ], $action);
448
449
        $this->_db->delta->updateMany([
450
            '_id' => [
451
                '$in' => $toset,
452
            ],
453
        ], $action);
454
455
        if ($this->getRealId() === $this->_id) {
456
            $this->acl = $acl;
457
            $this->shared = true;
458
            $this->share_name = $name;
459
            $this->save(['acl', 'shared', 'share_name']);
460
        } else {
461
            $this->_db->storage->updateOne([
462
                '_id' => $this->getRealId(),
463
            ], [
464
                '$set' => [
465
                    'share_name' => $name,
466
                    'acl' => $acl,
467
                ],
468
            ]);
469
        }
470
471
        return true;
472
    }
473
474
    /**
475
     * Unshare collection.
476
     */
477
    public function unshare(): Collection
478
    {
479
        if (!$this->_acl->isAllowed($this, 'm')) {
480
            throw new ForbiddenException('not allowed to share node', ForbiddenException::NOT_ALLOWED_TO_MANAGE);
481
        }
482
483
        if (true !== $this->shared) {
484
            throw new Exception\Conflict('Can not unshare a none shared collection', Exception\Conflict::NOT_SHARED);
485
        }
486
487
        $real = $this->getRealId();
488
489
        $action = [
490
            '$set' => [
491
                'owner' => $this->_fs->findRawNode($real)['owner'],
492
                'shared' => false,
493
            ],
494
        ];
495
496
        $this->shared = false;
497
        $this->share_name = null;
498
        $this->acl = [];
499
500
        $toset = $this->_fs->findNodesByFilterRecursiveToArray($this);
501
        $this->_db->storage->updateMany([
502
            '_id' => [
503
                '$in' => $toset,
504
            ],
505
        ], $action);
506
507
        if ($real === $this->_id) {
508
            $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...
509
        } else {
510
            $this->_db->storage->updateOne([
511
                '_id' => $real,
512
            ], [
513
                '$set' => [
514
                    'share_name' => null,
515
                    'acl' => [],
516
                    'shared' => false,
517
                ],
518
            ]);
519
520
            $this->delete();
521
        }
522
523
        return $this;
524
    }
525
526
    /**
527
     * Create new directory.
528
     */
529
    public function addDirectory($name, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): self
530
    {
531
        if (!$this->_acl->isAllowed($this, 'w')) {
532
            throw new ForbiddenException('not allowed to create new node here', ForbiddenException::NOT_ALLOWED_TO_CREATE);
533
        }
534
535
        $this->_hook->run('preCreateCollection', [$this, &$name, &$attributes, &$clone]);
536
        $name = $this->validateInsert($name, $conflict, Collection::class);
537
        $id = new ObjectId();
538
539
        if (isset($attributes['lock'])) {
540
            $attributes['lock'] = $this->prepareLock($attributes['lock']);
541
        }
542
543
        try {
544
            $meta = [
545
                '_id' => $id,
546
                'pointer' => $id,
547
                'name' => $name,
548
                'deleted' => false,
549
                'parent' => $this->getRealId(),
550
                'directory' => true,
551
                'created' => new UTCDateTime(),
552
                'changed' => new UTCDateTime(),
553
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
554
                'storage' => $this->_storage->createCollection($this, $name),
555
                'storage_reference' => $this->getMount(),
556
            ];
557
558
            if (null !== $this->_user) {
559
                $meta['owner'] = $this->_user->getId();
560
            }
561
562
            $save = array_merge($meta, $attributes);
563
564
            if (isset($save['filter'])) {
565
                $this->validateFilter($save['filter']);
566
            }
567
568
            $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...
569
570
            $this->_logger->info('added new collection ['.$save['_id'].'] under parent ['.$this->_id.']', [
571
                'category' => get_class($this),
572
            ]);
573
574
            $this->changed = $save['changed'];
575
            $this->save('changed');
576
577
            $new = $this->_fs->initNode($save, $this);
578
            $this->_hook->run('postCreateCollection', [$this, $new, $clone]);
579
580
            return $new;
581
        } catch (\Exception $e) {
582
            $this->_logger->error('failed create new collection under parent ['.$this->_id.']', [
583
                'category' => get_class($this),
584
                'exception' => $e,
585
            ]);
586
587
            throw $e;
588
        }
589
    }
590
591
    /**
592
     * Create new file as a child from this collection.
593
     */
594
    public function addFile($name, ?SessionInterface $session = null, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): File
595
    {
596
        if (!$this->_acl->isAllowed($this, 'w')) {
597
            throw new ForbiddenException('not allowed to create new node here', ForbiddenException::NOT_ALLOWED_TO_CREATE);
598
        }
599
600
        $this->_hook->run('preCreateFile', [$this, &$name, &$attributes, &$clone]);
601
        $name = $this->validateInsert($name, $conflict, File::class);
602
        $id = new ObjectId();
603
604
        if (isset($attributes['lock'])) {
605
            $attributes['lock'] = $this->prepareLock($attributes['lock']);
606
        }
607
608
        try {
609
            $meta = [
610
                '_id' => $id,
611
                'pointer' => $id,
612
                'name' => $name,
613
                'deleted' => false,
614
                'parent' => $this->getRealId(),
615
                'directory' => false,
616
                'hash' => null,
617
                'mime' => MimeType::getType($name),
618
                'created' => new UTCDateTime(),
619
                'changed' => new UTCDateTime(),
620
                'version' => 0,
621
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
622
                'storage_reference' => $this->getMount(),
623
            ];
624
625
            if (null !== $this->_user) {
626
                $meta['owner'] = $this->_user->getId();
627
            }
628
629
            $save = array_merge($meta, $attributes);
630
            $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...
631
632
            $this->_logger->info('added new file ['.$save['_id'].'] under parent ['.$this->_id.']', [
633
                'category' => get_class($this),
634
            ]);
635
636
            $this->changed = $save['changed'];
637
            $this->save('changed');
638
639
            $file = $this->_fs->initNode($save, $this);
640
641
            if ($session !== null) {
642
                $file->setContent($session, $attributes);
643
            }
644
645
            $this->_hook->run('postCreateFile', [$this, $file, $clone]);
646
647
            return $file;
648
        } catch (\Exception $e) {
649
            $this->_logger->error('failed add new file under parent ['.$this->_id.']', [
650
                'category' => get_class($this),
651
                'exception' => $e,
652
            ]);
653
654
            throw $e;
655
        }
656
    }
657
658
    /**
659
     * Create new file wrapper
660
     * (Sabe\DAV compatible method, elsewhere use addFile().
661
     *
662
     * Sabre\DAV requires that createFile() returns the ETag instead the newly created file instance
663
     *
664
     * @param string $name
665
     * @param string $data
666
     */
667
    public function createFile($name, $data = null): string
668
    {
669
        $session = $this->_session_factory->add($this->_user, $this->getParent(), $data);
670
671
        if ($this->childExists($name, NodeInterface::DELETED_INCLUDE, ['directory' => false])) {
672
            $file = $this->getChild($name, NodeInterface::DELETED_INCLUDE, ['directory' => false]);
673
            $file->setContent($session);
674
        } else {
675
            $file = $this->addFile($name, $session);
676
        }
677
678
        return $file->getETag();
679
    }
680
681
    /**
682
     * Create new directory wrapper
683
     * (Sabe\DAV compatible method, elsewhere use addDirectory().
684
     *
685
     * Sabre\DAV requires that createDirectory() returns void
686
     *
687
     * @param string $name
688
     */
689
    public function createDirectory($name): void
690
    {
691
        $this->addDirectory($name);
692
    }
693
694
    /**
695
     * Do recursive Action.
696
     */
697
    public function doRecursiveAction(callable $callable, int $deleted = NodeInterface::DELETED_EXCLUDE): bool
698
    {
699
        $children = $this->getChildren($deleted, []);
700
701
        foreach ($children as $child) {
702
            $callable($child);
703
        }
704
705
        return true;
706
    }
707
708
    /**
709
     * Validate insert.
710
     */
711
    public function validateInsert(string $name, int $conflict = NodeInterface::CONFLICT_NOACTION, string $type = Collection::class): string
712
    {
713
        if ($this->readonly) {
714
            throw new Exception\Conflict('node is set as readonly, it is not possible to add new sub nodes', Exception\Conflict::READONLY);
715
        }
716
717
        if ($this->isFiltered()) {
718
            throw new Exception\Conflict('could not add node '.$name.' into a filtered parent collection', Exception\Conflict::DYNAMIC_PARENT);
719
        }
720
721
        if ($this->isDeleted()) {
722
            throw new Exception\Conflict('could not add node '.$name.' into a deleted parent collection', Exception\Conflict::DELETED_PARENT);
723
        }
724
725
        $name = $this->checkName($name);
726
727
        if ($this->childExists($name)) {
728
            if (NodeInterface::CONFLICT_NOACTION === $conflict) {
729
                throw new Exception\Conflict('a node called '.$name.' does already exists in this collection', Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS);
730
            }
731
            if (NodeInterface::CONFLICT_RENAME === $conflict) {
732
                $name = $this->getDuplicateName($name, $type);
733
            }
734
        }
735
736
        return $name;
737
    }
738
739
    /**
740
     * Validate filtered collection query.
741
     */
742
    protected function validateFilter(string $filter): bool
743
    {
744
        $filter = toPHP(fromJSON($filter), [
745
            'root' => 'array',
746
            'document' => 'array',
747
            'array' => 'array',
748
        ]);
749
750
        $this->_db->storage->findOne($filter);
751
752
        return true;
753
    }
754
755
    /**
756
     * Validate acl.
757
     */
758
    protected function validateAcl(array $acl): bool
759
    {
760
        if (!$this->_acl->isAllowed($this, 'm')) {
761
            throw new ForbiddenException('not allowed to set acl', ForbiddenException::NOT_ALLOWED_TO_MANAGE);
762
        }
763
764
        if (!$this->isSpecial()) {
765
            throw new Exception\Conflict('node acl may only be set on share member nodes', Exception\Conflict::NOT_SHARED);
766
        }
767
768
        $this->_acl->validateAcl($this->_server, $acl);
769
770
        return true;
771
    }
772
773
    /**
774
     * Get children query filter.
775
     *
776
     * Deleted:
777
     *  0 - Exclude deleted
778
     *  1 - Only deleted
779
     *  2 - Include deleted
780
     */
781
    protected function getChildrenFilter(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): array
782
    {
783
        $search = $filter;
784
        $search['parent'] = $this->getRealId();
785
786
        if (NodeInterface::DELETED_EXCLUDE === $deleted) {
787
            $search['deleted'] = false;
788
        } elseif (NodeInterface::DELETED_ONLY === $deleted) {
789
            $search['deleted'] = ['$type' => 9];
790
        }
791
792
        if ($this->shared) {
793
            $search = [
794
                '$and' => [
795
                    $search,
796
                    [
797
                        '$or' => [
798
                            ['shared' => $this->reference],
799
                            ['shared' => $this->shared],
800
                            ['shared' => $this->_id],
801
                        ],
802
                    ],
803
                    [
804
                        '$or' => [
805
                            [
806
                                'acl' => ['$exists' => false],
807
                            ], [
808
                                'acl.id' => (string) $this->_user->getId(),
809
                                'acl.privilege' => ['$in' => ['m', 'rw', 'r', 'w', 'w+']],
810
                            ],
811
                        ],
812
                    ],
813
                ],
814
            ];
815
        } elseif (null !== $this->_user) {
816
            $search['owner'] = $this->_user->getId();
817
        }
818
819
        if ($this->filter !== null && $this->_user !== null) {
820
            $stored = toPHP(fromJSON($this->filter), [
821
                'root' => 'array',
822
                'document' => 'array',
823
                'array' => 'array',
824
            ]);
825
826
            $include = isset($search['deleted']) ? ['deleted' => $search['deleted']] : [];
827
            $stored_filter = ['$and' => [
828
                array_merge(
829
                    $include,
830
                    $stored,
831
                    $filter
832
                ),
833
                ['$or' => [
834
                    ['owner' => $this->_user->getId()],
835
                    ['shared' => ['$in' => $this->_user->getShares()]],
836
                ]],
837
                [
838
                    '_id' => ['$ne' => $this->_id],
839
                ],
840
            ]];
841
842
            $search = ['$or' => [
843
                $search,
844
                $stored_filter,
845
            ]];
846
        }
847
848
        return $search;
849
    }
850
851
    /**
852
     * Completely remove node.
853
     */
854
    protected function _forceDelete(?string $recursion = null, bool $recursion_first = true): bool
855
    {
856
        if (!$this->isReference() && !$this->isMounted() && !$this->isFiltered()) {
857
            $this->doRecursiveAction(function ($node) use ($recursion) {
858
                $node->delete(true, $recursion, false);
859
            }, NodeInterface::DELETED_INCLUDE);
860
        }
861
862
        try {
863
            $this->_parent->getStorage()->forceDeleteCollection($this);
864
            $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...
865
866
            if ($this->isShared()) {
867
                $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...
868
            }
869
870
            $this->_logger->info('force removed collection ['.$this->_id.']', [
871
                'category' => get_class($this),
872
            ]);
873
874
            $this->_hook->run(
875
                'postDeleteCollection',
876
                [$this, true, $recursion, $recursion_first]
877
            );
878
        } catch (\Exception $e) {
879
            $this->_logger->error('failed force remove collection ['.$this->_id.']', [
880
                'category' => get_class($this),
881
                'exception' => $e,
882
            ]);
883
884
            throw $e;
885
        }
886
887
        return true;
888
    }
889
}
890