Completed
Branch master (3adcdb)
by Raffael
08:09 queued 04:17
created

Collection::share()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 57
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 57
rs 8.7433
c 0
b 0
f 0
cc 5
eloc 34
nc 5
nop 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-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
     * @param array      $attributes
59
     * @param Filesystem $fs
60
     */
61
    public function __construct(array $attributes, Filesystem $fs, LoggerInterface $logger, Hook $hook, Acl $acl)
62
    {
63
        $this->_fs = $fs;
64
        $this->_server = $fs->getServer();
65
        $this->_db = $fs->getDatabase();
66
        $this->_user = $fs->getUser();
67
        $this->_logger = $logger;
68
        $this->_hook = $hook;
69
        $this->_acl = $acl;
70
71
        foreach ($attributes as $attr => $value) {
72
            $this->{$attr} = $value;
73
        }
74
75
        $this->mime = 'inode/directory';
76
        $this->raw_attributes = $attributes;
77
    }
78
79
    /**
80
     * Copy node with children.
81
     *
82
     * @param Collection $parent
83
     * @param int        $conflict
84
     * @param string     $recursion
85
     * @param bool       $recursion_first
86
     *
87
     * @return NodeInterface
88
     */
89
    public function copyTo(self $parent, int $conflict = NodeInterface::CONFLICT_NOACTION, ?string $recursion = null, bool $recursion_first = true): NodeInterface
90
    {
91
        if (null === $recursion) {
92
            $recursion_first = true;
93
            $recursion = uniqid();
94
        } else {
95
            $recursion_first = false;
96
        }
97
98
        $this->_hook->run(
99
            'preCopyCollection',
100
            [$this, $parent, &$conflict, &$recursion, &$recursion_first]
101
        );
102
103
        if (NodeInterface::CONFLICT_RENAME === $conflict && $parent->childExists($this->name)) {
104
            $name = $this->getDuplicateName();
105
        } else {
106
            $name = $this->name;
107
        }
108
109
        if ($this->_id === $parent->getId()) {
110
            throw new Exception\Conflict(
111
                'can not copy node into itself',
112
                Exception\Conflict::CANT_COPY_INTO_ITSELF
113
            );
114
        }
115
116
        if (NodeInterface::CONFLICT_MERGE === $conflict && $parent->childExists($this->name)) {
117
            $new_parent = $parent->getChild($this->name);
118
        } else {
119
            $new_parent = $parent->addDirectory($name, [
120
                'created' => $this->created,
121
                'changed' => $this->changed,
122
                'deleted' => $this->deleted,
123
                'filter' => $this->filter,
124
                'meta' => $this->meta,
125
            ], NodeInterface::CONFLICT_NOACTION, true);
126
        }
127
128
        foreach ($this->getChildNodes(NodeInterface::DELETED_INCLUDE) as $child) {
129
            $child->copyTo($new_parent, $conflict, $recursion, false);
130
        }
131
132
        $this->_hook->run(
133
            'postCopyCollection',
134
            [$this, $parent, $new_parent, $conflict, $recursion, $recursion_first]
135
        );
136
137
        return $new_parent;
138
    }
139
140
    /**
141
     * Get share.
142
     *
143
     * @return array
144
     */
145
    public function getAcl(): array
146
    {
147
        if ($this->isReference()) {
148
            $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...
149
        } elseif ($this->isShare()) {
150
            $acl = $this->acl;
151
        } else {
152
            return [];
153
        }
154
155
        return $this->_acl->resolveAclTable($this->_server, $acl);
156
    }
157
158
    /**
159
     * Get Share name.
160
     *
161
     * @return string
162
     */
163
    public function getShareName(): string
164
    {
165
        if ($this->isShare()) {
166
            return $this->share_name;
167
        }
168
169
        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...
170
    }
171
172
    /**
173
     * Get Attributes.
174
     *
175
     * @return array
176
     */
177
    public function getAttributes(): array
178
    {
179
        return [
180
            '_id' => $this->_id,
181
            'name' => $this->name,
182
            'shared' => $this->shared,
183
            'share_name' => $this->share_name,
184
            'acl' => $this->acl,
185
            'directory' => true,
186
            'reference' => $this->reference,
187
            'parent' => $this->parent,
188
            'app' => $this->app,
189
            'owner' => $this->owner,
190
            'meta' => $this->meta,
191
            'mime' => $this->mime,
192
            'filter' => $this->filter,
193
            'deleted' => $this->deleted,
194
            'changed' => $this->changed,
195
            'created' => $this->created,
196
            'destroy' => $this->destroy,
197
            'readonly' => $this->readonly,
198
        ];
199
    }
