Completed
Push — master ( a8898a...5c0dbc )
by Nazar
04:15
created

Management   C

Complexity

Total Complexity 67

Size/Duplication

Total Lines 453
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 1 Features 2
Metric Value
c 2
b 1
f 2
dl 0
loc 453
ccs 0
cts 261
cp 0
rs 5.7097
wmc 67
lcom 1
cbo 8

21 Methods

Rating   Name   Duplication   Size   Complexity  
A init_session() 0 10 2
B update_user_is() 0 18 5
A admin() 0 3 1
A user() 0 3 1
A guest() 0 3 1
A get_id() 0 3 2
A get_user() 0 3 1
A get() 0 5 1
C get_internal() 0 23 8
A is_good_session() 0 6 3
A is_session_owner() 0 4 2
B is_session_owner_internal() 0 20 7
B load() 0 26 4
A load_initialization() 0 6 1
B is_user_active() 0 44 5
B add() 0 29 6
B create_unique_session() 0 28 3
A del() 0 3 1
B del_internal() 0 31 6
A delete_old_sessions() 0 6 1
B del_all() 0 23 6

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\Session;
9
use
10
	cs\Config,
11
	cs\Event,
12
	cs\Language,
13
	cs\Page,
14
	cs\Request,
15
	cs\Response,
16
	cs\User;
17
18
/**
19
 * @method \cs\DB\_Abstract db()
20
 * @method \cs\DB\_Abstract db_prime()
21
 */
