Passed
Push — master ( a4754c...e0c6ec )
by Nazar
05:32
created

profile::profile_patch()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6.0163

Importance

Changes 0
Metric Value
cc 6
eloc 13
nc 5
nop 1
dl 0
loc 18
rs 8.8571
c 0
b 0
f 0
ccs 12
cts 13
cp 0.9231
crap 6.0163
1
<?php
2
/**
3
 * @package    CleverStyle Framework
4
 * @subpackage System module
5
 * @category   modules
6
 * @author     Nazar Mokrynskyi <[email protected]>
7
 * @copyright  Copyright (c) 2015-2017, Nazar Mokrynskyi
8
 * @license    MIT License, see license.txt
9
 */
10
namespace cs\modules\System\api\Controller;
11
use
12
	cs\Config,
13
	cs\Core,
14
	cs\Event,
15
	cs\ExitException,
16
	cs\Language,
17
	cs\Mail,
18
	cs\Session,
19
	cs\User;
20
21
/**
22
 * Provides next events:
23
 *  api/System/profile/sign_in/before
24
 *  [
25
 *    'login'    => $login,   // sha224 hash of login or email actually
26
 *    'password' => $password // sha512(sha512(password) + public_key)
27
 *  ]
28
 *
29
 *  api/System/profile/sign_in/success
30
 *
31
 *  api/System/profile/sign_in/error
32
 */
