Completed
Push — 3.0 ( cc19ac...d7662c )
by Olivier
02:32
created

User   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 427
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 10

Importance

Changes 31
Bugs 1 Features 3
Metric Value
wmc 30
c 31
b 1
f 3
lcom 2
cbo 10
dl 0
loc 427
rs 10

15 Methods

Rating   Name   Duplication   Size   Complexity  
A from() 0 14 3
A __construct() 0 9 2
A __get() 0 11 2
A create_validation_rules() 0 10 1
A save() 0 9 2
A to_array() 0 11 2
A get_name() 0 22 3
A get_is_admin() 0 4 1
A get_is_guest() 0 4 1
A lazy_get_restricted_sites_ids() 0 12 2
A has_permission() 0 9 2
A has_ownership() 0 4 1
A login() 0 13 2
A logout() 0 9 1
B get_css_class_names() 0 14 5
1
<?php
2
3
/*
4
 * This file is part of the Icybee package.
5
 *
6
 * (c) Olivier Laviale <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Icybee\Modules\Users;
13
14
use ICanBoogie\ActiveRecord;
15
use ICanBoogie\ActiveRecord\CreatedAtProperty;
16
17
use Brickrouge\AlterCSSClassNamesEvent;
18
use Brickrouge\CSSClassNames;
19
use Brickrouge\CSSClassNamesProperty;
20
21
use Icybee\Binding\Core\PrototypedBindings as IcybeeBindings;
22
use Icybee\Modules\Registry\Binding\UserBindings as RegistryBindings;
23
24
/**
25
 * A user.
26
 *
27
 * @property-read UserModel $model
28
 * @property-read \ICanBoogie\Core|Binding\CoreBindings $app
29
 *
30
 * @property-read string $name The formatted name of the user.
31
 * @property-read boolean $is_admin true if the user is admin, false otherwise.
32
 * @property-read boolean $is_guest true if the user is a guest, false otherwise.
33
 *
34
 * @property-read string $password_hash The password hash.
35
 * @property-read bool|null $has_legacy_password_hash Whether the password hash is a legacy hash.
36
 * {@link User::get_has_legacy_password_hash()}.
37
 */
