Code

< 40 %
40-60 %
> 60 %
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.');
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.');
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.');
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