Test Failed
Push — master ( 817d84...d52e3d )
by Raffael
05:44
created

Collection::getRealId()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 3
crap 12
nc 2
nop 0
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
            'directory' => true,
196
            'reference' => $this->reference,
197
            'parent' => $this->parent,
198
            'app' => $this->app,
199
            'owner' => $this->owner,
200
            'meta' => $this->meta,
201
            'mime' => $this->mime,
202
            'filter' => $this->filter,
203
            'deleted' => $this->deleted,
204
            'changed' => $this->changed,
205
            'created' => $this->created,
206
            'destroy' => $this->destroy,
207
            'readonly' => $this->readonly,
208
            'mount' => $this->mount,
209
            'storage_reference' => $this->storage_reference,
210
            'storage' => $this->storage,
211
        ];
212
    }
213
214
    /**
215
     * Set collection filter.
216
     */
217
    public function setFilter(?array $filter = null): bool
218
    {
219
        $this->filter = json_encode($filter);
220
221
        return $this->save('filter');
222
    }
223
224
    /**
225
     * Get collection.
226
     */
227
    public function get(): void
228
    {
229
        $this->getZip();
230
    }
231
232
    /**
233
     * Fetch children items of this collection.
234
     *
235
     * Deleted:
236
     *  0 - Exclude deleted
237
     *  1 - Only deleted
238
     *  2 - Include deleted
239
     */
240
    public function getChildNodes(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = [], ?int $offset = null, ?int $limit = null, bool $recursive = false): Generator
241
    {
242
        $filter = $this->getChildrenFilter($deleted, $filter);
243
244
        if ($recursive === false) {
245
            return $this->_fs->findNodesByFilter($filter, $offset, $limit);
246
        }
247
248
        unset($filter['parent']);
249
250
        return $this->_fs->findNodesByFilterRecursive($this, $filter, $offset, $limit);
251
    }
252
253
    /**
254
     * Fetch children items of this collection (as array).
255
     *
256
     * Deleted:
257
     *  0 - Exclude deleted
258
     *  1 - Only deleted
259
     *  2 - Include deleted
260
     */
261
    public function getChildren(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): array
262
    {
263
        return iterator_to_array($this->getChildNodes($deleted, $filter));
264
    }
265
266
    /**
267
     * Is custom filter node.
268
     */
269
    public function isFiltered(): bool
270
    {
271
        return !empty($this->filter);
272
    }
273
274
    /**
275
     * Get number of children.
276
     */
277
    public function getSize(): int
278
    {
279
        return count($this->getChildren());
280
    }
281
282
    /**
283
     * Get real id (reference).
284
     *
285
     * @return ObjectId
286
     */
287
    public function getRealId(): ?ObjectId
288
    {
289
        if (true === $this->shared && $this->isReference()) {
290
            return $this->reference;
291
        }
292
293
        return $this->_id;
294
    }
295
296
    /**
297
     * Get user quota information.
298
     */
299
    public function getQuotaInfo(): array
300
    {
301
        $quota = $this->_user->getQuotaUsage();
302
303
        return [
304
            $quota['used'],
305
            $quota['available'],
306
        ];
307
    }
308
309
    /**
310
     * Fetch children items of this collection.
311
     */
312
    public function getChild($name, int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): NodeInterface
313
    {
314
        $name = $this->checkName($name);
315
        $filter = $this->getChildrenFilter($deleted, $filter);
316
        $filter['name'] = new Regex('^'.preg_quote($name).'$', 'i');
317
        $node = $this->_db->storage->findOne($filter);
318
319
        if (null === $node) {
320
            throw new Exception\NotFound(
321
                'node called '.$name.' does not exists here',
322
                Exception\NotFound::NODE_NOT_FOUND
323
            );
324
        }
325
326
        $this->_logger->debug('loaded node ['.$node['_id'].' from parent node ['.$this->getRealId().']', [
327
            'category' => get_class($this),
328
        ]);
329
330
        return $this->_fs->initNode($node);
331
    }
332
333
    /**
334
     * Delete node.
335
     *
336
     * Actually the node will not be deleted (Just set a delete flag), set $force=true to
337
     * delete finally
338
     */
339
    public function delete(bool $force = false, ?string $recursion = null, bool $recursion_first = true): bool
340
    {
341
        if (!$this->isReference() && !$this->_acl->isAllowed($this, 'w')) {
342
            throw new ForbiddenException(
343
                'not allowed to delete node '.$this->name,
344
                ForbiddenException::NOT_ALLOWED_TO_DELETE
345
            );
346
        }
347
348
        if (null === $recursion) {
349
            $recursion_first = true;
350
            $recursion = uniqid();
351
        } else {
352
            $recursion_first = false;
353
        }
354
355
        $this->_hook->run(
356
            'preDeleteCollection',
357
            [$this, &$force, &$recursion, &$recursion_first]
358
        );
359
360
        if (true === $force) {
361
            return $this->_forceDelete($recursion, $recursion_first);
362
        }
363
364
        $this->deleted = new UTCDateTime();
365
        $this->storage = $this->_parent->getStorage()->deleteCollection($this);
366
367
        if (!$this->isReference() && !$this->isMounted() && !$this->isFiltered()) {
368
            $this->doRecursiveAction(function ($node) use ($recursion) {
369
                $node->delete(false, $recursion, false);
370
            }, NodeInterface::DELETED_EXCLUDE);
371
        }
372
373
        if (null !== $this->_id) {
374
            $result = $this->save([
375
                'deleted', 'storage',
376
            ], [], $recursion, false);
377
        } else {
378
            $result = true;
379
        }
380
381
        $this->_hook->run(
382
            'postDeleteCollection',
383
            [$this, $force, $recursion, $recursion_first]
384
        );
385
386
        return $result;
387
    }
