Completed
Push — master ( 2aab0a...440703 )
by Raffael
19:06 queued 15:05
created

Collection::addFile()   D

Complexity

Conditions 12
Paths 282

Size

Total Lines 95

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

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

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

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