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

Collection::addDirectory()   F

Complexity

Conditions 12
Paths 402

Size

Total Lines 92

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 0
Metric Value
dl 0
loc 92
ccs 0
cts 52
cp 0
rs 3.0789
c 0
b 0
f 0
cc 12
nc 402
nop 4
crap 156

How to fix   Long Method    Complexity   

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