Issues (10)

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.

src/NestedSetsBehavior.php (10 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/creocoder/yii2-nested-sets
4
 * @copyright Copyright (c) 2015 Alexander Kochetov
5
 * @license http://opensource.org/licenses/BSD-3-Clause
6
 */
7
8
namespace creocoder\nestedsets;
9
10
use yii\base\Behavior;
11
use yii\base\NotSupportedException;
12
use yii\db\ActiveRecord;
13
use yii\db\Exception;
14
use yii\db\Expression;
15
16
/**
17
 * NestedSetsBehavior
18
 *
19
 * @property ActiveRecord $owner
20
 *
21
 * @author Alexander Kochetov <[email protected]>
22
 */
23
class NestedSetsBehavior extends Behavior
24
{
25
    const OPERATION_MAKE_ROOT = 'makeRoot';
26
    const OPERATION_PREPEND_TO = 'prependTo';
27
    const OPERATION_APPEND_TO = 'appendTo';
28
    const OPERATION_INSERT_BEFORE = 'insertBefore';
29
    const OPERATION_INSERT_AFTER = 'insertAfter';
30
    const OPERATION_DELETE_WITH_CHILDREN = 'deleteWithChildren';
31
32
    /**
33
     * @var string|false
34
     */
35
    public $treeAttribute = false;
36
    /**
37
     * @var string
38
     */
39
    public $leftAttribute = 'lft';
40
    /**
41
     * @var string
42
     */
43
    public $rightAttribute = 'rgt';
44
    /**
45
     * @var string
46
     */
47
    public $depthAttribute = 'depth';
48
    /**
49
     * @var string|null
50
     */
51
    protected $operation;
52
    /**
53
     * @var ActiveRecord|null
54
     */
55
    protected $node;
56
57
    /**
58
     * @inheritdoc
59
     */
60 116
    public function events()
61
    {
62
        return [
63 116
            ActiveRecord::EVENT_BEFORE_INSERT => 'beforeInsert',
64 116
            ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert',
65 116
            ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeUpdate',
66 116
            ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate',
67 116
            ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
68 116
            ActiveRecord::EVENT_AFTER_DELETE => 'afterDelete',
69 116
        ];
70
    }
71
72
    /**
73
     * Creates the root node if the active record is new or moves it
74
     * as the root node.
75
     * @param boolean $runValidation
76
     * @param array $attributes
77
     * @return boolean
78
     */
79 10
    public function makeRoot($runValidation = true, $attributes = null)
80
    {
81 10
        $this->operation = self::OPERATION_MAKE_ROOT;
82
83 10
        return $this->owner->save($runValidation, $attributes);
84
    }
85
86
    /**
87
     * Creates a node as the first child of the target node if the active
88
     * record is new or moves it as the first child of the target node.
89
     * @param ActiveRecord $node
90
     * @param boolean $runValidation
91
     * @param array $attributes
92
     * @return boolean
93
     */
94 16 View Code Duplication
    public function prependTo($node, $runValidation = true, $attributes = null)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
95
    {
96 16
        $this->operation = self::OPERATION_PREPEND_TO;
97 16
        $this->node = $node;
98
99 16
        return $this->owner->save($runValidation, $attributes);
100
    }
101
102
    /**
103
     * Creates a node as the last child of the target node if the active
104
     * record is new or moves it as the last child of the target node.
105
     * @param ActiveRecord $node
106
     * @param boolean $runValidation
107
     * @param array $attributes
108
     * @return boolean
109
     */
110 16 View Code Duplication
    public function appendTo($node, $runValidation = true, $attributes = null)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
    {
112 16
        $this->operation = self::OPERATION_APPEND_TO;
113 16
        $this->node = $node;
114
115 16
        return $this->owner->save($runValidation, $attributes);
116
    }
117
118
    /**
119
     * Creates a node as the previous sibling of the target node if the active
120
     * record is new or moves it as the previous sibling of the target node.
121
     * @param ActiveRecord $node
122
     * @param boolean $runValidation
123
     * @param array $attributes
124
     * @return boolean
125
     */
126 20 View Code Duplication
    public function insertBefore($node, $runValidation = true, $attributes = null)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
127
    {
128 20
        $this->operation = self::OPERATION_INSERT_BEFORE;
129 20
        $this->node = $node;
130
131 20
        return $this->owner->save($runValidation, $attributes);
132
    }
133
134
    /**
135
     * Creates a node as the next sibling of the target node if the active
136
     * record is new or moves it as the next sibling of the target node.
137
     * @param ActiveRecord $node
138
     * @param boolean $runValidation
139
     * @param array $attributes
140
     * @return boolean
141
     */
142 20 View Code Duplication
    public function insertAfter($node, $runValidation = true, $attributes = null)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
143
    {
144 20
        $this->operation = self::OPERATION_INSERT_AFTER;
145 20
        $this->node = $node;
146
147 20
        return $this->owner->save($runValidation, $attributes);
148
    }
149
150
    /**
151
     * Deletes a node and its children.
152
     * @return integer|false the number of rows deleted or false if
153
     * the deletion is unsuccessful for some reason.
154
     * @throws \Exception
155
     */
156 4
    public function deleteWithChildren()
157
    {
158 4
        $this->operation = self::OPERATION_DELETE_WITH_CHILDREN;
159
160 4
        if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
161
            return $this->deleteWithChildrenInternal();
162
        }
163
164 4
        $transaction = $this->owner->getDb()->beginTransaction();
165
166
        try {
167 4
            $result = $this->deleteWithChildrenInternal();
168
169 2
            if ($result === false) {
170
                $transaction->rollBack();
171
            } else {
172 2
                $transaction->commit();
173
            }
174
175 2
            return $result;
176 2
        } catch (\Exception $e) {
177 2
            $transaction->rollBack();
178 2
            throw $e;
179
        }
180
    }
181
182
    /**
183
     * @return integer|false the number of rows deleted or false if
184
     * the deletion is unsuccessful for some reason.
185
     */
186 4
    protected function deleteWithChildrenInternal()
187
    {
188 4
        if (!$this->owner->beforeDelete()) {
189
            return false;
190
        }
191
192
        $condition = [
193 2
            'and',
194 2
            ['>=', $this->leftAttribute, $this->owner->getAttribute($this->leftAttribute)],
195 2
            ['<=', $this->rightAttribute, $this->owner->getAttribute($this->rightAttribute)]
196 2
        ];
197
198 2
        $this->applyTreeAttributeCondition($condition);
199 2
        $result = $this->owner->deleteAll($condition);
200 2
        $this->owner->setOldAttributes(null);
201 2
        $this->owner->afterDelete();
202
203 2
        return $result;
204
    }
205
206
    /**
207
     * Gets the parents of the node.
208
     * @param integer|null $depth the depth
209
     * @return \yii\db\ActiveQuery
210
     */
211 2 View Code Duplication
    public function parents($depth = null)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
212
    {
213
        $condition = [
214 2
            'and',
215 2
            ['<', $this->leftAttribute, $this->owner->getAttribute($this->leftAttribute)],
216 2
            ['>', $this->rightAttribute, $this->owner->getAttribute($this->rightAttribute)],
217 2
        ];
218
219 2
        if ($depth !== null) {
220 2
            $condition[] = ['>=', $this->depthAttribute, $this->owner->getAttribute($this->depthAttribute) - $depth];
221 2
        }
222
223 2
        $this->applyTreeAttributeCondition($condition);
224
225 2
        return $this->owner->find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]);
