File   F
last analyzed

Complexity

Total Complexity 70

Size/Duplication

Total Lines 577
Duplicated Lines 0 %

Importance

Changes 6
Bugs 0 Features 2
Metric Value
wmc 70
eloc 303
c 6
b 0
f 2
dl 0
loc 577
rs 2.8

40 Methods

Rating   Name   Duplication   Size   Complexity  
A setTime_complete_calculated() 0 1 1
A rules() 0 3 1
A getOldStatuse() 0 13 4
A getLogsGrouped() 0 13 1
A isStop() 0 2 1
A triggerDeleting() 0 3 1
A triggerLoaded() 0 4 1
A getCompleteRows() 0 2 1
A getAllowedExtensions() 0 17 1
A detectMimeType() 0 8 2
A triggerErrorRow() 0 3 1
A triggerException() 0 4 1
A getAllowedStatusesList() 0 11 3
A isCancelLoading() 0 2 1
A triggerStop() 0 3 1
A setStatus() 0 3 1
A checkWhatSheetsIsNotEmpty() 0 4 3
A scenarios() 0 9 1
A triggerLoading() 0 8 1
A beforeValidate() 0 7 2
A isComplete() 0 2 1
A isCheckExtension() 0 16 4
B getRows() 0 39 6
A __toString() 0 3 1
A search() 0 27 1
A getLogs() 0 6 1
A triggerSuccessRow() 0 3 1
A getErrorsPercent() 0 7 4
A getTime_complete_calculated() 0 21 4
A logError() 0 5 2
A getCalculatedRowsCount() 0 2 1
A beforeDelete() 0 4 1
A getSettings() 0 2 1
A isLoading() 0 2 1
A isError() 0 2 1
A deleteLogs() 0 12 5
A find() 0 4 1
A getCalculatedSetsCount() 0 2 1
A triggerCompleteRow() 0 6 3
B behaviors() 0 171 1

How to fix   Complexity   

Complex Class

Complex classes like File often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use File, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace execut\import\models;
4
5
use execut\actions\action\adapter\viewRenderer\DynaGridRow;
6
use execut\crudFields\Behavior;
7
use execut\crudFields\BehaviorStub;
8
use execut\crudFields\fields\Date;
9
use execut\crudFields\fields\Field;
10
use execut\crudFields\fields\HasManyMultipleInput;
11
use execut\crudFields\fields\HasOneSelect2;
12
use execut\crudFields\fields\RadiobuttonGroup;
13
use execut\crudFields\ModelsHelperTrait;
14
use execut\import\components\ToArrayConverter;
15
use execut\crudFields\fields\File as FileField;
16
17
use yii\behaviors\TimestampBehavior;
18
use yii\db\Exception;
19
use yii\db\Expression;
20
use yii\db\mysql\Schema;
21
use yii\helpers\ArrayHelper;
22
use yii\helpers\FileHelper;
23
use yii\web\UploadedFile;
24
25
/**
26
 * This is the model class for table "import_files".
27
 *
28
 * @property integer $id
29
 * @property string $created
30
 * @property string $updated
31
 * @property string $name
32
 * @property resource $content
33
 * @property string $md5
34
 * @property string $import_files_source_id
35
 * @property string $use_id
36
 * @property UploadedFile $contentFile
37
 *
38
 * @property \execut\import\models\FilesSource $importFilesSource
39
 * @property \execut\import\models\User $use
40
 */
