Issues (372)

Security Analysis    no request data  

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.

NestedSetsBehavior.php (3 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
2
/**
3
 * @link https://github.com/paulzi/yii2-nested-sets
4
 * @copyright Copyright (c) 2015 PaulZi <[email protected]>
5
 * @license MIT (https://github.com/paulzi/yii2-nested-sets/blob/master/LICENSE)
6
 */
7
8
namespace paulzi\nestedsets;
9
10
use Yii;
11
use yii\base\Behavior;
12
use yii\base\Exception;
13
use yii\base\NotSupportedException;
14
use yii\db\ActiveRecord;
15
use yii\db\Expression;
16
17
/**
18
 * Nested Sets Behavior for Yii2
19
 * @author PaulZi <[email protected]>
20
 * @author Alexander Kochetov <https://github.com/creocoder>
21
 *
22
 * @property ActiveRecord $owner
23
 */
24
class NestedSetsBehavior extends Behavior
25
{
26
    const OPERATION_MAKE_ROOT       = 1;
27
    const OPERATION_PREPEND_TO      = 2;
28
    const OPERATION_APPEND_TO       = 3;
29
    const OPERATION_INSERT_BEFORE   = 4;
30
    const OPERATION_INSERT_AFTER    = 5;
31
    const OPERATION_DELETE_ALL      = 6;
32
33
34
    /**
35
     * @var string|null
36
     */
37
    public $treeAttribute;
38
39
    /**
40
     * @var string
41
     */
42
    public $leftAttribute = 'lft';
43
44
    /**
45
     * @var string
46
     */
47
    public $rightAttribute = 'rgt';
48
49
    /**
50
     * @var string
51
     */
52
    public $depthAttribute = 'depth';
53
54
    /**
55
     * @var string|null
56
     */
57
    protected $operation;
58
59
    /**
60
     * @var ActiveRecord|self|null
61
     */
62
    protected $node;
63
64
    /**
65
     * @var string
66
     */
67
    protected $treeChange;
68
69
70
    /**
71
     * @inheritdoc
72
     */
73 201
    public function events()
74
    {
75
        return [
76 201
            ActiveRecord::EVENT_BEFORE_INSERT   => 'beforeInsert',
77
            ActiveRecord::EVENT_AFTER_INSERT    => 'afterInsert',
78
            ActiveRecord::EVENT_BEFORE_UPDATE   => 'beforeUpdate',
79
            ActiveRecord::EVENT_AFTER_UPDATE    => 'afterUpdate',
80
            ActiveRecord::EVENT_BEFORE_DELETE   => 'beforeDelete',
81
            ActiveRecord::EVENT_AFTER_DELETE    => 'afterDelete',
82
        ];
83
    }
84
85
    /**
86
     * @param int|null $depth
87
     * @return \yii\db\ActiveQuery
88
     */
89 6
    public function getParents($depth = null)
90
    {
91 6
        $tableName = $this->owner->tableName();
92
        $condition = [
93 6
            'and',
94 6
            ['<', "{$tableName}.[[{$this->leftAttribute}]]",  $this->owner->getAttribute($this->leftAttribute)],
95 6
            ['>', "{$tableName}.[[{$this->rightAttribute}]]", $this->owner->getAttribute($this->rightAttribute)],
96
        ];
97 6 View Code Duplication
        if ($depth !== null) {
98 6
            $condition[] = ['>=', "{$tableName}.[[{$this->depthAttribute}]]", $this->owner->getAttribute($this->depthAttribute) - $depth];
99
        }
100
101 6
        $query = $this->owner->find()
102 6
            ->andWhere($condition)
103 6
            ->andWhere($this->treeCondition())
104 6
            ->addOrderBy(["{$tableName}.[[{$this->leftAttribute}]]" => SORT_ASC]);
105 6
        $query->multiple = true;
106
107 6
        return $query;
108
    }
109
110
    /**
111
     * @return \yii\db\ActiveQuery
112
     */
113 3 View Code Duplication
    public function getParent()
114
    {
115 3
        $tableName = $this->owner->tableName();
116 3
        $query = $this->getParents(1)
117 3
            ->orderBy(["{$tableName}.[[{$this->leftAttribute}]]" => SORT_DESC])
118 3
            ->limit(1);
119 3
        $query->multiple = false;
120 3
        return $query;
121
    }
122
123
    /**
124
     * @return \yii\db\ActiveQuery
125
     */
126 3 View Code Duplication
    public function getRoot()
127
    {
128 3
        $tableName = $this->owner->tableName();
129 3
        $query = $this->owner->find()
130 3
            ->andWhere(["{$tableName}.[[{$this->leftAttribute}]]" => 1])
131 3
            ->andWhere($this->treeCondition())
132 3
            ->limit(1);
133 3
        $query->multiple = false;
134 3
        return $query;
135
    }
136
137
    /**
138
     * @param int|null $depth
139
     * @param bool $andSelf
140
     * @param bool $backOrder
141
     * @return \yii\db\ActiveQuery
142
     */
143 78
    public function getDescendants($depth = null, $andSelf = false, $backOrder = false)
144
    {
145 78
        $tableName = $this->owner->tableName();
146 78
        $attribute = $backOrder ? $this->rightAttribute : $this->leftAttribute;
147
        $condition = [
148 78
            'and',
149 78
            [$andSelf ? '>=' : '>', "{$tableName}.[[{$attribute}]]",  $this->owner->getAttribute($this->leftAttribute)],
150 78
            [$andSelf ? '<=' : '<', "{$tableName}.[[{$attribute}]]",  $this->owner->getAttribute($this->rightAttribute)],
151
        ];
152
153 78 View Code Duplication
        if ($depth !== null) {
154 12
            $condition[] = ['<=', "{$tableName}.[[{$this->depthAttribute}]]", $this->owner->getAttribute($this->depthAttribute) + $depth];
155
        }
156
157 78
        $query = $this->owner->find()
158 78
            ->andWhere($condition)
159 78
            ->andWhere($this->treeCondition())
160 78
            ->addOrderBy(["{$tableName}.[[{$attribute}]]" => $backOrder ? SORT_DESC : SORT_ASC]);
161 78
        $query->multiple = true;
162
163 78
        return $query;
164
    }
165
166
    /**
167
     * @return \yii\db\ActiveQuery
168
     */
169 3
    public function getChildren()
170
    {
171 3
        return $this->getDescendants(1);
172
    }
173
174
    /**
175
     * @param int|null $depth
176
     * @return \yii\db\ActiveQuery
177
     */
178 3
    public function getLeaves($depth = null)
179
    {
180 3
        $tableName = $this->owner->tableName();
181 3
        $query = $this->getDescendants($depth)
182 3
            ->andWhere(["{$tableName}.[[{$this->leftAttribute}]]" => new Expression("{$tableName}.[[{$this->rightAttribute}]] - 1")]);
183 3
        $query->multiple = true;
184 3
        return $query;
185
    }
186
187
    /**
188
     * @return \yii\db\ActiveQuery
189
     */
190 3 View Code Duplication
    public function getPrev()
191
    {
192 3
        $tableName = $this->owner->tableName();
193 3
        $query = $this->owner->find()
194 3
            ->andWhere(["{$tableName}.[[{$this->rightAttribute}]]" => $this->owner->getAttribute($this->leftAttribute) - 1])
195 3
            ->andWhere($this->treeCondition())
196 3
            ->limit(1);
197 3
        $query->multiple = false;
198 3
        return $query;
199
    }
200
201
    /**
202
     * @return \yii\db\ActiveQuery
203
     */
204 3 View Code Duplication
    public function getNext()
205
    {
206 3
        $tableName = $this->owner->tableName();
207 3
        $query = $this->owner->find()
208 3
            ->andWhere(["{$tableName}.[[{$this->leftAttribute}]]" => $this->owner->getAttribute($this->rightAttribute) + 1])
209 3
            ->andWhere($this->treeCondition())
210 3
            ->limit(1);
211 3
        $query->multiple = false;
212 3
        return $query;
213
    }
214
215
    /**
216
     * Populate children relations for self and all descendants
217
     * @param int $depth = null
218
     * @param string|array $with = null
219
     * @return static
220
     */
221 3
    public function populateTree($depth = null, $with = null)
222
    {
223
        /** @var ActiveRecord[]|static[] $nodes */
224 3
        $query = $this->getDescendants($depth);
225 3
        if ($with) {
226
            $query->with($with);
227
        }
228 3
        $nodes = $query->all();
229
230 3
        $key = $this->owner->getAttribute($this->leftAttribute);
231 3
        $relates = [];
232 3
        $parents = [$key];
233 3
        $prev = $this->owner->getAttribute($this->depthAttribute);
234 3
        foreach($nodes as $node)
235
        {
236 3
            $level = $node->getAttribute($this->depthAttribute);
237 3
            if ($level <= $prev) {
238 3
                $parents = array_slice($parents, 0, $level - $prev - 1);
239
            }
240
241 3
            $key = end($parents);
242 3
            if (!isset($relates[$key])) {
243 3
                $relates[$key] = [];
244
            }
245 3
            $relates[$key][] = $node;
246
247 3
            $parents[] = $node->getAttribute($this->leftAttribute);
248 3
            $prev = $level;
249
        }
250
251 3
        $ownerDepth = $this->owner->getAttribute($this->depthAttribute);
252 3
        $nodes[] = $this->owner;
253 3
        foreach ($nodes as $node) {
254 3
            $key = $node->getAttribute($this->leftAttribute);
255 3
            if (isset($relates[$key])) {
256 3
                $node->populateRelation('children', $relates[$key]);
257 3
            } elseif ($depth === null || $ownerDepth + $depth > $node->getAttribute($this->depthAttribute)) {
258 3
                $node->populateRelation('children', []);
259
            }
260
        }
261
262 3
        return $this->owner;
263
    }
264
265
    /**
266
     * @return bool
267
     */
268 72
    public function isRoot()
269
    {
270 72
        return $this->owner->getAttribute($this->leftAttribute) === 1;
271
    }
272
273
    /**
274
     * @param ActiveRecord $node
275
     * @return bool
276
     */
277 69
    public function isChildOf($node)
278
    {
279 69
        $result = $this->owner->getAttribute($this->leftAttribute) > $node->getAttribute($this->leftAttribute)
280 69
            && $this->owner->getAttribute($this->rightAttribute) < $node->getAttribute($this->rightAttribute);
281
282 69 View Code Duplication
        if ($result && $this->treeAttribute !== null) {
283 6
            $result = $this->owner->getAttribute($this->treeAttribute) === $node->getAttribute($this->treeAttribute);
284
        }
285
286 69
        return $result;
287
    }
288
289
    /**
290
     * @return bool
291
     */
292 6
    public function isLeaf()
293
    {
294 6
        return $this->owner->getAttribute($this->rightAttribute) - $this->owner->getAttribute($this->leftAttribute) === 1;
295
    }
296
297
    /**
298
     * @return ActiveRecord
299
     */
300 12
    public function makeRoot()
301
    {
302 12
        $this->operation = self::OPERATION_MAKE_ROOT;
303 12
        return $this->owner;
304
    }
305
306
    /**
307
     * @param ActiveRecord $node
308
     * @return ActiveRecord
309
     */
310 33
    public function prependTo($node)
311
    {
312 33
        $this->operation = self::OPERATION_PREPEND_TO;
313 33
        $this->node = $node;
314 33
        return $this->owner;
315
    }
316
317
    /**
318
     * @param ActiveRecord $node
319
     * @return ActiveRecord
320
     */
321 33
    public function appendTo($node)
322
    {
323 33
        $this->operation = self::OPERATION_APPEND_TO;
324 33
        $this->node = $node;
325 33
        return $this->owner;
326
    }
327
328
    /**
329
     * @param ActiveRecord $node
330
     * @return ActiveRecord
331
     */
332 27
    public function insertBefore($node)
333
    {
334 27
        $this->operation = self::OPERATION_INSERT_BEFORE;
335 27
        $this->node = $node;
336 27
        return $this->owner;
337
    }
338
339
    /**
340
     * @param ActiveRecord $node
341
     * @return ActiveRecord
342
     */
343 30
    public function insertAfter($node)
344
    {
345 30
        $this->operation = self::OPERATION_INSERT_AFTER;
346 30
        $this->node = $node;
347 30
        return $this->owner;
348
    }
349
350
    /**
351
     * Need for paulzi/auto-tree
352
     */
353
    public function preDeleteWithChildren()
354
    {
355
        $this->operation = self::OPERATION_DELETE_ALL;
356
    }
357
358
    /**
359
     * @return bool|int
360
     * @throws \Exception
361
     * @throws \yii\db\Exception
362
     */
363 9
    public function deleteWithChildren()
364
    {
365 9
        $this->operation = self::OPERATION_DELETE_ALL;
366 9
        if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
367
            $transaction = $this->owner->getDb()->beginTransaction();
368
            try {
369
                $result = $this->deleteWithChildrenInternal();
370
                if ($result === false) {
371
                    $transaction->rollBack();
372
                } else {
373
                    $transaction->commit();
374
                }
375
                return $result;
376
            } catch (\Exception $e) {
377
                $transaction->rollBack();
378
                throw $e;
379
            }
380
        } else {
381 9
            $result = $this->deleteWithChildrenInternal();
382
        }
383 6
        return $result;
384
    }
385
386
    /**
387
     * @throws Exception
388
     * @throws NotSupportedException
389
     */
390 48
    public function beforeInsert()
391
    {
392 48
        if ($this->node !== null && !$this->node->getIsNewRecord()) {
393 27
            $this->node->refresh();
394
        }
395 48
        switch ($this->operation) {
396 48
            case self::OPERATION_MAKE_ROOT:
397 6
                $condition = array_merge([$this->leftAttribute => 1], $this->treeCondition());
398 6
                if ($this->owner->find()->andWhere($condition)->one() !== null) {
399 3
                    throw new Exception('Can not create more than one root.');
400
                }
401 3
                $this->owner->setAttribute($this->leftAttribute,  1);
402 3
                $this->owner->setAttribute($this->rightAttribute, 2);
403 3
                $this->owner->setAttribute($this->depthAttribute, 0);
404 3
                break;
405
406 42
            case self::OPERATION_PREPEND_TO:
407 9
                $this->insertNode($this->node->getAttribute($this->leftAttribute) + 1, 1);
408 6
                break;
409
410 33
            case self::OPERATION_APPEND_TO:
411 9
                $this->insertNode($this->node->getAttribute($this->rightAttribute), 1);
412 6
                break;
413
414 24
            case self::OPERATION_INSERT_BEFORE:
415 9
                $this->insertNode($this->node->getAttribute($this->leftAttribute), 0);
416 6
                break;
417
418 15
            case self::OPERATION_INSERT_AFTER:
419 12
                $this->insertNode($this->node->getAttribute($this->rightAttribute) + 1, 0);
420 6
                break;
421
422
            default:
423 3
                throw new NotSupportedException('Method "'. $this->owner->className() . '::insert" is not supported for inserting new nodes.');
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
424
        }
425 27
    }
426
427
    /**
428
     * @throws Exception
429
     */
430 27
    public function afterInsert()
431
    {
432 27
        if ($this->operation === self::OPERATION_MAKE_ROOT && $this->treeAttribute !== null && $this->owner->getAttribute($this->treeAttribute) === null) {
433 3
            $id = $this->owner->getPrimaryKey();
434 3
            $this->owner->setAttribute($this->treeAttribute, $id);
435
436 3
            $primaryKey = $this->owner->primaryKey();
437 3
            if (!isset($primaryKey[0])) {
438
                throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
439
            }
440
441 3
            $this->owner->updateAll([$this->treeAttribute => $id], [$primaryKey[0] => $id]);
442
        }
443 27
        $this->operation = null;
444 27
        $this->node      = null;
445 27
    }
446
447
    /**
448
     * @throws Exception
449
     */
450 93
    public function beforeUpdate()
451
    {
452 93
        if ($this->node !== null && !$this->node->getIsNewRecord()) {
453 78
            $this->node->refresh();
454
        }
455
456 93
        switch ($this->operation) {
457 93
            case self::OPERATION_MAKE_ROOT:
458 6
                if ($this->treeAttribute === null) {
459
                    throw new Exception('Can not move a node as the root when "treeAttribute" is not set.');
460
                }
461 6
                if ($this->owner->getOldAttribute($this->treeAttribute) !== $this->owner->getAttribute($this->treeAttribute)) {
462 3
                    $this->treeChange = $this->owner->getAttribute($this->treeAttribute);
463 3
                    $this->owner->setAttribute($this->treeAttribute, $this->owner->getOldAttribute($this->treeAttribute));
464
                }
465 6
                break;
466
467 87
            case self::OPERATION_INSERT_BEFORE:
468 69
            case self::OPERATION_INSERT_AFTER:
469 36
                if ($this->node->isRoot()) {
470
                    throw new Exception('Can not move a node before/after root.');
471
                }
472
473 51
            case self::OPERATION_PREPEND_TO:
474 27
            case self::OPERATION_APPEND_TO:
475 84
                if ($this->node->getIsNewRecord()) {
476 6
                    throw new Exception('Can not move a node when the target node is new record.');
477
                }
478
479 78
                if ($this->owner->equals($this->node)) {
480 12
                    throw new Exception('Can not move a node when the target node is same.');
481
                }
482
483 66
                if ($this->node->isChildOf($this->owner)) {
484 12
                    throw new Exception('Can not move a node when the target node is child.');
485
                }
486
        }
487 63
    }
488
489
    /**
490
     *
491
     */
492 63
    public function afterUpdate()
493
    {
494 63
        switch ($this->operation) {
495 63
            case self::OPERATION_MAKE_ROOT:
496 6
                if ($this->treeChange || !$this->isRoot() || $this->owner->getIsNewRecord()) {
497 3
                    $this->moveNodeAsRoot();
498
                }
499 6
                break;
500
501 57
            case self::OPERATION_PREPEND_TO:
502 15
                $this->moveNode($this->node->getAttribute($this->leftAttribute) + 1, 1);
503 15
                break;
504
505 42
            case self::OPERATION_APPEND_TO:
506 15
                $this->moveNode($this->node->getAttribute($this->rightAttribute), 1);
507 15
                break;
508
509 27
            case self::OPERATION_INSERT_BEFORE:
510 12
                $this->moveNode($this->node->getAttribute($this->leftAttribute), 0);
511 12
                break;
512
513 15
            case self::OPERATION_INSERT_AFTER:
514 12
                $this->moveNode($this->node->getAttribute($this->rightAttribute) + 1, 0);
515 12
                break;
516
        }
517 63
        $this->operation  = null;
518 63
        $this->node       = null;
519 63
        $this->treeChange = null;
520 63
    }
521
522
    /**
523
     * @throws Exception
524
     */
525 18
    public function beforeDelete()
526
    {
527 18
        if ($this->owner->getIsNewRecord()) {
528 6
            throw new Exception('Can not delete a node when it is new record.');
529
        }
530 12
        if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) {
531 3
            throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.');
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
532
        }
533 9
        $this->owner->refresh();
534 9
    }