226
    }
227
228
    /**
229
     * Gets the children of the node.
230
     * @param integer|null $depth the depth
231
     * @return \yii\db\ActiveQuery
232
     */
233 2 View Code Duplication
    public function children($depth = null)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
234
    {
235
        $condition = [
236 2
            'and',
237 2
            ['>', $this->leftAttribute, $this->owner->getAttribute($this->leftAttribute)],
238 2
            ['<', $this->rightAttribute, $this->owner->getAttribute($this->rightAttribute)],
239 2
        ];
240
241 2
        if ($depth !== null) {
242 2
            $condition[] = ['<=', $this->depthAttribute, $this->owner->getAttribute($this->depthAttribute) + $depth];
243 2
        }
244
245 2
        $this->applyTreeAttributeCondition($condition);
246
247 2
        return $this->owner->find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]);
248
    }
249
250
    /**
251
     * Gets the leaves of the node.
252
     * @return \yii\db\ActiveQuery
253
     */
254 2
    public function leaves()
255
    {
256
        $condition = [
257 2
            'and',
258 2
            ['>', $this->leftAttribute, $this->owner->getAttribute($this->leftAttribute)],
259 2
            ['<', $this->rightAttribute, $this->owner->getAttribute($this->rightAttribute)],
260 2
            [$this->rightAttribute => new Expression($this->owner->getDb()->quoteColumnName($this->leftAttribute) . '+ 1')],
261 2
        ];
262
263 2
        $this->applyTreeAttributeCondition($condition);
264
265 2
        return $this->owner->find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]);
