Completed
Push — master ( 265ecc...a2d6d1 )
by Nazar
04:54 queued 46s
created

Management   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 400
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 99.42%

Importance

Changes 0
Metric Value
dl 0
loc 400
ccs 170
cts 171
cp 0.9942
rs 8.3206
c 0
b 0
f 0
wmc 51
lcom 1
cbo 6

10 Methods

Rating   Name   Duplication   Size   Complexity  
A delete_unconfirmed_users() 0 14 2
A set_password() 0 13 3
B validate_password() 0 21 6
A search_users() 0 20 2
B registration_confirmation() 0 58 6
D registration() 0 104 16
B del_user() 0 37 5
A registration_cancel() 0 7 2
B restore_password() 0 12 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-2016, 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
			"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 2
				`status` != '%2\$s'",
57
			$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>
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|string|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
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 18
	public function registration ($email, $confirmation = true, $auto_sign_in = true) {
85 18
		if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
86 4
			return false;
87
		}
88 18
		$email      = mb_strtolower($email);
89 18
		$email_hash = hash('sha224', $email);
90 18
		$login      = strstr($email, '@', true);
91 18
		$login_hash = hash('sha224', $login);
92
		if (
93 18
			$this->get_id($login_hash) !== false ||
94
			(
95 18
				$login &&
96 18
				in_array($login, file_get_json(MODULES.'/System/index.json')['profile'])
97
			)
98
		) {
99 2
			$login      = $email;
100 2
			$login_hash = $email_hash;
101
		}
102 18
		if ($this->db_prime()->qf(
103
			"SELECT `id`
104
			FROM `[prefix]users`
105
			WHERE `email_hash` = '%s'
106 18
			LIMIT 1",
107
			$email_hash
108
		)
109
		) {
110 2
			return 'exists';
111
		}
112 18
		$this->delete_unconfirmed_users();
113 18
		if (!Event::instance()->fire(
114 18
			'System/User/registration/before',
115
			[
116 18
				'email' => $email
117
			]
118
		)
119
		) {
120 2
			return false;
121
		}
122 18
		$Config       = Config::instance();
123 18
		$confirmation = $confirmation && $Config->core['require_registration_confirmation'];
124 18
		$reg_key      = md5(random_bytes(1000));
125 18
		if ($this->db_prime()->q(
126
			"INSERT INTO `[prefix]users` (
127
				`login`,
128
				`login_hash`,
129
				`email`,
130
				`email_hash`,
131
				`reg_date`,
132
				`reg_ip`,
133
				`reg_key`,
134
				`status`
135
			) VALUES (
136
				'%s',
137
				'%s',
138
				'%s',
139
				'%s',
140
				'%s',
141
				'%s',
142
				'%s',
143
				'%s'
144 18
			)",
145
			$login,
146
			$login_hash,
147
			$email,
148
			$email_hash,
149
			time(),
150 18
			ip2hex(Request::instance()->ip),
151
			$reg_key,
152 18
			!$confirmation ? 1 : -1
153
		)
154
		) {
155 18
			$this->reg_id = (int)$this->db_prime()->id();
156 18
			$password     = '';
157 18
			if (!$confirmation) {
158 16
				$password = password_generate($Config->core['password_min_length'], $Config->core['password_min_strength']);
159 16
				$this->set_password($password, $this->reg_id);
160 16
				$this->set_groups([User::USER_GROUP_ID], $this->reg_id);
161 16
				if ($auto_sign_in && $Config->core['auto_sign_in_after_registration']) {
162 10
					Session::instance()->add($this->reg_id);
163
				}
164
			}
165 18
			if (!Event::instance()->fire(
166 18
				'System/User/registration/after',
167
				[
168 18
					'id' => $this->reg_id
169
				]
170
			)
171
			) {
172 2
				$this->registration_cancel();
173 2
				return false;
174
			}
175 18
			if (!$confirmation) {
176 16
				$this->set_groups([User::USER_GROUP_ID], $this->reg_id);
177
			}
178 18
			unset($this->cache->$login_hash);
179
			return [
180 18
				'reg_key'  => !$confirmation ? true : $reg_key,
181 18
				'password' => $password,
182 18
				'id'       => $this->reg_id
183
			];
184
		} else {
185
			return 'error';
186
		}
187
	}
