Completed
Push — master ( 2d4c67...9c9bdc )
by Raffael
08:15
created

Collection   D

Complexity

Total Complexity 117

Size/Duplication

Total Lines 1028
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 117
lcom 1
cbo 10
dl 0
loc 1028
rs 4.4343
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 4
C copyTo() 0 47 8
D getShare() 0 38 9
A getAttribute() 0 21 2
A get() 0 4 1
D getChildNodes() 0 63 11
A getChildren() 0 4 1
A isCustomFilter() 0 4 1
A getSize() 0 8 2
A getRealId() 0 8 3
A getQuotaInfo() 0 9 1
B getChild() 0 59 7
A doRecursiveAction() 0 14 3
B delete() 0 54 9
B _forceDelete() 0 36 4
B childExists() 0 30 5
C share() 0 74 8
B unshare() 0 65 6
D getChildrenRecursive() 0 33 9
C addDirectory() 0 74 10
D addFile() 0 93 11
A createFile() 0 4 1
A createDirectory() 0 4 1

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
declare(strict_types=1);
3
4
/**
5
 * Balloon
6
 *
7
 * @author      Raffael Sahli <[email protected]>
8
 * @copyright   Copryright (c) 2012-2017 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPLv3 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Balloon\Filesystem\Node;
13
14
use \Sabre\DAV;
15
use Balloon\Exception;
16
use Balloon\Helper;
17
use Balloon\User;
18
use Balloon\Resource;
19
use \Psr\Log\LoggerInterface as Logger;
20
use Balloon\Plugin;
21
use Balloon\Filesystem;
22
use \MongoDB\BSON\ObjectId;
23
use \MongoDB\BSON\UTCDateTime;
24
use \MongoDB\Model\BSONDocument;
25
use \MongoDB\BSON\Regex;
26
use \Generator;
27
28
class Collection extends Node implements INode, DAV\ICollection, DAV\IQuota
29
{
30
    /**
31
     * Root folder
32
     */
33
    const ROOT_FOLDER = '/';
34
35
36
    /**
37
     * Mime type
38
     *
39
     * @var string
40
     */
41
    protected $mime = 'inode/directory';
42
43
    
44
    /**
45
     * Children
46
     *
47
     * @var array
48
     */
49
    protected $children = [];
50
51
52
    /**
53
     * Share acl
54
     *
55
     * @var array
56
     */
57
    protected $acl;
58
59
60
    /**
61
     * filter
62
     *
63
     * @param array
64
     */
65
    protected $filter = [];
66
67
68
    /**
69
     * Initialize
70
     *
71
     * @param  BSONDocument $node
72
     * @param  Filesystem $fs
73
     * @param  bool $force
74
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
75
     */
76
    public function __construct(?BSONDocument $node, Filesystem $fs, bool $force=false)
77
    {
78
        parent::__construct($node, $fs);
79
        
80
        if ($node === null) {
81
            $this->_id = null;
82
83
            if ($this->_user instanceof User) {
84
                $this->owner  = $this->_user->getId();
85
            }
86
        }
87
            
88
        if($force === false) {
89
            $this->_verifyAccess();
90
        }
91
    }
92
93
94
    /**
95
     * Copy node with children
96
     *
97
     * @param  Collection $parent
98
     * @param  int $conflict
99
     * @param  string $recursion
100
     * @param  bool $recursion_first
101
     * @return INode
102
     */
103
    public function copyTo(Collection $parent, int $conflict=INode::CONFLICT_NOACTION, ?string $recursion=null, bool $recursion_first=true): INode