266
    }
267
268
    /**
269
     * Gets the previous sibling of the node.
270
     * @return \yii\db\ActiveQuery
271
     */
272 2 View Code Duplication
    public function prev()
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
273
    {
274 2
        $condition = [$this->rightAttribute => $this->owner->getAttribute($this->leftAttribute) - 1];
275 2
        $this->applyTreeAttributeCondition($condition);
276
277 2
        return $this->owner->find()->andWhere($condition);
278
    }
279
280
    /**
281
     * Gets the next sibling of the node.
282
     * @return \yii\db\ActiveQuery
283
     */
284 2 View Code Duplication
    public function next()
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
285
    {
286 2
        $condition = [$this->leftAttribute => $this->owner->getAttribute($this->rightAttribute) + 1];
287 2
        $this->applyTreeAttributeCondition($condition);
288
289 2
        return $this->owner->find()->andWhere($condition);
290
    }
291
292
    /**
293
     * Determines whether the node is root.
294
     * @return boolean whether the node is root
295
     */
296 48
    public function isRoot()
297
    {
298 48
        return $this->owner->getAttribute($this->leftAttribute) == 1;
299
    }
300
301
    /**
302
     * Determines whether the node is child of the parent node.
303
     * @param ActiveRecord $node the parent node
304
     * @return boolean whether the node is child of the parent node
305
     */
306 34
    public function isChildOf($node)
307
    {
308 34
        $result = $this->owner->getAttribute($this->leftAttribute) > $node->getAttribute($this->leftAttribute)
309 34
            && $this->owner->getAttribute($this->rightAttribute) < $node->getAttribute($this->rightAttribute);
310
311 34
        if ($result && $this->treeAttribute !== false) {
312 2
            $result = $this->owner->getAttribute($this->treeAttribute) === $node->getAttribute($this->treeAttribute);
313 2
        }
314
315 34
        return $result;
316
    }
317
318
    /**
319
     * Determines whether the node is leaf.
320
     * @return boolean whether the node is leaf
321
     */
322 6
    public function isLeaf()
323
    {
324 6
        return $this->owner->getAttribute($this->rightAttribute) - $this->owner->getAttribute($this->leftAttribute) === 1;
325
    }
326
327
    /**
328
     * @throws NotSupportedException
329
     */
330 26
    public function beforeInsert()
331
    {
332 26
        if ($this->node !== null && !$this->node->getIsNewRecord()) {
333 12
            $this->node->refresh();
334 12
        }
335
336 26
        switch ($this->operation) {
337 26
            case self::OPERATION_MAKE_ROOT:
338 4
                $this->beforeInsertRootNode();
339 2
                break;
340 22
            case self::OPERATION_PREPEND_TO:
341 4
                $this->beforeInsertNode($this->node->getAttribute($this->leftAttribute) + 1, 1);
342 2
                break;
343 18
            case self::OPERATION_APPEND_TO:
344 5
                $this->beforeInsertNode($this->node->getAttribute($this->rightAttribute), 1);
345 2
                break;
346 14
            case self::OPERATION_INSERT_BEFORE:
347 6
                $this->beforeInsertNode($this->node->getAttribute($this->leftAttribute), 0);
348 2
                break;
349 8
            case self::OPERATION_INSERT_AFTER:
350 6
                $this->beforeInsertNode($this->node->getAttribute($this->rightAttribute) + 1, 0);
351 2
                break;
352 2
            default:
353 2
                throw new NotSupportedException('Method "'. get_class($this->owner) . '::insert" is not supported for inserting new nodes.');
354 12
        }
355 10
    }
356
357
    /**
358
     * @throws Exception
359
     */
360 4
    protected function beforeInsertRootNode()
361
    {
362 4
        if ($this->treeAttribute === false && $this->owner->find()->roots()->exists()) {
363 2
            throw new Exception('Can not create more than one root when "treeAttribute" is false.');
364
        }
365
366 2
        $this->owner->setAttribute($this->leftAttribute, 1);
367 2
        $this->owner->setAttribute($this->rightAttribute, 2);
368 2
        $this->owner->setAttribute($this->depthAttribute, 0);
369 2
    }
370
371
    /**
372
     * @param integer $value
373
     * @param integer $depth
374
     * @throws Exception
375
     */
376 20
    protected function beforeInsertNode($value, $depth)
