Completed
Push — master ( bf102e...fafcea )
by Nazar
06:46
created

Management   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 430
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 99.47%

Importance

Changes 0
Metric Value
dl 0
loc 430
ccs 187
cts 188
cp 0.9947
rs 7.4757
c 0
b 0
f 0
wmc 53
lcom 1
cbo 6

13 Methods

Rating   Name   Duplication   Size   Complexity  
A registration_login_already_occupied() 0 8 3
A activate_registered_user() 0 13 3
A registration_cancel() 0 7 2
A delete_unconfirmed_users() 0 14 2
A set_password() 0 13 3
B validate_password() 0 21 6
B restore_password() 0 12 5
A registration_get_login_login_hash() 0 9 2
B del_user() 0 47 5
A search_users() 0 20 2
C registration() 0 81 11
B registration_confirmation() 0 51 5
B restore_password_confirmation() 0 31 4

How to fix   Complexity   

Complex Class

Complex classes like Management 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Management, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2011-2017, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs\User;
9
use
10
	cs\Config,
11
	cs\Core,
12
	cs\Event,
13
	cs\Request,
14
	cs\Session,
15
	cs\User;
16
17
/**
18
 * Trait that contains all methods from <i>>cs\User</i> for working with user management (creation, modification, deletion)
19
 *
20
 * @property int              $id
21
 * @property \cs\Cache\Prefix $cache
22
 * @property string           $ip
23
 *
24
 * @method \cs\DB\_Abstract db()
25
 * @method \cs\DB\_Abstract db_prime()
26
 * @method false|int        get_id(string $login_hash)
27
 * @method bool             set_groups(int[] $groups, false|int $user = false)
28
 * @method false|string     get(array|string $item, false|int $user = false)
29
 * @method bool             set(array|string $item, mixed|null $value = null, false|int $user = false)
30
 * @method bool             del_permissions_all(false|int $user = false)
31
 */
32
trait Management {
33
	/**
34
	 * User id after registration
35
	 * @var int
36
	 */
37
	protected $reg_id = 0;
38
	/**
39
	 * Search keyword in login, username and email
40
	 *
41
	 * @param string $search_phrase
42
	 *
43
	 * @return false|int[]
44
	 */
45 2
	public function search_users ($search_phrase) {
46 2
		$search_phrase = trim($search_phrase, "%\n");
47 2
		$found_users   = $this->db()->qfas(
48 2
			"SELECT `id`
49
			FROM `[prefix]users`
50
			WHERE
51
				(
52
					`login`		LIKE '%1\$s' OR
53
					`username`	LIKE '%1\$s' OR
54
					`email`		LIKE '%1\$s'
55
				) AND
56
				`status` != '%2\$s'",
57 2
			$search_phrase,
58 2
			User::STATUS_NOT_ACTIVATED
59
		);
60 2
		if (!$found_users) {
61 2
			return false;
62
		}
63 2
		return _int($found_users);
64
	}
65
	/**
66
	 * User registration
67
	 *
68
	 * @param string $email
69
	 * @param bool   $confirmation If <b>true</b> - default system option is used, if <b>false</b> - registration will be finished without necessity of
70
	 *                             confirmation, independently from default system option (is used for manual registration).
71
	 * @param bool   $auto_sign_in If <b>false</b> - no auto sign in, if <b>true</b> - according to system configuration
72
	 *
73
	 * @return array|false|string  <b>exists</b> - if user with such email is already registered<br>
74
	 *                             <b>error</b> - if error occurred<br>
75
	 *                             <b>false</b> - if email is incorrect<br>
76
	 *                             <b>[<br>
77
	 *                             &nbsp;&nbsp;&nbsp;&nbsp;'reg_key'     => *,</b> //Registration confirmation key, or <b>true</b> if confirmation is not
78
	 *                             required<br>
79
	 *                             &nbsp;&nbsp;&nbsp;&nbsp;<b>'password' => *,</b> //Automatically generated password (empty if confirmation is needed and
80
	 *                             will be set during registration confirmation)<br>
81
	 *                             &nbsp;&nbsp;&nbsp;&nbsp;<b>'id'       => *</b>  //Id of registered user in DB<br>
82
	 *                             <b>]</b>
83
	 */
84 28
	public function registration ($email, $confirmation = true, $auto_sign_in = true) {
85 28
		if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
86 6
			return false;
87
		}
88 28
		$email      = mb_strtolower($email);
89 28
		$email_hash = hash('sha224', $email);
90 28
		if ($this->get_id($email_hash)) {
91 4
			return 'exists';
92
		}
93 28
		list($login, $login_hash) = $this->registration_get_login_login_hash($email);
94 28
		$this->delete_unconfirmed_users();
95 28
		if (!Event::instance()->fire(
96 28
			'System/User/registration/before',
97
			[
98 28
				'email' => $email
99
			]
100
		)
101
		) {
102 2
			return false;
103
		}
104 28
		$Config       = Config::instance();
105 28
		$confirmation = $confirmation && $Config->core['require_registration_confirmation'];
106 28
		$reg_key      = md5(random_bytes(1000));
107 28
		if ($this->db_prime()->q(
108 28
			"INSERT INTO `[prefix]users` (
109
				`login`,
110
				`login_hash`,
111
				`email`,
112
				`email_hash`,
113
				`reg_date`,
114
				`reg_ip`,
115
				`reg_key`,
116
				`status`
117
			) VALUES (
118
				'%s',
119
				'%s',
120
				'%s',
121
				'%s',
122
				'%s',
123
				'%s',
124
				'%s',
125
				'%s'
126
			)",
127 28
			$login,
128 28
			$login_hash,
129 28
			$email,
130 28
			$email_hash,
131 28
			time(),
132 28
			ip2hex(Request::instance()->ip),
133 28
			$reg_key,
134 28
			User::STATUS_NOT_ACTIVATED
135
		)
136
		) {
137 28
			$this->reg_id = (int)$this->db_prime()->id();
138 28
			$password     = '';
139 28
			if (!$confirmation) {
140 26
				$password = $this->activate_registered_user($Config, $this->reg_id, $auto_sign_in && $Config->core['auto_sign_in_after_registration']);
141
			}
142 28
			if (!Event::instance()->fire(
143 28
				'System/User/registration/after',
144
				[
145 28
					'id' => $this->reg_id
146
				]
147
			)
148
			) {
149 2
				$this->registration_cancel();
150 2
				return false;
151
			}
152 28
			if (!$confirmation) {
153 26
				$this->set_groups([User::USER_GROUP_ID], $this->reg_id);
154
			}
155 28
			unset($this->cache->$login_hash);
156
			return [
157 28
				'reg_key'  => !$confirmation ? true : $reg_key,
158 28
				'password' => $password,
159 28
				'id'       => $this->reg_id
160
			];
161
		} else {
162
			return 'error';
163
		}
