This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * @link https://github.com/paulzi/yii2-nested-intervals |
||
4 | * @copyright Copyright (c) 2015 PaulZi <[email protected]> |
||
5 | * @license MIT (https://github.com/paulzi/yii2-nested-intervals/blob/master/LICENSE) |
||
6 | */ |
||
7 | |||
8 | namespace paulzi\nestedintervals; |
||
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 | use yii\db\Query; |
||
17 | |||
18 | /** |
||
19 | * Nested Intervals Behavior for Yii2 |
||
20 | * @author PaulZi <[email protected]> |
||
21 | * |
||
22 | * @property ActiveRecord $owner |
||
23 | */ |
||
24 | class NestedIntervalsBehavior extends Behavior |
||
25 | { |
||
26 | |||
27 | const OPERATION_MAKE_ROOT = 1; |
||
28 | const OPERATION_PREPEND_TO = 2; |
||
29 | const OPERATION_APPEND_TO = 3; |
||
30 | const OPERATION_INSERT_BEFORE = 4; |
||
31 | const OPERATION_INSERT_AFTER = 5; |
||
32 | const OPERATION_DELETE_ALL = 6; |
||
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 int[] |
||
56 | */ |
||
57 | public $range = [0, 2147483647]; |
||
58 | |||
59 | /** |
||
60 | * @var int|int[] Average amount of children in each level |
||
61 | */ |
||
62 | public $amountOptimize = 10; |
||
63 | |||
64 | /** |
||
65 | * @var float |
||
66 | */ |
||
67 | public $reserveFactor = 1; |
||
68 | |||
69 | /** |
||
70 | * @var bool |
||
71 | */ |
||
72 | public $noPrepend = false; |
||
73 | |||
74 | /** |
||
75 | * @var bool |
||
76 | */ |
||
77 | public $noAppend = false; |
||
78 | |||
79 | /** |
||
80 | * @var bool |
||
81 | */ |
||
82 | public $noInsert = false; |
||
83 | |||
84 | /** |
||
85 | * @var string|null |
||
86 | */ |
||
87 | protected $operation; |
||
88 | |||
89 | /** |
||
90 | * @var ActiveRecord|self|null |
||
91 | */ |
||
92 | protected $node; |
||
93 | |||
94 | /** |
||
95 | * @var string |
||
96 | */ |
||
97 | protected $treeChange; |
||
98 | |||
99 | |||
100 | /** |
||
101 | * @inheritdoc |
||
102 | */ |
||
103 | 299 | public function events() |
|
104 | { |
||
105 | return [ |
||
106 | 299 | ActiveRecord::EVENT_BEFORE_INSERT => 'beforeInsert', |
|
107 | ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert', |
||
108 | ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeUpdate', |
||
109 | ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate', |
||
110 | ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete', |
||
111 | ActiveRecord::EVENT_AFTER_DELETE => 'afterDelete', |
||
112 | ]; |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * @param int|null $depth |
||
117 | * @return \yii\db\ActiveQuery |
||
118 | */ |
||
119 | 73 | public function getParents($depth = null) |
|
120 | { |
||
121 | 73 | $tableName = $this->owner->tableName(); |
|
122 | $condition = [ |
||
123 | 73 | 'and', |
|
124 | 73 | ['<', "{$tableName}.[[{$this->leftAttribute}]]", $this->owner->getAttribute($this->leftAttribute)], |
|
125 | 73 | ['>', "{$tableName}.[[{$this->rightAttribute}]]", $this->owner->getAttribute($this->rightAttribute)], |
|
126 | ]; |
||
127 | 73 | View Code Duplication | if ($depth !== null) { |
128 | 73 | $condition[] = ['>=', "{$tableName}.[[{$this->depthAttribute}]]", $this->owner->getAttribute($this->depthAttribute) - $depth]; |
|
129 | } |
||
130 | |||
131 | 73 | $query = $this->owner->find() |
|
132 | 73 | ->andWhere($condition) |
|
133 | 73 | ->andWhere($this->treeCondition()) |
|
134 | 73 | ->addOrderBy(["{$tableName}.[[{$this->leftAttribute}]]" => SORT_ASC]); |
|
135 | 73 | $query->multiple = true; |
|
136 | |||
137 | 73 | return $query; |
|
138 | } |
||
139 | |||
140 | /** |
||
141 | * @return \yii\db\ActiveQuery |
||
142 | */ |
||
143 | 68 | public function getParent() |
|
144 | { |
||
145 | 68 | $tableName = $this->owner->tableName(); |
|
146 | 68 | $query = $this->getParents(1) |
|
147 | 68 | ->orderBy(["{$tableName}.[[{$this->leftAttribute}]]" => SORT_DESC]) |
|
148 | 68 | ->limit(1); |
|
149 | 68 | $query->multiple = false; |
|
150 | 68 | return $query; |
|
151 | } |
||
152 | |||
153 | /** |
||
154 | * @return \yii\db\ActiveQuery |
||
155 | */ |
||
156 | 21 | public function getRoot() |
|
157 | { |
||
158 | 21 | $tableName = $this->owner->tableName(); |
|
159 | 21 | $query = $this->owner->find() |
|
160 | 21 | ->andWhere(["{$tableName}.[[{$this->leftAttribute}]]" => $this->range[0]]) |
|
161 | 21 | ->andWhere($this->treeCondition()) |
|
162 | 21 | ->limit(1); |
|
163 | 21 | $query->multiple = false; |
|
164 | 21 | return $query; |
|
165 | } |
||
166 | |||
167 | /** |
||
168 | * @param int|null $depth |
||
169 | * @param bool $andSelf |
||
170 | * @param bool $backOrder |
||
171 | * @return \yii\db\ActiveQuery |
||
172 | */ |
||
173 | 174 | public function getDescendants($depth = null, $andSelf = false, $backOrder = false) |
|
174 | { |
||
175 | 174 | $tableName = $this->owner->tableName(); |
|
176 | 174 | $attribute = $backOrder ? $this->rightAttribute : $this->leftAttribute; |
|
177 | $condition = [ |
||
178 | 174 | 'and', |
|
179 | 174 | [$andSelf ? '>=' : '>', "{$tableName}.[[{$attribute}]]", $this->owner->getAttribute($this->leftAttribute)], |
|
180 | 174 | [$andSelf ? '<=' : '<', "{$tableName}.[[{$attribute}]]", $this->owner->getAttribute($this->rightAttribute)], |
|
181 | ]; |
||
182 | |||
183 | 174 | View Code Duplication | if ($depth !== null) { |
184 | 129 | $condition[] = ['<=', "{$tableName}.[[{$this->depthAttribute}]]", $this->owner->getAttribute($this->depthAttribute) + $depth]; |
|
185 | } |
||
186 | |||
187 | 174 | $query = $this->owner->find() |
|
188 | 174 | ->andWhere($condition) |
|
189 | 174 | ->andWhere($this->treeCondition()) |
|
190 | 174 | ->addOrderBy(["{$tableName}.[[{$attribute}]]" => $backOrder ? SORT_DESC : SORT_ASC]); |
|
191 | 174 | $query->multiple = true; |
|
192 | |||
193 | 174 | return $query; |
|
194 | } |
||
195 | |||
196 | /** |
||
197 | * @return \yii\db\ActiveQuery |
||
198 | */ |
||
199 | 114 | public function getChildren() |
|
200 | { |
||
201 | 114 | return $this->getDescendants(1); |
|
202 | } |
||
203 | |||
204 | /** |
||
205 | * @param int|null $depth |
||
206 | * @return \yii\db\ActiveQuery |
||
207 | */ |
||
208 | 5 | public function getLeaves($depth = null) |
|
209 | { |
||
210 | 5 | $tableName = $this->owner->tableName(); |
|
211 | $condition = [ |
||
212 | 5 | 'and', |
|
213 | 5 | ['>', "leaves.[[{$this->leftAttribute}]]", new Expression("{$tableName}.[[{$this->leftAttribute}]]")], |
|
214 | 5 | ['<', "leaves.[[{$this->leftAttribute}]]", new Expression("{$tableName}.[[{$this->rightAttribute}]]")], |
|
215 | ]; |
||
216 | |||
217 | 5 | if ($this->treeAttribute !== null) { |
|
218 | 5 | $condition[] = ["leaves.[[{$this->treeAttribute}]]" => new Expression("{$tableName}.[[{$this->treeAttribute}]]")]; |
|
219 | } |
||
220 | |||
221 | 5 | $query = $this->getDescendants($depth) |
|
222 | 5 | ->leftJoin("{$tableName} leaves", $condition) |
|
223 | 5 | ->andWhere(["leaves.[[{$this->leftAttribute}]]" => null]); |
|
224 | 5 | $query->multiple = true; |
|
225 | 5 | return $query; |
|
226 | } |
||
227 | |||
228 | /** |
||
229 | * @return \yii\db\ActiveQuery |
||
230 | */ |
||
231 | 30 | View Code Duplication | public function getPrev() |
232 | { |
||
233 | 30 | $tableName = $this->owner->tableName(); |
|
234 | 30 | $query = $this->owner->find() |
|
235 | 30 | ->andWhere([ |
|
236 | 30 | 'and', |
|
237 | 30 | ['<', "{$tableName}.[[{$this->rightAttribute}]]", $this->owner->getAttribute($this->leftAttribute)], |
|
238 | 30 | ['>', "{$tableName}.[[{$this->rightAttribute}]]", $this->getParent()->select([$this->leftAttribute])], |
|
239 | ]) |
||
240 | 30 | ->andWhere($this->treeCondition()) |
|
241 | 30 | ->orderBy(["{$tableName}.[[{$this->rightAttribute}]]" => SORT_DESC]) |
|
242 | 30 | ->limit(1); |
|
243 | 30 | $query->multiple = false; |
|
244 | 30 | return $query; |
|
245 | } |
||
246 | |||
247 | /** |
||
248 | * @return \yii\db\ActiveQuery |
||
249 | */ |
||
250 | 33 | View Code Duplication | public function getNext() |
251 | { |
||
252 | 33 | $tableName = $this->owner->tableName(); |
|
253 | 33 | $query = $this->owner->find() |
|
254 | 33 | ->andWhere([ |
|
255 | 33 | 'and', |
|
256 | 33 | ['>', "{$tableName}.[[{$this->leftAttribute}]]", $this->owner->getAttribute($this->rightAttribute)], |
|
257 | 33 | ['<', "{$tableName}.[[{$this->leftAttribute}]]", $this->getParent()->select([$this->rightAttribute])], |
|
258 | ]) |
||
259 | 33 | ->andWhere($this->treeCondition()) |
|
260 | 33 | ->orderBy(["{$tableName}.[[{$this->leftAttribute}]]" => SORT_ASC]) |
|
261 | 33 | ->limit(1); |
|
262 | 33 | $query->multiple = false; |
|
263 | 33 | return $query; |
|
264 | } |
||
265 | |||
266 | /** |
||
267 | * Populate children relations for self and all descendants |
||
268 | * @param int $depth = null |
||
269 | * @param string|array $with = null |
||
270 | * @return static |
||
271 | */ |
||
272 | 5 | public function populateTree($depth = null, $with = null) |
|
273 | { |
||
274 | /** @var ActiveRecord[]|static[] $nodes */ |
||
275 | 5 | $query = $this->getDescendants($depth); |
|
276 | 5 | if ($with) { |
|
277 | $query->with($with); |
||
278 | } |
||
279 | 5 | $nodes = $query->all(); |
|
280 | |||
281 | 5 | $key = $this->owner->getAttribute($this->leftAttribute); |
|
282 | 5 | $relates = []; |
|
283 | 5 | $parents = [$key]; |
|
284 | 5 | $prev = $this->owner->getAttribute($this->depthAttribute); |
|
285 | 5 | foreach($nodes as $node) |
|
286 | { |
||
287 | 5 | $level = $node->getAttribute($this->depthAttribute); |
|
288 | 5 | if ($level <= $prev) { |
|
289 | 5 | $parents = array_slice($parents, 0, $level - $prev - 1); |
|
290 | } |
||
291 | |||
292 | 5 | $key = end($parents); |
|
293 | 5 | if (!isset($relates[$key])) { |
|
294 | 5 | $relates[$key] = []; |
|
295 | } |
||
296 | 5 | $relates[$key][] = $node; |
|
297 | |||
298 | 5 | $parents[] = $node->getAttribute($this->leftAttribute); |
|
299 | 5 | $prev = $level; |
|
300 | } |
||
301 | |||
302 | 5 | $ownerDepth = $this->owner->getAttribute($this->depthAttribute); |
|
303 | 5 | $nodes[] = $this->owner; |
|
304 | 5 | foreach ($nodes as $node) { |
|
305 | 5 | $key = $node->getAttribute($this->leftAttribute); |
|
306 | 5 | if (isset($relates[$key])) { |
|
307 | 5 | $node->populateRelation('children', $relates[$key]); |
|
308 | 5 | } elseif ($depth === null || $ownerDepth + $depth > $node->getAttribute($this->depthAttribute)) { |
|
309 | 5 | $node->populateRelation('children', []); |
|
310 | } |
||
311 | } |
||
312 | |||
313 | 5 | return $this->owner; |
|
314 | } |
||
315 | |||
316 | /** |
||
317 | * @return bool |
||
318 | */ |
||
319 | 80 | public function isRoot() |
|
320 | { |
||
321 | 80 | return $this->owner->getAttribute($this->leftAttribute) == $this->range[0]; |
|
322 | } |
||
323 | |||
324 | /** |
||
325 | * @param ActiveRecord $node |
||
326 | * @return bool |
||
327 | */ |
||
328 | 83 | public function isChildOf($node) |
|
329 | { |
||
330 | 83 | $result = $this->owner->getAttribute($this->leftAttribute) > $node->getAttribute($this->leftAttribute) |
|
331 | 83 | && $this->owner->getAttribute($this->rightAttribute) < $node->getAttribute($this->rightAttribute); |
|
332 | |||
333 | 83 | if ($result && $this->treeAttribute !== null) { |
|
334 | 11 | $result = $this->owner->getAttribute($this->treeAttribute) === $node->getAttribute($this->treeAttribute); |
|
335 | } |
||
336 | |||
337 | 83 | return $result; |
|
338 | } |
||
339 | |||
340 | /** |
||
341 | * @return bool |
||
342 | */ |
||
343 | 5 | public function isLeaf() |
|
344 | { |
||
345 | 5 | return count($this->owner->children) === 0; |
|
346 | } |
||
347 | |||
348 | /** |
||
349 | * @return ActiveRecord |
||
350 | */ |
||
351 | 13 | public function makeRoot() |
|
352 | { |
||
353 | 13 | $this->operation = self::OPERATION_MAKE_ROOT; |
|
0 ignored issues
–
show
|
|||
354 | 13 | return $this->owner; |
|
355 | } |
||
356 | |||
357 | /** |
||
358 | * @param ActiveRecord $node |
||
359 | * @return ActiveRecord |
||
360 | */ |
||
361 | 61 | public function prependTo($node) |
|
362 | { |
||
363 | 61 | $this->operation = self::OPERATION_PREPEND_TO; |
|
0 ignored issues
–
show
It seems like
self::OPERATION_PREPEND_TO of type integer is incompatible with the declared type string|null of property $operation .
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.. ![]() |
|||
364 | 61 | $this->node = $node; |
|
365 | 61 | return $this->owner; |
|
366 | } |
||
367 | |||
368 | /** |
||
369 | * @param ActiveRecord $node |
||
370 | * @return ActiveRecord |
||
371 | */ |
||
372 | 61 | public function appendTo($node) |
|
373 | { |
||
374 | 61 | $this->operation = self::OPERATION_APPEND_TO; |
|
0 ignored issues
–
show
It seems like
self::OPERATION_APPEND_TO of type integer is incompatible with the declared type string|null of property $operation .
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.. ![]() |
|||
375 | 61 | $this->node = $node; |
|
376 | 61 | return $this->owner; |
|
377 | } |
||
378 | |||
379 | /** |
||
380 | * @param ActiveRecord $node |
||
381 | * @return ActiveRecord |
||
382 | */ |
||
383 | 31 | public function insertBefore($node) |
|
384 | { |
||
385 | 31 | $this->operation = self::OPERATION_INSERT_BEFORE; |
|
0 ignored issues
–
show
It seems like
self::OPERATION_INSERT_BEFORE of type integer is incompatible with the declared type string|null of property $operation .
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.. ![]() |
|||
386 | 31 | $this->node = $node; |
|
387 | 31 | return $this->owner; |
|
388 | } |
||
389 | |||
390 | /** |
||
391 | * @param ActiveRecord $node |
||
392 | * @return ActiveRecord |
||
393 | */ |
||
394 | 34 | public function insertAfter($node) |
|
395 | { |
||
396 | 34 | $this->operation = self::OPERATION_INSERT_AFTER; |
|
0 ignored issues
–
show
It seems like
self::OPERATION_INSERT_AFTER of type integer is incompatible with the declared type string|null of property $operation .
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.. ![]() |
|||
397 | 34 | $this->node = $node; |
|
398 | 34 | return $this->owner; |
|
399 | } |
||
400 | |||
401 | /** |
||
402 | * Need for paulzi/auto-tree |
||
403 | */ |
||
404 | public function preDeleteWithChildren() |
||
405 | { |
||
406 | $this->operation = self::OPERATION_DELETE_ALL; |
||
0 ignored issues
–
show
It seems like
self::OPERATION_DELETE_ALL of type integer is incompatible with the declared type string|null of property $operation .
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.. ![]() |
|||
407 | } |
||
408 | |||
409 | /** |
||
410 | * @return bool|int |
||
411 | * @throws \Exception |
||
412 | * @throws \yii\db\Exception |
||
413 | */ |
||
414 | 11 | public function deleteWithChildren() |
|
415 | { |
||
416 | 11 | $this->operation = self::OPERATION_DELETE_ALL; |
|
0 ignored issues
–
show
It seems like
self::OPERATION_DELETE_ALL of type integer is incompatible with the declared type string|null of property $operation .
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.. ![]() |
|||
417 | 11 | if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) { |
|
418 | $transaction = $this->owner->getDb()->beginTransaction(); |
||
419 | try { |
||
420 | $result = $this->deleteWithChildrenInternal(); |
||
421 | if ($result === false) { |
||
422 | $transaction->rollBack(); |
||
423 | } else { |
||
424 | $transaction->commit(); |
||
425 | } |
||
426 | return $result; |
||
427 | } catch (\Exception $e) { |
||
428 | $transaction->rollBack(); |
||
429 | throw $e; |
||
430 | } |
||
431 | } else { |
||
432 | 11 | $result = $this->deleteWithChildrenInternal(); |
|
433 | } |
||
434 | 8 | return $result; |
|
435 | } |
||
436 | |||
437 | /** |
||
438 | * @return ActiveRecord |
||
439 | * @throws \Exception |
||
440 | * @throws \yii\db\Exception |
||
441 | */ |
||
442 | 5 | public function optimize() |
|
443 | { |
||
444 | 5 | if (!$this->owner->isTransactional(ActiveRecord::OP_UPDATE)) { |
|
445 | $transaction = $this->owner->getDb()->beginTransaction(); |
||
446 | try { |
||
447 | $this->optimizeInternal(); |
||
448 | $transaction->commit(); |
||
449 | } catch (\Exception $e) { |
||
450 | $transaction->rollBack(); |
||
451 | throw $e; |
||
452 | } |
||
453 | } else { |
||
454 | 5 | $this->optimizeInternal(); |
|
455 | } |
||
456 | 5 | return $this->owner; |
|
457 | } |
||
458 | |||
459 | /** |
||
460 | * @throws Exception |
||
461 | * @throws NotSupportedException |
||
462 | */ |
||
463 | 102 | public function beforeInsert() |
|
464 | { |
||
465 | 102 | if ($this->node !== null && !$this->node->getIsNewRecord()) { |
|
466 | 79 | $this->node->refresh(); |
|
467 | } |
||
468 | 102 | switch ($this->operation) { |
|
469 | 102 | case self::OPERATION_MAKE_ROOT: |
|
470 | 8 | $condition = array_merge([$this->leftAttribute => $this->range[0]], $this->treeCondition()); |
|
471 | 8 | if ($this->owner->find()->andWhere($condition)->one() !== null) { |
|
472 | 3 | throw new Exception('Can not create more than one root.'); |
|
473 | } |
||
474 | 5 | $this->owner->setAttribute($this->leftAttribute, $this->range[0]); |
|
475 | 5 | $this->owner->setAttribute($this->rightAttribute, $this->range[1]); |
|
476 | 5 | $this->owner->setAttribute($this->depthAttribute, 0); |
|
477 | 5 | break; |
|
478 | |||
479 | 94 | case self::OPERATION_PREPEND_TO: |
|
480 | 33 | $this->findPrependRange($left, $right); |
|
481 | 33 | $this->insertNode($left, $right, 1, false); |
|
482 | 30 | break; |
|
483 | |||
484 | 61 | case self::OPERATION_APPEND_TO: |
|
485 | 33 | $this->findAppendRange($left, $right); |
|
486 | 33 | $this->insertNode($left, $right, 1, true); |
|
487 | 30 | break; |
|
488 | |||
489 | 28 | case self::OPERATION_INSERT_BEFORE: |
|
490 | 11 | $parent = $this->findInsertBeforeRange($left, $right); |
|
491 | 11 | $this->insertNode($left, $right, 0, false, $parent); |
|
492 | 8 | break; |
|
493 | |||
494 | 17 | case self::OPERATION_INSERT_AFTER: |
|
495 | 14 | $parent = $this->findInsertAfterRange($left, $right); |
|
496 | 14 | $this->insertNode($left, $right, 0, true, $parent); |
|
497 | 8 | break; |
|
498 | |||
499 | default: |
||
500 | 3 | throw new NotSupportedException('Method "'. $this->owner->className() . '::insert" is not supported for inserting new nodes.'); |
|
501 | } |
||
502 | 81 | } |
|
503 | |||
504 | /** |
||
505 | * @throws Exception |
||
506 | */ |
||
507 | 81 | public function afterInsert() |
|
508 | { |
||
509 | 81 | if ($this->operation === self::OPERATION_MAKE_ROOT && $this->treeAttribute !== null && $this->owner->getAttribute($this->treeAttribute) === null) { |
|
510 | 5 | $id = $this->owner->getPrimaryKey(); |
|
511 | 5 | $this->owner->setAttribute($this->treeAttribute, $id); |
|
512 | 5 | $this->owner->updateAll([$this->treeAttribute => $id], [$this->getPrimaryKey() => $id]); |
|
513 | } |
||
514 | 81 | $this->operation = null; |
|
515 | 81 | $this->node = null; |
|
516 | 81 | } |
|
517 | |||
518 | /** |
||
519 | * @throws Exception |
||
520 | */ |
||
521 | 104 | public function beforeUpdate() |
|
522 | { |
||
523 | 104 | if ($this->node !== null && !$this->node->getIsNewRecord()) { |
|
524 | 90 | $this->node->refresh(); |
|
525 | } |
||
526 | |||
527 | 104 | switch ($this->operation) { |
|
528 | 104 | case self::OPERATION_MAKE_ROOT: |
|
529 | 5 | if ($this->treeAttribute === null) { |
|
530 | throw new Exception('Can not move a node as the root when "treeAttribute" is not set.'); |
||
531 | } |
||
532 | 5 | if ($this->owner->getOldAttribute($this->treeAttribute) !== $this->owner->getAttribute($this->treeAttribute)) { |
|
533 | 5 | $this->treeChange = $this->owner->getAttribute($this->treeAttribute); |
|
534 | 5 | $this->owner->setAttribute($this->treeAttribute, $this->owner->getOldAttribute($this->treeAttribute)); |
|
535 | } |
||
536 | 5 | break; |
|
537 | |||
538 | 99 | case self::OPERATION_INSERT_BEFORE: |
|
539 | 79 | case self::OPERATION_INSERT_AFTER: |
|
540 | 40 | if ($this->node->isRoot()) { |
|
541 | throw new Exception('Can not move a node before/after root.'); |
||
542 | } |
||
543 | |||
544 | 59 | case self::OPERATION_PREPEND_TO: |
|
545 | 31 | case self::OPERATION_APPEND_TO: |
|
546 | 96 | if ($this->node->getIsNewRecord()) { |
|
547 | 6 | throw new Exception('Can not move a node when the target node is new record.'); |
|
548 | } |
||
549 | |||
550 | 90 | if ($this->owner->equals($this->node)) { |
|
551 | 12 | throw new Exception('Can not move a node when the target node is same.'); |
|
552 | } |
||
553 | |||
554 | 78 | if ($this->node->isChildOf($this->owner)) { |
|
555 | 12 | throw new Exception('Can not move a node when the target node is child.'); |
|
556 | } |
||
557 | } |
||
558 | 74 | } |
|
559 | |||
560 | /** |
||
561 | * |
||
562 | */ |
||
563 | 74 | public function afterUpdate() |
|
564 | { |
||
565 | 74 | switch ($this->operation) { |
|
566 | 74 | case self::OPERATION_MAKE_ROOT: |
|
567 | 5 | $this->moveNodeAsRoot(); |
|
568 | 5 | break; |
|
569 | |||
570 | 69 | case self::OPERATION_PREPEND_TO: |
|
571 | 19 | $this->findPrependRange($left, $right); |
|
572 | 19 | if ($right !== $this->owner->getAttribute($this->leftAttribute)) { |
|
573 | 16 | $this->moveNode($left, $right, 1); |
|
574 | } |
||
575 | 19 | break; |
|
576 | |||
577 | 50 | View Code Duplication | case self::OPERATION_APPEND_TO: |
578 | 19 | $this->findAppendRange($left, $right); |
|
579 | 19 | if ($left !== $this->owner->getAttribute($this->rightAttribute)) { |
|
580 | 16 | $this->moveNode($left, $right, 1); |
|
581 | } |
||
582 | 19 | break; |
|
583 | |||
584 | 31 | View Code Duplication | case self::OPERATION_INSERT_BEFORE: |
585 | 14 | $this->findInsertBeforeRange($left, $right); |
|
586 | 14 | if ($left !== $this->owner->getAttribute($this->rightAttribute)) { |
|
587 | 11 | $this->moveNode($left, $right); |
|
588 | } |
||
589 | 14 | break; |
|
590 | |||
591 | 17 | case self::OPERATION_INSERT_AFTER: |
|
592 | 14 | $this->findInsertAfterRange($left, $right); |
|
593 | 14 | if ($right !== $this->owner->getAttribute($this->leftAttribute)) { |
|
594 | 11 | $this->moveNode($left, $right); |
|
595 | } |
||
596 | 14 | break; |
|
597 | } |
||
598 | 74 | $this->operation = null; |
|
599 | 74 | $this->node = null; |
|
600 | 74 | } |
|
601 | |||
602 | /** |
||
603 | * @throws Exception |
||
604 | */ |
||
605 | 22 | public function beforeDelete() |
|
606 | { |
||
607 | 22 | if ($this->owner->getIsNewRecord()) { |
|
608 | 6 | throw new Exception('Can not delete a node when it is new record.'); |
|
609 | } |
||
610 | 16 | if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) { |
|
611 | 3 | throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.'); |
|
612 | } |
||
613 | 13 | $this->owner->refresh(); |
|
614 | 13 | } |
|
615 | |||
616 | /** |
||
617 | * |
||
618 | */ |
||
619 | 13 | public function afterDelete() |
|
620 | { |
||
621 | 13 | if ($this->operation !== self::OPERATION_DELETE_ALL) { |
|
622 | 5 | $this->owner->updateAll([$this->depthAttribute => new Expression("[[{$this->depthAttribute}]] - 1")], $this->getDescendants()->where); |
|
623 | } |
||
624 | 13 | $this->operation = null; |
|
625 | 13 | $this->node = null; |
|
626 | 13 | } |
|
627 | |||
628 | /** |
||
629 | * @return mixed |
||
630 | * @throws Exception |
||
631 | */ |
||
632 | 31 | protected function getPrimaryKey() |
|
633 | { |
||
634 | 31 | $primaryKey = $this->owner->primaryKey(); |
|
635 | 31 | if (!isset($primaryKey[0])) { |
|
636 | throw new Exception('"' . $this->owner->className() . '" must have a primary key.'); |
||
637 | } |
||
638 | 31 | return $primaryKey[0]; |
|
639 | } |
||
640 | |||
641 | /** |
||
642 | * @return self |
||
643 | */ |
||
644 | 157 | public function getNodeBehavior() |
|
645 | { |
||
646 | 157 | foreach ($this->node->behaviors as $behavior) { |
|
647 | 157 | if ($behavior instanceof NestedIntervalsBehavior) { |
|
648 | 157 | return $behavior; |
|
649 | } |
||
650 | } |
||
651 | return null; |
||
652 | } |
||
653 | |||
654 | /** |
||
655 | * @return int |
||
656 | */ |
||
657 | 11 | protected function deleteWithChildrenInternal() |
|
658 | { |
||
659 | 11 | if (!$this->owner->beforeDelete()) { |
|
660 | return false; |
||
661 | } |
||
662 | 8 | $result = $this->owner->deleteAll($this->getDescendants(null, true)->where); |
|
663 | 8 | $this->owner->setOldAttributes(null); |
|
664 | 8 | $this->owner->afterDelete(); |
|
665 | 8 | return $result; |
|
666 | } |
||
667 | |||
668 | /** |
||
669 | * @param int $left |
||
670 | * @param int $right |
||
671 | * @param int $depth |
||
672 | * @param bool $forward |
||
673 | * @param array|null $parent |
||
674 | * @throws Exception |
||
675 | */ |
||
676 | 91 | protected function insertNode($left, $right, $depth = 0, $forward = true, $parent = null) |
|
677 | { |
||
678 | 91 | if ($this->node->getIsNewRecord()) { |
|
679 | 12 | throw new Exception('Can not create a node when the target node is new record.'); |
|
680 | } |
||
681 | |||
682 | 79 | if ($depth === 0 && $this->getNodeBehavior()->isRoot()) { |
|
683 | 3 | throw new Exception('Can not insert a node before/after root.'); |
|
684 | } |
||
685 | 76 | $this->owner->setAttribute($this->depthAttribute, $this->node->getAttribute($this->depthAttribute) + $depth); |
|
686 | 76 | if ($this->treeAttribute !== null) { |
|
687 | 76 | $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute)); |
|
688 | } |
||
689 | 76 | if ($right - $left < 3) { |
|
690 | 32 | for ($i = $right - $left; $i < 3; $i++) { |
|
691 | 32 | $unallocated = $this->findUnallocatedAll($left, $right); |
|
692 | 32 | if ($unallocated < $left) { |
|
693 | 5 | $this->shift($unallocated, $left, -1); |
|
694 | 5 | $left--; |
|
695 | } else { |
||
696 | 30 | $this->shift($right, $unallocated, 1); |
|
697 | 30 | $right++; |
|
698 | } |
||
699 | } |
||
700 | 32 | $this->owner->setAttribute($this->leftAttribute, $left + 1); |
|
701 | 32 | $this->owner->setAttribute($this->rightAttribute, $right - 1); |
|
702 | } else { |
||
703 | 76 | $left++; |
|
704 | 76 | $right--; |
|
705 | |||
706 | 76 | $isPadding = false; |
|
707 | 76 | if ($depth === 1 || $parent !== null) { |
|
708 | // prepending/appending |
||
709 | 66 | if (is_array($this->amountOptimize)) { |
|
710 | if (isset($this->amountOptimize[$depth - 1])) { |
||
711 | $amountOptimize = $this->amountOptimize[$depth - 1]; |
||
712 | } else { |
||
713 | $amountOptimize = $this->amountOptimize[count($this->amountOptimize) - 1]; |
||
714 | } |
||
715 | } else { |
||
716 | 66 | $amountOptimize = $this->amountOptimize; |
|
717 | } |
||
718 | 66 | $pLeft = $parent !== null ? (int)$parent[$this->leftAttribute] : $this->node->getAttribute($this->leftAttribute); |
|
719 | 66 | $pRight = $parent !== null ? (int)$parent[$this->rightAttribute] : $this->node->getAttribute($this->rightAttribute); |
|
720 | 66 | $isCenter = !$this->noAppend && !$this->noPrepend; |
|
721 | 66 | $isFirst = $left === $pLeft + 1 && $right === $pRight - 1; |
|
722 | 66 | $isPadding = !$this->noInsert || ($isFirst && ($forward ? !$this->noPrepend : !$this->noAppend)); |
|
723 | 66 | $step = $amountOptimize + $this->reserveFactor * ($this->noInsert ? ($isCenter ? 2 : 1) : $amountOptimize + 1); |
|
724 | 66 | $step = ($pRight - $pLeft - 1) / $step; |
|
725 | 66 | $stepGap = $step * $this->reserveFactor; |
|
726 | 66 | $padding = $isPadding ? $stepGap : 0; |
|
727 | |||
728 | 66 | if ($forward) { |
|
729 | 33 | $pLeft = $left + (int)floor($padding); |
|
730 | 33 | $pRight = $left + (int)floor($padding + $step) - 1; |
|
731 | } else { |
||
732 | 33 | $pLeft = $right - (int)floor($padding + $step) + 1; |
|
733 | 33 | $pRight = $right - (int)floor($padding); |
|
734 | } |
||
735 | 66 | if ($isFirst && $isCenter) { |
|
736 | 20 | $initPosition = (int)floor(($amountOptimize - 1) / 2) * (int)floor($step + ($this->noInsert ? 0 : $stepGap)) + ($this->noInsert ? 1 : 0); |
|
737 | 20 | $pLeft += $forward ? $initPosition : -$initPosition; |
|
738 | 20 | $pRight += $forward ? $initPosition : -$initPosition; |
|
739 | } |
||
740 | 66 | if ($pLeft < $pRight && $pRight <= $right && $left <= $pLeft && ($forward || $left < $pLeft) && (!$forward || $pRight < $right)) { |
|
741 | 64 | $this->owner->setAttribute($this->leftAttribute, $pLeft); |
|
742 | 64 | $this->owner->setAttribute($this->rightAttribute, $pRight); |
|
743 | 64 | return; |
|
744 | } |
||
745 | } |
||
746 | |||
747 | 52 | $isPadding = $isPadding || !$this->noInsert; |
|
748 | 52 | $step = (int)floor(($right - $left) / ($isPadding ? 3 : 2)); |
|
749 | 52 | $this->owner->setAttribute($this->leftAttribute, $left + ($forward && !$isPadding ? 0 : $step)); |
|
750 | 52 | $this->owner->setAttribute($this->rightAttribute, $right - (!$forward && !$isPadding ? 0 : $step)); |
|
751 | } |
||
752 | 66 | } |
|
753 | |||
754 | /** |
||
755 | * @param int $left |
||
756 | * @param int $right |
||
757 | * @param int $depth |
||
758 | * @throws Exception |
||
759 | */ |
||
760 | 54 | protected function moveNode($left, $right, $depth = 0) |
|
761 | { |
||
762 | 54 | $targetDepth = $this->node->getAttribute($this->depthAttribute) + $depth; |
|
763 | 54 | $depthDelta = $this->owner->getAttribute($this->depthAttribute) - $targetDepth; |
|
764 | 54 | if ($this->treeAttribute === null || $this->owner->getAttribute($this->treeAttribute) === $this->node->getAttribute($this->treeAttribute)) { |
|
765 | // same root |
||
766 | 38 | $sLeft = $this->owner->getAttribute($this->leftAttribute); |
|
767 | 38 | $sRight = $this->owner->getAttribute($this->rightAttribute); |
|
768 | 38 | $this->owner->updateAll( |
|
769 | 38 | [$this->depthAttribute => new Expression("-[[{$this->depthAttribute}]]" . sprintf('%+d', $depthDelta))], |
|
770 | 38 | $this->getDescendants(null, true)->where |
|
771 | ); |
||
772 | 38 | $sDelta = $sRight - $sLeft + 1; |
|
773 | 38 | if ($sLeft >= $right) { |
|
774 | 22 | $this->shift($right, $sLeft - 1, $sDelta); |
|
775 | 22 | $delta = $right - $sLeft; |
|
776 | } else { |
||
777 | 16 | $this->shift($sRight + 1, $left, -$sDelta); |
|
778 | 16 | $delta = $left - $sRight; |
|
779 | } |
||
780 | 38 | $this->owner->updateAll( |
|
781 | [ |
||
782 | 38 | $this->leftAttribute => new Expression("[[{$this->leftAttribute}]]" . sprintf('%+d', $delta)), |
|
783 | 38 | $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', $delta)), |
|
784 | 38 | $this->depthAttribute => new Expression("-[[{$this->depthAttribute}]]"), |
|
785 | ], |
||
786 | [ |
||
787 | 38 | 'and', |
|
788 | 38 | $this->getDescendants(null, true)->where, |
|
789 | 38 | ['<', $this->depthAttribute, 0], |
|
790 | ] |
||
791 | ); |
||
792 | } else { |
||
793 | // move from other root (slow!) |
||
794 | /** @var ActiveRecord|self $root */ |
||
795 | 16 | $root = $this->getNodeBehavior()->getRoot()->one(); |
|
796 | 16 | $countTo = (int)$root->getDescendants()->orderBy(null)->count(); |
|
797 | 16 | $countFrom = (int)$this->getDescendants(null, true)->orderBy(null)->count(); |
|
798 | 16 | $size = (int)floor(($this->range[1] - $this->range[0]) / (($countFrom + $countTo) * 2 + 1)); |
|
799 | |||
800 | 16 | $leftIdx = $this->optimizeAttribute($root->getDescendants(null, false, false), $this->leftAttribute, $this->range[0], $size, 0, $left, $countFrom, $targetDepth); |
|
801 | 16 | $rightIdx = $this->optimizeAttribute($root->getDescendants(null, false, true), $this->rightAttribute, $this->range[1], $size, 0, $right, $countFrom, $targetDepth); |
|
802 | 16 | if ($leftIdx !== null && $rightIdx !== null) { |
|
803 | 16 | $this->optimizeAttribute($this->getDescendants(null, true, false), $this->leftAttribute, $this->range[0], $size, $leftIdx); |
|
804 | 16 | $this->optimizeAttribute($this->getDescendants(null, true, true), $this->rightAttribute, $this->range[1], $size, $rightIdx, null, 0, null, [ |
|
805 | 16 | $this->treeAttribute => $this->node->getAttribute($this->treeAttribute), |
|
806 | 16 | $this->depthAttribute => new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', -$depthDelta)), |
|
807 | ]); |
||
808 | } else { |
||
809 | throw new Exception('Error move a node from other tree'); |
||
810 | } |
||
811 | } |
||
812 | 54 | } |
|
813 | |||
814 | /** |
||
815 | * |
||
816 | */ |
||
817 | 5 | protected function moveNodeAsRoot() |
|
818 | { |
||
819 | 5 | $left = $this->owner->getAttribute($this->leftAttribute); |
|
820 | 5 | $right = $this->owner->getAttribute($this->rightAttribute); |
|
821 | 5 | $depth = $this->owner->getAttribute($this->depthAttribute); |
|
822 | 5 | $tree = $this->treeChange ? $this->treeChange : $this->owner->getPrimaryKey(); |
|
823 | |||
824 | 5 | $factor = ($this->range[1] - $this->range[0]) / ($right - $left); |
|
825 | 5 | $formula = sprintf('ROUND(([[%%s]] * 1.0 %+d) * %d %+d)', -$left, (int)$factor, $this->range[0]); |
|
826 | 5 | $this->owner->updateAll( |
|
827 | [ |
||
828 | 5 | $this->leftAttribute => new Expression(sprintf($formula, $this->leftAttribute)), |
|
829 | 5 | $this->rightAttribute => new Expression(sprintf($formula, $this->rightAttribute)), |
|
830 | 5 | $this->depthAttribute => new Expression("[[{$this->depthAttribute}]] - {$depth}"), |
|
831 | 5 | $this->treeAttribute => $tree, |
|
832 | ], |
||
833 | 5 | $this->getDescendants()->where |
|
834 | ); |
||
835 | 5 | $this->owner->updateAll( |
|
836 | [ |
||
837 | 5 | $this->leftAttribute => $this->range[0], |
|
838 | 5 | $this->rightAttribute => $this->range[1], |
|
839 | 5 | $this->depthAttribute => 0, |
|
840 | 5 | $this->treeAttribute => $tree, |
|
841 | ], |
||
842 | 5 | [$this->getPrimaryKey() => $this->owner->getPrimaryKey()] |
|
843 | ); |
||
844 | 5 | $this->owner->refresh(); |
|
845 | 5 | } |
|
846 | |||
847 | /** |
||
848 | * @throws Exception |
||
849 | */ |
||
850 | 5 | protected function optimizeInternal() |
|
851 | { |
||
852 | 5 | $left = $this->owner->getAttribute($this->leftAttribute); |
|
853 | 5 | $right = $this->owner->getAttribute($this->rightAttribute); |
|
854 | |||
855 | 5 | $count = $this->getDescendants()->orderBy(null)->count() * 2 + 1; |
|
856 | 5 | $size = (int)floor(($right - $left) / $count); |
|
857 | |||
858 | 5 | $this->optimizeAttribute($this->getDescendants(null, false, false), $this->leftAttribute, $left, $size); |
|
859 | 5 | $this->optimizeAttribute($this->getDescendants(null, false, true), $this->rightAttribute, $right, $size); |
|
860 | 5 | } |
|
861 | |||
862 | /** |
||
863 | * @param \yii\db\ActiveQuery $query |
||
864 | * @param string $attribute |
||
865 | * @param int $from |
||
866 | * @param int $size |
||
867 | * @param int $offset |
||
868 | * @param int|null $freeFrom |
||
869 | * @param int $freeSize |
||
870 | * @param int|null $targetDepth |
||
871 | * @param array $additional |
||
872 | * @return int|null |
||
873 | * @throws Exception |
||
874 | * @throws \yii\db\Exception |
||
875 | */ |
||
876 | 21 | protected function optimizeAttribute($query, $attribute, $from, $size, $offset = 0, $freeFrom = null, $freeSize = 0, $targetDepth = null, $additional = []) |
|
877 | { |
||
878 | 21 | $primaryKey = $this->getPrimaryKey(); |
|
879 | 21 | $result = null; |
|
880 | 21 | $isForward = $attribute === $this->leftAttribute; |
|
881 | |||
882 | // @todo: pgsql and mssql optimization |
||
883 | 21 | if (in_array($this->owner->getDb()->driverName, ['mysql', 'mysqli'])) { |
|
884 | // mysql optimization |
||
885 | 8 | $tableName = $this->owner->tableName(); |
|
886 | 8 | $additionalString = null; |
|
887 | 8 | $additionalParams = []; |
|
888 | 8 | foreach ($additional as $name => $value) { |
|
889 | 6 | $additionalString .= ", [[{$name}]] = "; |
|
890 | 6 | if ($value instanceof Expression) { |
|
891 | 6 | $additionalString .= $value->expression; |
|
892 | 6 | foreach ($value->params as $n => $v) { |
|
893 | 6 | $additionalParams[$n] = $v; |
|
894 | } |
||
895 | } else { |
||
896 | 6 | $paramName = ':nestedIntervals' . count($additionalParams); |
|
897 | 6 | $additionalString .= $paramName; |
|
898 | 6 | $additionalParams[$paramName] = $value; |
|
899 | } |
||
900 | } |
||
901 | |||
902 | $command = $query |
||
903 | 8 | ->select([$primaryKey, $attribute, $this->depthAttribute]) |
|
904 | 8 | ->orderBy([$attribute => $isForward ? SORT_ASC : SORT_DESC]) |
|
905 | 8 | ->createCommand(); |
|
906 | 8 | $this->owner->getDb()->createCommand(" |
|
907 | UPDATE |
||
908 | 8 | {$tableName} u, |
|
909 | (SELECT |
||
910 | 8 | [[{$primaryKey}]], |
|
911 | IF (@i := @i + 1, 0, 0) |
||
912 | 8 | + IF ([[{$attribute}]] " . ($isForward ? '>' : '<') . " @freeFrom, |
|
913 | IF ( |
||
914 | (@result := @i) |
||
915 | + IF (@depth - :targetDepth > 0, @result := @result + @depth - :targetDepth, 0) |
||
916 | + (@i := @i + :freeSize * 2) |
||
917 | + (@freeFrom := NULL), 0, 0), |
||
918 | 0) |
||
919 | 8 | + IF (@depth - [[{$this->depthAttribute}]] >= 0, |
|
920 | 8 | IF (@i := @i + @depth - [[{$this->depthAttribute}]] + 1, 0, 0), |
|
921 | 0) |
||
922 | 8 | + (:from " . ($isForward ? '+' : '-') . " (CAST(@i AS UNSIGNED INTEGER) + :offset) * :size) |
|
923 | 8 | + IF ([[{$attribute}]] = @freeFrom, |
|
924 | IF ((@result := @i) + (@i := @i + :freeSize * 2) + (@freeFrom := NULL), 0, 0), |
||
925 | 0) |
||
926 | 8 | + IF (@depth := [[{$this->depthAttribute}]], 0, 0) |
|
927 | as 'new' |
||
928 | FROM |
||
929 | (SELECT @i := 0, @depth := -1, @freeFrom := :freeFrom, @result := NULL) v, |
||
930 | 8 | (" . $command->sql . ") t |
|
931 | ) tmp |
||
932 | 8 | SET u.[[{$attribute}]]=tmp.[[new]] {$additionalString} |
|
933 | 8 | WHERE tmp.[[{$primaryKey}]]=u.[[{$primaryKey}]]") |
|
934 | 8 | ->bindValues($additionalParams) |
|
935 | 8 | ->bindValues($command->params) |
|
936 | 8 | ->bindValues([ |
|
937 | 8 | ':from' => $from, |
|
938 | 8 | ':size' => $size, |
|
939 | 8 | ':offset' => $offset, |
|
940 | 8 | ':freeFrom' => $freeFrom, |
|
941 | 8 | ':freeSize' => $freeSize, |
|
942 | 8 | ':targetDepth' => $targetDepth, |
|
943 | ]) |
||
944 | 8 | ->execute(); |
|
945 | 8 | if ($freeFrom !== null) { |
|
946 | 6 | $result = $this->owner->getDb()->createCommand("SELECT IFNULL(@result, @i + 1 + IF (@depth - :targetDepth > 0, @depth - :targetDepth, 0))") |
|
947 | 6 | ->bindValue(':targetDepth', $targetDepth) |
|
948 | 6 | ->queryScalar(); |
|
949 | 6 | $result = $result === null ? null : (int)$result; |
|
950 | } |
||
951 | 8 | return $result; |
|
952 | } else { |
||
953 | // generic algorithm (very slow!) |
||
954 | $query |
||
955 | 13 | ->select([$primaryKey, $attribute, $this->depthAttribute]) |
|
956 | 13 | ->asArray() |
|
957 | 13 | ->orderBy([$attribute => $isForward ? SORT_ASC : SORT_DESC]); |
|
958 | |||
959 | 13 | $prevDepth = -1; |
|
960 | 13 | $i = 0; |
|
961 | 13 | foreach ($query->each() as $data) { |
|
962 | 13 | $i++; |
|
963 | 13 | if ($freeFrom !== null && $freeFrom !== (int)$data[$attribute] && ($freeFrom > (int)$data[$attribute] xor $isForward)) { |
|
964 | 7 | $result = $i; |
|
965 | 7 | $depthDiff = $prevDepth - $targetDepth; |
|
966 | 7 | if ($depthDiff > 0) { |
|
967 | $result += $depthDiff; |
||
968 | } |
||
969 | 7 | $i += $freeSize * 2; |
|
970 | 7 | $freeFrom = null; |
|
971 | } |
||
972 | 13 | $depthDiff = $prevDepth - $data[$this->depthAttribute]; |
|
973 | 13 | if ($depthDiff >= 0) { |
|
974 | 13 | $i += $depthDiff + 1; |
|
975 | } |
||
976 | 13 | $this->owner->updateAll( |
|
977 | 13 | array_merge($additional, [$attribute => $isForward ? $from + ($i + $offset) * $size : $from - ($i + $offset) * $size]), |
|
978 | 13 | [$primaryKey => $data[$primaryKey]] |
|
979 | ); |
||
980 | 13 | if ($freeFrom !== null && $freeFrom === (int)$data[$attribute]) { |
|
981 | 6 | $result = $i; |
|
982 | 6 | $i += $freeSize * 2; |
|
983 | 6 | $freeFrom = null; |
|
984 | } |
||
985 | 13 | $prevDepth = $data[$this->depthAttribute]; |
|
986 | } |
||
987 | 13 | if ($freeFrom !== null) { |
|
988 | 3 | $result = $i + 1; |
|
989 | 3 | $depthDiff = $prevDepth - $targetDepth; |
|
990 | 3 | if ($depthDiff > 0) { |
|
991 | 3 | $result += $depthDiff; |
|
992 | } |
||
993 | } |
||
994 | 13 | return $result; |
|
995 | } |
||
996 | } |
||
997 | |||
998 | /** |
||
999 | * @param int $left |
||
1000 | * @param int $right |
||
1001 | */ |
||
1002 | 52 | View Code Duplication | protected function findPrependRange(&$left, &$right) |
1003 | { |
||
1004 | 52 | $left = $this->node->getAttribute($this->leftAttribute); |
|
1005 | 52 | $right = $this->node->getAttribute($this->rightAttribute); |
|
1006 | 52 | $child = $this->getNodeBehavior()->getChildren() |
|
1007 | 52 | ->select($this->leftAttribute) |
|
1008 | 52 | ->orderBy([$this->leftAttribute => SORT_ASC]) |
|
1009 | 52 | ->limit(1) |
|
1010 | 52 | ->scalar(); |
|
1011 | 52 | if ($child !== false) { |
|
1012 | 26 | $right = (int)$child; |
|
1013 | } |
||
1014 | 52 | } |
|
1015 | |||
1016 | /** |
||
1017 | * @param int $left |
||
1018 | * @param int $right |
||
1019 | */ |
||
1020 | 52 | View Code Duplication | protected function findAppendRange(&$left, &$right) |
1021 | { |
||
1022 | 52 | $left = $this->node->getAttribute($this->leftAttribute); |
|
1023 | 52 | $right = $this->node->getAttribute($this->rightAttribute); |
|
1024 | 52 | $child = $this->getNodeBehavior()->getChildren() |
|
1025 | 52 | ->select($this->rightAttribute) |
|
1026 | 52 | ->orderBy([$this->rightAttribute => SORT_DESC]) |
|
1027 | 52 | ->limit(1) |
|
1028 | 52 | ->scalar(); |
|
1029 | 52 | if ($child !== false) { |
|
1030 | 26 | $left = (int)$child; |
|
1031 | } |
||
1032 | 52 | } |
|
1033 | |||
1034 | /** |
||
1035 | * @param int $left |
||
1036 | * @param int $right |
||
1037 | * @return array|null |
||
1038 | */ |
||
1039 | 25 | View Code Duplication | protected function findInsertBeforeRange(&$left, &$right) |
1040 | { |
||
1041 | 25 | $result = null; |
|
1042 | 25 | $right = $this->node->getAttribute($this->leftAttribute); |
|
1043 | 25 | $left = $this->getNodeBehavior()->getPrev() |
|
1044 | 25 | ->select($this->rightAttribute) |
|
1045 | 25 | ->scalar(); |
|
1046 | 25 | if ($left === false) { |
|
1047 | 9 | $result = $this->getNodeBehavior()->getParent() |
|
1048 | 9 | ->select([$this->leftAttribute, $this->rightAttribute]) |
|
1049 | 9 | ->createCommand() |
|
1050 | 9 | ->queryOne(); |
|
1051 | 9 | $left = $result[$this->leftAttribute]; |
|
1052 | } |
||
1053 | 25 | $left = (int)$left; |
|
1054 | 25 | return $result; |
|
1055 | } |
||
1056 | |||
1057 | /** |
||
1058 | * @param int $left |
||
1059 | * @param int $right |
||
1060 | * @return array|null |
||
1061 | */ |
||
1062 | 28 | View Code Duplication | protected function findInsertAfterRange(&$left, &$right) |
1063 | { |
||
1064 | 28 | $result = null; |
|
1065 | 28 | $left = $this->node->getAttribute($this->rightAttribute); |
|
1066 | 28 | $right = $this->getNodeBehavior()->getNext() |
|
1067 | 28 | ->select($this->leftAttribute) |
|
1068 | 28 | ->scalar(); |
|
1069 | 28 | if ($right === false) { |
|
1070 | 15 | $result = $this->getNodeBehavior()->getParent() |
|
1071 | 15 | ->select([$this->leftAttribute, $this->rightAttribute]) |
|
1072 | 15 | ->createCommand() |
|
1073 | 15 | ->queryOne(); |
|
1074 | 15 | $right = $result[$this->rightAttribute]; |
|
1075 | } |
||
1076 | 28 | $right = (int)$right; |
|
1077 | 28 | return $result; |
|
1078 | } |
||
1079 | |||
1080 | /** |
||
1081 | * @param int $value |
||
1082 | * @param string $attribute |
||
1083 | * @param bool $forward |
||
1084 | * @return bool|int |
||
1085 | */ |
||
1086 | 32 | protected function findUnallocated($value, $attribute, $forward = true) |
|
1087 | { |
||
1088 | 32 | $tableName = $this->owner->tableName(); |
|
1089 | 32 | $leftCondition = "l.[[{$this->leftAttribute}]] = {$tableName}.[[{$attribute}]] " . ($forward ? '+' : '-') . " 1"; |
|
1090 | 32 | $rightCondition = "r.[[{$this->rightAttribute}]] = {$tableName}.[[{$attribute}]] " . ($forward ? '+' : '-') . " 1"; |
|
1091 | 32 | if ($this->treeAttribute !== null) { |
|
1092 | 26 | $leftCondition = ['and', $leftCondition, "l.[[{$this->treeAttribute}]] = {$tableName}.[[{$this->treeAttribute}]]"]; |
|
1093 | 26 | $rightCondition = ['and', $rightCondition, "r.[[{$this->treeAttribute}]] = {$tableName}.[[{$this->treeAttribute}]]"]; |
|
1094 | } |
||
1095 | 32 | $result = (new Query()) |
|
1096 | 32 | ->select("{$tableName}.[[{$attribute}]]") |
|
1097 | 32 | ->from("{$tableName}") |
|
1098 | 32 | ->leftJoin("{$tableName} l", $leftCondition) |
|
1099 | 32 | ->leftJoin("{$tableName} r", $rightCondition) |
|
1100 | 32 | ->where([ |
|
1101 | 32 | 'and', |
|
1102 | 32 | [$forward ? '>=' : '<=', "{$tableName}.[[{$attribute}]]", $value], |
|
1103 | 32 | [$forward ? '<' : '>', "{$tableName}.[[{$attribute}]]", $this->range[$forward ? 1 : 0]], |
|
1104 | [ |
||
1105 | 32 | "l.[[{$this->leftAttribute}]]" => null, |
|
1106 | 32 | "r.[[{$this->rightAttribute}]]" => null, |
|
1107 | ], |
||
1108 | ]) |
||
1109 | 32 | ->andWhere($this->treeCondition()) |
|
1110 | 32 | ->orderBy(["{$tableName}.[[{$attribute}]]" => $forward ? SORT_ASC : SORT_DESC]) |
|
1111 | 32 | ->limit(1) |
|
1112 | 32 | ->scalar($this->owner->getDb()); |
|
1113 | 32 | if ($result !== false) { |
|
1114 | 32 | $result += $forward ? 1 : -1; |
|
1115 | } |
||
1116 | 32 | return $result; |
|
1117 | } |
||
1118 | |||
1119 | /** |
||
1120 | * @param int $left |
||
1121 | * @param int $right |
||
1122 | * @return bool|int |
||
1123 | */ |
||
1124 | 32 | protected function findUnallocatedAll($left, $right) |
|
1125 | { |
||
1126 | 32 | $unallocated = false; |
|
1127 | 32 | if ($right < (($this->range[1] - $this->range[0])>>1)) { |
|
1128 | 30 | $unallocated = $unallocated ?: $this->findUnallocated($right, $this->rightAttribute, true); |
|
1129 | 30 | $unallocated = $unallocated ?: $this->findUnallocated($right, $this->leftAttribute, true); |
|
1130 | 30 | $unallocated = $unallocated ?: $this->findUnallocated($left, $this->leftAttribute, false); |
|
1131 | 30 | $unallocated = $unallocated ?: $this->findUnallocated($left, $this->rightAttribute, false); |
|
1132 | } else { |
||
1133 | 5 | $unallocated = $unallocated ?: $this->findUnallocated($left, $this->leftAttribute, false); |
|
1134 | 5 | $unallocated = $unallocated ?: $this->findUnallocated($left, $this->rightAttribute, false); |
|
1135 | 5 | $unallocated = $unallocated ?: $this->findUnallocated($right, $this->rightAttribute, true); |
|
1136 | 5 | $unallocated = $unallocated ?: $this->findUnallocated($right, $this->leftAttribute, true); |
|
1137 | } |
||
1138 | 32 | return $unallocated; |
|
1139 | } |
||
1140 | |||
1141 | /** |
||
1142 | * @param int $from |
||
1143 | * @param int $to |
||
1144 | * @param int $delta |
||
1145 | */ |
||
1146 | 70 | protected function shift($from, $to, $delta) |
|
1147 | { |
||
1148 | 70 | if ($to >= $from && $delta !== 0) { |
|
1149 | 70 | foreach ([$this->leftAttribute, $this->rightAttribute] as $i => $attribute) { |
|
1150 | 70 | $this->owner->updateAll( |
|
1151 | 70 | [$attribute => new Expression("[[{$attribute}]]" . sprintf('%+d', $delta))], |
|
1152 | [ |
||
1153 | 70 | 'and', |
|
1154 | 70 | ['between', $attribute, $from, $to], |
|
1155 | 70 | $this->treeCondition() |
|
1156 | ] |
||
1157 | ); |
||
1158 | } |
||
1159 | } |
||
1160 | 70 | } |
|
1161 | |||
1162 | /** |
||
1163 | * @return array |
||
1164 | */ |
||
1165 | 238 | protected function treeCondition() |
|
1166 | { |
||
1167 | 238 | $tableName = $this->owner->tableName(); |
|
1168 | 238 | if ($this->treeAttribute === null) { |
|
1169 | 153 | return []; |
|
1170 | } else { |
||
1171 | 220 | return ["{$tableName}.[[{$this->treeAttribute}]]" => $this->owner->getAttribute($this->treeAttribute)]; |
|
1172 | } |
||
1173 | } |
||
1174 | } |
||
1175 |
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..