200
201
    /**
202
     * Set collection filter.
203
     *
204
     * @param string $filter
205
     *
206
     * @return bool
207
     */
208
    public function setFilter(?array $filter = null): bool
209
    {
210
        $this->filter = json_encode($filter);
211
212
        return $this->save('filter');
213
    }
214
215
    /**
216
     * Get collection.
217
     */
218
    public function get(): void
219
    {
220
        $this->getZip();
221
    }
222
223
    /**
224
     * Fetch children items of this collection.
225
     *
226
     * Deleted:
227
     *  0 - Exclude deleted
228
     *  1 - Only deleted
229
     *  2 - Include deleted
230
     *
231
     * @param int   $deleted
232
     * @param array $filter
233
     * @param int   $offset
234
     * @param int   $limit
235
     *
236
     * @return Generator
237
     */
238
    public function getChildNodes(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = [], ?int $offset = null, ?int $limit = null): Generator
239
    {
240
        $filter = $this->getChildrenFilter($deleted, $filter);
241
242
        return $this->_fs->findNodesByFilter($filter, $offset, $limit);
243
    }
244
245
    /**
246
     * Fetch children items of this collection (as array).
247
     *
248
     * Deleted:
249
     *  0 - Exclude deleted
250
     *  1 - Only deleted
251
     *  2 - Include deleted
252
     *
253
     * @param int   $deleted
254
     * @param array $filter
255
     *
256
     * @return array
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
     * @return bool
267
     */
268
    public function isCustomFilter(): bool
269
    {
270
        return !empty($this->filter);
271
    }
272
273
    /**
274
     * Get number of children.
275
     *
276
     * @return int
277
     */
278
    public function getSize(): int
279
    {
280
        return $this->_db->storage->count($this->getChildrenFilter());
281
    }
282
283
    /**
284
     * Get real id (reference).
285
     *
286
     * @return ObjectId
287
     */
288
    public function getRealId(): ?ObjectId
289
    {
290
        if (true === $this->shared && $this->isReference()) {
291
            return $this->reference;
292
        }
293
294
        return $this->_id;
295
    }
296
297
    /**
298
     * Get user quota information.
299
     *
300
     * @return array
301
     */
302
    public function getQuotaInfo(): array
303
    {
304
        $quota = $this->_user->getQuotaUsage();
305
306
        return [
307
            $quota['used'],
308
            $quota['available'],
309
        ];
310
    }
311
312
    /**
313
     * Fetch children items of this collection.
314
     *
315
     * @param string $node
0 ignored issues
show
Bug introduced by
There is no parameter named $node. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
316
     * @param int    $deleted
317
     * @param array  $filter
318
     * @param mixed  $name
319
     *
320
     * @return NodeInterface
321
     */
322
    public function getChild($name, int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): NodeInterface
323
    {
324
        $filter = $this->getChildrenFilter($deleted, $filter);
325
        $filter['name'] = new Regex('^'.preg_quote($name).'$', 'i');
326
        $node = $this->_db->storage->findOne($filter);
327
328
        if (null === $node) {
329
            throw new Exception\NotFound(
330
                'node called '.$name.' does not exists here',
331
                Exception\NotFound::NODE_NOT_FOUND
332
            );
333
        }
334
335
        $this->_logger->debug('loaded node ['.$node['_id'].' from parent node ['.$this->getRealId().']', [
336
            'category' => get_class($this),
337
        ]);
338
339
        return $this->_fs->initNode($node);
340
    }
341
342
    /**
343
     * Delete node.
344
     *
345
     * Actually the node will not be deleted (Just set a delete flag), set $force=true to
346
     * delete finally
347
     *
348
     * @param bool   $force
349
     * @param string $recursion       Identifier to identify a recursive action
350
     * @param bool   $recursion_first
351
     *
352
     * @return bool
353
     */
354
    public function delete(bool $force = false, ?string $recursion = null, bool $recursion_first = true): bool
