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\invitation\registration\UserInvitationRegistrationTrait; |
||
18 | use rhosocial\user\models\log\UserLoginTrait; |
||
19 | use rhosocial\user\models\UserUsernameTrait; |
||
20 | use rhosocial\user\security\UserPasswordHistoryTrait; |
||
21 | use Yii; |
||
22 | use yii\base\Event; |
||
23 | use yii\base\InvalidConfigException; |
||
24 | use yii\behaviors\AttributeBehavior; |
||
25 | use yii\caching\TagDependency; |
||
26 | use yii\db\ActiveRecord; |
||
27 | use yii\widgets\ActiveForm; |
||
28 | |||
29 | /** |
||
30 | * Common User Model. |
||
31 | * This model should be stored in a relational database. You can create a foreign |
||
32 | * key constraint on other models and this model. |
||
33 | * |
||
34 | * If you're using MySQL, we recommend that you create a data table using the following statement: |
||
35 | * |
||
36 | * ``` |
||
37 | * CREATE TABLE `user` ( |
||
38 | * `guid` varbinary(16) NOT NULL COMMENT 'GUID', |
||
39 | * `id` varchar(16) COLLATE utf8_unicode_ci NOT NULL COMMENT 'ID', |
||
40 | * `pass_hash` varchar(80) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Password Hash', |
||
41 | * `ip` varbinary(16) NOT NULL DEFAULT '0' COMMENT 'IP', |
||
42 | * `ip_type` tinyint(3) unsigned NOT NULL DEFAULT '4' COMMENT 'IP Address Type', |
||
43 | * `created_at` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT 'Create Time', |
||
44 | * `updated_at` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT 'Update Time', |
||
45 | * `auth_key` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Authentication Key', |
||
46 | * `access_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Access Token', |
||
47 | * `password_reset_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Password Reset Token', |
||
48 | * `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT 'Status', |
||
49 | * `type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT 'Type', |
||
50 | * `source` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Source', |
||
51 | * PRIMARY KEY (`guid`), |
||
52 | * UNIQUE KEY `user_id_unique` (`id`), |
||
53 | * UNIQUE KEY `user_username_unique` (`username`), |
||
54 | * KEY `user_auth_key_normal` (`auth_key`), |
||
55 | * KEY `user_access_token_normal` (`access_token`), |
||
56 | * KEY `user_password_reset_token` (`password_reset_token`), |
||
57 | * KEY `user_create_time_normal` (`created_at`) |
||
58 | * ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='User'; |
||
59 | * ``` |
||
60 | * |
||
61 | * The fields of User table in database are following: |
||
62 | * @property string $guid User's GUID. This property is used to uniquely identify a user. |
||
63 | * This property is automatically generated when the class is created, we do not |
||
64 | * recommend that you modify this property, unless you know the consequences of doing so. |
||
65 | * This property is also regareded as the foreign key target of other models associated |
||
66 | * with this model. If you have to modify this property, the foreign key constraints |
||
67 | * should be updating and deleting on cascade. |
||
68 | * @property string $id User Identifier No. It is a 8-digit number beginning with 4 by default. |
||
69 | * @property string $pass_hash Password Hash. |
||
70 | * We strongly recommend you NOT to change this property directly! |
||
71 | * If you want to set or reset password, please use setPassword() magic property instead. |
||
72 | * @property integer $ip IP address. |
||
73 | * @property integer $ipType |
||
74 | * @property string $createdAt |
||
75 | * @property string $updatedAt |
||
76 | * @property string $auth_key |
||
77 | * @property string $access_token |
||
78 | * @property string $password_reset_token |
||
79 | * @property integer $status |
||
80 | * @property integer $type |
||
81 | * @property string $source |
||
82 | * |
||
83 | * @property-read Profile $profile Profile. This magic property is read-only. |
||
84 | * If you want to modify anyone property of Profile model, please get it first, |
||
85 | * then change and save it, like following: |
||
86 | * ```php |
||
87 | * $profile = $user->profile; |
||
88 | * $profile->nickname = 'vistart'; |
||
89 | * $profile->save(); |
||
90 | * ``` |
||
91 | * If $profileClass is `false`, `null` returned. |
||
92 | * @version 1.0 |
||
93 | * @author vistart <[email protected]> |
||
94 | */ |
||
95 | class User extends BaseUserModel |
||
96 | { |
||
97 | use UserPasswordHistoryTrait, UserLoginTrait, UserUsernameTrait, UserInvitationRegistrationTrait; |
||
98 | |||
99 | /** |
||
100 | * @var string |
||
101 | */ |
||
102 | public $searchClass = UserSearch::class; |
||
103 | |||
104 | /** |
||
105 | * @return null |
||
106 | */ |
||
107 | public function getSearchModel() |
||
108 | { |
||
109 | $class = $this->searchClass; |
||
110 | if (empty($class) || !class_exists($class)) { |
||
111 | return null; |
||
112 | } |
||
113 | return new $class; |
||
114 | } |
||
115 | |||
116 | /** |
||
117 | * @inheritdoc |
||
118 | */ |
||
119 | public function attributeLabels() |
||
120 | { |
||
121 | $labels = [ |
||
122 | $this->guidAttribute => Yii::t('user', 'GUID'), |
||
123 | $this->idAttribute => Yii::t('user', 'ID'), |
||
124 | $this->passwordHashAttribute => Yii::t('user', 'Password Hash'), |
||
125 | $this->ipAttribute => Yii::t('user', 'IP Address'), |
||
126 | $this->ipTypeAttribute => Yii::t('user', 'IP Address Type'), |
||
127 | $this->createdAtAttribute => Yii::t('user', 'Creation Time'), |
||
128 | $this->updatedAtAttribute => Yii::t('user', 'Last Updated Time'), |
||
129 | $this->authKeyAttribute => Yii::t('user', 'Authentication Key'), |
||
130 | $this->accessTokenAttribute => Yii::t('user', 'Access Token'), |
||
131 | $this->passwordResetTokenAttribute => Yii::t('user', 'Password Reset Token'), |
||
132 | $this->statusAttribute => Yii::t('user', 'Status'), |
||
133 | 'type' => Yii::t('user', 'Type'), |
||
134 | $this->sourceAttribute => Yii::t('user', 'Source'), |
||
135 | 'createdAt' => Yii::t('user', 'Registration Time'), |
||
136 | 'updatedAt' => Yii::t('user', 'Last Updated Time'), |
||
137 | ]; |
||
138 | return $labels; |
||
139 | } |
||
140 | |||
141 | /** |
||
142 | * @inheritdoc |
||
143 | */ |
||
144 | public $idAttributeType = 1; |
||
145 | |||
146 | /** |
||
147 | * @inheritdoc |
||
148 | */ |
||
149 | public $idAttributeLength = 8; |
||
150 | |||
151 | /** |
||
152 | * @inheritdoc |
||
153 | */ |
||
154 | public $idAttributePrefix = '4'; |
||
155 | |||
156 | /** |
||
157 | * @var bool |
||
158 | */ |
||
159 | public $idPreassigned = true; |
||
160 | |||
161 | /** |
||
162 | * @inheritdoc |
||
163 | */ |
||
164 | public static function tableName() |
||
165 | { |
||
166 | return '{{%user}}'; |
||
167 | } |
||
168 | |||
169 | /** |
||
170 | * @var string|false Profile class name. If you do not need profile model, |
||
171 | * please set it false. |
||
172 | */ |
||
173 | public $profileClass = false; |
||
174 | |||
175 | /** |
||
176 | * @inheritdoc |
||
177 | * ----------- |
||
178 | * When the user is updated or deleted, the cache contents tagged with the corresponding user tag will be invalidated. |
||
179 | */ |
||
180 | public function init() |
||
181 | { |
||
182 | $this->on(static::$eventAfterRegister, [$this, 'onAddPasswordToHistory']); |
||
183 | $this->on(static::$eventAfterResetPassword, [$this, 'onAddPasswordToHistory']); |
||
184 | $this->on(static::EVENT_AFTER_UPDATE, [$this, 'onInvalidTags']); |
||
185 | $this->on(static::EVENT_AFTER_DELETE, [$this, 'onInvalidTags']); |
||
186 | parent::init(); |
||
187 | } |
||
188 | |||
189 | /** |
||
190 | * @var string |
||
191 | */ |
||
192 | public $cacheTagPrefix = 'tag_user_'; |
||
193 | |||
194 | /** |
||
195 | * Get cache tag. |
||
196 | * The cache tag ends with the user ID, but after the user ID is modified, the old ID will prevail. |
||
197 | * @return string |
||
198 | */ |
||
199 | public function getCacheTag() |
||
200 | { |
||
201 | return $this->cacheTagPrefix . |
||
202 | ($this->isAttributeChanged($this->idAttribute) ? $this->getOldAttribute($this->idAttribute) : $this->getID()); |
||
203 | } |
||
204 | |||
205 | /** |
||
206 | * @param Event $event |
||
207 | * @return bool|string|array |
||
208 | */ |
||
209 | public function onInvalidTags($event) |
||
210 | { |
||
211 | try { |
||
212 | $cache = Yii::$app->get('cache'); |
||
213 | } catch (InvalidConfigException $ex) { |
||
214 | return true; |
||
215 | } |
||
216 | $sender = $event->sender; |
||
217 | /*@var $sender static */ |
||
218 | return TagDependency::invalidate($cache, $sender->getCacheTag()); |
||
219 | } |
||
220 | |||
221 | /** |
||
222 | * Create profile. |
||
223 | * If profile of this user exists, it will be returned instead of creating it. |
||
224 | * Meanwhile, the $config parameter will be skipped. |
||
225 | * @param array $config Profile configuration. Skipped if it exists. |
||
226 | * @return Profile |
||
227 | */ |
||
228 | public function createProfile($config = []) |
||
229 | { |
||
230 | $profileClass = $this->profileClass; |
||
231 | if (empty($profileClass) || !is_string($this->profileClass)) { |
||
232 | return null; |
||
233 | } |
||
234 | $profile = $profileClass::findOne($this->getGUID()); |
||
235 | if (!$profile) { |
||
236 | $profile = $this->create($profileClass, $config); |
||
237 | $profile->setGUID($this->getGUID()); |
||
238 | } |
||
239 | return $profile; |
||
240 | } |
||
241 | |||
242 | /** |
||
243 | * |
||
244 | * @return boolean |
||
245 | */ |
||
246 | public function hasProfile() |
||
247 | { |
||
248 | if ($this->profileClass === false || !is_string($this->profileClass) || !class_exists($this->profileClass)) { |
||
249 | return false; |
||
250 | } |
||
251 | return true; |
||
252 | } |
||
253 | |||
254 | /** |
||
255 | * Get Profile query. |
||
256 | * If you want to get profile model, please access this method in magic property way, |
||
257 | * like following: |
||
258 | * |
||
259 | * ```php |
||
260 | * $user->profile; |
||
261 | * ``` |
||
262 | * |
||
263 | * @return BaseBlameableQuery |
||
264 | */ |
||
265 | public function getProfile() |
||
266 | { |
||
267 | if (!$this->hasProfile()) { |
||
268 | return null; |
||
269 | } |
||
270 | $profileClass = $this->profileClass; |
||
271 | $profileModel = $profileClass::buildNoInitModel(); |
||
272 | return $this->hasOne($profileClass, |
||
273 | [$profileModel->createdByAttribute => $this->guidAttribute])->inverseOf('user'); |
||
274 | } |
||
275 | |||
276 | /** |
||
277 | * @param $event |
||
278 | * @return mixed |
||
279 | */ |
||
280 | public function onGenerateId($event) |
||
281 | { |
||
282 | $sender = $event->sender; |
||
283 | /* @var $sender static */ |
||
284 | return $sender->generateId(); |
||
285 | } |
||
286 | |||
287 | /** |
||
288 | * @return array |
||
289 | */ |
||
290 | public function generateIdBehavior() |
||
291 | { |
||
292 | return [ |
||
293 | 'class' => AttributeBehavior::class, |
||
294 | 'attributes' => [ |
||
295 | ActiveRecord::EVENT_BEFORE_INSERT => $this->idAttribute, |
||
296 | ], |
||
297 | 'value' => [$this, 'onGenerateId'], |
||
298 | ]; |
||
299 | } |
||
300 | |||
301 | /** |
||
302 | * @return array |
||
303 | */ |
||
304 | public function behaviors() |
||
305 | { |
||
306 | $behaviors = parent::behaviors(); |
||
307 | $behavior = $this->generateIdBehavior(); |
||
308 | if (!empty($behavior)) { |
||
309 | $behaviors[] = $behavior; |
||
310 | } |
||
311 | return $behaviors; |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * @var string |
||
316 | */ |
||
317 | public static $idRegex = '/^\d{5,8}$/'; |
||
318 | |||
319 | /** |
||
320 | * @return array |
||
321 | */ |
||
322 | public function getIdRules() |
||
323 | { |
||
324 | return [ |
||
325 | [$this->idAttribute, 'safe'], |
||
326 | ]; |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * Friendly to IDE. |
||
331 | * @return UserQuery |
||
332 | */ |
||
333 | public static function find() |
||
334 | { |
||
335 | return parent::find(); |
||
336 | } |
||
337 | } |
||
338 |