GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Code

< 40 %
40-60 %
> 60 %
1
<?php
2
/**
3
 * @link https://github.com/paulzi/yii2-sortable
4
 * @copyright Copyright (c) 2015 PaulZi <[email protected]>
5
 * @license MIT (https://github.com/paulzi/yii2-sortable/blob/master/LICENSE)
6
 */
7
8
namespace paulzi\sortable;
9
10
use yii\base\Behavior;
11
use yii\db\ActiveQuery;
12
use yii\db\ActiveRecord;
13
use yii\db\Expression;
14
15
16
/**
17
 * Sortable Behavior for Yii2
18
 * @author PaulZi <[email protected]>
19
 *
20
 * @property ActiveRecord $owner
21
 */
22
class SortableBehavior extends Behavior
23
{
24
    const OPERATION_FIRST             = 1;
25
    const OPERATION_LAST              = 2;
26
    const OPERATION_POSITION_BACKWARD = 3;
27
    const OPERATION_POSITION_FORWARD  = 4;
28
29
    /**
30
     * List of attributes, callable or ActiveQuery
31
     * The list of attributes - a simple way to scope elements with the same content fields, the aliases do not need.
32
     * Warning! You MUST use tableName() alias in ActiveQuery, when you are using joinMode:
33
     * For example,
34
     *
35
     * ~~~
36
     * public function behaviors()
37
     * {
38
     *     return [
39
     *         [
40
     *             'class' => SortableBehavior::className(),
41
     *             'query' => ['parent_id'],
42
     *         ]
43
     *     ];
44
     * }
45
     * ~~~
46
     *
47
     * This is equivalent to:
48
     *
49
     * ~~~
50
     * public function behaviors()
51
     * {
52
     *     return [
53
     *         [
54
     *             'class' => SortableBehavior::className(),
55
     *             'query' => function ($model) {
56
     *                 $tableName = $model->tableName();
57
     *                 return $model->find()->andWhere(["{$tableName}.[[parent_id]]" => $model->parent_id]);
58
     *             },
59
     *         ]
60
     *     ];
61
     * }
62
     * ~~~
63
     *
64
     * @var array|callable|ActiveQuery
65
     */
66
    public $query;
67
68
    /**
69
     * @var string
70
     */
71
    public $sortAttribute = 'sort';
72
73
    /**
74
     * @var int
75
     */
76
    public $step = 100;
77
78
    /**
79
     * Search method of unallocated value.
80
     * When joinMode is true, using join table with self. Otherwise, use the search in the window. Window size defined by $windowSize property.
81
     * @var bool
82
     */
83
    public $joinMode = true;
84
85
    /**
86
     * Defines the size of the search window, when joinMode is false.
87
     * @var int
88
     */
89
    public $windowSize = 1000;
90
91
    /**
92
     * @var integer|null
93
     */
94
    protected $operation;
95
96
    /**
97
     * @var integer
98
     */
99
    protected $position;
100
101
102
    /**
103
     * @inheritdoc
104
     */
105 63
    public function events()
106
    {
107
        return [
108 63
            ActiveRecord::EVENT_BEFORE_INSERT   => 'beforeSave',
109 63
            ActiveRecord::EVENT_AFTER_INSERT    => 'afterSave',
110 63
            ActiveRecord::EVENT_BEFORE_UPDATE   => 'beforeSave',
111 63
            ActiveRecord::EVENT_AFTER_UPDATE    => 'afterSave',
112 63
        ];
113
    }
114
115
    /**
116
     * @return integer
117
     */
118 3
    public function getSortablePosition()
119
    {
120 3
        return $this->owner->getAttribute($this->sortAttribute);
121
    }
122
123
    /**
124
     * @return ActiveRecord
125
     */
126 15
    public function moveFirst()
127
    {
128 15
        $this->operation = self::OPERATION_FIRST;
129 15
        return $this->owner;
130
    }
131
132
    /**
133
     * @return ActiveRecord
134
     */
135 15
    public function moveLast()
136
    {
137 15
        $this->operation = self::OPERATION_LAST;
138 15
        return $this->owner;
139
    }
140
141
    /**
142
     * @param integer $position
143
     * @param bool $forward Move existing items to forward or backward
144
     * @return ActiveRecord
145
     */
146 21
    public function moveTo($position, $forward = true)
147
    {
148 21
        $this->operation = $forward ? self::OPERATION_POSITION_FORWARD : self::OPERATION_POSITION_BACKWARD;
149 21
        $this->position  = (int)$position;
150 21
        return $this->owner;
151
    }
152
153
    /**
154
     * @param ActiveRecord $model
155
     * @return ActiveRecord
156
     */
157 3
    public function moveBefore($model)
158
    {
159 3
        return $this->moveTo($model->getAttribute($this->sortAttribute) - 1, false);
160
    }
161
162
    /**
163
     * @param ActiveRecord $model
164
     * @return ActiveRecord
165
     */
166 3
    public function moveAfter($model)
167
    {
168 3
        return $this->moveTo($model->getAttribute($this->sortAttribute) + 1, true);
169
    }
170
171
    /**
172
     * Reorders items with values of sortAttribute begin from zero.
173
     * @param bool $middle
174
     * @return integer
175
     * @throws \Exception
176
     */
177 3
    public function reorder($middle = true)
178
    {
179 3
        $result = 0;
180
        \Yii::$app->getDb()->transaction(function () use (&$result, $middle) {
181 3
            $list = $this->getQueryInternal()
182 3
                ->select($this->owner->primaryKey())
183 3
                ->orderBy([$this->sortAttribute => SORT_ASC])
184 3
                ->asArray()
185 3
                ->all();
186 3
            $from = $middle ? count($list) >> 1 : 0;
187 3
            foreach ($list as $i => $item) {
188 3
                $result += $this->owner->updateAll([$this->sortAttribute => ($i - $from) * $this->step], $item);
189 3
            }
190 3
        });
191
192 3
        return $result;
193
    }
194
195
    /**
196
     *
197
     */
198 57
    public function beforeSave()
199
    {
200 57
        if ($this->owner->getIsNewRecord() && $this->operation === null) {
201 3
            $this->operation = self::OPERATION_LAST;
202 3
        }
203
204 57
        switch ($this->operation) {
205 57
            case self::OPERATION_FIRST:
206 57
            case self::OPERATION_LAST:
207 33
                $query = $this->getQueryInternal();
208 33
                $query->orderBy(null);
209 33
                $position = $this->operation === self::OPERATION_LAST ? $query->max($this->sortAttribute) : $query->min($this->sortAttribute);
210
211 33
                $isSelf = false;
212 33
                if ($position !== null && !$this->owner->getIsNewRecord() && (int)$position === $this->owner->getAttribute($this->sortAttribute)) {
213 6
                    if ($this->query instanceof ActiveQuery || is_callable($this->query)) {
214 6
                        $isSelf = $this->getQueryInternal()
215 6
                            ->andWhere([$this->sortAttribute => $position])
216 6
                            ->andWhere($this->selfCondition())
217 6
                            ->exists();
218
219 6
                    } else {
220 6
                        $isSelf = count($this->owner->getDirtyAttributes($this->query)) === 0;
221
                    }
222 6
                }
223
224 33
                if ($position === null) {
225 6
                    $this->owner->setAttribute($this->sortAttribute, 0);
226 33
                } elseif (!$isSelf) {
227 21
                    if ($this->operation === self::OPERATION_LAST) {
228 12
                        $this->owner->setAttribute($this->sortAttribute, $position + $this->step);
229 12
                    } else {
230 9
                        $this->owner->setAttribute($this->sortAttribute, $position - $this->step);
231
                    }
232 21
                }
233 33
                break;
234
235 24
            case self::OPERATION_POSITION_BACKWARD:
236 24
            case self::OPERATION_POSITION_FORWARD:
237 21
                $this->moveToInternal($this->position, $this->operation === self::OPERATION_POSITION_FORWARD);
238 21
                break;
239 57
        }
240 57
    }
241
242
    /**
243
     *
244
     */
245 57
    public function afterSave()
246
    {
247 57
        $this->operation = null;
248 57
    }
249
250
    /**
251
     * @return ActiveQuery
252
     */
253 57
    protected function getQueryInternal()
254
    {
255 57
        if ($this->query instanceof ActiveQuery) {
256
            $query = clone $this->query;
257
            return $query;
258 57
        } elseif (is_callable($this->query)) {
259 57
            return call_user_func($this->query, $this->owner);
260
        } else {
261 57
            $tableName  = $this->owner->tableName();
262 57
            $attributes = $this->owner->getAttributes($this->query);
263 57
            $attributes = array_combine(
264
                array_map(function ($value) use ($tableName) { return "{$tableName}.[[{$value}]]"; }, array_keys($attributes)),
265 57
                array_values($attributes)
266 57
            );
267 57
            return $this->owner->find()->andWhere($attributes);
268
        }
269
    }
270
271
    /**
272
     * @param string $tableName
273
     * @param string $string
274
     * @return string
275
     */
276 21
    protected static function getJoinConditionReplace($tableName, $string)
277
    {
278 21
        return str_replace($tableName . '.', 'n.', $string);
279
    }
280
281
    /**
282
     * @param string $tableName
283
     * @param array|string $condition
284
     * @return array|string
285
     */
286 21
    protected static function getJoinCondition($tableName, $condition)
287
    {
288 21
        if (is_string($condition)) {
289
            return static::getJoinConditionReplace($tableName, $condition);
290 21
        } elseif (is_array($condition)) {
291 21
            $joinCondition = [];
292 21
            array_walk($condition, function ($value, $key) use ($tableName, &$joinCondition) {
293 21
                $joinCondition[static::getJoinConditionReplace($tableName, $key)] = static::getJoinCondition($tableName, $value);
294 21
            });
295 21
            return $joinCondition;
296 21
        } elseif ($condition instanceof Expression) {
297
            $condition->expression = static::getJoinConditionReplace($tableName, $condition->expression);
298
        }
299 21
        return $condition;
300
    }
301
302
    /**
303
     * @return array
304
     */
305 27
    protected function selfCondition()
306
    {
307 27
        $tableName = $this->owner->tableName();
308 27
        $result = [];
309 27
        foreach ($this->owner->getPrimaryKey(true) as $field => $value) {
310 27
            $result["{$tableName}.[[{$field}]]"] = $value;
311 27
        }
312 27
        return $result;
313
    }
314
315
    /**
316
     * @param integer $from
317
     * @param integer|null $to
318
     * @param bool $forward
319
     */
320 21
    protected function shift($from, $to, $forward)
321
    {
322 21
        $query = $this->getQueryInternal();
323 21
        if ($to === null) {
324 6
            $condition = [$forward ? '>=' : '<=', $this->sortAttribute, $from];
325 6
        } else {
326 18
            $condition = ['between', $this->sortAttribute, $forward ? $from : $to, $forward ? $to : $from];
327
        }
328 21
        $this->owner->updateAll(
329 21
            [$this->sortAttribute => new Expression("[[{$this->sortAttribute}]] " . ($forward ? '+ 1' : '- 1'))],
330
            [
331 21
                'and',
332 21
                $query->where,
333 21
                $condition,
334
            ]
335 21
        );
336 21
    }
337
338
    /**
339
     * @param integer $position
340
     * @param bool $forward
341
     */
342 21
    protected function moveToInternal($position, $forward)
343
    {
344 21
        if ($this->joinMode) {
345 21
            $this->moveToInternalJoinMode($position, $forward);
346 21
        } else {
347 21
            $this->moveToInternalWindowMode($position, $forward);
348
        }
349 21
    }
350
351
    /**
352
     * @param integer $position
353
     * @param bool $forward
354
     */
355 21
    protected function moveToInternalJoinMode($position, $forward)
356
    {
357 21
        $this->owner->setAttribute($this->sortAttribute, $position);
358
359 21
        $tableName = $this->owner->tableName();
360 21
        $query     = $this->getQueryInternal();
361
        $joinCondition = [
362 21
            'and',
363 21
            static::getJoinCondition($tableName, $query->where),
364 21
            ["n.[[{$this->sortAttribute}]]" => new Expression("{$tableName}.[[{$this->sortAttribute}]] " . ($forward ? '+ 1' : '- 1'))],
365 21
        ];
366 21
        if (!$this->owner->getIsNewRecord()) {
367 15
            $joinCondition[] = ['not', static::getJoinCondition($tableName, $this->selfCondition())];
368 15
        }
369
370
        $exists = $query
371 21
            ->andWhere(["{$tableName}.[[{$this->sortAttribute}]]" => $position])
372 21
            ->andWhere(['not', $this->selfCondition()])
373 21
            ->exists();
374 21
        if ($exists) {
375 12
            $unallocated = $this->getQueryInternal()
376 12
                ->select("{$tableName}.[[{$this->sortAttribute}]]")
377 12
                ->leftJoin("{$tableName} n", $joinCondition)
378 12
                ->andWhere([
379 12
                    'and',
380 12
                    [$forward ? '>=' : '<=', "{$tableName}.[[{$this->sortAttribute}]]", $position - ($forward ? 1 : -1)],
381 12
                    ["n.[[{$this->sortAttribute}]]" => null],
382 12
                ])
383 12
                ->orderBy(["{$tableName}.[[{$this->sortAttribute}]]" => $forward ? SORT_ASC : SORT_DESC])
384 12
                ->limit(1)
385 12
                ->scalar();
386 12
            $this->shift($position, $unallocated, $forward);
387 12
        }
388 21
    }
389
390
    /**
391
     * @param integer $position
392
     * @param bool $forward
393
     */
394 21
    protected function moveToInternalWindowMode($position, $forward)
395
    {
396 21
        $this->owner->setAttribute($this->sortAttribute, $position);
397
398 21
        $tableName  = $this->owner->tableName();
399 21
        $query      = $this->getQueryInternal();
400 21
        if (!$this->owner->getIsNewRecord()) {
401 15
            $query->andWhere(['not', $this->selfCondition()]);
402 15
        }
403
404
        $list = $query
405 21
            ->select("{$tableName}.[[{$this->sortAttribute}]]")
406 21
            ->andWhere([$forward ? '>=' : '<=', "{$tableName}.[[{$this->sortAttribute}]]", $position])
407 21
            ->orderBy(["{$tableName}.[[{$this->sortAttribute}]]" => $forward ? SORT_ASC : SORT_DESC])
408 21
            ->limit($this->windowSize)
409 21
            ->column();
410 21
        $unallocated = null;
411 21
        $prev = $position - ($forward ? 1 : -1);
412 21
        foreach ($list as $item) {
413 21
            if (abs($item - $prev) > 1) {
414 15
                $unallocated = $prev;
415 15
                break;
416
            }
417 9
            $prev = $item;
418 21
        }
419
420 21
        $this->shift($position, $unallocated, $forward);
421 21
    }
422
}
423