Completed
Branch master (fd1517)
by
unknown
55:33
created

NestedSetBehavior::checkAndSetPath()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 9
rs 9.2
cc 4
eloc 5
nc 3
nop 4
1
<?php
2
3
/**
4
 * This file is part of the fangface/yii2-concord package
5
 * For the full copyright and license information, please view
6
 * the file LICENSE.md that was distributed with this source code.
7
 *
8
 * @package fangface/yii2-concord
9
 * @author Fangface <[email protected]>
10
 * @copyright Copyright (c) 2014 Fangface <[email protected]>
11
 * @license https://github.com/fangface/yii2-concord/blob/master/LICENSE.md MIT License
12
 */
13
14
/**
15
 * Based on;
16
 *
17
 * @link https://github.com/creocoder/yii2-nested-set-behavior
18
 * @copyright Copyright (c) 2013 Alexander Kochetov
19
 * @license http://opensource.org/licenses/BSD-3-Clause
20
 */
21
namespace fangface\behaviors;
22
23
use fangface\Tools;
24
use fangface\db\ActiveRecord;
25
use yii\base\Behavior;
26
use yii\base\ModelEvent;
27
use yii\db\ActiveQuery;
28
use yii\db\Exception;
29
use yii\db\Expression;
30
31
/**
32
 * Nested Set behavior for attaching to ActiveRecord
33
 * CREATE TABLE `example_nest` (
34
 * `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
35
 * `name` char(100) NOT NULL DEFAULT '',
36
 * `path` varchar(255) NOT NULL DEFAULT '',
37
 * `lft` bigint(20) unsigned NOT NULL DEFAULT '0',
38
 * `rgt` bigint(20) unsigned NOT NULL DEFAULT '0',
39
 * `level` smallint(5) unsigned NOT NULL DEFAULT '0',
40
 * `created_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
41
 * `created_by` bigint(20) unsigned NOT NULL DEFAULT '0',
42
 * `modified_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
43
 * `modified_by` bigint(20) unsigned NOT NULL DEFAULT '0',
44
 * PRIMARY KEY (`id`),
45
 * KEY `lft` (`lft`),
46
 * KEY `rgt` (`rgt`),
47
 * KEY `level` (`level`,`lft`) USING BTREE
48
 * ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
49
 *
50
 * @author Alexander Kochetov <[email protected]>
51
 * @author Fangface <[email protected]>
52
 */
