Completed
Push — master ( afaf42...8ac772 )
by Raffael
20:10 queued 16:21
created

Collection::addFile()   F

Complexity

Conditions 12
Paths 306

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 52
cp 0
rs 3.9557
c 0
b 0
f 0
cc 12
nc 306
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
     * @param Collection $parent
109
     * @param string     $recursion
110
     */
111
    public function copyTo(self $parent, int $conflict = NodeInterface::CONFLICT_NOACTION, ?string $recursion = null, bool $recursion_first = true): NodeInterface
112
    {
113
        if (null === $recursion) {
114
            $recursion_first = true;
115
            $recursion = uniqid();
116
        } else {
117
            $recursion_first = false;
118
        }
119
120
        $this->_hook->run(
121
            'preCopyCollection',
122
            [$this, $parent, &$conflict, &$recursion, &$recursion_first]
123
        );
124
125
        if (NodeInterface::CONFLICT_RENAME === $conflict && $parent->childExists($this->name)) {
126
            $name = $this->getDuplicateName();
127
        } else {
128
            $name = $this->name;
129
        }
130
131
        if ($this->_id === $parent->getId()) {
132
            throw new Exception\Conflict(
133
                'can not copy node into itself',
134
                Exception\Conflict::CANT_COPY_INTO_ITSELF
135
            );
136
        }
137
138
        if (NodeInterface::CONFLICT_MERGE === $conflict && $parent->childExists($this->name)) {
139
            $new_parent = $parent->getChild($this->name);
140
        } else {
141
            $new_parent = $parent->addDirectory($name, [
142
                'created' => $this->created,
143
                'changed' => $this->changed,
144
                'deleted' => $this->deleted,
145
                'filter' => $this->filter,
146
                'meta' => $this->meta,
147
            ], NodeInterface::CONFLICT_NOACTION, true);
148
        }
149
150
        foreach ($this->getChildNodes(NodeInterface::DELETED_INCLUDE) as $child) {
151
            $child->copyTo($new_parent, $conflict, $recursion, false);
152
        }
153
154
        $this->_hook->run(
155
            'postCopyCollection',
156
            [$this, $parent, $new_parent, $conflict, $recursion, $recursion_first]
157
        );
158
159
        return $new_parent;
160
    }
161
162
    /**
163
     * Is mount.
164
     */
165
    public function isMounted(): bool
166
    {
167
        return count($this->mount) > 0;
168
    }
169
170
    /**
171
     * Get Share name.
172
     */
173
    public function getShareName(): string
174
    {
175
        if ($this->isShare()) {
176
            return $this->share_name;
177
        }
178
179
        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...
180
    }
181
182
    /**
183
     * Get Attributes.
184
     */
185
    public function getAttributes(): array
186
    {
187
        return [
188
            '_id' => $this->_id,
189
            'name' => $this->name,
190
            'shared' => $this->shared,
191
            'share_name' => $this->share_name,
192
            'acl' => $this->acl,
193
            'directory' => true,
194
            'reference' => $this->reference,
195
            'parent' => $this->parent,
196
            'app' => $this->app,
197
            'owner' => $this->owner,
198
            'meta' => $this->meta,
199
            'mime' => $this->mime,
200
            'filter' => $this->filter,
201
            'deleted' => $this->deleted,
202
            'changed' => $this->changed,
203
            'created' => $this->created,
204
            'destroy' => $this->destroy,
205
            'readonly' => $this->readonly,
206
            'mount' => $this->mount,
207
            'storage_reference' => $this->storage_reference,
208
            'storage' => $this->storage,
209
        ];
210
    }
211
212
    /**
213
     * Set collection filter.
214
     *
215
     * @param string $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 $this->_db->storage->count($this->getChildrenFilter());
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
        if (!$this->_acl->isAllowed($this, 'm')) {
443
            throw new ForbiddenException(
444
                'not allowed to share node',
445
                ForbiddenException::NOT_ALLOWED_TO_MANAGE
446
            );
447
        }
448
449
        $this->_acl->validateAcl($this->_server, $acl);
450
451
        $action = [
452
            '$set' => [
453
                'shared' => $this->getRealId(),
454
            ],
455
        ];
456
457
        $toset = $this->getChildrenRecursive($this->getRealId(), $shares);
458
459
        if (!empty($shares)) {
460
            throw new Exception('child folder contains a shared folder');
461
        }
462
463
        $this->_db->storage->updateMany([
464
            '_id' => [
465
                '$in' => $toset,
466
            ],
467
        ], $action);
468
469
        $this->_db->delta->updateMany([
470
            '_id' => [
471
                '$in' => $toset,
472
            ],
473
        ], $action);
474
475
        if ($this->getRealId() === $this->_id) {
476
            $this->acl = $acl;
477
            $this->shared = true;
478
            $this->share_name = $name;
479
            $this->save(['acl', 'shared', 'share_name']);
480
        } else {
481
            $this->_db->storage->updateOne([
482
                '_id' => $this->getRealId(),
483
            ], [
484
                '$set' => [
485
                    'share_name' => $name,
486
                    'acl' => $acl,
487
                ],
488
            ]);
489
        }
490
491
        return true;
492
    }
493
494
    /**
495
     * Unshare collection.
496
     */