164
	}
165
	/**
166
	 * @param string $email
167
	 *
168
	 * @return string[]
169
	 */
170 28
	protected function registration_get_login_login_hash ($email) {
171 28
		$login      = strstr($email, '@', true);
172 28
		$login_hash = hash('sha224', $login);
173 28
		if ($this->registration_login_already_occupied($login, $login_hash)) {
174 2
			$login      = $email;
175 2
			$login_hash = hash('sha224', $login);
176
		}
177 28
		return [$login, $login_hash];
178
	}
179
	/**
180
	 * @param string $login
181
	 * @param string $login_hash
182
	 *
183
	 * @return bool
184
	 */
185 28
	protected function registration_login_already_occupied ($login, $login_hash) {
186
		return
187 28
			$this->get_id($login_hash) !== false ||
188
			(
189 28
				$login &&
190 28
				in_array($login, file_get_json(MODULES.'/System/index.json')['profile'])
191
			);
192
	}
193
	/**
194
	 * @param Config $Config
195
	 * @param int    $id
196
	 * @param bool   $create_session
197
	 *
198
	 * @return string
199
	 */
200 28
	protected function activate_registered_user ($Config, $id, $create_session) {
201 28
		$password = '';
202 28
		if (!$this->get('password_hash', $id)) {
203 28
			$password = password_generate($Config->core['password_min_length'], $Config->core['password_min_strength']);
204 28
			$this->set_password($password, $id);
205
		}
206 28
		$this->set('status', User::STATUS_ACTIVE, $id);
207 28
		$this->set_groups([User::USER_GROUP_ID], $id);
208 28
		if ($create_session) {
209 14
			Session::instance()->add($id);
210
		}
211 28
		return $password;
212
	}
213
	/**
214
	 * Confirmation of registration process
215
	 *
216
	 * @param string $reg_key
217
	 *
218
	 * @return array|false ['id' => <i>id</i>, 'email' => <i>email</i>, 'password' => <i>password</i>] or <b>false</b> on failure
219
	 */