41
class File extends base\File implements DynaGridRow
42
{
43
    use ModelsHelperTrait, BehaviorStub;
44
45
    const MODEL_NAME = '{n,plural,=0{Files} =1{File} other{Files}}';
46
    public $eventsCount = null;
47
    protected $rows = null;
48
    public $contentFile = null;
49
    public $preview = null;
50
    public $progress = null;
51
52
    public function setTime_complete_calculated() {
53
    }
54
55
56
    /**
57
     * @inheritdoc
58
     */
59
    public function rules()
60
    {
61
        return $this->getBehavior('fields')->rules();
62
    }
63
64
    public function behaviors()
65
    {
66
        return [
67
            'fields' => [
68
                'class' => Behavior::class,
69
                'module' => 'import',
70
                'fields' => $this->getStandardFields(['visible', 'name'], [
71
                    'contentFile' => [
72
                        'class' => FileField::class,
73
                        'attribute' => 'contentFile',
74
                        'md5Attribute' => 'md5',
75
                        'dataAttribute' => 'content',
76
                        'downloadUrl' => [
77
                            '/import/files/download'
78
                        ],
79
                        'allowedExtensions' => $this->getAllowedExtensions(),
80
                    ],
81
                    'import_setting_id' => [
82
                        'class' => HasOneSelect2::class,
83
                        'required' => true,
84
                        'attribute' => 'import_setting_id',
85
                        'relation' => 'setting',
86
                        'url' => [
87
                            '/import/settings'
88
                        ],
89
                    ],
90
                    'import_files_statuse_id' => [
91
                        'class' => RadiobuttonGroup::class,
92
                        'attribute' => 'import_files_statuse_id',
93
                        'relation' => 'statuse',
94
                        'required' => true,
95
                        'defaultValue' => FilesStatuse::getIdByKey(FilesStatuse::NEW),
96
                        'data' => function () {
97
                            return $this->getAllowedStatusesList();
98
                        },
99
                        'rules' => [
100
                            'validateStatus' => ['import_files_statuse_id', 'in', 'range' => function () {
101
                                return array_keys($this->getAllowedStatusesList());
102
                            }],
103
                        ],
104
//                        'rules' => [
105
//                            'defaultValueOnForm' => [
106
//                                'import_files_statuse_id',
107
//                                'default',
108
//                                'value' => FilesStatuse::getIdByKey(FilesStatuse::NEW),
109
//                                'on' => Field::SCENARIO_FORM,
110
//                            ],
111
//                        ],
112
                    ],
113
                    'import_files_source_id' => [
114
                        'class' => HasOneSelect2::class,
115
                        'attribute' => 'import_files_source_id',
116
                        'required' => true,
117
                        'relation' => 'source',
118
//                        'defaultValue' => FilesStatuse::getIdByKey('new'),
119
                    ],
120
                    'rows_count' => [
121
                        'attribute' => 'rows_count',
122
                        'displayOnly' => true,
123
                    ],
124
                    'rows_errors' => [
125
                        'attribute' => 'rows_errors',
126
                        'displayOnly' => true,
127
                    ],
128
                    'rows_success' => [
129
                        'attribute' => 'rows_success',
130
                        'displayOnly' => true,
131
                    ],
132
                    'time_complete_calculated' => [
133
                        'attribute' => 'time_complete_calculated',
134
                        'displayOnly' => true,
135
                        'scope' => false,
136
                        'column' => [
137
                            'filter' => false,
138
//                            'value' => function () {
139
//                                return $this->getTime_complete_calculated();
140
//                            }
141
                        ],
142
                    ],
143
//                    'eventsCount' => [
144
//                        'attribute' => 'eventsCount',
145
//                        'column' => [
146
//                            'filter' => [
147
//                                '0' => '0',
148
//                                '1' => '>0',
149
//                            ],
150
//                        ],
151
//                        'scope' => function ($q, $model) {
152
//                            $q->byEventsCount($model->eventsCount)
153
//                                ->withEventsCount();
154
//                        },
155
//                        'displayOnly' => true,
156
//                    ],
157
                    'progress' => [
158
                        'attribute' => 'progress',
159
                        'field' => [
160
                            'format' => ['percent', 2],
161
                        ],
162
                        'column' => [
163
                            'format' => ['percent', 2],
164
                        ],
165
                        'displayOnly' => true,
166
                        'rules' => false,
167
                    ],
168
                    'errorsPercent' => [
169
                        'attribute' => 'errorsPercent',
170
                        'field' => [
171
                            'format' => ['percent', 2],
172
                        ],
173
                        'column' => [
174
                            'format' => ['percent', 2],
175
                        ],
176
                        'displayOnly' => true,
177
                        'rules' => false,
178
                    ],
179
                    'start_date' => [
180
                        'class' => Date::class,
181
                        'attribute' => 'start_date',
182
                        'isTime' => true,
183
                        'displayOnly' => true,
184
                    ],
185
                    'end_date' => [
186
                        'class' => Date::class,
187
                        'attribute' => 'end_date',
188
                        'isTime' => true,
189
                        'displayOnly' => true,
190
                    ],
191
                    'logsGrouped' => [
192
                        'class' => HasManyMultipleInput::class,
193
                        'order' => 115,
194
                        'columnRecordsLimit' => 10,
195
                        'attribute' => 'logsGrouped',
196
                        'relation' => 'logsGrouped',
197
                        'isGridForOldRecords' => true,
198
                        'scope' => false,
199
                        'column' => false,
200
                        'with' => false,
201
                        'field' => false,
202
                        'fieldGrid' => new HasManyMultipleInput\Grid\Field([
0 ignored issues
show
Bug introduced by eXeCUT
The type execut\crudFields\fields...ultipleInput\Grid\Field 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...
203
                            'responsiveWrap' => false,
204
                            'showPageSummary' => true,
205
                        ]),
206
                    ],
207
                    'logs' => [
208
                        'class' => HasManyMultipleInput::class,
209
                        'order' => 115,
210
                        'columnRecordsLimit' => 10,
211
                        'attribute' => 'logs',
212
                        'relation' => 'logs',
213
                        'with' => false,
214
                        'isGridForOldRecords' => true,
215
                        'scope' => false,
216
                        'column' => false,
217
                        'field' => false,
218
                        'fieldGrid' => new HasManyMultipleInput\Grid\Field([
219
                            'responsiveWrap' => false,
220
                            'showPageSummary' => true,
221
                        ]),
222
                    ],
223
                    'process_id' => [
224
                        'attribute' => 'process_id',
225
                        'displayOnly' => true
226
                    ],
227
                ]),
228
                'plugins' => \yii::$app->getModule('import')->getFilesCrudFieldsPlugins(),
229
            ],
230
            'date' => [
231
                'class' => TimestampBehavior::class,
232
                'createdAtAttribute' => 'created',
233
                'updatedAtAttribute' => 'updated',
234
                'value' => new Expression('NOW()'),
235
            ],
236
        ];
237
    }
238
239
    public function getOldStatuse() {
240
        if ($attributes = $this->getDirtyAttributes(['import_files_statuse_id'])) {
0 ignored issues
show
Unused Code introduced by eXeCUT
The assignment to $attributes is dead and can be removed.
Loading history...
241
            if (empty($this->oldAttributes['import_files_statuse_id'])) {
242
                return;
243
            }
244
245
            $id = $this->oldAttributes['import_files_statuse_id'];
246
        } else {
247
            $id = $this->import_files_statuse_id;
248
        }
249
250
        if ($id) {
251
            return FilesStatuse::findOne($id);
252
        }
253
    }
254
255
    public function getAllowedStatusesList() {
256
        $q = FilesStatuse::find();
257
        if ($this->scenario === 'form') {
258
            if ($statuse = $this->getOldStatuse()) {
259
                $q->isAllowedForKey($statuse->key);
260
            } else {
261
                $q->byKey(FilesStatuse::NEW);
262
            }
263
        }
264
265
        return $q->forSelect();
266
    }
267
268
    public function getTime_complete_calculated() {
269
        $secondsElapse = false;
270
        if (!$this->isLoading()) {
271
            $startTime = strtotime($this->start_date);
272
            $endTime = strtotime($this->end_date);
273
274
            $secondsElapse = $endTime - $startTime;
275
        } else {
276
277
            $startTime = strtotime($this->start_date);
278
            $currentTime = time();
279
280
            $totalRows = ($this->rows_success + $this->rows_errors);
281
            if ($totalRows > 0) {
282
                $secondsElapse = ($currentTime - $startTime) / $totalRows * $this->rows_count;
283
            }
284
285
        }
286
287
        if ($secondsElapse) {
0 ignored issues
show
Bug Best Practice introduced by eXeCUT
The expression $secondsElapse of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
288
            return date('H:i:s', $startTime + $secondsElapse) . ' (' . gmdate("H:i:s", $secondsElapse) . ')';
289
        }
290
    }
291
292
    public function search() {
293
        $dp = $this->getBehavior('fields')->search();
294
        $q = $dp->query;
295
296
        $select = $this->attributes();
297
        unset($select[array_search('content', $select)]);
298
299
        $sort = $dp->sort;
300
//        $sort->attributes['eventsCount'] = [
301
//            'asc' => ['eventsCount' => SORT_ASC],
302
//            'desc' => ['eventsCount' => SORT_DESC],
303
//        ];
304
305
        $sort->attributes['progress'] = [
306
            'asc' => ['progress' => SORT_ASC],
307
            'desc' => ['progress' => SORT_DESC],
308
        ];
309
310
        $sort->attributes['errorsPercent'] = [
311
            'asc' => ['errorsPercent' => SORT_ASC],
312
            'desc' => ['errorsPercent' => SORT_DESC],
313
        ];
314
315
        unset($q->select[array_search('*', $q->select)]);
316
        $q->select = array_merge($q->select, $select);
317
318
        return $dp;
319
    }
320
321
322
    public function beforeValidate()
323
    {
324
        if (!empty($this->contentFile)) {
325
            $this->name = $this->md5 = $this->extension = $this->mime_type = null;
326
        }
327
328
        return parent::beforeValidate(); // TODO: Change the autogenerated stub
329
    }
330
331
    public function isLoading() {
332
        return $this->import_files_statuse_id === FilesStatuse::getIdByKey(FilesStatuse::LOADING);
333
    }
334
335
    public function isError() {
336
        return $this->import_files_statuse_id === FilesStatuse::getIdByKey(FilesStatuse::ERROR);
337
    }
338
339
    public function isComplete() {
340
        return $this->import_files_statuse_id === FilesStatuse::getIdByKey(FilesStatuse::LOADED);
341
    }
342
343
344
    public function getAllowedExtensions() {
345
        return [
346
            'rar',
347
            'zip',
348
            'xls',
349
            'xlt',
350
            'xlsx',
351
            'xlsm',
352
            'xltx',
353
            'xltm',
354
            'ods',
355
            'ots',
356
            'slk',
357
            'xml',
358
            'csv',
359
            'txt',
360
            'gnumeric',
361
        ];
362
    }
363
364
    /**
365
     * @return \execut\import\models\queries\File
366
     */
367
    public static function find()
368
    {
369
        $q = new \execut\import\models\queries\File(__CLASS__);
370
        return $q->withErrorsPercent()->withProgress();
371
    }
372
373
    public function isStop() {
374
        return File::find()->isStop()->byId($this->id)->count() > 0;
375
    }
376
377
    public function isCancelLoading() {
378
        return File::find()->isCancelLoading()->byId($this->id)->count() > 0;
379
    }
380
381
    public function triggerStop() {
382
        $this->setStatus(FilesStatuse::STOPED);
383
        $this->save(false);
384
    }
385
386
    public function triggerDeleting() {
387
        $this->setStatus(FilesStatuse::DELETING);
388
        $this->save();
389
    }
390
391
    public function triggerLoading() {
392
        $this->deleteLogs();
393
        $this->process_id = getmypid();
0 ignored issues
show
Bug Best Practice introduced by eXeCUT
The property process_id does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
394
        $this->start_date = date('Y-m-d H:i:s');
395
        $this->rows_errors = 0;
396
        $this->rows_success = 0;
397
        $this->setStatus(FilesStatuse::LOADING);
398
        $this->save();
399
    }
400
401
    public function getCalculatedSetsCount() {
402
        return count($this->getSettings()) * $this->getCalculatedRowsCount();
403
    }
404
405
    public function getErrorsPercent() {
406
        $percent = 0;
407
        if (($this->rows_success || $this->rows_errors) && ($this->rows_success + $this->rows_errors) > 0) {
408
            $percent = $this->rows_errors / ($this->rows_success + $this->rows_errors);
409
        }
410
411
        return $percent;
412
    }
413
414
    public function triggerLoaded() {
415
        $this->end_date = date('Y-m-d H:i:s');
416
        $this->setStatus(FilesStatuse::LOADED);
417
        $this->save(false, ['end_date', 'import_files_statuse_id']);
418
    }
419
420
    public function triggerErrorRow() {
421
        $this->rows_errors += count($this->getSettings());
422
        $this->triggerCompleteRow();
423
    }
424
425
    public function triggerException() {
426
        $this->end_date = date('Y-m-d H:i:s');
427
        $this->setStatus(FilesStatuse::ERROR);
428
        $this->save();
429
    }
430
431
    public function triggerSuccessRow() {
432
        $this->rows_success++;
433
        $this->triggerCompleteRow();
434
    }
435
436
    public function scenarios()
437
    {
438
        return array_merge(parent::scenarios(), [
439
            'import' => [
440
                'import_files_statuse_id',
441
                'start_date',
442
                'rows_count',
443
                'rows_errors',
444
                'rows_success',
445
            ],
446
        ]);
447
    }
448
449
    protected $currentStep = 0;
450
    protected $saveStep = 10;
451
    public function triggerCompleteRow() {
452
        if ($this->currentStep === $this->saveStep || $this->completeRows == $this->calculatedRowsCount) {
0 ignored issues
show
Bug Best Practice introduced by eXeCUT
The property completeRows does not exist on execut\import\models\File. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by eXeCUT
The property calculatedRowsCount does not exist on execut\import\models\File. Since you implemented __get, consider adding a @property annotation.
Loading history...
453
            $this->currentStep = 0;
454
            $this->save(false, ['rows_errors', 'rows_success']);
455
        } else {
456
            $this->currentStep++;
457
        }
458
    }
459
460
    public function getCompleteRows() {
461
        return $this->rows_errors + $this->rows_success;
462
    }
463
464
    public function getCalculatedRowsCount() {
465
        return count($this->getRows());
466
    }
467
468
    public function getRows() {
469
        if ($this->rows !== null) {
470
            return $this->rows;
471
        }
472
473
        $fileHandler = $this->content;
474
        $isUnlink = false;
475
        if (self::getDb()->schema instanceof Schema) {
476
            $file = tempnam(sys_get_temp_dir(), 'import_');
477
            file_put_contents($file, $fileHandler);
478
            $fileHandler = $file;
479
            $isUnlink = true;
480
        }
481
482
        $setting = $this->setting;
0 ignored issues
show
Bug Best Practice introduced by eXeCUT
The property setting does not exist on execut\import\models\File. Since you implemented __get, consider adding a @property annotation.
Loading history...
483
        $mimeType = $this->detectMimeType();
484
        $converter = new ToArrayConverter([
485
            'file' => $fileHandler,
486
            'trim' => '\'',
487
            'encoding' => $setting->filesEncoding->key,
488
            'mimeType' => $mimeType,
489
        ]);
490
        if (!empty($setting->csv_delimiter)) {
491
            $converter->delimiter = $setting->csv_delimiter;
492
        }
493
494
        if (!empty($setting->csv_enclosure)) {
495
            $converter->enclosure = $setting->csv_enclosure;
496
        }
497
498
        $data = $converter->convert();
499
        $startFrom = $setting->ignored_lines;
500
501
        $data = array_splice($data, $startFrom);
502
        if ($isUnlink) {
503
            unlink($file);
0 ignored issues
show
Comprehensibility Best Practice introduced by eXeCUT
The variable $file does not seem to be defined for all execution paths leading up to this point.
Loading history...
504
        }
505
506
        return $this->rows = $data;
507
    }
508
509
    protected function detectMimeType() {
510
        if ($this->setting->is_check_mime_type) {
0 ignored issues
show
Bug Best Practice introduced by eXeCUT
The property setting does not exist on execut\import\models\File. Since you implemented __get, consider adding a @property annotation.
Loading history...
511
            return;
512
        }
513
514
        $mimeType = FileHelper::getMimeTypeByExtension($this->name);
515
516
        return $mimeType;
517
    }
518
519
    public function isCheckExtension() {
520
        if (!$this->setting->is_check_mime_type) {
0 ignored issues
show
Bug Best Practice introduced by eXeCUT
The property setting does not exist on execut\import\models\File. Since you implemented __get, consider adding a @property annotation.
Loading history...
521
            return true;
522
        }
523
524
        if (empty($this->mime_type)) {
525
            return false;
526
        }
527
528
        $extensionsByMimeType = FileHelper::getExtensionsByMimeType($this->mime_type);
529
530
        if (!in_array($this->extension, $extensionsByMimeType, true)) {
531
            return false;
532
        }
533
534
        return true;
535
    }
536
537
    /**
538
     * @param $attributes
539
     */
540
    public function logError($attributes)
541
    {
542
        $attributes['import_file_id'] = $this->id;
543
        $log = new Log($attributes);
544
        if (!$log->save()) {
545
//            var_dump($attributes);
546
//            exit;
547
        }
548
    }
549
550
    public function deleteLogs() {
551
        for ($tryCount = 0; $tryCount < 4; $tryCount++) {
552
            try {
553
                Log::deleteAll(['import_file_id' => $this->id]);
554
555
                return true;
556
            } catch (Exception $e) {
557
                if (strpos($e->getMessage(), 'Deadlock detected') !== false && $tryCount == 3) {
558
                    throw $e;
559
                }
560
561
                sleep(10);
562
            }
563
        }
564
    }
565
566
    public function checkWhatSheetsIsNotEmpty() {
567
        if ($this->setting) {
0 ignored issues
show
Bug Best Practice introduced by eXeCUT
The property setting does not exist on execut\import\models\File. Since you implemented __get, consider adding a @property annotation.
Loading history...
568
            if (empty($this->setting->settingsSheets)) {
569
                $this->addError('import_setting_id', 'You must add at least one sheet to selected setting');
570
            }
571
        }
572
    }
573
574
    public function beforeDelete()
575
    {
576
        $this->deleteLogs();
577
        return parent::beforeDelete(); // TODO: Change the autogenerated stub
578
    }
579
580
    /**
581
     * @param $key
582
     */
583
    protected function setStatus($key)
584
    {
585
        $this->import_files_statuse_id = FilesStatuse::getIdByKey($key);
586
    }
587
588
    public function getSettings() {
589
        return $this->setting->settingsSheets[0]->getSettings();
0 ignored issues
show
Bug Best Practice introduced by eXeCUT
The property setting does not exist on execut\import\models\File. Since you implemented __get, consider adding a @property annotation.
Loading history...
590
    }
591
592
    public function getLogsGrouped()
593
    {
594
        $result = parent::getLogs(); // TODO: Change the autogenerated stub
595
        $result->modelClass = LogGrouped::class;
596
597
        return $result->select([
598
            'message',
599
            'category',
600
            'logsCount' => new Expression('count(category)'),
601
            ])->groupBy([
602
                'message',
603
                'category',
604
            ])->orderBy('logsCount DESC');
605
    }
606
607
    public function getLogs()
608
    {
609
        $result = parent::getLogs(); // TODO: Change the autogenerated stub
610
        $result->modelClass = Log::class;
611
612
        return $result;
613
    }
614
615
    public function __toString()
616
    {
617
        return '#' . $this->id . ' ' . $this->name;
618
    }
619
}
620