355
    {
356
        if (!$this->isReference() && !$this->_acl->isAllowed($this, 'w')) {
357
            throw new ForbiddenException(
358
                'not allowed to delete node '.$this->name,
359
                ForbiddenException::NOT_ALLOWED_TO_DELETE
360
            );
361
        }
362
363
        if (null === $recursion) {
364
            $recursion_first = true;
365
            $recursion = uniqid();
366
        } else {
367
            $recursion_first = false;
368
        }
369
370
        $this->_hook->run(
371
            'preDeleteCollection',
372
            [$this, &$force, &$recursion, &$recursion_first]
373
        );
374
375
        if ($this->readonly && null !== $this->_user) {
376
            throw new Exception\Conflict(
377
                'node is marked as readonly, it is not possible to delete it',
378
                Exception\Conflict::READONLY
379
            );
380
        }
381
382
        if (true === $force) {
383
            return $this->_forceDelete($recursion, $recursion_first);
384
        }
385
386
        $this->deleted = new UTCDateTime();
387
388
        if (!$this->isReference()) {
389
            $this->doRecursiveAction(function ($node) use ($recursion) {
390
                $node->delete(false, $recursion, false);
391
            }, NodeInterface::DELETED_EXCLUDE);
392
        }
393
394
        if (null !== $this->_id) {
395
            $result = $this->save([
396
                'deleted',
397
            ], [], $recursion, false);
398
        } else {
399
            $result = true;
400
        }
401
402
        $this->_hook->run(
403
            'postDeleteCollection',
404
            [$this, $force, $recursion, $recursion_first]
405
        );
406
407
        return $result;
408
    }
409
410
    /**
411
     * Check if this collection has child named $name.
412
     *
413
     * deleted:
414
     *
415
     *  0 - Exclude deleted
416
     *  1 - Only deleted
417
     *  2 - Include deleted
418
     *
419
     * @param string $name
420
     * @param int    $deleted
421
     * @param array  $filter
422
     *
423
     * @return bool
424
     */
425
    public function childExists($name, $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): bool
426
    {
427
        $find = [
428
            'parent' => $this->getRealId(),
429
            'name' => new Regex('^'.preg_quote($name).'$', 'i'),
430
        ];
431
432
        if (null !== $this->_user) {
433
            $find['owner'] = $this->_user->getId();
434
        }
435
436
        switch ($deleted) {
437
            case NodeInterface::DELETED_EXCLUDE:
438
                $find['deleted'] = false;
439
440
                break;
441
            case NodeInterface::DELETED_ONLY:
442
                $find['deleted'] = ['$type' => 9];
443
444
                break;
445
        }
446
447
        $find = array_merge($filter, $find);
448
449
        if ($this->isSpecial()) {
450
            unset($find['owner']);
451
        }
452
453
        $node = $this->_db->storage->findOne($find);
454
455
        return (bool) $node;
456
    }
457
458
    /**
459
     * Share collection.
460
     *
461
     * @param array  $acl
462
     * @param string $name
463
     *
464
     * @return bool
465
     */
466
    public function share(array $acl, string $name): bool
467
    {
468
        if ($this->isShareMember()) {
469
            throw new Exception('a sub node of a share can not be shared');
470
        }
471
472
        if (!$this->_acl->isAllowed($this, 'm')) {
473
            throw new ForbiddenException(
474
                'not allowed to share node',
475
                ForbiddenException::NOT_ALLOWED_TO_SHARE
476
            );
477
        }
478
479
        $this->_acl->validateAcl($this->_server, $acl);
480
481
        $action = [
482
            '$set' => [
483
                'shared' => $this->getRealId(),
484
            ],
485
        ];
486
487
        $toset = $this->getChildrenRecursive($this->getRealId(), $shares);
488
489
        if (!empty($shares)) {
490
            throw new Exception('child folder contains a shared folder');
491
        }
492
493
        $this->_db->storage->updateMany([
494
            '_id' => [
495
                '$in' => $toset,
496
            ],
497
        ], $action);
498
499
        $this->_db->delta->updateMany([
500
            '_id' => [
501
                '$in' => $toset,
502
            ],
503
        ], $action);
504
505
        if ($this->getRealId() === $this->_id) {
506
            $this->acl = $acl;
507
            $this->shared = true;
508
            $this->share_name = $name;
509
            $this->save(['acl', 'shared', 'share_name']);
510
        } else {
511
            $this->_db->storage->updateOne([
512
                '_id' => $this->getRealId(),
513
            ], [
514
                '$set' => [
515
                    'share_name' => $name,
516
                    'acl' => $acl,
517
                ],
518
            ]);
519
        }
520
521
        return true;
522
    }