220 2
	public function registration_confirmation ($reg_key) {
221 2
		if (!is_md5($reg_key)) {
222 2
			return false;
223
		}
224 2
		if (!Event::instance()->fire(
225 2
			'System/User/registration/confirmation/before',
226
			[
227 2
				'reg_key' => $reg_key
228
			]
229
		)
230
		) {
231 2
			$this->registration_cancel();
232 2
			return false;
233
		}
234 2
		$this->delete_unconfirmed_users();
235 2
		$data = $this->db_prime()->qf(
236 2
			"SELECT
237
				`id`,
238
				`login_hash`,
239
				`email`
240
			FROM `[prefix]users`
241
			WHERE
242
				`reg_key`	= '%s' AND
243
				`status`	= '%s'
244
			LIMIT 1",
245 2
			$reg_key,
246 2
			User::STATUS_NOT_ACTIVATED
247
		);
248 2
		if (!$data) {
249 2
			return false;
250
		}
251 2
		$this->reg_id = (int)$data['id'];
252 2
		$Config       = Config::instance();
253 2
		$password     = $this->activate_registered_user($Config, $this->reg_id, true);
254 2
		if (!Event::instance()->fire(
255 2
			'System/User/registration/confirmation/after',
256
			[
257 2
				'id' => $this->reg_id
258
			]
259
		)
260
		) {
261 2
			$this->registration_cancel();
262 2
			return false;
263
		}
264 2
		unset($this->cache->{$data['login_hash']});
265
		return [
266 2
			'id'       => $this->reg_id,
267 2
			'email'    => $data['email'],
268 2
			'password' => $password
269
		];
270
	}
271
	/**
272
	 * Canceling of bad/failed registration
273
	 */
274 6
	public function registration_cancel () {
275 6
		if ($this->reg_id) {
276 6
			Session::instance()->add(User::GUEST_ID);
277 6
			$this->del_user($this->reg_id);
278 6
			$this->reg_id = 0;
279
		}
280 6
	}
281
	/**
282
	 * Checks for unconfirmed registrations and deletes expired
283
	 */
284 28
	protected function delete_unconfirmed_users () {
285 28
		$reg_date = time() - Config::instance()->core['registration_confirmation_time'] * 86400;    //1 day = 86400 seconds
286 28
		$ids      = $this->db_prime()->qfas(
287
			"SELECT `id`
288
			FROM `[prefix]users`
289
			WHERE
290
				`status`	= '%s' AND
291 28
				`reg_date`	< $reg_date",
292 28
			User::STATUS_NOT_ACTIVATED
293
		);
294 28
		if ($ids) {
295 2
			$this->del_user($ids);
296
		}
297 28
	}
298
	/**
299
	 * Proper password setting without any need to deal with low-level implementation
300
	 *
301
	 * @param string    $new_password
302
	 * @param false|int $user
303
	 * @param bool      $already_prepared If true - assumed that `sha512(sha512(password) + public_key)` was applied to password
304
	 *
305
	 * @return bool
306
	 */
307 28
	public function set_password ($new_password, $user = false, $already_prepared = false) {
308 28
		$public_key = Core::instance()->public_key;
309 28
		if (!$already_prepared) {
310 28
			$new_password = hash('sha512', hash('sha512', $new_password).$public_key);
311
		}
312
		/**
313
		 * Do not allow to set password to empty
314
		 */
315 28
		if ($new_password == hash('sha512', hash('sha512', '').$public_key)) {
316 4
			return false;
317
		}
318 28
		return $this->set('password_hash', password_hash($new_password, PASSWORD_DEFAULT), $user);
319
	}
320
	/**
321
	 * Proper password validation without any need to deal with low-level implementation
322
	 *
323
	 * @param string    $password
324
	 * @param false|int $user
325
	 * @param bool      $already_prepared If true - assumed that `sha512(sha512(password) + public_key)` was applied to password
326
	 *
327
	 * @return bool
328
	 */
