Completed
Push — master ( 15085c...c17da1 )
by vistart
04:03
created

User.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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;
14
15
use rhosocial\base\models\models\BaseUserModel;
16
use rhosocial\base\models\queries\BaseBlameableQuery;
17
use rhosocial\user\models\log\UserLoginTrait;
18
use rhosocial\user\security\UserPasswordHistoryTrait;
19
use Yii;
20
use yii\base\Event;
21
use yii\base\InvalidConfigException;
22
use yii\behaviors\AttributeBehavior;
23
use yii\caching\TagDependency;
24
use yii\db\ActiveRecord;
25
use yii\widgets\ActiveForm;
26
27
/**
28
 * Common User Model.
29
 * This model should be stored in a relational database. You can create a foreign
30
 * key constraint on other models and this model.
31
 *
32
 * If you're using MySQL, we recommend that you create a data table using the following statement:
33
 *
34
 * ```
35
 * CREATE TABLE `user` (
36
 *   `guid` varbinary(16) NOT NULL COMMENT 'GUID',
37
 *   `id` varchar(16) COLLATE utf8_unicode_ci NOT NULL COMMENT 'ID',
38
 *   `pass_hash` varchar(80) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Password Hash',
39
 *   `ip` varbinary(16) NOT NULL DEFAULT '0' COMMENT 'IP',
40
 *   `ip_type` tinyint(3) unsigned NOT NULL DEFAULT '4' COMMENT 'IP Address Type',
41
 *   `created_at` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT 'Create Time',
42
 *   `updated_at` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT 'Update Time',
43
 *   `auth_key` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Authentication Key',
44
 *   `access_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Access Token',
45
 *   `password_reset_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Password Reset Token',
46
 *   `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT 'Status',
47
 *   `type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT 'Type',
48
 *   `source` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Source',
49
 *   PRIMARY KEY (`guid`),
50
 *   UNIQUE KEY `user_id_unique` (`id`),
51
 *   UNIQUE KEY `user_username_unique` (`username`),
52
 *   KEY `user_auth_key_normal` (`auth_key`),
53
 *   KEY `user_access_token_normal` (`access_token`),
54
 *   KEY `user_password_reset_token` (`password_reset_token`),
55
 *   KEY `user_create_time_normal` (`created_at`)
56
 * ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='User';
57
 * ```
58
 *
59
 * The fields of User table in database are following:
60
 * @property string $guid User's GUID. This property is used to uniquely identify a user.
61
 * This property is automatically generated when the class is created, we do not
62
 * recommend that you modify this property, unless you know the consequences of doing so.
63
 * This property is also regareded as the foreign key target of other models associated
64
 * with this model. If you have to modify this property, the foreign key constraints
65
 * should be updating and deleting on cascade.
66
 * @property string $id User Identifier No. It is a 8-digit number beginning with 4 by default.
67
 * @property string $pass_hash Password Hash.
68
 * We strongly recommend you NOT to change this property directly!
69
 * If you want to set or reset password, please use setPassword() magic property instead.
70
 * @property integer $ip IP address.
71
 * @property integer $ipType
72
 * @property string $createdAt
73
 * @property string $updatedAt
74
 * @property string $auth_key
75
 * @property string $access_token
76
 * @property string $password_reset_token
77
 * @property integer $status
78
 * @property integer $type
79
 * @property string $source
80
 *
81
 * @property-read Profile $profile Profile. This magic property is read-only.
82
 * If you want to modify anyone property of Profile model, please get it first,
83
 * then change and save it, like following:
84
 * ```php
85
 * $profile = $user->profile;
86
 * $profile->nickname = 'vistart';
87
 * $profile->save();
88
 * ```
89
 * If $profileClass is `false`, `null` returned.
90
 * @version 1.0
91
 * @author vistart <[email protected]>
92
 */