535
536
    /**
537
     *
538
     */
539 9
    public function afterDelete()
540
    {
541 9
        $left  = $this->owner->getAttribute($this->leftAttribute);
542 9
        $right = $this->owner->getAttribute($this->rightAttribute);
543 9
        if ($this->operation === static::OPERATION_DELETE_ALL || $this->isLeaf()) {
544 6
            $this->shift($right + 1, null, $left - $right - 1);
545
        } else {
546 3
            $this->owner->updateAll(
547
                [
548 3
                    $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]] - 1"),
549 3
                    $this->rightAttribute => new Expression("[[{$this->rightAttribute}]] - 1"),
550 3
                    $this->depthAttribute => new Expression("[[{$this->depthAttribute}]] - 1"),
551
                ],
552 3
                $this->getDescendants()->where
553
            );
554 3
            $this->shift($right + 1, null, -2);
555
        }
556 9
        $this->operation = null;
557 9
        $this->node      = null;
558 9
    }
559
560
    /**
561
     * @return int
562
     */
563 9
    protected function deleteWithChildrenInternal()
564
    {
565 9
        if (!$this->owner->beforeDelete()) {
566
            return false;
567
        }
568 6
        $result = $this->owner->deleteAll($this->getDescendants(null, true)->where);
569 6
        $this->owner->setOldAttributes(null);
570 6
        $this->owner->afterDelete();
571 6
        return $result;
572
    }
