Completed
Push — master ( 0967fb...923f89 )
by Raffael
09:50 queued 05:40
created

Filesystem::executeAggregation()   B

Complexity

Conditions 6
Paths 24

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 0
cts 17
cp 0
rs 8.8337
c 0
b 0
f 0
cc 6
nc 24
nop 3
crap 42
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2019 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Balloon;
13
14
use Balloon\Filesystem\Acl;
15
use Balloon\Filesystem\Acl\Exception\Forbidden as ForbiddenException;
16
use Balloon\Filesystem\Delta;
17
use Balloon\Filesystem\Exception;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Balloon\Exception.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
18
use Balloon\Filesystem\Node\Collection;
19
use Balloon\Filesystem\Node\Factory as NodeFactory;
20
use Balloon\Filesystem\Node\NodeInterface;
21
use Balloon\Server\User;
22
use Generator;
23
use MongoDB\BSON\ObjectId;
24
use MongoDB\BSON\UTCDateTime;
25
use MongoDB\Database;
26
use Psr\Log\LoggerInterface;
27
28
class Filesystem
29
{
30
    /**
31
     * Database.
32
     *
33
     * @var Database
34
     */
35
    protected $db;
36
37
    /**
38
     * LoggerInterface.
39
     *
40
     * @var LoggerInterface
41
     */
42
    protected $logger;
43
44
    /**
45
     * Hook.
46
     *
47
     * @var Hook
48
     */
49
    protected $hook;
50
51
    /**
52
     * Server.
53
     *
54
     * @var Server
55
     */
56
    protected $server;
57
58
    /**
59
     * Root collection.
60
     *
61
     * @var Collection
62
     */
63
    protected $root;
64
65
    /**
66
     * User.
67
     *
68
     * @var Delta
69
     */
70
    protected $delta;
71
72
    /**
73
     * Get user.
74
     *
75
     * @var User
76
     */
77
    protected $user;
78
79
    /**
80
     * Node factory.
81
     *
82
     * @var NodeFactory
83
     */
84
    protected $node_factory;
85
86
    /**
87
     * Acl.
88
     *
89
     * @var Acl
90
     */
91
    protected $acl;
92
93
    /**
94
     * Cache.
95
     *
96
     * @var array
97
     */
98
    protected $cache = [];
99
100
    /**
101
     * RAW Cache.
102
     *
103
     * @var array
104
     */
105
    protected $raw_cache = [];
106
107
    /**
108
     * Initialize.
109
     */
110
    public function __construct(Server $server, Database $db, Hook $hook, LoggerInterface $logger, NodeFactory $node_factory, Acl $acl, ?User $user = null)
111
    {
112
        $this->user = $user;
113
        $this->server = $server;
114
        $this->db = $db;
115
        $this->logger = $logger;
116
        $this->hook = $hook;
117
        $this->node_factory = $node_factory;
118
        $this->acl = $acl;
119
    }
120
121
    /**
122
     * Get user.
123
     */
124
    public function getUser(): ?User
125
    {
126
        return $this->user;
127
    }
128
129
    /**
130
     * Get server.
131
     */
132
    public function getServer(): Server
133
    {
134
        return $this->server;
135
    }
136
137
    /**
138
     * Get database.
139
     */
140
    public function getDatabase(): Database
141
    {
142
        return $this->db;
143
    }
144
145
    /**
146
     * Get root.
147
     */
148
    public function getRoot(): Collection
149
    {
150
        if ($this->root instanceof Collection) {
151
            return $this->root;
152
        }
153
154
        return $this->root = $this->initNode([
155
            'directory' => true,
156
            '_id' => null,
157
            'owner' => $this->user ? $this->user->getId() : null,
158
        ]);
159
    }
160
161
    /**
162
     * Get delta.
163
     */
164
    public function getDelta(): Delta
165
    {
166
        if ($this->delta instanceof Delta) {
167
            return $this->delta;
168
        }
169
170
        return $this->delta = new Delta($this, $this->db, $this->acl);
171
    }
172
173
    /**
174
     * Find raw node.
175
     */
176
    public function findRawNode(ObjectId $id): array
177
    {
178
        if (isset($this->raw_cache[(string) $id])) {
179
            return $this->raw_cache[(string) $id];
180
        }
181
182
        $node = $this->db->storage->findOne(['_id' => $id]);
183
        if (null === $node) {
184
            throw new Exception\NotFound(
185
                'node '.$id.' not found',
186
                Exception\NotFound::NODE_NOT_FOUND
187
            );
188
        }
189
190
        $this->raw_cache[(string) $id] = $node;
191
192
        return $node;
193
    }
194
195
    /**
196
     * Factory loader.
197
     */
198
    public function findNodeById($id, ?string $class = null, int $deleted = NodeInterface::DELETED_INCLUDE): NodeInterface
199
    {
200
        if (isset($this->cache[(string) $id])) {
201
            return $this->cache[(string) $id];
202
        }
203
204
        if (!is_string($id) && !($id instanceof ObjectId)) {
0 ignored issues
show
Bug introduced by
The class MongoDB\BSON\ObjectId does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
205
            throw new Exception\InvalidArgument($id.' node id has to be a string or instance of \MongoDB\BSON\ObjectId');
206
        }
207
208
        try {
209
            if (is_string($id)) {
210
                $id = new ObjectId($id);
211
            }
212
        } catch (\Exception $e) {
213
            throw new Exception\InvalidArgument('invalid node id specified');
214
        }
215
216
        $filter = [
217
            '_id' => $id,
218
        ];
219
220
        $result = iterator_to_array($this->findNodesByFilterRecursiveChildren($filter, $deleted, 0, 1));
221
222
        if (count($result) === 0) {
223
            throw new Exception\NotFound(
224
                'node ['.$id.'] not found',
225
                Exception\NotFound::NODE_NOT_FOUND
226
            );
227
        }
228
229
        $node = array_shift($result);
230
        if (null !== $class && !($node instanceof $class)) {
231
            throw new Exception('node '.get_class($node).' is not instance of '.$class);
232
        }
233
234
        return $node;
235
    }
236
237
    /**
238
     * Find one.
239
     */
240
    public function findOne(array $filter, int $deleted = NodeInterface::DELETED_INCLUDE): NodeInterface
241
    {
242
        $result = iterator_to_array($this->findNodesByFilterRecursiveChildren($filter, $deleted, 0, 1));
243
244
        if (count($result) === 0) {
245
            throw new Exception\NotFound(
246
                'requested node not found',
247
                Exception\NotFound::NODE_NOT_FOUND
248
            );
249
        }
250
251
        return array_shift($result);
252
    }
253
254
    /**
255
     * Load nodes by id.
256
     *
257
     * @deprecated
258
     */
259
    public function findNodesById(array $id = [], ?string $class = null, int $deleted = NodeInterface::DELETED_INCLUDE): Generator
260
    {
261
        $find = [];
262
        foreach ($id as $i) {
263
            $find[] = new ObjectId($i);
264
        }
265
266
        $filter = [
267
            '_id' => ['$in' => $find],
268
        ];
269
270
        switch ($deleted) {
271
            case NodeInterface::DELETED_INCLUDE:
272
                break;
273
            case NodeInterface::DELETED_EXCLUDE:
274
                $filter['deleted'] = false;
275
276
                break;
277
            case NodeInterface::DELETED_ONLY:
278
                $filter['deleted'] = ['$type' => 9];
279
280
                break;
281
        }
282
283
        $result = $this->db->storage->find($filter);
284
285
        $nodes = [];
286
        foreach ($result as $node) {
287
            try {
288
                $return = $this->initNode($node);
289
290
                if (in_array($return->getId(), $nodes)) {
291
                    continue;
292
                }
293
294
                $nodes[] = $return->getId();
295
            } catch (\Exception $e) {
296
                $this->logger->error('remove node from result list, failed load node', [
297
                    'category' => get_class($this),
298
                    'exception' => $e,
299
                ]);
300
301
                continue;
302
            }
303
304
            if (null !== $class && !($return instanceof $class)) {
305
                throw new Exception('node is not an instance of '.$class);
306
            }
307
308
            yield $return;
309
        }
310
    }
311
312
    /**
313
     * Load nodes by id.
314
     *
315
     * @param null|mixed $class
316
     *
317
     * @deprecated
318
     */
319
    public function getNodes(?array $id = null, $class = null, int $deleted = NodeInterface::DELETED_EXCLUDE): Generator
320
    {
321
        return $this->findNodesById($id, $class, $deleted);
0 ignored issues
show
Deprecated Code introduced by
The method Balloon\Filesystem::findNodesById() has been deprecated.

This method has been deprecated.

Loading history...
322
    }
323
324
    /**
325
     * Load node.
326
     *
327
     * @param null|mixed $id
328
     * @param null|mixed $class
329
     *
330
     * @deprecated
331
     */
332
    public function getNode($id = null, $class = null, bool $multiple = false, bool $allow_root = false, ?int $deleted = null): NodeInterface
333
    {
334
        if (empty($id)) {
335
            if (true === $allow_root) {
336
                return $this->getRoot();
337
            }
338
339
            throw new Exception\InvalidArgument('invalid id given');
340
        }
341
342
        if (null === $deleted) {
343
            $deleted = NodeInterface::DELETED_INCLUDE;
344
        }
345
346
        if (true === $multiple && is_array($id)) {
347
            return $this->findNodesById($id, $class, $deleted);
0 ignored issues
show
Deprecated Code introduced by
The method Balloon\Filesystem::findNodesById() has been deprecated.

This method has been deprecated.

Loading history...
348
        }
349
350
        return $this->findNodeById($id, $class, $deleted);
351
    }
352
353
    /**
354
     * Find node with custom filter.
355
     */
356
    public function findNodeByFilter(array $filter): NodeInterface
357
    {
358
        $result = $this->db->storage->findOne($filter);
359
        if (null === $result) {
360
            throw new Exception\NotFound(
361
                'node with custom filter was not found',
362
                Exception\NotFound::NODE_NOT_FOUND
363
            );
364
        }
365
366
        return $this->initNode($result);
367
    }
368
369
    /**
370
     * Count.
371
     */
372
    public function countNodes(array $filter = []): int
373
    {
374
        return $this->db->storage->count($filter);
375
    }
376
377
    /**
378
     * Find nodes with custom filters.
379
     */
380
    public function findNodesByFilter(array $filter, ?int $offset = null, ?int $limit = null): Generator
381
    {
382
        $result = $this->db->storage->find($filter, [
383
            'skip' => $offset,
384
            'limit' => $limit,
385
        ]);
386
387
        $count = $this->countNodes($filter);
388
389
        foreach ($result as $node) {
390
            try {
391
                yield $this->initNode($node);
392
            } catch (\Exception $e) {
393
                $this->logger->error('remove node from result list, failed load node', [
394
                    'category' => get_class($this),
395
                    'exception' => $e,
396
                ]);
397
            }
398
        }
399
400
        return $count;
401
    }
402
403
    /**
404
     * Find nodes with custom filter recursive.
405
     */
406
    public function findNodesByFilterRecursiveToArray(Collection $collection, array $filter = []): array
407
    {
408
        $graph = [
409
            'from' => 'storage',
410
            'startWith' => '$pointer',
411
            'connectFromField' => 'pointer',
412
            'connectToField' => 'parent',
413
            'as' => 'children',
414
        ];
415
416
        if (count($filter) > 0) {
417
            $graph['restrictSearchWithMatch'] = $filter;
418
        }
419
420
        $query = [
421
            ['$match' => ['_id' => $collection->getId()]],
422
            ['$graphLookup' => $graph],
423
            ['$unwind' => '$children'],
424
            ['$project' => ['id' => '$children._id']],
425
        ];
426
427
        $result = $this->db->storage->aggregate($query);
428
429
        return array_column(iterator_to_array($result), 'id');
430
    }
431
432
    /**
433
     * Get deleted nodes.
434
     *
435
     * Note this query excludes deleted nodes which have a deleted parent
436
     */
437
    public function getTrash(array $query = [], ?int $offset = null, ?int $limit = null): Generator
438
    {
439
        $shares = $this->user->getShares();
440
        $parent_filter = ['$and' => [
441
            ['deleted' => ['$ne' => false]],
442
            ['$or' => [
443
                ['owner' => $this->user->getId()],
444
                ['shared' => ['$in' => $shares]],
445
            ]],
446
        ]];
447
448
        if (count($query) > 0) {
449
            $parent_filter = [
450
                '$and' => [$parent_filter, $query],
451
            ];
452
        }
453
454
        $query = [
455
            ['$match' => $parent_filter],
456
            ['$graphLookup' => [
457
                'from' => 'storage',
458
                'startWith' => '$parent',
459
                'connectFromField' => 'parent',
460
                'connectToField' => 'pointer',
461
                'as' => 'parents',
462
                'maxDepth' => 0,
463
                'restrictSearchWithMatch' => [
464
                    '$or' => [
465
                        [
466
                            'shared' => true,
467
                            'owner' => $this->user->getId(),
468
                        ],
469
                        [
470
                            'shared' => ['$ne' => true],
471
                        ],
472
                    ],
473
                ],
474
            ]], [
475
                '$addFields' => [
476
                    'parents' => [
477
                        '$arrayElemAt' => ['$parents', 0],
478
                    ],
479
                ],
480
            ], [
481
                '$match' => [
482
                    '$or' => [
483
                        ['parents' => null],
484
                        ['parents.deleted' => false],
485
                    ],
486
                ],
487
            ], ['$graphLookup' => [
488
                'from' => 'storage',
489
                'startWith' => '$pointer',
490
                'connectFromField' => 'pointer',
491
                'connectToField' => 'parent',
492
                'as' => 'children',
493
                'maxDepth' => 0,
494
                'restrictSearchWithMatch' => $this->prepareChildrenFilter(NodeInterface::DELETED_ONLY),
495
            ]],
496
            ['$addFields' => [
497
                'size' => [
498
                    '$cond' => [
499
                        'if' => ['$eq' => ['$directory', true]],
500
                        'then' => ['$size' => '$children'],
501
                        'else' => '$size',
502
                    ],
503
                ],
504
            ]],
505
            ['$project' => ['children' => 0, 'parents' => 0]],
506
            ['$group' => ['_id' => null, 'total' => ['$sum' => 1]]],
507
        ];
508
509
        return $this->executeAggregation($query, $offset, $limit);
510
    }
511
512
    /**
513
     * Find nodes with custom filter recursive.
514
     */
515
    public function findNodesByFilterRecursiveChildren(array $parent_filter, int $deleted, ?int $offset = null, ?int $limit = null): Generator
516
    {
517
        $query = [
518
            ['$match' => $parent_filter],
519
            ['$graphLookup' => [
520
                'from' => 'storage',
521
                'startWith' => '$pointer',
522
                'connectFromField' => 'pointer',
523
                'connectToField' => 'parent',
524
                'as' => 'children',
525
                'maxDepth' => 0,
526
                'restrictSearchWithMatch' => $this->prepareChildrenFilter($deleted),
527
            ]],
528
            ['$addFields' => [
529
                'size' => [
530
                    '$cond' => [
531
                        'if' => ['$eq' => ['$directory', true]],
532
                        'then' => ['$size' => '$children'],
533
                        'else' => '$size',
534
                    ],
535
                ],
536
            ]],
537
            ['$project' => ['children' => 0]],
538
            ['$group' => ['_id' => null, 'total' => ['$sum' => 1]]],
539
        ];
540
541
        return $this->executeAggregation($query, $offset, $limit);
542
    }
543
544
    /**
545
     * Find nodes with custom filter recursive.
546
     */
547
    public function findNodesByFilterRecursive(Collection $collection, array $filter = [], ?int $offset = null, ?int $limit = null): Generator
548
    {
549
        $graph = [
550
            'from' => 'storage',
551
            'startWith' => '$pointer',
552
            'connectFromField' => 'pointer',
553
            'connectToField' => 'parent',
554
            'as' => 'children',
555
        ];
556
557
        if (count($filter) > 0) {
558
            $graph['restrictSearchWithMatch'] = $filter;
559
        }
560
561
        $query = [
562
            ['$match' => ['_id' => $collection->getId()]],
563
            ['$graphLookup' => $graph],
564
            ['$unwind' => '$children'],
565
            ['$group' => ['_id' => null, 'total' => ['$sum' => 1]]],
566
        ];
567
568
        return $this->executeAggregation($query, $offset, $limit);
569
    }
570
571
    /**
572
     * Get custom filtered children.
573
     *
574
     * @deprecated
575
     */
576
    public function findNodesByFilterUser(int $deleted, array $filter, ?int $offset = null, ?int $limit = null): Generator
577
    {
578
        $shares = $this->user->getShares();
579
        $stored_filter = ['$and' => [
580
            [],
581
            ['$or' => [
582
                ['owner' => $this->user->getId()],
583
                ['shared' => ['$in' => $shares]],
584
            ]],
585
        ]];
586
587
        if (NodeInterface::DELETED_EXCLUDE === $deleted) {
588
            $stored_filter['$and'][0]['deleted'] = false;
589
        } elseif (NodeInterface::DELETED_ONLY === $deleted) {
590
            $stored_filter['$and'][0]['deleted'] = ['$type' => 9];
591
        }
592
593
        $stored_filter['$and'][0] = array_merge($filter, $stored_filter['$and'][0]);
594
595
        return $this->findNodesByFilterRecursiveChildren($stored_filter, $deleted, $offset, $limit);
596
    }
597
598
    /**
599
     * Init node.
600
     */
601
    public function initNode(array $node): NodeInterface
602
    {
603
        $id = $node['_id'];
0 ignored issues
show
Unused Code introduced by
$id 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...
604
605
        if (isset($node['shared']) && true === $node['shared'] && null !== $this->user && $node['owner'] != $this->user->getId()) {
606
            $node = $this->findReferenceNode($node);
607
        }
608
609
        if (isset($node['parent'])) {
610
            $parent = $this->findNodeById($node['parent']);
611
        } elseif ($node['_id'] !== null) {
612
            $parent = $this->getRoot();
613
        } else {
614
            $parent = null;
615
        }
616
617
        if (!array_key_exists('directory', $node)) {
618
            throw new Exception('invalid node ['.$node['_id'].'] found, directory attribute does not exists');
619
        }
620
621
        $instance = $this->node_factory->build($this, $node, $parent);
622
623
        if (!$this->acl->isAllowed($instance, 'r')) {
624
            if ($instance->isReference()) {
625
                $instance->delete(true);
626
            }
627
628
            throw new ForbiddenException(
629
                'not allowed to access node',
630
                ForbiddenException::NOT_ALLOWED_TO_ACCESS
631
            );
632
        }
633
634
        $loaded = isset($this->cache[(string) $node['_id']]);
635
636
        if ($loaded === false) {
637
            $this->cache[(string) $node['_id']] = $instance;
638
        }
639
640
        if ($loaded === false && isset($node['destroy']) && $node['destroy'] instanceof UTCDateTime && $node['destroy']->toDateTime()->format('U') <= time()) {
0 ignored issues
show
Bug introduced by
The class MongoDB\BSON\UTCDateTime does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
641
            $this->logger->info('node ['.$node['_id'].'] is not accessible anmyore, destroy node cause of expired destroy flag', [
642
                'category' => get_class($this),
643
            ]);
644
645
            $instance->delete(true);
646
647
            throw new Exception\Conflict('node is not available anymore');
648
        }
649
650
        if (PHP_SAPI === 'cli') {
651
            unset($this->cache[(string) $node['_id']]);
652
        }
653
654
        return $instance;
655
    }
656
657
    /**
658
     * Find node with path.
659
     */
660
    public function findNodeByPath(string $path = '', ?string $class = null): NodeInterface
661
    {
662
        if (empty($path) || '/' !== $path[0]) {
663
            $path = '/'.$path;
664
        }
665
        $last = strlen($path) - 1;
666
        if ('/' === $path[$last]) {
667
            $path = substr($path, 0, -1);
668
        }
669
        $parts = explode('/', $path);
670
        $parent = $this->getRoot();
671
        array_shift($parts);
672
        $count = count($parts);
673
        $i = 0;
674
        $filter = [];
675
        foreach ($parts as $node) {
676
            ++$i;
677
            if ($count === $i && $class !== null) {
678
                $filter = [
679
                    'directory' => ($class === Collection::class),
680
                ];
681
            }
682
683
            try {
684
                $parent = $parent->getChild($node, NodeInterface::DELETED_EXCLUDE, $filter);
685
            } catch (Exception\NotFound $e) {
686
                if ($count == $i) {
687
                    $parent = $parent->getChild($node, NodeInterface::DELETED_INCLUDE, $filter);
688
                } else {
689
                    throw $e;
690
                }
691
            }
692
        }
693
        if (null !== $class && !($parent instanceof $class)) {
694
            throw new Exception('node is not instance of '.$class);
695
        }
696
697
        return $parent;
698
    }
699
700
    /**
701
     * Prepare children filter.
702
     */
703
    protected function prepareChildrenFilter(int $deleted)
704
    {
705
        $deleted_filter = [];
706
        if (NodeInterface::DELETED_EXCLUDE === $deleted) {
707
            $deleted_filter['deleted'] = false;
708
        } elseif (NodeInterface::DELETED_ONLY === $deleted) {
709
            $deleted_filter['deleted'] = ['$type' => 9];
710
        }
711
712
        $query = ['_id' => ['$exists' => true]];
713
714
        if ($this->user !== null) {
715
            $query = [
716
                '$or' => [
717
                    [
718
                        'acl' => ['$exists' => false],
719
                    ], [
720
                        'acl.id' => (string) $this->user->getId(),
721
                    ],
722
                ],
723
            ];
724
        }
725
726
        if (count($deleted_filter) > 0) {
727
            $query = ['$and' => [$deleted_filter, $query]];
728
        }
729
730
        return $query;
731
    }
732
733
    /**
734
     * Execute complex aggregation.
735
     */
736
    protected function executeAggregation(array $query, ?int $offset = null, ?int $limit = null): Generator
737
    {
738
        $result = $this->db->storage->aggregate($query);
739
740
        $total = 0;
741
        $result = iterator_to_array($result);
742
        if (count($result) > 0) {
743
            $total = $result[0]['total'];
744
        }
745
746
        array_pop($query);
747
748
        $offset !== null ? $query[] = ['$skip' => $offset] : false;
749
        $limit !== null ? $query[] = ['$limit' => $limit] : false;
750
        $result = $this->db->storage->aggregate($query);
751
752
        foreach ($result as $node) {
753
            try {
754
                yield $this->initNode($node);
755
            } catch (\Exception $e) {
756
                $this->logger->error('remove node from result list, failed load node', [
757
                    'category' => get_class($this),
758
                    'exception' => $e,
759
                ]);
760
            }
761
        }
762
763
        return $total;
764
    }
765
766
    /**
767
     * Resolve shared node to reference or share depending who requested.
768
     */
769
    protected function findReferenceNode(array $node): array
770
    {
771
        if (isset($node['reference']) && ($node['reference'] instanceof ObjectId)) {
0 ignored issues
show
Bug introduced by
The class MongoDB\BSON\ObjectId does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
772
            $this->logger->debug('reference node ['.$node['_id'].'] requested from share owner, trying to find the shared node', [
773
                'category' => get_class($this),
774
            ]);
775
776
            $result = $this->db->storage->findOne([
777
                'owner' => $this->user->getId(),
778
                'shared' => true,
779
                '_id' => $node['reference'],
780
            ]);
781
782
            if (null === $result) {
783
                throw new Exception\NotFound(
784
                    'no share node for reference node '.$node['reference'].' found',
785
                    Exception\NotFound::SHARE_NOT_FOUND
786
                );
787
            }
788
        } else {
789
            $this->logger->debug('share node ['.$node['_id'].'] requested from member, trying to find the reference node', [
790
                'category' => get_class($this),
791
            ]);
792
793
            $result = $this->db->storage->findOne([
794
                'owner' => $this->user->getId(),
795
                'shared' => true,
796
                'reference' => $node['_id'],
797
            ]);
798
799
            if (null === $result) {
800
                throw new Exception\NotFound(
801
                    'no share reference for node '.$node['_id'].' found',
802
                    Exception\NotFound::REFERENCE_NOT_FOUND
803
                );
804
            }
805
        }
806
807
        return $result;
808
    }
809
}
810