104
    {
105
        if ($recursion === null) {
106
            $recursion_first = true;
107
            $recursion = uniqid();
108
        } else {
109
            $recursion_first = false;
110
        }
111
112
        $this->_pluginmgr->run('preCopyCollection',
113
            [$this, $parent, &$conflict, &$recursion, &$recursion_first]
114
        );
115
116
        if ($conflict === INode::CONFLICT_RENAME && $parent->childExists($this->name)) {
117
            $name = $this->_getDuplicateName();
118
        } else {
119
            $name = $this->name;
120
        }
121
        
122
        if ($this->_id == $parent->getId()) {
123
            throw new Exception\Conflict('can not copy node into itself',
124
                Exception\Conflict::CANT_COPY_INTO_ITSELF
125
            );
126
        }
127
128
        if ($conflict === INode::CONFLICT_MERGE && $parent->childExists($this->name)) {
129
            $new_parent = $parent->getChild($this->name);
130
        } else {
131
            $new_parent = $parent->addDirectory($name, [
132
                'created' => $this->created,
133
                'changed' => $this->changed,
134
                'deleted' => $this->deleted,
135
                'filter'  => $this->filter,
136
		'meta' => $this->meta
137
            ], INode::CONFLICT_NOACTION, true);
138
        }
139
140
        foreach ($this->getChildNodes(INode::DELETED_INCLUDE) as $child) {
141
            $child->copyTo($new_parent, $conflict, $recursion, false);
142
        }
143
144
        $this->_pluginmgr->run('postCopyCollection',
145
            [$this, $parent, $new_parent, $conflict, $recursion, $recursion_first]
146
        );
147
148
        return $new_parent;
149
    }
150
151
152
    /**
153
     * Get share
154
     *
155
     * @return array|bool
156
     */
157
    public function getShare()
158
    {
159
        if (!$this->isShared()) {
160
            return false;
161
        } else {
162
            if (is_array($this->acl)) {
163
                $resource = new Resource($this->_user, $this->_logger, $this->_fs);
164
                $return = [];
165
                if (array_key_exists('user', $this->acl)) {
166
                    foreach ((array)$this->acl['user'] as $user) {
167
                        $data = $resource->searchUser($user['user'], true);
168
169
                        if (empty($data)) {
170
                            continue;
171
                        }
172
173
                        $data['priv'] = $user['priv'];
174
                        $return[] = $data;
175
                    }
176
                }
177
                if (array_key_exists('group', $this->acl)) {
178
                    foreach ((array)$this->acl['group'] as $group) {
179
                        $data = $resource->searchGroup($group['group'], true);
180
181
                        if (empty($data)) {
182
                            continue;
183
                        }
184
185
                        $data['priv'] = $group['priv'];
186
                        $return[] = $data;
187
                    }
188
                }
189
                return $return;
190
            } else {
191
                return false;
192
            }
193
        }
194
    }
195
196
197
    /**
198
     * Get Attribute
199
     *
200
     * @param  array|string $attribute
201
     * @return array|string
202
     */
203
    public function getAttribute($attribute=[])
204
    {
205
        if (empty($attribute)) {
206
            $attribute = [
207
                'id',
208
                'name',
209
                'meta',
210
                'mime',
211
                'size',
212
                'sharelink',
213
                'reference',
214
                'deleted',
215
                'changed',
216
                'created',
217
                'share',
218
                'directory'
219
            ];
220
        }
221
    
222
        return $this->_getAttribute($attribute);
223
    }
224
225
    
226
    /**
227
     * Get collection
228
     *
229
     * @return void
230
     */
231
    public function get(): void
232
    {
233
        $this->getZip();
234
    }
235
236
    
237
    /**
238
     * Fetch children items of this collection
239
     *
240
     * Deleted:
241
     *  0 - Exclude deleted
242
     *  1 - Only deleted
243
     *  2 - Include deleted
244
     *
245
     * @param   int $deleted
246
     * @param   array $filter
247
     * @return  Generator
248
     */
249
    public function getChildNodes(int $deleted=INode::DELETED_EXCLUDE, array $filter=[]): Generator
