Login   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 234
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 0
Metric Value
wmc 31
lcom 2
cbo 4
dl 0
loc 234
rs 9.92
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
B init() 0 13 8
A onDeleteExtraRecords() 0 14 3
A deleteExtraRecords() 0 23 5
A deleteExpiredRecords() 0 24 4
A getLoginRules() 0 9 1
A rules() 0 4 1
A getStatusDesc() 0 7 2
A getDeviceDesc() 0 7 2
A tableName() 0 4 1
A getLatests() 0 8 4
1
<?php
2
3
/**
4
 *  _   __ __ _____ _____ ___  ____  _____
5
 * | | / // // ___//_  _//   ||  __||_   _|
6
 * | |/ // /(__  )  / / / /| || |     | |
7
 * |___//_//____/  /_/ /_/ |_||_|     |_|
8
 * @link https://vistart.me/
9
 * @copyright Copyright (c) 2016 - 2017 vistart
10
 * @license https://vistart.me/license/
11
 */
12
13
namespace rhosocial\user\models\log;
14
15
use rhosocial\base\models\models\BaseBlameableModel;
16
use rhosocial\user\User;
17
use Yii;
18
use yii\base\ModelEvent;
19
20
/**
21
 * Login log.
22
 *
23
 * If you're using MySQL, we recommend that you create a data table using the following statement:
24
 ```SQL
25
CREATE TABLE `log_login` (
26
  `guid` varbinary(16) NOT NULL,
27
  `id` varchar(4) COLLATE utf8_unicode_ci NOT NULL,
28
  `user_guid` varbinary(16) NOT NULL,
29
  `ip` varbinary(16) NOT NULL,
30
  `ip_type` smallint(6) NOT NULL DEFAULT '4',
31
  `created_at` datetime NOT NULL DEFAULT '1970-01-01 00:00:00',
32
  `status` int(11) NOT NULL DEFAULT '0',
33
  `device` int(11) NOT NULL DEFAULT '0',
34
  PRIMARY KEY (`guid`),
35
  UNIQUE KEY `login_log_id_unique` (`guid`,`id`),
36
  KEY `login_log_creator_fk` (`user_guid`),
37
  CONSTRAINT `login_log_creator_fk` FOREIGN KEY (`user_guid`) REFERENCES `user` (`guid`) ON DELETE CASCADE ON UPDATE CASCADE
38
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
39
 ```
40
 *
41
 * @property integer $status Login status.
42
 * @property integer $device Login device.
43
 *
44
 * @version 1.0
45
 * @author vistart <[email protected]>
46
 */
47
class Login extends BaseBlameableModel
48
{
49
    public $contentAttribute = false;
50
    public $updatedAtAttribute = false;
51
    public $updatedByAttribute = false;
52
    
53
    const LIMIT_NO_LIMIT = 0x0;
54
    const LIMIT_MAX = 0x1;
55
    const LIMIT_DURATION = 0x2;
56
    
57
    public $limitType = 0x3;
58
    public $limitMax = 100;
59
    public $limitDuration = 90 * 86400;
60
61
    /**
62
     *
63
     */
64
    public function init()
65
    {
66
        if (($this->limitType & static::LIMIT_MAX) && ($this->limitMax < 2 || !is_int($this->limitMax))) { // at least 2 records.
67
            $this->limitMax = 100;  // 100 records.
68
        }
69
        if (($this->limitType & static::LIMIT_DURATION) && ($this->limitDuration < 86400 || !is_int($this->limitDuration))) { // at least one day.
70
            $this->limitDuration = 90 * 86400; // 90 Days.
71
        }
72
        if ($this->limitType > 0) {
73
            $this->on(static::EVENT_AFTER_INSERT, [$this, 'onDeleteExtraRecords']);
74
        }
75
        parent::init();
76
    }
77
    
78
    /**
79
     *
80
     * @param ModelEvent $event
81
     * @return integer
82
     */
83
    public function onDeleteExtraRecords($event)
84
    {
85
        $sender = $event->sender;
86
        /* @var $sender static */
87
        Yii::info('Login Log limit type:' . $sender->limitType, __METHOD__);
88
        $result = 0;
89
        if ($sender->limitType & static::LIMIT_MAX) {
90
            $result += $sender->deleteExtraRecords();
91
        }
92
        if ($sender->limitType & static::LIMIT_DURATION) {
93
            $result += $sender->deleteExpiredRecords();
94
        }
95
        return $result;
96
    }
97
    
98
    /**
99
     * Delete extra records.
100
     * @return integer The total of rows deleted.
101
     */
102
    protected function deleteExtraRecords()
103
    {
104
        try {
105
            $limit = (int)($this->limitMax);
106
        } catch (\Exception $ex) {
0 ignored issues
show
Unused Code introduced by
catch (\Exception $ex) {...HOD__); return 0; } does not seem to be 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...
107
            Yii::error($ex->getMessage(), __METHOD__);
108
            return 0;
109
        }
110
        $host = $this->host;
111
         /* @var $host \rhosocial\user\User */
112
        $count = static::find()->createdBy($host)->count();
113
        Yii::info($host->getReadableGUID() . " has $count login logs.", __METHOD__);
114
        if ($count > $limit) {
115
            foreach (static::find()->createdBy($host)->orderByCreatedAt()->limit($count - $limit)->all() as $login) {
116
                /* @var $login static */
117
                $result = $login->delete();
118
                if (YII_ENV_DEV) {
119
                    Yii::info($host->getReadableGUID() . ": ($result) login record created at (" . $login->getCreatedAt() . ") was just deleted.", __METHOD__);
120
                }
121
            }
122
        }
123
        return $count - $limit;
124
    }
