Issues (1519)

system/Inji/Model.php (52 issues)

1
<?php
2
/**
3
 * Model
4
 *
5
 * @author Alexey Krupskiy <[email protected]>
6
 * @link http://inji.ru/
7
 * @copyright 2015 Alexey Krupskiy
8
 * @license https://github.com/injitools/cms-Inji/blob/master/LICENSE
9
 */
10
11
namespace Inji;
12
13
use Inji\Model\Builder;
14
15
/**
16
 * Class Model
17
 * @package Inji
18
 *
19
 * @method static Model\Builder connection($connectionName)
20
 */
21
class Model {
22
23
    public static $loaded = [];
24
25
    /**
26
     * Object storage type
27
     *
28
     * @var array
29
     */
30
    public static $storage = ['type' => 'db'];
31
32
    /**
33
     * Object name
34
     *
35
     * @var string
36
     */
37
    public static $objectName = '';
38
39
    /**
40
     * Object current params
41
     *
42
     * @var array
43
     */
44
    public $_params = [];
45
46
    /**
47
     * List of changed params in current instance
48
     *
49
     * @var array
50
     */
51
    public $_changedParams = [];
52
53
    /**
54
     * Loaded relations
55
     *
56
     * @var array
57
     */
58
    public $loadedRelations = [];
59
60
    /**
61
     * Model name where this model uses as category
62
     *
63
     * @var string
64
     */
65
    public static $treeCategory = '';
66
67
    /**
68
     * Model name who uses as category in this model
69
     *
70
     * @var string
71
     */
72
    public static $categoryModel = '';
73
74
    /**
75
     * Col labels
76
     *
77
     * @var array
78
     */
79
    public static $labels = [];
80
81
    /**
82
     * Model forms
83
     *
84
     * @var array
85
     */
86
    public static $forms = [];
87
88
    /**
89
     * Model cols
90
     *
91
     * @var array
92
     */
93
    public static $cols = [];
94
95
    /**
96
     * Options group for display inforamtion from model
97
     *
98
     * @var array
99
     */
100
    public static $view = [];
101
102
    /**
103
     * List of relations need loaded with item
104
     *
105
     * @var array
106
     */
107
    public static $needJoin = [];
108
109
    /**
110
     * List of joins who need to laod
111
     *
112
     * @var array
113
     */
114
    public static $relJoins = [];
115
116
    public $connectionName = 'default';
117
    public $dbOptions = [];
118
    public $app;
119
120
    /**
121
     * Set params when model create
122
     *
123
     * @param array $params
124
     * @param App $app
125
     */
126 3
    public function __construct($params = [], $app = null) {
127 3
        $this->setParams($params);
128 3
        if (!$app) {
129 3
            $this->app = App::$primary;
130
        } else {
131
            $this->app = $app;
132
        }
133 3
    }
134
135 1
    public static function create($params = [], $app = null) {
136 1
        return new static($params, $app);
137
    }
138
139 4
    public static function __callStatic($name, $arguments) {
140 4
        if (method_exists('Inji\Model\Builder', $name)) {
141 4
            return call_user_func_array([new Model\Builder(get_called_class()), $name], $arguments);
142
        }
143
        trigger_error('Undefined method ' . $name . ' in Inji\Model', E_USER_ERROR);
144
    }
145
146
    public static $logging = true;
147
148
    /**
149
     * return object name
150
     *
151
     * @return string
152
     */
153
    public static function objectName() {
154
        return static::$objectName;
155
    }
156
157
    /**
158
     * Retrn col value with col params and relations path
159
     *
160
     * @param Model $object
161
     * @param string $valuePath
162
     * @param boolean $convert
163
     * @param boolean $manageHref
164
     * @return string
165
     */
166
    public static function getColValue($object, $valuePath, $convert = false, $manageHref = false) {
167
        if (is_array($object)) {
168
            $object = array_shift($object);
169
        }
170
        if (strpos($valuePath, ':')) {
171
            $rel = substr($valuePath, 0, strpos($valuePath, ':'));
172
            $param = substr($valuePath, strpos($valuePath, ':') + 1);
173
            if (!$object->$rel) {
174
                $modelName = get_class($object);
175
                $relations = $modelName::relations();
176
                if (empty($relations[$rel]['type']) || $relations[$rel]['type'] == 'one') {
177
                    return $object->{$relations[$rel]['col']};
178
                }
179
                return 0;
180
            }
181
            if (strpos($valuePath, ':')) {
182
                return self::getColValue($object->$rel, $param, $convert, $manageHref);
183
            } else {
184
                return $convert ? Model::resloveTypeValue($object->$rel, $param, $manageHref) : $object->$rel->$param;
185
            }
186
        } else {
187
            return $convert ? Model::resloveTypeValue($object, $valuePath, $manageHref) : $object->$valuePath;
188
        }
189
    }
190
191
    /**
192
     * Retrun value for view
193
     *
194
     * @param Model $item
195
     * @param string $colName
196
     * @param boolean $manageHref
197
     * @param array $params
198
     * @return string
199
     */
200
    public static function resloveTypeValue($item, $colName, $manageHref = false, $colInfo = []) {
201
        $modelName = get_class($item);
202
        if (!$colInfo) {
203
            $colInfo = $modelName::getColInfo($colName);
204
        }
205
        $type = !empty($colInfo['colParams']['type']) ? $colInfo['colParams']['type'] : 'string';
206
        $value = '';
207
        switch ($type) {
208
            case 'autocomplete':
209
                $options = $colInfo['colParams']['options'];
210
                if (isset($options['snippet']) && is_string($options['snippet'])) {
211
                    $snippets = \App::$cur->Ui->getSnippets('autocomplete');
212
                    if (isset($snippets[$options['snippet']])) {
213
                        $value = $snippets[$options['snippet']]['getValueText']($item->$colName, $options['snippetParams']);
214
                    }
215
                }
216
                break;
217
            case 'select':
218
                switch ($colInfo['colParams']['source']) {
219
                    case 'model':
220
                        $sourceValue = '';
221
                        if ($item->$colName) {
222
                            $sourceValue = $colInfo['colParams']['model']::get($item->$colName);
223
                        }
224
                        $value = $sourceValue ? $sourceValue->name() : 'Не задано';
225
                        break;
226
                    case 'array':
227
                        $value = !empty($colInfo['colParams']['sourceArray'][$item->$colName]) ? $colInfo['colParams']['sourceArray'][$item->$colName] : 'Не задано';
228
                        if (is_array($value) && $value['text']) {
229
                            $value = $value['text'];
230
                        }
231
                        break;
232
                    case 'bool':
233
                        return $item->$colName ? 'Да' : 'Нет';
234
                    case 'method':
235
                        if (!empty($colInfo['colParams']['params'])) {
236
                            $values = call_user_func_array([App::$cur->{$colInfo['colParams']['module']}, $colInfo['colParams']['method']],
237
                                $colInfo['colParams']['params'] + [$item]
238
                            );
239
                        } else {
240
                            $values = \App::$cur->{$colInfo['colParams']['module']}->{$colInfo['colParams']['method']}($item);
241
                        }
242
                        $value = !empty($values[$item->$colName]) ? $values[$item->$colName] : 'Не задано';
243
                        break;
244
                    case 'void':
245
                        if (!empty($modelName::$cols[$colName]['value']['type']) && $modelName::$cols[$colName]['value']['type'] == 'moduleMethod') {
0 ignored issues
show
The property cols does not exist on string.
Loading history...
246
                            return \App::$cur->{$modelName::$cols[$colName]['value']['module']}->{$modelName::$cols[$colName]['value']['method']}($item, $colName, $modelName::$cols[$colName]);
247
                        }
248
                        break;
249
                    case 'relation':
250
                        if (strpos($colInfo['colParams']['relation'], ':')) {
251
                            $relationPath = explode(':', $colInfo['colParams']['relation']);
252
                            $relationName = array_pop($relationPath);
253
                            $curItem = $item;
254
                            foreach ($relationPath as $path) {
255
                                $curItem = $curItem->$path;
256
                            }
257
                            $itemModel = get_class($curItem);
258
                            $relation = $itemModel::getRelation($relationName);
259
                            $relModel = $relation['model'];
260
                        } else {
261
                            $itemModel = get_class($item);
262
                            $relation = $itemModel::getRelation($colInfo['colParams']['relation']);
263
                            $relModel = $relation['model'];
264
                        }
265
                        $relValue = $relModel::get($item->$colName);
266
                        $relModel = strpos($relModel, '\\') === 0 ? substr($relModel, 1) : $relModel;
267
                        if ($manageHref) {
268
                            $value = $relValue ? "<a href='/admin/" . str_replace('\\', '/view/', $relModel) . "/" . $relValue->pk() . "'>" . $relValue->name() . "</a>" : 'Не задано';
269
                        } else {
270
                            $value = $relValue ? $relValue->name() : 'Не задано';
271
                        }
272
                        break;
273
                }
274
                break;
275
            case 'image':
276
                $file = Files\File::get($item->$colName);
277
                if ($file) {
278
                    $photoId = Tools::randomString();
279
                    $value = '<a href = "' . $file->path . '" id="' . $photoId . '" rel="fgall[allimg]"><img src="' . $file->path . '?resize=60x120" /></a>';
280
                    $value .= '<script>inji.onLoad(function(){$("[rel]").fancybox();});</script>';
281
                } else {
282
                    $value = '<img src="/static/system/images/no-image.png?resize=60x120" />';
283
                }
284
                break;
285
            case 'file':
286
                $file = Files\File::get($item->$colName);
287
                if ($file) {
288
                    $value = '<a href="' . $file->path . '">' . $file->name . '.' . $file->type->ext . '</a>';
289
                } else {
290
                    $value = 'Файл не загружен';
291
                }
292
                break;
293
            case 'bool':
294
                $value = $item->$colName ? 'Да' : 'Нет';
295
                break;
296
            case 'void':
297
                if (!empty($colInfo['colParams']['value']['type']) && $colInfo['colParams']['value']['type'] == 'moduleMethod') {
298
                    return \App::$cur->{$colInfo['colParams']['value']['module']}->{$colInfo['colParams']['value']['method']}($item, $colName, $colInfo['colParams']);
299
                }
300
                break;
301
            case 'map':
302
                if ($item->$colName && json_decode($item->$colName, true)) {
303
                    $addres = json_decode($item->$colName, true);
304
                    $name = $addres['address'] ? $addres['address'] : 'lat:' . $addres['lat'] . ': lng:' . $addres['lng'];
305
                    \App::$cur->libs->loadLib('yandexMap');
306
                    ob_start();
307
                    $uid = Tools::randomString();
308
                    ?>
309
                    <div id='map<?= $uid; ?>_container' style="display:none;">
310
                        <script>/*
311
                             <div id='map<?= $uid; ?>' style="width: 100%; height: 500px"></div>
312
                             <script>
313
                             var myMap<?= $uid; ?>;
314
                             var myMap<?= $uid; ?>CurPin;
315
                             inji.onLoad(function () {
316
                             ymaps.ready(init<?= $uid; ?>);
317
                             function init<?= $uid; ?>() {
318
                             var myPlacemark;
319
                             myMap<?= $uid; ?> = new ymaps.Map("map<?= $uid; ?>", {
320
                             center: ["<?= $addres['lat'] ?>", "<?= $addres['lng']; ?>"],
321
                             zoom: 13
322
                             });
323
                             myCoords = ["<?= $addres['lat'] ?>", "<?= $addres['lng']; ?>"];
324
                             myMap<?= $uid; ?>CurPin = new ymaps.Placemark(myCoords,
325
                             {iconContent: "<?= $addres['address']; ?>"},
326
                             {preset: 'islands#greenStretchyIcon'}
327
                             );
328
                             myMap<?= $uid; ?>.geoObjects.add(myMap<?= $uid; ?>CurPin, 0);
329
                             }
330
                             window['init<?= $uid; ?>'] = init<?= $uid; ?>;
331
                             });
332
                             */</script>
333
                    </div>
334
                    <?php
335
                    $content = ob_get_contents();
336
                    ob_end_clean();
337
                    $onclick = 'inji.Ui.modals.show("' . addcslashes($addres['address'], '"') . '", $("#map' . $uid . '_container script").html().replace(/^\/\*/g, "").replace(/\*\/$/g, "")+"</script>","mapmodal' . $uid . '","modal-lg");';
338
                    $onclick .= 'return false;';
339
                    $value = "<a href ='#' onclick='{$onclick}' >{$name}</a>";
340
                    $value .= $content;
341
                } else {
342
                    $value = 'Местоположение не заданно';
343
                }
344
345
                break;
346
            case 'dynamicType':
347
                switch ($colInfo['colParams']['typeSource']) {
348
                    case 'selfMethod':
349
                        $type = $item->{$colInfo['colParams']['selfMethod']}();
350
                        if (is_array($type)) {
351
                            $value = static::resloveTypeValue($item, $colName, $manageHref, ['colParams' => $type]);
352
                        } else {
353
                            $value = static::resloveTypeValue($item, $colName, $manageHref, ['colParams' => ['type' => $type]]);
354
                        }
355
                        break;
356
                }
357
                break;
358
            default:
359
                $value = $item->$colName;
360
        }
361
        return $value;
362
    }
363
364
    /**
365
     * Fix col prefix
366
     *
367
     * @param mixed $array
368
     * @param string $searchtype
369
     * @param string $rootModel
370
     * @return null
371
     */
372
    public static function fixPrefix(&$array, $searchtype = 'key', $rootModel = '') {
373
        if (!$rootModel) {
374
            $rootModel = get_called_class();
375
        }
376
        $cols = static::cols();
377
        if (!$array) {
378
            return;
379
        }
380
        if (!is_array($array)) {
381
            if (!isset($cols[static::colPrefix() . $array]) && isset(static::$cols[$array])) {
382
                static::createCol($array);
383
                $cols = static::cols();
384
            }
385
            if (!isset($cols[$array]) && isset($cols[static::colPrefix() . $array])) {
386
                $array = static::colPrefix() . $array;
387
            } else {
388
                static::checkForJoin($array, $rootModel);
389
            }
390
            return;
391
        }
392
        switch ($searchtype) {
393
            case 'key':
394
                foreach ($array as $key => $item) {
395
                    if (!isset($cols[static::colPrefix() . $key]) && isset(static::$cols[$key])) {
396
                        static::createCol($key);
397
                        $cols = static::cols(true);
398
                    }
399
                    if (!isset($cols[$key]) && isset($cols[static::colPrefix() . $key])) {
400
                        $array[static::colPrefix() . $key] = $item;
401
                        unset($array[$key]);
402
                        $key = static::colPrefix() . $key;
403
                    }
404
                    if (is_array($array[$key])) {
405
                        static::fixPrefix($array[$key], 'key', $rootModel);
406
                    } else {
407
                        static::checkForJoin($key, $rootModel);
408
                    }
409
                }
410
                break;
411
            case 'first':
412
                if (isset($array[0]) && is_string($array[0])) {
413
                    if (!isset($cols[static::colPrefix() . $array[0]]) && isset(static::$cols[$array[0]])) {
414
                        static::createCol($array[0]);
415
                        $cols = static::cols();
416
                    }
417
                    if (!isset($cols[$array[0]]) && isset($cols[static::colPrefix() . $array[0]])) {
418
                        $array[0] = static::colPrefix() . $array[0];
419
                    } else {
420
                        static::checkForJoin($array[0], $rootModel);
421
                    }
422
                } elseif (isset($array[0]) && is_array($array[0])) {
423
                    foreach ($array as &$item) {
424
                        static::fixPrefix($item, 'first', $rootModel);
425
                    }
426
                }
427
                break;
428
        }
429
    }
430
431
    /**
432
     * @param boolean $new
433
     */
434 3
    public function logChanges($new) {
435 3
        if (!App::$cur->db->connect || !App::$cur->dashboard) {
0 ignored issues
show
Bug Best Practice introduced by
The property dashboard does not exist on Inji\App. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property db does not exist on Inji\App. Since you implemented __get, consider adding a @property annotation.
Loading history...
436 3
            return false;
437
        }
438
        $class = get_class($this);
439
        if (!Model::$logging || !$class::$logging || (is_string($class::$logging) && $class::$logging != ($new ? 'new' : 'changes'))) {
0 ignored issues
show
The property logging does not exist on string.
Loading history...
440
            return false;
441
        }
442
        $user_id = class_exists('Users\User') ? \Users\User::$cur->id : 0;
443
        if (!$new && !empty($this->_changedParams)) {
444
            $activity = new Dashboard\Activity([
445
                'user_id' => $user_id,
446
                'module' => substr($class, 0, strpos($class, '\\')),
447
                'model' => $class,
448
                'item_id' => $this->pk(),
449
                'type' => 'changes'
450
            ]);
451
            $changes_text = [];
452
            foreach ($this->_changedParams as $fullColName => $oldValue) {
453
                $colName = substr($fullColName, strlen($class::colPrefix()));
454
                if (isset($class::$cols[$colName]['logging']) && $class::$cols[$colName]['logging'] === false) {
0 ignored issues
show
The property cols does not exist on string.
Loading history...
455
                    continue;
456
                }
457
                if (!isset($class::$cols[$colName]['logging']) || $class::$cols[$colName]['logging'] !== 'noValue') {
458
                    $oldValueText = $oldValue;
459
                    if (isset($class::$cols[$colName]) && $class::$cols[$colName]['type'] === 'select') {
460
                        switch ($class::$cols[$colName]['source']) {
461
                            case 'array':
462
                                $oldValueText = isset($class::$cols[$colName]['sourceArray'][$oldValue]) ? $class::$cols[$colName]['sourceArray'][$oldValue] : $oldValue;
463
                                break;
464
                            case 'relation':
465
                                $relation = $class::getRelation($class::$cols[$colName]['relation']);
466
                                $relModel = $relation['model'];
467
                                $rel = $relModel::get($oldValue);
468
                                if ($rel) {
469
                                    $oldValueText = $rel->name();
470
                                }
471
                        }
472
                    }
473
                    $newValueText = $this->$colName;
474
                    if (isset($class::$cols[$colName]) && $class::$cols[$colName]['type'] === 'select') {
475
                        switch ($class::$cols[$colName]['source']) {
476
                            case 'array':
477
                                $newValueText = isset($class::$cols[$colName]['sourceArray'][$this->$colName]) ? $class::$cols[$colName]['sourceArray'][$this->$colName] : $this->$colName;
478
                                break;
479
                            case 'relation':
480
                                $relation = $class::getRelation($class::$cols[$colName]['relation']);
481
                                $relModel = $relation['model'];
482
                                $rel = $relModel::get($this->$colName);
483
                                if ($rel) {
484
                                    $newValueText = $rel->name();
485
                                }
486
                        }
487
                    }
488
                }
489
                if ((!isset($class::$cols[$colName]['logging']) || $class::$cols[$colName]['logging'] !== 'noValue') && strlen($oldValueText) + strlen($newValueText) < 200) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $newValueText does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $oldValueText does not seem to be defined for all execution paths leading up to this point.
Loading history...
490
                    $changes_text[] = (!empty($class::$labels[$colName]) ? $class::$labels[$colName] : $colName) . ": \"{$oldValueText}\" => \"{$newValueText}\"";
0 ignored issues
show
The property labels does not exist on string.
Loading history...
491
                } else {
492
                    $changes_text[] = !empty($class::$labels[$colName]) ? $class::$labels[$colName] : $colName;
493
                }
494
            }
495
            if (!$changes_text) {
496
                return false;
497
            }
498
            $activity->changes_text = implode(', ', $changes_text);
499
            $activity->save();
500
            foreach ($this->_changedParams as $fullColName => $oldValue) {
501
                $colName = substr($fullColName, strlen($class::colPrefix()));
502
                if (isset($class::$cols[$colName]['logging']) && !$class::$cols[$colName]['logging']) {
503
                    continue;
504
                }
505
                $colName = substr($fullColName, strlen($class::colPrefix()));
506
                $change = new Dashboard\Activity\Change([
0 ignored issues
show
The type Inji\Dashboard\Activity\Change was not found. Did you mean Dashboard\Activity\Change? If so, make sure to prefix the type with \.
Loading history...
507
                    'activity_id' => $activity->id,
508
                    'col' => $colName,
509
                    'old' => $oldValue,
510
                    'new' => $this->$colName
511
                ]);
512
                $change->save();
513
            }
514
        } elseif ($new) {
515
            $activity = new Dashboard\Activity([
516
                'user_id' => $user_id,
517
                'module' => substr($class, 0, strpos($class, '\\')),
518
                'model' => $class,
519
                'item_id' => $this->pk(),
520
                'type' => 'new'
521
            ]);
522
            $activity->save();
523
        }
524
        return true;
525
    }