250
    {
251
        if ($this->_user instanceof User) {
252
            $this->_user->findNewShares();
253
        }
254
255
        $search = [
256
            'parent' => $this->getRealId(),
257
        ];
258
259
        if ($deleted === INode::DELETED_EXCLUDE) {
260
            $search['deleted'] = false;
261
        } elseif ($deleted === INode::DELETED_ONLY) {
262
            $search['deleted'] = ['$type' => 9];
263
        }
264
265
        $search = array_merge($filter, $search);
266
267
        if ($this->shared) {
268
            $node = $this->_db->storage->find([
269
                '$and' => [
270
                    $search,
271
                    [
272
                        '$or' => [
273
                            ['shared' => $this->reference],
274
                            ['shared' => $this->shared],
275
                            ['shared' => $this->_id],
276
                        ]
277
                    ]
278
                ]
279
            ]);
280
        } else {
281
            if ($this->_user !== null) {
282
                $search['owner'] = $this->_user->getId();
283
            }
284
            
285
            $node = $this->_db->storage->find($search);
286
        }
287
        
288
        $list = [];
0 ignored issues
show
Unused Code introduced by
$list 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...
289
        foreach ($node as $child) {
290
            try {
291
                yield $this->getChild($child);
292
            } catch (\Exception $e) {
293
                $this->_logger->info('remove node from children list, failed load node', [
294
                    'category'  => get_class($this),
295
                    'exception' => $e
296
                ]);
297
            }
298
        }
299
        
300
        if (!empty($this->filter)) {
301
            if(is_string($this->filter)) {
302
                $filter = json_decode($this->filter, true);
303
            } else {
304
                $filter = $this->filter;
305
            }
306
307
            foreach ($this->_fs->findNodesWithCustomFilterUser($deleted, $filter) as $node) {
308
                yield $node;
309
            }
310
        }
311
    }
312
313
314
    /**
315
     * Fetch children items of this collection (as array)
316
     *
317
     * Deleted:
318
     *  0 - Exclude deleted
319
     *  1 - Only deleted
320
     *  2 - Include deleted
321
     *
322
     * @param   int $deleted
323
     * @param   array $filter
324
     * @return  Generator
325
     */
326
    public function getChildren(int $deleted=INode::DELETED_EXCLUDE, array $filter=[]): array
327
    {
328
        return iterator_to_array($this->getChildNodes($deleted, $filter));
329
    }
330
331
332
    /**
333
     * Is custom filter node
334
     *
335
     * @return bool
336
     */
337
    public function isCustomFilter(): bool
338
    {
339
        return !empty($this->filter);
340
    }
341
342
    
343
    /**
344
     * Get number of children
345
     *
346
     * @return int
347
     */
348
    public function getSize(): int
349
    {
350
        if ($this->isDeleted()) {
351
            return count(iterator_to_array($this->getChildNodes(INode::DELETED_INCLUDE)));
352
        } else {
353
            return count(iterator_to_array($this->getChildNodes()));
354
        }
355
    }
356
357
358
    /**
359
     * Get real id (reference)
360
     *
361
     * @return ObjectId
362
     */
363
    public function getRealId(): ?ObjectId
364
    {
365
        if ($this->shared == true && $this->isReference()) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
366
            return $this->reference;
367
        } else {
368
            return $this->_id;
369
        }
370
    }
371
372
373
    /**
374
     * Get user quota information
375
     *
376
     * @return array
377
     */
378
    public function getQuotaInfo(): array
379
    {
380
        $quota = $this->_user->getQuotaUsage();
381
        
382
        return [
383
            $quota['used'],
384
            $quota['available']
385
        ];
386
    }
387
388
389
    /**
390
     * Fetch children items of this collection
391
     *
392
     * @param  Collection|File|string $node
393
     * @param  int $deleted
394
     * @param  array $filter
395
     * @return INode
396
     */
397
    public function getChild($node, int $deleted=INode::DELETED_EXCLUDE, array $filter=[]): INode