497
    public function unshare(): bool
498
    {
499
        if (!$this->_acl->isAllowed($this, 'm')) {
500
            throw new ForbiddenException(
501
                'not allowed to share node',
502
                ForbiddenException::NOT_ALLOWED_TO_MANAGE
503
            );
504
        }
505
506
        if (true !== $this->shared) {
507
            throw new Exception\Conflict(
508
                'Can not unshare a none shared collection',
509
                Exception\Conflict::NOT_SHARED
510
            );
511
        }
512
513
        $this->shared = false;
514
        $this->share_name = null;
515
        $this->acl = [];
516
        $action = [
517
            '$unset' => [
518
                'shared' => $this->_id,
519
            ],
520
            '$set' => [
521
                'owner' => $this->_user->getId(),
522
            ],
523
        ];
524
525
        $toset = $this->getChildrenRecursive($this->getRealId(), $shares);
526
527
        $this->_db->storage->updateMany([
528
            '_id' => [
529
                '$in' => $toset,
530
            ],
531
        ], $action);
532
533
        $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...
534
535
        return true;
536
    }
537
538
    /**
539
     * Get children.
540
     */
541
    public function getChildrenRecursive(?ObjectId $id = null, ?array &$shares = []): array
542
    {
543
        $list = [];
544
        $result = $this->_db->storage->find([
545
            'parent' => $id,
546
        ], [
547
            '_id' => 1,
548
            'directory' => 1,
549
            'reference' => 1,
550
            'shared' => 1,
551
        ]);
552
553
        foreach ($result as $node) {
554
            $list[] = $node['_id'];
555
556
            if ($node['directory'] === true) {
557
                if (isset($node['reference']) || isset($node['shared']) && true === $node['shared']) {
558
                    $shares[] = $node['_id'];
559
                }
560
561
                if (true === $node['directory'] && !isset($node['reference'])) {
562
                    $list = array_merge($list, $this->getChildrenRecursive($node['_id'], $shares));
563
                }
564
            }
565
        }
566
567
        return $list;
568
    }
569
570
    /**
571
     * Create new directory.
572
     *
573
     * @param string $name
574
     * @param arracy $attributes
575
     *
576
     * @return Collection
577
     */
578
    public function addDirectory($name, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): self
579
    {
580
        if (!$this->_acl->isAllowed($this, 'w')) {
581
            throw new ForbiddenException(
582
                'not allowed to create new node here',
583
                ForbiddenException::NOT_ALLOWED_TO_CREATE
584
            );
585
        }
586
587
        $this->_hook->run('preCreateCollection', [$this, &$name, &$attributes, &$clone]);
588
589
        if ($this->readonly) {
590
            throw new Exception\Conflict(
591
                'node is set as readonly, it is not possible to add new sub nodes',
592
                Exception\Conflict::READONLY
593
            );
594
        }
595
596
        $name = $this->checkName($name);
597
598
        if ($this->childExists($name)) {
599
            if (NodeInterface::CONFLICT_NOACTION === $conflict) {
600
                throw new Exception\Conflict(
601
                    'a node called '.$name.' does already exists in this collection',
602
                    Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
603
                );
604
            }
605
            if (NodeInterface::CONFLICT_RENAME === $conflict) {
606
                $name = $this->getDuplicateName($name);
607
            }
608
        }
609
610
        if ($this->isDeleted()) {
611
            throw new Exception\Conflict(
612
                'could not add node '.$name.' into a deleted parent collection',
613
                Exception\Conflict::DELETED_PARENT
614
            );
615
        }
616
617
        try {
618
            $meta = [
619
                'name' => $name,
620
                'deleted' => false,
621
                'parent' => $this->getRealId(),
622
                'directory' => true,
623
                'created' => new UTCDateTime(),
624
                'changed' => new UTCDateTime(),
625
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
626
                'storage' => $this->_storage->createCollection($this, $name),
627
                'storage_reference' => $this->getMount(),
628
            ];
629
630
            if (null !== $this->_user) {
631
                $meta['owner'] = $this->_user->getId();
632
            }
633
634
            $save = array_merge($meta, $attributes);
635
636
            if (isset($save['acl'])) {
637
                $this->validateAcl($save['acl']);
638
            }
639
640
            $result = $this->_db->storage->insertOne($save, [
641
                '$isolated' => true,
642
            ]);
643
644
            $save['_id'] = $result->getInsertedId();
645
646
            $this->_logger->info('added new collection ['.$save['_id'].'] under parent ['.$this->_id.']', [
647
                'category' => get_class($this),
648
            ]);
649
650
            $this->changed = $save['changed'];
651
            $this->save('changed');
652
653
            $new = $this->_fs->initNode($save);
654
            $this->_hook->run('postCreateCollection', [$this, $new, $clone]);
655
656
            return $new;
657
        } catch (\Exception $e) {
658
            $this->_logger->error('failed create new collection under parent ['.$this->_id.']', [
659
                'category' => get_class($this),
660
                'exception' => $e,
661
            ]);
662
663
            throw $e;
664
        }
665
    }
