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

Profile::set_internal_allowed()   C

Complexity

Conditions 13
Paths 9

Size

Total Lines 31
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 13

Importance

Changes 0
Metric Value
cc 13
eloc 22
nc 9
nop 3
dl 0
loc 31
ccs 19
cts 19
cp 1
crap 13
rs 5.1234
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 18
		$result   = true;
121 18
		foreach (xap($data) as $i => $v) {
122 18
			$result = $result && $this->set_internal($i, $v, $user, $data_set);
123
		}
124 18
		if (!$result) {
125 4
			return false;
126
		}
127 18
		if (!$data_set) {
128 2
			return true;
129
		}
130 18
		$this->set_internal_correct_login($data_set, $user);
131 18
		$update = [];
132 18
		foreach (array_keys($data_set) as $column) {
133 18
			$update[] = "`$column` = ?";
134
		}
135 18
		$update = implode(', ', $update);
136 18
		$result = $this->db_prime()->q(
137
			"UPDATE `[prefix]users`
138 18
			SET $update
139 18
			WHERE `id` = '$user'",
140
			$data_set
141
		);
142 18
		if ($result) {
143 18
			unset($this->data[$user], $this->cache->$user);
144
		}
145 18
		return (bool)$result;
146
	}
147
	/**
148
	 * Set data item of specified user
149
	 *
150
	 * @param string     $item Item-value array may be specified for setting several items at once
151
	 * @param int|string $value
152
	 * @param int        $user If not specified - current user assumed
153
	 * @param array      $data_set
154
	 *
155
	 * @return bool
156
	 */
157 18
	protected function set_internal ($item, $value, $user, &$data_set) {
158 18
		if (!$this->set_internal_allowed($user, $item, $value)) {
159 4
			return false;
160
		}
161 18
		$old_value = $this->get($item, $user);
162 18
		if ($value == $old_value) {
163 4
			return true;
164
		}
165 18
		if ($item == 'language') {
166 4
			$value = $value && Language::instance()->get('clanguage', $value) == $value ? $value : '';
167 18
		} elseif ($item == 'timezone') {
168 2
			$value = in_array($value, get_timezones_list(), true) ? $value : '';
169 18
		} elseif ($item == 'avatar') {
170
			if (
171 2
				strpos($value, 'http://') !== 0 &&
172 2
				strpos($value, 'https://') !== 0
173
			) {
174 2
				$value = '';
175
			}
176 2
			$Event = Event::instance();
177 2
			$Event->fire(
178 2
				'System/upload_files/del_tag',
179
				[
180 2
					'url' => $old_value,
181 2
					'tag' => "users/$user/avatar"
182
				]
183
			);
184 2
			$Event->fire(
185 2
				'System/upload_files/add_tag',
186
				[
187 2
					'url' => $value,
188 2
					'tag' => "users/$user/avatar"
189
				]
190
			);
191
		}
192
		/**
193
		 * @var string $item
194
		 */
195 18
		$data_set[$item] = $value;
196 18
		if (in_array($item, ['login', 'email'], true)) {
197 4
			$old_value               = $this->get($item.'_hash', $user);
198 4
			$data_set[$item.'_hash'] = hash('sha224', $value);
199 4
			unset($this->cache->$old_value);
200 18
		} elseif ($item == 'password_hash' || ($item == 'status' && $value != User::STATUS_ACTIVE)) {
201 18
			Session::instance()->del_all($user);
202
		}
203 18
		return true;
204
	}
205
	/**
206
	 * Check whether setting specified item to specified value for specified user is allowed
207
	 *
208
	 * @param int    $user
209
	 * @param string $item
210
	 * @param string $value
211
	 *
212
	 * @return bool
213
	 */
214 18
	protected function set_internal_allowed ($user, $item, $value) {
215
		if (
216 18
			$user == User::GUEST_ID ||
217 18
			$item == 'id' ||
218 18
			!in_array($item, $this->users_columns, true)
219
		) {
220 4
			return false;
221
		}
222 18
		if (in_array($item, ['login', 'email'], true)) {
223 4
			$value = mb_strtolower($value);
224
			if (
225 4
				$item == 'email' &&
226 4
				!filter_var($value, FILTER_VALIDATE_EMAIL)
227
			) {
228 2
				return false;
229
			}
230
			if (
231 4
				$item == 'login' &&
232 4
				filter_var($value, FILTER_VALIDATE_EMAIL) &&
233 4
				$value != $this->get('email', $user)
234
			) {
235 2
				return false;
236
			}
237 4
			if ($value == $this->get($item, $user)) {
238 4
				return true;
239
			}
240 4
			$existing_user = $this->get_id(hash('sha224', $value)) ?: $user;
241 4
			return $value && $existing_user == $user;
242
		}
243 18
		return true;
244
	}