377
    {
378 20
        if ($this->node->getIsNewRecord()) {
379 8
            throw new Exception('Can not create a node when the target node is new record.');
380
        }
381
382 12
        if ($depth === 0 && $this->node->isRoot()) {
383 4
            throw new Exception('Can not create a node when the target node is root.');
384
        }
385
386 8
        $this->owner->setAttribute($this->leftAttribute, $value);
387 8
        $this->owner->setAttribute($this->rightAttribute, $value + 1);
388 8
        $this->owner->setAttribute($this->depthAttribute, $this->node->getAttribute($this->depthAttribute) + $depth);
389
390 8
        if ($this->treeAttribute !== false) {
391 8
            $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
392 8
        }
393
394 8
        $this->shiftLeftRightAttribute($value, 2);
395 8
    }
396
397
    /**
398
     * @throws Exception
399
     */
400 10
    public function afterInsert()
401
    {
402 10
        if ($this->operation === self::OPERATION_MAKE_ROOT && $this->treeAttribute !== false) {
403 2
            $this->owner->setAttribute($this->treeAttribute, $this->owner->getPrimaryKey());
404 2
            $primaryKey = $this->owner->primaryKey();
405
406 2
            if (!isset($primaryKey[0])) {
407
                throw new Exception('"' . get_class($this->owner) . '" must have a primary key.');
408
            }
409
410 2
            $this->owner->updateAll(
411 2
                [$this->treeAttribute => $this->owner->getAttribute($this->treeAttribute)],
412 2
                [$primaryKey[0] => $this->owner->getAttribute($this->treeAttribute)]
413 2
            );
414 2
        }
415
416 10
        $this->operation = null;
417 10
        $this->node = null;
418 10
    }
419
420
    /**
421
     * @throws Exception
422
     */
423 60
    public function beforeUpdate()
424
    {
425 60
        if ($this->node !== null && !$this->node->getIsNewRecord()) {
426 44
            $this->node->refresh();
427 44
        }
428
429 60
        switch ($this->operation) {
430 60
            case self::OPERATION_MAKE_ROOT:
431 6
                if ($this->treeAttribute === false) {
432 2
                    throw new Exception('Can not move a node as the root when "treeAttribute" is false.');
433
                }
434
435 4
                if ($this->owner->isRoot()) {
436 2
                    throw new Exception('Can not move the root node as the root.');
437
                }
438
439 2
                break;
440 54
            case self::OPERATION_INSERT_BEFORE:
441 54
            case self::OPERATION_INSERT_AFTER:
442 28
                if ($this->node->isRoot()) {
443 4
                    throw new Exception('Can not move a node when the target node is root.');
444
                }
445 50
            case self::OPERATION_PREPEND_TO:
446 50
            case self::OPERATION_APPEND_TO:
447 48
                if ($this->node->getIsNewRecord()) {
448 8
                    throw new Exception('Can not move a node when the target node is new record.');
449
                }
450
451 40
                if ($this->owner->equals($this->node)) {
0 ignored issues
show
It seems like $this->node can be null; however, equals() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
452 8
                    throw new Exception('Can not move a node when the target node is same.');
453
                }
454
455 32
                if ($this->node->isChildOf($this->owner)) {
456 8
                    throw new Exception('Can not move a node when the target node is child.');
457
                }
458 28
        }
459 28
    }
460
461
    /**
462
     * @return void
463
     */
464 28
    public function afterUpdate()
465
    {
466 28
        switch ($this->operation) {
467 28
            case self::OPERATION_MAKE_ROOT:
468 2
                $this->moveNodeAsRoot();
469 2
                break;
470 26
            case self::OPERATION_PREPEND_TO:
471 6
                $this->moveNode($this->node->getAttribute($this->leftAttribute) + 1, 1);
472 6
                break;
473 20
            case self::OPERATION_APPEND_TO:
474 6
                $this->moveNode($this->node->getAttribute($this->rightAttribute), 1);
475 6
                break;
476 14
            case self::OPERATION_INSERT_BEFORE:
477 6
                $this->moveNode($this->node->getAttribute($this->leftAttribute), 0);
478 6
                break;
479 8
            case self::OPERATION_INSERT_AFTER:
480 6
                $this->moveNode($this->node->getAttribute($this->rightAttribute) + 1, 0);
481 6
                break;
482 2
            default:
483 2
                return;
484 28
        }
485
486 26
        $this->operation = null;
487 26
        $this->node = null;
488 26
    }
489
490
    /**
491
     * @return void
492
     */