22
trait Management {
23
	/**
24
	 * Id of current session
25
	 *
26
	 * @var false|string
27
	 */
28
	protected $session_id;
29
	/**
30
	 * User id of current session
31
	 *
32
	 * @var int
33
	 */
34
	protected $user_id;
35
	/**
36
	 * @var bool
37
	 */
38
	protected $is_admin;
39
	/**
40
	 * @var bool
41
	 */
42
	protected $is_user;
43
	/**
44
	 * @var bool
45
	 */
46
	protected $is_guest;
47
	/**
48
	 * Use cookie as source of session id, load session
49
	 */
50
	protected function init_session () {
51
		$Request = Request::instance();
52
		/**
53
		 * If session exists
54
		 */
55
		if ($Request->cookie('session')) {
56
			$this->user_id = $this->load();
57
		}
58
		$this->update_user_is();
59
	}
60
	/**
61
	 * Updates information about who is user accessed by methods ::guest() ::user() admin()
62
	 */
63
	protected function update_user_is () {
64
		$this->is_guest = $this->user_id == User::GUEST_ID;
65
		$this->is_user  = false;
66
		$this->is_admin = false;
67
		if ($this->is_guest) {
68
			return;
69
		}
70
		/**
71
		 * Checking of user type
72
		 */
73
		$groups = User::instance()->get_groups($this->user_id) ?: [];
74
		if (in_array(User::ADMIN_GROUP_ID, $groups)) {
75
			$this->is_admin = true;
76
			$this->is_user  = true;
77
		} elseif (in_array(User::USER_GROUP_ID, $groups)) {
78
			$this->is_user = true;
79
		}
80
	}
81
	/**
82
	 * Is admin
83
	 *
84
	 * @return bool
85
	 */
86
	function admin () {
87
		return $this->is_admin;
88
	}
89
	/**
90
	 * Is user
91
	 *
92
	 * @return bool
93
	 */
94
	function user () {
95
		return $this->is_user;
96
	}
97
	/**
98
	 * Is guest
99
	 *
100
	 * @return bool
101
	 */
102
	function guest () {
103
		return $this->is_guest;
104
	}
105
	/**
106
	 * Returns id of current session
107
	 *
108
	 * @return false|string
109
	 */
110
	function get_id () {
111
		return $this->session_id ?: false;
112
	}
113
	/**
114
	 * Returns user id of current session
115
	 *
116
	 * @return int
117
	 */
118
	function get_user () {
119
		return $this->user_id;
120
	}
121
	/**
122
	 * Returns session details by session id
123
	 *
124
	 * @param false|null|string $session_id If `null` - loaded from `$this->session_id`, and if that also empty - from cookies
125
	 *
126
	 * @return false|array
127
	 */
128
	function get ($session_id) {
129
		$session_data = $this->get_internal($session_id);
130
		unset($session_data['data']);
131
		return $session_data;
132
	}
133
	/**
134
	 * @param false|null|string $session_id
135
	 *
136
	 * @return false|array
137
	 */
138
	protected function get_internal ($session_id) {
139
		if (!$session_id) {
140
			if (!$this->session_id) {
141
				$this->session_id = Request::instance()->cookie('session');
142
			}
143
			$session_id = $this->session_id;
144
		}
145
		if (!is_md5($session_id)) {
146
			return false;
147
		}
148
		$data = $this->cache->get(
149
			$session_id,
150
			function () use ($session_id) {
151
				$data = $this->read($session_id);
152
				if (!$data || $data['expire'] <= time()) {
153
					return false;
154
				}
155
				$data['data'] = $data['data'] ?: [];
156
				return $data;
157
			}
158
		);
159
		return $this->is_good_session($data) ? $data : false;
160
	}
161
	/**
162
	 * Check whether session was not expired, user agent and IP corresponds to what is expected and user is actually active
163
	 *
164
	 * @param mixed $session_data
165
	 *
166
	 * @return bool
167
	 */
168
	protected function is_good_session ($session_data) {
169
		return
170
			isset($session_data['expire'], $session_data['user']) &&
171
			$session_data['expire'] > time() &&
172
			$this->is_user_active($session_data['user']);
173
	}
174
	/**
175
	 * Whether session data belongs to current visitor (user agent, remote addr and ip check)
176
	 *
177
	 * @param string $session_id
178
	 * @param string $user_agent
179
	 * @param string $remote_addr
180
	 * @param string $ip
181
	 *
182
	 * @return bool
183
	 */
184
	function is_session_owner ($session_id, $user_agent, $remote_addr, $ip) {
185
		$session_data = $this->get($session_id);
186
		return $session_data ? $this->is_session_owner_internal($session_data, $user_agent, $remote_addr, $ip) : false;
187
	}
188
	/**
189
	 * Whether session data belongs to current visitor (user agent, remote addr and ip check)
190
	 *
191
	 * @param array       $session_data
192
	 * @param string|null $user_agent
193
	 * @param string|null $remote_addr
194
	 * @param string|null $ip
195
	 *
196
	 * @return bool
197
	 */
198
	protected function is_session_owner_internal ($session_data, $user_agent = null, $remote_addr = null, $ip = null) {
199
		/**
200
		 * md5() as protection against timing attacks
201
		 */
202
		if ($user_agent === null && $remote_addr === null && $ip === null) {
203
			$Request     = Request::instance();
204
			$user_agent  = $Request->header('user-agent');
205
			$remote_addr = $Request->remote_addr;
206
			$ip          = $Request->ip;
207
		}
208
		return
209
			md5($session_data['user_agent']) == md5($user_agent) &&
210
			(
211
				!Config::instance()->core['remember_user_ip'] ||
212
				(
213
					md5($session_data['remote_addr']) == md5(ip2hex($remote_addr)) &&
214
					md5($session_data['ip']) == md5(ip2hex($ip))
215
				)
216
			);
217
	}
218
	/**
219
	 * Load session by id and return id of session owner (user), update session expiration
220
	 *
221
	 * @param false|null|string $session_id If not specified - loaded from `$this->session_id`, and if that also empty - from cookies
222
	 *
223
	 * @return int User id
224
	 */
225
	function load ($session_id = null) {
226
		$session_data = $this->get_internal($session_id);
227
		if (!$session_data || !$this->is_session_owner_internal($session_data)) {
228
			$this->add(User::GUEST_ID);
229
			return User::GUEST_ID;
230
		}
231
		/**
232
		 * Updating last online time and ip
233
		 */
234
		$Config = Config::instance();
235
		$time   = time();
236
		if ($session_data['expire'] - $time < $Config->core['session_expire'] * $Config->core['update_ratio'] / 100) {
237
			$session_data['expire'] = $time + $Config->core['session_expire'];
238
			$this->update($session_data);
239
			$this->cache->set($session_data['id'], $session_data);
240
			Response::instance()->cookie('session', $session_data['id'], $session_data['expire'], true);
241
		}
242
		unset($session_data['data']);
243
		Event::instance()->fire(
244
			'System/Session/load',
245
			[
246
				'session_data' => $session_data
247
			]
248
		);
249
		return $this->load_initialization($session_data['id'], $session_data['user']);
250
	}
251
	/**
252
	 * Initialize session (set user id, session id and update who user is)
253
	 *
254
	 * @param string $session_id
255
	 * @param int    $user_id
256
	 *
257
	 * @return int User id
258
	 */
259
	protected function load_initialization ($session_id, $user_id) {
260
		$this->session_id = $session_id;
261
		$this->user_id    = $user_id;
262
		$this->update_user_is();
263
		return $user_id;
264
	}
265
	/**
266
	 * Whether profile is activated, not disabled and not blocked
267
	 *
268
	 * @param int $user
269
	 *
270
	 * @return bool
271
	 */
272
	protected function is_user_active ($user) {
273
		/**
274
		 * Optimization, more data requested than actually used here, because data will be requested later, and it would be nice to have that data cached
275
		 */
276
		$data = User::instance()->get(
277
			[
278
				'login',
279
				'username',
280
				'language',
281
				'timezone',
282
				'status',
283
				'block_until',
284
				'avatar'
285
			],
286
			$user
287
		);
288
		if (!$data) {
289
			return false;
290
		}
291
		$L    = Language::prefix('system_profile_sign_in_');
292
		$Page = Page::instance();
293
		switch ($data['status']) {
294
			case User::STATUS_INACTIVE:
295
				/**
296
				 * If user is disabled
297
				 */
298
				$Page->warning($L->your_account_disabled);
299
				return false;
300
			case User::STATUS_NOT_ACTIVATED:
301
				/**
302
				 * If user is not active
303
				 */
304
				$Page->warning($L->your_account_is_not_active);
305
				return false;
306
		}
307
		if ($data['block_until'] > time()) {
308
			/**
309
			 * If user if blocked
310
			 */
311
			$Page->warning($L->your_account_blocked_until(date($L->_datetime, $data['block_until'])));
312
			return false;
313
		}
314
		return true;
315
	}
316
	/**
317
	 * Create the session for the user with specified id
318
	 *
319
	 * @param int  $user
320
	 * @param bool $delete_current_session
321
	 *
322
	 * @return false|string Session id on success, `false` otherwise
323
	 */
324
	function add ($user, $delete_current_session = true) {
325
		$user = (int)$user ?: User::GUEST_ID;
326
		if ($delete_current_session && is_md5($this->session_id)) {
327
			$this->del_internal($this->session_id, false);
328
		}
329
		if (!$this->is_user_active($user)) {
330
			/**
331
			 * If data was not loaded or account is not active - create guest session
332
			 */
333
			return $this->add(User::GUEST_ID);
334
		}
335
		$session_data = $this->create_unique_session($user);
336
		Response::instance()->cookie('session', $session_data['id'], $session_data['expire'], true);
337
		$this->load_initialization($session_data['id'], $session_data['user']);
338
		/**
339
		 * Delete old sessions using probability and system configuration of inserts limits and update ratio
340
		 */
341
		$Config = Config::instance();
342
		if (mt_rand(0, $Config->core['inserts_limit']) < $Config->core['inserts_limit'] / 100 * (100 - $Config->core['update_ratio']) / 5) {
343
			$this->delete_old_sessions();
344
		}
345
		Event::instance()->fire(
346
			'System/Session/add',
347
			[
348
				'session_data' => $session_data
349
			]
350
		);
351
		return $session_data['id'];
352
	}
353
	/**
354
	 * @param int $user
355
	 *
356
	 * @return array Session data
357
	 */
358
	protected function create_unique_session ($user) {
359
		$Config      = Config::instance();
360
		$Request     = Request::instance();
361
		$remote_addr = ip2hex($Request->remote_addr);
362
		$ip          = ip2hex($Request->ip);
363
		/**
364
		 * Many guests open only one page (or do not store any cookies), so create guest session only for 5 minutes max initially
365
		 */
366
		$expire_in = $user == User::GUEST_ID ? min($Config->core['session_expire'], self::INITIAL_SESSION_EXPIRATION) : $Config->core['session_expire'];
367
		$expire    = time() + $expire_in;
368
		/**
369
		 * Create unique session
370
		 */
371
		$session_data = [
372
			'id'          => null,
373
			'user'        => $user,
374
			'created'     => time(),
375
			'expire'      => $expire,
376
			'user_agent'  => $Request->header('user-agent'),
377
			'remote_addr' => $remote_addr,
378
			'ip'          => $ip,
379
			'data'        => []
380
		];
381
		do {
382
			$session_data['id'] = md5(random_bytes(1000));
383
		} while (!$this->create($session_data));
384
		return $session_data;
385
	}
386
	/**
387
	 * Destroying of the session
388
	 *
389
	 * @param null|string $session_id
390
	 *
391
	 * @return bool
392
	 */
393
	function del ($session_id = null) {
394
		return (bool)$this->del_internal($session_id);
395
	}
396
	/**
397
	 * Deletion of the session
398
	 *
399
	 * @param string|null $session_id
400
	 * @param bool        $create_guest_session
401
	 *
402
	 * @return bool
403
	 */
404
	protected function del_internal ($session_id = null, $create_guest_session = true) {
405
		$session_id = $session_id ?: $this->session_id;
406
		if (!is_md5($session_id)) {
407
			return false;
408
		}
409
		Event::instance()->fire(
410
			'System/Session/del/before',
411
			[
412
				'id' => $session_id
413
			]
414
		);
415
		unset($this->cache->$session_id);
416
		if ($session_id == $this->session_id) {
417
			$this->session_id = false;
418
			$this->user_id    = User::GUEST_ID;
419
		}
420
		Response::instance()->cookie('session', '');
421
		$result = $this->delete($session_id);
422
		if ($result) {
423
			if ($create_guest_session) {
424
				return (bool)$this->add(User::GUEST_ID);
425
			}
426
			Event::instance()->fire(
427
				'System/Session/del/after',
428
				[
429
					'id' => $session_id
430
				]
431
			);
432
		}
433
		return (bool)$result;
434
	}
435
	/**
436
	 * Delete all old sessions from DB
437
	 */
438
	protected function delete_old_sessions () {
439
		$this->db_prime()->q(
440
			"DELETE FROM `[prefix]sessions`
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal DELETE FROM `[prefix]ses...`\n WHERE `expire` < does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
441
			WHERE `expire` < ".time()
442
		);
443
	}
444
	/**
445
	 * Deletion of all user sessions
446
	 *
447
	 * @param false|int $user If not specified - current user assumed
448
	 *
449
	 * @return bool
450
	 */
451
	function del_all ($user = false) {
452
		$user = $user ?: $this->user_id;
453
		if ($user == User::GUEST_ID) {
454
			return false;
455
		}
456
		Event::instance()->fire(
457
			'System/Session/del_all',
458
			[
459
				'id' => $user
460
			]
461
		);
462
		$sessions = $this->db_prime()->qfas(
463
			"SELECT `id`
464
			FROM `[prefix]sessions`
465
			WHERE `user` = '$user'"
466
		);
467
		foreach ($sessions ?: [] as $session) {
468
			if (!$this->del($session)) {
469
				return false;
470
			}
471
		}
472
		return true;
473
	}
474
}
475