388
389
    /**
390
     * Check if this collection has child named $name.
391
     *
392
     * deleted:
393
     *
394
     *  0 - Exclude deleted
395
     *  1 - Only deleted
396
     *  2 - Include deleted
397
     *
398
     * @param string $name
399
     * @param int    $deleted
400
     */
401
    public function childExists($name, $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): bool
402
    {
403
        $name = $this->checkName($name);
404
405
        $find = [
406
            'parent' => $this->getRealId(),
407
            'name' => new Regex('^'.preg_quote($name).'$', 'i'),
408
        ];
409
410
        if (null !== $this->_user) {
411
            $find['owner'] = $this->_user->getId();
412
        }
413
414
        switch ($deleted) {
415
            case NodeInterface::DELETED_EXCLUDE:
416
                $find['deleted'] = false;
417
418
                break;
419
            case NodeInterface::DELETED_ONLY:
420
                $find['deleted'] = ['$type' => 9];
421
422
                break;
423
        }
424
425
        $find = array_merge($filter, $find);
426
427
        if ($this->isSpecial()) {
428
            unset($find['owner']);
429
        }
430
431
        $node = $this->_db->storage->findOne($find);
432
433
        return (bool) $node;
434
    }
435
436
    /**
437
     * Share collection.
438
     */
439
    public function share(array $acl, string $name): bool
440
    {
441
        if ($this->isShareMember()) {
442
            throw new Exception('a sub node of a share can not be shared');
443
        }
444
445
        $this->checkName($name);
446
        $this->_acl->validateAcl($this->_server, $acl);
447
448
        $action = [
449
            '$set' => [
450
                'shared' => $this->getRealId(),
451
            ],
452
        ];
453
454
        $query = [
455
            '$or' => [
456
                ['reference' => ['exists' => true]],
457
                ['shared' => true],
458
            ],
459
        ];
460
461
        if (iterator_count($this->_fs->findNodesByFilterRecursive($this, $query, 0, 1)) !== 0) {
462
            throw new Exception\Conflict(
463
                'folder contains a shared folder',
464
                Exception\Conflict::NODE_CONTAINS_SHARED_NODE
465
            );
466
        }
467
468
        $toset = $this->_fs->findNodesByFilterRecursiveToArray($this);
469
        $this->_db->storage->updateMany([
470
            '_id' => [
471
                '$in' => $toset,
472
            ],
473
        ], $action);
474
475
        $this->_db->delta->updateMany([
476
            '_id' => [
477
                '$in' => $toset,
478
            ],
479
        ], $action);
480
481
        if ($this->getRealId() === $this->_id) {
482
            $this->acl = $acl;
483
            $this->shared = true;
484
            $this->share_name = $name;
485
            $this->save(['acl', 'shared', 'share_name']);
486
        } else {
487
            $this->_db->storage->updateOne([
488
                '_id' => $this->getRealId(),
489
            ], [
490
                '$set' => [
491
                    'share_name' => $name,
492
                    'acl' => $acl,
493
                ],
494
            ]);
495
        }
496
497
        return true;
498
    }
499
500
    /**
501
     * Unshare collection.
502
     */
503
    public function unshare(): bool
504
    {
505
        if (!$this->_acl->isAllowed($this, 'm')) {
506
            throw new ForbiddenException(
507
                'not allowed to share node',
508
                ForbiddenException::NOT_ALLOWED_TO_MANAGE
509
            );
510
        }
511
512
        if (true !== $this->shared) {
513
            throw new Exception\Conflict(
514
                'Can not unshare a none shared collection',
515
                Exception\Conflict::NOT_SHARED
516
            );
517
        }
518
519
        $this->shared = false;
520
        $this->share_name = null;
521
        $this->acl = [];
522
        $action = [
523
            '$set' => [
524
                'owner' => $this->_user->getId(),
525
                'shared' => false,
526
            ],
527
        ];
528
529
        $toset = $this->_fs->findNodesByFilterRecursiveToArray($this);
530
        $this->_db->storage->updateMany([
531
            '_id' => [
532
                '$in' => $toset,
533
            ],
534
        ], $action);
535
536
        $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...
537
538
        return true;
539
    }
540
541
    /**
542
     * Create new directory.
543
     */
544
    public function addDirectory($name, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): self