493 2
    protected function moveNodeAsRoot()
494
    {
495 2
        $db = $this->owner->getDb();
496 2
        $leftValue = $this->owner->getAttribute($this->leftAttribute);
497 2
        $rightValue = $this->owner->getAttribute($this->rightAttribute);
498 2
        $depthValue = $this->owner->getAttribute($this->depthAttribute);
499 2
        $treeValue = $this->owner->getAttribute($this->treeAttribute);
0 ignored issues
show
It seems like $this->treeAttribute can also be of type false; however, yii\db\BaseActiveRecord::getAttribute() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
500 2
        $leftAttribute = $db->quoteColumnName($this->leftAttribute);
501 2
        $rightAttribute = $db->quoteColumnName($this->rightAttribute);
502 2
        $depthAttribute = $db->quoteColumnName($this->depthAttribute);
503
504 2
        $this->owner->updateAll(
505
            [
506 2
                $this->leftAttribute => new Expression($leftAttribute . sprintf('%+d', 1 - $leftValue)),
507 2
                $this->rightAttribute => new Expression($rightAttribute . sprintf('%+d', 1 - $leftValue)),
508 2
                $this->depthAttribute => new Expression($depthAttribute  . sprintf('%+d', -$depthValue)),
509 2
                $this->treeAttribute => $this->owner->getPrimaryKey(),
510 2
            ],
511
            [
512 2
                'and',
513 2
                ['>=', $this->leftAttribute, $leftValue],
514 2
                ['<=', $this->rightAttribute, $rightValue],
515 2
                [$this->treeAttribute => $treeValue]
516 2
            ]
517 2
        );
518
519 2
        $this->shiftLeftRightAttribute($rightValue + 1, $leftValue - $rightValue - 1);
520 2
    }
521
522
    /**
523
     * @param integer $value
524
     * @param integer $depth
525
     */
526 24
    protected function moveNode($value, $depth)
527
    {
528 24
        $db = $this->owner->getDb();
529 24
        $leftValue = $this->owner->getAttribute($this->leftAttribute);
530 24
        $rightValue = $this->owner->getAttribute($this->rightAttribute);
531 24
        $depthValue = $this->owner->getAttribute($this->depthAttribute);
532 24
        $depthAttribute = $db->quoteColumnName($this->depthAttribute);
533 24
        $depth = $this->node->getAttribute($this->depthAttribute) - $depthValue + $depth;
534
535 24
        if ($this->treeAttribute === false
536 24
            || $this->owner->getAttribute($this->treeAttribute) === $this->node->getAttribute($this->treeAttribute)) {
537 16
            $delta = $rightValue - $leftValue + 1;
538 16
            $this->shiftLeftRightAttribute($value, $delta);
539
540 16
            if ($leftValue >= $value) {
541 8
                $leftValue += $delta;
542 8
                $rightValue += $delta;
543 8
            }
544
545 16
            $condition = ['and', ['>=', $this->leftAttribute, $leftValue], ['<=', $this->rightAttribute, $rightValue]];
546 16
            $this->applyTreeAttributeCondition($condition);
547
548 16
            $this->owner->updateAll(
549 16
                [$this->depthAttribute => new Expression($depthAttribute . sprintf('%+d', $depth))],
550
                $condition
551 16
            );
552
553 16
            foreach ([$this->leftAttribute, $this->rightAttribute] as $attribute) {
554 16
                $condition = ['and', ['>=', $attribute, $leftValue], ['<=', $attribute, $rightValue]];
555 16
                $this->applyTreeAttributeCondition($condition);
556
557 16
                $this->owner->updateAll(
558 16
                    [$attribute => new Expression($db->quoteColumnName($attribute) . sprintf('%+d', $value - $leftValue))],
559
                    $condition
560 16
                );
561 16
            }
562
563 16
            $this->shiftLeftRightAttribute($rightValue + 1, -$delta);
564 16
        } else {
565 8
            $leftAttribute = $db->quoteColumnName($this->leftAttribute);
566 8
            $rightAttribute = $db->quoteColumnName($this->rightAttribute);
567 8
            $nodeRootValue = $this->node->getAttribute($this->treeAttribute);
568
569 8
            foreach ([$this->leftAttribute, $this->rightAttribute] as $attribute) {
570 8
                $this->owner->updateAll(
571 8
                    [$attribute => new Expression($db->quoteColumnName($attribute) . sprintf('%+d', $rightValue - $leftValue + 1))],
572 8
                    ['and', ['>=', $attribute, $value], [$this->treeAttribute => $nodeRootValue]]
573 8
                );
574 8
            }
575
576 8
            $delta = $value - $leftValue;
577
578 8
            $this->owner->updateAll(
579
                [
580 8
                    $this->leftAttribute => new Expression($leftAttribute . sprintf('%+d', $delta)),
581 8
                    $this->rightAttribute => new Expression($rightAttribute . sprintf('%+d', $delta)),
582 8
                    $this->depthAttribute => new Expression($depthAttribute . sprintf('%+d', $depth)),
583 8
                    $this->treeAttribute => $nodeRootValue,
584 8
                ],
585
                [
586 8
                    'and',
587 8
                    ['>=', $this->leftAttribute, $leftValue],
588 8
                    ['<=', $this->rightAttribute, $rightValue],
589 8
                    [$this->treeAttribute => $this->owner->getAttribute($this->treeAttribute)],
590
                ]
591 8
            );
592
593 8
            $this->shiftLeftRightAttribute($rightValue + 1, $leftValue - $rightValue - 1);
594
        }
595 24
    }
