Issues (20)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/NodeTrait.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php namespace Arcanedev\LaravelNestedSet;
2
3
use Arcanedev\LaravelNestedSet\Contracts\Nodeable;
4
use Arcanedev\LaravelNestedSet\Eloquent\AncestorsRelation;
5
use Arcanedev\LaravelNestedSet\Eloquent\DescendantsRelation;
6
use Arcanedev\LaravelNestedSet\Traits\EloquentTrait;
7
use Arcanedev\LaravelNestedSet\Traits\SoftDeleteTrait;
8
use Arcanedev\LaravelNestedSet\Utilities\NestedSet;
9
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
10
use LogicException;
11
12
/**
13
 * Class     NodeTrait
14
 *
15
 * @package  Arcanedev\LaravelNestedSet
16
 * @author   ARCANEDEV <[email protected]>
17
 *
18
 * @property  int                                             id
19
 * @property  int                                             _lft
20
 * @property  int                                             _rgt
21
 * @property  int                                             parent_id
22
 * @property  array                                           attributes
23
 * @property  array                                           original
24
 * @property  bool                                            exists
25
 * @property  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  parent
26
 * @property  \Illuminate\Database\Eloquent\Collection        children
27
 *
28
 * @method  static  bool                                               isBroken()
29
 * @method  static  array                                              getNodeData($id, $required = false)
30
 * @method  static  array                                              getPlainNodeData($id, $required = false)
31
 * @method  static  int                                                rebuildTree(array $data, bool $delete = false)
32
 * @method  static  int                                                fixTree()
33
 * @method  static  \Arcanedev\LaravelNestedSet\Contracts\Nodeable     root(array $columns = ['*'])
34
 *
35
 * @method  static  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  ancestorsOf(mixed $id, array $columns = ['*'])
36
 * @method  static  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  withDepth(string $as = 'depth')
37
 * @method  static  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  withoutRoot()
38
 * @method  static  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  whereDescendantOf(mixed $id, string $boolean = 'and', bool $not = false)
39
 * @method  static  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  whereAncestorOf(mixed $id)
40
 * @method  static  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  whereIsRoot()
41
 * @method  static  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  descendantsAndSelf(mixed $id, array $columns = ['*'])
42
 * @method  static  array                                              countErrors()
43
 *
44
 * @method  \Illuminate\Database\Eloquent\Relations\BelongsTo          belongsTo(string $related, string $foreignKey = null, string $otherKey = null, string $relation = null)
45
 * @method  \Illuminate\Database\Eloquent\Relations\HasMany            hasMany(string $related, string $foreignKey = null, string $localKey = null)
46
 * @method  static  \Illuminate\Database\Eloquent\Builder              where(string $column, string $operator = null, mixed $value = null, string $boolean = 'and')
47
 */