188
	/**
189
	 * Confirmation of registration process
190
	 *
191
	 * @param string $reg_key
192
	 *
193
	 * @return array|false ['id' => <i>id</i>, 'email' => <i>email</i>, 'password' => <i>password</i>] or <b>false</b> on failure
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
194
	 */
195 2
	public function registration_confirmation ($reg_key) {
196 2
		if (!is_md5($reg_key)) {
197 2
			return false;
198
		}
199 2
		if (!Event::instance()->fire(
200 2
			'System/User/registration/confirmation/before',
201
			[
202 2
				'reg_key' => $reg_key
203
			]
204
		)
205
		) {
206 2
			$this->registration_cancel();
207 2
			return false;
208
		}
209 2
		$this->delete_unconfirmed_users();
210 2
		$data = $this->db_prime()->qf(
211
			"SELECT
212
				`id`,
213
				`login_hash`,
214
				`email`
215
			FROM `[prefix]users`
216
			WHERE
217
				`reg_key`	= '%s' AND
218
				`status`	= '%s'
219 2
			LIMIT 1",
220
			$reg_key,
221 2
			User::STATUS_NOT_ACTIVATED
222
		);
223 2
		if (!$data) {
224 2
			return false;
225
		}
226 2
		$this->reg_id = (int)$data['id'];
227 2
		$Config       = Config::instance();
228 2
		$password     = '';
229 2
		if (!$this->get('password_hash', $this->reg_id)) {
230 2
			$password = password_generate($Config->core['password_min_length'], $Config->core['password_min_strength']);
231 2
			$this->set_password($password, $this->reg_id);
232
		}
233 2
		$this->set('status', User::STATUS_ACTIVE, $this->reg_id);
234 2
		$this->set_groups([User::USER_GROUP_ID], $this->reg_id);
235 2
		Session::instance()->add($this->reg_id);
236 2
		if (!Event::instance()->fire(
237 2
			'System/User/registration/confirmation/after',
238
			[
239 2
				'id' => $this->reg_id
240
			]
241
		)
242
		) {
243 2
			$this->registration_cancel();
244 2
			return false;
245
		}
246 2
		unset($this->cache->{$data['login_hash']});
247
		return [
248 2
			'id'       => $this->reg_id,
249 2
			'email'    => $data['email'],
250 2
			'password' => $password
251
		];
252
	}
253
	/**
254
	 * Canceling of bad/failed registration
255
	 */
256 4
	public function registration_cancel () {
257 4
		if ($this->reg_id) {
258 4
			Session::instance()->add(User::GUEST_ID);
259 4
			$this->del_user($this->reg_id);
260 4
			$this->reg_id = 0;
261
		}
262 4
	}
263
	/**
264
	 * Checks for unconfirmed registrations and deletes expired
265
	 */
266 18
	protected function delete_unconfirmed_users () {
267 18
		$reg_date = time() - Config::instance()->core['registration_confirmation_time'] * 86400;    //1 day = 86400 seconds
268 18
		$ids      = $this->db_prime()->qfas(
269
			"SELECT `id`
270
			FROM `[prefix]users`
271
			WHERE
272
				`status`	= '%s' AND
273 18
				`reg_date`	< $reg_date",
274 18
			User::STATUS_NOT_ACTIVATED
275
		);
276 18
		if ($ids) {
277 2
			$this->del_user($ids);
278
		}
279 18
	}
280
	/**
281
	 * Proper password setting without any need to deal with low-level implementation
282
	 *
283
	 * @param string    $new_password
284
	 * @param false|int $user
285
	 * @param bool      $already_prepared If true - assumed that `sha512(sha512(password) + public_key)` was applied to password
286
	 *
287
	 * @return bool
288
	 */
289 18
	public function set_password ($new_password, $user = false, $already_prepared = false) {
290 18
		$public_key = Core::instance()->public_key;
291 18
		if (!$already_prepared) {
292 18
			$new_password = hash('sha512', hash('sha512', $new_password).$public_key);
293
		}
294
		/**
295
		 * Do not allow to set password to empty
296
		 */
297 18
		if ($new_password == hash('sha512', hash('sha512', '').$public_key)) {
298 2
			return false;
299
		}
300 18
		return $this->set('password_hash', password_hash($new_password, PASSWORD_DEFAULT), $user);
301
	}
302
	/**
303
	 * Proper password validation without any need to deal with low-level implementation
304
	 *
305
	 * @param string    $password
306
	 * @param false|int $user
307
	 * @param bool      $already_prepared If true - assumed that `sha512(sha512(password) + public_key)` was applied to password
308
	 *
309
	 * @return bool
310
	 */