125
    
126
    /**
127
     * Delete expired records.
128
     * @return integer The total of rows deleted.
129
     */
130
    protected function deleteExpiredRecords()
131
    {
132
        try {
133
            $limit = (int)($this->limitDuration);
134
        } catch (\Exception $ex) {
0 ignored issues
show
Unused Code introduced by
catch (\Exception $ex) {...HOD__); return 0; } does not seem to be 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...
135
            Yii::error($ex->getMessage(), __METHOD__);
136
            return 0;
137
        }
138
        $count = 0;
139
        $host = $this->host;
140
        /* @var $host \rhosocial\user\User */
141
        foreach (static::find()
142
                ->createdBy($host)
143
                ->andWhere(['<=', $this->createdAtAttribute, $this->offsetDatetime($this->currentUtcDatetime(), -$limit)])
144
                ->all() as $login) {
145
            /* @var $login static */
146
            $result = $login->delete();
147
            $count += $result;
148
            if (YII_ENV_DEV) {
149
                Yii::info($host->getReadableGUID() . ": ($result) login record created at (" . $login->getCreatedAt() . ") was just deleted.", __METHOD__);
150
            }
151
        }
152
        return $count;
153
    }
154
155
    const STATUS_NORMAL = 0x000;
156
    const STATUS_ABNORMAL_UNUSUAL_LOCATION = 0x001;
157
    const STATUS_ABNORMAL_TOO_MANY_WRONG_PASSWORD_ATTEMPTS = 0x002;
158
    const STATUS_ABNORMAL_UNUSUAL_DEVICE = 0x003;
159
    
160
    public static $statuses = [
161
        self::STATUS_NORMAL => 'Normal',
162
        self::STATUS_ABNORMAL_UNUSUAL_LOCATION => 'Abnormal (Unusual location)',
163
        self::STATUS_ABNORMAL_TOO_MANY_WRONG_PASSWORD_ATTEMPTS => 'Abnormal (Too many wrong password attempts)',
164
        self::STATUS_ABNORMAL_UNUSUAL_DEVICE => 'Abnormal (Unusual device)',
165
    ];
166
167
    const DEVICE_UNKNOWN = 0x000;
168
    const DEVICE_PC_NO_CLASSIFICATION = 0x010;
169
    const DEVICE_PC_WINDOWS_BROWSER = 0x011;
170
    const DEVICE_PC_LINUX_BROWSER = 0x012;
171
    const DEVICE_PC_OSX_BROWSER = 0x013;
172
173
    const DEVICE_MOBILE_NO_CLASSICATION = 0x020;
174
    const DEVICE_MOBILE_ANDROID_BROWSER = 0x021;
175
    const DEVICE_MOBILE_WINDOWSPHONE_BROWSER = 0x022;
176
    const DEVICE_MOBILE_IOS_BROWSER = 0x023;
177
178
    const DEVICE_PC_WINDOWS_APPLICATION = 0x031;
179
    const DEVICE_PC_LINUX_APPLICATION = 0x032;
180
    const DEVICE_PC_OSX_APPLICATION = 0x033;