523
524
    /**
525
     * Unshare collection.
526
     *
527
     * @return bool
528
     */
529
    public function unshare(): bool
530
    {
531
        if (!$this->_acl->isAllowed($this, 'm')) {
532
            throw new ForbiddenException(
533
                'not allowed to share node',
534
                ForbiddenException::NOT_ALLOWED_TO_SHARE
535
            );
536
        }
537
538
        if (true !== $this->shared) {
539
            throw new Exception\Conflict(
540
                'Can not unshare a none shared collection',
541
                Exception\Conflict::NOT_SHARED
542
            );
543
        }
544
545
        $this->shared = false;
546
        $this->share_name = null;
547
        $this->acl = [];
548
        $action = [
549
            '$unset' => [
550
                'shared' => $this->_id,
551
            ],
552
            '$set' => [
553
                'owner' => $this->_user->getId(),
554
            ],
555
        ];
556
557
        $toset = $this->getChildrenRecursive($this->getRealId(), $shares);
558
559
        $this->_db->storage->updateMany([
560
            '_id' => [
561
                '$in' => $toset,
562
            ],
563
        ], $action);
564
565
        $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...
566
567
        return true;
568
    }
569
570
    /**
571
     * Get children.
572
     *
573
     * @param ObjectId $id
574
     * @param array    $shares
575
     *
576
     * @return array
577
     */
578
    public function getChildrenRecursive(?ObjectId $id = null, ?array &$shares = []): array
579
    {
580
        $list = [];
581
        $result = $this->_db->storage->find([
582
            'parent' => $id,
583
        ], [
584
            '_id' => 1,
585
            'directory' => 1,
586
            'reference' => 1,
587
            'shared' => 1,
588
        ]);
589
590
        foreach ($result as $node) {
591
            $list[] = $node['_id'];
592
593
            if ($node['directory'] === true) {
594
                if (isset($node['reference']) || isset($node['shared']) && true === $node['shared']) {
595
                    $shares[] = $node['_id'];
596
                }
597
598
                if (true === $node['directory'] && !isset($node['reference'])) {
599
                    $list = array_merge($list, $this->getChildrenRecursive($node['_id'], $shares));
600
                }
601
            }
602
        }
603
604
        return $list;
605
    }
606
607
    /**
608
     * Create new directory.
609
     *
610
     * @param string $name
611
     * @param arracy $attributes
612
     * @param int    $conflict
613
     * @param bool   $clone
614
     *
615
     * @return Collection
616
     */
617
    public function addDirectory($name, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): self
618
    {
619
        if (!$this->_acl->isAllowed($this, 'w')) {
620
            throw new ForbiddenException(
621
                'not allowed to create new node here',
622
                ForbiddenException::NOT_ALLOWED_TO_CREATE
623
            );
624
        }
625
626
        $this->_hook->run('preCreateCollection', [$this, &$name, &$attributes, &$clone]);
627
628
        if ($this->readonly) {
629
            throw new Exception\Conflict(
630
                'node is set as readonly, it is not possible to add new sub nodes',
631
                Exception\Conflict::READONLY
632
            );
633
        }
634
635
        $name = $this->checkName($name);
636
637
        if ($this->childExists($name)) {
638
            if (NodeInterface::CONFLICT_NOACTION === $conflict) {
639
                throw new Exception\Conflict(
640
                    'a node called '.$name.' does already exists in this collection',
641
                    Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
642
                );
643
            }
644
            if (NodeInterface::CONFLICT_RENAME === $conflict) {
645
                $name = $this->getDuplicateName($name);
646
            }
647
        }
648
649
        if ($this->isDeleted()) {
650
            throw new Exception\Conflict(
651
                'could not add node '.$name.' into a deleted parent collection',
652
                Exception\Conflict::DELETED_PARENT
653
            );
654
        }
655
656
        try {
657
            $meta = [
658
                'name' => $name,
659
                'deleted' => false,
660
                'parent' => $this->getRealId(),
661
                'directory' => true,
662
                'created' => new UTCDateTime(),
663
                'changed' => new UTCDateTime(),
664
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
665
            ];
666
667
            if (null !== $this->_user) {
668
                $meta['owner'] = $this->_user->getId();
669
            }
670
671
            $save = array_merge($meta, $attributes);
672
673
            $result = $this->_db->storage->insertOne($save, [
674
                '$isolated' => true,
675
            ]);
676
677
            $save['_id'] = $result->getInsertedId();
678
679
            $this->_logger->info('added new collection ['.$save['_id'].'] under parent ['.$this->_id.']', [
680
                'category' => get_class($this),
681
            ]);
682
683
            $this->changed = $save['changed'];
684
            $this->save('changed');
685
686
            $new = $this->_fs->initNode($save);
687
            $this->_hook->run('postCreateCollection', [$this, $new, $clone]);
688
689
            return $new;
690
        } catch (\Exception $e) {
691
            $this->_logger->error('failed create new collection under parent ['.$this->_id.']', [
692
                'category' => get_class($this),
693
                'exception' => $e,
694
            ]);
695
696
            throw $e;
697
        }
698
    }