48
trait NodeTrait
49
{
50
    /* ------------------------------------------------------------------------------------------------
51
     |  Traits
52
     | ------------------------------------------------------------------------------------------------
53
     */
54
    use EloquentTrait, SoftDeleteTrait;
55
56
    /* ------------------------------------------------------------------------------------------------
57
     |  Properties
58
     | ------------------------------------------------------------------------------------------------
59
     */
60
    /**
61
     * Pending operation.
62
     *
63
     * @var array|null
64
     */
65
    protected $pending;
66
67
    /**
68
     * Whether the node has moved since last save.
69
     *
70
     * @var bool
71
     */
72
    protected $moved = false;
73
74
    /**
75
     * Keep track of the number of performed operations.
76
     *
77
     * @var int
78
     */
79
    public static $actionsPerformed = 0;
80
81
    /* ------------------------------------------------------------------------------------------------
82
     |  Boot Function
83
     | ------------------------------------------------------------------------------------------------
84
     */
85
    /**
86
     * Sign on model events.
87
     */
88 216
    public static function bootNodeTrait()
89
    {
90
        static::saving(function ($model) {
91 75
            return $model->callPendingAction();
92 216
        });
93
94
        static::deleting(function ($model) {
95
            // We will need fresh data to delete node safely
96 15
            $model->refreshNode();
97 216
        });
98
99
        static::deleted(function ($model) {
100 15
            $model->deleteDescendants();
101 216
        });
102
103 216
        if (static::usesSoftDelete()) {
104
            static::restoring(function ($model) {
105 3
                static::$deletedAt = $model->{$model->getDeletedAtColumn()};
106 183
            });
107 183
            static::restored(function ($model) {
108 3
                $model->restoreDescendants(static::$deletedAt);
109 183
            });
110 61
        }
111 216
    }
112
113
    /* ------------------------------------------------------------------------------------------------
114
     |  Relationships
115
     | ------------------------------------------------------------------------------------------------
116
     */
117
    /**
118
     * Relation to the parent.
119
     *
120
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
121
     */
122 12
    public function parent()
123
    {
124 12
        return $this->belongsTo(get_class($this), $this->getParentIdName())
125 12
            ->setModel($this);
126
    }
127
128
    /**
129
     * Relation to children.
130
     *
131
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
132
     */
133 12
    public function children()
134
    {
135 12
        return $this->hasMany(get_class($this), $this->getParentIdName())
136 12
            ->setModel($this);
137
    }
138
139
    /**
140
     * Get query for descendants of the node.
141
     *
142
     * @return \Arcanedev\LaravelNestedSet\Eloquent\DescendantsRelation
143
     */
144 36
    public function descendants()
145
    {
146 36
        return new DescendantsRelation($this->newScopedQuery(), $this);
147
    }
148
149
    /**
150
     * Get query for siblings of the node.
151
     *
152
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
153
     */
154 6
    public function siblings()
155
    {
156 6
        return $this->newScopedQuery()
157 6
            ->where($this->getKeyName(), '<>', $this->getKey())
158 6
            ->where($this->getParentIdName(), '=', $this->getParentId());
159
    }
160
161
    /**
162
     * Get query for the node siblings and the node itself.
163
     *
164
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
165
     */
166
    public function siblingsAndSelf()
167
    {
168
        return $this->newScopedQuery()
169
            ->where($this->getParentIdName(), '=', $this->getParentId());
170
    }
171
172
    /**
173
     * Get the node siblings and the node itself.
174
     *
175
     * @param  array  $columns
176
     *
177
     * @return \Illuminate\Database\Eloquent\Collection
178
     */
179
    public function getSiblingsAndSelf(array $columns = ['*'])
180
    {
181
        return $this->siblingsAndSelf()->get($columns);
182
    }
183
184
    /* ------------------------------------------------------------------------------------------------
185
     |  Getters & Setters
186
     | ------------------------------------------------------------------------------------------------
187
     */
188
    /**
189
     * Get the lft key name.
190
     *
191
     * @return string
192
     */
193 198
    public function getLftName()
194
    {
195 198
        return NestedSet::LFT;
196
    }
197
198
    /**
199
     * Get the rgt key name.
200
     *
201
     * @return string
202
     */
203 171
    public function getRgtName()
204
    {
205 171
        return NestedSet::RGT;
206
    }
207
208
    /**
209
     * Get the parent id key name.
210
     *
211
     * @return string
212
     */
213 129
    public function getParentIdName()
214
    {
215 129
        return NestedSet::PARENT_ID;
216
    }
217
218
    /**
219
     * Get the value of the model's lft key.
220
     *
221
     * @return int
222
     */
223 144
    public function getLft()
224
    {
225 144
        return $this->getAttributeValue($this->getLftName());
226 1
    }
227
228
    /**
229
     * Set the value of the model's lft key.
230
     *
231
     * @param  int  $value
232
     *
233
     * @return self
234
     */
235 45
    public function setLft($value)
236
    {
237 45
        $this->attributes[$this->getLftName()] = $value;
238
239 45
        return $this;
240
    }
241
242
    /**
243
     * Get the value of the model's rgt key.
244
     *
245
     * @return int
246
     */
247 96
    public function getRgt()
248
    {
249 96
        return $this->getAttributeValue($this->getRgtName());
250
    }
251
252
    /**
253
     * Set the value of the model's rgt key.
254
     *
255
     * @param  int  $value
256
     *
257
     * @return self
258
     */
259 45
    public function setRgt($value)
260
    {
261 45
        $this->attributes[$this->getRgtName()] = $value;
262
263 45
        return $this;
264
    }
265
266
    /**
267
     * Get the value of the model's parent id key.
268
     *
269
     * @return int
270
     */
271 78
    public function getParentId()
272
    {
273 78
        return $this->getAttributeValue($this->getParentIdName());
274
    }
275
276
    /**
277
     * Set the value of the model's parent id key.
278
     *
279
     * @param  int  $value
280
     *
281
     * @return self
282
     */
283 51
    public function setParentId($value)
284
    {
285 51
        $this->attributes[$this->getParentIdName()] = $value;
286
287 51
        return $this;
288
    }
289
290
    /**
291
     * Apply parent model.
292
     *
293
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable|null  $value
294
     *
295
     * @return self
296
     */
297 42
    protected function setParent($value)
298
    {
299 42
        $this->setParentId($value ? $value->getKey() : null)
300 42
            ->setRelation('parent', $value);
301
302 42
        return $this;
303
    }
304
305
    /**
306
     * Set the value of model's parent id key.
307
     *
308
     * Behind the scenes node is appended to found parent node.
309
     *
310
     * @param  int  $value
311
     *
312
     * @throws \Exception If parent node doesn't exists
313
     */
314 9
    public function setParentIdAttribute($value)
315
    {
316 9
        if ($this->getParentId() == $value) return;
317
318 9
        if ($value) {
319
            /** @var \Arcanedev\LaravelNestedSet\Contracts\Nodeable $node */
320 9
            $node = $this->newScopedQuery()->findOrFail($value);
321
322 9
            $this->appendToNode($node);
323 3
        } else {
324 3
            $this->makeRoot();
325
        }
326 9
    }
327
328
    /**
329
     * Get the boundaries.
330
     *
331
     * @return array
332
     */
333 36
    public function getBounds()
334
    {
335 36
        return [$this->getLft(), $this->getRgt()];
336
    }
337
338
    /**
339
     * Get the scope attributes.
340
     *
341
     * @return array
342
     */
343 123
    protected function getScopeAttributes()
344
    {
345 123
        return null;
346
    }
347
348
    /**
349
     * Set the lft and rgt boundaries to null.
350
     *
351
     * @return self
352
     */
353 51
    protected function dirtyBounds()
354
    {
355 51
        $this->original[$this->getLftName()] = null;
356 51
        $this->original[$this->getRgtName()] = null;
357
358 51
        return $this;
359
    }
360
361
    /**
362
     * Returns node that is next to current node without constraining to siblings.
363
     * This can be either a next sibling or a next sibling of the parent node.
364
     *
365
     * @param  array  $columns
366
     *
367
     * @return self
368
     */
369
    public function getNextNode(array $columns = ['*'])
370
    {
371
        return $this->nextNodes()->defaultOrder()->first($columns);
372
    }
373
374
    /**
375
     * Returns node that is before current node without constraining to siblings.
376
     * This can be either a prev sibling or parent node.
377
     *
378
     * @param  array  $columns
379
     *
380
     * @return self
381
     */
382 3
    public function getPrevNode(array $columns = ['*'])
383
    {
384 3
        return $this->prevNodes()->defaultOrder('desc')->first($columns);
385
    }
386
387
    /**
388
     * Get the ancestors nodes.
389
     *
390
     * @param  array  $columns
391
     *
392
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection
393
     */
394 6
    public function getAncestors(array $columns = ['*'])
395
    {
396 6
        return $this->newScopedQuery()
397 6
            ->defaultOrder()
398 6
            ->ancestorsOf($this, $columns);
399
    }
400
401
    /**
402
     * Get the descendants nodes.
403
     *
404
     * @param  array  $columns
405
     *
406
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection
407
     */
408 9
    public function getDescendants(array $columns = ['*'])
409
    {
410 9
        return $this->descendants()->get($columns);
411
    }
412
413
    /**
414
     * Get the siblings nodes.
415
     *
416
     * @param  array  $columns
417
     *
418
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection
419
     */
420 6
    public function getSiblings(array $columns = ['*'])
421
    {
422 6
        return $this->siblings()->get($columns);
423
    }
424
425
    /**
426
     * Get the next siblings nodes.
427
     *
428
     * @param  array  $columns
429
     *
430
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection
431
     */
432 6
    public function getNextSiblings(array $columns = ['*'])
433
    {
434 6
        return $this->nextSiblings()->get($columns);
435
    }
436
437
    /**
438
     * Get the previous siblings nodes.
439
     *
440
     * @param  array  $columns
441
     *
442
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection
443
     */
444 6
    public function getPrevSiblings(array $columns = ['*'])
445
    {
446 6
        return $this->prevSiblings()->get($columns);
447
    }
448
449
    /**
450
     * Get the next sibling node.
451
     *
452
     * @param  array  $columns
453
     *
454
     * @return self
455
     */
456 3
    public function getNextSibling(array $columns = ['*'])
457
    {
458 3
        return $this->nextSiblings()->defaultOrder()->first($columns);
459
    }
460
461
    /**
462
     * Get the previous sibling node.
463
     *
464
     * @param  array  $columns
465
     *
466
     * @return self
467
     */
468 3
    public function getPrevSibling(array $columns = ['*'])
469
    {
470 3
        return $this->prevSiblings()->defaultOrder('desc')->first($columns);
471
    }
472
473
    /**
474
     * Get node height (rgt - lft + 1).
475
     *
476
     * @return int
477
     */
478 27
    public function getNodeHeight()
479
    {
480 27
        if ( ! $this->exists) return 2;
481
482 3
        return $this->getRgt() - $this->getLft() + 1;
483
    }
484
485
    /**
486
     * Get number of descendant nodes.
487
     *
488
     * @return int
489
     */
490 3
    public function getDescendantCount()
491
    {
492 3
        return (int) ceil($this->getNodeHeight() / 2) - 1;
493
    }
494
495
    /**
496
     * Set raw node.
497
     *
498
     * @param  int  $lft
499
     * @param  int  $rgt
500
     * @param  int  $parentId
501
     *
502
     * @return self
503
     */
504 9
    public function rawNode($lft, $rgt, $parentId)
505
    {
506 9
        $this->setLft($lft)->setRgt($rgt)->setParentId($parentId);
507
508 9
        return $this->setNodeAction('raw');
509
    }
510
511
    /**
512
     * Set an action.
513
     *
514
     * @param  string  $action
515
     *
516
     * @return self
517
     */
518 90
    protected function setNodeAction($action)
519
    {
520 90
        $this->pending = func_get_args();
521 90
        unset($action);
522
523 90
        return $this;
524
    }
525
526
    /* ------------------------------------------------------------------------------------------------
527
     |  Other Functions
528
     | ------------------------------------------------------------------------------------------------
529
     */
530
    /**
531
     * Get the lower bound.
532
     *
533
     * @return int
534
     */
535 24
    protected function getLowerBound()
536
    {
537 24
        return (int) $this->newNestedSetQuery()->max($this->getRgtName());
0 ignored issues
show
Documentation Bug introduced by
The method max does not exist on object<Arcanedev\Laravel...\Eloquent\QueryBuilder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
538
    }
539
540
    /**
541
     * Call pending action.
542
     */
543 75
    protected function callPendingAction()
544
    {
545 75
        $this->moved = false;
546
547 75
        if ( ! $this->pending && ! $this->exists) {
548 15
            $this->makeRoot();
549 5
        }
550
551 75
        if ( ! $this->pending) return;
552
553 72
        $method        = 'action'.ucfirst(array_shift($this->pending));
554 72
        $parameters    = $this->pending;
555
556 72
        $this->pending = null;
557 72
        $this->moved   = call_user_func_array([$this, $method], $parameters);
558 72
    }
559
560
    /**
561
     * @return bool
562
     */
563 9
    protected function actionRaw()
564
    {
565 9
        return true;
566
    }
567
568
    /**
569
     * Make a root node.
570
     */
571 24
    protected function actionRoot()
572
    {
573
        // Simplest case that do not affect other nodes.
574 24
        if ( ! $this->exists) {
575 15
            $cut = $this->getLowerBound() + 1;
576
577 15
            $this->setLft($cut);
578 15
            $this->setRgt($cut + 1);
579
580 15
            return true;
581
        }
582
583 9
        if ($this->isRoot()) return false;
584
585
586
        // Reset parent object
587 9
        $this->setParent(null);
588
589 9
        return $this->insertAt($this->getLowerBound() + 1);
590
    }
591
592
    /**
593
     * Append or prepend a node to the parent.
594
     *
595
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $parent
596
     * @param  bool                                            $prepend
597
     *
598
     * @return bool
599
     */
600 24
    protected function actionAppendOrPrepend(Nodeable $parent, $prepend = false)
601
    {
602 24
        $parent->refreshNode();
603
604 24
        $cut = $prepend ? $parent->getLft() + 1 : $parent->getRgt();
605
606 24
        if ( ! $this->insertAt($cut)) {
607
            return false;
608
        }
609
610 24
        $parent->refreshNode();
611
612 24
        return true;
613
    }
614
615
    /**
616
     * Insert node before or after another node.
617
     *
618
     * @param  self  $node
619
     * @param  bool  $after
620
     *
621
     * @return bool
622
     */
623 21
    protected function actionBeforeOrAfter(self $node, $after = false)
624
    {
625 21
        $node->refreshNode();
626
627 21
        return $this->insertAt($after ? $node->getRgt() + 1 : $node->getLft());
628
    }
629
630
    /**
631
     * Refresh node's crucial attributes.
632
     */
633 66
    public function refreshNode()
634
    {
635 66
        if ( ! $this->exists || static::$actionsPerformed === 0) return;
636
637 48
        $attributes = $this->newNestedSetQuery()->getNodeData($this->getKey());
638
639 48
        $this->attributes = array_merge($this->attributes, $attributes);
640 48
    }
641
642
    /**
643
     * Get query for siblings after the node.
644
     *
645
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
646
     */
647 18
    public function nextSiblings()
648
    {
649 18
        return $this->nextNodes()
650 18
            ->where($this->getParentIdName(), '=', $this->getParentId());
651
    }
652
653
    /**
654
     * Get query for siblings before the node.
655
     *
656
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
657
     */
658 12
    public function prevSiblings()
659
    {
660 12
        return $this->prevNodes()
661 12
            ->where($this->getParentIdName(), '=', $this->getParentId());
662
    }
663
664
    /**
665
     * Get query for nodes after current node.
666
     *
667
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
668
     */
669 21
    public function nextNodes()
670
    {
671 21
        return $this->newScopedQuery()
672 21
            ->where($this->getLftName(), '>', $this->getLft());
673
    }
674
675
    /**
676
     * Get query for nodes before current node in reversed order.
677
     *
678
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
679
     */
680 15
    public function prevNodes()
681
    {
682 15
        return $this->newScopedQuery()
683 15
            ->where($this->getLftName(), '<', $this->getLft());
684
    }
685
686
    /**
687
     * Get query for ancestors to the node not including the node itself.
688
     *
689
     * @return \Arcanedev\LaravelNestedSet\Eloquent\AncestorsRelation
690
     */
691 3
    public function ancestors()
692
    {
693 3
        return new AncestorsRelation($this->newScopedQuery(), $this);
694
    }
695
696
    /**
697
     * Make this node a root node.
698
     *
699
     * @return self
700
     */
701 36
    public function makeRoot()
702
    {
703 36
        return $this->setNodeAction('root');
704
    }
705
706
    /**
707
     * Save node as root.
708
     *
709
     * @return bool
710
     */
711 6
    public function saveAsRoot()
712
    {
713 6
        return ($this->exists && $this->isRoot())
714 2
            ? $this->save()
715 6
            : $this->makeRoot()->save();
716
    }
717
718
    /**
719
     * Append and save a node.
720
     *
721
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
722
     *
723
     * @return bool
724
     */
725 12
    public function appendNode(Nodeable $node)
726
    {
727 12
        return $node->appendToNode($this)->save();
728
    }
729
730
    /**
731
     * Prepend and save a node.
732
     *
733
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
734
     *
735
     * @return bool
736
     */
737 3
    public function prependNode(Nodeable $node)
738
    {
739 3
        return $node->prependToNode($this)->save();
740
    }
741
742
    /**
743
     * Append a node to the new parent.
744
     *
745
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $parent
746
     *
747
     * @return self
748
     */
749 33
    public function appendToNode(Nodeable $parent)
750
    {
751 33
        return $this->appendOrPrependTo($parent);
752
    }
753
754
    /**
755
     * Prepend a node to the new parent.
756
     *
757
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $parent
758
     *
759
     * @return \Arcanedev\LaravelNestedSet\Contracts\Nodeable
760
     */
761 6
    public function prependToNode(Nodeable $parent)
762
    {
763 6
        return $this->appendOrPrependTo($parent, true);
764
    }
765
766
    /**
767
     * Append or prepend a node to parent.
768
     *
769
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $parent
770
     * @param  bool                                            $prepend
771
     *
772
     * @return \Arcanedev\LaravelNestedSet\Contracts\Nodeable
773
     */
774 39
    public function appendOrPrependTo(Nodeable $parent, $prepend = false)
775
    {
776 39
        $this->assertNodeExists($parent)
777 36
             ->assertNotDescendant($parent);
778
779 30
        $this->setParent($parent)->dirtyBounds();
780
781 30
        return $this->setNodeAction('appendOrPrepend', $parent, $prepend);
782
    }
783
784
    /**
785
     * Insert self after a node.
786
     *
787
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
788
     *
789
     * @return \Arcanedev\LaravelNestedSet\Contracts\Nodeable
790
     */
791 21
    public function afterNode(Nodeable $node)
792
    {
793 21
        return $this->beforeOrAfterNode($node, true);
794
    }
795
796
    /**
797
     * Insert self before node.
798
     *
799
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
800
     *
801
     * @return \Arcanedev\LaravelNestedSet\Contracts\Nodeable
802
     */
803 6
    public function beforeNode(Nodeable $node)
804
    {
805 6
        return $this->beforeOrAfterNode($node);
806
    }
807
808
    /**
809
     * Set before or after a node.
810
     *
811
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
812
     * @param  bool                                            $after
813
     *
814
     * @return \Arcanedev\LaravelNestedSet\Contracts\Nodeable
815
     */
816 27
    public function beforeOrAfterNode(Nodeable $node, $after = false)
817
    {
818 27
        $this->assertNodeExists($node)->assertNotDescendant($node);
819
820 24
        if ( ! $this->isSiblingOf($node)) {
821 9
            $this->setParent($node->getRelationValue('parent'));
822 3
        }
823
824 24
        $this->dirtyBounds();
825
826 24
        return $this->setNodeAction('beforeOrAfter', $node, $after);
827
    }
828
829
    /**
830
     * Insert after a node and save.
831
     *
832
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
833
     *
834
     * @return bool
835
     */
836 12
    public function insertAfterNode(Nodeable $node)
837
    {
838 12
        return $this->afterNode($node)->save();
839
    }
840
841
    /**
842
     * Insert self before a node and save.
843
     *
844
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
845
     *
846
     * @return bool
847
     */
848 3
    public function insertBeforeNode(Nodeable $node)
849
    {
850 3
        if ( ! $this->beforeNode($node)->save()) return false;
851
852
        // We'll update the target node since it will be moved
853 3
        $node->refreshNode();
854
855 3
        return true;
856
    }
857
858
    /**
859
     * Move node up given amount of positions.
860
     *
861
     * @param  int  $amount
862
     *
863
     * @return bool
864
     */
865 3
    public function up($amount = 1)
866
    {
867 3
        $sibling = $this->prevSiblings()
0 ignored issues
show
Documentation Bug introduced by
The method skip does not exist on object<Arcanedev\Laravel...\Eloquent\QueryBuilder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
868 3
                        ->defaultOrder('desc')
869 3
                        ->skip($amount - 1)
870 3
                        ->first();
871
872 3
        if ( ! $sibling) return false;
873
874 3
        return $this->insertBeforeNode($sibling);
875
    }
876
877
    /**
878
     * Move node down given amount of positions.
879
     *
880
     * @param  int  $amount
881
     *
882
     * @return bool
883
     */
884 12
    public function down($amount = 1)
885
    {
886 12
        $sibling = $this->nextSiblings()
0 ignored issues
show
Documentation Bug introduced by
The method skip does not exist on object<Arcanedev\Laravel...\Eloquent\QueryBuilder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
887 12
                        ->defaultOrder()
888 12
                        ->skip($amount - 1)
889 12
                        ->first();
890
891 12
        if ( ! $sibling) return false;
892
893 12
        return $this->insertAfterNode($sibling);
894
    }
895
896
    /**
897
     * Insert node at specific position.
898
     *
899
     * @param  int  $position
900
     *
901
     * @return bool
902
     */
903 51
    protected function insertAt($position)
904
    {
905 51
        ++static::$actionsPerformed;
906
907 51
        $result = $this->exists
908 37
            ? $this->moveNode($position)
909 51
            : $this->insertNode($position);
910
911 51
        return $result;
912
    }
913
914
    /**
915
     * Move a node to the new position.
916
     *
917
     * @param  int  $position
918
     *
919
     * @return int
920
     */
921 30
    protected function moveNode($position)
922
    {
923 30
        $updated = $this->newNestedSetQuery()
924 30
                        ->moveNode($this->getKey(), $position) > 0;
925
926 30
        if ($updated) $this->refreshNode();
927
928 30
        return $updated;
929
    }
930
931
    /**
932
     * Insert new node at specified position.
933
     *
934
     * @param  int  $position
935
     *
936
     * @return bool
937
     */
938 24
    protected function insertNode($position)
939
    {
940 24
        $this->newNestedSetQuery()->makeGap($position, 2);
941
942 24
        $height = $this->getNodeHeight();
943
944 24
        $this->setLft($position);
945 24
        $this->setRgt($position + $height - 1);
946
947 24
        return true;
948
    }
949
950
    /**
951
     * Update the tree when the node is removed physically.
952
     */
953 15
    protected function deleteDescendants()
954
    {
955 15
        $lft = $this->getLft();
956 15
        $rgt = $this->getRgt();
957
958 15
        $method = ($this->usesSoftDelete() && $this->forceDeleting)
959 11
            ? 'forceDelete'
960 15
            : 'delete';
961
962 15
        $this->descendants()->{$method}();
963
964 15
        if ($this->hardDeleting()) {
965 12
            $height = $rgt - $lft + 1;
966
967 12
            $this->newNestedSetQuery()->makeGap($rgt + 1, -$height);
968
969
            // In case if user wants to re-create the node
970 12
            $this->makeRoot();
971
972 12
            static::$actionsPerformed++;
973 4
        }
974 15
    }
975
976
    /**
977
     * Restore the descendants.
978
     *
979
     * @param  \Carbon\Carbon  $deletedAt
980
     */
981 3
    protected function restoreDescendants($deletedAt)
982
    {
983 3
        $this->descendants()
0 ignored issues
show
Documentation Bug introduced by
The method where does not exist on object<Arcanedev\Laravel...nt\DescendantsRelation>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
984 3
            ->where($this->getDeletedAtColumn(), '>=', $deletedAt)
985 3
            ->applyScopes()
986 3
            ->restore();
987 3
    }
988
989
    /**
990
     * Get a new base query that includes deleted nodes.
991
     *
992
     * @param  string|null $table
993
     *
994
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
995
     */
996 90
    public function newNestedSetQuery($table = null)
997
    {
998 90
        $builder = $this->usesSoftDelete()
999 78
            ? $this->withTrashed()
1000 90
            : $this->newQuery();
1001
1002 90
        return $this->applyNestedSetScope($builder, $table);
1003
    }
1004
1005
    /**
1006
     * Create a new scoped query.
1007
     *
1008
     * @param  string|null  $table
1009
     *
1010
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
1011
     */
1012 105
    public function newScopedQuery($table = null)
1013
    {
1014 105
        return $this->applyNestedSetScope($this->newQuery(), $table);
1015
    }
1016
1017
    /**
1018
     * Apply the nested set scope.
1019
     *
1020
     * @param  \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder  $query
1021
     * @param  string                                                                    $table
1022
     *
1023
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder|\Illuminate\Database\Query\Builder
1024
     */
1025 156
    public function applyNestedSetScope($query, $table = null)
1026
    {
1027 156
        if ( ! $scoped = $this->getScopeAttributes()) {
1028 123
            return $query;
1029
        }
1030
1031 33
        if ($table === null) {
1032 33
            $table = $this->getTable();
1033 11
        }
1034
1035 33
        foreach ($scoped as $attribute) {
1036 33
            $query->where("$table.$attribute", '=', $this->getAttributeValue($attribute));
1037 11
        }
1038
1039 33
        return $query;
1040
    }
1041
1042
    /**
1043
     * @param  array  $attributes
1044
     *
1045
     * @return self
1046
     */
1047 9
    public static function scoped(array $attributes)
1048
    {
1049 9
        $instance = new static;
1050
1051 9
        $instance->setRawAttributes($attributes);
1052
1053 9
        return $instance->newScopedQuery();
1054
    }
1055
1056
    /**
1057
     * Save a new model and return the instance.
1058
     *
1059
     * Use `children` key on `$attributes` to create child nodes.
1060
     *
1061
     * @param  array  $attributes
1062
     * @param  self   $parent
1063
     *
1064
     * @return self
1065
     */
1066 9
    public static function create(array $attributes = [], self $parent = null)
1067
    {
1068 9
        $children = array_pull($attributes, 'children');
1069 9
        $instance = new static($attributes);
1070
1071 9
        if ($parent) {
1072 3
            $instance->appendToNode($parent);
1073 1
        }
1074
1075 9
        $instance->save();
1076
1077
        // Now create children
1078 9
        $relation = new EloquentCollection;
1079
1080 9
        foreach ((array) $children as $child) {
1081 3
            $relation->add($child = static::create($child, $instance));
1082
1083 3
            $child->setRelation('parent', $instance);
1084 3
        }
1085
1086 9
        return $instance->setRelation('children', $relation);
1087
    }
1088
1089
    /* ------------------------------------------------------------------------------------------------
1090
     |  Check Functions
1091
     | ------------------------------------------------------------------------------------------------
1092
     */
1093
    /**
1094
     * Get whether node is root.
1095
     *
1096
     * @return bool
1097
     */
1098 12
    public function isRoot()
1099
    {
1100 12
        return is_null($this->getParentId());
1101
    }
1102
1103
    /**
1104
     * Get whether a node is a descendant of other node.
1105
     *
1106
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
1107
     *
1108
     * @return bool
1109
     */
1110 57
    public function isDescendantOf(Nodeable $node)
1111
    {
1112
        return (
1113 57
            $this->getLft() > $node->getLft() &&
1114 55
            $this->getLft() < $node->getRgt()
1115 19
        );
1116
    }
1117
1118
    /**
1119
     * Get whether the node is immediate children of other node.
1120
     *
1121
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
1122
     *
1123
     * @return bool
1124
     */
1125 3
    public function isChildOf(Nodeable $node)
1126
    {
1127 3
        return $this->getParentId() == $node->getKey();
1128
    }
1129
1130
    /**
1131
     * Get whether the node is a sibling of another node.
1132
     *
1133
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
1134
     *
1135
     * @return bool
1136
     */
1137 24
    public function isSiblingOf(Nodeable $node)
1138
    {
1139 24
        return $this->getParentId() == $node->getParentId();
1140
    }
1141
1142
    /**
1143
     * Get whether the node is an ancestor of other node, including immediate parent.
1144
     *
1145
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
1146
     *
1147
     * @return bool
1148
     */
1149 3
    public function isAncestorOf(Nodeable $node)
1150
    {
1151
        /** @var \Arcanedev\LaravelNestedSet\Contracts\Nodeable $this */
1152 3
        return $node->isDescendantOf($this);
1153
    }
1154
1155
    /**
1156
     * Get whether the node has moved since last save.
1157
     *
1158
     * @return bool
1159
     */
1160 18
    public function hasMoved()
1161
    {
1162 18
        return $this->moved;
1163
    }
1164
1165
    /* ------------------------------------------------------------------------------------------------
1166
     |  Assertion Functions
1167
     | ------------------------------------------------------------------------------------------------
1168
     */
1169
    /**
1170
     * Assert that the node is not a descendant.
1171
     *
1172
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
1173
     *
1174
     * @return self
1175
     *
1176
     * @throws \LogicException
1177
     */
1178 60
    protected function assertNotDescendant(Nodeable $node)
1179
    {
1180
        /** @var \Arcanedev\LaravelNestedSet\Contracts\Nodeable $this */
1181 60
        if ($node == $this || $node->isDescendantOf($this)) {
1182 9
            throw new LogicException('Node must not be a descendant.');
1183
        }
1184
1185 51
        return $this;
1186
    }
1187
1188
    /**
1189
     * Assert node exists.
1190
     *
1191
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
1192
     *
1193
     * @return self
1194
     *
1195
     * @throws \LogicException
1196
     */
1197 63
    protected function assertNodeExists(Nodeable $node)
1198
    {
1199 63
        if ( ! $node->getLft() || ! $node->getRgt()) {
1200 3
            throw new LogicException('Node must exists.');
1201
        }
1202
1203 60
        return $this;
1204
    }
1205
}
1206