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-sets |
||
4 | * @copyright Copyright (c) 2015 PaulZi <[email protected]> |
||
5 | * @license MIT (https://github.com/paulzi/yii2-nested-sets/blob/master/LICENSE) |
||
6 | */ |
||
7 | |||
8 | namespace paulzi\nestedsets; |
||
9 | |||
10 | use Yii; |
||
11 | use yii\base\Behavior; |
||
12 | use yii\base\Exception; |
||
13 | use yii\base\NotSupportedException; |
||
14 | use yii\db\ActiveRecord; |
||
15 | use yii\db\Expression; |
||
16 | |||
17 | /** |
||
18 | * Nested Sets Behavior for Yii2 |
||
19 | * @author PaulZi <[email protected]> |
||
20 | * @author Alexander Kochetov <https://github.com/creocoder> |
||
21 | * |
||
22 | * @property ActiveRecord $owner |
||
23 | */ |
||
24 | class NestedSetsBehavior extends Behavior |
||
25 | { |
||
26 | const OPERATION_MAKE_ROOT = 1; |
||
27 | const OPERATION_PREPEND_TO = 2; |
||
28 | const OPERATION_APPEND_TO = 3; |
||
29 | const OPERATION_INSERT_BEFORE = 4; |
||
30 | const OPERATION_INSERT_AFTER = 5; |
||
31 | const OPERATION_DELETE_ALL = 6; |
||
32 | |||
33 | |||
34 | /** |
||
35 | * @var string|null |
||
36 | */ |
||
37 | public $treeAttribute; |
||
38 | |||
39 | /** |
||
40 | * @var string |
||
41 | */ |
||
42 | public $leftAttribute = 'lft'; |
||
43 | |||
44 | /** |
||
45 | * @var string |
||
46 | */ |
||
47 | public $rightAttribute = 'rgt'; |
||
48 | |||
49 | /** |
||
50 | * @var string |
||
51 | */ |
||
52 | public $depthAttribute = 'depth'; |
||
53 | |||
54 | /** |
||
55 | * @var string|null |
||
56 | */ |
||
57 | protected $operation; |
||
58 | |||
59 | /** |
||
60 | * @var ActiveRecord|self|null |
||
61 | */ |
||
62 | protected $node; |
||
63 | |||
64 | /** |
||
65 | * @var string |
||
66 | */ |
||
67 | protected $treeChange; |
||
68 | |||
69 | |||
70 | /** |
||
71 | * @inheritdoc |
||
72 | */ |
||
73 | 201 | public function events() |
|
74 | { |
||
75 | return [ |
||
76 | 201 | ActiveRecord::EVENT_BEFORE_INSERT => 'beforeInsert', |
|
77 | ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert', |
||
78 | ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeUpdate', |
||
79 | ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate', |
||
80 | ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete', |
||
81 | ActiveRecord::EVENT_AFTER_DELETE => 'afterDelete', |
||
82 | ]; |
||
83 | } |
||
84 | |||
85 | /** |
||
86 | * @param int|null $depth |
||
87 | * @return \yii\db\ActiveQuery |
||
88 | */ |
||
89 | 6 | public function getParents($depth = null) |
|
90 | { |
||
91 | 6 | $tableName = $this->owner->tableName(); |
|
92 | $condition = [ |
||
93 | 6 | 'and', |
|
94 | 6 | ['<', "{$tableName}.[[{$this->leftAttribute}]]", $this->owner->getAttribute($this->leftAttribute)], |
|
95 | 6 | ['>', "{$tableName}.[[{$this->rightAttribute}]]", $this->owner->getAttribute($this->rightAttribute)], |
|
96 | ]; |
||
97 | 6 | View Code Duplication | if ($depth !== null) { |
98 | 6 | $condition[] = ['>=', "{$tableName}.[[{$this->depthAttribute}]]", $this->owner->getAttribute($this->depthAttribute) - $depth]; |
|
99 | } |
||
100 | |||
101 | 6 | $query = $this->owner->find() |
|
102 | 6 | ->andWhere($condition) |
|
103 | 6 | ->andWhere($this->treeCondition()) |
|
104 | 6 | ->addOrderBy(["{$tableName}.[[{$this->leftAttribute}]]" => SORT_ASC]); |
|
105 | 6 | $query->multiple = true; |
|
106 | |||
107 | 6 | return $query; |
|
108 | } |
||
109 | |||
110 | /** |
||
111 | * @return \yii\db\ActiveQuery |
||
112 | */ |
||
113 | 3 | View Code Duplication | public function getParent() |
114 | { |
||
115 | 3 | $tableName = $this->owner->tableName(); |
|
116 | 3 | $query = $this->getParents(1) |
|
117 | 3 | ->orderBy(["{$tableName}.[[{$this->leftAttribute}]]" => SORT_DESC]) |
|
118 | 3 | ->limit(1); |
|
119 | 3 | $query->multiple = false; |
|
120 | 3 | return $query; |
|
121 | } |
||
122 | |||
123 | /** |
||
124 | * @return \yii\db\ActiveQuery |
||
125 | */ |
||
126 | 3 | View Code Duplication | public function getRoot() |
127 | { |
||
128 | 3 | $tableName = $this->owner->tableName(); |
|
129 | 3 | $query = $this->owner->find() |
|
130 | 3 | ->andWhere(["{$tableName}.[[{$this->leftAttribute}]]" => 1]) |
|
131 | 3 | ->andWhere($this->treeCondition()) |
|
132 | 3 | ->limit(1); |
|
133 | 3 | $query->multiple = false; |
|
134 | 3 | return $query; |
|
135 | } |
||
136 | |||
137 | /** |
||
138 | * @param int|null $depth |
||
139 | * @param bool $andSelf |
||
140 | * @param bool $backOrder |
||
141 | * @return \yii\db\ActiveQuery |
||
142 | */ |
||
143 | 78 | public function getDescendants($depth = null, $andSelf = false, $backOrder = false) |
|
144 | { |
||
145 | 78 | $tableName = $this->owner->tableName(); |
|
146 | 78 | $attribute = $backOrder ? $this->rightAttribute : $this->leftAttribute; |
|
147 | $condition = [ |
||
148 | 78 | 'and', |
|
149 | 78 | [$andSelf ? '>=' : '>', "{$tableName}.[[{$attribute}]]", $this->owner->getAttribute($this->leftAttribute)], |
|
150 | 78 | [$andSelf ? '<=' : '<', "{$tableName}.[[{$attribute}]]", $this->owner->getAttribute($this->rightAttribute)], |
|
151 | ]; |
||
152 | |||
153 | 78 | View Code Duplication | if ($depth !== null) { |
154 | 12 | $condition[] = ['<=', "{$tableName}.[[{$this->depthAttribute}]]", $this->owner->getAttribute($this->depthAttribute) + $depth]; |
|
155 | } |
||
156 | |||
157 | 78 | $query = $this->owner->find() |
|
158 | 78 | ->andWhere($condition) |
|
159 | 78 | ->andWhere($this->treeCondition()) |
|
160 | 78 | ->addOrderBy(["{$tableName}.[[{$attribute}]]" => $backOrder ? SORT_DESC : SORT_ASC]); |
|
161 | 78 | $query->multiple = true; |
|
162 | |||
163 | 78 | return $query; |
|
164 | } |
||
165 | |||
166 | /** |
||
167 | * @return \yii\db\ActiveQuery |
||
168 | */ |
||
169 | 3 | public function getChildren() |
|
170 | { |
||
171 | 3 | return $this->getDescendants(1); |
|
172 | } |
||
173 | |||
174 | /** |
||
175 | * @param int|null $depth |
||
176 | * @return \yii\db\ActiveQuery |
||
177 | */ |
||
178 | 3 | public function getLeaves($depth = null) |
|
179 | { |
||
180 | 3 | $tableName = $this->owner->tableName(); |
|
181 | 3 | $query = $this->getDescendants($depth) |
|
182 | 3 | ->andWhere(["{$tableName}.[[{$this->leftAttribute}]]" => new Expression("{$tableName}.[[{$this->rightAttribute}]] - 1")]); |
|
183 | 3 | $query->multiple = true; |
|
184 | 3 | return $query; |
|
185 | } |
||
186 | |||
187 | /** |
||
188 | * @return \yii\db\ActiveQuery |
||
189 | */ |
||
190 | 3 | View Code Duplication | public function getPrev() |
191 | { |
||
192 | 3 | $tableName = $this->owner->tableName(); |
|
193 | 3 | $query = $this->owner->find() |
|
194 | 3 | ->andWhere(["{$tableName}.[[{$this->rightAttribute}]]" => $this->owner->getAttribute($this->leftAttribute) - 1]) |
|
195 | 3 | ->andWhere($this->treeCondition()) |
|
196 | 3 | ->limit(1); |
|
197 | 3 | $query->multiple = false; |
|
198 | 3 | return $query; |
|
199 | } |
||
200 | |||
201 | /** |
||
202 | * @return \yii\db\ActiveQuery |
||
203 | */ |
||
204 | 3 | View Code Duplication | public function getNext() |
205 | { |
||
206 | 3 | $tableName = $this->owner->tableName(); |
|
207 | 3 | $query = $this->owner->find() |
|
208 | 3 | ->andWhere(["{$tableName}.[[{$this->leftAttribute}]]" => $this->owner->getAttribute($this->rightAttribute) + 1]) |
|
209 | 3 | ->andWhere($this->treeCondition()) |
|
210 | 3 | ->limit(1); |
|
211 | 3 | $query->multiple = false; |
|
212 | 3 | return $query; |
|
213 | } |
||
214 | |||
215 | /** |
||
216 | * Populate children relations for self and all descendants |
||
217 | * @param int $depth = null |
||
218 | * @param string|array $with = null |
||
219 | * @return static |
||
220 | */ |
||
221 | 3 | public function populateTree($depth = null, $with = null) |
|
222 | { |
||
223 | /** @var ActiveRecord[]|static[] $nodes */ |
||
224 | 3 | $query = $this->getDescendants($depth); |
|
225 | 3 | if ($with) { |
|
226 | $query->with($with); |
||
227 | } |
||
228 | 3 | $nodes = $query->all(); |
|
229 | |||
230 | 3 | $key = $this->owner->getAttribute($this->leftAttribute); |
|
231 | 3 | $relates = []; |
|
232 | 3 | $parents = [$key]; |
|
233 | 3 | $prev = $this->owner->getAttribute($this->depthAttribute); |
|
234 | 3 | foreach($nodes as $node) |
|
235 | { |
||
236 | 3 | $level = $node->getAttribute($this->depthAttribute); |
|
237 | 3 | if ($level <= $prev) { |
|
238 | 3 | $parents = array_slice($parents, 0, $level - $prev - 1); |
|
239 | } |
||
240 | |||
241 | 3 | $key = end($parents); |
|
242 | 3 | if (!isset($relates[$key])) { |
|
243 | 3 | $relates[$key] = []; |
|
244 | } |
||
245 | 3 | $relates[$key][] = $node; |
|
246 | |||
247 | 3 | $parents[] = $node->getAttribute($this->leftAttribute); |
|
248 | 3 | $prev = $level; |
|
249 | } |
||
250 | |||
251 | 3 | $ownerDepth = $this->owner->getAttribute($this->depthAttribute); |
|
252 | 3 | $nodes[] = $this->owner; |
|
253 | 3 | foreach ($nodes as $node) { |
|
254 | 3 | $key = $node->getAttribute($this->leftAttribute); |
|
255 | 3 | if (isset($relates[$key])) { |
|
256 | 3 | $node->populateRelation('children', $relates[$key]); |
|
257 | 3 | } elseif ($depth === null || $ownerDepth + $depth > $node->getAttribute($this->depthAttribute)) { |
|
258 | 3 | $node->populateRelation('children', []); |
|
259 | } |
||
260 | } |
||
261 | |||
262 | 3 | return $this->owner; |
|
263 | } |
||
264 | |||
265 | /** |
||
266 | * @return bool |
||
267 | */ |
||
268 | 72 | public function isRoot() |
|
269 | { |
||
270 | 72 | return $this->owner->getAttribute($this->leftAttribute) === 1; |
|
271 | } |
||
272 | |||
273 | /** |
||
274 | * @param ActiveRecord $node |
||
275 | * @return bool |
||
276 | */ |
||
277 | 69 | public function isChildOf($node) |
|
278 | { |
||
279 | 69 | $result = $this->owner->getAttribute($this->leftAttribute) > $node->getAttribute($this->leftAttribute) |
|
280 | 69 | && $this->owner->getAttribute($this->rightAttribute) < $node->getAttribute($this->rightAttribute); |
|
281 | |||
282 | 69 | View Code Duplication | if ($result && $this->treeAttribute !== null) { |
283 | 6 | $result = $this->owner->getAttribute($this->treeAttribute) === $node->getAttribute($this->treeAttribute); |
|
284 | } |
||
285 | |||
286 | 69 | return $result; |
|
287 | } |
||
288 | |||
289 | /** |
||
290 | * @return bool |
||
291 | */ |
||
292 | 6 | public function isLeaf() |
|
293 | { |
||
294 | 6 | return $this->owner->getAttribute($this->rightAttribute) - $this->owner->getAttribute($this->leftAttribute) === 1; |
|
295 | } |
||
296 | |||
297 | /** |
||
298 | * @return ActiveRecord |
||
299 | */ |
||
300 | 12 | public function makeRoot() |
|
301 | { |
||
302 | 12 | $this->operation = self::OPERATION_MAKE_ROOT; |
|
303 | 12 | return $this->owner; |
|
304 | } |
||
305 | |||
306 | /** |
||
307 | * @param ActiveRecord $node |
||
308 | * @return ActiveRecord |
||
309 | */ |
||
310 | 33 | public function prependTo($node) |
|
311 | { |
||
312 | 33 | $this->operation = self::OPERATION_PREPEND_TO; |
|
313 | 33 | $this->node = $node; |
|
314 | 33 | return $this->owner; |
|
315 | } |
||
316 | |||
317 | /** |
||
318 | * @param ActiveRecord $node |
||
319 | * @return ActiveRecord |
||
320 | */ |
||
321 | 33 | public function appendTo($node) |
|
322 | { |
||
323 | 33 | $this->operation = self::OPERATION_APPEND_TO; |
|
324 | 33 | $this->node = $node; |
|
325 | 33 | return $this->owner; |
|
326 | } |
||
327 | |||
328 | /** |
||
329 | * @param ActiveRecord $node |
||
330 | * @return ActiveRecord |
||
331 | */ |
||
332 | 27 | public function insertBefore($node) |
|
333 | { |
||
334 | 27 | $this->operation = self::OPERATION_INSERT_BEFORE; |
|
335 | 27 | $this->node = $node; |
|
336 | 27 | return $this->owner; |
|
337 | } |
||
338 | |||
339 | /** |
||
340 | * @param ActiveRecord $node |
||
341 | * @return ActiveRecord |
||
342 | */ |
||
343 | 30 | public function insertAfter($node) |
|
344 | { |
||
345 | 30 | $this->operation = self::OPERATION_INSERT_AFTER; |
|
346 | 30 | $this->node = $node; |
|
347 | 30 | return $this->owner; |
|
348 | } |
||
349 | |||
350 | /** |
||
351 | * Need for paulzi/auto-tree |
||
352 | */ |
||
353 | public function preDeleteWithChildren() |
||
354 | { |
||
355 | $this->operation = self::OPERATION_DELETE_ALL; |
||
356 | } |
||
357 | |||
358 | /** |
||
359 | * @return bool|int |
||
360 | * @throws \Exception |
||
361 | * @throws \yii\db\Exception |
||
362 | */ |
||
363 | 9 | public function deleteWithChildren() |
|
364 | { |
||
365 | 9 | $this->operation = self::OPERATION_DELETE_ALL; |
|
366 | 9 | if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) { |
|
367 | $transaction = $this->owner->getDb()->beginTransaction(); |
||
368 | try { |
||
369 | $result = $this->deleteWithChildrenInternal(); |
||
370 | if ($result === false) { |
||
371 | $transaction->rollBack(); |
||
372 | } else { |
||
373 | $transaction->commit(); |
||
374 | } |
||
375 | return $result; |
||
376 | } catch (\Exception $e) { |
||
377 | $transaction->rollBack(); |
||
378 | throw $e; |
||
379 | } |
||
380 | } else { |
||
381 | 9 | $result = $this->deleteWithChildrenInternal(); |
|
382 | } |
||
383 | 6 | return $result; |
|
384 | } |
||
385 | |||
386 | /** |
||
387 | * @throws Exception |
||
388 | * @throws NotSupportedException |
||
389 | */ |
||
390 | 48 | public function beforeInsert() |
|
391 | { |
||
392 | 48 | if ($this->node !== null && !$this->node->getIsNewRecord()) { |
|
393 | 27 | $this->node->refresh(); |
|
394 | } |
||
395 | 48 | switch ($this->operation) { |
|
396 | 48 | case self::OPERATION_MAKE_ROOT: |
|
397 | 6 | $condition = array_merge([$this->leftAttribute => 1], $this->treeCondition()); |
|
398 | 6 | if ($this->owner->find()->andWhere($condition)->one() !== null) { |
|
399 | 3 | throw new Exception('Can not create more than one root.'); |
|
400 | } |
||
401 | 3 | $this->owner->setAttribute($this->leftAttribute, 1); |
|
402 | 3 | $this->owner->setAttribute($this->rightAttribute, 2); |
|
403 | 3 | $this->owner->setAttribute($this->depthAttribute, 0); |
|
404 | 3 | break; |
|
405 | |||
406 | 42 | case self::OPERATION_PREPEND_TO: |
|
407 | 9 | $this->insertNode($this->node->getAttribute($this->leftAttribute) + 1, 1); |
|
408 | 6 | break; |
|
409 | |||
410 | 33 | case self::OPERATION_APPEND_TO: |
|
411 | 9 | $this->insertNode($this->node->getAttribute($this->rightAttribute), 1); |
|
412 | 6 | break; |
|
413 | |||
414 | 24 | case self::OPERATION_INSERT_BEFORE: |
|
415 | 9 | $this->insertNode($this->node->getAttribute($this->leftAttribute), 0); |
|
416 | 6 | break; |
|
417 | |||
418 | 15 | case self::OPERATION_INSERT_AFTER: |
|
419 | 12 | $this->insertNode($this->node->getAttribute($this->rightAttribute) + 1, 0); |
|
420 | 6 | break; |
|
421 | |||
422 | default: |
||
423 | 3 | throw new NotSupportedException('Method "'. $this->owner->className() . '::insert" is not supported for inserting new nodes.'); |
|
424 | } |
||
425 | 27 | } |
|
426 | |||
427 | /** |
||
428 | * @throws Exception |
||
429 | */ |
||
430 | 27 | public function afterInsert() |
|
431 | { |
||
432 | 27 | if ($this->operation === self::OPERATION_MAKE_ROOT && $this->treeAttribute !== null && $this->owner->getAttribute($this->treeAttribute) === null) { |
|
433 | 3 | $id = $this->owner->getPrimaryKey(); |
|
434 | 3 | $this->owner->setAttribute($this->treeAttribute, $id); |
|
435 | |||
436 | 3 | $primaryKey = $this->owner->primaryKey(); |
|
437 | 3 | if (!isset($primaryKey[0])) { |
|
438 | throw new Exception('"' . $this->owner->className() . '" must have a primary key.'); |
||
439 | } |
||
440 | |||
441 | 3 | $this->owner->updateAll([$this->treeAttribute => $id], [$primaryKey[0] => $id]); |
|
442 | } |
||
443 | 27 | $this->operation = null; |
|
444 | 27 | $this->node = null; |
|
445 | 27 | } |
|
446 | |||
447 | /** |
||
448 | * @throws Exception |
||
449 | */ |
||
450 | 93 | public function beforeUpdate() |
|
451 | { |
||
452 | 93 | if ($this->node !== null && !$this->node->getIsNewRecord()) { |
|
453 | 78 | $this->node->refresh(); |
|
454 | } |
||
455 | |||
456 | 93 | switch ($this->operation) { |
|
457 | 93 | case self::OPERATION_MAKE_ROOT: |
|
458 | 6 | if ($this->treeAttribute === null) { |
|
459 | throw new Exception('Can not move a node as the root when "treeAttribute" is not set.'); |
||
460 | } |
||
461 | 6 | if ($this->owner->getOldAttribute($this->treeAttribute) !== $this->owner->getAttribute($this->treeAttribute)) { |
|
462 | 3 | $this->treeChange = $this->owner->getAttribute($this->treeAttribute); |
|
463 | 3 | $this->owner->setAttribute($this->treeAttribute, $this->owner->getOldAttribute($this->treeAttribute)); |
|
464 | } |
||
465 | 6 | break; |
|
466 | |||
467 | 87 | case self::OPERATION_INSERT_BEFORE: |
|
468 | 69 | case self::OPERATION_INSERT_AFTER: |
|
0 ignored issues
–
show
Coding Style
introduced
by
![]() |
|||
469 | 36 | if ($this->node->isRoot()) { |
|
470 | throw new Exception('Can not move a node before/after root.'); |
||
471 | } |
||
472 | |||
473 | 51 | case self::OPERATION_PREPEND_TO: |
|
474 | 27 | case self::OPERATION_APPEND_TO: |
|
475 | 84 | if ($this->node->getIsNewRecord()) { |
|
476 | 6 | throw new Exception('Can not move a node when the target node is new record.'); |
|
477 | } |
||
478 | |||
479 | 78 | if ($this->owner->equals($this->node)) { |
|
480 | 12 | throw new Exception('Can not move a node when the target node is same.'); |
|
481 | } |
||
482 | |||
483 | 66 | if ($this->node->isChildOf($this->owner)) { |
|
484 | 12 | throw new Exception('Can not move a node when the target node is child.'); |
|
485 | } |
||
486 | } |
||
487 | 63 | } |
|
488 | |||
489 | /** |
||
490 | * |
||
491 | */ |
||
492 | 63 | public function afterUpdate() |
|
493 | { |
||
494 | 63 | switch ($this->operation) { |
|
495 | 63 | case self::OPERATION_MAKE_ROOT: |
|
496 | 6 | if ($this->treeChange || !$this->isRoot() || $this->owner->getIsNewRecord()) { |
|
497 | 3 | $this->moveNodeAsRoot(); |
|
498 | } |
||
499 | 6 | break; |
|
500 | |||
501 | 57 | case self::OPERATION_PREPEND_TO: |
|
502 | 15 | $this->moveNode($this->node->getAttribute($this->leftAttribute) + 1, 1); |
|
503 | 15 | break; |
|
504 | |||
505 | 42 | case self::OPERATION_APPEND_TO: |
|
506 | 15 | $this->moveNode($this->node->getAttribute($this->rightAttribute), 1); |
|
507 | 15 | break; |
|
508 | |||
509 | 27 | case self::OPERATION_INSERT_BEFORE: |
|
510 | 12 | $this->moveNode($this->node->getAttribute($this->leftAttribute), 0); |
|
511 | 12 | break; |
|
512 | |||
513 | 15 | case self::OPERATION_INSERT_AFTER: |
|
514 | 12 | $this->moveNode($this->node->getAttribute($this->rightAttribute) + 1, 0); |
|
515 | 12 | break; |
|
516 | } |
||
517 | 63 | $this->operation = null; |
|
518 | 63 | $this->node = null; |
|
519 | 63 | $this->treeChange = null; |
|
520 | 63 | } |
|
521 | |||
522 | /** |
||
523 | * @throws Exception |
||
524 | */ |
||
525 | 18 | public function beforeDelete() |
|
526 | { |
||
527 | 18 | if ($this->owner->getIsNewRecord()) { |
|
528 | 6 | throw new Exception('Can not delete a node when it is new record.'); |
|
529 | } |
||
530 | 12 | if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) { |
|
531 | 3 | throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.'); |
|
532 | } |
||
533 | 9 | $this->owner->refresh(); |
|
534 | 9 | } |
|
535 | |||
536 | /** |
||
537 | * |
||
538 | */ |
||
539 | 9 | public function afterDelete() |
|
540 | { |
||
541 | 9 | $left = $this->owner->getAttribute($this->leftAttribute); |
|
542 | 9 | $right = $this->owner->getAttribute($this->rightAttribute); |
|
543 | 9 | if ($this->operation === static::OPERATION_DELETE_ALL || $this->isLeaf()) { |
|
544 | 6 | $this->shift($right + 1, null, $left - $right - 1); |
|
545 | } else { |
||
546 | 3 | $this->owner->updateAll( |
|
547 | [ |
||
548 | 3 | $this->leftAttribute => new Expression("[[{$this->leftAttribute}]] - 1"), |
|
549 | 3 | $this->rightAttribute => new Expression("[[{$this->rightAttribute}]] - 1"), |
|
550 | 3 | $this->depthAttribute => new Expression("[[{$this->depthAttribute}]] - 1"), |
|
551 | ], |
||
552 | 3 | $this->getDescendants()->where |
|
553 | ); |
||
554 | 3 | $this->shift($right + 1, null, -2); |
|
555 | } |
||
556 | 9 | $this->operation = null; |
|
557 | 9 | $this->node = null; |
|
558 | 9 | } |
|
559 | |||
560 | /** |
||
561 | * @return int |
||
562 | */ |
||
563 | 9 | protected function deleteWithChildrenInternal() |
|
564 | { |
||
565 | 9 | if (!$this->owner->beforeDelete()) { |
|
566 | return false; |
||
567 | } |
||
568 | 6 | $result = $this->owner->deleteAll($this->getDescendants(null, true)->where); |
|
569 | 6 | $this->owner->setOldAttributes(null); |
|
570 | 6 | $this->owner->afterDelete(); |
|
571 | 6 | return $result; |
|
572 | } |
||
573 | |||
574 | /** |
||
575 | * @param int $to |
||
576 | * @param int $depth |
||
577 | * @throws Exception |
||
578 | */ |
||
579 | 39 | protected function insertNode($to, $depth = 0) |
|
580 | { |
||
581 | 39 | if ($this->node->getIsNewRecord()) { |
|
582 | 12 | throw new Exception('Can not create a node when the target node is new record.'); |
|
583 | } |
||
584 | |||
585 | 27 | if ($depth === 0 && $this->node->isRoot()) { |
|
586 | 3 | throw new Exception('Can not insert a node before/after root.'); |
|
587 | } |
||
588 | 24 | $this->owner->setAttribute($this->leftAttribute, $to); |
|
589 | 24 | $this->owner->setAttribute($this->rightAttribute, $to + 1); |
|
590 | 24 | $this->owner->setAttribute($this->depthAttribute, $this->node->getAttribute($this->depthAttribute) + $depth); |
|
591 | 24 | if ($this->treeAttribute !== null) { |
|
592 | 24 | $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute)); |
|
593 | } |
||
594 | 24 | $this->shift($to, null, 2); |
|
595 | 24 | } |
|
596 | |||
597 | /** |
||
598 | * @param int $to |
||
599 | * @param int $depth |
||
600 | * @throws Exception |
||
601 | */ |
||
602 | 54 | protected function moveNode($to, $depth = 0) |
|
603 | { |
||
604 | 54 | $left = $this->owner->getAttribute($this->leftAttribute); |
|
605 | 54 | $right = $this->owner->getAttribute($this->rightAttribute); |
|
606 | 54 | $depth = $this->owner->getAttribute($this->depthAttribute) - $this->node->getAttribute($this->depthAttribute) - $depth; |
|
607 | 54 | if ($this->treeAttribute === null || $this->owner->getAttribute($this->treeAttribute) === $this->node->getAttribute($this->treeAttribute)) { |
|
608 | // same root |
||
609 | 42 | $this->owner->updateAll( |
|
610 | 42 | [$this->depthAttribute => new Expression("-[[{$this->depthAttribute}]]" . sprintf('%+d', $depth))], |
|
611 | 42 | $this->getDescendants(null, true)->where |
|
612 | ); |
||
613 | 42 | $delta = $right - $left + 1; |
|
614 | 42 | if ($left >= $to) { |
|
615 | 24 | $this->shift($to, $left - 1, $delta); |
|
616 | 24 | $delta = $to - $left; |
|
617 | } else { |
||
618 | 18 | $this->shift($right + 1, $to - 1, -$delta); |
|
619 | 18 | $delta = $to - $right - 1; |
|
620 | } |
||
621 | 42 | $this->owner->updateAll( |
|
622 | [ |
||
623 | 42 | $this->leftAttribute => new Expression("[[{$this->leftAttribute}]]" . sprintf('%+d', $delta)), |
|
624 | 42 | $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', $delta)), |
|
625 | 42 | $this->depthAttribute => new Expression("-[[{$this->depthAttribute}]]"), |
|
626 | ], |
||
627 | [ |
||
628 | 42 | 'and', |
|
629 | 42 | $this->getDescendants(null, true)->where, |
|
630 | 42 | ['<', $this->depthAttribute, 0], |
|
631 | ] |
||
632 | ); |
||
633 | } else { |
||
634 | // move from other root |
||
635 | 12 | $tree = $this->node->getAttribute($this->treeAttribute); |
|
636 | 12 | $this->shift($to, null, $right - $left + 1, $tree); |
|
637 | 12 | $delta = $to - $left; |
|
638 | 12 | $this->owner->updateAll( |
|
639 | [ |
||
640 | 12 | $this->leftAttribute => new Expression("[[{$this->leftAttribute}]]" . sprintf('%+d', $delta)), |
|
641 | 12 | $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', $delta)), |
|
642 | 12 | $this->depthAttribute => new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', -$depth)), |
|
643 | 12 | $this->treeAttribute => $tree, |
|
644 | ], |
||
645 | 12 | $this->getDescendants(null, true)->where |
|
646 | ); |
||
647 | 12 | $this->shift($right + 1, null, $left - $right - 1); |
|
648 | } |
||
649 | 54 | } |
|
650 | |||
651 | /** |
||
652 | * |
||
653 | */ |
||
654 | 3 | protected function moveNodeAsRoot() |
|
655 | { |
||
656 | 3 | $left = $this->owner->getAttribute($this->leftAttribute); |
|
657 | 3 | $right = $this->owner->getAttribute($this->rightAttribute); |
|
658 | 3 | $depth = $this->owner->getAttribute($this->depthAttribute); |
|
659 | 3 | $tree = $this->treeChange ? $this->treeChange : $this->owner->getPrimaryKey(); |
|
660 | |||
661 | 3 | $this->owner->updateAll( |
|
662 | [ |
||
663 | 3 | $this->leftAttribute => new Expression("[[{$this->leftAttribute}]]" . sprintf('%+d', 1 - $left)), |
|
664 | 3 | $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', 1 - $left)), |
|
665 | 3 | $this->depthAttribute => new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', -$depth)), |
|
666 | 3 | $this->treeAttribute => $tree, |
|
667 | ], |
||
668 | 3 | $this->getDescendants(null, true)->where |
|
669 | ); |
||
670 | 3 | $this->shift($right + 1, null, $left - $right - 1); |
|
671 | 3 | } |
|
672 | |||
673 | |||
674 | |||
675 | /** |
||
676 | * @param int $from |
||
677 | * @param int $to |
||
678 | * @param int $delta |
||
679 | * @param int|null $tree |
||
680 | */ |
||
681 | 90 | protected function shift($from, $to, $delta, $tree = null) |
|
682 | { |
||
683 | 90 | if ($delta !== 0 && ($to === null || $to >= $from)) { |
|
684 | 78 | View Code Duplication | if ($this->treeAttribute !== null && $tree === null) { |
685 | 78 | $tree = $this->owner->getAttribute($this->treeAttribute); |
|
686 | } |
||
687 | 78 | foreach ([$this->leftAttribute, $this->rightAttribute] as $i => $attribute) { |
|
688 | 78 | $this->owner->updateAll( |
|
689 | 78 | [$attribute => new Expression("[[{$attribute}]]" . sprintf('%+d', $delta))], |
|
690 | [ |
||
691 | 78 | 'and', |
|
692 | 78 | $to === null ? ['>=', $attribute, $from] : ['between', $attribute, $from, $to], |
|
693 | 78 | $this->treeAttribute !== null ? [$this->treeAttribute => $tree] : [], |
|
694 | ] |
||
695 | ); |
||
696 | } |
||
697 | } |
||
698 | 90 | } |
|
699 | |||
700 | /** |
||
701 | * @return array |
||
702 | */ |
||
703 | 99 | protected function treeCondition() |
|
704 | { |
||
705 | 99 | $tableName = $this->owner->tableName(); |
|
706 | 99 | if ($this->treeAttribute === null) { |
|
707 | 84 | return []; |
|
708 | } else { |
||
709 | 96 | return ["{$tableName}.[[{$this->treeAttribute}]]" => $this->owner->getAttribute($this->treeAttribute)]; |
|
710 | } |
||
711 | } |
||
712 | } |
||
713 |