666
667
    /**
668
     * Create new file as a child from this collection.
669
     */
670
    public function addFile($name, ?ObjectId $session = null, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): File
671
    {
672
        if (!$this->_acl->isAllowed($this, 'w')) {
673
            throw new ForbiddenException(
674
                'not allowed to create new node here',
675
                ForbiddenException::NOT_ALLOWED_TO_CREATE
676
            );
677
        }
678
679
        $this->_hook->run('preCreateFile', [$this, &$name, &$attributes, &$clone]);
680
681
        if ($this->readonly) {
682
            throw new Exception\Conflict(
683
                'node is set as readonly, it is not possible to add new sub nodes',
684
                Exception\Conflict::READONLY
685
            );
686
        }
687
688
        $name = $this->checkName($name);
689
690
        if ($this->childExists($name)) {
691
            if (NodeInterface::CONFLICT_NOACTION === $conflict) {
692
                throw new Exception\Conflict(
693
                    'a node called '.$name.' does already exists in this collection',
694
                    Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
695
                );
696
            }
697
            if (NodeInterface::CONFLICT_RENAME === $conflict) {
698
                $name = $this->getDuplicateName($name, File::class);
699
            }
700
        }
701
702
        if ($this->isDeleted()) {
703
            throw new Exception\Conflict(
704
                'could not add node '.$name.' into a deleted parent collection',
705
                Exception\Conflict::DELETED_PARENT
706
            );
707
        }
708
709
        try {
710
            $meta = [
711
                'name' => $name,
712
                'deleted' => false,
713
                'parent' => $this->getRealId(),
714
                'directory' => false,
715
                'hash' => null,
716
                'mime' => MimeType::getType($name),
717
                'created' => new UTCDateTime(),
718
                'changed' => new UTCDateTime(),
719
                'version' => 0,
720
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
721
                'storage_reference' => $this->getMount(),
722
            ];
723
724
            if (null !== $this->_user) {
725
                $meta['owner'] = $this->_user->getId();
726
            }
727
728
            $save = array_merge($meta, $attributes);
729
730
            if (isset($save['acl'])) {
731
                $this->validateAcl($save['acl']);
732
            }
733
734
            $result = $this->_db->storage->insertOne($save, [
735
                '$isolated' => true,
736
            ]);
737
738
            $save['_id'] = $result->getInsertedId();
739
740
            $this->_logger->info('added new file ['.$save['_id'].'] under parent ['.$this->_id.']', [
741
                'category' => get_class($this),
742
            ]);
743
744
            $this->changed = $save['changed'];
745
            $this->save('changed');
746
747
            $file = $this->_fs->initNode($save);
748
749
            if ($session !== null) {
750
                $file->setContent($session, $attributes);
751
            }
752
753
            $this->_hook->run('postCreateFile', [$this, $file, $clone]);
754
755
            return $file;
756
        } catch (\Exception $e) {
757
            $this->_logger->error('failed add new file under parent ['.$this->_id.']', [
758
                'category' => get_class($this),
759
                'exception' => $e,
760
            ]);
761
762
            throw $e;
763
        }
764
    }
765
766
    /**
767
     * Create new file wrapper
768
     * (Sabe\DAV compatible method, elsewhere use addFile().
769
     *
770
     * Sabre\DAV requires that createFile() returns the ETag instead the newly created file instance
771
     *
772
     * @param string $name
773
     * @param string $data
774
     */
775
    public function createFile($name, $data = null): string
