Completed
Push — master ( 21ca38...4312f5 )
by Raffael
10:13 queued 05:12
created

Collection::unshare()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 54

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 54
ccs 0
cts 26
cp 0
rs 9.0036
c 0
b 0
f 0
cc 4
nc 4
nop 0
crap 20

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 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(): Collection
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
        $real = $this->getRealId();
520
521
        $action = [
522
            '$set' => [
523
                'owner' => $this->_fs->findRawNode($real)['owner'],
524
                'shared' => false,
525
            ],
526
        ];
527
528
        $this->shared = false;
529
        $this->share_name = null;
530
        $this->acl = [];
531
532
        $toset = $this->_fs->findNodesByFilterRecursiveToArray($this);
533
        $this->_db->storage->updateMany([
534
            '_id' => [
535
                '$in' => $toset,
536
            ],
537
        ], $action);
538
539
        if ($real === $this->_id) {
540
            $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...
541
        } else {
542
            $this->_db->storage->updateOne([
543
                '_id' => $real,
544
            ], [
545
                '$set' => [
546
                    'share_name' => null,
547
                    'acl' => [],
548
                    'shared' => false,
549
                ],
550
            ]);
551
552
            $this->delete();
553
        }
554
555
        return $this;
556
    }
557
558
    /**
559
     * Create new directory.
560
     */
561
    public function addDirectory($name, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): self
562
    {
563
        if (!$this->_acl->isAllowed($this, 'w')) {
564
            throw new ForbiddenException(
565
                'not allowed to create new node here',
566
                ForbiddenException::NOT_ALLOWED_TO_CREATE
567
            );
568
        }
569
570
        $this->_hook->run('preCreateCollection', [$this, &$name, &$attributes, &$clone]);
571
        $name = $this->validateInsert($name, $conflict, Collection::class);
572
        $id = new ObjectId();
573
574
        try {
575
            $meta = [
576
                '_id' => $id,
577
                'pointer' => $id,
578
                'name' => $name,
579
                'deleted' => false,
580
                'parent' => $this->getRealId(),
581
                'directory' => true,
582
                'created' => new UTCDateTime(),
583
                'changed' => new UTCDateTime(),
584
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
585
                'storage' => $this->_storage->createCollection($this, $name),
586
                'storage_reference' => $this->getMount(),
587
            ];
588
589
            if (null !== $this->_user) {
590
                $meta['owner'] = $this->_user->getId();
591
            }
592
593
            $save = array_merge($meta, $attributes);
594
595
            if (isset($save['filter'])) {
596
                $this->validateFilter($save['filter']);
597
            }
598
599
            if (isset($save['acl'])) {
600
                $this->validateAcl($save['acl']);
601
            }
602
603
            $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...
604
605
            $this->_logger->info('added new collection ['.$save['_id'].'] under parent ['.$this->_id.']', [
606
                'category' => get_class($this),
607
            ]);
608
609
            $this->changed = $save['changed'];
610
            $this->save('changed');
611
612
            $new = $this->_fs->initNode($save);
613
            $this->_hook->run('postCreateCollection', [$this, $new, $clone]);
614
615
            return $new;
616
        } catch (\Exception $e) {
617
            $this->_logger->error('failed create new collection under parent ['.$this->_id.']', [
618
                'category' => get_class($this),
619
                'exception' => $e,
620
            ]);
621
622
            throw $e;
623
        }
624
    }
625
626
    /**
627
     * Create new file as a child from this collection.
628
     */
629
    public function addFile($name, ?ObjectId $session = null, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): File