33
trait profile {
34 3
	public static function profile_get () {
35 3
		$User         = User::instance();
36
		$fields       = [
37 3
			'id',
38
			'login',
39
			'username',
40
			'language',
41
			'timezone',
42
			'avatar'
43
		];
44 3
		$result       = $User->get($fields, $User->id);
45 3
		$result['id'] = (int)$result['id'];
46 3
		if ($User->guest()) {
47 3
			$result['username'] = Language::instance()->system_profile_guest;
0 ignored issues
show
Bug Best Practice introduced by
The property system_profile_guest does not exist on cs\Language. Since you implemented __get, consider adding a @property annotation.
Loading history...
48 3
			$result['avatar']   = $User->avatar();
49
		}
50 3
		return $result;
51
	}
52
	/**
53
	 * @param \cs\Request $Request
54
	 *
55
	 * @throws ExitException
56
	 */
57 3
	public static function profile_patch ($Request) {
58 3
		$user_data = $Request->data('login', 'username', 'language', 'timezone', 'avatar');
0 ignored issues
show
Bug introduced by
'login' of type string is incompatible with the type array<mixed,string[]>|string[] expected by parameter $name of cs\Request::data(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

58
		$user_data = $Request->data(/** @scrutinizer ignore-type */ 'login', 'username', 'language', 'timezone', 'avatar');
Loading history...
59
		if (
60 3
			!$user_data ||
61 3
			!$user_data['login']
62
		) {
63 3
			throw new ExitException(400);
64
		}
65 3
		$User = User::instance();
66 3
		if ($User->guest()) {
67 3
			throw new ExitException(403);
68
		}
69 3
		$user_data['login'] = mb_strtolower($user_data['login']);
70 3
		if (!static::can_change_login_to($User, $user_data['login'])) {
71 3
			throw new ExitException(Language::instance()->system_admin_users_login_occupied, 400);
0 ignored issues
show
Bug Best Practice introduced by
The property system_admin_users_login_occupied does not exist on cs\Language. Since you implemented __get, consider adding a @property annotation.
Loading history...
72
		}
73 3
		if (!$User->set($user_data)) {
74
			throw new ExitException(500);
75
		}
76 3
	}
77
	/**
78
	 * Check for changing login to new one and whether it is available
79
	 *
80
	 * @param User   $User
81
	 * @param string $login
82
	 *
83
	 * @return bool
84
	 */
85 3
	protected static function can_change_login_to ($User, $login) {
86
		return
87 3
			$login == $User->login ||
88 3
			$login == $User->email ||
89
			(
90 3
				!filter_var($login, FILTER_VALIDATE_EMAIL) &&
91 3
				$User->get_id(hash('sha224', $login)) === false
92
			);
93
	}
94
	/**
95
	 * @param \cs\Request $Request
96
	 *
97
	 * @throws ExitException
98
	 */
99 3
	public static function profile_change_password ($Request) {
100 3
		$data = $Request->data('current_password', 'new_password');
0 ignored issues
show
Bug introduced by
'current_password' of type string is incompatible with the type array<mixed,string[]>|string[] expected by parameter $name of cs\Request::data(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

100
		$data = $Request->data(/** @scrutinizer ignore-type */ 'current_password', 'new_password');
Loading history...
101 3
		if (!$data) {
102 3
			throw new ExitException(400);
103
		}
104 3
		$User = User::instance();
105 3
		if (!$User->user()) {
106 3
			throw new ExitException(403);
107
		}
108 3
		$L = Language::prefix('system_profile_');
109 3
		if (!$data['new_password']) {
110 3
			throw new ExitException($L->please_type_new_password, 400);
0 ignored issues
show
Bug Best Practice introduced by
The property please_type_new_password does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
111
		}
112 3
		if (!$User->validate_password($data['current_password'], $User->id, true)) {
113 3
			throw new ExitException($L->wrong_current_password, 400);
0 ignored issues
show
Bug Best Practice introduced by
The property wrong_current_password does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
114
		}
115 3
		$id = $User->id;
116 3
		if ($User->set_password($data['new_password'], $id, true)) {
117 3
			Session::instance()->add($id);
118
		} else {
119 3
			throw new ExitException($L->change_password_server_error, 500);
0 ignored issues
show
Bug Best Practice introduced by
The property change_password_server_error does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
120
		}
121 3
	}
122
	/**
123
	 * @param \cs\Request  $Request
124
	 * @param \cs\Response $Response
125
	 *
126
	 * @throws ExitException
127
	 */
128 3
	public static function profile_registration ($Request, $Response) {
129 3
		$Config = Config::instance();
130 3
		$L      = Language::prefix('system_profile_registration_');
131 3
		$User   = User::instance();
132 3
		if (!$User->guest()) {
133 3
			throw new ExitException(403);
134
		}
135 3
		if (!$Config->core['allow_user_registration']) {
136 3
			throw new ExitException($L->prohibited, 403);
0 ignored issues
show
Bug Best Practice introduced by
The property prohibited does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
137
		}
138 3
		$email   = $Request->data('email');
0 ignored issues
show
Bug introduced by
'email' of type string is incompatible with the type array<mixed,string[]>|string[] expected by parameter $name of cs\Request::data(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

138
		$email   = $Request->data(/** @scrutinizer ignore-type */ 'email');
Loading history...
139 3
		$email   = mb_strtolower($email);
0 ignored issues
show
Bug introduced by
It seems like $email can also be of type array and array; however, parameter $str of mb_strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

139
		$email   = mb_strtolower(/** @scrutinizer ignore-type */ $email);
Loading history...
140 3
		$result  = static::try_to_register($User, $L, $email);
141 3
		$confirm = $result['reg_key'] !== true;
142 3
		static::fill_optional_profile_data($Request, $User, $result['id']);
143 3
		$username  = $User->username($result['id']);
144 3
		$login     = $User->get('login', $result['id']);
145 3
		$site_name = $Config->core['site_name'];
146 3
		$title     = $L->success_mail($site_name);
0 ignored issues
show
Bug introduced by
The method success_mail() does not exist on cs\Language\Prefix. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

146
		/** @scrutinizer ignore-call */ 
147
  $title     = $L->success_mail($site_name);
Loading history...
147 3
		$body      = $L->success_mail($username, $site_name, $Config->core_url().'/profile/settings', $login);
148 3
		if ($confirm) {
149 3
			$title = $L->need_confirmation_mail($site_name);
0 ignored issues
show
Bug introduced by
The method need_confirmation_mail() does not exist on cs\Language\Prefix. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

149
			/** @scrutinizer ignore-call */ 
150
   $title = $L->need_confirmation_mail($site_name);
Loading history...
150 3
			$body  = $L->need_confirmation_mail_body(
0 ignored issues
show
Bug introduced by
The method need_confirmation_mail_body() does not exist on cs\Language\Prefix. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

150
			/** @scrutinizer ignore-call */ 
151
   $body  = $L->need_confirmation_mail_body(
Loading history...
151 3
				$username,
152 3
				$site_name,
153 3
				$Config->core_url()."/profile/registration_confirmation/$result[reg_key]",
154 3
				$L->time($Config->core['registration_confirmation_time'], 'd')
155
			);
156 3
		} elseif (!$Request->data('password') && $result['password']) {
157 3
			$body = $L->success_mail_with_password_body($username, $site_name, $Config->core_url().'/profile/settings', $login, $result['password']);
0 ignored issues
show
Bug introduced by
The method success_mail_with_password_body() does not exist on cs\Language\Prefix. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

157
			/** @scrutinizer ignore-call */ 
158
   $body = $L->success_mail_with_password_body($username, $site_name, $Config->core_url().'/profile/settings', $login, $result['password']);
Loading history...
158
		}
159 3
		if (!Mail::instance()->send_to($email, $title, $body)) {
160 3
			$User->registration_cancel();
161 3
			throw new ExitException($L->mail_sending_error, 500);
0 ignored issues
show
Bug Best Practice introduced by
The property mail_sending_error does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
162
		}
163 3
		$Response->code = $confirm ? 202 : 201;
164 3
	}
165
	/**
166
	 * @param User            $User
167
	 * @param Language\Prefix $L
168
	 * @param string          $email
169
	 *
170
	 * @return array
171
	 *
172
	 * @throws ExitException
173
	 */
174 3
	protected static function try_to_register ($User, $L, $email) {
175 3
		$result = $User->registration($email);
176 3
		if ($result === false) {
177 3
			throw new ExitException($L->please_type_correct_email, 400);
0 ignored issues
show
Bug Best Practice introduced by
The property please_type_correct_email does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
178
		}
179 3
		if ($result == 'error') {
180
			throw new ExitException($L->server_error, 500);
0 ignored issues
show
Bug Best Practice introduced by
The property server_error does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
181
		}
182 3
		if ($result == 'exists') {
183 3
			throw new ExitException($L->error_exists, 400);
0 ignored issues
show
Bug Best Practice introduced by
The property error_exists does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
184
		}
185 3
		return $result;
186
	}
187
	/**
188
	 * @param \cs\Request $Request
189
	 * @param User        $User
190
	 * @param int         $user_id
191
	 */
192 3
	protected static function fill_optional_profile_data ($Request, $User, $user_id) {
193 3
		if ($Request->data('username')) {
0 ignored issues
show
Bug introduced by
'username' of type string is incompatible with the type array<mixed,string[]>|string[] expected by parameter $name of cs\Request::data(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

193
		if ($Request->data(/** @scrutinizer ignore-type */ 'username')) {
Loading history...
194 3
			$User->set('username', $Request->data['username'], $user_id);
195
		}
196
		// Actually `sha512(sha512(password) + public_key)` instead of plain password
197 3
		if ($Request->data('password')) {
198 3
			$User->set_password($Request->data['password'], $user_id, true);
199
		}
200 3
		if ($Request->data('language')) {
201 3
			$User->set('language', $Request->data['language'], $user_id);
202
		}
203 3
		if ($Request->data('timezone')) {
204 3
			$User->set('timezone', $Request->data['timezone'], $user_id);
205
		}
206 3
		if ($Request->data('avatar')) {
207 3
			$User->set('avatar', $Request->data['avatar'], $user_id);
208
		}
209 3
	}
210
	/**
211
	 * @param \cs\Request $Request
212
	 *
213
	 * @throws ExitException
214
	 */
215 3
	public static function profile_restore_password ($Request) {
216 3
		$User = User::instance();
217 3
		if (!$User->guest()) {
218 3
			throw new ExitException(403);
219
		}
220 3
		$L     = Language::prefix('system_profile_restore_password_');
221 3
		$email = $Request->data('email');
0 ignored issues
show
Bug introduced by
'email' of type string is incompatible with the type array<mixed,string[]>|string[] expected by parameter $name of cs\Request::data(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

221
		$email = $Request->data(/** @scrutinizer ignore-type */ 'email');
Loading history...
222 3
		if (!$email) {
223 3
			throw new ExitException($L->please_type_your_email, 400);
0 ignored issues
show
Bug Best Practice introduced by
The property please_type_your_email does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
224
		}
225 3
		$id = $User->get_id(mb_strtolower($email));
0 ignored issues
show
Bug introduced by
It seems like $email can also be of type array and array; however, parameter $str of mb_strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

225
		$id = $User->get_id(mb_strtolower(/** @scrutinizer ignore-type */ $email));
Loading history...
226 3
		if (!$id) {
227 3
			throw new ExitException($L->user_with_such_login_email_not_found, 400);
0 ignored issues
show
Bug Best Practice introduced by
The property user_with_such_login_email_not_found does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
228
		}
229 3
		$key    = $User->restore_password($id);
230 3
		$Config = Config::instance();
231
		if (
232 3
			!$key ||
233 3
			!Mail::instance()->send_to(
234 3
				$User->get('email', $id),
0 ignored issues
show
Bug introduced by
It seems like $User->get('email', $id) can also be of type false; however, parameter $email of cs\Mail::send_to() does only seem to accept string|array|string[], maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

234
				/** @scrutinizer ignore-type */ $User->get('email', $id),
Loading history...
235 3
				$L->confirmation_mail($Config->core['site_name']),
0 ignored issues
show
Bug introduced by
The method confirmation_mail() does not exist on cs\Language\Prefix. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

235
				$L->/** @scrutinizer ignore-call */ 
236
        confirmation_mail($Config->core['site_name']),
Loading history...
236 3
				$L->confirmation_mail_body(
0 ignored issues
show
Bug introduced by
The method confirmation_mail_body() does not exist on cs\Language\Prefix. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

236
				$L->/** @scrutinizer ignore-call */ 
237
        confirmation_mail_body(
Loading history...
237 3
					$User->username($id),
238 3
					$Config->core['site_name'],
239 3
					$Config->core_url()."/profile/restore_password_confirmation/$key",
240 3
					$L->time($Config->core['registration_confirmation_time'], 'd')
241
				)
242
			)
243
		) {
244 3
			throw new ExitException($L->server_error, 500);
0 ignored issues
show
Bug Best Practice introduced by
The property server_error does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
245
		}
246 3
	}
247
	/**
248
	 * @param \cs\Request $Request
249
	 *
250
	 * @throws ExitException
251
	 */
252 3
	public static function profile_sign_in ($Request) {
253 3
		$L    = Language::prefix('system_profile_sign_in_');
254 3
		$User = User::instance();
255 3
		$data = $Request->data('login', 'password');
0 ignored issues
show
Bug introduced by
'login' of type string is incompatible with the type array<mixed,string[]>|string[] expected by parameter $name of cs\Request::data(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

255
		$data = $Request->data(/** @scrutinizer ignore-type */ 'login', 'password');
Loading history...
256 3
		if (!$data) {
257 3
			throw new ExitException(400);
258
		}
259 3
		if (!$User->guest()) {
260 3
			return;
261
		}
262 3
		$Event = Event::instance();
263 3
		if (!$Event->fire('api/System/profile/sign_in/before', $data)) {
264 3
			throw new ExitException(403);
265
		}
266 3
		$id = $User->get_id($data['login']);
267 3
		if ($id && $User->validate_password($data['password'], $id, true)) {
268 3
			$status = $User->get('status', $id);
269 3
			if ($status == User::STATUS_NOT_ACTIVATED) {
270 3
				throw new ExitException($L->your_account_is_not_active, 403);
0 ignored issues
show
Bug Best Practice introduced by
The property your_account_is_not_active does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
271
			}
272 3
			if ($status == User::STATUS_INACTIVE) {
273 3
				throw new ExitException($L->your_account_disabled, 403);
0 ignored issues
show
Bug Best Practice introduced by
The property your_account_disabled does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
274
			}
275 3
			Session::instance()->add($id);
276 3
			$Event->fire('api/System/profile/sign_in/success');
277
		} else {
278 3
			$Event->fire('api/System/profile/sign_in/error');
279 3
			throw new ExitException($L->authentication_error, 400);
0 ignored issues
show
Bug Best Practice introduced by
The property authentication_error does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
280
		}
281 3
	}
282
	/**
283
	 * @param \cs\Request  $Request
284
	 * @param \cs\Response $Response
285
	 *
286
	 * @throws ExitException
287
	 */
288 3
	public static function profile_sign_out (
289
		/** @noinspection PhpUnusedParameterInspection */
290
		$Request,
0 ignored issues
show
Unused Code introduced by
The parameter $Request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

290
		/** @scrutinizer ignore-unused */ $Request,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
291
		$Response
292
	) {
293 3
		if (User::instance()->guest()) {
294 3
			return;
295
		}
296 3
		if (!Session::instance()->del()) {
297
			throw new ExitException(500);
298
		}
299
		/**
300
		 * Hack for 403 after sign out in administration
301
		 */
302 3
		$Response->cookie('sign_out', 1, time() + 5, true);
303 3
	}
304 3
	public static function profile_configuration () {
305 3
		$Config = Config::instance();
306
		return [
307 3
			'public_key'            => Core::instance()->public_key,
308 3
			'password_min_length'   => (int)$Config->core['password_min_length'],
309 3
			'password_min_strength' => (int)$Config->core['password_min_strength']
310
		];
311
	}
312
}
313