38
class User extends ActiveRecord implements CSSClassNames
39
{
40
	use IcybeeBindings, RegistryBindings;
41
	use CreatedAtProperty, LoggedAtProperty, CSSClassNamesProperty;
42
	use PasswordTrait;
43
44
	const MODEL_ID = 'users';
45
46
	const UID = 'uid';
47
	const EMAIL = 'email';
48
	const PASSWORD = 'password';
49
	const PASSWORD_HASH = 'password_hash';
50
	const PASSWORD_VERIFY = 'password-verify';
51
	const USERNAME = 'username';
52
	const FIRSTNAME = 'firstname';
53
	const LASTNAME = 'lastname';
54
	const NICKNAME = 'nickname';
55
	const CREATED_AT = 'created_at';
56
	const LOGGED_AT = 'logged_at';
57
	const CONSTRUCTOR = 'constructor';
58
	const LANGUAGE = 'language';
59
	const TIMEZONE = 'timezone';
60
	const IS_ACTIVATED = 'is_activated';
61
	const ROLES = 'roles';
62
	const RESTRICTED_SITES = 'restricted_sites';
63
64
	const NAME_AS = 'name_as';
65
66
	/**
67
	 * The {@link $name} property should be created from `$username`.
68
	 *
69
	 * @var int
70
	 */
71
	const NAME_AS_USERNAME = 0;
72
73
	/**
74
	 * The {@link $name} property should be created from `$firstname`.
75
	 *
76
	 * @var int
77
	 */
78
	const NAME_AS_FIRSTNAME = 1;
79
80
	/**
81
	 * The {@link $name} property should be created from `$lastname`.
82
	 *
83
	 * @var int
84
	 */
85
	const NAME_AS_LASTNAME = 2;
86
87
	/**
88
	 * The {@link $name} property should be created from `$firstname $lastname`.
89
	 *
90
	 * @var int
91
	 */
92
	const NAME_AS_FIRSTNAME_LASTNAME = 3;
93
94
	/**
95
	 * The {@link $name} property should be created from `$lastname $firstname`.
96
	 *
97
	 * @var int
98
	 */
99
	const NAME_AS_LASTNAME_FIRSTNAME = 4;
100
101
	/**
102
	 * The {@link $name} property should be created from `$nickname`.
103
	 *
104
	 * @var int
105
	 */
106
	const NAME_AS_NICKNAME = 5;
107
108
	/**
109
	 * @inheritdoc
110
	 *
111
	 * The method takes care of setting the {@link password_hash} property which is not
112
	 * settable otherwise.
113
	 *
114
	 * @return static
115
	 */
116
	static public function from($properties = null, array $construct_args = [], $class_name = null)
117
	{
118
		if (!is_array($properties) || !array_key_exists('password_hash', $properties))
119
		{
120
			return parent::from($properties, $construct_args, $class_name);
121
		}
122
123
		$password_hash = $properties['password_hash'];
124
		unset($properties['password_hash']);
125
		$instance = parent::from($properties, $construct_args);
126
		$instance->password_hash = $password_hash;
127
128
		return $instance;
129
	}
130
131
	/**
132
	 * User identifier.
133
	 *
134
	 * @var string
135
	 */
136
	public $uid;
137
138
	/**
139
	 * Constructor of the user record (module id).
140
	 *
141
	 * The property MUST be defined to persist the record.
142
	 *
143
	 * @var string
144
	 */
145
	public $constructor;
146
147
	/**
148
	 * User email.
149
	 *
150
	 * The property MUST be defined to persist the record.
151
	 *
152
	 * @var string
153
	 */
154
	public $email;
155
156
	/**
157
	 * Username of the user.
158
	 *
159
	 * The property MUST be defined to persist the record.
160
	 *
161
	 * @var string
162
	 */
163
	public $username;
164
165
	/**
166
	 * First name of the user.
167
	 *
168
	 * @var string
169
	 */
170
	public $firstname = '';
171
172
	/**
173
	 * Last name of the user.
174
	 *
175
	 * @var string
176
	 */
177
	public $lastname = '';
178
179
	/**
180
	 * Nickname of the user.
181
	 *
182
	 * @var string
183
	 */
184
	public $nickname = '';
185
186
	/**
187
	 * Preferred format to create the value of the {@link $name} property.
188
	 *
189
	 * @var string
190
	 */
191
	public $name_as = self::NAME_AS_USERNAME;
192
193
	/**
194
	 * Preferred language of the user.
195
	 *
196
	 * @var string
197
	 */
198
	public $language = '';
199
200
	/**
201
	 * Preferred timezone of the user.
202
	 *
203
	 * @var string
204
	 */
205
	public $timezone = '';
206
207
	/**
208
	 * State of the user account activation.
209
	 *
210
	 * @var bool
211
	 */
212
	public $is_activated = false;
213
214
	/**
215
	 * If empty, the {@link $constructor} property is initialized with the model identifier.
216
	 *
217
	 * @inheritdoc
218
	 */
219
	public function __construct($model = null)
220
	{
221
		parent::__construct($model);
222
223
		if (empty($this->constructor))
224
		{
225
			$this->constructor = $this->model_id;
226
		}
227
	}
228
229
	/**
230
	 * @inheritdoc
231
	 */
232
	public function __get($property)
233
	{
234
		$value = parent::__get($property);
235
236
		if ($property === 'css_class_names')
237
		{
238
			new AlterCSSClassNamesEvent($this, $value);
239
		}
240
241
		return $value;
242
	}
243
244
	/**
245
	 * @inheritdoc
246
	 */
247
	public function create_validation_rules()
248
	{
249
		return [
250
251
			'username' => 'required',
252
			'email' => 'required|email|unique',
253
			'timezone' => 'timezone'
254
255
		];
256
	}
257
258
	/**
259
	 * @inheritdoc
260
	 */
261
	public function save(array $options = [])
262
	{
263
		if ($this->get_created_at()->is_empty)
264
		{
265
			$this->set_created_at('now');
266
		}
267
268
		return parent::save($options);
269
	}
270
271
	/**
272
	 * Adds the {@link $password_hash} property.
273
	 */
274
	public function to_array()
275
	{
276
		$array = parent::to_array();
277
278
		if ($this->password_hash)
279
		{
280
			$array['password_hash'] = $this->password_hash;
281
		}
282
283
		return $array;
284
	}
285
286
	/**
287
	 * Returns the formatted name of the user.
288
	 *
289
	 * The format of the name is defined by the {@link $name_as} property. The {@link $username},
290
	 * {@link $firstname}, {@link $lastname} and {@link $nickname} properties can be used to
291
	 * format the name.
292
	 *
293
	 * This is the getter for the {@link $name} magic property.
294
	 *
295
	 * @return string
296
	 */
297
	protected function get_name()
298
	{
299
		$values = [
300
301
			self::NAME_AS_USERNAME => $this->username,
302
			self::NAME_AS_FIRSTNAME => $this->firstname,
303
			self::NAME_AS_LASTNAME => $this->lastname,
304
			self::NAME_AS_FIRSTNAME_LASTNAME => $this->firstname . ' ' . $this->lastname,
305
			self::NAME_AS_LASTNAME_FIRSTNAME => $this->lastname . ' ' . $this->firstname,
306
			self::NAME_AS_NICKNAME => $this->nickname
307
308
		];
309
310
		$rc = isset($values[$this->name_as]) ? $values[$this->name_as] : null;
311
312
		if (!trim($rc))
313
		{
314
			return $this->username;
315
		}
316
317
		return $rc;
318
	}
319
320
	/**
321
	 * Checks if the user is the admin user.
322
	 *
323
	 * This is the getter for the {@link $is_admin} magic property.
324
	 *
325
	 * @return boolean `true` if the user is the admin user, `false` otherwise.
326
	 */
327
	protected function get_is_admin()
328
	{
329
		return $this->uid == 1;
330
	}
331
332
	/**
333
	 * Checks if the user is a guest user.
334
	 *
335
	 * This is the getter for the {@link $is_guest} magic property.
336
	 *
337
	 * @return boolean `true` if the user is a guest user, `false` otherwise.
338
	 */
339
	protected function get_is_guest()
340
	{
341
		return !$this->uid;
342
	}
343
344
	/**
345
	 * Returns the ids of the sites the user is restricted to.
346
	 *
347
	 * This is the getter for the {@link $restricted_sites_ids} magic property.
348
	 *
349
	 * @return array The array is empty if the user has no site restriction.
350
	 */
351
	protected function lazy_get_restricted_sites_ids()
352
	{
353
		if ($this->is_admin)
354
		{
355
			return [];
356
		}
357
358
		return $this->model->models['users/has_many_sites']
359
		->select('site_id')
360
		->filter_by_uid($this->uid)
361
		->all(\PDO::FETCH_COLUMN);
362
	}
363
364
	/**
365
	 * Checks if the user has a given permission.
366
	 *
367
	 * @param string|int $permission
368
	 * @param mixed $target
369
	 *
370
	 * @return mixed
371
	 */
372
	public function has_permission($permission, $target = null)
373
	{
374
		if ($this->is_admin)
375
		{
376
			return Module::PERMISSION_ADMINISTER;
377
		}
378
379
		return $this->app->check_user_permission($this, $permission, $target);
380
	}
381
382
	/**
383
	 * Checks if the user has the ownership of an entry.
384
	 *
385
	 * If the ownership information is missing from the entry (the 'uid' property is null), the user
386
	 * must have the ADMINISTER level to be considered the owner.
387
	 *
388
	 * @param ActiveRecord $record
389
	 *
390
	 * @return boolean
391
	 */
392
	public function has_ownership($record)
393
	{
394
		return $this->app->check_user_ownership($this, $record);
395
	}
396
397
	/**
398
	 * Logs the user in.
399
	 *
400
	 * A user is logged in by setting its id in the `user_id` session key.
401
	 *
402
	 * Note: The method does *not* check user authentication!
403
	 *
404
	 * The following things happen when the user is logged in:
405
	 *
406
	 * - The `$app->user` property is set to the user.
407
	 * - The `$app->user_id` property is set to the user id.
408
	 * - The session id is regenerated and the user id, ip and user agent are stored in the session.
409
	 *
410
	 * @throws \Exception in attempt to log in a guest user.
411
	 *
412
	 * @see \Icybee\Modules\Users\Hooks\get_user_id
413
	 */
414
	public function login()
415
	{
416
		if (!$this->uid)
417
		{
418
			throw new \Exception('Guest users cannot login.');
419
		}
420
421
		$app = $this->app;
422
		$app->user = $this;
423
		$app->user_id = $this->uid;
424
		$app->session->regenerate();
425
		$app->session['user_id'] = $this->uid;
426
	}
427
428
	/**
429
	 * Log the user out.
430
	 *
431
	 * The following things happen when the user is logged out:
432
	 *
433
	 * - The `$app->user` property is unset.
434
	 * - The `$app->user_id` property is unset.
435
	 * - The `user_id` session property is removed.
436
	 */
437
	public function logout()
438
	{
439
		$app = $this->app;
440
		$app->session->regenerate();
441
442
		unset($app->user);
443
		unset($app->user_id);
444
		unset($app->session['user_id']);
445
	}
446
447
	/**
448
	 * @inheritdoc
449
	 */
450
	protected function get_css_class_names()
451
	{
452
		return [
453
454
			'type' => 'user',
455
			'id' => ($this->uid && !$this->is_guest) ? 'user-id-' . $this->uid : null,
456
			'username' => ($this->username && !$this->is_guest) ? 'user-' . $this->username : null,
457
			'constructor' => 'constructor-' . \ICanBoogie\normalize($this->constructor),
458
			'is-admin' => $this->is_admin,
459
			'is-guest' => $this->is_guest,
460
			'is-logged' => !$this->is_guest
461
462
		];
463
	}
464
}
465