93
class User extends BaseUserModel
94
{
95
    use UserPasswordHistoryTrait, UserLoginTrait;
96
97
    /**
98
     * @var string|boolean
99
     */
100
    public $usernameAttribute = false;
101
102
    /**
103
     * @return mixed|null
104
     */
105 1
    public function getUsername()
106
    {
107 1
        if (is_string($this->usernameAttribute) && !empty($this->usernameAttribute)) {
108
            return $this->{$this->usernameAttribute};
109
        }
110 1
        return null;
111
    }
112
113
    /**
114
     * @param string $username
115
     * @return null|string
116
     */
117
    public function setUsername($username = '')
118
    {
119
        if (is_string($this->usernameAttribute) && !empty($this->usernameAttribute)) {
120
            return $this->{$this->usernameAttribute} = $username;
121
        }
122
        return null;
123
    }
124
125
    /**
126
     * @var string
127
     */
128
    public $searchClass = UserSearch::class;
129
130
    /**
131
     * @return null
132
     */
133
    public function getSearchModel()
134
    {
135
        $class = $this->searchClass;
136
        if (empty($class) || !class_exists($class)) {
137
            return null;
138
        }
139
        return new $class;
140
    }
141
142
    /**
143
     * @inheritdoc
144
     */
145 1
    public function attributeLabels()
146
    {
147
        $labels = [
148 1
            $this->guidAttribute => Yii::t('user', 'GUID'),
149 1
            $this->idAttribute => Yii::t('user', 'ID'),
150 1
            $this->passwordHashAttribute => Yii::t('user', 'Password Hash'),
151 1
            $this->ipAttribute => Yii::t('user', 'IP Address'),
152 1
            $this->ipTypeAttribute => Yii::t('user', 'IP Address Type'),
153 1
            $this->createdAtAttribute => Yii::t('user', 'Creation Time'),
154 1
            $this->updatedAtAttribute => Yii::t('user', 'Last Updated Time'),
155 1
            $this->authKeyAttribute => Yii::t('user', 'Authentication Key'),
156 1
            $this->accessTokenAttribute => Yii::t('user', 'Access Token'),
157 1
            $this->passwordResetTokenAttribute => Yii::t('user', 'Password Reset Token'),
158 1
            $this->statusAttribute => Yii::t('user', 'Status'),
159 1
            'type' => Yii::t('user', 'Type'),
160 1
            $this->sourceAttribute => Yii::t('user', 'Source'),
161 1
            'createdAt' => Yii::t('user', 'Registration Time'),
162 1
            'updatedAt' => Yii::t('user', 'Last Updated Time'),
163
        ];
164 1
        if (is_string($this->username) && !empty($this->username)) {
0 ignored issues
show
The property username does not seem to exist. Did you mean usernameAttribute?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
165
            $labels = array_merge($labels, [
166
                $this->usernameAttribute => Yii::t('user', 'Username'),
167
            ]);
168
        }
169 1
        return $labels;
170
    }
171
172
    /**
173
     * @inheritdoc
174
     */
175
    public $idAttributeType = 1;
176
177
    /**
178
     * @inheritdoc
179
     */
180
    public $idAttributeLength = 8;
181
182
    /**
183
     * @inheritdoc
184
     */
185
    public $idAttributePrefix = '4';
186
187
    /**
188
     * @var bool
189
     */
190
    public $idPreassigned = true;
191
192
    /**
193
     * @inheritdoc
194
     */
195 41
    public static function tableName()
196
    {
197 41
        return '{{%user}}';
198
    }
199
200
    /**
201
     * @var string|false Profile class name. If you do not need profile model,
202
     * please set it false.
203
     */
204
    public $profileClass = false;
205
206
    /**
207
     * @inheritdoc
208
     * -----------
209
     * When the user is updated or deleted, the cache contents tagged with the corresponding user tag will be invalidated.
210
     */
211 41
    public function init()
212
    {
213 41
        $this->on(static::$eventAfterRegister, [$this, 'onAddPasswordToHistory']);
214 41
        $this->on(static::$eventAfterResetPassword, [$this, 'onAddPasswordToHistory']);
215 41
        $this->on(static::EVENT_AFTER_UPDATE, [$this, 'onInvalidTags']);
216 41
        $this->on(static::EVENT_AFTER_DELETE, [$this, 'onInvalidTags']);
217 41
        parent::init();
218 41
    }
219
220
    /**
221
     * @var string
222
     */
223
    public $cacheTagPrefix = 'tag_user_';
224
225
    /**
226
     * Get cache tag.
227
     * The cache tag ends with the user ID, but after the user ID is modified, the old ID will prevail.
228
     * @return string
229
     */
230 36
    public function getCacheTag()
231
    {
232 36
        return $this->cacheTagPrefix .
233 36
            ($this->isAttributeChanged($this->idAttribute) ? $this->getOldAttribute($this->idAttribute) : $this->getID());
234
    }
235
236
    /**
237
     * @param Event $event
238
     * @return bool|string|array
239
     */
240 36
    public function onInvalidTags($event)
241
    {
242
        try {
243 36
            $cache = Yii::$app->get('cache');
244
        } catch (InvalidConfigException $ex) {
245
            return true;
246
        }
247 36
        $sender = $event->sender;
248
        /*@var $sender static */
249 36
        return TagDependency::invalidate($cache, $sender->getCacheTag());
250
    }
251
252
    /**
253
     * Create profile.
254
     * If profile of this user exists, it will be returned instead of creating it.
255
     * Meanwhile, the $config parameter will be skipped.
256
     * @param array $config Profile configuration. Skipped if it exists.
257
     * @return Profile
258
     */
259 19
    public function createProfile($config = [])
260
    {
261 19
        $profileClass = $this->profileClass;
262 19
        if (empty($profileClass) || !is_string($this->profileClass)) {
263 1
            return null;
264
        }
265 18
        $profile = $profileClass::findOne($this->getGUID());
266 18
        if (!$profile) {
267 18
            $profile = $this->create($profileClass, $config);
268 18
            $profile->setGUID($this->getGUID());
269
        }
270 18
        return $profile;
271
    }
272
273
    /**
274
     * 
275
     * @return boolean
276
     */
277 4
    public function hasProfile()
278
    {
279 4
        if ($this->profileClass === false || !is_string($this->profileClass) || !class_exists($this->profileClass)) {
280 2
            return false;
281
        }
282 2
        return true;
283
    }
284
285
    /**
286
     * Get Profile query.
287
     * If you want to get profile model, please access this method in magic property way,
288
     * like following:
289
     *
290
     * ```php
291
     * $user->profile;
292
     * ```
293
     *
294
     * @return BaseBlameableQuery
295
     */
296 4
    public function getProfile()
297
    {
298 4
        if (!$this->hasProfile()) {
299 2
            return null;
300
        }
301 2
        $profileClass = $this->profileClass;
302 2
        $profileModel = $profileClass::buildNoInitModel();
303 2
        return $this->hasOne($profileClass,
304 2
                [$profileModel->createdByAttribute => $this->guidAttribute])->inverseOf('user');
305
    }
306
307
    /**
308
     * @param $event
309
     * @return mixed
310
     */
311 36
    public function onGenerateId($event)
312
    {
313 36
        $sender = $event->sender;
314
        /* @var $sender static */
315 36
        return $sender->generateId();
316
    }
317
318
    /**
319
     * @return array
320
     */
321 41
    public function generateIdBehavior()
322
    {
323
        return [
324 41
            'class' => AttributeBehavior::class,
325
            'attributes' => [
326 41
                ActiveRecord::EVENT_BEFORE_INSERT => $this->idAttribute,
327
            ],
328 41
            'value' => [$this, 'onGenerateId'],
329
        ];
330
    }
331
332
    /**
333
     * @return array
334
     */
335 41
    public function behaviors()
336
    {
337 41
        $behaviors = parent::behaviors();
338 41
        $behavior = $this->generateIdBehavior();
339 41
        if (!empty($behavior)) {
340 41
            $behaviors[] = $behavior;
341
        }
342 41
        return $behaviors;
343
    }
344
345
    /**
346
     * @var string
347
     */
348
    public static $idRegex = '\d{5,8}';
349
350
    /**
351
     * @return array
352
     */
353 2
    public function getIdRules()
354
    {
355
        return [
356 2
            [$this->idAttribute, 'safe'],
357
        ];
358
    }
359
360
    /**
361
     * @var string
362
     */
363
    public static $usernameRegex = '\w{1,32}';
364
365
    /**
366
     * Get the rules associated with `username` property.
367
     * The `username` must not be empty, and its length must not be greater than 32.
368
     * This property should be confirmed unique among all users.
369
     * If you do not need this property, please override this method and return empty array.
370
     * @return array
371
     */
372 36
    public function getUsernameRules()
373
    {
374 36
        if (is_string($this->usernameAttribute) && !empty($this->usernameAttribute)) {
375
            return [
376
                [$this->usernameAttribute, 'required'],
377
                [$this->usernameAttribute, 'string', 'max' => 32],
378
                [$this->usernameAttribute, 'unique'],
379
            ];
380
        }
381 36
        return [];
382
    }
383
384
    /**
385
     * @return array
386
     */
387 36
    public function rules()
388
    {
389 36
        $rules = $this->getUsernameRules();
390 36
        if (!empty($rules)) {
391
            return array_merge(parent::rules(), $rules);
392
        }
393 36
        return parent::rules();
394
    }
395
}
396