573
574
    /**
575
     * @param int $to
576
     * @param int $depth
577
     * @throws Exception
578
     */
579 39
    protected function insertNode($to, $depth = 0)
580
    {
581 39
        if ($this->node->getIsNewRecord()) {
582 12
            throw new Exception('Can not create a node when the target node is new record.');
583
        }
584
585 27
        if ($depth === 0 && $this->node->isRoot()) {
586 3
            throw new Exception('Can not insert a node before/after root.');
587
        }
588 24
        $this->owner->setAttribute($this->leftAttribute,  $to);
589 24
        $this->owner->setAttribute($this->rightAttribute, $to + 1);
590 24
        $this->owner->setAttribute($this->depthAttribute, $this->node->getAttribute($this->depthAttribute) + $depth);
591 24
        if ($this->treeAttribute !== null) {
592 24
            $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
593
        }
594 24
        $this->shift($to, null, 2);
595 24
    }
596
597
    /**
598
     * @param int $to
599
     * @param int $depth
600
     * @throws Exception
601
     */
602 54
    protected function moveNode($to, $depth = 0)
603
    {
604 54
        $left  = $this->owner->getAttribute($this->leftAttribute);
605 54
        $right = $this->owner->getAttribute($this->rightAttribute);
606 54
        $depth = $this->owner->getAttribute($this->depthAttribute) - $this->node->getAttribute($this->depthAttribute) - $depth;
607 54
        if ($this->treeAttribute === null || $this->owner->getAttribute($this->treeAttribute) === $this->node->getAttribute($this->treeAttribute)) {
608
            // same root
609 42
            $this->owner->updateAll(
610 42
                [$this->depthAttribute => new Expression("-[[{$this->depthAttribute}]]" . sprintf('%+d', $depth))],
611 42
                $this->getDescendants(null, true)->where
612
            );
613 42
            $delta = $right - $left + 1;
614 42
            if ($left >= $to) {
615 24
                $this->shift($to, $left - 1, $delta);
616 24
                $delta = $to - $left;
617
            } else {
618 18
                $this->shift($right + 1, $to - 1, -$delta);
619 18
                $delta = $to - $right - 1;
620
            }
621 42
            $this->owner->updateAll(
622
                [
623 42
                    $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]]"  . sprintf('%+d', $delta)),
624 42
                    $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', $delta)),
625 42
                    $this->depthAttribute => new Expression("-[[{$this->depthAttribute}]]"),
626
                ],
627
                [
628 42
                    'and',
629 42
                    $this->getDescendants(null, true)->where,
630 42
                    ['<', $this->depthAttribute, 0],
631
                ]
632
            );