398
    {
399
        //if $node is string load the object from the backend based on the current parent (the name
400
        //is unique per depth, so we can load the object)
401
        if (is_string($node)) {
402
            $name = $node;
403
            $search = [
404
                'name'    => new Regex('^'.preg_quote($name).'$', 'i'),
405
                'parent'  => $this->getRealId(),
406
            ];
407
    
408
            switch ($deleted) {
409
                case INode::DELETED_EXCLUDE:
410
                    $search['deleted'] = false;
411
                    break;
412
                case INode::DELETED_ONLY:
413
                    $search['deleted'] = ['$type' => '9'];
414
                    break;
415
            }
416
417
418
            $search = array_merge($filter, $search);
419
420
            if ($this->shared) {
421
                $node = $this->_db->storage->findOne([
422
                    '$and' => [
423
                        $search,
424
                        [
425
                            '$or' => [
426
                                ['shared' => $this->reference],
427
                                ['shared' => $this->shared],
428
                                ['shared' => $this->_id]
429
                            ]
430
                        ]
431
                    ]
432
                ]);
433
            } else {
434
                $search['owner'] = $this->_user->getId();
435
                $node = $this->_db->storage->findOne($search);
436
            }
437
438
            if ($node === null) {
439
                throw new Exception\NotFound('node called '.$name.' does not exists here',
440
                    Exception\NotFound::NODE_NOT_FOUND
441
                );
442
            }
443
        }
444
445
        $this->_logger->debug('loaded node ['.$node['_id'].' (directory='.$node['directory'].')] from parent node ['.$this->getRealId().']', [
446
            'category' => get_class($this),
447
        ]);
448
        
449
        //if the item has the directory flag we create a collection else the item is file
450
        if ($node['directory'] == true) {
451
            return new Collection($node, $this->_fs);
452
        } else {
453
            return new File($node, $this->_fs);
454
        }
455
    }
456
457
458
    /**
459
     * Do recursive Action
460
     *
461
     * @param   string $method
462
     * @param   array $params
463
     * @param   int $deleted
464
     * @return  bool
465
     */
466
    protected function doRecursiveAction(string $method, array $params=[], int $deleted=INode::DELETED_EXCLUDE): bool
467
    {
468
        if (!is_callable([$this, $method])) {
469
            throw new Exception("method $method is not callable in ".__CLASS__);
470
        }
471
472
        $children = $this->getChildNodes($deleted);
473
474
        foreach ($children as $child) {
475
            call_user_func_array([$child, $method], $params);
476
        }
477
478
        return true;
479
    }
480
481
482
    /**
483
     * Delete node
484
     *
485
     * Actually the node will not be deleted (Just set a delete flag), set $force=true to
486
     * delete finally
487
     *
488
     * @param   bool $force
489
     * @param   string $recursion Identifier to identify a recursive action
490
     * @param   bool $recursion_first
491
     * @return  bool
492
     */
493
    public function delete(bool $force=false, ?string $recursion=null, bool $recursion_first=true): bool
494
    {
495
        if (!$this->isAllowed('w') && !$this->isReference()) {
496
            throw new Exception\Forbidden('not allowed to delete node '.$this->name,
497
                Exception\Forbidden::NOT_ALLOWED_TO_DELETE
498
            );
499
        }
500
501
        if ($recursion === null) {
502
            $recursion_first = true;
503
            $recursion = uniqid();
504
        } else {
505
            $recursion_first = false;
506
        }
507
508
        $this->_pluginmgr->run('preDeleteCollection',
509
            [$this, &$force, &$recursion, &$recursion_first]
510
        );
511
        
512
        if ($this->readonly && $this->_user !== null) {
513
            throw new Exception\Conflict('node is marked as readonly, it is not possible to delete it',
514
                Exception\Conflict::READONLY
515
            );
516
        }
517
        
518
        if ($force === true) {
519
            return $this->_forceDelete($recursion, $recursion_first);
520
        }
521
        
522
        $this->deleted = new UTCDateTime();
523
        
524
525
        if (!$this->isReference()) {
526
            $this->doRecursiveAction('delete', [
527
                'force'     => false,
528
                'recursion' => $recursion,
529
                'recursion_first' => false,
530
            ]);
531
        }
532
533
        if ($this->_id !== null) {
534
            $result = $this->save([
535
                'deleted',
536
            ], [], $recursion, false);
537
        } else {
538
            $result = true;
539
        }
540
541
        $this->_pluginmgr->run('postDeleteCollection',
542
            [$this, $force, $recursion, $recursion_first]
543
        );
544
545
        return $result;
546
    }