545
    {
546
        if (!$this->_acl->isAllowed($this, 'w')) {
547
            throw new ForbiddenException(
548
                'not allowed to create new node here',
549
                ForbiddenException::NOT_ALLOWED_TO_CREATE
550
            );
551
        }
552
553
        $this->_hook->run('preCreateCollection', [$this, &$name, &$attributes, &$clone]);
554
        $name = $this->validateInsert($name, $conflict, Collection::class);
555
        $id = new ObjectId();
556
557
        try {
558
            $meta = [
559
                '_id' => $id,
560
                'pointer' => $id,
561
                'name' => $name,
562
                'deleted' => false,
563
                'parent' => $this->getRealId(),
564
                'directory' => true,
565
                'created' => new UTCDateTime(),
566
                'changed' => new UTCDateTime(),
567
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
568
                'storage' => $this->_storage->createCollection($this, $name),
569
                'storage_reference' => $this->getMount(),
570
            ];
571
572
            if (null !== $this->_user) {
573
                $meta['owner'] = $this->_user->getId();
574
            }
575
576
            $save = array_merge($meta, $attributes);
577
578
            if (isset($save['filter'])) {
579
                $this->validateFilter($save['filter']);
580
            }
581
582
            if (isset($save['acl'])) {
583
                $this->validateAcl($save['acl']);
584
            }
585
586
            $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...
587
588
            $this->_logger->info('added new collection ['.$save['_id'].'] under parent ['.$this->_id.']', [
589
                'category' => get_class($this),
590
            ]);
591
592
            $this->changed = $save['changed'];
593
            $this->save('changed');
594
595
            $new = $this->_fs->initNode($save);
596
            $this->_hook->run('postCreateCollection', [$this, $new, $clone]);
597
598
            return $new;
599
        } catch (\Exception $e) {
600
            $this->_logger->error('failed create new collection under parent ['.$this->_id.']', [
601
                'category' => get_class($this),
602
                'exception' => $e,
603
            ]);
604
605
            throw $e;
606
        }
607
    }
608
609
    /**
610
     * Create new file as a child from this collection.
611
     */
612
    public function addFile($name, ?ObjectId $session = null, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): File
613
    {
614
        if (!$this->_acl->isAllowed($this, 'w')) {
615
            throw new ForbiddenException(
616
                'not allowed to create new node here',
617
                ForbiddenException::NOT_ALLOWED_TO_CREATE
618
            );
619
        }
620
621
        $this->_hook->run('preCreateFile', [$this, &$name, &$attributes, &$clone]);
622
        $name = $this->validateInsert($name, $conflict, File::class);
623
        $id = new ObjectId();
624
625
        try {
626
            $meta = [
627
                '_id' => $id,
628
                'pointer' => $id,
629
                'name' => $name,
630
                'deleted' => false,
631
                'parent' => $this->getRealId(),
632
                'directory' => false,
633
                'hash' => null,
634
                'mime' => MimeType::getType($name),
635
                'created' => new UTCDateTime(),
636
                'changed' => new UTCDateTime(),
637
                'version' => 0,
638
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
639
                'storage_reference' => $this->getMount(),
640
            ];
641
642
            if (null !== $this->_user) {
643
                $meta['owner'] = $this->_user->getId();
644
            }
645
646
            $save = array_merge($meta, $attributes);
647
648
            if (isset($save['acl'])) {
649
                $this->validateAcl($save['acl']);
650
            }
651
652
            $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...
653
654
            $this->_logger->info('added new file ['.$save['_id'].'] under parent ['.$this->_id.']', [
655
                'category' => get_class($this),
656
            ]);
657
658
            $this->changed = $save['changed'];
659
            $this->save('changed');
660
661
            $file = $this->_fs->initNode($save);
662
663
            if ($session !== null) {
664
                $file->setContent($session, $attributes);
665
            }
666
667
            $this->_hook->run('postCreateFile', [$this, $file, $clone]);
668
669
            return $file;
670
        } catch (\Exception $e) {
671
            $this->_logger->error('failed add new file under parent ['.$this->_id.']', [
672
                'category' => get_class($this),
673
                'exception' => $e,
674
            ]);
675
676
            throw $e;
677
        }
678
    }
679
680
    /**
681
     * Create new file wrapper
682
     * (Sabe\DAV compatible method, elsewhere use addFile().
683
     *
684
     * Sabre\DAV requires that createFile() returns the ETag instead the newly created file instance
685
     *
686
     * @param string $name
687
     * @param string $data
688
     */
689
    public function createFile($name, $data = null): string
690
    {
691
        $session = $this->_storage->storeTemporaryFile($data, $this->_user);
692
693
        if ($this->childExists($name, NodeInterface::DELETED_INCLUDE, ['directory' => false])) {
694
            $file = $this->getChild($name, NodeInterface::DELETED_INCLUDE, ['directory' => false]);
695
            $file->setContent($session);
696
        } else {
697
            $file = $this->addFile($name, $session);
698
        }
699
700
        return $file->getETag();
0 ignored issues
show
Bug introduced by
The method getETag does only exist in Balloon\Filesystem\Node\File, but not in Balloon\Filesystem\Node\NodeInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

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