630
    {
631
        if (!$this->_acl->isAllowed($this, 'w')) {
632
            throw new ForbiddenException(
633
                'not allowed to create new node here',
634
                ForbiddenException::NOT_ALLOWED_TO_CREATE
635
            );
636
        }
637
638
        $this->_hook->run('preCreateFile', [$this, &$name, &$attributes, &$clone]);
639
        $name = $this->validateInsert($name, $conflict, File::class);
640
        $id = new ObjectId();
641
642
        try {
643
            $meta = [
644
                '_id' => $id,
645
                'pointer' => $id,
646
                'name' => $name,
647
                'deleted' => false,
648
                'parent' => $this->getRealId(),
649
                'directory' => false,
650
                'hash' => null,
651
                'mime' => MimeType::getType($name),
652
                'created' => new UTCDateTime(),
653
                'changed' => new UTCDateTime(),
654
                'version' => 0,
655
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
656
                'storage_reference' => $this->getMount(),
657
            ];
658
659
            if (null !== $this->_user) {
660
                $meta['owner'] = $this->_user->getId();
661
            }
662
663
            $save = array_merge($meta, $attributes);
664
665
            if (isset($save['acl'])) {
666
                $this->validateAcl($save['acl']);
667
            }
668
669
            $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...
670
671
            $this->_logger->info('added new file ['.$save['_id'].'] under parent ['.$this->_id.']', [
672
                'category' => get_class($this),
673
            ]);
674
675
            $this->changed = $save['changed'];
676
            $this->save('changed');
677
678
            $file = $this->_fs->initNode($save);
679
680
            if ($session !== null) {
681
                $file->setContent($session, $attributes);
682
            }
683
684
            $this->_hook->run('postCreateFile', [$this, $file, $clone]);
685
686
            return $file;
687
        } catch (\Exception $e) {
688
            $this->_logger->error('failed add new file under parent ['.$this->_id.']', [
689
                'category' => get_class($this),
690
                'exception' => $e,
691
            ]);
692
693
            throw $e;
694
        }
695
    }
696
697
    /**
698
     * Create new file wrapper
699
     * (Sabe\DAV compatible method, elsewhere use addFile().
700
     *
701
     * Sabre\DAV requires that createFile() returns the ETag instead the newly created file instance
702
     *
703
     * @param string $name
704
     * @param string $data
705
     */
706
    public function createFile($name, $data = null): string
707
    {
708
        $session = $this->_storage->storeTemporaryFile($data, $this->_user);
709
710
        if ($this->childExists($name, NodeInterface::DELETED_INCLUDE, ['directory' => false])) {
711
            $file = $this->getChild($name, NodeInterface::DELETED_INCLUDE, ['directory' => false]);
712
            $file->setContent($session);
713
        } else {
714
            $file = $this->addFile($name, $session);
715
        }
716
717
        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...
718
    }
719
720
    /**
721
     * Create new directory wrapper
722
     * (Sabe\DAV compatible method, elsewhere use addDirectory().
723
     *
724
     * Sabre\DAV requires that createDirectory() returns void
725
     *
726
     * @param string $name
727
     */
728
    public function createDirectory($name): void
729
    {
730
        $this->addDirectory($name);
731
    }
732
733
    /**
734
     * Do recursive Action.
735
     */
736
    public function doRecursiveAction(callable $callable, int $deleted = NodeInterface::DELETED_EXCLUDE): bool
737
    {
738
        $children = $this->getChildNodes($deleted, []);
739
740
        foreach ($children as $child) {
741
            $callable($child);
742
        }
743
744
        return true;
745
    }
746
747
    /**
748
     * Validate insert.
749
     */
750
    protected function validateInsert(string $name, int $conflict = NodeInterface::CONFLICT_NOACTION, string $type = Collection::class): string
751
    {
752
        if ($this->readonly) {
753
            throw new Exception\Conflict(
754
                'node is set as readonly, it is not possible to add new sub nodes',
755
                Exception\Conflict::READONLY
756
            );
757
        }
758
759
        $name = $this->checkName($name);
760
761
        if ($this->childExists($name)) {
762
            if (NodeInterface::CONFLICT_NOACTION === $conflict) {
763
                throw new Exception\Conflict(
764
                    'a node called '.$name.' does already exists in this collection',
765
                    Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
766
                );
767
            }
768
            if (NodeInterface::CONFLICT_RENAME === $conflict) {
769
                $name = $this->getDuplicateName($name, $type);
770
            }
771
        }
772
773
        if ($this->isFiltered()) {
774
            throw new Exception\Conflict(
775
                'could not add node '.$name.' into a filtered parent collection',
776
                Exception\Conflict::DYNAMIC_PARENT
777
            );
778
        }
779
780
        if ($this->isDeleted()) {
781
            throw new Exception\Conflict(
782
                'could not add node '.$name.' into a deleted parent collection',
783
                Exception\Conflict::DELETED_PARENT
784
            );
785
        }
786
787
        return $name;
788
    }