776
    {
777
        $session = $this->_storage->storeTemporaryFile($data, $this->_user);
778
779
        if ($this->childExists($name)) {
780
            $file = $this->getChild($name);
781
            $file->setContent($session);
782
        } else {
783
            $file = $this->addFile($name, $session);
784
        }
785
786
        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...
787
    }
788
789
    /**
790
     * Create new directory wrapper
791
     * (Sabe\DAV compatible method, elsewhere use addDirectory().
792
     *
793
     * Sabre\DAV requires that createDirectory() returns void
794
     *
795
     * @param string $name
796
     */
797
    public function createDirectory($name): void
798
    {
799
        $this->addDirectory($name);
800
    }
801
802
    /**
803
     * Do recursive Action.
804
     */
805
    public function doRecursiveAction(callable $callable, int $deleted = NodeInterface::DELETED_EXCLUDE): bool
806
    {
807
        $children = $this->getChildNodes($deleted, []);
808
809
        foreach ($children as $child) {
810
            $callable($child);
811
        }
812
813
        return true;
814
    }
815
816
    /**
817
     * Validate acl.
818
     */
819
    protected function validateAcl(array $acl): bool
820
    {
821
        if (!$this->_acl->isAllowed($this, 'm')) {
822
            throw new ForbiddenException(
823
                 'not allowed to set acl',
824
                  ForbiddenException::NOT_ALLOWED_TO_MANAGE
825
            );
826
        }
827
828
        if (!$this->isSpecial()) {
829
            throw new Exception\Conflict('node acl may only be set on share member nodes', Exception\Conflict::NOT_SHARED);
830
        }
831
832
        $this->_acl->validateAcl($this->_server, $acl);
833
834
        return true;
835
    }
836
837
    /**
838
     * Get children query filter.
839
     *
840
     * Deleted:
841
     *  0 - Exclude deleted
842
     *  1 - Only deleted
843
     *  2 - Include deleted
844
     */
845
    protected function getChildrenFilter(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): array
846
    {
847
        $search = [
848
            'parent' => $this->getRealId(),
849
        ];
850
851
        if (NodeInterface::DELETED_EXCLUDE === $deleted) {
852
            $search['deleted'] = false;
853
        } elseif (NodeInterface::DELETED_ONLY === $deleted) {
854
            $search['deleted'] = ['$type' => 9];
855
        }
856
857
        $search = array_merge($filter, $search);
858
859
        if ($this->shared) {
860
            $search = [
861
                '$and' => [
862
                    $search,
863
                    [
864
                        '$or' => [
865
                            ['shared' => $this->reference],
866
                            ['shared' => $this->shared],
867
                            ['shared' => $this->_id],
868
                        ],
869
                    ],
870
                ],
871
            ];
872
        } elseif (null !== $this->_user) {
873
            $search['owner'] = $this->_user->getId();
874
        }
875
876
        if ($this->filter !== null && $this->_user !== null) {
877
            $include = isset($search['deleted']) ? ['deleted' => $search['deleted']] : [];
878
            $stored_filter = ['$and' => [
879
                array_merge(
880
                    $include,
881
                    json_decode($this->filter, true),
882
                    $filter
883
                ),
884
                ['$or' => [
885
                    ['owner' => $this->_user->getId()],
886
                    ['shared' => ['$in' => $this->_user->getShares()]],
887
                ]],
888
            ]];
889
890
            $search = ['$or' => [
891
                $search,
892
                $stored_filter,
893
            ]];
894
        }
895
896
        return $search;
897
    }
898
899
    /**
900
     * Completely remove node.
901
     */
902
    protected function _forceDelete(?string $recursion = null, bool $recursion_first = true): bool
903
    {
904
        if (!$this->isReference() && !$this->isMounted()) {
905
            $this->doRecursiveAction(function ($node) use ($recursion) {
906
                $node->delete(true, $recursion, false);
907
            }, NodeInterface::DELETED_INCLUDE);
908
        }
909
910
        try {
911
            $this->_parent->getStorage()->forceDeleteCollection($this);
912
            $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...
913
914
            if ($this->isShared()) {
915
                $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...
916
            }
917
918
            $this->_logger->info('force removed collection ['.$this->_id.']', [
919
                'category' => get_class($this),
920
            ]);
921
922
            $this->_hook->run(
923
                'postDeleteCollection',
924
                [$this, true, $recursion, $recursion_first]
925
            );
926
        } catch (\Exception $e) {
927
            $this->_logger->error('failed force remove collection ['.$this->_id.']', [
928
                'category' => get_class($this),
929
                'exception' => $e,
930
            ]);
931
932
            throw $e;
933
        }
934
935
        return true;
936
    }
937
}
938