181
182
    const DEVICE_MOBILE_ANDROID_APPLICATION = 0x041;
183
    const DEVICE_MOBILE_WINDOWSPHONE_APPLICATION = 0x042;
184
    const DEVICE_MOBILE_IOS_APPLICATION = 0x043;
185
186
    const DEVICE_3PA_NO_CLASSIFICATION = 0x050;
187
    const DEVICE_3PA_PC = 0x051;
188
    const DEVICE_3PA_MOBILE = 0x052;
189
    const DEVICE_3PA_BROWSER = 0x053;
190
    
191
    public static $devices = [
192
        self::DEVICE_UNKNOWN => 'Unknown',
193
        self::DEVICE_PC_NO_CLASSIFICATION => 'PC (No classification)',
194
        self::DEVICE_PC_WINDOWS_BROWSER => 'PC (Windows, Browser)',
195
        self::DEVICE_PC_LINUX_BROWSER => 'PC (Linux, Browser)',
196
        self::DEVICE_PC_OSX_BROWSER => 'PC (OS X, Browser)',
197
        self::DEVICE_MOBILE_NO_CLASSICATION => 'Mobile (No classification)',
198
        self::DEVICE_MOBILE_ANDROID_BROWSER => 'Mobile (Android, Browser)',
199
        self::DEVICE_MOBILE_WINDOWSPHONE_BROWSER => 'Mobile (Windows Phone, Browser)',
200
        self::DEVICE_MOBILE_IOS_BROWSER => 'Mobile (iOS, Browser)',
201
        self::DEVICE_PC_WINDOWS_BROWSER => 'PC (Windows, Application)',
202
        self::DEVICE_PC_LINUX_APPLICATION => 'PC (Linux, Application)',
203
        self::DEVICE_PC_OSX_APPLICATION => 'PC (OS X, Application)',
204
        self::DEVICE_MOBILE_ANDROID_APPLICATION => 'Mobile (Android, Application)',
205
        self::DEVICE_MOBILE_WINDOWSPHONE_APPLICATION => 'Mobile (Windows Phone, Application)',
206
        self::DEVICE_MOBILE_IOS_APPLICATION => 'Mobile (iOS, Application)',
207
        self::DEVICE_3PA_NO_CLASSIFICATION => 'Third party authorization (No classification)',
208
        self::DEVICE_3PA_PC => 'Third party authorization (PC)',
209
        self::DEVICE_3PA_MOBILE => 'Third party authorization (Mobile)',
210
        self::DEVICE_3PA_BROWSER => 'Third party authorization (Browser)',
211
    ];
212
213
    /**
214
     * @return array
215
     */
216
    public function getLoginRules()
217
    {
218
        return [
219
            ['status', 'in', 'range' => array_keys(static::$statuses)],
220
            ['status', 'default', 'value' => self::STATUS_NORMAL],
221
            ['device', 'in', 'range' => array_keys(static::$devices)],
222
            ['device', 'default', 'value' => self::DEVICE_UNKNOWN],
223
        ];
224
    }
225
226
    /**
227
     * @return array
228
     */
229
    public function rules()
230
    {
231
        return array_merge($this->getLoginRules(), parent::rules());
232
    }
233
234
    /**
235
     * @return mixed|null
236
     */
237
    public function getStatusDesc()
238
    {
239
        if (array_key_exists($this->status, static::$statuses)) {
240
            return static::$statuses[$this->status];
241
        }
242
        return null;
243
    }
244
245
    /**
246
     * @return mixed|null
247
     */
248
    public function getDeviceDesc()
249
    {
250
        if (array_key_exists($this->device, static::$devices)) {
251
            return static::$devices[$this->device];
252
        }
253
        return null;
254
    }
255
256
    /**
257
     * @return string
258
     */
259
    public static function tableName()
260
    {
261
        return '{{%log_login}}';
262
    }
263
264
    const GET_ALL_LATESTS = 'all';
265
    
266
    /**
267
     * Get latest ones.
268
     * @param User $user
269
     * @param integer $limit
270
     * @return static[]
271
     */
272
    public static function getLatests(User $user, $limit = 1)
273
    {
274
        $query = static::find()->createdBy($user)->orderByCreatedAt(SORT_DESC);
275
        if ($limit == self::GET_ALL_LATESTS || !is_int($limit) || $limit < 1) {
276
            return $query->all();
277
        }
278
        return $query->limit($limit)->all();
279
    }
280
}
281