Passed
Push — master ( a9f547...49cca7 )
by Raffael
04:18
created

Collection   F

Complexity

Total Complexity 93

Size/Duplication

Total Lines 864
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 93
lcom 1
dl 0
loc 864
ccs 0
cts 365
cp 0
rs 3.9999
c 0
b 0
f 0
cbo 11

26 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 2
C copyTo() 0 50 8
A getAcl() 0 12 3
A getShareName() 0 8 2
A getAttributes() 0 23 1
A setFilter() 0 6 1
A get() 0 4 1
A getChildNodes() 0 6 1
A getChildren() 0 4 1
A isCustomFilter() 0 4 1
A getSize() 0 4 1
A getRealId() 0 8 3
A getQuotaInfo() 0 9 1
A getChild() 0 19 2
C delete() 0 48 7
B childExists() 0 32 5
B share() 0 57 5
B unshare() 0 40 3
C getChildrenRecursive() 0 28 8
C addDirectory() 0 82 10
D addFile() 0 101 11
A createFile() 0 4 1
A createDirectory() 0 4 1
A doRecursiveAction() 0 10 2
B getChildrenFilter() 0 53 8
B _forceDelete() 0 34 4

How to fix   Complexity   

Complex Class

Complex classes like Collection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Collection, and based on these observations, apply Extract Interface, too.

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\Hook;
19
use Balloon\Server\User;
20
use Generator;
21
use MongoDB\BSON\ObjectId;
22
use MongoDB\BSON\Regex;
23
use MongoDB\BSON\UTCDateTime;
24
use Psr\Log\LoggerInterface;
25
use Sabre\DAV\IQuota;
26
27
class Collection extends AbstractNode implements IQuota
28
{
29
    /**
30
     * Root folder.
31
     */
32
    const ROOT_FOLDER = '/';
33
34
    /**
35
     * Share acl.
36
     *
37
     * @var array
38
     */
39
    protected $acl = [];
40
41
    /**
42
     * Share name.
43
     *
44
     * @var string
45
     */
46
    protected $share_name;
47
48
    /**
49
     * filter.
50
     *
51
     * @param string
52
     */
53
    protected $filter;
54
55
    /**
56
     * Initialize.
57
     */
58
    public function __construct(array $attributes, Filesystem $fs, LoggerInterface $logger, Hook $hook, Acl $acl)
59
    {
60
        $this->_fs = $fs;
61
        $this->_server = $fs->getServer();
62
        $this->_db = $fs->getDatabase();
63
        $this->_user = $fs->getUser();
64
        $this->_logger = $logger;
65
        $this->_hook = $hook;
66
        $this->_acl = $acl;
67
68
        foreach ($attributes as $attr => $value) {
69
            $this->{$attr} = $value;
70
        }
71
72
        $this->mime = 'inode/directory';
73
        $this->raw_attributes = $attributes;
74
    }
75
76
    /**
77
     * Copy node with children.
78
     *
79
     * @param Collection $parent
80
     * @param string     $recursion
81
     */
82
    public function copyTo(self $parent, int $conflict = NodeInterface::CONFLICT_NOACTION, ?string $recursion = null, bool $recursion_first = true): NodeInterface
83
    {
84
        if (null === $recursion) {
85
            $recursion_first = true;
86
            $recursion = uniqid();
87
        } else {
88
            $recursion_first = false;
89
        }
90
91
        $this->_hook->run(
92
            'preCopyCollection',
93
            [$this, $parent, &$conflict, &$recursion, &$recursion_first]
94
        );
95
96
        if (NodeInterface::CONFLICT_RENAME === $conflict && $parent->childExists($this->name)) {
97
            $name = $this->getDuplicateName();
98
        } else {
99
            $name = $this->name;
100
        }
101
102
        if ($this->_id === $parent->getId()) {
103
            throw new Exception\Conflict(
104
                'can not copy node into itself',
105
                Exception\Conflict::CANT_COPY_INTO_ITSELF
106
            );
107
        }
108
109
        if (NodeInterface::CONFLICT_MERGE === $conflict && $parent->childExists($this->name)) {
110
            $new_parent = $parent->getChild($this->name);
111
        } else {
112
            $new_parent = $parent->addDirectory($name, [
113
                'created' => $this->created,
114
                'changed' => $this->changed,
115
                'deleted' => $this->deleted,
116
                'filter' => $this->filter,
117
                'meta' => $this->meta,
118
            ], NodeInterface::CONFLICT_NOACTION, true);
119
        }
120
121
        foreach ($this->getChildNodes(NodeInterface::DELETED_INCLUDE) as $child) {
122
            $child->copyTo($new_parent, $conflict, $recursion, false);
123
        }
124
125
        $this->_hook->run(
126
            'postCopyCollection',
127
            [$this, $parent, $new_parent, $conflict, $recursion, $recursion_first]
128
        );
129
130
        return $new_parent;
131
    }
132
133
    /**
134
     * Get share.
135
     */
136
    public function getAcl(): array
137
    {
138
        if ($this->isReference()) {
139
            $acl = $this->_fs->findRawNode($this->getShareId())['acl'];
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...
140
        } elseif ($this->isShare()) {
141
            $acl = $this->acl;
142
        } else {
143
            return [];
144
        }
145
146
        return $this->_acl->resolveAclTable($this->_server, $acl);
147
    }
148
149
    /**
150
     * Get Share name.
151
     */
152
    public function getShareName(): string
153
    {
154
        if ($this->isShare()) {
155
            return $this->share_name;
156
        }
157
158
        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...
159
    }
160
161
    /**
162
     * Get Attributes.
163
     */
164
    public function getAttributes(): array
165
    {
166
        return [
167
            '_id' => $this->_id,
168
            'name' => $this->name,
169
            'shared' => $this->shared,
170
            'share_name' => $this->share_name,
171
            'acl' => $this->acl,
172
            'directory' => true,
173
            'reference' => $this->reference,
174
            'parent' => $this->parent,
175
            'app' => $this->app,
176
            'owner' => $this->owner,
177
            'meta' => $this->meta,
178
            'mime' => $this->mime,
179
            'filter' => $this->filter,
180
            'deleted' => $this->deleted,
181
            'changed' => $this->changed,
182
            'created' => $this->created,
183
            'destroy' => $this->destroy,
184
            'readonly' => $this->readonly,
185
        ];
186
    }
187
188
    /**
189
     * Set collection filter.
190
     *
191
     * @param string $filter
192
     */
193
    public function setFilter(?array $filter = null): bool
194
    {
195
        $this->filter = json_encode($filter);
196
197
        return $this->save('filter');
198
    }
199
200
    /**
201
     * Get collection.
202
     */
203
    public function get(): void
204
    {
205
        $this->getZip();
206
    }
207
208
    /**
209
     * Fetch children items of this collection.
210
     *
211
     * Deleted:
212
     *  0 - Exclude deleted
213
     *  1 - Only deleted
214
     *  2 - Include deleted
215
     *
216
     * @param int $offset
217
     * @param int $limit
218
     */
219
    public function getChildNodes(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = [], ?int $offset = null, ?int $limit = null): Generator
220
    {
221
        $filter = $this->getChildrenFilter($deleted, $filter);
222
223
        return $this->_fs->findNodesByFilter($filter, $offset, $limit);
224
    }
225
226
    /**
227
     * Fetch children items of this collection (as array).
228
     *
229
     * Deleted:
230
     *  0 - Exclude deleted
231
     *  1 - Only deleted
232
     *  2 - Include deleted
233
     */
234
    public function getChildren(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): array
235
    {
236
        return iterator_to_array($this->getChildNodes($deleted, $filter));
237
    }
238
239
    /**
240
     * Is custom filter node.
241
     */
242
    public function isCustomFilter(): bool
243
    {
244
        return !empty($this->filter);
245
    }
246
247
    /**
248
     * Get number of children.
249
     */
250
    public function getSize(): int
251
    {
252
        return $this->_db->storage->count($this->getChildrenFilter());
253
    }
254
255
    /**
256
     * Get real id (reference).
257
     *
258
     * @return ObjectId
259
     */
260
    public function getRealId(): ?ObjectId
261
    {
262
        if (true === $this->shared && $this->isReference()) {
263
            return $this->reference;
264
        }
265
266
        return $this->_id;
267
    }
268
269
    /**
270
     * Get user quota information.
271
     */
272
    public function getQuotaInfo(): array
273
    {
274
        $quota = $this->_user->getQuotaUsage();
275
276
        return [
277
            $quota['used'],
278
            $quota['available'],
279
        ];
280
    }
281
282
    /**
283
     * Fetch children items of this collection.
284
     */
285
    public function getChild($name, int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): NodeInterface
286
    {
287
        $filter = $this->getChildrenFilter($deleted, $filter);
288
        $filter['name'] = new Regex('^'.preg_quote($name).'$', 'i');
289
        $node = $this->_db->storage->findOne($filter);
290
291
        if (null === $node) {
292
            throw new Exception\NotFound(
293
                'node called '.$name.' does not exists here',
294
                Exception\NotFound::NODE_NOT_FOUND
295
            );
296
        }
297
298
        $this->_logger->debug('loaded node ['.$node['_id'].' from parent node ['.$this->getRealId().']', [
299
            'category' => get_class($this),
300
        ]);
301
302
        return $this->_fs->initNode($node);
303
    }
304
305
    /**
306
     * Delete node.
307
     *
308
     * Actually the node will not be deleted (Just set a delete flag), set $force=true to
309
     * delete finally
310
     *
311
     * @param string $recursion Identifier to identify a recursive action
312
     */
313
    public function delete(bool $force = false, ?string $recursion = null, bool $recursion_first = true): bool
314
    {
315
        if (!$this->isReference() && !$this->_acl->isAllowed($this, 'w')) {
316
            throw new ForbiddenException(
317
                'not allowed to delete node '.$this->name,
318
                ForbiddenException::NOT_ALLOWED_TO_DELETE
319
            );
320
        }
321
322
        if (null === $recursion) {
323
            $recursion_first = true;
324
            $recursion = uniqid();
325
        } else {
326
            $recursion_first = false;
327
        }
328
329
        $this->_hook->run(
330
            'preDeleteCollection',
331
            [$this, &$force, &$recursion, &$recursion_first]
332
        );
333
334
        if (true === $force) {
335
            return $this->_forceDelete($recursion, $recursion_first);
336
        }
337
338
        $this->deleted = new UTCDateTime();
339
340
        if (!$this->isReference()) {
341
            $this->doRecursiveAction(function ($node) use ($recursion) {
342
                $node->delete(false, $recursion, false);
343
            }, NodeInterface::DELETED_EXCLUDE);
344
        }
345
346
        if (null !== $this->_id) {
347
            $result = $this->save([
348
                'deleted',
349
            ], [], $recursion, false);
350
        } else {
351
            $result = true;
352
        }
353
354
        $this->_hook->run(
355
            'postDeleteCollection',
356
            [$this, $force, $recursion, $recursion_first]
357
        );
358
359
        return $result;
360
    }
361
362
    /**
363
     * Check if this collection has child named $name.
364
     *
365
     * deleted:
366
     *
367
     *  0 - Exclude deleted
368
     *  1 - Only deleted
369
     *  2 - Include deleted
370
     *
371
     * @param string $name
372
     * @param int    $deleted
373
     */
374
    public function childExists($name, $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): bool
375
    {
376
        $find = [
377
            'parent' => $this->getRealId(),
378
            'name' => new Regex('^'.preg_quote($name).'$', 'i'),
379
        ];
380
381
        if (null !== $this->_user) {
382
            $find['owner'] = $this->_user->getId();
383
        }
384
385
        switch ($deleted) {
386
            case NodeInterface::DELETED_EXCLUDE:
387
                $find['deleted'] = false;
388
389
                break;
390
            case NodeInterface::DELETED_ONLY:
391
                $find['deleted'] = ['$type' => 9];
392
393
                break;
394
        }
395
396
        $find = array_merge($filter, $find);
397
398
        if ($this->isSpecial()) {
399
            unset($find['owner']);
400
        }
401
402
        $node = $this->_db->storage->findOne($find);
403
404
        return (bool) $node;
405
    }
406
407
    /**
408
     * Share collection.
409
     */
410
    public function share(array $acl, string $name): bool
411
    {
412
        if ($this->isShareMember()) {
413
            throw new Exception('a sub node of a share can not be shared');
414
        }
415
416
        if (!$this->_acl->isAllowed($this, 'm')) {
417
            throw new ForbiddenException(
418
                'not allowed to share node',
419
                ForbiddenException::NOT_ALLOWED_TO_SHARE
420
            );
421
        }
422
423
        $this->_acl->validateAcl($this->_server, $acl);
424
425
        $action = [
426
            '$set' => [
427
                'shared' => $this->getRealId(),
428
            ],
429
        ];
430
431
        $toset = $this->getChildrenRecursive($this->getRealId(), $shares);
432
433
        if (!empty($shares)) {
434
            throw new Exception('child folder contains a shared folder');
435
        }
436
437
        $this->_db->storage->updateMany([
438
            '_id' => [
439
                '$in' => $toset,
440
            ],
441
        ], $action);
442
443
        $this->_db->delta->updateMany([
444
            '_id' => [
445
                '$in' => $toset,
446
            ],
447
        ], $action);
448
449
        if ($this->getRealId() === $this->_id) {
450
            $this->acl = $acl;
451
            $this->shared = true;
452
            $this->share_name = $name;
453
            $this->save(['acl', 'shared', 'share_name']);
454
        } else {
455
            $this->_db->storage->updateOne([
456
                '_id' => $this->getRealId(),
457
            ], [
458
                '$set' => [
459
                    'share_name' => $name,
460
                    'acl' => $acl,
461
                ],
462
            ]);
463
        }
464
465
        return true;
466
    }
467
468
    /**
469
     * Unshare collection.
470
     */
471
    public function unshare(): bool
472
    {
473
        if (!$this->_acl->isAllowed($this, 'm')) {
474
            throw new ForbiddenException(
475
                'not allowed to share node',
476
                ForbiddenException::NOT_ALLOWED_TO_SHARE
477
            );
478
        }
479
480
        if (true !== $this->shared) {
481
            throw new Exception\Conflict(
482
                'Can not unshare a none shared collection',
483
                Exception\Conflict::NOT_SHARED
484
            );
485
        }
486
487
        $this->shared = false;
488
        $this->share_name = null;
489
        $this->acl = [];
490
        $action = [
491
            '$unset' => [
492
                'shared' => $this->_id,
493
            ],
494
            '$set' => [
495
                'owner' => $this->_user->getId(),
496
            ],
497
        ];
498
499
        $toset = $this->getChildrenRecursive($this->getRealId(), $shares);
500
501
        $this->_db->storage->updateMany([
502
            '_id' => [
503
                '$in' => $toset,
504
            ],
505
        ], $action);
506
507
        $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...
508
509
        return true;
510
    }
511
512
    /**
513
     * Get children.
514
     *
515
     * @param ObjectId $id
516
     * @param array    $shares
517
     */
518
    public function getChildrenRecursive(?ObjectId $id = null, ?array &$shares = []): array
519
    {
520
        $list = [];
521
        $result = $this->_db->storage->find([
522
            'parent' => $id,
523
        ], [
524
            '_id' => 1,
525
            'directory' => 1,
526
            'reference' => 1,
527
            'shared' => 1,
528
        ]);
529
530
        foreach ($result as $node) {
531
            $list[] = $node['_id'];
532
533
            if ($node['directory'] === true) {
534
                if (isset($node['reference']) || isset($node['shared']) && true === $node['shared']) {
535
                    $shares[] = $node['_id'];
536
                }
537
538
                if (true === $node['directory'] && !isset($node['reference'])) {
539
                    $list = array_merge($list, $this->getChildrenRecursive($node['_id'], $shares));
540
                }
541
            }
542
        }
543
544
        return $list;
545
    }
546
547
    /**
548
     * Create new directory.
549
     *
550
     * @param string $name
551
     * @param arracy $attributes
552
     *
553
     * @return Collection
554
     */
555
    public function addDirectory($name, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): self
556
    {
557
        if (!$this->_acl->isAllowed($this, 'w')) {
558
            throw new ForbiddenException(
559
                'not allowed to create new node here',
560
                ForbiddenException::NOT_ALLOWED_TO_CREATE
561
            );
562
        }
563
564
        $this->_hook->run('preCreateCollection', [$this, &$name, &$attributes, &$clone]);
565
566
        if ($this->readonly) {
567
            throw new Exception\Conflict(
568
                'node is set as readonly, it is not possible to add new sub nodes',
569
                Exception\Conflict::READONLY
570
            );
571
        }
572
573
        $name = $this->checkName($name);
574
575
        if ($this->childExists($name)) {
576
            if (NodeInterface::CONFLICT_NOACTION === $conflict) {
577
                throw new Exception\Conflict(
578
                    'a node called '.$name.' does already exists in this collection',
579
                    Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
580
                );
581
            }
582
            if (NodeInterface::CONFLICT_RENAME === $conflict) {
583
                $name = $this->getDuplicateName($name);
584
            }
585
        }
586
587
        if ($this->isDeleted()) {
588
            throw new Exception\Conflict(
589
                'could not add node '.$name.' into a deleted parent collection',
590
                Exception\Conflict::DELETED_PARENT
591
            );
592
        }
593
594
        try {
595
            $meta = [
596
                'name' => $name,
597
                'deleted' => false,
598
                'parent' => $this->getRealId(),
599
                'directory' => true,
600
                'created' => new UTCDateTime(),
601
                'changed' => new UTCDateTime(),
602
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
603
            ];
604
605
            if (null !== $this->_user) {
606
                $meta['owner'] = $this->_user->getId();
607
            }
608
609
            $save = array_merge($meta, $attributes);
610
611
            $result = $this->_db->storage->insertOne($save, [
612
                '$isolated' => true,
613
            ]);
614
615
            $save['_id'] = $result->getInsertedId();
616
617
            $this->_logger->info('added new collection ['.$save['_id'].'] under parent ['.$this->_id.']', [
618
                'category' => get_class($this),
619
            ]);
620
621
            $this->changed = $save['changed'];
622
            $this->save('changed');
623
624
            $new = $this->_fs->initNode($save);
625
            $this->_hook->run('postCreateCollection', [$this, $new, $clone]);
626
627
            return $new;
628
        } catch (\Exception $e) {
629
            $this->_logger->error('failed create new collection under parent ['.$this->_id.']', [
630
                'category' => get_class($this),
631
                'exception' => $e,
632
            ]);
633
634
            throw $e;
635
        }
636
    }
637
638
    /**
639
     * Create new file as a child from this collection.
640
     *
641
     * @param string     $name
642
     * @param null|mixed $data
643
     */
644
    public function addFile($name, $data = null, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): File
645
    {
646
        if (!$this->_acl->isAllowed($this, 'w')) {
647
            throw new ForbiddenException(
648
                'not allowed to create new node here',
649
                ForbiddenException::NOT_ALLOWED_TO_CREATE
650
            );
651
        }
652
653
        $this->_hook->run('preCreateFile', [$this, &$name, &$attributes, &$clone]);
654
655
        if ($this->readonly) {
656
            throw new Exception\Conflict(
657
                'node is set as readonly, it is not possible to add new sub nodes',
658
                Exception\Conflict::READONLY
659
            );
660
        }
661
662
        $name = $this->checkName($name);
663
664
        if ($this->childExists($name)) {
665
            if (NodeInterface::CONFLICT_NOACTION === $conflict) {
666
                throw new Exception\Conflict(
667
                    'a node called '.$name.' does already exists in this collection',
668
                    Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
669
                );
670
            }
671
            if (NodeInterface::CONFLICT_RENAME === $conflict) {
672
                $name = $this->getDuplicateName($name, File::class);
673
            }
674
        }
675
676
        if ($this->isDeleted()) {
677
            throw new Exception\Conflict(
678
                'could not add node '.$name.' into a deleted parent collection',
679
                Exception\Conflict::DELETED_PARENT
680
            );
681
        }
682
683
        try {
684
            $meta = [
685
                'name' => $name,
686
                'deleted' => false,
687
                'parent' => $this->getRealId(),
688
                'directory' => false,
689
                'hash' => null,
690
                'created' => new UTCDateTime(),
691
                'changed' => new UTCDateTime(),
692
                'version' => 0,
693
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
694
                'storage_adapter' => $this->storage_adapter,
695
            ];
696
697
            if (null !== $this->_user) {
698
                $meta['owner'] = $this->_user->getId();
699
            }
700
701
            $save = array_merge($meta, $attributes);
702
703
            $result = $this->_db->storage->insertOne($save, [
704
                '$isolated' => true,
705
            ]);
706
707
            $save['_id'] = $result->getInsertedId();
708
709
            $this->_logger->info('added new file ['.$save['_id'].'] under parent ['.$this->_id.']', [
710
                'category' => get_class($this),
711
            ]);
712
713
            $this->changed = $save['changed'];
714
            $this->save('changed');
715
716
            $file = $this->_fs->initNode($save);
717
718
            try {
719
                $file->put($data, true, $attributes);
720
            } catch (Exception\InsufficientStorage $e) {
721
                $this->_logger->warning('failed add new file under parent ['.$this->_id.'], cause user quota is full', [
722
                    'category' => get_class($this),
723
                    'exception' => $e,
724
                ]);
725
726
                $this->_db->storage->deleteOne([
727
                    '_id' => $save['_id'],
728
                ]);
729
730
                throw $e;
731
            }
732
733
            $this->_hook->run('postCreateFile', [$this, $file, $clone]);
734
735
            return $file;
736
        } catch (\Exception $e) {
737
            $this->_logger->error('failed add new file under parent ['.$this->_id.']', [
738
                'category' => get_class($this),
739
                'exception' => $e,
740
            ]);
741
742
            throw $e;
743
        }
744
    }
745
746
    /**
747
     * Create new file wrapper
748
     * (Sabe\DAV compatible method, elsewhere use addFile().
749
     *
750
     * Sabre\DAV requires that createFile() returns the ETag instead the newly created file instance
751
     *
752
     * @param string $name
753
     * @param string $data
754
     *
755
     * @return File
756
     */
757
    public function createFile($name, $data = null): String
758
    {
759
        return $this->addFile($name, $data)->getETag();
760
    }
761
762
    /**
763
     * Create new directory wrapper
764
     * (Sabe\DAV compatible method, elsewhere use addDirectory().
765
     *
766
     * Sabre\DAV requires that createDirectory() returns void
767
     *
768
     * @param string $name
769
     */
770
    public function createDirectory($name): void
771
    {
772
        $this->addDirectory($name);
773
    }
774
775
    /**
776
     * Do recursive Action.
777
     */
778
    public function doRecursiveAction(callable $callable, int $deleted = NodeInterface::DELETED_EXCLUDE): bool
779
    {
780
        $children = $this->getChildNodes($deleted, []);
781
782
        foreach ($children as $child) {
783
            $callable($child);
784
        }
785
786
        return true;
787
    }
788
789
    /**
790
     * Get children query filter.
791
     *
792
     * Deleted:
793
     *  0 - Exclude deleted
794
     *  1 - Only deleted
795
     *  2 - Include deleted
796
     */
797
    protected function getChildrenFilter(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): array
798
    {
799
        $search = [
800
            'parent' => $this->getRealId(),
801
        ];
802
803
        if (NodeInterface::DELETED_EXCLUDE === $deleted) {
804
            $search['deleted'] = false;
805
        } elseif (NodeInterface::DELETED_ONLY === $deleted) {
806
            $search['deleted'] = ['$type' => 9];
807
        }
808
809
        $search = array_merge($filter, $search);
810
811
        if ($this->shared) {
812
            $search = [
813
                '$and' => [
814
                    $search,
815
                    [
816
                        '$or' => [
817
                            ['shared' => $this->reference],
818
                            ['shared' => $this->shared],
819
                            ['shared' => $this->_id],
820
                        ],
821
                    ],
822
                ],
823
            ];
824
        } elseif (null !== $this->_user) {
825
            $search['owner'] = $this->_user->getId();
826
        }
827
828
        if ($this->filter !== null && $this->_user !== null) {
829
            $include = isset($search['deleted']) ? ['deleted' => $search['deleted']] : [];
830
            $stored_filter = ['$and' => [
831
                array_merge(
832
                    $include,
833
                    json_decode($this->filter, true),
834
                    $filter
835
                ),
836
                ['$or' => [
837
                    ['owner' => $this->_user->getId()],
838
                    ['shared' => ['$in' => $this->_user->getShares()]],
839
                ]],
840
            ]];
841
842
            $search = ['$or' => [
843
                $search,
844
                $stored_filter,
845
            ]];
846
        }
847
848
        return $search;
849
    }
850
851
    /**
852
     * Completely remove node.
853
     *
854
     * @param string $recursion Identifier to identify a recursive action
855
     */
856
    protected function _forceDelete(?string $recursion = null, bool $recursion_first = true): bool
857
    {
858
        if (!$this->isReference()) {
859
            $this->doRecursiveAction(function ($node) use ($recursion) {
860
                $node->delete(true, $recursion, false);
861
            }, NodeInterface::DELETED_INCLUDE);
862
        }
863
864
        try {
865
            $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...
866
867
            if ($this->isShared()) {
868
                $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...
869
            }
870
871
            $this->_logger->info('force removed collection ['.$this->_id.']', [
872
                'category' => get_class($this),
873
            ]);
874
875
            $this->_hook->run(
876
                'postDeleteCollection',
877
                [$this, true, $recursion, $recursion_first]
878
            );
879
        } catch (\Exception $e) {
880
            $this->_logger->error('failed force remove collection ['.$this->_id.']', [
881
                'category' => get_class($this),
882
                'exception' => $e,
883
            ]);
884
885
            throw $e;
886
        }
887
888
        return true;
889
    }
890
}
891