699
700
    /**
701
     * Create new file as a child from this collection.
702
     *
703
     * @param string $name
704
     * @param mixed  $data
705
     * @param array  $attributes
706
     * @param int    $conflict
707
     * @param bool   $clone
708
     *
709
     * @return File
710
     */
711
    public function addFile($name, $data = null, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): File
712
    {
713
        if (!$this->_acl->isAllowed($this, 'w')) {
714
            throw new ForbiddenException(
715
                'not allowed to create new node here',
716
                ForbiddenException::NOT_ALLOWED_TO_CREATE
717
            );
718
        }
719
720
        $this->_hook->run('preCreateFile', [$this, &$name, &$attributes, &$clone]);
721
722
        if ($this->readonly) {
723
            throw new Exception\Conflict(
724
                'node is set as readonly, it is not possible to add new sub nodes',
725
                Exception\Conflict::READONLY
726
            );
727
        }
728
729
        $name = $this->checkName($name);
730
731
        if ($this->childExists($name)) {
732
            if (NodeInterface::CONFLICT_NOACTION === $conflict) {
733
                throw new Exception\Conflict(
734
                    'a node called '.$name.' does already exists in this collection',
735
                    Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
736
                );
737
            }
738
            if (NodeInterface::CONFLICT_RENAME === $conflict) {
739
                $name = $this->getDuplicateName($name);
740
            }
741
        }
742
743
        if ($this->isDeleted()) {
744
            throw new Exception\Conflict(
745
                'could not add node '.$name.' into a deleted parent collection',
746
                Exception\Conflict::DELETED_PARENT
747
            );
748
        }
749
750
        try {
751
            $meta = [
752
                'name' => $name,
753
                'deleted' => false,
754
                'parent' => $this->getRealId(),
755
                'directory' => false,
756
                'hash' => null,
757
                'created' => new UTCDateTime(),
758
                'changed' => new UTCDateTime(),
759
                'version' => 0,
760
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
761
                'storage_adapter' => $this->storage_adapter,
762
            ];
763
764
            if (null !== $this->_user) {
765
                $meta['owner'] = $this->_user->getId();
766
            }
767
768
            $save = array_merge($meta, $attributes);
769
770
            $result = $this->_db->storage->insertOne($save, [
771
                '$isolated' => true,
772
            ]);
773
774
            $save['_id'] = $result->getInsertedId();
775
776
            $this->_logger->info('added new file ['.$save['_id'].'] under parent ['.$this->_id.']', [
777
                'category' => get_class($this),
778
            ]);
779
780
            $this->changed = $save['changed'];
781
            $this->save('changed');
782
783
            $file = $this->_fs->initNode($save);
784
785
            try {
786
                $file->put($data, true, $attributes);
787
            } catch (Exception\InsufficientStorage $e) {
788
                $this->_logger->warning('failed add new file under parent ['.$this->_id.'], cause user quota is full', [
789
                    'category' => get_class($this),
790
                    'exception' => $e,
791
                ]);
792
793
                $this->_db->storage->deleteOne([
794
                    '_id' => $save['_id'],
795
                ]);
796
797
                throw $e;
798
            }
799
800
            $this->_hook->run('postCreateFile', [$this, $file, $clone]);
801
802
            return $file;
803
        } catch (\Exception $e) {
804
            $this->_logger->error('failed add new file under parent ['.$this->_id.']', [
805
                'category' => get_class($this),
806
                'exception' => $e,
807
            ]);
808
809
            throw $e;
810
        }
811
    }