547
548
549
    /**
550
     * Completely remove node
551
     *
552
     * @param   string $recursion Identifier to identify a recursive action
553
     * @param   bool $recursion_first
554
     * @return  bool
555
     */
556
    protected function _forceDelete(?string $recursion=null, bool $recursion_first=true): bool
557
    {
558
        if (!$this->isReference()) {
559
            $this->doRecursiveAction('delete', [
560
                'force'     => true,
561
                'recursion' => $recursion,
562
                'recursion_first' => false,
563
            ], INode::DELETED_INCLUDE);
564
        }
565
  
566
        try {
567
            $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...
568
569
            if ($this->isShared()) {
570
                $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...
571
            }
572
573
            $this->_logger->info('force removed collection ['.$this->_id.']', [
574
                'category' => get_class($this),
575
            ]);
576
        
577
578
            $this->_pluginmgr->run('postDeleteCollection',
579
                [$this, true, $recursion, $recursion_first]
580
            );
581
        } catch (\Exception $e) {
582
            $this->_logger->error('failed force remove collection ['.$this->_id.']', [
583
                'category' => get_class($this),
584
                'exception' => $e,
585
            ]);
586
            
587
            throw $e;
588
        }
589
590
        return true;
591
    }
592
593
594
    /**
595
     * Check if this collection has child named $name
596
     *
597
     * deleted:
598
     *
599
     *  0 - Exclude deleted
600
     *  1 - Only deleted
601
     *  2 - Include deleted
602
     *
603
     * @param   string $name
604
     * @param   int $deleted
605
     * @param   array $filter
606
     * @return  bool
607
     */
608
    public function childExists($name, $deleted=INode::DELETED_EXCLUDE, array $filter=[]): bool
609
    {
610
        $find = [
611
            'parent'  => $this->getRealId(),
612
            'name'    => new Regex('^'.preg_quote($name).'$', 'i'),
613
        ];
614
615
        if ($this->_user !== null) {
616
            $find['owner'] = $this->_user->getId();
617
        }
618
619
        switch ($deleted) {
620
            case INode::DELETED_EXCLUDE:
621
                $find['deleted'] = false;
622
                break;
623
            case INode::DELETED_ONLY:
624
                $find['deleted'] = ['$type' => 9];
625
                break;
626
        }
627
628
        $find = array_merge($filter, $find);
629
630
        if ($this->isSpecial()) {
631
            unset($find['owner']);
632
        }
633
        
634
        $node = $this->_db->storage->findOne($find);
635
    
636
        return (bool)$node;
637
    }
638
    
639
640
    /**
641
     * Share collection
642
     *
643
     * @param   array $acl
644
     * @return  bool
645
     */
646
    public function share(array $acl): bool
647
    {
648
        if ($this->isReference()) {
649
            throw new Exception('a collection reference can not be shared');
650
        }
651
        
652
        if ($this->isShareMember()) {
653
            throw new Exception('a sub node of a share can not be shared');
654
        }
655
        
656
        if (!$this->isAllowed('w')) {
657
            throw new Exception\Forbidden('not allowed to share node',
658
                Exception\Forbidden::NOT_ALLOWED_TO_SHARE
659
            );
660
        }
661
662
        $this->shared = true;
663
664
        $this->acl = $acl;
665
        $action = [
666
            '$set' => [
667
                'shared' => $this->_id
668
            ]
669
        ];
670
    
671
        $toset = $this->getChildrenRecursive($this->getRealId(), $shares, $files);
672
        
673
        if (!empty($shares)) {
674
            throw new Exception('child folder contains a shared folder');
675
        }
676
        
677
        $this->_db->storage->updateMany([
678
            '_id' => [
679
                '$in' => $toset,
680
            ],
681
        ], $action);
682
683
        $this->_db->delta->updateMany([
684
            '_id' => [
685
                '$in' => $toset,
686
            ],
687
        ], $action);
688
        
689
        $list = [];
0 ignored issues
show
Unused Code introduced by
$list 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...
690
        $result = $this->save(['shared', 'acl']);
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...
691
692
        if (!is_array($files)) {
693
            return true;
694
        }
695
696
        foreach ($files as $node) {
697
            if ($node['file'] === null) {
698
                continue;
699
            }
700
701
            $share_ref = [
702
                'id'    => $node['id'],
703
                'share' => $this->_id,
704
            ];
705
706
            $this->_db->{'fs.files'}->updateOne(
707
                [
708
                    '_id' => $node['file']
709
                ],
710
                [
711
                    '$addToSet' => [
712
                        'metadata.share_ref' => $share_ref,
713
                    ]
714
                ]
715
            );
716
        }
717
718
        return true;
719
    }