53
class NestedSetBehavior extends Behavior
54
{
55
56
    /**
57
     *
58
     * @var ActiveRecord the owner of this behavior.
59
     */
60
    public $owner;
61
62
    /**
63
     *
64
     * @var boolean
65
     */
66
    public $hasManyRoots = false;
67
68
    /**
69
     *
70
     * @var boolean
71
     */
72
    public $hasPaths = false;
73
74
    /**
75
     *
76
     * @var boolean
77
     */
78
    public $hasAction = false;
79
80
    /**
81
     *
82
     * @var boolean should deletes be performed on each active record individually
83
     */
84
    public $deleteIndividual = false;
85
86
    /**
87
     *
88
     * @var string
89
     */
90
    public $rootAttribute = 'root';
91
92
    /**
93
     *
94
     * @var string
95
     */
96
    public $leftAttribute = 'lft';
97
98
    /**
99
     *
100
     * @var string
101
     */
102
    public $rightAttribute = 'rgt';
103
104
    /**
105
     *
106
     * @var string
107
     */
108
    public $levelAttribute = 'level';
109
110
    /**
111
     *
112
     * @var string
113
     */
114
    public $nameAttribute = 'name';
115
116
    /**
117
     *
118
     * @var string
119
     */
120
    public $pathAttribute = 'path';
121
122
    /**
123
     *
124
     * @var boolean
125
     */
126
    private $_ignoreEvent = false;
127
128
    /**
129
     *
130
     * @var boolean
131
     */
132
    private $_deleted = false;
133
134
    /**
135
     *
136
     * @var string
137
     */
138
    private $_previousPath = '';
139
140
    /**
141
     *
142
     * @var integer
143
     */
144
    private $_id;
145
146
    /**
147
     *
148
     * @var array
149
     */
150
    private static $_cached;
151
152
    /**
153
     *
154
     * @var integer
155
     */
156
    private static $_c = 0;
157
158
159
    /**
160
     * @inheritdoc
161
     */
162
    public function events()
163
    {
164
        return [
165
            ActiveRecord::EVENT_AFTER_FIND => 'afterFind',
166
            ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
167
            ActiveRecord::EVENT_BEFORE_INSERT => 'beforeInsert',
168
            ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeUpdate',
169
            ActiveRecord::EVENT_BEFORE_SAVE_ALL => 'beforeSaveAll',
170
            ActiveRecord::EVENT_BEFORE_DELETE_FULL => 'beforeDeleteFull'
171
        ];
172
    }
173
174
175
    /**
176
     * @inheritdoc
177
     */
178
    public function attach($owner)
179
    {
180
        parent::attach($owner);
181
        self::$_cached[get_class($this->owner)][$this->_id = self::$_c++] = $this->owner;
182
    }
183
184
185
    /**
186
     * Gets descendants for node
187
     *
188
     * @param integer $depth
189
     *        the depth
190
     * @param ActiveRecord $object
191
     *        [optional] defaults to $this->owner
192
     * @param integer $limit
193
     *        [optional] limit results (typically used when only after limited number of immediate children)
194
     * @return ActiveQuery|integer
195
     */
196
    public function descendants($depth = null, $object = null, $limit = 0)
197
    {
198
        $object = (!is_null($object) ? $object : $this->owner);
199
        $query = $object->find()->orderBy([
200
            $this->levelAttribute => SORT_ASC,
201
            $this->leftAttribute => SORT_ASC
202
        ]);
203
        $db = $object->getDb();
204
        $query->andWhere($db->quoteColumnName($this->leftAttribute) . '>' . $object->getAttribute($this->leftAttribute));
205
        $query->andWhere($db->quoteColumnName($this->rightAttribute) . '<' . $object->getAttribute($this->rightAttribute));
206
        $query->addOrderBy($db->quoteColumnName($this->leftAttribute));
207
208
        if ($depth !== null) {
209
            $query->andWhere($db->quoteColumnName($this->levelAttribute) . '<=' . ($object->getAttribute($this->levelAttribute) + $depth));
210
        }
211
212
        if ($this->hasManyRoots) {
213
            $query->andWhere($db->quoteColumnName($this->rootAttribute) . '=:' . $this->rootAttribute, [
214
                ':' . $this->rootAttribute => $object->getAttribute($this->rootAttribute)
215
            ]);
216
        }
217
218
        if ($limit) {
219
            $query->limit($limit);
220
        }
221
222
        return $query;
223
    }
224
225
226
    /**
227
     * Gets children for node (direct descendants only)
228
     *
229
     * @param ActiveRecord $object
230
     *        [optional] defaults to $this->owner
231
     * @param integer $limit
232
     *        [optional] limit results (typically used when only after limited number of immediate children)
233
     * @return ActiveQuery|integer
234
     */
235
    public function children($object = null, $limit = 0)
236
    {
237
        return $this->descendants(1, $object, $limit);
238
    }
239
240
241
    /**
242
     * Gets one child for node (first direct descendant only).
243
     *
244
     * @param ActiveRecord $object
245
     *        [optional] defaults to $this->owner
246
     * @return ActiveQuery
247
     */
248
    public function oneChild($object = null)
249
    {
250
        return $this->children($object, 1);
251
    }
252
253
254
    /**
255
     * Gets ancestors for node
256
     *
257
     * @param integer $depth
258
     *        the depth
259
     * @param ActiveRecord $object
260
     *        [optional] defaults to $this->owner
261
     * @param boolean $reverse
262
     *        Should the result be in reverse order i.e. root first
263
     * @param boolean $idOnly
264
     *        Should an array of IDs be returned only
265
     * @return ActiveQuery
266
     */
267
    public function ancestors($depth = null, $object = null, $reverse = false, $idOnly = false)
268
    {
269
        $object = (!is_null($object) ? $object : $this->owner);
270
        $query = $object->find();
271
272
        if ($idOnly) {
273
            $query->select($object->primaryKey());
274
        }
275
276
        if ($reverse) {
277
            $query->orderBy([
278
                $this->levelAttribute => SORT_ASC,
279
                $this->leftAttribute => SORT_ASC
280
            ]);
281
            ;
282
        } else {
283
            $query->orderBy([
284
                $this->levelAttribute => SORT_DESC,
285
                $this->leftAttribute => SORT_ASC
286
            ]);
287
            ;
288
        }
289
290
        $db = $object->getDb();
291
292
        $query->andWhere($db->quoteColumnName($this->leftAttribute) . '<' . $object->getAttribute($this->leftAttribute));
293
        $query->andWhere($db->quoteColumnName($this->rightAttribute) . '>' . $object->getAttribute($this->rightAttribute));
294
        $query->addOrderBy($db->quoteColumnName($this->leftAttribute));
295
296
        if ($depth !== null) {
297
            $query->andWhere($db->quoteColumnName($this->levelAttribute) . '>=' . ($object->getAttribute($this->levelAttribute) - $depth));
298
        }
299
300
        if ($this->hasManyRoots) {
301
            $query->andWhere($db->quoteColumnName($this->rootAttribute) . '=:' . $this->rootAttribute, [
302
                ':' . $this->rootAttribute => $object->getAttribute($this->rootAttribute)
303
            ]);
304
        }
305
306
        return $query;
307
    }
308
309
310
    /**
311
     * Gets parent of node
312
     *
313
     * @param ActiveRecord $object
314
     *        [optional] defaults to $this->owner
315
     * @param boolean $idOnly
316
     *        Should only the id be returned
317
     * @return ActiveQuery
318
     */
319
    public function parentOnly($object = null, $idOnly = false)
320
    {
321
        return $this->ancestors(1, $object, false, $idOnly);
322
    }
323
324
325
    /**
326
     * Gets entries at the same level of node (including self)
327
     *
328
     * @param ActiveRecord $object
329
     *        [optional] defaults to $this->owner
330
     * @param integer $limit
331
     *        [optional] limit results (typically used when only after limited number of immediate children)
332
     * @return ActiveQuery|integer
333
     */
334
    public function level($object = null, $limit = 0)
335
    {
336
        $parent = $this->parentOnly($object)->one();
337
        return $this->children($parent, $limit);
0 ignored issues
show
Bug introduced by
It seems like $parent defined by $this->parentOnly($object)->one() on line 336 can also be of type array or object<yii\db\ActiveRecord>; however, fangface\behaviors\NestedSetBehavior::children() does only seem to accept object<fangface\db\ActiveRecord>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
338
    }
339
340
341
    /**
342
     * Gets a count of entries at the same level of node (including self)
343
     *
344
     * @param ActiveRecord $object
345
     *        [optional] defaults to $this->owner
346
     * @return integer
347
     */
348
    public function levelCount($object = null)
349
    {
350
        return $this->level($object, true)->count();
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
351
    }
352
353
354
    /**
355
     * Gets previous sibling of node
356
     *
357
     * @param ActiveRecord $object
358
     *        [optional] defaults to $this->owner
359
     * @return ActiveQuery
360
     */
361
    public function prev($object = null)
362
    {
363
        $object = (!is_null($object) ? $object : $this->owner);
364
        $query = $object->find();
365
        $db = $object->getDb();
366
        $query->andWhere($db->quoteColumnName($this->rightAttribute) . '=' . ($object->getAttribute($this->leftAttribute) - 1));
367
368
        if ($this->hasManyRoots) {
369
            $query->andWhere($db->quoteColumnName($this->rootAttribute) . '=:' . $this->rootAttribute, [
370
                ':' . $this->rootAttribute => $object->getAttribute($this->rootAttribute)
371
            ]);
372
        }
373
374
        return $query;
375
    }
376
377
378
    /**
379
     * Gets next sibling of node
380
     *
381
     * @param ActiveRecord $object
382
     *        [optional] defaults to $this->owner
383
     * @return ActiveQuery
384
     */
385
    public function next($object = null)
386
    {
387
        $object = (!is_null($object) ? $object : $this->owner);
388
        $query = $object->find();
389
        $db = $object->getDb();
390
        $query->andWhere($db->quoteColumnName($this->leftAttribute) . '=' . ($object->getAttribute($this->rightAttribute) + 1));
391
392
        if ($this->hasManyRoots) {
393
            $query->andWhere($db->quoteColumnName($this->rootAttribute) . '=:' . $this->rootAttribute, [
394
                ':' . $this->rootAttribute => $object->getAttribute($this->rootAttribute)
395
            ]);
396
        }
397
398
        return $query;
399
    }
400
401
402
    /**
403
     * Create root node if multiple-root tree mode.
404
     * Update node if it's not new
405
     *
406
     * @param boolean $runValidation
407
     *        should validations be executed on all models before allowing save()
408
     * @param array $attributes
409
     *        which attributes should be saved (default null means all changed attributes)
410
     * @param boolean $hasParentModel
411
     *        whether this method was called from the top level or by a parent
412
     *        If false, it means the method was called at the top level
413
     * @param boolean $fromSaveAll
414
     *        has the save() call come from saveAll() or not
415
     * @return boolean did save() successfully process
416
     */
417
    private function save($runValidation = true, $attributes = null, $hasParentModel = false, $fromSaveAll = false)
418
    {
419
        if ($this->owner->getReadOnly() && !$hasParentModel) {
420
421
            // return failure if we are at the top of the tree and should not be asking to saveAll
422
            // not allowed to amend or delete
423
            $message = 'Attempting to save on ' . Tools::getClassName($this->owner) . ' readOnly model';
424
            //$this->addActionError($message);
425
            throw new \fangface\db\Exception($message);
426
        } elseif ($this->owner->getReadOnly() && $hasParentModel) {
427
428
            $message = 'Skipping save on ' . Tools::getClassName($this->owner) . ' readOnly model';
429
            $this->addActionWarning($message);
0 ignored issues
show
Documentation Bug introduced by
The method addActionWarning does not exist on object<fangface\behaviors\NestedSetBehavior>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
430
            return true;
431
        } else {
432
433
            if ($runValidation && !$this->owner->validate($attributes)) {
434
                return false;
435
            }
436
437
            if ($this->owner->getIsNewRecord()) {
438
                return $this->makeRoot($attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 417 can also be of type null; however, fangface\behaviors\NestedSetBehavior::makeRoot() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
439
            }
440
441
            $updateChildPaths = false;
442
            if ($this->hasPaths && !$this->owner->getIsNewRecord()) {
443
                if ($this->owner->hasAttribute($this->pathAttribute)) {
444
                    if ($this->owner->hasChanged($this->pathAttribute)) {
445
                        $updateChildPaths = true;
446
                        if ($this->_previousPath == '') {
447
                            $this->_previousPath = $this->owner->getOldAttribute($this->pathAttribute);
448
                        }
449
                    }
450
                }
451
                if (!$updateChildPaths && $this->owner->hasAttribute($this->nameAttribute)) {
452
                    if ($this->owner->hasChanged($this->nameAttribute)) {
453
                        $this->_previousPath = $this->owner->getAttribute($this->pathAttribute);
454
                        $this->checkAndSetPath($this->owner);
455
                        if ($this->_previousPath != $this->owner->getAttribute($this->pathAttribute)) {
456
                            $updateChildPaths = true;
457
                        }
458
                    }
459
                }
460
            }
461
462
            $nameChanged = false;
463
            if ($this->owner->hasAttribute($this->nameAttribute) && $this->owner->hasChanged($this->nameAttribute)) {
464
                $nameChanged = true;
465
                if (!$this->beforeRenameNode($this->_previousPath)) {
466
                    return false;
467
                }
468
            }
469
470
            $result = false;
471
            $db = $this->owner->getDb();
472
473
            if ($db->getTransaction() === null) {
474
                $transaction = $db->beginTransaction();
475
            }
476
477
            try {
478
479
                $this->_ignoreEvent = true;
480
                //$result = $this->owner->update(false, $attributes);
481
                if (false && method_exists($this->owner, 'saveAll')) {
482
                    $result = $this->owner->saveAll(false, $hasParentModel, false, $attributes);
483
                } else {
484
                    $result = $this->owner->save(false, $attributes, $hasParentModel, $fromSaveAll);
485
                }
486
                $this->_ignoreEvent = false;
487
488
                if ($result && $updateChildPaths) {
489
                    // only if we have children
490
                    if ($this->owner->getAttribute($this->rightAttribute) > $this->owner->getAttribute($this->leftAttribute) + 1) {
491
                        $condition = $db->quoteColumnName($this->leftAttribute) . '>' . $this->owner->getAttribute($this->leftAttribute) . ' AND ' . $db->quoteColumnName($this->rightAttribute) . '<' . $this->owner->getAttribute($this->rightAttribute);
492
                        $params = [];
493
                        if ($this->hasManyRoots) {
494
                            $condition .= ' AND ' . $db->quoteColumnName($this->rootAttribute) . '=:' . $this->rootAttribute;
495
                            $params[':' . $this->rootAttribute] = $this->owner->getAttribute($this->rootAttribute);
496
                        }
497
498
                        $updateColumns = [];
499
                        $pathLength = Tools::strlen($this->_previousPath) + 1;
500
                        // SQL Server: SUBSTRING() rather than SUBSTR
501
                        // SQL Server: + instead of CONCAT
502
                        if ($db->getDriverName() == 'mssql') {
503
                            $updateColumns[$this->pathAttribute] = new Expression($db->quoteValue($this->owner->getAttribute($this->pathAttribute)) . ' + SUBSTRING(' . $db->quoteColumnName($this->pathAttribute) . ', ' . $pathLength . '))');
504
                        } else {
505
                            $updateColumns[$this->pathAttribute] = new Expression('CONCAT(' . $db->quoteValue($this->owner->getAttribute($this->pathAttribute)) . ', SUBSTR(' . $db->quoteColumnName($this->pathAttribute) . ', ' . $pathLength . '))');
506
                        }
507
                        $result = $this->owner->updateAll($updateColumns, $condition, $params);
508
                    }
509
                }
510
511
                if ($result && $nameChanged) {
512
                    $result = $this->afterRenameNode($this->_previousPath);
513
                }
514
            } catch (\Exception $e) {
515
                if (isset($transaction)) {
516
                    $transaction->rollback();
517
                }
518
                throw $e;
519
            }
520
521
            if (isset($transaction)) {
522
                if (!$result) {
523
                    $transaction->rollback();
524
                } else {
525
                    $transaction->commit();
526
                }
527
            }
528
529
            $this->_previousPath = '';
530
        }
531
532
        return $result;
533
    }
534
535
536
    /**
537
     * Create root node if multiple-root tree mode.
538
     * Update node if it's not new
539
     *
540
     * @param boolean $runValidation
541
     *        whether to perform validation
542
     * @param array $attributes
543
     *        list of attributes
544
     * @return boolean whether the saving succeeds
545
     */
546
    public function saveNode($runValidation = true, $attributes = null)
547
    {
548
        return $this->save($runValidation, $attributes);
549
    }
550
551
552
    /**
553
     * Deletes node and it's descendants
554
     *
555
     * @throws Exception.
556
     * @throws \Exception.
557
     * @param boolean $hasParentModel
558
     *        whether this method was called from the top level or by a parent
559
     *        If false, it means the method was called at the top level
560
     * @param boolean $fromDeleteFull
561
     *        has the delete() call come from deleteFull() or not
562
     * @return boolean did delete() successfully process
563
     */
564
    private function delete($hasParentModel = false, $fromDeleteFull = false)
0 ignored issues
show
Unused Code introduced by
The parameter $fromDeleteFull is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
565
    {
566
        if ($this->owner->getIsNewRecord()) {
567
            throw new Exception('The node can\'t be deleted because it is new.');
568
        }
569
570
        if ($this->getIsDeletedRecord()) {
571
            throw new Exception('The node can\'t be deleted because it is already deleted.');
572
        }
573
574
        if (!$this->beforeDeleteNode()) {
575
            return false;
576
        }
577
578
        $db = $this->owner->getDb();
579
580
        if ($db->getTransaction() === null) {
581
            $transaction = $db->beginTransaction();
582
        }
583
584
        if ($this->owner->hasAttribute($this->pathAttribute) && $this->owner->hasAttribute($this->nameAttribute)) {
585
            $this->_previousPath = $this->owner->getAttribute($this->pathAttribute);
586
        }
587
588
        try {
589
590
            $result = true;
591
            if (!$this->owner->isLeaf()) {
0 ignored issues
show
Documentation Bug introduced by
The method isLeaf does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
592
593
                $condition = $db->quoteColumnName($this->leftAttribute) . '>=' . $this->owner->getAttribute($this->leftAttribute) . ' AND ' . $db->quoteColumnName($this->rightAttribute) . '<=' . $this->owner->getAttribute($this->rightAttribute);
594
595
                if ($this->hasManyRoots) {
596
                    $condition .= ' AND ' . $db->quoteColumnName($this->rootAttribute) . '=' . $this->owner->getAttribute($this->rootAttribute);
597
                }
598
599
                if (!$this->deleteIndividual) {
600
601
                    $result = $this->owner->deleteAll($condition) > 0;
602
603
                } else {
604
605
                    $nodes = $this->owner->descendants()->all();
0 ignored issues
show
Documentation Bug introduced by
The method descendants does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
606
                    foreach ($nodes as $node) {
607
608
                        $node->setIgnoreEvents(true);
609
                        if (method_exists($node, 'deleteFull')) {
610
                            $result = $node->deleteFull($hasParentModel);
611
                        } else {
612
                            $result = $node->delete();
613
                        }
614
                        $node->setIgnoreEvents(false);
615
616
                        if (method_exists($node, 'hasActionErrors')) {
617
                            if ($node->hasActionErrors()) {
618
                                $this->owner->mergeActionErrors($node->getActionErrors());
619
                            }
620
                        }
621
622
                        if (method_exists($node, 'hasActionWarnings')) {
623
                            if ($node->hasActionWarnings()) {
624
                                $this->owner->mergeActionWarnings($node->getActionWarnings());
625
                            }
626
                        }
627
                        if (!$result) {
628
                            break;
629
                        }
630
                    }
631
                }
632
            }
633
634
            if ($result) {
635
636
                $this->shiftLeftRight($this->owner->getAttribute($this->rightAttribute) + 1, $this->owner->getAttribute($this->leftAttribute) - $this->owner->getAttribute($this->rightAttribute) - 1);
637
638
                $left = $this->owner->getAttribute($this->leftAttribute);
639
                $right = $this->owner->getAttribute($this->rightAttribute);
640
641
                $this->_ignoreEvent = true;
642
                if (method_exists($this->owner, 'deleteFull')) {
643
                    $result = $this->owner->deleteFull($hasParentModel);
644
                } else {
645
                    $result = $this->owner->delete();
646
                }
647
                $this->_ignoreEvent = false;
648
649
                $this->correctCachedOnDelete($left, $right);
650
            }
651
652
            if ($result) {
653
                $result = $this->afterDeleteNode($this->_previousPath);
654
            }
655
656
            $this->_previousPath = '';
657
658
            if (!$result) {
659
                if (isset($transaction)) {
660
                    $transaction->rollback();
661
                }
662
                return false;
663
            }
664
665
            if (isset($transaction)) {
666
                $transaction->commit();
667
            }
668
        } catch (\Exception $e) {
669
            if (isset($transaction)) {
670
                $transaction->rollback();
671
            }
672
673
            throw $e;
674
        }
675
        $this->_previousPath = '';
676
        return true;
677
    }
678
679
680
    /**
681
     * Deletes node and it's descendants.
682
     *
683
     * @param boolean $hasParentModel
684
     *        whether this method was called from the top level or by a parent
685
     *        If false, it means the method was called at the top level
686
     * @param boolean $fromDeleteFull
687
     *        has the delete() call come from deleteFull() or not
688
     * @return boolean did deleteNode() successfully process
689
     */
690
    public function deleteNode($hasParentModel = false, $fromDeleteFull = false)
691
    {
692
        return $this->delete($hasParentModel, $fromDeleteFull);
693
    }
694
695
696
    /**
697
     * Prepends node to target as first child
698
     *
699
     * @param ActiveRecord $target
700
     *        the target
701
     * @param boolean $runValidation
702
     *        [optional] whether to perform validation
703
     * @param array $attributes
704
     *        [optional] list of attributes
705
     * @return boolean whether the prepending succeeds
706
     */
707
    public function prependTo($target, $runValidation = true, $attributes = null)
708
    {
709
        if ($runValidation) {
710
            if (!$this->owner->validate($attributes)) {
711
                return false;
712
            }
713
            $runValidation = false;
714
        }
715
        $this->checkAndSetPath($target, true);
716
        return $this->addNode($target, $target->getAttribute($this->leftAttribute) + 1, 1, $runValidation, $attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 707 can also be of type null; however, fangface\behaviors\NestedSetBehavior::addNode() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
717
    }
718
719
720
    /**
721
     * Prepends target to node as first child
722
     *
723
     * @param ActiveRecord $target
724
     *        the target
725
     * @param boolean $runValidation
726
     *        [optional] whether to perform validation
727
     * @param array $attributes
728
     *        [optional] list of attributes
729
     * @return boolean whether the prepending succeeds
730
     */
731
    public function prepend($target, $runValidation = true, $attributes = null)
732
    {
733
        return $target->prependTo($this->owner, $runValidation, $attributes);
0 ignored issues
show
Documentation Bug introduced by
The method prependTo does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
734
    }
735
736
737
    /**
738
     * Appends node to target as last child
739
     *
740
     * @param ActiveRecord $target
741
     *        the target
742
     * @param boolean $runValidation
743
     *        [optional] whether to perform validation
744
     * @param array $attributes
745
     *        [optional] list of attributes
746
     * @return boolean whether the appending succeeds
747
     */
748
    public function appendTo($target, $runValidation = true, $attributes = null)
749
    {
750
        if ($runValidation) {
751
            if (!$this->owner->validate($attributes)) {
752
                return false;
753
            }
754
            $runValidation = false;
755
        }
756
        $this->checkAndSetPath($target, true);
757
        return $this->addNode($target, $target->getAttribute($this->rightAttribute), 1, $runValidation, $attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 748 can also be of type null; however, fangface\behaviors\NestedSetBehavior::addNode() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
758
    }
759
760
761
    /**
762
     * Appends target to node as last child
763
     *
764
     * @param ActiveRecord $target
765
     *        the target
766
     * @param boolean $runValidation
767
     *        [optional] whether to perform validation
768
     * @param array $attributes
769
     *        [optional] list of attributes
770
     * @return boolean whether the appending succeeds
771
     */
772
    public function append($target, $runValidation = true, $attributes = null)
773
    {
774
        return $target->appendTo($this->owner, $runValidation, $attributes);
0 ignored issues
show
Documentation Bug introduced by
The method appendTo does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
775
    }
776
777
778
    /**
779
     * Inserts node as previous sibling of target.
780
     *
781
     * @param ActiveRecord $target
782
     *        the target.
783
     * @param boolean $runValidation
784
     *        [optional] whether to perform validation
785
     * @param array $attributes
786
     *        [optional] list of attributes
787
     * @param ActiveRecord $parent
788
     *        [optional] parent node if already known
789
     * @return boolean whether the inserting succeeds.
790
     */
791
    public function insertBefore($target, $runValidation = true, $attributes = null, $parent = null)
792
    {
793
        if ($runValidation) {
794
            if (!$this->owner->validate($attributes)) {
795
                return false;
796
            }
797
            $runValidation = false;
798
        }
799
        $this->checkAndSetPath($target, false, false, $parent);
800
        return $this->addNode($target, $target->getAttribute($this->leftAttribute), 0, $runValidation, $attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 791 can also be of type null; however, fangface\behaviors\NestedSetBehavior::addNode() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
801
    }
802
803
804
    /**
805
     * Inserts node as next sibling of target
806
     *
807
     * @param ActiveRecord $target
808
     *        the target
809
     * @param boolean $runValidation
810
     *        [optional] whether to perform validation
811
     * @param array $attributes
812
     *        [optional] list of attributes
813
     * @param ActiveRecord $parent
814
     *        [optional] parent node if already known
815
     * @return boolean whether the inserting succeeds
816
     */
817
    public function insertAfter($target, $runValidation = true, $attributes = null, $parent = null)
818
    {
819
        if ($runValidation) {
820
            if (!$this->owner->validate($attributes)) {
821
                return false;
822
            }
823
            $runValidation = false;
824
        }
825
        $this->checkAndSetPath($target, false, false, $parent);
826
        return $this->addNode($target, $target->getAttribute($this->rightAttribute) + 1, 0, $runValidation, $attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 817 can also be of type null; however, fangface\behaviors\NestedSetBehavior::addNode() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
827
    }
828
829
830
    /**
831
     * Move node as previous sibling of target
832
     *
833
     * @param ActiveRecord $target
834
     *        the target
835
     * @param ActiveRecord $parent
836
     *        [optional] parent node if already known
837
     * @return boolean whether the moving succeeds
838
     */
839
    public function moveBefore($target, $parent = null)
840
    {
841
        $this->checkAndSetPath($target, false, true, $parent);
842
        return $this->moveNode($target, $target->getAttribute($this->leftAttribute), 0);
843
    }
844
845
846
    /**
847
     * Move node as next sibling of target
848
     *
849
     * @param ActiveRecord $target
850
     *        the target
851
     * @param ActiveRecord $parent
852
     *        [optional] parent node if already known
853
     * @return boolean whether the moving succeeds
854
     */
855
    public function moveAfter($target, $parent = null)
856
    {
857
        $this->checkAndSetPath($target, false, true, $parent);
858
        return $this->moveNode($target, $target->getAttribute($this->rightAttribute) + 1, 0);
859
    }
860
861
862
    /**
863
     * Move node as first child of target
864
     *
865
     * @param ActiveRecord $target
866
     *        the target
867
     * @param ActiveRecord $parent
868
     *        [optional] parent node if already known
869
     * @return boolean whether the moving succeeds
870
     */
871
    public function moveAsFirst($target, $parent = null)
872
    {
873
        $this->checkAndSetPath($target, true, true, $parent);
874
        return $this->moveNode($target, $target->getAttribute($this->leftAttribute) + 1, 1);
875
    }
876
877
878
    /**
879
     * Move node as last child of target
880
     *
881
     * @param ActiveRecord $target
882
     *        the target
883
     * @param ActiveRecord $parent
884
     *        [optional] parent node if already known
885
     * @return boolean whether the moving succeeds
886
     */
887
    public function moveAsLast($target, $parent = null)
888
    {
889
        $this->checkAndSetPath($target, true, true, $parent);
890
        return $this->moveNode($target, $target->getAttribute($this->rightAttribute), 1);
891
    }
892
893
894
    /**
895
     * Move node as new root
896
     *
897
     * @throws Exception
898
     * @return boolean whether the moving succeeds
899
     */
900
    public function moveAsRoot()
901
    {
902
        if (!$this->hasManyRoots) {
903
            throw new Exception('Many roots mode is off.');
904
        }
905
906
        if ($this->owner->getIsNewRecord()) {
907
            throw new Exception('The node should not be new record.');
908
        }
909
910
        if ($this->getIsDeletedRecord()) {
911
            throw new Exception('The node should not be deleted.');
912
        }
913
914
        if ($this->owner->isRoot()) {
0 ignored issues
show
Documentation Bug introduced by
The method isRoot does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
915
            throw new Exception('The node already is root node.');
916
        }
917
918
        if ($this->hasPaths) {
919
            throw new Exception('Paths not yet supported for moveAsRoot.');
920
        }
921
922
        $db = $this->owner->getDb();
923
924
        if ($db->getTransaction() === null) {
925
            $transaction = $db->beginTransaction();
926
        }
927
928
        try {
929
            $left = $this->owner->getAttribute($this->leftAttribute);
930
            $right = $this->owner->getAttribute($this->rightAttribute);
931
            $levelDelta = 1 - $this->owner->getAttribute($this->levelAttribute);
932
            $delta = 1 - $left;
933
            $this->owner->updateAll([
934
                $this->leftAttribute => new Expression($db->quoteColumnName($this->leftAttribute) . sprintf('%+d', $delta)),
935
                $this->rightAttribute => new Expression($db->quoteColumnName($this->rightAttribute) . sprintf('%+d', $delta)),
936
                $this->levelAttribute => new Expression($db->quoteColumnName($this->levelAttribute) . sprintf('%+d', $levelDelta)),
937
                $this->rootAttribute => $this->owner->getPrimaryKey()
938
            ], $db->quoteColumnName($this->leftAttribute) . '>=' . $left . ' AND ' . $db->quoteColumnName($this->rightAttribute) . '<=' . $right . ' AND ' . $db->quoteColumnName($this->rootAttribute) . '=:' . $this->rootAttribute, [
939
                ':' . $this->rootAttribute => $this->owner->getAttribute($this->rootAttribute)
940
            ]);
941
            $this->shiftLeftRight($right + 1, $left - $right - 1);
942
943
            if (isset($transaction)) {
944
                $transaction->commit();
945
            }
946
947
            $this->correctCachedOnMoveBetweenTrees(1, $levelDelta, $this->owner->getPrimaryKey());
948
        } catch (\Exception $e) {
949
            if (isset($transaction)) {
950
                $transaction->rollback();
951
            }
952
953
            throw $e;
954
        }
955
956
        return true;
957
    }
958
959
960
    /**
961
     * Check to see if this nested set supports path or not
962
     *
963
     * @param ActiveRecord $target
964
     *        the target
965
     * @param boolean $isParent
966
     *        is target the parent node
967
     * @param boolean $isMove
968
     *        is this relating to a node move
969
     * @param boolean $isParent
970
     *        is $target the parent of $this->owner
971
     * @param ActiveRecord $parent
972
     *        [optional] parent node if already known
973
     */
974
    public function checkAndSetPath($target, $isParent = false, $isMove = false, $parent = null)
0 ignored issues
show
Unused Code introduced by
The parameter $isMove is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
975
    {
976
        if ($this->hasPaths) {
977
            if ($this->owner->hasAttribute($this->pathAttribute) && $this->owner->hasAttribute($this->nameAttribute)) {
978
                $this->_previousPath = $this->owner->getAttribute($this->pathAttribute);
979
                $this->owner->setAttribute($this->pathAttribute, $this->calculatePath($target, $isParent, $parent));
980
            }
981
        }
982
    }
983
984
985
    /**
986
     * Calculate path based on name and target
987
     *
988
     * @param ActiveRecord $target
989
     *        the target
990
     * @param boolean $isParent
991
     *        is target the parent node
992
     * @param ActiveRecord $parent
993
     *        [optional] parent node if already known
994
     * @return string
995
     */
996
    public function calculatePath($target, $isParent = false, $parent = null)
997
    {
998
        $uniqueNames = false;
999
        if (method_exists($this->owner, 'getIsUniqueNames')) {
1000
            $uniqueNames = $this->owner->getIsUniqueNames();
0 ignored issues
show
Documentation Bug introduced by
The method getIsUniqueNames does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1001
        }
1002
1003
        if ($this->hasPaths || $uniqueNames) {
1004
            if (!$isParent && $parent) {
1005
                $target = $parent;
1006
            } elseif (!$isParent) {
1007
                $target = $target->parentOnly()->one();
0 ignored issues
show
Documentation Bug introduced by
The method parentOnly does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1008
            }
1009
        }
1010
        if ($this->hasPaths) {
1011
            if ($target->getAttribute($this->pathAttribute) == '/') {
1012
                $path = '/' . $this->owner->getAttribute($this->nameAttribute);
1013
            } else {
1014
                $path = $target->getAttribute($this->pathAttribute) . '/' . $this->owner->getAttribute($this->nameAttribute);
1015
            }
1016
        } else {
1017
            $path = '';
1018
        }
1019
        if ($uniqueNames) {
1020
            $matches = $this->children($target)->andWhere([
1021
                $this->nameAttribute => $this->owner->getAttribute($this->nameAttribute)
1022
            ]);
1023
            if (!$this->owner->getIsNewRecord()) {
1024
                $matches->andWhere('id != ' . $this->owner->id);
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<fangface\db\ActiveRecord>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1025
            }
1026
            if ($matches->count()) {
1027
                $path = '__DUPLICATE__';
1028
            }
1029
        }
1030
        return $path;
1031
    }
1032
1033
1034
    /**
1035
     * Determines if node is descendant of subject node
1036
     *
1037
     * @param ActiveRecord $subj
1038
     *        the subject node
1039
     * @param ActiveRecord $object
1040
     *        [optional] defaults to $this->owner
1041
     * @return boolean whether the node is descendant of subject node
1042
     */
1043
    public function isDescendantOf($subj, $object = null)
1044
    {
1045
        $object = (!is_null($object) ? $object : $this->owner);
1046
        $result = ($object->getAttribute($this->leftAttribute) > $subj->getAttribute($this->leftAttribute)) && ($object->getAttribute($this->rightAttribute) < $subj->getAttribute($this->rightAttribute));
1047
1048
        if ($this->hasManyRoots) {
1049
            $result = $result && ($object->getAttribute($this->rootAttribute) === $subj->getAttribute($this->rootAttribute));
1050
        }
1051
1052
        return $result;
1053
    }
1054
1055
1056
    /**
1057
     * Determines if node is leaf
1058
     *
1059
     * @param ActiveRecord $object
1060
     *        [optional] defaults to $this->owner
1061
     * @return boolean whether the node is leaf
1062
     */
1063
    public function isLeaf($object = null)
1064
    {
1065
        $object = (!is_null($object) ? $object : $this->owner);
1066
        return $object->getAttribute($this->rightAttribute) - $object->getAttribute($this->leftAttribute) === 1;
1067
    }
1068
1069
1070
    /**
1071
     * Determines if node is root
1072
     *
1073
     * @param ActiveRecord $object
1074
     *        [optional] defaults to $this->owner
1075
     * @return boolean whether the node is root
1076
     */
1077
    public function isRoot($object = null)
1078
    {
1079
        $object = (!is_null($object) ? $object : $this->owner);
1080
        return $object->getAttribute($this->leftAttribute) == 1;
1081
    }
1082
1083
1084
    /**
1085
     * Returns if the current node is deleted
1086
     *
1087
     * @return boolean whether the node is deleted
1088
     */
1089
    public function getIsDeletedRecord()
1090
    {
1091
        return $this->_deleted;
1092
    }
1093
1094
1095
    /**
1096
     * Sets if the current node is deleted
1097
     *
1098
     * @param boolean $value
1099
     *        whether the node is deleted
1100
     */
1101
    public function setIsDeletedRecord($value)
1102
    {
1103
        $this->_deleted = $value;
1104
    }
1105
1106
1107
    /**
1108
     * Handle 'afterFind' event of the owner
1109
     *
1110
     * @param ModelEvent $event
1111
     *        event parameter
1112
     */
1113
    public function afterFind($event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1114
    {
1115
        self::$_cached[get_class($this->owner)][$this->_id = self::$_c++] = $this->owner;
1116
    }
1117
1118
1119
    /**
1120
     * Handle 'beforeInsert' event of the owner
1121
     *
1122
     * @param ModelEvent $event
1123
     *        event parameter
1124
     * @throws Exception
1125
     * @return boolean
1126
     */
1127
    public function beforeInsert($event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1128
    {
1129
        if ($this->_ignoreEvent) {
1130
            return true;
1131
        } else {
1132
            throw new Exception('You should not use ActiveRecord::save() or ActiveRecord::insert() methods when NestedSetBehavior attached.');
1133
        }
1134
    }
1135
1136
1137
    /**
1138
     * Handle 'beforeUpdate' event of the owner
1139
     *
1140
     * @param ModelEvent $event
1141
     *        event parameter
1142
     * @throws Exception
1143
     * @return boolean
1144
     */
1145
    public function beforeUpdate($event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1146
    {
1147
        if ($this->_ignoreEvent) {
1148
            return true;
1149
        } else {
1150
            throw new Exception('You should not use ActiveRecord::save() or ActiveRecord::update() methods when NestedSetBehavior attached.');
1151
        }
1152
    }
1153
1154
1155
    /**
1156
     * Handle 'beforeDelete' event of the owner
1157
     *
1158
     * @param ModelEvent $event
1159
     *        event parameter
1160
     * @throws Exception
1161
     * @return boolean
1162
     */
1163
    public function beforeDelete($event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1164
    {
1165
        if ($this->_ignoreEvent) {
1166
            return true;
1167
        } else {
1168
            throw new Exception('You should not use ActiveRecord::delete() method when NestedSetBehavior behavior attached.');
1169
        }
1170
    }
1171
1172
1173
    /**
1174
     * Handle 'beforeSaveAll' event of the owner
1175
     *
1176
     * @param ModelEvent $event
1177
     *        event parameter
1178
     * @throws Exception
1179
     * @return boolean
1180
     */
1181
    public function beforeSaveAll($event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1182
    {
1183
        if ($this->_ignoreEvent) {
1184
            return true;
1185
        } elseif ($this->owner->getIsNewRecord()) {
1186
            throw new Exception('You should not use ActiveRecord::saveAll() on new records when NestedSetBehavior attached.');
1187
        }
1188
    }
1189
1190
1191
    /**
1192
     * Handle 'beforeDeleteFull' event of the owner
1193
     *
1194
     * @param ModelEvent $event
1195
     *        event parameter
1196
     * @throws Exception
1197
     * @return boolean
1198
     */
1199
    public function beforeDeleteFull($event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1200
    {
1201
        if ($this->_ignoreEvent) {
1202
            return true;
1203
        } else {
1204
            throw new Exception('You should not use ActiveRecord::beforeDeleteFull() method when NestedSetBehavior attached.');
1205
        }
1206
    }
1207
1208
1209
    /**
1210
     *
1211
     * @param integer $key.
0 ignored issues
show
Bug introduced by
There is no parameter named $key.. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1212
     * @param integer $delta.
0 ignored issues
show
Documentation introduced by
There is no parameter named $delta.. Did you maybe mean $delta?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
1213
     */
1214
    private function shiftLeftRight($key, $delta)
1215
    {
1216
        $db = $this->owner->getDb();
1217
1218
        foreach ([
1219
            $this->leftAttribute,
1220
            $this->rightAttribute
1221
        ] as $attribute) {
1222
            $condition = $db->quoteColumnName($attribute) . '>=' . $key;
1223
            $params = [];
1224
1225
            if ($this->hasManyRoots) {
1226
                $condition .= ' AND ' . $db->quoteColumnName($this->rootAttribute) . '=:' . $this->rootAttribute;
1227
                $params[':' . $this->rootAttribute] = $this->owner->getAttribute($this->rootAttribute);
1228
            }
1229
1230
            $this->owner->updateAll([
1231
                $attribute => new Expression($db->quoteColumnName($attribute) . sprintf('%+d', $delta))
1232
            ], $condition, $params);
1233
        }
1234
    }
1235
1236
1237
    /**
1238
     *
1239
     * @param ActiveRecord $target
1240
     * @param int $key
1241
     * @param int $levelUp
1242
     * @param boolean $runValidation
1243
     * @param array $attributes
1244
     * @throws Exception
1245
     * @return boolean
1246
     */
1247
    private function addNode($target, $key, $levelUp, $runValidation, $attributes)
1248
    {
1249
        if (!$this->owner->getIsNewRecord()) {
1250
            throw new Exception('The node can\'t be inserted because it is not new.');
1251
        }
1252
1253
        if ($this->getIsDeletedRecord()) {
1254
            throw new Exception('The node can\'t be inserted because it is deleted.');
1255
        }
1256
1257
        if ($target->getIsDeletedRecord()) {
0 ignored issues
show
Bug introduced by
The method getIsDeletedRecord() does not exist on fangface\db\ActiveRecord. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1258
            throw new Exception('The node can\'t be inserted because target node is deleted.');
1259
        }
1260
1261
        if ($this->owner->equals($target)) {
1262
            throw new Exception('The target node should not be self.');
1263
        }
1264
1265
        if (!$levelUp && $target->isRoot()) {
0 ignored issues
show
Documentation Bug introduced by
The method isRoot does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1266
            throw new Exception('The target node should not be root.');
1267
        }
1268
1269
        if ($this->hasPaths && $this->owner->getAttribute($this->pathAttribute) == '__DUPLICATE__') {
1270
            throw new Exception('New node has duplicate path.');
1271
        }
1272
1273
        if ($runValidation && !$this->owner->validate($attributes)) {
1274
            return false;
1275
        }
1276
1277
        if (!$this->beforeAddNode()) {
1278
            return false;
1279
        }
1280
1281
        if ($this->hasManyRoots) {
1282
            $this->owner->setAttribute($this->rootAttribute, $target->getAttribute($this->rootAttribute));
1283
        }
1284
1285
        $db = $this->owner->getDb();
1286
1287
        if ($db->getTransaction() === null) {
1288
            $transaction = $db->beginTransaction();
1289
        }
1290
1291
        try {
1292
            $this->shiftLeftRight($key, 2);
1293
            $this->owner->setAttribute($this->leftAttribute, $key);
1294
            $this->owner->setAttribute($this->rightAttribute, $key + 1);
1295
            $this->owner->setAttribute($this->levelAttribute, $target->getAttribute($this->levelAttribute) + $levelUp);
1296
            $this->_ignoreEvent = true;
1297
            //$result = $this->owner->insert(false, $attributes);
1298
            if (method_exists($this->owner, 'saveAll')) {
1299
                $result = $this->owner->saveAll(false, false, false, $attributes);
1300
            } else {
1301
                $result = $this->owner->save(false, $attributes);
1302
            }
1303
            $this->_ignoreEvent = false;
1304
1305
            if ($result) {
1306
                $result = $this->afterAddNode();
1307
            }
1308
1309
            if (!$result) {
1310
                if (isset($transaction)) {
1311
                    $transaction->rollback();
1312
                }
1313
                return false;
1314
            }
1315
1316
            $this->owner->setIsNewRecord(false);
1317
1318
            if (isset($transaction)) {
1319
                $transaction->commit();
1320
            }
1321
1322
            $this->correctCachedOnAddNode($key);
1323
        } catch (\Exception $e) {
1324
            if (isset($transaction)) {
1325
                $transaction->rollback();
1326
            }
1327
            throw $e;
1328
        }
1329
1330
        return true;
1331
    }
1332
1333
1334
    /**
1335
     *
1336
     * @param array $attributes
1337
     * @throws Exception
1338
     * @return boolean
1339
     */
1340
    private function makeRoot($attributes)
1341
    {
1342
        $this->owner->setAttribute($this->leftAttribute, 1);
1343
        $this->owner->setAttribute($this->rightAttribute, 2);
1344
        $this->owner->setAttribute($this->levelAttribute, 1);
1345
        if ($this->hasPaths && $this->owner->hasAttribute($this->pathAttribute) && $this->owner->getAttribute($this->pathAttribute) == '') {
1346
            $this->owner->setAttribute($this->pathAttribute, '/');
1347
        }
1348
1349
        if ($this->hasManyRoots) {
1350
            $db = $this->owner->getDb();
1351
1352
            if ($db->getTransaction() === null) {
1353
                $transaction = $db->beginTransaction();
1354
            }
1355
1356
            try {
1357
                $this->_ignoreEvent = true;
1358
                //$result = $this->owner->insert(false, $attributes);
1359
                if (method_exists($this->owner, 'saveAll')) {
1360
                    $result = $this->owner->saveAll(false, false, false, $attributes);
1361
                } else {
1362
                    $result = $this->owner->save(false, $attributes);
1363
                }
1364
                $this->_ignoreEvent = false;
1365
1366
                if (!$result) {
1367
                    if (isset($transaction)) {
1368
                        $transaction->rollback();
1369
                    }
1370
1371
                    return false;
1372
                }
1373
1374
                $this->owner->setIsNewRecord(false);
1375
1376
                $this->owner->setAttribute($this->rootAttribute, $this->owner->getPrimaryKey());
1377
                $primaryKey = $this->owner->primaryKey();
1378
1379
                if (!isset($primaryKey[0])) {
1380
                    throw new Exception(get_class($this->owner) . ' must have a primary key.');
1381
                }
1382
1383
                $this->owner->updateAll([
1384
                    $this->rootAttribute => $this->owner->getAttribute($this->rootAttribute)
1385
                ], [
1386
                    $primaryKey[0] => $this->owner->getAttribute($this->rootAttribute)
1387
                ]);
1388
1389
                if (isset($transaction)) {
1390
                    $transaction->commit();
1391
                }
1392
            } catch (\Exception $e) {
1393
                if (isset($transaction)) {
1394
                    $transaction->rollback();
1395
                }
1396
1397
                throw $e;
1398
            }
1399
        } else {
1400
            if ($this->owner->find()
1401
                ->roots()
1402
                ->exists()) {
1403
                throw new Exception('Can\'t create more than one root in single root mode.');
1404
            }
1405
1406
            $this->_ignoreEvent = true;
1407
            //$result = $this->owner->insert(false, $attributes);
1408
            if (method_exists($this->owner, 'saveAll')) {
1409
                $result = $this->owner->saveAll(false, false, false, $attributes);
1410
            } else {
1411
                $result = $this->owner->save(false, $attributes);
1412
            }
1413
            $this->_ignoreEvent = false;
1414
1415
            if (!$result) {
1416
                return false;
1417
            }
1418
1419
            $this->owner->setIsNewRecord(false);
1420
        }
1421
1422
        return true;
1423
    }
1424
1425
1426
    /**
1427
     *
1428
     * @param ActiveRecord $target
1429
     * @param int $key
1430
     * @param int $levelUp
1431
     * @throws Exception
1432
     * @return boolean
1433
     */
1434
    private function moveNode($target, $key, $levelUp)
1435
    {
1436
        if ($this->owner->getIsNewRecord()) {
1437
            throw new Exception('The node should not be new record.');
1438
        }
1439
1440
        if ($this->getIsDeletedRecord()) {
1441
            throw new Exception('The node should not be deleted.');
1442
        }
1443
1444
        if ($target->getIsDeletedRecord()) {
0 ignored issues
show
Bug introduced by
The method getIsDeletedRecord() does not exist on fangface\db\ActiveRecord. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1445
            throw new Exception('The target node should not be deleted.');
1446
        }
1447
1448
        if ($this->owner->equals($target)) {
1449
            throw new Exception('The target node should not be self.');
1450
        }
1451
1452
        if ($target->isDescendantOf($this->owner)) {
0 ignored issues
show
Documentation Bug introduced by
The method isDescendantOf does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1453
            throw new Exception('The target node should not be descendant.');
1454
        }
1455
1456
        if (!$levelUp && $target->isRoot()) {
0 ignored issues
show
Documentation Bug introduced by
The method isRoot does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1457
            throw new Exception('The target node should not be root.');
1458
        }
1459
1460
        if (!$this->beforeMoveNode($this->_previousPath)) {
1461
            return false;
1462
        }
1463
1464
        $db = $this->owner->getDb();
1465
1466
        if ($db->getTransaction() === null) {
1467
            $transaction = $db->beginTransaction();
1468
        }
1469
1470
        try {
1471
            $left = $this->owner->getAttribute($this->leftAttribute);
1472
            $right = $this->owner->getAttribute($this->rightAttribute);
1473
            $levelDelta = $target->getAttribute($this->levelAttribute) - $this->owner->getAttribute($this->levelAttribute) + $levelUp;
1474
1475
            if ($this->hasManyRoots && $this->owner->getAttribute($this->rootAttribute) !== $target->getAttribute($this->rootAttribute)) {
1476
1477
                foreach ([
1478
                    $this->leftAttribute,
1479
                    $this->rightAttribute
1480
                ] as $attribute) {
1481
                    $this->owner->updateAll([
1482
                        $attribute => new Expression($db->quoteColumnName($attribute) . sprintf('%+d', $right - $left + 1))
1483
                    ], $db->quoteColumnName($attribute) . '>=' . $key . ' AND ' . $db->quoteColumnName($this->rootAttribute) . '=:' . $this->rootAttribute, [
1484
                        ':' . $this->rootAttribute => $target->getAttribute($this->rootAttribute)
1485
                    ]);
1486
                }
1487
1488
                $delta = $key - $left;
1489
                $this->owner->updateAll([
1490
                    $this->leftAttribute => new Expression($db->quoteColumnName($this->leftAttribute) . sprintf('%+d', $delta)),
1491
                    $this->rightAttribute => new Expression($db->quoteColumnName($this->rightAttribute) . sprintf('%+d', $delta)),
1492
                    $this->levelAttribute => new Expression($db->quoteColumnName($this->levelAttribute) . sprintf('%+d', $levelDelta)),
1493
                    $this->rootAttribute => $target->getAttribute($this->rootAttribute)
1494
                ], $db->quoteColumnName($this->leftAttribute) . '>=' . $left . ' AND ' . $db->quoteColumnName($this->rightAttribute) . '<=' . $right . ' AND ' . $db->quoteColumnName($this->rootAttribute) . '=:' . $this->rootAttribute, [
1495
                    ':' . $this->rootAttribute => $this->owner->getAttribute($this->rootAttribute)
1496
                ]);
1497
                $this->shiftLeftRight($right + 1, $left - $right - 1);
1498
1499
                if (isset($transaction)) {
1500
                    $transaction->commit();
1501
                }
1502
1503
                $this->correctCachedOnMoveBetweenTrees($key, $levelDelta, $target->getAttribute($this->rootAttribute));
1504
            } else {
1505
                $delta = $right - $left + 1;
1506
                $this->shiftLeftRight($key, $delta);
1507
1508
                if ($left >= $key) {
1509
                    $left += $delta;
1510
                    $right += $delta;
1511
                }
1512
1513
                $condition = $db->quoteColumnName($this->leftAttribute) . '>=' . $left . ' AND ' . $db->quoteColumnName($this->rightAttribute) . '<=' . $right;
1514
                $params = [];
1515
1516
                if ($this->hasManyRoots) {
1517
                    $condition .= ' AND ' . $db->quoteColumnName($this->rootAttribute) . '=:' . $this->rootAttribute;
1518
                    $params[':' . $this->rootAttribute] = $this->owner->getAttribute($this->rootAttribute);
1519
                }
1520
1521
                $updateColumns = [];
1522
                $updateColumns[$this->levelAttribute] = new Expression($db->quoteColumnName($this->levelAttribute) . sprintf('%+d', $levelDelta));
1523
1524
                if ($this->hasPaths && $this->owner->hasAttribute($this->pathAttribute)) {
1525
                    $pathLength = Tools::strlen($this->_previousPath) + 1;
1526
                    // SQL Server: SUBSTRING() rather than SUBSTR
1527
                    // SQL Server: + instead of CONCAT
1528
                    if ($db->getDriverName() == 'mssql') {
1529
                        $updateColumns[$this->pathAttribute] = new Expression($db->quoteValue($this->owner->getAttribute($this->pathAttribute)) . ' + SUBSTRING(' . $db->quoteColumnName($this->pathAttribute) . ', ' . $pathLength . '))');
1530
                    } else {
1531
                        $updateColumns[$this->pathAttribute] = new Expression('CONCAT(' . $db->quoteValue($this->owner->getAttribute($this->pathAttribute)) . ', SUBSTR(' . $db->quoteColumnName($this->pathAttribute) . ', ' . $pathLength . '))');
1532
                    }
1533
                }
1534
1535
                $this->owner->updateAll($updateColumns, $condition, $params);
1536
1537
                foreach ([
1538
                    $this->leftAttribute,
1539
                    $this->rightAttribute
1540
                ] as $attribute) {
1541
                    $condition = $db->quoteColumnName($attribute) . '>=' . $left . ' AND ' . $db->quoteColumnName($attribute) . '<=' . $right;
1542
                    $params = [];
1543
1544
                    if ($this->hasManyRoots) {
1545
                        $condition .= ' AND ' . $db->quoteColumnName($this->rootAttribute) . '=:' . $this->rootAttribute;
1546
                        $params[':' . $this->rootAttribute] = $this->owner->getAttribute($this->rootAttribute);
1547
                    }
1548
1549
                    $this->owner->updateAll([
1550
                        $attribute => new Expression($db->quoteColumnName($attribute) . sprintf('%+d', $key - $left))
1551
                    ], $condition, $params);
1552
                }
1553
1554
                $this->shiftLeftRight($right + 1, -$delta);
1555
1556
                $result = $this->afterMoveNode($this->_previousPath);
1557
1558
                if (isset($transaction)) {
1559
                    if ($result) {
1560
                        $transaction->commit();
1561
                    } else {
1562
                        $transaction->rollback();
1563
                        $this->_previousPath = '';
1564
                        return false;
1565
                    }
1566
                }
1567
1568
                $this->correctCachedOnMoveNode($key, $levelDelta);
1569
            }
1570
        } catch (\Exception $e) {
1571
            if (isset($transaction)) {
1572
                $transaction->rollback();
1573
            }
1574
1575
            throw $e;
1576
        }
1577
1578
        $this->_previousPath = '';
1579
1580
        return true;
1581
    }
1582
1583
1584
    /**
1585
     * Correct cache for [[delete()]] and [[deleteNode()]].
1586
     *
1587
     * @param integer $left
1588
     * @param integer $right
1589
     */
1590
    private function correctCachedOnDelete($left, $right)
1591
    {
1592
        $key = $right + 1;
1593
        $delta = $left - $right - 1;
1594
        foreach (self::$_cached[get_class($this->owner)] as $node) {
1595
            /** @var $node ActiveRecord */
1596
            if ($node->getIsNewRecord() || $node->getIsDeletedRecord()) {
0 ignored issues
show
Bug introduced by
The method getIsDeletedRecord() does not exist on fangface\db\ActiveRecord. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1597
                continue;
1598
            }
1599
1600
            if ($this->hasManyRoots && $this->owner->getAttribute($this->rootAttribute) !== $node->getAttribute($this->rootAttribute)) {
1601
                continue;
1602
            }
1603
1604
            if ($node->getAttribute($this->leftAttribute) >= $left && $node->getAttribute($this->rightAttribute) <= $right) {
1605
                $node->setIsDeletedRecord(true);
0 ignored issues
show
Bug introduced by
The method setIsDeletedRecord() does not exist on fangface\db\ActiveRecord. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1606
            } else {
1607
                if ($node->getAttribute($this->leftAttribute) >= $key) {
1608
                    $node->setAttribute($this->leftAttribute, $node->getAttribute($this->leftAttribute) + $delta);
1609
                }
1610
1611
                if ($node->getAttribute($this->rightAttribute) >= $key) {
1612
                    $node->setAttribute($this->rightAttribute, $node->getAttribute($this->rightAttribute) + $delta);
1613
                }
1614
            }
1615
        }
1616
    }
1617
1618
1619
    /**
1620
     * Correct cache for [[addNode()]]
1621
     *
1622
     * @param int $key
1623
     */
1624
    private function correctCachedOnAddNode($key)
1625
    {
1626
        foreach (self::$_cached[get_class($this->owner)] as $node) {
1627
            /** @var $node ActiveRecord */
1628
            if ($node->getIsNewRecord() || $node->getIsDeletedRecord()) {
0 ignored issues
show
Bug introduced by
The method getIsDeletedRecord() does not exist on fangface\db\ActiveRecord. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1629
                continue;
1630
            }
1631
1632
            if ($this->hasManyRoots && $this->owner->getAttribute($this->rootAttribute) !== $node->getAttribute($this->rootAttribute)) {
1633
                continue;
1634
            }
1635
1636
            if ($this->owner === $node) {
1637
                continue;
1638
            }
1639
1640
            if ($node->getAttribute($this->leftAttribute) >= $key) {
1641
                $node->setAttribute($this->leftAttribute, $node->getAttribute($this->leftAttribute) + 2);
1642
            }
1643
1644
            if ($node->getAttribute($this->rightAttribute) >= $key) {
1645
                $node->setAttribute($this->rightAttribute, $node->getAttribute($this->rightAttribute) + 2);
1646
            }
1647
        }
1648
    }
1649
1650
1651
    /**
1652
     * Correct cache for [[moveNode()]]
1653
     *
1654
     * @param int $key
1655
     * @param int $levelDelta
1656
     */
1657
    private function correctCachedOnMoveNode($key, $levelDelta)
1658
    {
1659
        $left = $this->owner->getAttribute($this->leftAttribute);
1660
        $right = $this->owner->getAttribute($this->rightAttribute);
1661
        $delta = $right - $left + 1;
1662
1663
        if ($left >= $key) {
1664
            $left += $delta;
1665
            $right += $delta;
1666
        }
1667
1668
        $delta2 = $key - $left;
1669
1670
        foreach (self::$_cached[get_class($this->owner)] as $node) {
1671
            /** @var $node ActiveRecord */
1672
            if ($node->getIsNewRecord() || $node->getIsDeletedRecord()) {
0 ignored issues
show
Bug introduced by
The method getIsDeletedRecord() does not exist on fangface\db\ActiveRecord. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1673
                continue;
1674
            }
1675
1676
            if ($this->hasManyRoots && $this->owner->getAttribute($this->rootAttribute) !== $node->getAttribute($this->rootAttribute)) {
1677
                continue;
1678
            }
1679
1680
            if ($node->getAttribute($this->leftAttribute) >= $key) {
1681
                $node->setAttribute($this->leftAttribute, $node->getAttribute($this->leftAttribute) + $delta);
1682
            }
1683
1684
            if ($node->getAttribute($this->rightAttribute) >= $key) {
1685
                $node->setAttribute($this->rightAttribute, $node->getAttribute($this->rightAttribute) + $delta);
1686
            }
1687
1688
            if ($node->getAttribute($this->leftAttribute) >= $left && $node->getAttribute($this->rightAttribute) <= $right) {
1689
                $node->setAttribute($this->levelAttribute, $node->getAttribute($this->levelAttribute) + $levelDelta);
1690
            }
1691
1692
            if ($node->getAttribute($this->leftAttribute) >= $left && $node->getAttribute($this->leftAttribute) <= $right) {
1693
                $node->setAttribute($this->leftAttribute, $node->getAttribute($this->leftAttribute) + $delta2);
1694
            }
1695
1696
            if ($node->getAttribute($this->rightAttribute) >= $left && $node->getAttribute($this->rightAttribute) <= $right) {
1697
                $node->setAttribute($this->rightAttribute, $node->getAttribute($this->rightAttribute) + $delta2);
1698
            }
1699
1700
            if ($node->getAttribute($this->leftAttribute) >= $right + 1) {
1701
                $node->setAttribute($this->leftAttribute, $node->getAttribute($this->leftAttribute) - $delta);
1702
            }
1703
1704
            if ($node->getAttribute($this->rightAttribute) >= $right + 1) {
1705
                $node->setAttribute($this->rightAttribute, $node->getAttribute($this->rightAttribute) - $delta);
1706
            }
1707
        }
1708
    }
1709
1710
1711
    /**
1712
     * Correct cache for [[moveNode()]]
1713
     *
1714
     * @param int $key
1715
     * @param int $levelDelta
1716
     * @param int $root
1717
     */
1718
    private function correctCachedOnMoveBetweenTrees($key, $levelDelta, $root)
1719
    {
1720
        $left = $this->owner->getAttribute($this->leftAttribute);
1721
        $right = $this->owner->getAttribute($this->rightAttribute);
1722
        $delta = $right - $left + 1;
1723
        $delta2 = $key - $left;
1724
        $delta3 = $left - $right - 1;
1725
1726
        foreach (self::$_cached[get_class($this->owner)] as $node) {
1727
            /** @var $node ActiveRecord */
1728
            if ($node->getIsNewRecord() || $node->getIsDeletedRecord()) {
0 ignored issues
show
Bug introduced by
The method getIsDeletedRecord() does not exist on fangface\db\ActiveRecord. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1729
                continue;
1730
            }
1731
1732
            if ($node->getAttribute($this->rootAttribute) === $root) {
1733
                if ($node->getAttribute($this->leftAttribute) >= $key) {
1734
                    $node->setAttribute($this->leftAttribute, $node->getAttribute($this->leftAttribute) + $delta);
1735
                }
1736
1737
                if ($node->getAttribute($this->rightAttribute) >= $key) {
1738
                    $node->setAttribute($this->rightAttribute, $node->getAttribute($this->rightAttribute) + $delta);
1739
                }
1740
            } elseif ($node->getAttribute($this->rootAttribute) === $this->owner->getAttribute($this->rootAttribute)) {
1741
                if ($node->getAttribute($this->leftAttribute) >= $left && $node->getAttribute($this->rightAttribute) <= $right) {
1742
                    $node->setAttribute($this->leftAttribute, $node->getAttribute($this->leftAttribute) + $delta2);
1743
                    $node->setAttribute($this->rightAttribute, $node->getAttribute($this->rightAttribute) + $delta2);
1744
                    $node->setAttribute($this->levelAttribute, $node->getAttribute($this->levelAttribute) + $levelDelta);
1745
                    $node->setAttribute($this->rootAttribute, $root);
1746
                } else {
1747
                    if ($node->getAttribute($this->leftAttribute) >= $right + 1) {
1748
                        $node->setAttribute($this->leftAttribute, $node->getAttribute($this->leftAttribute) + $delta3);
1749
                    }
1750
1751
                    if ($node->getAttribute($this->rightAttribute) >= $right + 1) {
1752
                        $node->setAttribute($this->rightAttribute, $node->getAttribute($this->rightAttribute) + $delta3);
1753
                    }
1754
                }
1755
            }
1756
        }
1757
    }
1758
1759
1760
    /**
1761
     * Optionally perform actions/checks before addNode is processed
1762
     *
1763
     * @return boolean success
1764
     */
1765
    protected function beforeAddNode()
1766
    {
1767
        if (method_exists($this->owner, 'beforeAddNode')) {
1768
            return $this->owner->beforeAddNode();
0 ignored issues
show
Documentation Bug introduced by
The method beforeAddNode does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1769
        }
1770
        return true;
1771
    }
1772
1773
1774
    /**
1775
     * Optionally perform actions/checks after addNode has processed
1776
     *
1777
     * @return boolean success
1778
     */
1779
    protected function afterAddNode()
1780
    {
1781
        if (method_exists($this->owner, 'afterAddNode')) {
1782
            return $this->owner->afterAddNode();
0 ignored issues
show
Documentation Bug introduced by
The method afterAddNode does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1783
        }
1784
        return true;
1785
    }
1786
1787
1788
    /**
1789
     * Optionally perform actions/checks before a node name is changed
1790
     *
1791
     * @param string $old
1792
     *        old folder path
1793
     * @return boolean success
1794
     */
1795
    protected function beforeRenameNode($old)
1796
    {
1797
        if (method_exists($this->owner, 'beforeRenameNode')) {
1798
            return $this->owner->beforeRenameNode($old);
0 ignored issues
show
Documentation Bug introduced by
The method beforeRenameNode does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1799
        }
1800
        return true;
1801
    }
1802
1803
1804
    /**
1805
     * Optionally perform actions/checks after the node name has been changed
1806
     *
1807
     * @param string $old
1808
     *        old folder path
1809
     * @return boolean success
1810
     */
1811
    protected function afterRenameNode($old)
1812
    {
1813
        if (method_exists($this->owner, 'afterRenameNode')) {
1814
            return $this->owner->afterRenameNode($old);
0 ignored issues
show
Documentation Bug introduced by
The method afterRenameNode does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1815
        }
1816
        return true;
1817
    }
1818
1819
1820
    /**
1821
     * Optionally perform actions/checks before a node is moved
1822
     *
1823
     * @param string $old
1824
     *        old folder path
1825
     * @return boolean success
1826
     */
1827
    protected function beforeMoveNode($old)
1828
    {
1829
        if (method_exists($this->owner, 'beforeMoveNode')) {
1830
            return $this->owner->beforeMoveNode($old);
0 ignored issues
show
Documentation Bug introduced by
The method beforeMoveNode does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1831
        }
1832
        return true;
1833
    }
1834
1835
1836
    /**
1837
     * Optionally perform actions/checks after the node is moved
1838
     *
1839
     * @param string $old
1840
     *        old folder path
1841
     * @return boolean success
1842
     */
1843
    protected function afterMoveNode($old)
1844
    {
1845
        if (method_exists($this->owner, 'afterMoveNode')) {
1846
            return $this->owner->afterMoveNode($old);
0 ignored issues
show
Documentation Bug introduced by
The method afterMoveNode does not exist on object<fangface\db\ActiveRecord>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1847
        }
1848
        return true;
1849
    }
1850
1851
1852
    /**
1853
     * Optionally perform actions/checks before a node is deleted
1854
     *
1855
     * @return boolean success
1856
     */
1857
    protected function beforeDeleteNode()
1858
    {
1859
        if (method_exists($this->owner, 'beforeDeleteNode')) {
1860
            return $this->owner->beforeDeleteNode();
0 ignored issues
show
Bug introduced by
The method beforeDeleteNode() does not exist on fangface\db\ActiveRecord. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1861
        }
1862
        return true;
1863
    }
1864
1865
1866
    /**
1867
     * Optionally perform actions/checks after the node has been deleted
1868
     *
1869
     * @param string $path
1870
     * @return boolean success
1871
     */
1872
    protected function afterDeleteNode($path)
1873
    {
1874
        if (method_exists($this->owner, 'afterDeleteNode')) {
1875
            return $this->owner->afterDeleteNode($path);
0 ignored issues
show
Bug introduced by
The method afterDeleteNode() does not exist on fangface\db\ActiveRecord. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1876
        }
1877
        return true;
1878
    }
1879
1880
1881
    /**
1882
     * Override ignore events flag
1883
     *
1884
     * @param boolean $value
1885
     */
1886
    public function setIgnoreEvents($value)
1887
    {
1888
        $this->_ignoreEvent = $value;
1889
    }
1890
1891
1892
    /**
1893
     * Set previous path (sometimes useful to avoid looking up parent multiple times)
1894
     *
1895
     * @param string $path
1896
     */
1897
    public function setPreviousPath($path)
1898
    {
1899
        $this->_previousPath = $path;
1900
    }
1901
1902
1903
    /**
1904
     * Destructor
1905
     */
1906
    public function __destruct()
1907
    {
1908
        unset(self::$_cached[get_class($this->owner)][$this->_id]);
1909
    }
1910
1911
}
1912