633
        } else {
634
            // move from other root
635 12
            $tree = $this->node->getAttribute($this->treeAttribute);
636 12
            $this->shift($to, null, $right - $left + 1, $tree);
637 12
            $delta = $to - $left;
638 12
            $this->owner->updateAll(
639
                [
640 12
                    $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]]"  . sprintf('%+d', $delta)),
641 12
                    $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', $delta)),
642 12
                    $this->depthAttribute => new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', -$depth)),
643 12
                    $this->treeAttribute  => $tree,
644
                ],
645 12
                $this->getDescendants(null, true)->where
646
            );
647 12
            $this->shift($right + 1, null, $left - $right - 1);
648
        }
649 54
    }
650
651
    /**
652
     *
653
     */
654 3
    protected function moveNodeAsRoot()
655
    {
656 3
        $left   = $this->owner->getAttribute($this->leftAttribute);
657 3
        $right  = $this->owner->getAttribute($this->rightAttribute);
658 3
        $depth  = $this->owner->getAttribute($this->depthAttribute);
659 3
        $tree   = $this->treeChange ? $this->treeChange : $this->owner->getPrimaryKey();
660
661 3
        $this->owner->updateAll(
662
            [
663 3
                $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]]"  . sprintf('%+d', 1 - $left)),
664 3
                $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', 1 - $left)),
665 3
                $this->depthAttribute => new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', -$depth)),
666 3
                $this->treeAttribute  => $tree,
667
            ],