526
527
    /**
528
     * Check model relations path and load need relations
529
     *
530
     * @param string $col
531
     * @param string $rootModel
532
     */
533
    public static function checkForJoin(&$col, $rootModel) {
534
535
        if (strpos($col, ':') !== false) {
536
            $relations = static::relations();
537
            if (isset($relations[substr($col, 0, strpos($col, ':'))])) {
538
                $rel = substr($col, 0, strpos($col, ':'));
539
                $col = substr($col, strpos($col, ':') + 1);
540
                $type = empty($relations[$rel]['type']) ? 'to' : $relations[$rel]['type'];
541
                $joinName = $relations[$rel]['model'] . '_' . $rel;
542
                switch ($type) {
543
                    case 'to':
544
                        $relCol = $relations[$rel]['col'];
545
                        static::fixPrefix($relCol);
546
                        $rootModel::$relJoins[$joinName] = [$relations[$rel]['model']::table(), $relations[$rel]['model']::index() . ' = ' . $relCol, 'left', ''];
0 ignored issues
show
The property relJoins does not exist on string.
Loading history...
547
                        break;
548
                    case 'one':
549
                    case 'many':
550
                        $relCol = $relations[$rel]['col'];
551
                        $relations[$rel]['model']::fixPrefix($relCol);
552
                        $rootModel::$relJoins[$joinName] = [$relations[$rel]['model']::table(), static::index() . ' = ' . $relCol, 'left', ''];
553
                        break;
554
                    case 'relModel':
555
                        $relation = $relations[$rel];
556
                        $fixedCol = $relation['model']::index();
557
                        $relation['relModel']::fixPrefix($fixedCol);
558
                        $joinName = $relations[$rel]['relModel'] . '_' . $rel;
559
                        $rootModel::$relJoins[$joinName] = [$relation['relModel']::table(), $relation['relModel']::colPrefix() . static::index() . ' = ' . static::index(), 'INNER'];
560
                        $joinName = $relations[$rel]['model'] . '_' . $rel;
561
                        $rootModel::$relJoins[$joinName] = [$relation['model']::table(), $relation['relModel']::colPrefix() . $relation['model']::index() . ' = ' . $relation['model']::index(), 'INNER'];
562
                        //$rootModel::$relJoins[$joinName] = [$relations[$rel]['model']::table(), static::index() . ' = ' . $relCol, 'left', ''];
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
563
                        break;
564
                }
565
                $relations[$rel]['model']::fixPrefix($col, 'key', $rootModel);
566
            }
567
        }
568
    }