311 4
	public function validate_password ($password, $user = false, $already_prepared = false) {
312 4
		if (!$already_prepared) {
313 2
			$password = hash('sha512', hash('sha512', $password).Core::instance()->public_key);
314
		}
315 4
		$user          = (int)$user ?: $this->id;
316 4
		$password_hash = $this->get('password_hash', $user);
317 4
		if (!password_verify($password, $password_hash)) {
318 2
			return false;
319
		}
320
		/**
321
		 * Rehash password if needed
322
		 */
323 4
		if (password_needs_rehash($password_hash, PASSWORD_DEFAULT)) {
324 2
			$current_user_id = $this->id;
325 2
			$this->set_password($password, $user, true);
326 2
			if ($current_user_id == $user) {
327 2
				Session::instance()->add($current_user_id);
328
			}
329
		}
330 4
		return true;
331
	}
332
	/**
333
	 * Restoring of password
334
	 *
335
	 * @param int $user
336
	 *
337
	 * @return false|string Key for confirmation or <b>false</b> on failure
338
	 */
339 2
	public function restore_password ($user) {
340 2
		if ($user && $user != User::GUEST_ID) {
341 2
			$reg_key = md5(random_bytes(1000));
342 2
			if ($this->set('reg_key', $reg_key, $user)) {
343 2
				$restore_until = time() + Config::instance()->core['registration_confirmation_time'] * 86400;
344 2
				if ($this->set_data('restore_until', $restore_until, $user)) {
345 2
					return $reg_key;
346
				}
347
			}
348
		}
349 2
		return false;
350
	}
351
	/**
352
	 * Confirmation of password restoring process
353
	 *
354
	 * @param string $key
355
	 *
356
	 * @return array|false ['id' => <i>id</i>, 'password' => <i>password</i>] or <b>false</b> on failure
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
357
	 */
358 2
	public function restore_password_confirmation ($key) {
359 2
		if (!is_md5($key)) {
360 2
			return false;
361
		}
362 2
		$id = (int)$this->db_prime()->qfs(
363
			"SELECT `id`
364
			FROM `[prefix]users`
365
			WHERE
366
				`reg_key`	= '%s' AND
367
				`status`	= '%s'
368 2
			LIMIT 1",
369
			$key,
370 2
			User::STATUS_ACTIVE
371
		);
372 2
		if (!$id) {
373 2
			return false;
374
		}
375 2
		$restore_until = $this->get_data('restore_until', $id);
376 2
		$this->del_data('restore_until', $id);
377 2
		if ($restore_until < time()) {
378 2
			return false;
379
		}
380 2
		$Config   = Config::instance();
381 2
		$password = password_generate($Config->core['password_min_length'], $Config->core['password_min_strength']);
382 2
		$this->set_password($password, $id);
383 2
		Session::instance()->add($id);
384
		return [
385 2
			'id'       => $id,
386 2
			'password' => $password
387
		];
388
	}
389
	/**
390
	 * Delete specified user or array of users
391
	 *
392
	 * @param int|int[] $user User id or array of users ids
393
	 */
394 6
	public function del_user ($user) {
395 6
		$this->disable_memory_cache();
396 6
		if (is_array($user)) {
397 2
			foreach ($user as $id) {
398 2
				$this->del_user($id);
399
			}
400 2
			return;
401
		}
402 6
		$user = (int)$user;
403 6
		if (in_array($user, [0, User::GUEST_ID, User::ROOT_ID]) || !$this->get('id', $user)) {
404 2
			return;
405
		}
406 6
		Event::instance()->fire(
407 6
			'System/User/del/before',
408
			[
409 6
				'id' => $user
410
			]
411
		);
412 6
		$this->set_groups([], $user);
413 6
		$this->del_permissions_all($user);
414 6
		$Cache      = $this->cache;
415 6
		$login_hash = hash('sha224', $this->get('login', $user));
416 6
		$this->db_prime()->q(
417
			"DELETE FROM `[prefix]users`
418 6
			WHERE `id` = $user"
419
		);
420
		unset(
421 6
			$Cache->$login_hash,
422 6
			$Cache->$user
423
		);
424 6
		Event::instance()->fire(
425 6
			'System/User/del/after',
426
			[
427 6
				'id' => $user
428
			]
429
		);
430 6
	}
431
}
432