245
	/**
246
	 * A bit tricky here
247
	 *
248
	 * 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
249
	 * be the same as previous email, so we need to change login to new email as well.
250
	 *
251
	 * @param array $data_set
252
	 * @param int   $user
253
	 */
254 18
	protected function set_internal_correct_login (&$data_set, $user) {
255
		/**
256
		 * @var array $old_data
257
		 */
258 18
		$old_data      = $this->get(['login', 'email'], $user);
259 18
		$current_login = isset($data_set['login']) ? $data_set['login'] : $old_data['login'];
260
		if (
261 18
			isset($data_set['email']) &&
262 18
			$current_login == $old_data['email']
263
		) {
264 2
			$data_set['login']      = $data_set['email'];
265 2
			$data_set['login_hash'] = $data_set['email_hash'];
266
		}
267 18
	}
268
	/**
269
	 * Get user id by login or email hash (sha224) (hash from lowercase string)
270
	 *
271
	 * @param  string $login_hash Login or email hash
272
	 *
273
	 * @return false|int User id if found and not guest, otherwise - boolean <i>false</i>
274
	 */
275 20
	public function get_id ($login_hash) {
276 20
		if (!preg_match('/^[0-9a-z]{56}$/', $login_hash)) {
277 2
			return false;
278
		}
279 20
		$id = $this->cache->get(
280
			$login_hash,
281 20
			function () use ($login_hash) {
282 18
				return (int)$this->db()->qfs(
283
					"SELECT `id`
284
					FROM `[prefix]users`
285
					WHERE
286
						`login_hash`	= '%s' OR
287
						`email_hash`	= '%s'
288 18
					LIMIT 1",
289
					$login_hash,
290
					$login_hash
291 18
				) ?: false;
292 20
			}
293
		);
294 20
		return $id && $id != User::GUEST_ID ? $id : false;
295
	}
296
	/**
297
	 * Get user avatar, if no one present - uses Gravatar
298
	 *
299
	 * @param int|null  $size Avatar size, if not specified or resizing is not possible - original image is used
300
	 * @param false|int $user If not specified - current user assumed
301
	 *
302
	 * @return string
303
	 */
304 8
	public function avatar ($size = null, $user = false) {
305 8
		$user         = (int)$user ?: $this->id;
306 8
		$avatar       = $this->get('avatar', $user);
307 8
		$Config       = Config::instance();
308 8
		$guest_avatar = $Config->core_url().'/includes/img/guest.svg';
309 8
		if (!$avatar && $this->id != User::GUEST_ID && $Config->core['gravatar_support']) {
310 4
			$email_hash = md5($this->get('email', $user));
311 4
			$avatar     = "https://www.gravatar.com/avatar/$email_hash?d=mm&s=$size&d=".urlencode($guest_avatar);
312
		}
313 8
		if (!$avatar) {
314 8
			$avatar = $guest_avatar;
315
		}
316 8
		return h::prepare_url($avatar, true);
317
	}
318
	/**
319
	 * Get user name or login or email, depending on existing information
320
	 *
321
	 * @param false|int $user If not specified - current user assumed
322
	 *
323
	 * @return string
324
	 */
325 4
	public function username ($user = false) {
326 4
		$user = (int)$user ?: $this->id;
327 4
		if ($user == User::GUEST_ID) {
328 2
			return Language::instance()->system_profile_guest;
329
		}
330 2
		$username = $this->get('username', $user);
331 2
		if (!$username) {
332 2
			$username = $this->get('login', $user);
333
		}
334 2
		return $username;
335
	}
336
	/**
337
	 * Returns array of users columns, available for getting of data
338
	 *
339
	 * @return array
340
	 */
341 2
	public function get_users_columns () {
342 2
		return $this->users_columns;
343
	}
344
}
345