Test Failed
Push — master ( 817d84...d52e3d )
by Raffael
05:44
created

AbstractNode::isRoot()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 2
crap 6
nc 2
nop 0
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\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;
20
use Balloon\Server\User;
21
use MimeType\MimeType;
22
use MongoDB\BSON\ObjectId;
23
use MongoDB\BSON\UTCDateTime;
24
use MongoDB\Database;
25
use Normalizer;
26
use Psr\Log\LoggerInterface;
27
use ZipStream\ZipStream;
28
29
abstract class AbstractNode implements NodeInterface
30
{
31
    /**
32
     * name max lenght.
33
     */
34
    const MAX_NAME_LENGTH = 255;
35
36
    /**
37
     * Unique id.
38
     *
39
     * @var ObjectId
40
     */
41
    protected $_id;
42
43
    /**
44
     * Node name.
45
     *
46
     * @var string
47
     */
48
    protected $name = '';
49
50
    /**
51
     * Owner.
52
     *
53
     * @var ObjectId
54
     */
55
    protected $owner;
56
57
    /**
58
     * Mime.
59
     *
60
     * @var string
61
     */
62
    protected $mime;
63
64
    /**
65
     * Meta attributes.
66
     *
67
     * @var array
68
     */
69
    protected $meta = [];
70
71
    /**
72
     * Parent collection.
73
     *
74
     * @var ObjectId
75
     */
76
    protected $parent;
77
78
    /**
79
     * Is file deleted.
80
     *
81
     * @var bool|UTCDateTime
82
     */
83
    protected $deleted = false;
84
85
    /**
86
     * Is shared?
87
     *
88
     * @var bool
89
     */
90
    protected $shared = false;
91
92
    /**
93
     * Destory at a certain time.
94
     *
95
     * @var UTCDateTime
96
     */
97
    protected $destroy;
98
99
    /**
100
     * Changed timestamp.
101
     *
102
     * @var UTCDateTime
103
     */
104
    protected $changed;
105
106
    /**
107
     * Created timestamp.
108
     *
109
     * @var UTCDateTime
110
     */
111
    protected $created;
112
113
    /**
114
     * Point to antother node (Means this node is reference to $reference).
115
     *
116
     * @var ObjectId
117
     */
118
    protected $reference;
119
120
    /**
121
     * Raw attributes before any processing or modifications.
122
     *
123
     * @var array
124
     */
125
    protected $raw_attributes;
126
127
    /**
128
     * Readonly flag.
129
     *
130
     * @var bool
131
     */
132
    protected $readonly = false;
133
134
    /**
135
     * App attributes.
136
     *
137
     * @var array
138
     */
139
    protected $app = [];
140
141
    /**
142
     * Filesystem.
143
     *
144
     * @var Filesystem
145
     */
146
    protected $_fs;
147
148
    /**
149
     * Database.
150
     *
151
     * @var Database
152
     */
153
    protected $_db;
154
155
    /**
156
     * User.
157
     *
158
     * @var User
159
     */
160
    protected $_user;
161
162
    /**
163
     * Logger.
164
     *
165
     * @var LoggerInterface
166
     */
167
    protected $_logger;
168
169
    /**
170
     * Server.
171
     *
172
     * @var Server
173
     */
174
    protected $_server;
175
176
    /**
177
     * Hook.
178
     *
179
     * @var Hook
180
     */
181
    protected $_hook;
182
183
    /**
184
     * Acl.
185
     *
186
     * @var Acl
187
     */
188
    protected $_acl;
189
190
    /**
191
     * Mount.
192
     *
193
     * @var ObjectId
194
     */
195
    protected $storage_reference;
196
197
    /**
198
     * Storage attributes.
199
     *
200
     * @var array
201
     */
202
    protected $storage;
203
204
    /**
205
     * Acl.
206
     *
207
     * @var array
208
     */
209
    protected $acl = [];
210
211
    /**
212
     * Mount.
213
     *
214
     * @var array
215
     */
216
    protected $mount = [];
217
218
    /**
219
     * Parent collection.
220
     *
221
     * @var Collection
222
     */
223
    protected $_parent;
224
225
    /**
226
     * Convert to filename.
227
     *
228
     * @return string
229
     */
230
    public function __toString()
231
    {
232
        return $this->name;
233
    }
234
235
    /**
236
     * Get owner.
237
     */
238
    public function getOwner(): ObjectId
239
    {
240
        return $this->owner;
241
    }
242
243
    /**
244
     * Set filesystem.
245
     */
246
    public function setFilesystem(Filesystem $fs): NodeInterface
247
    {
248
        $this->_fs = $fs;
249
        $this->_user = $fs->getUser();
250
251
        return $this;
252
    }
253
254
    /**
255
     * Get filesystem.
256
     */
257
    public function getFilesystem(): Filesystem
258
    {
259
        return $this->_fs;
260
    }
261
262
    /**
263
     * Check if $node is a sub node of any parent nodes of this node.
264
     */
265
    public function isSubNode(NodeInterface $node): bool
266
    {
267
        if ($node->getId() == $this->_id) {
268
            return true;
269
        }
270
271
        foreach ($node->getParents() as $node) {
272
            if ($node->getId() == $this->_id) {
273
                return true;
274
            }
275
        }
276
277
        if ($this->isRoot()) {
278
            return true;
279
        }
280
281
        return false;
282
    }
283
284
    /**
285
     * Move node.
286
     */
287
    public function setParent(Collection $parent, int $conflict = NodeInterface::CONFLICT_NOACTION): NodeInterface
288
    {
289
        if ($this->parent == $parent->getId()) {
290
            throw new Exception\Conflict(
291
                'source node '.$this->name.' is already in the requested parent folder',
292
                Exception\Conflict::ALREADY_THERE
293
            );
294
        }
295
        if ($this->isSubNode($parent)) {
296
            throw new Exception\Conflict(
297
                'node called '.$this->name.' can not be moved into itself',
298
                Exception\Conflict::CANT_BE_CHILD_OF_ITSELF
299
            );
300
        }
301
        if (!$this->_acl->isAllowed($this, 'w') && !$this->isReference()) {
302
            throw new ForbiddenException(
303
                'not allowed to move node '.$this->name,
304
                ForbiddenException::NOT_ALLOWED_TO_MOVE
305
            );
306
        }
307
308
        $exists = $parent->childExists($this->name);
309
        if (true === $exists && NodeInterface::CONFLICT_NOACTION === $conflict) {
310
            throw new Exception\Conflict(
311
                'a node called '.$this->name.' does already exists in this collection',
312
                Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
313
            );
314
        }
315
        if ($this->isShared() && $this instanceof Collection && $parent->isShared()) {
316
            throw new Exception\Conflict(
317
                'a shared folder can not be a child of a shared folder',
318
                Exception\Conflict::SHARED_NODE_CANT_BE_CHILD_OF_SHARE
319
            );
320
        }
321
        if ($parent->isDeleted()) {
322
            throw new Exception\Conflict(
323
                'cannot move node into a deleted collction',
324
                Exception\Conflict::DELETED_PARENT
325
            );
326
        }
327
328
        if (true === $exists && NodeInterface::CONFLICT_RENAME === $conflict) {
329
            $this->setName($this->getDuplicateName());
330
            $this->raw_attributes['name'] = $this->name;
331
        }
332
333
        if ($parent->isFiltered()) {
334
            throw new Exception\Conflict(
335
                'can not move node into a filtered parent collection',
336
                Exception\Conflict::DYNAMIC_PARENT
337
            );
338
        }
339
340
        if ($this instanceof Collection) {
341
            $query = [
342
                '$or' => [
343
                    ['reference' => ['exists' => true]],
344
                    ['shared' => true],
345
                ],
346
            ];
347
348
            if ($parent->isShared() && iterator_count($this->_fs->findNodesByFilterRecursive($this, $query, 0, 1)) !== 0) {
349
                throw new Exception\Conflict(
350
                    'folder contains a shared folder',
351
                    Exception\Conflict::NODE_CONTAINS_SHARED_NODE
352
                );
353
            }
354
        }
355
356
        if ($this->isShared() && $parent->isSpecial()) {
357
            throw new Exception\Conflict(
358
                'a shared folder can not be an indirect child of a shared folder',
359
                Exception\Conflict::SHARED_NODE_CANT_BE_INDIRECT_CHILD_OF_SHARE
360
            );
361
        }
362
363
        if (($parent->isSpecial() && $this->shared != $parent->getShareId())
364
          || (!$parent->isSpecial() && $this->isShareMember())
365
          || ($parent->getMount() != $this->getParent()->getMount())) {
366
            $new = $this->copyTo($parent, $conflict);
367
            $this->delete();
368
369
            return $new;
370
        }
371
372
        if (true === $exists && NodeInterface::CONFLICT_MERGE === $conflict) {
373
            $new = $this->copyTo($parent, $conflict);
374
            $this->delete(true);
375
376
            return $new;
377
        }
378
379
        $this->storage = $this->_parent->getStorage()->move($this, $parent);
380
        $this->parent = $parent->getRealId();
381
        $this->owner = $this->_user->getId();
382
383
        $this->save(['parent', 'shared', 'owner', 'storage']);
384
385
        return $this;
386
    }
387
388
    /**
389
     * Set node acl.
390
     */
391
    public function setAcl(array $acl): NodeInterface
392
    {
393
        if (!$this->_acl->isAllowed($this, 'm')) {
394
            throw new ForbiddenException(
395
                'not allowed to update acl',
396
                ForbiddenException::NOT_ALLOWED_TO_MANAGE
397
            );
398
        }
399
400
        if (!$this->isShareMember()) {
401
            throw new Exception\Conflict('node acl may only be set on share member nodes', Exception\Conflict::NOT_SHARED);
402
        }
403
404
        $this->_acl->validateAcl($this->_server, $acl);
405
        $this->acl = $acl;
406
        $this->save(['acl']);
407
408
        return $this;
409
    }
410
411
    /**
412
     * Get ACL.
413
     */
414
    public function getAcl(): array
415
    {
416
        if ($this->isReference()) {
417
            $acl = $this->_fs->findRawNode($this->getShareId())['acl'];
0 ignored issues
show
Bug introduced by
It seems like $this->getShareId() targeting Balloon\Filesystem\Node\AbstractNode::getShareId() can also be of type boolean or null; however, Balloon\Filesystem::findRawNode() does only seem to accept object<MongoDB\BSON\ObjectId>, maybe add an additional type check?

This check looks at variables that 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...
418
        } else {
419
            $acl = $this->acl;
420
        }
421
422
        return $this->_acl->resolveAclTable($this->_server, $acl);
423
    }
424
425
    /**
426
     * Get share id.
427
     */
428
    public function getShareId(bool $reference = false): ?ObjectId
429
    {
430
        if ($this->isReference() && true === $reference) {
431
            return $this->_id;
432
        }
433
        if ($this->isShareMember() && true === $reference) {
434
            return $this->shared;
435
        }
436
        if ($this->isShared() && $this->isReference()) {
437
            return $this->reference;
438
        }
439
        if ($this->isShared()) {
440
            return $this->_id;
441
        }
442
        if ($this->isShareMember()) {
443
            return $this->shared;
444
        }
445
446
        return null;
447
    }
448
449
    /**
450
     * Get share node.
451
     */
452
    public function getShareNode(): ?Collection
453
    {
454
        if ($this->isShare()) {
455
            return $this;
456
        }
457
458
        if ($this->isSpecial()) {
459
            return $this->_fs->findNodeById($this->getShareId(true));
460
        }
461
462
        return null;
463
    }
464
465
    /**
466
     * Is node marked as readonly?
467
     */
468
    public function isReadonly(): bool
469
    {
470
        return $this->readonly;
471
    }
472
473
    /**
474
     * Request is from node owner?
475
     */
476
    public function isOwnerRequest(): bool
477
    {
478
        return null !== $this->_user && $this->owner == $this->_user->getId();
479
    }
480
481
    /**
482
     * Check if node is kind of special.
483
     */
484
    public function isSpecial(): bool
485
    {
486
        if ($this->isShared()) {
487
            return true;
488
        }
489
        if ($this->isReference()) {
490
            return true;
491
        }
492
        if ($this->isShareMember()) {
493
            return true;
494
        }
495
496
        return false;
497
    }
498
499
    /**
500
     * Check if node is a sub node of a share.
501
     */
502
    public function isShareMember(): bool
503
    {
504
        return $this->shared instanceof ObjectId && !$this->isReference();
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...
505
    }
506
507
    /**
508
     * Check if node is a sub node of an external storage mount.
509
     */
510
    public function isMountMember(): bool
511
    {
512
        return $this->storage_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...
513
    }
514
515
    /**
516
     * Is share.
517
     */
518
    public function isShare(): bool
519
    {
520
        return true === $this->shared && !$this->isReference();
521
    }
522
523
    /**
524
     * Is share (Reference or master share).
525
     */
526
    public function isShared(): bool
527
    {
528
        if (true === $this->shared) {
529
            return true;
530
        }
531
532
        return false;
533
    }
534
535
    /**
536
     * Set the name.
537
     */
538
    public function setName($name): bool
539
    {
540
        $name = $this->checkName($name);
541
542
        try {
543
            $child = $this->getParent()->getChild($name);
544
            if ($child->getId() != $this->_id) {
545
                throw new Exception\Conflict(
546
                    'a node called '.$name.' does already exists in this collection',
547
                    Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
548
                );
549
            }
550
        } catch (Exception\NotFound $e) {
551
            //child does not exists, we can safely rename
552
        }
553
554
        $this->storage = $this->_parent->getStorage()->rename($this, $name);
555
        $this->name = $name;
556
557
        if ($this instanceof File) {
558
            $this->mime = MimeType::getType($this->name);
559
        }
560
561
        return $this->save(['name', 'storage', 'mime']);
562
    }
563
564
    /**
565
     * Check name.
566
     */
567
    public function checkName(string $name): string
568
    {
569
        if (preg_match('/([\\\<\>\:\"\/\|\*\?])|(^$)|(^\.$)|(^\..$)/', $name)) {
570
            throw new Exception\InvalidArgument('name contains invalid characters');
571
        }
572
        if (strlen($name) > self::MAX_NAME_LENGTH) {
573
            throw new Exception\InvalidArgument('name is longer than '.self::MAX_NAME_LENGTH.' characters');
574
        }
575
576
        if (!Normalizer::isNormalized($name)) {
577
            $name = Normalizer::normalize($name);
578
        }
579
580
        return $name;
581
    }
582
583
    /**
584
     * Get the name.
585
     */
586
    public function getName(): string
587
    {
588
        return $this->name;
589
    }
590
591
    /**
592
     * Get mount node.
593
     */
594
    public function getMount(): ?ObjectId
595
    {
596
        return count($this->mount) > 0 ? $this->_id : $this->storage_reference;
597
    }
598
599
    /**
600
     * Undelete.
601
     */
602
    public function undelete(int $conflict = NodeInterface::CONFLICT_NOACTION, ?string $recursion = null, bool $recursion_first = true): bool
603
    {
604
        if (!$this->_acl->isAllowed($this, 'w') && !$this->isReference()) {
605
            throw new ForbiddenException(
606
                'not allowed to restore node '.$this->name,
607
                ForbiddenException::NOT_ALLOWED_TO_UNDELETE
608
            );
609
        }
610
611
        $parent = $this->getParent();
612
        if ($parent->isDeleted()) {
613
            throw new Exception\Conflict(
614
                'could not restore node '.$this->name.' into a deleted parent',
615
                Exception\Conflict::DELETED_PARENT
616
            );
617
        }
618
619
        if ($parent->childExists($this->name)) {
620
            if (NodeInterface::CONFLICT_MERGE === $conflict) {
621
                $new = $this->copyTo($parent, $conflict, null, true, NodeInterface::DELETED_INCLUDE);
622
623
                if ($new->getId() != $this->getId()) {
624
                    $this->delete(true);
625
                }
626
            } elseif (NodeInterface::CONFLICT_RENAME === $conflict) {
627
                $this->setName($this->getDuplicateName());
628
                $this->raw_attributes['name'] = $this->name;
629
            } else {
630
                throw new Exception\Conflict(
631
                    'a node called '.$this->name.' does already exists in this collection',
632
                    Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
633
                );
634
            }
635
        }
636
637
        if (null === $recursion) {
638
            $recursion_first = true;
639
            $recursion = uniqid();
640
        } else {
641
            $recursion_first = false;
642
        }
643
644
        $this->storage = $this->_parent->getStorage()->undelete($this);
645
        $this->deleted = false;
646
647
        $this->save([
648
                'storage',
649
                'name',
650
                'deleted',
651
            ], [], $recursion, $recursion_first);
652
653
        if ($this instanceof File || $this->isReference() || $this->isMounted() || $this->isFiltered()) {
654
            return true;
655
        }
656
657
        return $this->doRecursiveAction(function ($node) use ($conflict, $recursion) {
658
            $node->undelete($conflict, $recursion, false);
659
        }, NodeInterface::DELETED_ONLY);
660
    }
661
662
    /**
663
     * Is node deleted?
664
     */
665
    public function isDeleted(): bool
666
    {
667
        return $this->deleted instanceof UTCDateTime;
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...
668
    }
669
670
    /**
671
     * Get last modified timestamp.
672
     */
673
    public function getLastModified(): int
674
    {
675
        if ($this->changed instanceof UTCDateTime) {
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...
676
            return (int) $this->changed->toDateTime()->format('U');
677
        }
678
679
        return 0;
680
    }
681
682
    /**
683
     * Get unique id.
684
     */
685 1
    public function getId(): ?ObjectId
686
    {
687 1
        return $this->_id;
688
    }
689
690
    /**
691
     * Get parent.
692
     */
693
    public function getParent(): ?Collection
694
    {
695
        return $this->_parent;
696
    }
697
698
    /**
699
     * Get parents.
700
     */
701
    public function getParents(?NodeInterface $node = null, array $parents = []): array
702
    {
703
        if (null === $node) {
704
            $node = $this;
705
        }
706
707
        if ($node->isInRoot()) {
708
            return $parents;
709
        }
710
        $parent = $node->getParent();
711
        $parents[] = $parent;
712
713
        return $node->getParents($parent, $parents);
714
    }
715
716
    /**
717
     * Get as zip.
718
     */
719
    public function getZip(): void
720
    {
721
        $archive = new ZipStream($this->name.'.zip');
722
        $this->zip($archive, false);
723
        $archive->finish();
724
    }
725
726
    /**
727
     * Create zip.
728
     */
729
    public function zip(ZipStream $archive, bool $self = true, ?NodeInterface $parent = null, string $path = '', int $depth = 0): bool
730
    {
731
        if (null === $parent) {
732
            $parent = $this;
733
        }
734
735
        if ($parent instanceof Collection) {
736
            $children = $parent->getChildNodes();
737
738
            if (true === $self && 0 === $depth) {
739
                $path = $parent->getName().DIRECTORY_SEPARATOR;
0 ignored issues
show
Bug introduced by
Consider using $parent->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
740
            } elseif (0 === $depth) {
741
                $path = '';
742
            } elseif (0 !== $depth) {
743
                $path .= DIRECTORY_SEPARATOR.$parent->getName().DIRECTORY_SEPARATOR;
0 ignored issues
show
Bug introduced by
Consider using $parent->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
744
            }
745
746
            foreach ($children as $child) {
747
                $name = $path.$child->getName();
748
749
                if ($child instanceof Collection) {
750
                    $this->zip($archive, $self, $child, $name, ++$depth);
751
                } elseif ($child instanceof File) {
752
                    try {
753
                        $resource = $child->get();
754
                        if ($resource !== null) {
755
                            $archive->addFileFromStream($name, $resource);
756
                        }
757
                    } catch (\Exception $e) {
758
                        $this->_logger->error('failed add file ['.$child->getId().'] to zip stream', [
759
                            'category' => get_class($this),
760
                            'exception' => $e,
761
                        ]);
762
                    }
763
                }
764
            }
765
        } elseif ($parent instanceof File) {
766
            $resource = $parent->get();
767
            if ($resource !== null) {
768
                $archive->addFileFromStream($parent->getName(), $resource);
0 ignored issues
show
Bug introduced by
Consider using $parent->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
769
            }
770
        }
771
772
        return true;
773
    }
774
775
    /**
776
     * Get mime type.
777
     */
778 1
    public function getContentType(): string
779
    {
780 1
        return $this->mime;
781
    }
782
783
    /**
784
     * Is reference.
785
     */
786
    public function isReference(): bool
787
    {
788
        return $this->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...
789
    }
790
791
    /**
792
     * Set app attributes.
793
     */
794
    public function setAppAttributes(string $namespace, array $attributes): NodeInterface
795
    {
796
        $this->app[$namespace] = $attributes;
797
        $this->save('app.'.$namespace);
798
799
        return $this;
800
    }
801
802
    /**
803
     * Set app attribute.
804
     */
805
    public function setAppAttribute(string $namespace, string $attribute, $value): NodeInterface
806
    {
807
        if (!isset($this->app[$namespace])) {
808
            $this->app[$namespace] = [];
809
        }
810
811
        $this->app[$namespace][$attribute] = $value;
812
        $this->save('app.'.$namespace);
813
814
        return $this;
815
    }
816
817
    /**
818
     * Remove app attribute.
819
     */
820
    public function unsetAppAttributes(string $namespace): NodeInterface
821
    {
822
        if (isset($this->app[$namespace])) {
823
            unset($this->app[$namespace]);
824
            $this->save('app.'.$namespace);
825
        }
826
827
        return $this;
828
    }
829
830
    /**
831
     * Remove app attribute.
832
     */
833
    public function unsetAppAttribute(string $namespace, string $attribute): NodeInterface
834
    {
835
        if (isset($this->app[$namespace][$attribute])) {
836
            unset($this->app[$namespace][$attribute]);
837
            $this->save('app'.$namespace);
838
        }
839
840
        return $this;
841
    }
842
843
    /**
844
     * Get app attribute.
845
     */
846
    public function getAppAttribute(string $namespace, string $attribute)
847
    {
848
        if (isset($this->app[$namespace][$attribute])) {
849
            return $this->app[$namespace][$attribute];
850
        }
851
852
        return null;
853
    }
854
855
    /**
856
     * Get app attributes.
857
     */
858
    public function getAppAttributes(string $namespace): array
859
    {
860
        if (isset($this->app[$namespace])) {
861
            return $this->app[$namespace];
862
        }
863
864
        return [];
865
    }
866
867
    /**
868
     * Set meta attributes.
869
     */
870
    public function setMetaAttributes(array $attributes): NodeInterface
871
    {
872
        $attributes = $this->validateMetaAttributes($attributes);
873
        foreach ($attributes as $attribute => $value) {
874
            if (empty($value) && isset($this->meta[$attribute])) {
875
                unset($this->meta[$attribute]);
876
            } elseif (!empty($value)) {
877
                $this->meta[$attribute] = $value;
878
            }
879
        }
880
881
        $this->save('meta');
882
883
        return $this;
884
    }
885
886
    /**
887
     * Get meta attributes as array.
888
     */
889
    public function getMetaAttributes(array $attributes = []): array
890
    {
891
        if (empty($attributes)) {
892
            return $this->meta;
893
        }
894
        if (is_array($attributes)) {
895
            return array_intersect_key($this->meta, array_flip($attributes));
896
        }
897
    }
898
899
    /**
900
     * Mark node as readonly.
901
     */
902
    public function setReadonly(bool $readonly = true): bool
903
    {
904
        $this->readonly = $readonly;
905
        $this->storage = $this->_parent->getStorage()->readonly($this, $readonly);
906
907
        return $this->save(['readonly', 'storage']);
908
    }
909
910
    /**
911
     * Mark node as self-destroyable.
912
     */
913
    public function setDestroyable(?UTCDateTime $ts): bool
914
    {
915
        $this->destroy = $ts;
916
917
        if (null === $ts) {
918
            return $this->save([], 'destroy');
919
        }
920
921
        return $this->save('destroy');
922
    }
923
924
    /**
925
     * Get original raw attributes before any processing.
926
     */
927
    public function getRawAttributes(): array
928
    {
929
        return $this->raw_attributes;
930
    }
931
932
    /**
933
     * Check if node is in root.
934
     */
935
    public function isInRoot(): bool
936
    {
937
        return null === $this->parent;
938
    }
939
940
    /**
941
     * Check if node is an instance of the actual root collection.
942
     */
943
    public function isRoot(): bool
944
    {
945
        return null === $this->_id && ($this instanceof Collection);
946
    }
947
948
    /**
949
     * Resolve node path.
950
     */
951
    public function getPath(): string
952
    {
953
        $path = '';
954
        foreach (array_reverse($this->getParents()) as $parent) {
955
            $path .= DIRECTORY_SEPARATOR.$parent->getName();
956
        }
957
958
        $path .= DIRECTORY_SEPARATOR.$this->getName();
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
959
960
        return $path;
961
    }
962
963
    /**
964
     * Save node attributes.
965
     *
966
     * @param array|string $attributes
967
     * @param array|string $remove
968
     * @param string       $recursion
969
     */
970
    public function save($attributes = [], $remove = [], ?string $recursion = null, bool $recursion_first = true): bool
971
    {
972
        if (!$this->_acl->isAllowed($this, 'w') && !$this->isReference()) {
973
            throw new ForbiddenException(
974
                'not allowed to modify node '.$this->name,
975
                ForbiddenException::NOT_ALLOWED_TO_MODIFY
976
            );
977
        }
978
979
        if ($this instanceof Collection && $this->isRoot()) {
980
            return false;
981
        }
982
983
        $remove = (array) $remove;
984
        $attributes = (array) $attributes;
985
        $this->_hook->run(
986
            'preSaveNodeAttributes',
987
            [$this, &$attributes, &$remove, &$recursion, &$recursion_first]
988
        );
989
990
        try {
991
            $set = [];
992
            $values = $this->getAttributes();
993
            foreach ($attributes as $attr) {
994
                $set[$attr] = $this->getArrayValue($values, $attr);
995
            }
996
997
            $update = [];
998
            if (!empty($set)) {
999
                $update['$set'] = $set;
1000
            }
1001
1002
            if (!empty($remove)) {
1003
                $remove = array_fill_keys($remove, 1);
1004
                $update['$unset'] = $remove;
1005
            }
1006
1007
            if (empty($update)) {
1008
                return false;
1009
            }
1010
            $result = $this->_db->storage->updateOne([
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...
1011
                    '_id' => $this->_id,
1012
                ], $update);
1013
1014
            $this->_hook->run(
1015
                'postSaveNodeAttributes',
1016
                [$this, $attributes, $remove, $recursion, $recursion_first]
1017
            );
1018
1019
            $this->_logger->info('modified node attributes of ['.$this->_id.']', [
1020
                'category' => get_class($this),
1021
                'params' => $update,
1022
            ]);
1023
1024
            return true;
1025
        } catch (\Exception $e) {
1026
            $this->_logger->error('failed modify node attributes of ['.$this->_id.']', [
1027
                'category' => get_class($this),
1028
                'exception' => $e,
1029
            ]);
1030
1031
            throw $e;
1032
        }
1033
    }
1034
1035
    /**
1036
     * Get array value via string path.
1037
     */
1038
    protected function getArrayValue(Iterable $array, string $path, string $separator = '.')
1039
    {
1040
        if (isset($array[$path])) {
1041
            return $array[$path];
1042
        }
1043
        $keys = explode($separator, $path);
1044
1045
        foreach ($keys as $key) {
1046
            if (!array_key_exists($key, $array)) {
1047
                return;
1048
            }
1049
1050
            $array = $array[$key];
1051
        }
1052
1053
        return $array;
1054
    }
1055
1056
    /**
1057
     * Validate meta attributes.
1058
     */
1059
    protected function validateMetaAttributes(array $attributes): array
1060
    {
1061
        foreach ($attributes as $attribute => $value) {
1062
            $const = __CLASS__.'::META_'.strtoupper($attribute);
1063
            if (!defined($const)) {
1064
                throw new Exception('meta attribute '.$attribute.' is not valid');
1065
            }
1066
1067
            if ($attribute === NodeInterface::META_TAGS && !empty($value) && (!is_array($value) || array_filter($value, 'is_string') != $value)) {
1068
                throw new Exception('tags meta attribute must be an array of strings');
1069
            }
1070
1071
            if ($attribute !== NodeInterface::META_TAGS && !is_string($value)) {
1072
                throw new Exception($attribute.' meta attribute must be a string');
1073
            }
1074
        }
1075
1076
        return $attributes;
1077
    }
1078
1079
    /**
1080
     * Duplicate name with a uniqid within name.
1081
     */
1082
    protected function getDuplicateName(?string $name = null, ?string $class = null): string
1083
    {
1084
        if (null === $name) {
1085
            $name = $this->name;
1086
        }
1087
1088
        if (null === $class) {
1089
            $class = get_class($this);
1090
        }
1091
1092
        if ($class === Collection::class) {
1093
            return $name.' ('.substr(uniqid('', true), -4).')';
1094
        }
1095
1096
        $ext = substr(strrchr($name, '.'), 1);
1097
        if (false === $ext) {
1098
            return $name.' ('.substr(uniqid('', true), -4).')';
1099
        }
1100
1101
        $name = substr($name, 0, -(strlen($ext) + 1));
1102
1103
        return $name.' ('.substr(uniqid('', true), -4).')'.'.'.$ext;
1104
    }
1105
1106
    /**
1107
     * Completly remove node.
1108
     */
1109
    abstract protected function _forceDelete(): bool;
1110
}
1111