596
597
    /**
598
     * @throws Exception
599
     * @throws NotSupportedException
600
     */
601 10
    public function beforeDelete()
602
    {
603 10
        if ($this->owner->getIsNewRecord()) {
604 4
            throw new Exception('Can not delete a node when it is new record.');
605
        }
606
607 6
        if ($this->owner->isRoot() && $this->operation !== self::OPERATION_DELETE_WITH_CHILDREN) {
608 2
            throw new NotSupportedException('Method "'. get_class($this->owner) . '::delete" is not supported for deleting root nodes.');
609
        }
610
611 4
        $this->owner->refresh();
612 4
    }
613
614
    /**
615
     * @return void
616
     */
617 42
    public function afterDelete()
618
    {
619 4
        $leftValue = $this->owner->getAttribute($this->leftAttribute);
620 4
        $rightValue = $this->owner->getAttribute($this->rightAttribute);
621
622 4
        if ($this->owner->isLeaf() || $this->operation === self::OPERATION_DELETE_WITH_CHILDREN) {
623 2
            $this->shiftLeftRightAttribute($rightValue + 1, $leftValue - $rightValue - 1);
624 2
        } else {
625
            $condition = [
626 2
                'and',
627 2
                ['>=', $this->leftAttribute, $this->owner->getAttribute($this->leftAttribute)],
628 2
                ['<=', $this->rightAttribute, $this->owner->getAttribute($this->rightAttribute)]
629 2
            ];
630
631 2
            $this->applyTreeAttributeCondition($condition);
632 2
            $db = $this->owner->getDb();
633
634 2
            $this->owner->updateAll(
635
                [
636 2
                    $this->leftAttribute => new Expression($db->quoteColumnName($this->leftAttribute) . sprintf('%+d', -1)),
637 2
                    $this->rightAttribute => new Expression($db->quoteColumnName($this->rightAttribute) . sprintf('%+d', -1)),
638 42
                    $this->depthAttribute => new Expression($db->quoteColumnName($this->depthAttribute) . sprintf('%+d', -1)),
639 2
                ],
640
                $condition
641 2
            );
642
643 2
            $this->shiftLeftRightAttribute($rightValue + 1, -2);
644
        }
645
646 4
        $this->operation = null;
647 4
        $this->node = null;
648 4
    }
649
650
    /**
651
     * @param integer $value
652
     * @param integer $delta
653
     */
654 38
    protected function shiftLeftRightAttribute($value, $delta)
655
    {
656 38
        $db = $this->owner->getDb();
657
658 38
        foreach ([$this->leftAttribute, $this->rightAttribute] as $attribute) {
659 38
            $condition = ['>=', $attribute, $value];
660 38
            $this->applyTreeAttributeCondition($condition);
661
662 38
            $this->owner->updateAll(
663 38
                [$attribute => new Expression($db->quoteColumnName($attribute) . sprintf('%+d', $delta))],
664
                $condition
665 38
            );
666 38
        }
667 38
    }
668
669
    /**
670
     * @param array $condition
671
     */
672 48
    protected function applyTreeAttributeCondition(&$condition)
673
    {
674 48
        if ($this->treeAttribute !== false) {
675
            $condition = [
676 48
                'and',
677 48
                $condition,
678 48
                [$this->treeAttribute => $this->owner->getAttribute($this->treeAttribute)]
679 48
            ];
680 48
        }
681
682 48
    }
683
}
684