789
790
    /**
791
     * Validate filtered collection query.
792
     */
793
    protected function validateFilter(string $filter): bool
794
    {
795
        $filter = toPHP(fromJSON($filter), [
796
            'root' => 'array',
797
            'document' => 'array',
798
            'array' => 'array',
799
        ]);
800
801
        $this->_db->storage->findOne($filter);
802
803
        return true;
804
    }
805
806
    /**
807
     * Validate acl.
808
     */
809
    protected function validateAcl(array $acl): bool
810
    {
811
        if (!$this->_acl->isAllowed($this, 'm')) {
812
            throw new ForbiddenException(
813
                'not allowed to set acl',
814
                ForbiddenException::NOT_ALLOWED_TO_MANAGE
815
            );
816
        }
817
818
        if (!$this->isSpecial()) {
819
            throw new Exception\Conflict('node acl may only be set on share member nodes', Exception\Conflict::NOT_SHARED);
820
        }
821
822
        $this->_acl->validateAcl($this->_server, $acl);
823
824
        return true;
825
    }
826
827
    /**
828
     * Get children query filter.
829
     *
830
     * Deleted:
831
     *  0 - Exclude deleted
832
     *  1 - Only deleted
833
     *  2 - Include deleted
834
     */
835
    protected function getChildrenFilter(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): array
836
    {
837
        $search = [
838
            'parent' => $this->getRealId(),
839
        ];
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
        $search = array_merge($filter, $search);
848
849
        if ($this->shared) {
850
            $search = [
851
                '$and' => [
852
                    $search,
853
                    [
854
                        '$or' => [
855
                            ['shared' => $this->reference],
856
                            ['shared' => $this->shared],
857
                            ['shared' => $this->_id],
858
                        ],
859
                    ],
860
                ],
861
            ];
862
        } elseif (null !== $this->_user) {
863
            $search['owner'] = $this->_user->getId();
864
        }
865
866
        if ($this->filter !== null && $this->_user !== null) {
867
            $stored = toPHP(fromJSON($this->filter), [
868
                'root' => 'array',
869
                'document' => 'array',
870
                'array' => 'array',
871
            ]);
872
873
            $include = isset($search['deleted']) ? ['deleted' => $search['deleted']] : [];
874
            $stored_filter = ['$and' => [
875
                array_merge(
876
                    $include,
877
                    $stored,
878
                    $filter
879
                ),
880
                ['$or' => [
881
                    ['owner' => $this->_user->getId()],
882
                    ['shared' => ['$in' => $this->_user->getShares()]],
883
                ]],
884
                [
885
                    '_id' => ['$ne' => $this->_id],
886
                ],
887
            ]];
888
889
            $search = ['$or' => [
890
                $search,
891
                $stored_filter,
892
            ]];
893
        }
894
895
        return $search;
896
    }
897
898
    /**
899
     * Completely remove node.
900
     */
901
    protected function _forceDelete(?string $recursion = null, bool $recursion_first = true): bool
902
    {
903
        if (!$this->isReference() && !$this->isMounted() && !$this->isFiltered()) {
904
            $this->doRecursiveAction(function ($node) use ($recursion) {
905
                $node->delete(true, $recursion, false);
906
            }, NodeInterface::DELETED_INCLUDE);
907
        }
908
909
        try {
910
            $this->_parent->getStorage()->forceDeleteCollection($this);
911
            $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...
912
913
            if ($this->isShared()) {
914
                $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...
915
            }
916
917
            $this->_logger->info('force removed collection ['.$this->_id.']', [
918
                'category' => get_class($this),
919
            ]);
920
921
            $this->_hook->run(
922
                'postDeleteCollection',
923
                [$this, true, $recursion, $recursion_first]
924
            );
925
        } catch (\Exception $e) {
926
            $this->_logger->error('failed force remove collection ['.$this->_id.']', [
927
                'category' => get_class($this),
928
                'exception' => $e,
929
            ]);
930
931
            throw $e;
932
        }
933
934
        return true;
935
    }
936
}
937