812
813
    /**
814
     * Create new file wrapper
815
     * (Sabe\DAV compatible method, elsewhere use addFile().
816
     *
817
     * Sabre\DAV requires that createFile() returns the ETag instead the newly created file instance
818
     *
819
     * @param string $name
820
     * @param string $data
821
     *
822
     * @return File
823
     */
824
    public function createFile($name, $data = null): String
825
    {
826
        return $this->addFile($name, $data)->getETag();
827
    }
828
829
    /**
830
     * Create new directory wrapper
831
     * (Sabe\DAV compatible method, elsewhere use addDirectory().
832
     *
833
     * Sabre\DAV requires that createDirectory() returns void
834
     *
835
     * @param string $name
836
     */
837
    public function createDirectory($name): void
838
    {
839
        $this->addDirectory($name);
840
    }
841
842
    /**
843
     * Do recursive Action.
844
     *
845
     * @param callable $callable
846
     * @param int      $deleted
847
     * @param bool     $ignore_exception
0 ignored issues
show
Bug introduced by
There is no parameter named $ignore_exception. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
848
     *
849
     * @return bool
850
     */
851
    public function doRecursiveAction(callable $callable, int $deleted = NodeInterface::DELETED_EXCLUDE): bool
852
    {
853
        $children = $this->getChildNodes($deleted, []);
854
855
        foreach ($children as $child) {
856
            $callable($child);
857
        }
858
859
        return true;
860
    }
861
862
    /**
863
     * Get children query filter.
864
     *
865
     * Deleted:
866
     *  0 - Exclude deleted
867
     *  1 - Only deleted
868
     *  2 - Include deleted
869
     *
870
     * @param int   $deleted
871
     * @param array $filter
872
     *
873
     * @return array
874
     */
875
    protected function getChildrenFilter(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): array
876
    {
877
        $search = [
878
            'parent' => $this->getRealId(),
879
        ];
880
881
        if (NodeInterface::DELETED_EXCLUDE === $deleted) {
882
            $search['deleted'] = false;
883
        } elseif (NodeInterface::DELETED_ONLY === $deleted) {
884
            $search['deleted'] = ['$type' => 9];
885
        }
886
887
        $search = array_merge($filter, $search);
888
889
        if ($this->shared) {
890
            $search = [
891
                '$and' => [
892
                    $search,
893
                    [
894
                        '$or' => [
895
                            ['shared' => $this->reference],
896
                            ['shared' => $this->shared],
897
                            ['shared' => $this->_id],
898
                        ],
899
                    ],
900
                ],
901
            ];
902
        } elseif (null !== $this->_user) {
903
            $search['owner'] = $this->_user->getId();
904
        }
905
906
        if ($this->filter !== null) {
907
            $stored_filter = ['$and' => [
908
                array_merge(
909
                    ['deleted' => $search['deleted']],
910
                    json_decode($this->filter),
911
                    $filter
912
                ),
913
                ['$or' => [
914
                    ['owner' => $this->_user->getId()],
915
                    ['shared' => ['$in' => $this->_user->getShares()]],
916
                ]],
917
            ]];
918
919
            $search = ['$or' => [
920
                $search,
921
                $stored_filter,
922
            ]];
923
        }
924
925
        return $search;
926
    }
927
928
    /**
929
     * Completely remove node.
930
     *
931
     * @param string $recursion       Identifier to identify a recursive action
932
     * @param bool   $recursion_first
933
     *
934
     * @return bool
935
     */
936
    protected function _forceDelete(?string $recursion = null, bool $recursion_first = true): bool
937
    {
938
        if (!$this->isReference()) {
939
            $this->doRecursiveAction(function ($node) use ($recursion) {
940
                $node->delete(true, $recursion, false);
941
            }, NodeInterface::DELETED_INCLUDE);
942
        }
943
944
        try {
945
            $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...
946
947
            if ($this->isShared()) {
948
                $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...
949
            }
950
951
            $this->_logger->info('force removed collection ['.$this->_id.']', [
952
                'category' => get_class($this),
953
            ]);
954
955
            $this->_hook->run(
956
                'postDeleteCollection',
957
                [$this, true, $recursion, $recursion_first]
958
            );
959
        } catch (\Exception $e) {
960
            $this->_logger->error('failed force remove collection ['.$this->_id.']', [
961
                'category' => get_class($this),
962
                'exception' => $e,
963
            ]);
964
965
            throw $e;
966
        }
967
968
        return true;
969
    }
970
}
971