569
570
    /**
571
     * Return full col information
572
     *
573
     * @param string $col
574
     * @return array
575
     */
576
    public static function getColInfo($col) {
577
        return static::parseColRecursion($col);
578
    }
579
580
    /**
581
     * Information extractor for col relations path
582
     *
583
     * @param string $info
584
     * @return array
585
     */
586
    public static function parseColRecursion($info) {
587
        if (is_string($info)) {
588
            $info = ['col' => $info, 'rawCol' => $info, 'rawModel' => get_called_class(), 'modelName' => get_called_class(), 'label' => $info, 'joins' => []];
589
        }
590
        if ($info['col'] === 'id') {
591
            $info['colParams'] = [
592
                'type' => 'number',
593
            ];
594
            return $info;
595
        }
596
        if (strpos($info['col'], ':') !== false) {
597
            $relations = static::relations();
598
            if (isset($relations[substr($info['col'], 0, strpos($info['col'], ':'))])) {
599
                $rel = substr($info['col'], 0, strpos($info['col'], ':'));
600
                $info['col'] = substr($info['col'], strpos($info['col'], ':') + 1);
601
                //$info['modelName'] = $relations[$rel]['model'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
602
                $type = empty($relations[$rel]['type']) ? 'to' : $relations[$rel]['type'];
603
                $joinName = $relations[$rel]['model'] . '_' . $rel;
604
                switch ($type) {
605
                    case 'to':
606
                        $relCol = $relations[$rel]['col'];
607
                        static::fixPrefix($relCol);
608
                        $info['joins'][$joinName] = [$relations[$rel]['model']::table(), $relations[$rel]['model']::index() . ' = ' . $relCol];
609
                        break;
610
                    case 'one':
611
                        $relCol = $relations[$rel]['col'];
612
                        $relations[$rel]['model']::fixPrefix($relCol);
613
                        $info['joins'][$joinName] = [$relations[$rel]['model']::table(), static::index() . ' = ' . $relCol];
614
                        break;
615
                }
616
                $info = $relations[$rel]['model']::parseColRecursion($info);
617
            }
618
        } else {
619
            $cols = static::cols();
620
            if (!empty(static::$labels[$info['col']])) {
621
                $info['label'] = static::$labels[$info['col']];
622
            }
623
624
            if (isset(static::$cols[$info['col']])) {
625
                $info['colParams'] = static::$cols[$info['col']];
626
            } elseif (isset(static::$cols[str_replace(static::colPrefix(), '', $info['col'])])) {
627
                $info['colParams'] = static::$cols[str_replace(static::colPrefix(), '', $info['col'])];
628
            } else {
629
                $info['colParams'] = [];
630
            }
631
            if (!isset($cols[$info['col']]) && isset($cols[static::colPrefix() . $info['col']])) {
632
                $info['col'] = static::colPrefix() . $info['col'];
633
            }
634
        }
635
        if (!empty(static::$labels[$info['rawCol']])) {
636
            $info['label'] = static::$labels[$info['rawCol']];
637
        }
638
        return $info;
639
    }
