Completed
Push — master ( e6f232...1c9aae )
by Nazar
04:10
created

Profile   C

Complexity

Total Complexity 71

Size/Duplication

Total Lines 312
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 312
ccs 140
cts 140
cp 1
rs 5.5904
c 0
b 0
f 0
wmc 71
lcom 1
cbo 6

10 Methods

Rating   Name   Duplication   Size   Complexity  
C get_internal() 0 39 11
A get() 0 6 3
A initialize_data() 0 8 1
D set() 0 48 12
C set_internal() 0 48 15
C set_internal_allowed() 0 31 13
B get_id() 0 21 5
B avatar() 0 14 6
A username() 0 11 4
A get_users_columns() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Profile 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 Profile, 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\Event,
12
	cs\Language,
13
	cs\Session,
14
	cs\User,
15
	h;
16
17
/**
18
 * Trait that contains all methods for `cs\User` for working with user's profile
19
 *
20
 * @property \cs\Cache\Prefix $cache
21
 * @property int              $id
22
 *
23
 * @method \cs\DB\_Abstract db()
24
 * @method \cs\DB\_Abstract db_prime()
25
 * @method false|int[]      get_groups(false|int $user)
26
 */
27
trait Profile {
28
	/**
29
	 * Copy of columns list of users table for internal needs without Cache usage
30
	 * @var array
31
	 */
32
	protected $users_columns = [];
33
	/**
34
	 * Local cache of users data
35
	 * @var array
36
	 */
37
	protected $data = [];
38 32
	protected function initialize_data () {
39 32
		$this->users_columns = $this->cache->get(
40 32
			'columns',
41
			function () {
42 4
				return $this->db()->columns('[prefix]users');
43 32
			}
44
		);
45 32
	}
46
	/**
47
	 * Get data item of specified user
48
	 *
49
	 * @param string|string[] $item
50
	 * @param false|int       $user If not specified - current user assumed
51
	 *
52
	 * @return false|int|mixed[]|string|Properties If <i>$item</i> is integer - cs\User\Properties object will be returned
53
	 */
54 26
	public function get ($item, $user = false) {
55 26
		if (is_scalar($item) && ctype_digit((string)$item)) {
56 2
			return new Properties($item);
57
		}
58 26
		return $this->get_internal($item, $user);
59
	}
60
	/**
61
	 * Get data item of specified user
62
	 *
63
	 * @param string|string[] $item
64
	 * @param false|int       $user If not specified - current user assumed
65
	 *
66
	 * @return false|int|string|mixed[]
67
	 */
68 26
	protected function get_internal ($item, $user = false) {
69 26
		$user = (int)$user ?: $this->id;
70 26
		if (isset($this->data[$user])) {
71 24
			$data = $this->data[$user];
72
		} else {
73 26
			$data = $this->cache->get(
74
				$user,
75
				function () use ($user) {
76 22
					return $this->db()->qf(
77
						"SELECT *
78
						FROM `[prefix]users`
79 22
						WHERE `id` = $user
80 22
						LIMIT 1"
81 22
					) ?: false;
82 26
				}
83
			);
84 26
			if (!$data) {
85 6
				return false;
86 26
			} elseif ($this->memory_cache || $user == User::GUEST_ID) {
87 26
				$this->data[$user] = $data;
88
			}
89
		}
90
		/**
91
		 * If get an array of values
92
		 */
93 26
		if (is_array($item)) {
94 24
			$result = [];
95
			/**
96
			 * Trying to get value from the local cache, or make up an array of missing values
97
			 */
98 24
			foreach ($item as $i) {
99 24
				if (in_array($i, $this->users_columns)) {
100 24
					$result[$i] = $data[$i];
101
				}
102
			}
103 24
			return $result;
104
		}
105 24
		return in_array($item, $this->users_columns) ? $data[$item] : false;
106
	}
107
	/**
108
	 * Set data item of specified user
109
	 *
110
	 * @param array|string    $item Item-value array may be specified for setting several items at once
111
	 * @param int|null|string $value
112
	 * @param false|int       $user If not specified - current user assumed
113
	 *
114
	 * @return bool
115
	 */
116 18
	public function set ($item, $value = null, $user = false) {
117 18
		$user     = (int)$user ?: $this->id;
118 18
		$data_set = [];
119 18
		$data     = is_array($item) ? $item : [$item => $value];
120
		/**
121
		 * @var array $old_data
122
		 */
123 18
		$old_data = $this->get(['login', 'email'], $user);
124 18
		$result   = true;
125 18
		foreach ($data as $i => $v) {
126 18
			$result = $result && $this->set_internal($i, $v, $user, $data_set);
127
		}
128 18
		if (!$result) {
129 4
			return false;
130
		}
131 18
		if (!$data_set) {
132 2
			return true;
133
		}
134
		/**
135
		 * A bit tricky here
136
		 *
137
		 * User is allowed to change login to own email, but not to any other email. However, when user changes email, it might happen that login will remain to
138
		 * be the same as previous email, so we need to change login to new email as well.
139
		 */
140 18
		$current_login = isset($data_set['login']) ? $data_set['login'] : $old_data['login'];
141
		if (
142 18
			isset($data_set['email']) &&
143 18
			$current_login == $old_data['email']
144
		) {
145 2
			$data_set['login']      = $data_set['email'];
146 2
			$data_set['login_hash'] = $data_set['email_hash'];
147
		}
148 18
		$update = [];
149 18
		foreach (array_keys($data_set) as $column) {
150 18
			$update[] = "`$column` = '%s'";
151
		}
152 18
		$update = implode(', ', $update);
153 18
		$result = $this->db_prime()->q(
154
			"UPDATE `[prefix]users`
155 18
			SET $update
156 18
			WHERE `id` = '$user'",
157 18
			xap($data_set, false)
158
		);
159 18
		if ($result) {
160 18
			unset($this->data[$user], $this->cache->$user);
161
		}
162 18
		return (bool)$result;
163
	}
164
	/**
165
	 * Set data item of specified user
166
	 *
167
	 * @param string     $item Item-value array may be specified for setting several items at once
168
	 * @param int|string $value
169
	 * @param int        $user If not specified - current user assumed
170
	 * @param array      $data_set
171
	 *
172
	 * @return bool
173
	 */
174 18
	protected function set_internal ($item, $value, $user, &$data_set) {
175 18
		if (!$this->set_internal_allowed($user, $item, $value)) {
176 4
			return false;
177
		}
178 18
		$old_value = $this->get($item, $user);
179 18
		if ($value == $old_value) {
180 4
			return true;
181
		}
182 18
		if ($item == 'language') {
183 4
			$value = $value && Language::instance()->get('clanguage', $value) == $value ? $value : '';
184 18
		} elseif ($item == 'timezone') {
185 2
			$value = in_array($value, get_timezones_list(), true) ? $value : '';
186 18
		} elseif ($item == 'avatar') {
187
			if (
188 2
				strpos($value, 'http://') !== 0 &&
189 2
				strpos($value, 'https://') !== 0
190
			) {
191 2
				$value = '';
192
			}
193 2
			$Event = Event::instance();
194 2
			$Event->fire(
195 2
				'System/upload_files/del_tag',
196
				[
197 2
					'url' => $old_value,
198 2
					'tag' => "users/$user/avatar"
199
				]
200
			);
201 2
			$Event->fire(
202 2
				'System/upload_files/add_tag',
203
				[
204 2
					'url' => $value,
205 2
					'tag' => "users/$user/avatar"
206
				]
207
			);
208
		}
209
		/**
210
		 * @var string $item
211
		 */
212 18
		$data_set[$item] = $value;
213 18
		if (in_array($item, ['login', 'email'], true)) {
214 4
			$old_value               = $this->get($item.'_hash', $user);
215 4
			$data_set[$item.'_hash'] = hash('sha224', $value);
216 4
			unset($this->cache->$old_value);
217 18
		} elseif ($item == 'password_hash' || ($item == 'status' && $value != User::STATUS_ACTIVE)) {
218 18
			Session::instance()->del_all($user);
219
		}
220 18
		return true;
221
	}
222
	/**
223
	 * Check whether setting specified item to specified value for specified user is allowed
224
	 *
225
	 * @param int    $user
226
	 * @param string $item
227
	 * @param string $value
228
	 *
229
	 * @return bool
230
	 */
231 18
	protected function set_internal_allowed ($user, $item, $value) {
232
		if (
233 18
			$user == User::GUEST_ID ||
234 18
			$item == 'id' ||
235 18
			!in_array($item, $this->users_columns, true)
236
		) {
237 4
			return false;
238
		}
239 18
		if (in_array($item, ['login', 'email'], true)) {
240 4
			$value = mb_strtolower($value);
241
			if (
242 4
				$item == 'email' &&
243 4
				!filter_var($value, FILTER_VALIDATE_EMAIL)
244
			) {
245 2
				return false;
246
			}
247
			if (
248 4
				$item == 'login' &&
249 4
				filter_var($value, FILTER_VALIDATE_EMAIL) &&
250 4
				$value != $this->get('email', $user)
251
			) {
252 2
				return false;
253
			}
254 4
			if ($value == $this->get($item, $user)) {
255 4
				return true;
256
			}
257 4
			$existing_user = $this->get_id(hash('sha224', $value)) ?: $user;
258 4
			return $value && $existing_user == $user;
259
		}
260 18
		return true;
261
	}
262
	/**
263
	 * Get user id by login or email hash (sha224) (hash from lowercase string)
264
	 *
265
	 * @param  string $login_hash Login or email hash
266
	 *
267
	 * @return false|int User id if found and not guest, otherwise - boolean <i>false</i>
268
	 */
269 20
	public function get_id ($login_hash) {
270 20
		if (!preg_match('/^[0-9a-z]{56}$/', $login_hash)) {
271 2
			return false;
272
		}
273 20
		$id = $this->cache->get(
274
			$login_hash,
275 20
			function () use ($login_hash) {
276 18
				return (int)$this->db()->qfs(
277
					"SELECT `id`
278
					FROM `[prefix]users`
279
					WHERE
280
						`login_hash`	= '%s' OR
281
						`email_hash`	= '%s'
282 18
					LIMIT 1",
283
					$login_hash,
284
					$login_hash
285 18
				) ?: false;
286 20
			}
287
		);
288 20
		return $id && $id != User::GUEST_ID ? $id : false;
289
	}
290
	/**
291
	 * Get user avatar, if no one present - uses Gravatar
292
	 *
293
	 * @param int|null  $size Avatar size, if not specified or resizing is not possible - original image is used
294
	 * @param false|int $user If not specified - current user assumed
295
	 *
296
	 * @return string
297
	 */
298 8
	public function avatar ($size = null, $user = false) {
299 8
		$user         = (int)$user ?: $this->id;
300 8
		$avatar       = $this->get('avatar', $user);
301 8
		$Config       = Config::instance();
302 8
		$guest_avatar = $Config->core_url().'/includes/img/guest.svg';
303 8
		if (!$avatar && $this->id != User::GUEST_ID && $Config->core['gravatar_support']) {
304 4
			$email_hash = md5($this->get('email', $user));
305 4
			$avatar     = "https://www.gravatar.com/avatar/$email_hash?d=mm&s=$size&d=".urlencode($guest_avatar);
306
		}
307 8
		if (!$avatar) {
308 8
			$avatar = $guest_avatar;
309
		}
310 8
		return h::prepare_url($avatar, true);
311
	}
312
	/**
313
	 * Get user name or login or email, depending on existing information
314
	 *
315
	 * @param false|int $user If not specified - current user assumed
316
	 *
317
	 * @return string
318
	 */
319 4
	public function username ($user = false) {
320 4
		$user = (int)$user ?: $this->id;
321 4
		if ($user == User::GUEST_ID) {
322 2
			return Language::instance()->system_profile_guest;
323
		}
324 2
		$username = $this->get('username', $user);
325 2
		if (!$username) {
326 2
			$username = $this->get('login', $user);
327
		}
328 2
		return $username;
329
	}
330
	/**
331
	 * Returns array of users columns, available for getting of data
332
	 *
333
	 * @return array
334
	 */
335 2
	public function get_users_columns () {
336 2
		return $this->users_columns;
337
	}
338
}
339