720
721
722
    /**
723
     * Unshare collection
724
     *
725
     * @return  bool
726
     */
727
    public function unshare(): bool
728
    {
729
        if (!$this->isAllowed('w')) {
730
            throw new Exception\Forbidden('not allowed to share node',
731
                Exception\Forbidden::NOT_ALLOWED_TO_SHARE
732
            );
733
        }
734
735
        if ($this->shared !== true) {
736
            throw new Exception\Conflict('Can not unshare a none shared collection',
737
                Exception\Conflict::NOT_SHARED
738
            );
739
        }
740
741
        $this->shared = false;
742
        $this->acl   = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $acl.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
743
        $action = [
744
            '$unset' => [
745
                'shared' => $this->_id
746
            ],
747
            '$set' => [
748
                'owner' => $this->_user->getId()
749
            ]
750
        ];
751
    
752
        $toset = $this->getChildrenRecursive($this->getRealId(), $shares, $files);
753
        
754
        
755
        $this->_db->storage->updateMany([
756
            '_id' => [
757
                '$in' => $toset,
758
            ],
759
        ], $action);
760
        
761
        $list = [];
0 ignored issues
show
Unused Code introduced by
$list 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...
762
        $result = $this->save(['shared'], ['acl']);
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...
763
764
        if (!is_array($files)) {
765
            return true;
766
        }
767
768
        foreach ($files as $node) {
769
            if ($node['file'] === null) {
770
                continue;
771
            }
772
773
            $share_ref = [
774
                'id'    => $node['id'],
775
                'share' => $this->_id,
776
            ];
777
778
            $this->_db->{'fs.files'}->updateOne(
779
                [
780
                    '_id' => $node['file']
781
                ],
782
                [
783
                '$pull' => [
784
                    'metadata.share_ref' => $share_ref,
785
                    ]
786
                ]
787
            );
788
        }
789
790
        return true;
791
    }
792
793
    
794
    /**
795
     * Get children
796
     *
797
     * @param   ObjectId $id
798
     * @param   array $shares
799
     * @param   arary $files
800
     * @return  array
801
     */
802
    public function getChildrenRecursive(?ObjectId $id=null, ?array &$shares=[], ?array &$files=[]): array
803
    {
804
        $list = [];
805
        $result = $this->_db->storage->find([
806
            'parent' => $id,
807
        ], [
808
            '_id' => 1,
809
            'directory' => 1,
810
            'reference' => 1,
811
            'shared'    => 1,
812
            'file'      => 1
813
        ]);
814
        
815
        foreach ($result as $node) {
816
            $list[] = $node['_id'];
817
818
            if ($node['directory'] === false) {
819
                $files[] = [
820
                    'id'    => $node['_id'],
821
                    'file'  => isset($node['file']) ? $node['file'] : null,
822
                ];
823
            } else {
824
                if (isset($node['reference']) || isset($node['shared']) && $node['shared'] === true) {
825
                    $shares[] = $node['_id'];
826
                }
827
        
828
                if ($node['directory'] === true && !isset($node['reference'])) {
829
                    $list = array_merge($list, $this->getChildrenRecursive($node['_id'], $shares, $files));
830
                }
831
            }
832
        }
833
        return $list;
834
    }