668 3
            $this->getDescendants(null, true)->where
669
        );
670 3
        $this->shift($right + 1, null, $left - $right - 1);
671 3
    }
672
673
674
675
    /**
676
     * @param int $from
677
     * @param int $to
678
     * @param int $delta
679
     * @param int|null $tree
680
     */
681 90
    protected function shift($from, $to, $delta, $tree = null)
682
    {
683 90
        if ($delta !== 0 && ($to === null || $to >= $from)) {
684 78 View Code Duplication
            if ($this->treeAttribute !== null && $tree === null) {
685 78
                $tree = $this->owner->getAttribute($this->treeAttribute);
686
            }
687 78
            foreach ([$this->leftAttribute, $this->rightAttribute] as $i => $attribute) {
688 78
                $this->owner->updateAll(
689 78
                    [$attribute => new Expression("[[{$attribute}]]" . sprintf('%+d', $delta))],
690
                    [
691 78
                        'and',
692 78
                        $to === null ? ['>=', $attribute, $from] : ['between', $attribute, $from, $to],
693 78
                        $this->treeAttribute !== null ? [$this->treeAttribute => $tree] : [],
694
                    ]
695
                );
696
            }
697
        }
698 90
    }
699
700
    /**
701
     * @return array
702
     */
703 99
    protected function treeCondition()
704
    {
705 99
        $tableName = $this->owner->tableName();
706 99
        if ($this->treeAttribute === null) {
707 84
            return [];
708
        } else {
709 96
            return ["{$tableName}.[[{$this->treeAttribute}]]" => $this->owner->getAttribute($this->treeAttribute)];
710
        }
711
    }
712
}
713