Test Setup Failed
Push — dev ( df7ea0...1d5d0f )
by
unknown
03:09
created

NodeMoveAction::getLr()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 1
eloc 8
nc 1
nop 1
1
<?php
2
3
namespace devgroup\JsTreeWidget\actions\nestedset;
4
5
use devgroup\JsTreeWidget\widgets\TreeWidget;
6
use yii\base\Action;
7
use Yii;
8
use yii\base\InvalidConfigException;
9
use yii\db\ActiveRecord;
10
use yii\db\Expression;
11
use yii\web\Response;
12
13
/**
14
 * Class NodeMoveAction
15
 *
16
 * @package devgroup\JsTreeWidget\actions\nestedset
17
 */
18
class NodeMoveAction extends Action
19
{
20
    /** @var  ActiveRecord */
21
    public $className;
22
    /** @var string set root column name for multi root tree */
23
    public $rootAttribute = false;
24
    /** @var string  */
25
    public $leftAttribute = 'lft';
26
    /** @var string  */
27
    public $rightAttribute = 'rgt';
28
    /** @var string  */
29
    public $depthAttribute = 'depth';
30
31
    /** @var  ActiveRecord */
32
    private $node;
33
    /** @var  ActiveRecord */
34
    private $parent;
35
    /** @var  string */
36
    private $tableName;
37
38
    /**
39
     * @inheritdoc
40
     */
41 View Code Duplication
    public function init()
0 ignored issues
show
Duplication introduced by
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...
42
    {
43
        if (true === empty($this->className) || false === is_subclass_of($this->className, ActiveRecord::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \yii\db\ActiveRecord::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
44
            throw new InvalidConfigException('"className" param must be set and must be child of ActiveRecord');
45
        }
46
        /** @var ActiveRecord $class */
47
        $class = $this->className;
48
        $this->tableName = $class::tableName();
49
        $scheme = Yii::$app->getDb()->getTableSchema($this->tableName);
50
        $columns = $scheme->columns;
51
        if (false !== $this->rootAttribute && false === isset($columns[$this->rootAttribute])) {
52
            throw new InvalidConfigException("Column '{$this->rootAttribute}' not found in the '{$this->tableName}' table");
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 124 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
53
        }
54
        if (false === isset(
55
                $columns[$this->leftAttribute],
56
                $columns[$this->rightAttribute],
57
                $columns[$this->depthAttribute]
58
            )
59
        ) {
60
            throw new InvalidConfigException(
61
                "Some of the '{$this->leftAttribute}', '{$this->rightAttribute}', '{$this->depthAttribute}', "
62
                . "not found in the '{$this->tableName}' columns list"
63
            );
64
        }
65
        TreeWidget::registerTranslations();
66
        parent::init();
67
    }
68
69
    /**
70
     * @inheritdoc
71
     */
72
    public function run()
73
    {
74
        Yii::$app->response->format = Response::FORMAT_JSON;
75
        $newParentId = Yii::$app->request->post('parent');
76
        $oldParentId = Yii::$app->request->post('old_parent');
77
        $position = Yii::$app->request->post('position');
78
        $oldPosition = Yii::$app->request->post('old_position');
79
        $nodeId = Yii::$app->request->post('node_id');
80
        $siblings = Yii::$app->request->post('siblings', []);
81
        $class = $this->className;
82
        if ((int)$newParentId == 0) {
83
            return ['error' => Yii::t('jstw', 'Can not move node as root!')];
84
        }
85
        if ((null === $node = $class::findOne($nodeId)) || (null === $parent = $class::findOne($newParentId))) {
86
            return ['error' => Yii::t('jstw', 'Invalid node id or parent id received!')];
87
        }
88
        $this->node = $node;
0 ignored issues
show
Documentation Bug introduced by
It seems like $node of type array or boolean is incompatible with the declared type object<yii\db\ActiveRecord> of property $node.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
89
        $this->parent = $parent;
0 ignored issues
show
Documentation Bug introduced by
It seems like $parent of type array or boolean is incompatible with the declared type object<yii\db\ActiveRecord> of property $parent.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
90
        if (false !== $this->rootAttribute && ($node->{$this->rootAttribute} != $parent->{$this->rootAttribute})) {
91
            return $this->moveMultiRoot($position, $siblings, $oldParentId);
92
        }
93
        if ($newParentId == $oldParentId) {
94
            return $this->reorder($oldPosition, $position, $siblings);
95
        } else {
96
            return $this->move($position, $siblings, $oldParentId);
97
        }
98
    }
99
100
    /**
101
     * Moves node inside one parent inside one root
102
     *
103
     * @param null $oldPosition
104
     * @param null $position
105
     * @param array $siblings
106
     * @return array|bool
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>|boolean.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
107
     * @throws \yii\db\Exception
108
     */
109
    public function reorder($oldPosition = null, $position = null, $siblings = [])
110
    {
111
        if (null === $oldPosition || null === $position || true === empty($siblings)) {
112
            return ['error' => Yii::t('jstw', 'Invalid data provided!')];
113
        }
114
        $nodeId = $siblings[$position];
115
        $class = $this->className;
116
        $lr = $workWith = [];
0 ignored issues
show
Unused Code introduced by
$workWith is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
Unused Code introduced by
$lr is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
117
        $nodeOperator = $siblingsOperator = '';
0 ignored issues
show
Unused Code introduced by
$siblingsOperator is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
Unused Code introduced by
$nodeOperator is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
118
        if ($oldPosition > $position) {
119
            //change next
120
            $nodeOperator = '-';
121
            $siblingsOperator = '+';
122
            $workWith = array_slice($siblings, $position, $oldPosition - $position + 1);
123
        } else if ($oldPosition < $position) {
124
            //change previous
125
            $nodeOperator = '+';
126
            $siblingsOperator = '-';
127
            $workWith = array_slice($siblings, $oldPosition, $position - $oldPosition + 1);
128
        } else {
129
            return true;
130
        }
131
        if (true === empty($workWith)) {
132
            return ['error' => Yii::t('jstw', 'Invalid data provided!')];
133
        }
134
        $lr = $workWithLr = $this->getLr($workWith);
135
        if (true === empty($lr)) {
136
            return ['error' => Yii::t('jstw', 'Invalid data provided!')];
137
        }
138
        unset($workWithLr[$nodeId]);
139
        $lft = array_column($workWithLr, $this->leftAttribute);
140
        $lft = min($lft);
141
        $rgt = array_column($workWithLr, $this->rightAttribute);
142
        $rgt = max($rgt);
143
        $nodeCondition = [
144
            'and',
145
            ['>=', $this->leftAttribute, $lft],
146
            ['<=', $this->rightAttribute, $rgt]
147
        ];
148
        $this->applyRootCondition($nodeCondition);
149
        $nodeDelta = $this->getCount($nodeCondition);
150
        $nodeDelta *= 2;
151
        $siblingsCondition = [
152
            'and',
153
            ['>=', $this->leftAttribute, $lr[$nodeId][$this->leftAttribute]],
154
            ['<=', $this->rightAttribute, $lr[$nodeId][$this->rightAttribute]]
155
        ];
156
        $this->applyRootCondition($siblingsCondition);
157
        $nodeChildren = $this->getChildIds($siblingsCondition);
158
        $siblingsDelta = count($nodeChildren) * 2;
159
        $db = Yii::$app->getDb();
160
        $transaction = $db->beginTransaction();
161
        try {
162
            //updating necessary node siblings
163
            $db->createCommand()->update(
164
                $class::tableName(),
165
                [
166
                    $this->leftAttribute => new Expression($this->leftAttribute . sprintf('%s%d', $siblingsOperator, $siblingsDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
167
                    $this->rightAttribute => new Expression($this->rightAttribute . sprintf('%s%d', $siblingsOperator, $siblingsDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
168
                ],
169
                $nodeCondition
170
            )->execute();
171
            //updating node
172
            $db->createCommand()->update(
173
                $class::tableName(),
174
                [
175
                    $this->leftAttribute => new Expression($this->leftAttribute . sprintf('%s%d', $nodeOperator, $nodeDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
176
                    $this->rightAttribute => new Expression($this->rightAttribute . sprintf('%s%d', $nodeOperator, $nodeDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 128 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
177
                ],
178
                ['id' => $nodeChildren]
179
            )->execute();
180
            $transaction->commit();
181
        } catch (\Exception $e) {
182
            $transaction->rollBack();
183
            return ['error' => $e->getMessage()];
184
        }
185
        return true;
186
    }
187
188
    /**
189
     * Moves node inside one root
190
     *
191
     * @param null $position
192
     * @param array $siblings
193
     * @param string | integer $oldParentId
194
     * @return array|bool
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>|boolean.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
195
     * @throws \yii\db\Exception
196
     */
197
    private function move($position = null, $siblings = [], $oldParentId)
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
198
    {
199
        $class = $this->className;
200 View Code Duplication
        if (null === $oldParent = $class::findOne($oldParentId)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
201
            return ['error' => Yii::t('jstw', "Old parent with id '{id}' not found!", ['id' => $oldParentId])];
202
        }
203
        $nodeCountCondition = [
204
            'and',
205
            ['>=', $this->leftAttribute, $this->node{$this->leftAttribute}],
206
            ['<=', $this->rightAttribute, $this->node{$this->rightAttribute}]
207
        ];
208
        $this->applyRootCondition($nodeCountCondition);
209
        $nodeChildren = $this->getChildIds($nodeCountCondition);
210
        $siblingsDelta = count($nodeChildren) * 2;
211 View Code Duplication
        if ($position == 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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
            $compareRight = $this->parent->{$this->leftAttribute} + 1;
213
        } else {
214
            if (false === isset($siblings[$position - 1])) {
215
                return ['error' => Yii::t('jstw', 'New previous sibling not exists')];
216
            }
217
            $newPrevSiblingId = $siblings[$position - 1];
218
            $newPrevSiblingData = $this->getLr($newPrevSiblingId);
219
            $compareRight = $newPrevSiblingData[$newPrevSiblingId][$this->rightAttribute];
220
        }
221
        if ($this->node->{$this->leftAttribute} > $compareRight) {
222
            //move node up
223 View Code Duplication
            if ($position == 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
224
                $leftFrom = $this->parent->{$this->leftAttribute} + 1;
225
            } else {
226
                $leftFrom = $newPrevSiblingData[$newPrevSiblingId][$this->rightAttribute] + 1;
0 ignored issues
show
Bug introduced by
The variable $newPrevSiblingData does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $newPrevSiblingId does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
227
            }
228
            $rightTo = $this->node->{$this->leftAttribute};
229
            $nodeDelta = $this->node->{$this->leftAttribute} - $leftFrom;
230
            $nodeOperator = '-';
231
            $parentOperator = $siblingsOperator = '+';
232
            $newParentUpdateField = $this->rightAttribute;
233
            $oldParentUpdateField = $this->leftAttribute;
234
        } else if ($this->node->{$this->leftAttribute} < $compareRight) {
235
            //move node down
236
            $leftFrom = $this->node->{$this->rightAttribute};
237 View Code Duplication
            if ($position == 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
238
                $rightTo = $this->parent->{$this->leftAttribute};
239
            } else {
240
                $rightTo = $newPrevSiblingData[$newPrevSiblingId][$this->rightAttribute];
241
            }
242
            $nodeOperator = '+';
243
            $parentOperator = $siblingsOperator = '-';
244
            $nodeDelta = $rightTo - $siblingsDelta + 1 - $this->node->{$this->leftAttribute};
245
            $newParentUpdateField = $this->leftAttribute;
246
            $oldParentUpdateField = $this->rightAttribute;
247
        } else {
248
            return ['error' => Yii::t('jstw', 'There are two nodes with same "left" value. This should not be.')];
249
        }
250
        $siblingsCondition = [
251
            'and',
252
            ['>=', $this->leftAttribute, $leftFrom],
253
            ['<=', $this->rightAttribute, $rightTo]
254
        ];
255
        $this->applyRootCondition($siblingsCondition);
256
        $db = Yii::$app->getDb();
257
        $transaction = $db->beginTransaction();
258
        $oldParentDepth = $oldParent->{$this->depthAttribute};
259
        $newParentDepth = $this->parent->{$this->depthAttribute};
260 View Code Duplication
        if ($newParentDepth < $oldParentDepth) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
261
            $depthOperator = '-';
262
            $depthDelta = $oldParentDepth - $newParentDepth;
263
        } else {
264
            $depthOperator = '+';
265
            $depthDelta = $newParentDepth - $oldParentDepth;
266
        }
267
        $commonParentsCondition = [
268
            'and',
269
            ['<', $this->leftAttribute, $leftFrom],
270
            ['>', $this->rightAttribute, $rightTo]
271
        ];
272
        $this->applyRootCondition($commonParentsCondition);
273
        $commonParentsIds = $class::find()->select('id')->where($commonParentsCondition)->column();
274
        $commonCondition = [
275
            ['!=', $this->depthAttribute, 0],
276
            ['not in', 'id', $commonParentsIds],
277
        ];
278
        $this->applyRootCondition($commonCondition);
279
        $newParentCondition = array_merge([
280
            'and',
281
            ['<=', $this->leftAttribute, $this->parent->{$this->leftAttribute}],
282
            ['>=', $this->rightAttribute, $this->parent->{$this->rightAttribute}],
283
        ], $commonCondition);
284
        $oldParentsCondition = array_merge([
285
            'and',
286
            ['<', $this->leftAttribute, $this->node->{$this->leftAttribute}],
287
            ['>', $this->rightAttribute, $this->node->{$this->rightAttribute}],
288
        ], $commonCondition);
289
        try {
290
            //updating necessary node siblings
291
            $db->createCommand()->update(
292
                $class::tableName(),
293
                [
294
                    $this->leftAttribute => new Expression($this->leftAttribute . sprintf('%s%d', $siblingsOperator, $siblingsDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
295
                    $this->rightAttribute => new Expression($this->rightAttribute . sprintf('%s%d', $siblingsOperator, $siblingsDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
296
                ],
297
                $siblingsCondition
298
            )->execute();
299
            //updating old parents
300
            $db->createCommand()->update(
301
                $class::tableName(),
302
                [
303
                    //down - right
304
                    $oldParentUpdateField => new Expression($oldParentUpdateField . sprintf('%s%d', $parentOperator, $siblingsDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
305
                ],
306
                $oldParentsCondition
307
            )->execute();
308
            //updating new parents
309
            $db->createCommand()->update(
310
                $class::tableName(),
311
                [
312
                    //down - left
313
                    $newParentUpdateField => new Expression($newParentUpdateField . sprintf('%s%d', $parentOperator, $siblingsDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
314
                ],
315
                $newParentCondition
316
            )->execute();
317
            //updating node with children
318
            $db->createCommand()->update(
319
                $class::tableName(),
320
                [
321
                    $this->leftAttribute => new Expression($this->leftAttribute . sprintf('%s%d', $nodeOperator, $nodeDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
322
                    $this->rightAttribute => new Expression($this->rightAttribute . sprintf('%s%d', $nodeOperator, $nodeDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 128 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
323
                    $this->depthAttribute => new Expression($this->depthAttribute . sprintf('%s%d', $depthOperator, $depthDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 130 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
324
                ],
325
                ['id' => $nodeChildren]
326
            )->execute();
327
            $transaction->commit();
328
        } catch (\Exception $e) {
329
            $transaction->rollBack();
330
            return ['error' => $e->getMessage()];
331
        }
332
        return true;
333
    }
334
335
    /**
336
     * Moves node between two roots
337
     *
338
     * @param null $position
339
     * @param array $siblings
340
     * @param string | integer $oldParentId
341
     * @return array|bool
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>|boolean.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
342
     * @throws \yii\db\Exception
343
     */
344
    private function moveMultiRoot($position = null, $siblings = [], $oldParentId)
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
345
    {
346
        $class = $this->className;
347 View Code Duplication
        if (null === $oldParent = $class::findOne($oldParentId)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
348
            return ['error' => Yii::t('jstw', "Old parent with id '{id}' not found!", ['id' => $oldParentId])];
349
        }
350
        $nodeCountCondition = [
351
            'and',
352
            ['>=', $this->leftAttribute, $this->node->{$this->leftAttribute}],
353
            ['<=', $this->rightAttribute, $this->node->{$this->rightAttribute}],
354
            [$this->rootAttribute => $this->node->{$this->rootAttribute}]
355
        ];
356
        $nodeChildren = $this->getChildIds($nodeCountCondition);
357
        $siblingsDelta = count($nodeChildren) * 2;
358 View Code Duplication
        if ($position == 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
359
            $leftFrom = $this->parent->{$this->leftAttribute} + 1;
360
        } else {
361
            if (false === isset($siblings[$position - 1])) {
362
                return ['error' => Yii::t('jstw', 'New previous sibling not exists')];
363
            }
364
            $newPrevSiblingId = $siblings[$position - 1];
365
            $newPrevSiblingData = $this->getLr($newPrevSiblingId);
366
            $leftFrom = $newPrevSiblingData[$newPrevSiblingId][$this->rightAttribute] + 1;
367
        }
368
        if ($this->node->{$this->leftAttribute} > $leftFrom) {
369
            $nodeDelta = $this->node->{$this->leftAttribute} - $leftFrom;
370
            $nodeOperator = '-';
371
        } else {
372
            $nodeDelta = $leftFrom - $this->node->{$this->leftAttribute};
373
            $nodeOperator = '+';
374
        }
375
        $siblingsCondition = [
376
            'and',
377
            ['>=', $this->leftAttribute, $leftFrom],
378
            [$this->rootAttribute => $this->parent->{$this->rootAttribute}]
379
        ];
380
        $oldSiblingsCondition = [
381
            'and',
382
            ['>', $this->leftAttribute, $this->node->{$this->rightAttribute}],
383
            [$this->rootAttribute => $this->node->{$this->rootAttribute}]
384
        ];
385
        $db = Yii::$app->getDb();
386
        $transaction = $db->beginTransaction();
387
        $oldParentDepth = $oldParent->{$this->depthAttribute};
388
        $newParentDepth = $this->parent->{$this->depthAttribute};
389 View Code Duplication
        if ($newParentDepth < $oldParentDepth) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
390
            $depthOperator = '-';
391
            $depthDelta = $oldParentDepth - $newParentDepth;
392
        } else {
393
            $depthOperator = '+';
394
            $depthDelta = $newParentDepth - $oldParentDepth;
395
        }
396
        $newParentCondition = [
397
            'and',
398
            ['<=', $this->leftAttribute, $this->parent->{$this->leftAttribute}],
399
            ['>=', $this->rightAttribute, $this->parent->{$this->rightAttribute}],
400
            [$this->rootAttribute => $this->parent->{$this->rootAttribute}]
401
        ];
402
        $oldParentsCondition = [
403
            'and',
404
            ['<=', $this->leftAttribute, $oldParent->{$this->leftAttribute}],
405
            ['>=', $this->rightAttribute, $oldParent->{$this->rightAttribute}],
406
            [$this->rootAttribute => $oldParent->{$this->rootAttribute}]
407
        ];
408
        try {
409
            //updating necessary node new siblings
410
            $db->createCommand()->update(
411
                $class::tableName(),
412
                [
413
                    $this->leftAttribute => new Expression($this->leftAttribute . sprintf('+%d', $siblingsDelta)),
414
                    $this->rightAttribute => new Expression($this->rightAttribute . sprintf('+%d', $siblingsDelta)),
415
                ],
416
                $siblingsCondition
417
            )->execute();
418
            //updating necessary node old siblings
419
            $db->createCommand()->update(
420
                $class::tableName(),
421
                [
422
                    $this->leftAttribute => new Expression($this->leftAttribute . sprintf('-%d', $siblingsDelta)),
423
                    $this->rightAttribute => new Expression($this->rightAttribute . sprintf('-%d', $siblingsDelta)),
424
                ],
425
                $oldSiblingsCondition
426
            )->execute();
427
            //updating old parents
428
            $db->createCommand()->update(
429
                $class::tableName(),
430
                [
431
                    $this->rightAttribute => new Expression($this->rightAttribute . sprintf('-%d', $siblingsDelta)),
432
                ],
433
                $oldParentsCondition
434
            )->execute();
435
            //updating new parents
436
            $db->createCommand()->update(
437
                $class::tableName(),
438
                [
439
                    $this->rightAttribute => new Expression($this->rightAttribute . sprintf('+%d', $siblingsDelta)),
440
                ],
441
                $newParentCondition
442
            )->execute();
443
            //updating node with children
444
            $db->createCommand()->update(
445
                $class::tableName(),
446
                [
447
                    $this->leftAttribute => new Expression($this->leftAttribute . sprintf('%s%d', $nodeOperator, $nodeDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
448
                    $this->rightAttribute => new Expression($this->rightAttribute . sprintf('%s%d', $nodeOperator, $nodeDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 128 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
449
                    $this->depthAttribute => new Expression($this->depthAttribute . sprintf('%s%d', $depthOperator, $depthDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 130 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
450
                    $this->rootAttribute => $this->parent->{$this->rootAttribute}
451
                ],
452
                ['id' => $nodeChildren]
453
            )->execute();
454
            $transaction->commit();
455
        } catch (\Exception $e) {
456
            $transaction->rollBack();
457
            return ['error' => $e->getMessage()];
458
        }
459
        return true;
460
    }
461
462
    /**
463
     * Returns field set of rows to be modified while reordering
464
     *
465
     * @param array $ids
466
     * @return array|\yii\db\ActiveRecord[]
467
     */
468
    private function getLr($ids)
469
    {
470
        $class = $this->className;
471
        return $class::find()
472
            ->select(['id', $this->leftAttribute, $this->rightAttribute])
473
            ->where(['id' => $ids])
474
            ->indexBy('id')
475
            ->asArray(true)
476
            ->all();
477
    }
478
479
    /**
480
     * Returns count of records to be modified while reordering
481
     *
482
     * @param array $condition
483
     * @return int|string
484
     */
485
    public function getCount($condition)
486
    {
487
        $class = $this->className;
488
        return $class::find()
489
            ->select(['id', $this->leftAttribute, $this->rightAttribute, $this->rootAttribute])
490
            ->where($condition)
491
            ->count();
492
    }
493
494
495
    /**
496
     * Returns child ids of selected node
497
     *
498
     * @param array $condition
499
     * @return array
500
     */
501
    private function getChildIds($condition)
502
    {
503
        $class = $this->className;
504
        return $class::find()
505
            ->select('id')
506
            ->where($condition)
507
            ->column();
508
    }
509
510
    /**
511
     * Applies tree root condition if multi root
512
     *
513
     * @param $condition
514
     */
515
    private function applyRootCondition(&$condition)
516
    {
517
        if (false !== $this->rootAttribute) {
518
            $condition[] = [$this->rootAttribute => $this->node->{$this->rootAttribute}];
519
        }
520
    }
521
}