835
836
837
    /**
838
     * Create new directory
839
     *
840
     * @param   string $name
841
     * @param   arracy $attributes
842
     * @param   int $conflict
843
     * @param   bool $clone
844
     * @return  Collection
845
     */
846
    public function addDirectory($name, array $attributes=[], int $conflict=INode::CONFLICT_NOACTION, bool $clone=false): Collection
847
    {
848
        if (!$this->isAllowed('w')) {
849
            throw new Exception\Forbidden('not allowed to create new node here',
850
                Exception\Forbidden::NOT_ALLOWED_TO_CREATE
851
            );
852
        }
853
854
        $this->_pluginmgr->run('preCreateCollection', [$this, &$name, &$attributes, &$clone]);
855
856
        if ($this->readonly) {
857
            throw new Exception\Conflict('node is set as readonly, it is not possible to add new sub nodes',
858
                Exception\Conflict::READONLY
859
            );
860
        }
861
862
        $name = $this->checkName($name);
863
864
        if ($this->childExists($name)) {
865
            if ($conflict === INode::CONFLICT_NOACTION) {
866
                throw new Exception\Conflict('a node called '.$name.' does already exists in this collection',
867
                    Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
868
                );
869
            } elseif ($conflict === INode::CONFLICT_RENAME) {
870
                $name = $this->getDuplicateName($name);
0 ignored issues
show
Bug introduced by
The method getDuplicateName() does not exist on Balloon\Filesystem\Node\Collection. Did you maybe mean _getDuplicateName()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
871
            }
872
        }
873
        
874
        if ($this->isDeleted()) {
875
            throw new Exception\Conflict('could not add node '.$name.' into a deleted parent collection',
876
                Exception\Conflict::DELETED_PARENT
877
            );
878
        }
879
        
880
        try {
881
            $meta = [
882
                'name'      => $name,
883
                'deleted'   => false,
884
                'parent'    => $this->getRealId(),
885
                'directory' => true,
886
                'meta'      => [],
887
                'created'   => new UTCDateTime(),
888
                'changed'   => new UTCDateTime(),
889
                'owner'     => $this->_user->getId(),
890
                'shared'    => ($this->shared === true ? $this->getRealId() : $this->shared)
891
            ];
892
893
            $save  = array_merge($meta, $attributes);
894
895
            $result = $this->_db->storage->insertOne($save);
896
            $save['_id'] = $result->getInsertedId();
897
898
            $this->_logger->info('added new collection ['.$save['_id'].'] under parent ['.$this->_id.']', [
899
                'category' => get_class($this),
900
            ]);
901
            
902
	    if(!$this->isRoot()) {
903
                $this->changed = $save['changed'];
904
                $this->save('changed');
905
            }            
906
907
            $new = new Collection(new BSONDocument($save), $this->_fs, true);
908
            $this->_pluginmgr->run('postCreateCollection', [$this, $new, $clone]);
909
     
910
            return $new;
911
        } catch (\Exception $e) {
912
            $this->_logger->error('failed create new collection under parent ['.$this->_id.']', [
913
                'category' => get_class($this),
914
                'exception'=> $e,
915
            ]);
916
917
            throw $e;
918
        }
919
    }
920
921
    /**
922
     * Create new file as a child from this collection
923
     *
924
     * @param   string $name
925
     * @param   ressource|string $data
926
     * @param   array $attributes
927
     * @param   int $conflict
928
     * @param   bool $clone
929
     * @return  File
930
     */
931
    public function addFile($name, $data=null, array $attributes=[], int $conflict=INode::CONFLICT_NOACTION, bool $clone=false): File