640
641
    /**
642
     * Return actual cols from data base
643
     *
644
     * @param boolean $refresh
645
     * @return array
646
     */
647
    public static function cols($refresh = false) {
648
        if (static::$storage['type'] == 'moduleConfig') {
649
            return [];
650
        }
651
        if (empty(\Inji\Model::$cols[static::table()]) || $refresh) {
652
            \Inji\Model::$cols[static::table()] = App::$cur->db->getTableCols(static::table());
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Inji\App. Since you implemented __get, consider adding a @property annotation.
Loading history...
The method getTableCols() does not exist on Inji\Module. It seems like you code against a sub-type of Inji\Module such as Inji\Db. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

652
            /** @scrutinizer ignore-call */ 
653
            \Inji\Model::$cols[static::table()] = App::$cur->db->getTableCols(static::table());
Loading history...
The method getTableCols() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

652
            /** @scrutinizer ignore-call */ 
653
            \Inji\Model::$cols[static::table()] = App::$cur->db->getTableCols(static::table());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
653
        }
654
        if (!isset(\Inji\Model::$cols[static::table()])) {
655
            static::createTable();
656
            \Inji\Model::$cols[static::table()] = App::$cur->db->getTableCols(static::table());
657
        }
658
        return \Inji\Model::$cols[static::table()];
659
    }
660
661
    /**
662
     * Return cols indexes for create tables
663
     *
664
     * @return array
665
     */
666
    public static function indexes() {
667
        return [];
668
    }
669
670
    /**
671
     * Generate params string for col by name
672
     *
673
     * @param string $colName
674
     * @return false|string
675
     */
676
    public static function genColParams($colName) {
677
        if (empty(static::$cols[$colName]) || static::$storage['type'] == 'moduleConfig') {
678
            return false;
679
        }
680
        $null = ' NULL';
681
        if (empty(static::$cols[$colName]['null'])) {
682
            $null = ' NOT NULL';
683
        }
684
685
        $params = false;
686
        switch (static::$cols[$colName]['type']) {
687
            case 'select':
688
                switch (static::$cols[$colName]['source']) {
689
                    case 'relation':
690
                        $params = 'int(11) UNSIGNED' . $null;
691
                        break;
692
                    default:
693
                        $params = 'varchar(255)' . $null;
694
                }
695
                break;
696
            case 'image':
697
            case 'file':
698
                $params = 'int(11) UNSIGNED' . $null;
699
                break;
700
            case 'number':
701
                $params = 'int(11)' . $null;
702
                break;
703
            case 'text':
704
            case 'email':
705
                $params = 'varchar(255)' . $null;
706
                break;
707
            case 'html':
708
            case 'textarea':
709
            case 'json':
710
            case 'password':
711
            case 'dynamicType':
712
            case 'map':
713
                $params = 'text' . $null;
714
                break;
715
            case 'bool':
716
                $params = 'tinyint(1) UNSIGNED' . $null;
717
                break;
718
            case 'decimal':
719
                $params = 'decimal(8, 2)' . $null;
720
                break;
721
            case 'time':
722
                $params = 'time' . $null;
723
                break;
724
            case 'date':
725
                $params = 'date' . $null;
726
                break;
727
            case 'dateTime':
728
                $params = 'timestamp' . $null;
729
                break;
730
        }
731
        return $params;
732
    }
733
734
    /**
735
     * Create new col in data base
736
     *
737
     * @param string $colName
738
     * @return boolean|integer
739
     */
740
    public static function createCol($colName) {
741
        $cols = static::cols();
742
        if (!empty($cols[static::colPrefix() . $colName])) {
743
            return true;
744
        }
745
        $params = static::genColParams($colName);
746
        if ($params === false) {
747
            return false;
748
        }
749
        $result = App::$cur->db->addCol(static::table(), static::colPrefix() . $colName, $params);
0 ignored issues
show
The method addCol() does not exist on Inji\Module. It seems like you code against a sub-type of Inji\Module such as Inji\Db. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

749
        /** @scrutinizer ignore-call */ 
750
        $result = App::$cur->db->addCol(static::table(), static::colPrefix() . $colName, $params);
Loading history...
Bug Best Practice introduced by
The property db does not exist on Inji\App. Since you implemented __get, consider adding a @property annotation.
Loading history...
750
        static::cols(true);
751
        return $result;
752
    }