329 8
	public function validate_password ($password, $user = false, $already_prepared = false) {
330 8
		if (!$already_prepared) {
331 4
			$password = hash('sha512', hash('sha512', $password).Core::instance()->public_key);
332
		}
333 8
		$user          = (int)$user ?: $this->id;
334 8
		$password_hash = $this->get('password_hash', $user);
335 8
		if (!password_verify($password, $password_hash)) {
336 6
			return false;
337
		}
338
		/**
339
		 * Rehash password if needed
340
		 */
341 8
		if (password_needs_rehash($password_hash, PASSWORD_DEFAULT)) {
342 2
			$current_user_id = $this->id;
343 2
			$this->set_password($password, $user, true);
344 2
			if ($current_user_id == $user) {
345 2
				Session::instance()->add($current_user_id);
346
			}
347
		}
348 8
		return true;
349
	}
350
	/**
351
	 * Restoring of password
352
	 *
353
	 * @param int $user
354
	 *
355
	 * @return false|string Key for confirmation or <b>false</b> on failure
356
	 */
357 4
	public function restore_password ($user) {
358 4
		if ($user && $user != User::GUEST_ID) {
359 4
			$reg_key = md5(random_bytes(1000));
360 4
			if ($this->set('reg_key', $reg_key, $user)) {
361 4
				$restore_until = time() + Config::instance()->core['registration_confirmation_time'] * 86400;
362 4
				if ($this->set_data('restore_until', $restore_until, $user)) {
363 4
					return $reg_key;
364
				}
365
			}
366
		}
367 2
		return false;
368
	}
369
	/**
370
	 * Confirmation of password restoring process
371
	 *
372
	 * @param string $key
373
	 *
374
	 * @return array|false ['id' => <i>id</i>, 'password' => <i>password</i>] or <b>false</b> on failure
375
	 */
376 2
	public function restore_password_confirmation ($key) {
377 2
		if (!is_md5($key)) {
378 2
			return false;
379
		}
380 2
		$id = (int)$this->db_prime()->qfs(
381 2
			"SELECT `id`
382
			FROM `[prefix]users`
383
			WHERE
384
				`reg_key`	= '%s' AND
385
				`status`	= '%s'
386
			LIMIT 1",
387 2
			$key,
388 2
			User::STATUS_ACTIVE
389
		);
390 2
		if (!$id) {
391 2
			return false;
392
		}
393 2
		$restore_until = $this->get_data('restore_until', $id);
394 2
		$this->del_data('restore_until', $id);
395 2
		if ($restore_until < time()) {
396 2
			return false;
397
		}
398 2
		$Config   = Config::instance();
399 2
		$password = password_generate($Config->core['password_min_length'], $Config->core['password_min_strength']);
400 2
		$this->set_password($password, $id);
401 2
		Session::instance()->add($id);
402
		return [
403 2
			'id'       => $id,
404 2
			'password' => $password
405
		];
406
	}
407
	/**
408
	 * Delete specified user or array of users
409
	 *
410
	 * @param int|int[] $user User id or array of users ids
411
	 *
412
	 * @return bool
413
	 */
414 16
	public function del_user ($user) {
415 16
		$this->disable_memory_cache();
416 16
		if (is_array($user)) {
417 2
			return array_map_arguments_bool([$this, 'del_user'], $user);
418
		}
419 16
		$user = (int)$user;
420 16
		if (in_array($user, [0, User::GUEST_ID, User::ROOT_ID]) || !$this->get('id', $user)) {
421 2
			return false;
422
		}
423 16
		Event::instance()->fire(
424 16
			'System/User/del/before',
425
			[
426 16
				'id' => $user
427
			]
428
		);
429 16
		$Cache      = $this->cache;
430 16
		$login_hash = $this->get('login_hash', $user);
431 16
		$email_hash = $this->get('email_hash', $user);
432
		/** @noinspection ExceptionsAnnotatingAndHandlingInspection */
433 16
		$result = $this->db_prime()->transaction(
434 16
			function ($cdb) use ($user) {
435
				/**
436
				 * @var \cs\DB\_Abstract $cdb
437
				 */
438 16
				$this->set_groups([], $user);
439 16
				$this->del_permissions_all($user);
440 16
				return $cdb->q(
441
					"DELETE FROM `[prefix]users`
442 16
					WHERE `id` = $user"
443
				);
444 16
			}
445
		);
446 16
		if ($result) {
447
			unset(
448 16
				$Cache->$login_hash,
449 16
				$Cache->$email_hash,
450 16
				$Cache->$user
451
			);
452 16
			Event::instance()->fire(
453 16
				'System/User/del/after',
454
				[
455 16
					'id' => $user
456
				]
457
			);
458
		}
459 16
		return $result;
460
	}
461
}
462