932
    {
933
        if (!$this->isAllowed('w')) {
934
            throw new Exception\Forbidden('not allowed to create new node here',
935
                Exception\Forbidden::NOT_ALLOWED_TO_CREATE
936
            );
937
        }
938
939
        $this->_pluginmgr->run('preCreateFile', [$this, &$name, &$attributes, &$clone]);
940
941
        if ($this->readonly) {
942
            throw new Exception\Conflict('node is set as readonly, it is not possible to add new sub nodes',
943
                Exception\Conflict::READONLY
944
            );
945
        }
946
947
        $name = $this->checkName($name);
948
949
        if ($this->childExists($name)) {
950
            if ($conflict === INode::CONFLICT_NOACTION) {
951
                throw new Exception\Conflict('a node called '.$name.' does already exists in this collection',
952
                    Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
953
                );
954
            } elseif ($conflict === INode::CONFLICT_RENAME) {
955
                $name = $this->getDuplicateName($name);
0 ignored issues
show
Bug introduced by
The method getDuplicateName() does not exist on Balloon\Filesystem\Node\Collection. Did you maybe mean _getDuplicateName()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
956
            }
957
        }
958
959
        if ($this->isDeleted()) {
960
            throw new Exception\Conflict('could not add node '.$name.' into a deleted parent collection',
961
                Exception\Conflict::DELETED_PARENT
962
            );
963
        }
964
965
        try {
966
            $meta = [
967
                'name'      => $name,
968
                'deleted'   => false,
969
                'parent'    => $this->getRealId(),
970
                'directory' => false,
971
                'hash'      => null,
972
                'meta'      => [],
973
                'created'   => new UTCDateTime(),
974
                'changed'   => new UTCDateTime(),
975
                'owner'     => $this->_user->getId(),
976
                'history'   => [],
977
                'version'   => 0,
978
                'shared'    => ($this->shared === true ? $this->getRealId() : $this->shared),
979
            ];
980
981
            $save  = array_merge($meta, $attributes);
982
983
            $result = $this->_db->storage->insertOne($save);
984
            $save['_id'] = $result->getInsertedId();
985
986
            $this->_logger->info('added new file ['.$save['_id'].'] under parent ['.$this->_id.']', [
987
                'category' => get_class($this),
988
            ]);
989
990
            if(!$this->isRoot()) {
991
                $this->changed = $save['changed'];
992
                $this->save('changed');
993
            }            
994
995
            $file = new File(new BSONDocument($save), $this->_fs, true);
996
997
            try {
998
                $file->put($data, true, $attributes);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 931 can also be of type null; however, Balloon\Filesystem\Node\File::put() does only seem to accept resource|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
999
            } catch (Exception\InsufficientStorage $e) {
1000
                $this->_logger->warning('failed add new file under parent ['.$this->_id.'], cause user quota is full', [
1001
                    'category' => get_class($this),
1002
                    'exception' => $e,
1003
                ]);
1004
1005
                $this->_db->storage->deleteOne([
1006
                    '_id' => $save['_id']
1007
                ]);
1008
1009
                throw $e;
1010
            }
1011
            
1012
            $this->_pluginmgr->run('postCreateFile', [$this, $file, $clone]);
1013
1014
            return $file;
1015
        } catch (\Exception $e) {
1016
            $this->_logger->error('failed add new file under parent ['.$this->_id.']', [
1017
                'category' => get_class($this),
1018
                'exception' => $e,
1019
            ]);
1020
1021
            throw $e;
1022
        }
1023
    }
1024
1025
1026
    /**
1027
     * Create new file wrapper
1028
     * (Sabe\DAV compatible method, elsewhere use addFile()
1029
     *
1030
     * Sabre\DAV requires that createFile() returns the ETag instead the newly created file instance
1031
     *
1032
     * @param   string $name
1033
     * @param   string $data
1034
     * @return  File
1035
     */
1036
    public function createFile($name, $data=null): String
1037
    {
1038
        return $this->addFile($name, $data)->getETag();
1039
    }
1040
    
1041
1042
    /**
1043
     * Create new directory wrapper
1044
     * (Sabe\DAV compatible method, elsewhere use addDirectory()
1045
     *
1046
     * Sabre\DAV requires that createDirectory() returns void
1047
     *
1048
     * @param   string $name
1049
     * @return  void
1050
     */
1051
    public function createDirectory($name): void
1052
    {
1053
        $this->addDirectory($name);
1054
    }
1055
}
1056