753
754
    public static function createTable() {
755
        if (static::$storage['type'] == 'moduleConfig') {
756
            return true;
757
        }
758
        if (!App::$cur->db) {
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Inji\App. Since you implemented __get, consider adding a @property annotation.
Loading history...
759
            return false;
760
        }
761
762
        $query = App::$cur->db->newQuery();
0 ignored issues
show
The method newQuery() does not exist on Inji\Module. It seems like you code against a sub-type of Inji\Module such as Inji\Db. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

762
        /** @scrutinizer ignore-call */ 
763
        $query = App::$cur->db->newQuery();
Loading history...
763
        if (!$query) {
764
            return false;
765
        }
766
767
        if (!isset($this)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $this seems to never exist and therefore isset should always be false.
Loading history...
768
            $tableName = static::table();
769
            $colPrefix = static::colPrefix();
770
            $indexes = static::indexes();
771
        } else {
772
            $tableName = $this->table();
773
            $colPrefix = $this->colPrefix();
774
            $indexes = $this->indexes();
775
        }
776
        if (App::$cur->db->tableExist($tableName)) {
0 ignored issues
show
The method tableExist() does not exist on Inji\Module. It seems like you code against a sub-type of Inji\Module such as Inji\Db. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

776
        if (App::$cur->db->/** @scrutinizer ignore-call */ tableExist($tableName)) {
Loading history...
777
            return true;
778
        }
779
        $cols = [
780
            $colPrefix . 'id' => 'pk'
781
        ];
782
        $className = get_called_class();
783
        if (!empty($className::$cols)) {
0 ignored issues
show
The property cols does not exist on string.
Loading history...
784
            foreach ($className::$cols as $colName => $colParams) {
785
                if ($colName == 'date_create') {
786
                    $cols[$colPrefix . 'date_create'] = 'timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP';
787
                    continue;
788
                }
789
                $params = $className::genColParams($colName);
790
                if ($params) {
791
                    $cols[$colPrefix . $colName] = $params;
792
                }
793
            }
794
        }
795
        if (empty($cols[$colPrefix . 'date_create'])) {
796
            $cols[$colPrefix . 'date_create'] = 'timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP';
797
        }
798
        $tableIndexes = [];
799
        if ($indexes) {
800
            foreach ($indexes as $indexName => $index) {
801
                $tableIndexes[] = $index['type'] . ' ' . App::$cur->db->table_prefix . $indexName . ' (' . implode(',', $index['cols']) . ')';
0 ignored issues
show
The property table_prefix does not seem to exist on Inji\Module.
Loading history...
802
            }
803
        }
804
805
        $query->createTable($tableName, $cols, $tableIndexes);
806
        return true;
807
    }
808
809
    /**
810
     * Return table name
811
     *
812
     * @return string
813
     */
814 4
    public static function table() {
815 4
        return strtolower(str_replace('\\', '_', get_called_class()));
816
    }
817
818
    /**
819
     * Return table index col name
820
     *
821
     * @return string
822
     */
823
    public static function index() {
824
        return 'id';
825
    }
826
827
    /**
828
     * Return col prefix
829
     *
830
     * @return string
831
     */
832 4
    public static function colPrefix() {
833 4
        $classPath = explode('\\', get_called_class());
834 4
        $classPath = array_slice($classPath, 2);
835 4
        return strtolower(implode('_', $classPath)) . '_';
836
    }
837
838
    /**
839
     * return relations list
840
     *
841
     * @return array
842
     */
843 1
    public static function relations() {
844 1
        return [];
845
    }
846
847
    /**
848
     * views list
849
     *
850
     * @return array
851
     */
852
    public static $views = [];
853
854
    /**
855
     * Return name of col with object name
856
     *
857
     * @return string
858
     */
859
    public static function nameCol() {
860
        return 'name';
861
    }
862
863
    /**
864
     * Return object name
865
     *
866
     * @return string
867
     */
868
    public function name() {
869
        return $this->{$this->nameCol()} ? $this->{$this->nameCol()} : '№' . $this->pk();
870
    }
871
872
    /**
873
     * Get single object from data base
874
     *
875
     * @param mixed $param
876
     * @param string $col
877
     * @param array $options
878
     * @return boolean|static
879
     */
880
    public static function get($param, $col = null, $options = []) {
881
        $builder = new Builder(get_called_class());
882
        foreach ($options as $optionName => $option) {
883
            $builder->$optionName($option);
884
        }
885
        if (is_array($param)) {
886
            $builder->where($param);
887
        } else {
888
            $builder->where($col === null ? static::index() : $col, $param);
889
        }
890
        return $builder->get();
891
        if (static::$storage['type'] == 'moduleConfig') {
0 ignored issues
show
IfNode is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
892
            return static::getFromModuleStorage($param, $col, $options);
893
        }
894
        if (!empty($col)) {
895
            static::fixPrefix($col);
896
        }
897
898
        if (is_array($param)) {
899
            static::fixPrefix($param, 'first');
900
        }
901
        $query = App::$cur->db->newQuery();
902
        foreach (static::$relJoins as $join) {
903
            $query->join($join);
904
        }
905
        static::$relJoins = [];
906
        foreach (static::$needJoin as $rel) {
907
            $relations = static::relations();
908
            if (isset($relations[$rel])) {
909
                $type = empty($relations[$rel]['type']) ? 'to' : $relations[$rel]['type'];
910
                switch ($type) {
911
                    case 'to':
912
                        $relCol = $relations[$rel]['col'];
913
                        static::fixPrefix($relCol);
914
                        $query->join($relations[$rel]['model']::table(), $relations[$rel]['model']::index() . ' = ' . $relCol);
915
                        break;
916
                    case 'one':
917
                        $col = $relations[$rel]['col'];
918
                        $relations[$rel]['model']::fixPrefix($col);
919
                        $query->join($relations[$rel]['model']::table(), static::index() . ' = ' . $col);
920
                        break;
921
                }
922
            }
923
        }
924
        static::$needJoin = [];
925
        if (is_array($param)) {
926
            $query->where($param);
927
        } else {
928
            if ($col === null) {
929
930
                $col = static::index();
931
            }
932
            if ($param !== null) {
933
                $cols = static::cols();
934
                if (!isset($cols[$col]) && isset($cols[static::colPrefix() . $col])) {
935
                    $col = static::colPrefix() . $col;
936
                }
937
                $query->where($col, $param);
938
            } else {
939
                return false;
940
            }
941
        }
942
        if (!$query->where) {
943
            return false;
944
        }
945
        try {
946
            $result = $query->select(static::table());
947
        } catch (\PDOException $exc) {
948
            if ($exc->getCode() == '42S02') {
949
                static::createTable();
950
            } else {
951
                throw $exc;
952
            }
953
            $result = $query->select(static::table());
954
        }
955
        if (!$result) {
956
            return false;
957
        }
958
        return $result->fetch(get_called_class());
959
    }
960
961
    /**
962
     * Old method
963
     *
964
     * @param type $options
0 ignored issues
show
The type Inji\type was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
965
     * @return Array
966
     */
967
    public static function get_list($options = [], $debug = false) {
968
        $query = App::$cur->db->newQuery();
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Inji\App. Since you implemented __get, consider adding a @property annotation.
Loading history...
969
        if (!$query) {
970
            return [];
971
        }
972
        if (!empty($options['where'])) {
973
            $query->where($options['where']);
974
        }
975
        if (!empty($options['cols'])) {
976
            $query->cols = $options['cols'];
977
        }
978
        if (!empty($options['group'])) {
979
            $query->group($options['group']);
980
        }
981
        if (!empty($options['having'])) {
982
            $query->having($options['having']);
983
        }
984
        if (!empty($options['order'])) {
985
            $query->order($options['order']);
986
        }
987
        if (!empty($options['join'])) {
988
            $query->join($options['join']);
989
        }
990
        if (!empty($options['distinct'])) {
991
            $query->distinct = $options['distinct'];
992
        }
993
994
        foreach (static::$needJoin as $rel) {
995
            $relations = static::relations();
996
            foreach ($query->join as $item) {
997
                if ($item[0] === $relations[$rel]['model']::table() && $item[3] === '') {
998
                    continue 2;
999
                }
1000
            }
1001
            if (isset($relations[$rel])) {
1002
                $type = empty($relations[$rel]['type']) ? 'to' : $relations[$rel]['type'];
1003
                switch ($type) {
1004
                    case 'to':
1005
                        $relCol = $relations[$rel]['col'];
1006
                        static::fixPrefix($relCol);
1007
                        $query->join($relations[$rel]['model']::table(), $relations[$rel]['model']::index() . ' = ' . $relCol);
1008
                        break;
1009
                    case 'one':
1010
                        $col = $relations[$rel]['col'];
1011
                        $relations[$rel]['model']::fixPrefix($col);
1012
                        $query->join($relations[$rel]['model']::table(), static::index() . ' = ' . $col);
1013
                        break;
1014
                }
1015
            }
1016
        }
1017
        static::$needJoin = [];
1018
1019
        foreach (static::$relJoins as $join) {
1020
            foreach ($query->join as $item) {
1021
                if ($item[0] === $join[0] && $item[3] === $join[3]) {
1022
                    continue 2;
1023
                }
1024
            }
1025
            $query->join($join);
1026
        }
1027
        static::$relJoins = [];
1028
        if (!empty($options['limit'])) {
1029
            $limit = (int)$options['limit'];
1030
        } else {
1031
            $limit = 0;
1032
        }
1033
        if (!empty($options['start'])) {
1034
            $start = (int)$options['start'];
1035
        } else {
1036
            $start = 0;
1037
        }
1038
        if ($limit || $start) {
1039
            $query->limit($start, $limit);
1040
        }
1041
        if (isset($options['key'])) {
1042
            $key = $options['key'];
1043
        } else {
1044
            $key = static::index();
1045
        }
1046
1047
        if ($debug) {
1048
            $query->operation = 'SELECT';
1049
            $query->table = static::table();
1050
            return $query->buildQuery();
1051
        }
1052
        try {
1053
            $query->operation = 'SELECT';
1054
            $query->table = static::table();
1055
            $queryArr = $query->buildQuery();
1056
            $result = $query->query($queryArr);
1057
        } catch (PDOException $exc) {
0 ignored issues
show
The type Inji\PDOException was not found. Did you mean PDOException? If so, make sure to prefix the type with \.
Loading history...
1058
            if ($exc->getCode() == '42S02') {
1059
                static::createTable();
1060
                $result = $query->query($queryArr);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $queryArr does not seem to be defined for all execution paths leading up to this point.
Loading history...
1061
            } else {
1062
                throw $exc;
1063
            }
1064
        }
1065
1066
        if (!empty($options['array'])) {
1067
            static::fixPrefix($key);
1068
            return $result->getArray($key);
1069
        }
1070
        $list = $result->getObjects(get_called_class(), $key);
1071
        if (!empty($options['forSelect'])) {
1072
            $return = [];
1073
            foreach ($list as $key => $item) {
1074
                $return[$key] = $item->name();
1075
            }
1076
            return $return;
1077
        }
1078
        return $list;
1079
    }
1080
1081
    /**
1082
     * Return list of objects from data base
1083
     *
1084
     * @param array $options
1085
     * @return static[]
1086
     */
1087
    public static function getList($options = [], $debug = false) {
1088
        $builder = new Builder(get_called_class());
1089
        foreach ($options as $optionName => $option) {
1090
            $builder->$optionName($option);
1091
        }
1092
        return $builder->getList();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $builder->getList() also could return the type boolean which is incompatible with the documented return type array<mixed,Inji\Model>.
Loading history...
1093
        if (static::$storage['type'] != 'db') {
0 ignored issues
show
IfNode is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1094
            return static::getListFromModuleStorage($options);
1095
        }
1096
        if (!empty($options['where'])) {
1097
            static::fixPrefix($options['where'], 'first');
1098
        }
1099
        if (!empty($options['group'])) {
1100
            static::fixPrefix($options['group'], 'first');
1101
        }
1102
        if (!empty($options['order'])) {
1103
            static::fixPrefix($options['order'], 'first');
1104
        }
1105
        if (!empty($options['having'])) {
1106
            static::fixPrefix($options['having'], 'first');
1107
        }
1108
        return static::get_list($options, $debug);
1109
    }
1110
1111
1112
    /**
1113
     * Return count of records from data base
1114
     *
1115
     * @param array $options
1116
     * @return array|int
1117
     */
1118
    public static function getCount($options = []) {
1119
        $builder = new Builder(get_called_class());
1120
        foreach ($options as $optionName => $option) {
1121
            $builder->$optionName($option);
1122
        }
1123
        $builder->count('*');
1124
        return $builder->get();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $builder->get() returns the type false which is incompatible with the documented return type integer|array.
Loading history...
1125
1126
        foreach (static::$needJoin as $rel) {
0 ignored issues
show
ForeachNode is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1127
            $relations = static::relations();
1128
            foreach ($query->join as $item) {
1129
                if ($item[0] === $relations[$rel]['model']::table() && $item[3] === '') {
1130
                    continue 2;
1131
                }
1132
            }
1133
            if (isset($relations[$rel])) {
1134
                $type = empty($relations[$rel]['type']) ? 'to' : $relations[$rel]['type'];
1135
                switch ($type) {
1136
                    case 'to':
1137
                        $relCol = $relations[$rel]['col'];
1138
                        static::fixPrefix($relCol);
1139
                        $query->join($relations[$rel]['model']::table(), $relations[$rel]['model']::index() . ' = ' . $relCol);
1140
                        break;
1141
                    case 'one':
1142
                        $col = $relations[$rel]['col'];
1143
                        $relations[$rel]['model']::fixPrefix($col);
1144
                        $query->join($relations[$rel]['model']::table(), static::index() . ' = ' . $col);
1145
                        break;
1146
                }
1147
            }
1148
        }
1149
        static::$needJoin = [];
1150
        foreach (static::$relJoins as $join) {
1151
            foreach ($query->join as $item) {
1152
                if ($item[0] === $join[0] && $item[3] === $join[3]) {
1153
                    continue 2;
1154
                }
1155
            }
1156
            $query->join($join);
1157
        }
1158
        static::$relJoins = [];
1159
        $cols = 'COUNT(';
1160
1161
        if (!empty($options['distinct'])) {
1162
            if (is_bool($options['distinct'])) {
1163
                $cols .= 'DISTINCT *';
1164
            } else {
1165
                $cols .= "DISTINCT {$options['distinct']}";
1166
            }
1167
        } else {
1168
            $cols .= '*';
1169
        }
1170
        $cols .= ') as `count`' . (!empty($options['cols']) ? ',' . $options['cols'] : '');
1171
        $query->cols = $cols;
1172
        if (!empty($options['group'])) {
1173
            $query->group($options['group']);
1174
        }
1175
        try {
1176
            $result = $query->select(static::table());
1177
        } catch (PDOException $exc) {
1178
            if ($exc->getCode() == '42S02') {
1179
                static::createTable();
1180
            } else {
1181
                throw $exc;
1182
            }
1183
            $result = $query->select(static::table());
1184
        }
1185
        if (!empty($options['group'])) {
1186
            $count = $result->getArray();
1187
            return $count;
1188
        } else {
1189
            $count = $result->fetch();
1190
            return $count['count'];
1191
        }
1192
    }
1193
1194
    /**
1195
     * Update records in data base
1196
     *
1197
     * @param array $params
1198
     * @param array $where
1199
     * @return false|null
1200
     */
1201
    public static function update($params, $where = []) {
1202
        static::fixPrefix($params);
1203
1204
        $cols = self::cols();
1205
1206
        $values = [];
1207
        foreach ($cols as $col => $param) {
1208
            if (isset($params[$col])) {
1209
                $values[$col] = $params[$col];
1210
            }
1211
        }
1212
        if (empty($values)) {
1213
            return false;
1214
        }
1215
1216
        if (!empty($where)) {
1217
            static::fixPrefix($where, 'first');
1218
1219
            App::$cur->db->where($where);
0 ignored issues
show
The method where() does not exist on Inji\Module. It seems like you code against a sub-type of Inji\Module such as Inji\Db. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1219
            App::$cur->db->/** @scrutinizer ignore-call */ 
1220
                           where($where);
Loading history...
Bug Best Practice introduced by
The property db does not exist on Inji\App. Since you implemented __get, consider adding a @property annotation.
Loading history...
1220
        }
1221
        App::$cur->db->update(static::table(), $values);
0 ignored issues
show
The method update() does not exist on Inji\Module. It seems like you code against a sub-type of Inji\Module such as Inji\Db. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1221
        App::$cur->db->/** @scrutinizer ignore-call */ 
1222
                       update(static::table(), $values);
Loading history...
1222
    }
1223
1224
    /**
1225
     * Return primary key of object
1226
     *
1227
     * @return mixed
1228
     */
1229 4
    public function pk() {
1230 4
        return $this->{$this->index()};
1231
    }
1232
1233
    /**
1234
     * Before save trigger
1235
     */
1236 3
    public function beforeSave() {
1237
1238 3
    }
1239
1240
1241
    /**
1242
     * Update tree path category
1243
     */
1244
    public function changeCategoryTree() {
1245
        $class = get_class($this);
1246
        $itemModel = $class::$treeCategory;
0 ignored issues
show
The property treeCategory does not exist on string.
Loading history...
1247
        $oldPath = $this->tree_path;
1248
        $newPath = $this->getCatalogTree($this);
1249
        $itemsTable = \App::$cur->db->table_prefix . $itemModel::table();
1250
        $itemTreeCol = $itemModel::colPrefix() . 'tree_path';
1251
        $categoryTreeCol = $this->colPrefix() . 'tree_path';
1252
        $categoryTable = \App::$cur->db->table_prefix . $this->table();
1253
        if ($oldPath) {
1254
            \App::$cur->db->query('UPDATE
1255
                ' . $categoryTable . ' 
1256
                    SET 
1257
                        ' . $categoryTreeCol . ' = REPLACE(' . $categoryTreeCol . ', "' . $oldPath . $this->id . '/' . '", "' . $newPath . $this->id . '/' . '") 
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on Inji\Model. Since you implemented __get, consider adding a @property annotation.
Loading history...
1258
                    WHERE ' . $categoryTreeCol . ' LIKE "' . $oldPath . $this->id . '/' . '%"');
1259
1260
            \App::$cur->db->query('UPDATE
1261
                ' . $itemsTable . '
1262
                    SET 
1263
                        ' . $itemTreeCol . ' = REPLACE(' . $itemTreeCol . ', "' . $oldPath . $this->id . '/' . '", "' . $newPath . $this->id . '/' . '") 
1264
                    WHERE ' . $itemTreeCol . ' LIKE "' . $oldPath . $this->id . '/' . '%"');
1265
        }
1266
        $itemModel::update([$itemTreeCol => $newPath . $this->id . '/'], [$itemModel::colPrefix() . $this->index(), $this->id]);
1267
        $this->tree_path = $newPath;
0 ignored issues
show
Bug Best Practice introduced by
The property tree_path does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1268
    }
1269
1270
    /**
1271
     * Return tree path
1272
     *
1273
     * @param \Inji\Model $catalog
1274
     * @return string
1275
     */
1276
    public function getCatalogTree($catalog) {
1277
        $catalogClass = get_class($catalog);
1278
        $catalogParent = $catalogClass::get($catalog->parent_id);
0 ignored issues
show
Bug Best Practice introduced by
The property parent_id does not exist on Inji\Model. Since you implemented __get, consider adding a @property annotation.
Loading history...
1279
        if ($catalog && $catalogParent) {
1280
            if ($catalogParent->tree_path) {
1281
                return $catalogParent->tree_path . $catalogParent->id . '/';
1282
            } else {
1283
                return $this->getCatalogTree($catalogParent) . $catalogParent->id . '/';
1284
            }
1285
        }
1286
        return '/';
1287
    }
1288
1289
    /**
1290
     * Update tree path item
1291
     */
1292
    public function changeItemTree() {
1293
        $class = get_class($this);
1294
        $categoryModel = $class::$categoryModel;
0 ignored issues
show
The property categoryModel does not exist on string.
Loading history...
1295
        $category = $categoryModel::get($this->{$categoryModel::index()});
1296
        if ($category) {
1297
            $this->tree_path = $category->tree_path . $category->pk() . '/';
0 ignored issues
show
Bug Best Practice introduced by
The property tree_path does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1298
        } else {
1299
            $this->tree_path = '/';
1300
        }
1301
    }
1302
1303
    /**
1304
     * Save object to data base
1305
     *
1306
     * @param array $options
1307
     * @return boolean|int
1308
     */
1309 3
    public function save($options = []) {
1310
1311 3
        $builder = new Builder(get_called_class(), $this->app);
1312 3
        $builder->connection($this->connectionName);
1313 3
        foreach ($this->dbOptions as $dbOption => $value) {
1314 3
            $builder->setDbOption($dbOption, $value);
1315
        }
1316 3
        $class = get_called_class();
1317 3
        if (!empty($this->_changedParams) && $this->pk()) {
1318 2
            \Inji::$inst->event('modelItemParamsChanged-' . $class, $this);
1319
        }
1320 3
        $this->beforeSave();
1321 3
        $values = [];
1322
1323 3
        foreach (static::$cols as $col => $param) {
1324 3
            if (isset($this->_params[$col]) && (!$this->pk() || ($this->pk() && isset($this->_changedParams[$col])))) {
1325 3
                $values[$col] = $this->_params[$col];
1326
            }
1327
        }
1328 3
        if (!$this->pk()) {
1329 1
            foreach ($class::$cols as $colName => $params) {
0 ignored issues
show
The property cols does not exist on string.
Loading history...
1330 1
                if (isset($params['default']) && !isset($values[$colName])) {
1331 1
                    $this->_params[$colName] = $values[$colName] = $params['default'];
1332
                }
1333
            }
1334
        }
1335 3
        if (empty($values) && empty($options['empty'])) {
1336
            return false;
1337
        }
1338 3
        if (static::$categoryModel) {
1339
            $this->changeItemTree();
1340
        }
1341 3
        if (static::$treeCategory) {
1342
            $this->changeCategoryTree();
1343
        }
1344 3
        $new = !$this->pk();
1345
1346 3
        if ($new) {
1347 1
            $builder->where(static::index(), $builder->insert($values));
1348
        } else {
1349
1350 2
            $builder->where(static::index(), $this->pk());
1351 2
            $builder->update($values);
1352
        }
1353 3
        $this->logChanges($new);
1354 3
        $this->_params = $builder->get(['array' => true]);
0 ignored issues
show
Documentation Bug introduced by
It seems like $builder->get(array('array' => true)) of type false is incompatible with the declared type array of property $_params.

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

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

Loading history...
1355 3
        if ($new) {
1356 1
            \Inji::$inst->event('modelCreatedItem-' . get_called_class(), $this);
1357
        }
1358 3
        $this->afterSave();
1359 3
        return $this->pk();
1360
    }
1361
1362
    /**
1363
     * After save trigger
1364
     */
1365 3
    public function afterSave() {
1366
1367 3
    }
1368
1369
    /**
1370
     * Before delete trigger
1371
     */
1372 1
    public function beforeDelete() {
1373
1374 1
    }
1375
1376
    /**
1377
     * Delete item from data base
1378
     *
1379
     * @param array $options
1380
     * @return boolean
1381
     */
1382 1
    public function delete($options = []) {
1383 1
        $this->beforeDelete();
1384 1
        if (!empty($this->pk())) {
1385 1
            $builder = new Builder(get_called_class(), $this->app);
1386 1
            $builder->connection($this->connectionName);
1387 1
            foreach ($this->dbOptions as $dbOption => $value) {
1388 1
                $builder->setDbOption($dbOption, $value);
1389
            }
1390 1
            $builder->where($this->index(), $this->pk());
1391 1
            $result = $builder->delete();
1392 1
            if ($result) {
1393 1
                $this->afterDelete();
1394 1
                return $result;
1395
            }
1396
        }
1397
        return false;
1398
    }
1399
1400
    /**
1401
     * Delete items from data base
1402
     *
1403
     * @param array $where
1404
     */
1405
    public static function deleteList($where = []) {
1406
        if (!empty($where)) {
1407
            static::fixPrefix($where, 'first');
1408
            App::$cur->db->where($where);
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist on Inji\App. Since you implemented __get, consider adding a @property annotation.
Loading history...
1409
        }
1410
        App::$cur->db->delete(static::table());
0 ignored issues
show
The method delete() does not exist on Inji\Module. It seems like you code against a sub-type of Inji\Module such as Inji\Db. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1410
        App::$cur->db->/** @scrutinizer ignore-call */ 
1411
                       delete(static::table());
Loading history...
1411
    }
1412
1413
    /**
1414
     * After delete trigger
1415
     */
1416 1
    public function afterDelete() {
1417
1418 1
    }
1419
1420
    /**
1421
     * find relation for col name
1422
     *
1423
     * @param string $col
1424
     * @return array|null
1425
     */
1426
    public static function findRelation($col) {
1427
1428
        foreach (static::relations() as $relName => $rel) {
1429
            if ($rel['col'] == $col) {
1430
                return $relName;
1431
            }
1432
        }
1433
        return null;
1434
    }
1435
1436
    /**
1437
     * Set params for model
1438
     *
1439
     * @param array $params
1440
     */
1441 3
    public function setParams($params) {
1442 3
        foreach ($params as $paramName => $value) {
1443 3
            $this->$paramName = $value;
1444
        }
1445 3
    }
1446
1447
    /**
1448
     * Return relation
1449
     *
1450
     * @param string $relName
1451
     * @return array|boolean
1452
     */
1453 1
    public static function getRelation($relName) {
1454 1
        $relations = static::relations();
1455 1
        return !empty($relations[$relName]) ? $relations[$relName] : false;
1456
    }
1457
1458
    /**
1459
     * Load relation
1460
     *
1461
     * @param string $name
1462
     * @param array $params
1463
     * @return null|array|integer|\Model
1464
     */
1465 1
    public function loadRelation($name, $params = []) {
1466 1
        $relation = static::getRelation($name);
1467 1
        if ($relation) {
1468
            if (!isset($relation['type'])) {
1469
                $type = 'to';
1470
            } else {
1471
                $type = $relation['type'];
1472
            }
1473
            $getCol = null;
1474
            $getParams = [];
1475
            switch ($type) {
1476
                case 'relModel':
1477
                    if (!$this->pk()) {
1478
                        return [];
1479
                    }
1480
                    $fixedCol = $relation['model']::index();
1481
                    $relation['relModel']::fixPrefix($fixedCol);
1482
                    $join = [$relation['relModel']::table(), $relation['relModel']::colPrefix() . $this->index() . ' = ' . $this->pk() . ' and ' . $relation['relModel']::colPrefix() . $relation['model']::index() . ' = ' . $relation['model']::index(), 'INNER'];
1483
                    $getType = 'getList';
1484
                    $options = [
1485
                        'cols' => (isset($params['cols'])) ? $params['cols'] : ((isset($relation['cols'])) ? $relation['cols'] : null),
1486
                        'join' => [$join],
1487
                        'where' => (isset($params['where'])) ? $params['where'] : ((isset($relation['where'])) ? $relation['where'] : null),
1488
                        'array' => (!empty($params['array'])) ? true : false,
1489
                        'key' => (isset($params['key'])) ? $params['key'] : ((isset($relation['resultKey'])) ? $relation['resultKey'] : null),
1490
                        'start' => (isset($params['start'])) ? $params['start'] : ((isset($relation['start'])) ? $relation['start'] : null),
1491
                        'order' => (isset($params['order'])) ? $params['order'] : ((isset($relation['order'])) ? $relation['order'] : null),
1492
                        'limit' => (isset($params['limit'])) ? $params['limit'] : ((isset($relation['limit'])) ? $relation['limit'] : null),
1493
                    ];
1494
                    break;
1495
                case 'many':
1496
                    if (!$this->{$this->index()}) {
1497
                        return [];
1498
                    }
1499
                    $getType = 'getList';
1500
                    $options = [
1501
                        'cols' => (isset($params['cols'])) ? $params['cols'] : ((isset($relation['cols'])) ? $relation['cols'] : null),
1502
                        'join' => (isset($relation['join'])) ? $relation['join'] : null,
1503
                        'key' => (isset($params['key'])) ? $params['key'] : ((isset($relation['resultKey'])) ? $relation['resultKey'] : null),
1504
                        'array' => (!empty($params['array'])) ? true : false,
1505
                        'forSelect' => !empty($params['forSelect']),
1506
                        'order' => (isset($params['order'])) ? $params['order'] : ((isset($relation['order'])) ? $relation['order'] : null),
1507
                        'start' => (isset($params['start'])) ? $params['start'] : ((isset($relation['start'])) ? $relation['start'] : null),
1508
                        'limit' => (isset($params['limit'])) ? $params['limit'] : ((isset($relation['limit'])) ? $relation['limit'] : null),
1509
                        'where' => []
1510
                    ];
1511
                    $options['where'][] = [$relation['col'], $this->{$this->index()}];
1512
                    if (!empty($relation['where'])) {
1513
                        $options['where'] = array_merge($options['where'], [$relation['where']]);
1514
                    }
1515
                    if (!empty($params['where'])) {
1516
                        $options['where'] = array_merge($options['where'], [$params['where']]);
1517
                    }
1518
                    break;
1519
                case 'one':
1520
                    $getType = 'get';
1521
                    $options = [$relation['col'], $this->pk()];
1522
                    break;
1523
                default:
1524
                    if ($this->{$relation['col']} === null) {
1525
                        return null;
1526
                    }
1527
                    $getType = 'get';
1528
                    $options = $this->{$relation['col']};
1529
            }
1530
            if (!empty($params['count'])) {
1531
                if (class_exists($relation['model'])) {
1532
                    return $relation['model']::getCount($options);
1533
                }
1534
                return 0;
1535
            } else {
1536
                if (class_exists($relation['model'])) {
1537
                    $this->loadedRelations[$name][json_encode($params)] = $relation['model']::$getType($options, $getCol, $getParams);
1538
                } else {
1539
                    $this->loadedRelations[$name][json_encode($params)] = [];
1540
                }
1541
            }
1542
            return $this->loadedRelations[$name][json_encode($params)];
1543
        }
1544 1
        return null;
1545
    }
1546
1547
    /**
1548
     * Add relation item
1549
     *
1550
     * @param string $relName
1551
     * @param \Inji\Model $objectId
1552
     * @return \Inji\Model|boolean
1553
     */
1554
    public function addRelation($relName, $objectId) {
1555
        $relation = $this->getRelation($relName);
1556
        if ($relation) {
1557
            $rel = $relation['relModel']::get([[$relation['model']::index(), $objectId], [$this->index(), $this->pk()]]);
1558
            if (!$rel) {
1559
                $rel = new $relation['relModel']([
1560
                    $relation['model']::index() => $objectId,
1561
                    $this->index() => $this->pk()
1562
                ]);
1563
                $rel->save();
1564
            }
1565
            return $rel;
1566
        }
1567
        return false;
1568
    }
1569
1570
    /**
1571
     * Check user access for form
1572
     *
1573
     * @param string $formName
1574
     * @return boolean
1575
     */
1576
    public function checkFormAccess($formName) {
1577
        if ($formName == 'manage' && !Users\User::$cur->isAdmin()) {
1578
            return false;
1579
        }
1580
        return true;
1581
    }
1582
1583
    /**
1584
     * Check access for model
1585
     *
1586
     * @param string $mode
1587
     * @param \Users\User $user
1588
     * @return boolean
1589
     */
1590
    public function checkAccess($mode = 'write', $user = null) {
1591
        if (!$user) {
1592
            $user = \Users\User::$cur;
1593
        }
1594
        return $user->isAdmin();
1595
    }
1596
1597
    /**
1598
     * Param and relation with params getter
1599
     *
1600
     * @param string $name
1601
     * @param array $params
1602
     * @return \Value|mixed
0 ignored issues
show
The type Value was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1603
     */
1604
    public function __call($name, $params) {
1605
        $fixedName = $name;
1606
        static::fixPrefix($fixedName);
1607
        if (isset($this->_params[$fixedName])) {
1608
            return new Value($this, $fixedName);
1609
        } elseif (isset($this->_params[$name])) {
1610
            return new Value($this, $name);
1611
        } elseif (!empty($params[0]) && isset($this->loadedRelations[$name][json_encode($params[0])])) {
1612
            return $this->loadedRelations[$name][json_encode($params[0])];
1613
        }
1614
        return call_user_func_array([$this, 'loadRelation'], array_merge([$name], $params));
1615
    }
1616
1617
    /**
1618
     * Param and relation getter
1619
     *
1620
     * @param string $name
1621
     * @return mixed
1622
     */
1623 4
    public function __get($name) {
1624 4
        if (isset($this->_params[$name])) {
1625 4
            return $this->_params[$name];
1626
        }
1627 1
        if (isset($this->loadedRelations[$name][json_encode([])])) {
1628
            return $this->loadedRelations[$name][json_encode([])];
1629
        }
1630 1
        return $this->loadRelation($name);
1631
    }
1632
1633
    /**
1634
     * Return model value in object
1635
     *
1636
     * @param string $name
1637
     * @return \Value|null
1638
     */
1639
    public function value($name) {
1640
        $fixedName = $name;
1641
        static::fixPrefix($fixedName);
1642
        if (isset($this->_params[$fixedName])) {
1643
            return new Value($this, $fixedName);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Inji\Value($this, $fixedName) returns the type Inji\Value which is incompatible with the documented return type null|Value.
Loading history...
1644
        } elseif ($this->_params[$name]) {
1645
            return new Value($this, $name);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Inji\Value($this, $name) returns the type Inji\Value which is incompatible with the documented return type null|Value.
Loading history...
1646
        }
1647
        return null;
1648
    }
1649
1650
    /**
1651
     * Return manager filters
1652
     *
1653
     * @return array
1654
     */
1655
    public static function managerFilters() {
1656
        return [];
1657
    }
1658
1659
    /**
1660
     * Return validators for cols
1661
     *
1662
     * @return array
1663
     */
1664
    public static function validators() {
1665
        return [];
1666
    }
1667
1668
    /**
1669
     * Return validator by name
1670
     *
1671
     * @param string $name
1672
     * @return array
1673
     */
1674
    public static function validator($name) {
1675
        $validators = static::validators();
1676
        if (!empty($validators[$name])) {
1677
            return $validators[$name];
1678
        }
1679
        return [];
1680
    }
1681
1682
    public function genViewLink() {
1683
        $className = get_class($this);
1684
        $link = substr($className, 0, strpos($className, '\\'));
1685
        $link .= '/view/';
1686
        $link .= str_replace('\\', '%5C', substr($className, strpos($className, '\\') + 1));
1687
        $link .= "/{$this->id}";
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on Inji\Model. Since you implemented __get, consider adding a @property annotation.
Loading history...
1688
        return $link;
1689
    }
1690
1691
    public function extract($model) {
1692
        $params = [];
1693
        if (empty($this->_params[$model::index()])) {
1694
            return false;
1695
        }
1696
        $params['id'] = $this->_params[$model::index()];
1697
        $indexes = array_keys($this->_params);
1698
        foreach ($model::$cols as $colName => $colParams) {
1699
            if (in_array($model::colPrefix() . $colName, $indexes)) {
1700
                $params[$model::colPrefix() . $colName] = $this->_params[$model::colPrefix() . $colName];
1701
            }
1702
        }
1703
        if (!$params) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1704
            return FALSE;
1705
        }
1706
        return new $model($params);
1707
    }
1708
1709
    /**
1710
     * Set handler for model params
1711
     *
1712
     * @param string $name
1713
     * @param mixed $value
1714
     */
1715 3
    public function __set($name, $value) {
1716
        //static::fixPrefix($name);
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1717 3
        $className = get_called_class();
1718 3
        $shortName = $name;
1719 3
        if (!$value && !empty(static::$cols[$shortName]) && in_array('emptyValue', array_keys(static::$cols[$shortName]))) {
1720
            $value = static::$cols[$shortName]['emptyValue'];
1721
        }
1722 3
        if (is_null($value) && empty(static::$cols[$shortName]['null'])) {
1723
            $value = '';
1724
        }
1725 3
        if (!empty($className::$cols[$shortName])) {
0 ignored issues
show
The property cols does not exist on string.
Loading history...
1726 3
            switch ($className::$cols[$shortName]['type']) {
1727
                case 'decimal':
1728
                    $value = (float)$value;
1729
                    break;
1730
                case 'number':
1731
                    $value = (int)$value;
1732
                    break;
1733
                case 'bool':
1734
                    $value = (bool)$value;
1735
                    break;
1736
            }
1737
        }
1738 3
        if (isset($this->_params[$name]) && $this->_params[$name] != $value && !isset($this->_changedParams[$name])) {
1739 1
            $this->_changedParams[$name] = $this->_params[$name];
1740
        }
1741 3
        $this->_params[$name] = $value;
1742 3
    }
1743
1744
    /**
1745
     * Isset handler for model params
1746
     *
1747
     * @param string $name
1748
     * @return boolean
1749
     */
1750
    public function __isset($name) {
1751
        static::fixPrefix($name);
1752
        return isset($this->_params[$name]);
1753
    }
1754
1755
    /**
1756
     * Convert object to string
1757
     *
1758
     * @return string
1759
     */
1760
    public function __toString() {
1761
        return $this->name();
1762
    }
1763
}