1<?php
2/**
3 * Elgg user settings functions.
4 * Functions for adding and manipulating options on the user settings panel.
5 */
6
7use Elgg\Request;
8use Elgg\Http\ResponseBuilder;
9
10/**
11 * Set a user's password
12 * Returns null if no change is required
13 * Returns true or false indicating success or failure if change was needed
14 *
15 * @elgg_plugin_hook usersettings:save user
16 *
17 * @param \Elgg\Hook $hook 'usersettings:save', 'user'
18 *
19 * @return bool|null|void
20 * @since 1.8.0
21 * @internal
22 */
23function _elgg_set_user_password(\Elgg\Hook $hook) {
24
25	$actor = elgg_get_logged_in_user_entity();
26	if (!$actor instanceof ElggUser) {
27		return;
28	}
29
30	$user = $hook->getUserParam();
31	$request = $hook->getParam('request');
32
33	if (!$user instanceof ElggUser || !$request instanceof Request) {
34		return;
35	}
36
37	$password = $request->getParam('password', null, false);
38	$password2 = $request->getParam('password2', null, false);
39
40	if (!$password) {
41		return null;
42	}
43
44	if (!$actor->isAdmin() || $user->guid === $actor->guid) {
45		// let admin user change anyone's password without knowing it except his own.
46
47		$current_password = $request->getParam('current_password', null, false);
48
49		try {
50			elgg()->accounts->assertCurrentPassword($user, $current_password);
51		} catch (RegistrationException $e) {
52			$request->validation()->fail('password', '', elgg_echo('LoginException:ChangePasswordFailure'));
53
54			return false;
55		}
56	}
57
58	try {
59		elgg()->accounts->assertValidPassword([$password, $password2]);
60	} catch (RegistrationException $e) {
61		$request->validation()->fail('password', '', $e->getMessage());
62
63		return false;
64	}
65
66	$user->setPassword($password);
67	_elgg_services()->persistentLogin->handlePasswordChange($user, $actor);
68
69	if (elgg_get_config('security_notify_user_password')) {
70		// notify the user that their password has changed
71		$site = elgg_get_site_entity();
72
73		$subject = elgg_echo('user:notification:password_change:subject', [], $user->language);
74		$body = elgg_echo('user:notification:password_change:body', [
75			$user->getDisplayName(),
76			$site->getDisplayName(),
77			elgg_generate_url('account:password:reset'),
78			$site->getURL(),
79		], $user->language);
80
81		$params = [
82			'object' => $user,
83			'action' => 'password_change',
84		];
85
86		notify_user($user->guid, $site->guid, $subject, $body, $params, ['email']);
87	}
88
89	$request->validation()->pass('password', '', elgg_echo('user:password:success'));
90}
91
92/**
93 * Set a user's display name
94 * Returns null if no change is required or input is not present in the form
95 * Returns true or false indicating success or failure if change was needed
96 *
97 * @elgg_plugin_hook usersettings:save user
98 *
99 * @param \Elgg\Hook $hook Hook
100 *
101 * @return bool|null
102 * @since 1.8.0
103 * @internal
104 */
105function _elgg_set_user_name(\Elgg\Hook $hook) {
106
107	$user = $hook->getUserParam();
108	$request = $hook->getParam('request');
109
110	if (!$user instanceof ElggUser || !$request instanceof Request) {
111		return;
112	}
113
114	$name = $request->getParam('name');
115	if (!isset($name)) {
116		return null;
117	}
118
119	$name = strip_tags($name);
120	if (empty($name)) {
121		$request->validation()->fail('name', $request->getParam('name'), elgg_echo('user:name:fail'));
122
123		return false;
124	}
125
126	if ($name === $user->name) {
127		return null;
128	}
129
130	$user->name = $name;
131
132	$request->validation()->pass('name', $name, elgg_echo('user:name:success'));
133}
134
135/**
136 * Set a user's username
137 * Returns null if no change is required or input is not present in the form
138 * Returns true or false indicating success or failure if change was needed
139 *
140 * @elgg_plugin_hook usersettings:save user
141 *
142 * @param \Elgg\Hook $hook Hook
143 *
144 * @return bool|null
145 *
146 * @since 3.0
147 * @internal
148 */
149function _elgg_set_user_username(\Elgg\Hook $hook) {
150
151	$user = $hook->getUserParam();
152	$request = $hook->getParam('request');
153
154	if (!$user instanceof ElggUser || !$request instanceof Request) {
155		return null;
156	}
157
158	$username = $request->getParam('username');
159	if (!isset($username)) {
160		return null;
161	}
162
163	if (!elgg_is_admin_logged_in() && !elgg_get_config('can_change_username', false)) {
164		return null;
165	}
166
167	if (!$user->canEdit()) {
168		return null;
169	}
170
171	if ($user->username === $username) {
172		return null;
173	}
174
175	// check if username is valid and does not exist
176	try {
177		elgg()->accounts->assertValidUsername($username, true);
178	} catch (RegistrationException $ex) {
179		$request->validation()->fail('username', $username, $ex->getMessage());
180
181		return false;
182	}
183
184	$user->username = $username;
185
186	$request->validation()->pass('username', $username, elgg_echo('user:username:success'));
187
188	// correctly forward after after a username change
189	elgg_register_plugin_hook_handler('response', 'action:usersettings/save', function (\Elgg\Hook $hook) use ($username) {
190		$response = $hook->getValue();
191		if (!$response instanceof ResponseBuilder) {
192			return;
193		}
194
195		if ($response->getForwardURL() === REFERRER) {
196			$response->setForwardURL(elgg_generate_url('settings:account', [
197				'username' => $username,
198			]));
199		}
200
201		return $response;
202	});
203}
204
205/**
206 * Set a user's language
207 * Returns null if no change is required or input is not present in the form
208 * Returns true or false indicating success or failure if change was needed
209 *
210 * @elgg_plugin_hook usersettings:save user
211 *
212 * @param \Elgg\Hook $hook Hook
213 *
214 * @return bool|null
215 * @since 1.8.0
216 * @internal
217 */
218function _elgg_set_user_language(\Elgg\Hook $hook) {
219
220	$user = $hook->getUserParam();
221	$request = $hook->getParam('request');
222
223	if (!$user instanceof ElggUser || !$request instanceof Request) {
224		return null;
225	}
226
227	$language = $request->getParam('language');
228	if (!isset($language)) {
229		return null;
230	}
231
232	if ($language === $user->language) {
233		return null;
234	}
235
236	if (!in_array($language, elgg()->translator->getAllowedLanguages())) {
237		return null;
238	}
239
240	$user->language = $language;
241
242	$request->validation()->pass('language', $language, elgg_echo('user:language:success'));
243}
244
245/**
246 * Set a user's email address
247 * Returns null if no change is required or input is not present in the form
248 * Returns true or false indicating success or failure if change was needed
249 *
250 * @elgg_plugin_hook usersettings:save user
251 *
252 * @param \Elgg\Hook $hook Hook
253 *
254 * @return bool|null
255 * @since 1.8.0
256 * @internal
257 */
258function _elgg_set_user_email(\Elgg\Hook $hook) {
259
260	$actor = elgg_get_logged_in_user_entity();
261	if (!$actor instanceof ElggUser) {
262		return null;
263	}
264
265	$user = $hook->getUserParam();
266	$request = $hook->getParam('request');
267
268	if (!$user instanceof ElggUser || !$request instanceof Request) {
269		return null;
270	}
271
272	$email = $request->getParam('email');
273	if (!isset($email)) {
274		return null;
275	}
276
277	if (strcmp($email, $user->email) === 0) {
278		// no change
279		return null;
280	}
281
282	try {
283		elgg()->accounts->assertValidEmail($email, true);
284	} catch (RegistrationException $ex) {
285		$request->validation()->fail('email', $email, $ex->getMessage());
286
287		return false;
288	}
289
290	if (elgg()->config->security_email_require_password && $user->guid === $actor->guid) {
291		try {
292			// validate password
293			elgg()->accounts->assertCurrentPassword($user, $request->getParam('email_password'));
294		} catch (RegistrationException $e) {
295			$request->validation()->fail('email', $email, elgg_echo('email:save:fail:password'));
296			return false;
297		}
298	}
299
300	$hook_params = $hook->getParams();
301	$hook_params['email'] = $email;
302
303	if (!elgg_trigger_plugin_hook('change:email', 'user', $hook_params, true)) {
304		return null;
305	}
306
307	if (elgg()->config->security_email_require_confirmation) {
308		// validate the new email address
309		try {
310			elgg()->accounts->requestNewEmailValidation($user, $email);
311
312			$request->validation()->pass('email', $email, elgg_echo('account:email:request:success', [$email]));
313			return true;
314		} catch (InvalidParameterException $e) {
315			$request->validation()->fail('email', $email, elgg_echo('email:save:fail:password'));
316			return false;
317		}
318	}
319
320	$user->email = $email;
321	$request->validation()->pass('email', $email, elgg_echo('email:save:success'));
322}
323
324/**
325 * Set a user's default access level
326 * Returns null if no change is required or input is not present in the form
327 * Returns true or false indicating success or failure if change was needed
328 *
329 * @elgg_plugin_hook usersettings:save user
330 *
331 * @param \Elgg\Hook $hook Hook
332 *
333 * @return bool|null
334 * @since 1.8.0
335 * @internal
336 * @throws DatabaseException
337 */
338function _elgg_set_user_default_access(\Elgg\Hook $hook) {
339
340	if (!elgg()->config->allow_user_default_access) {
341		return null;
342	}
343
344	$user = $hook->getUserParam();
345	$request = $hook->getParam('request');
346
347	if (!$user instanceof ElggUser || !$request instanceof Request) {
348		return null;
349	}
350
351	$default_access = $request->getParam('default_access');
352	if (!isset($default_access)) {
353		return null;
354	}
355
356	if ($user->setPrivateSetting('elgg_default_access', $default_access)) {
357		$request->validation()->pass('default_access', $default_access, elgg_echo('user:default_access:success'));
358	} else {
359		$request->validation()->fail('default_access', $default_access, elgg_echo(elgg_echo('user:default_access:failure')));
360	}
361}
362
363/**
364 * Register menu items for the user settings page menu
365 *
366 * @param \Elgg\Hook $hook 'register', 'menu:page'
367 *
368 * @return void|ElggMenuItem[]
369 *
370 * @internal
371 * @since 3.0
372 */
373function _elgg_user_settings_menu_register(\Elgg\Hook $hook) {
374	$user = elgg_get_page_owner_entity();
375	if (!$user) {
376		return;
377	}
378
379	if (!elgg_in_context('settings')) {
380		return;
381	}
382
383	$return = $hook->getValue();
384
385	$return[] = \ElggMenuItem::factory([
386		'name' => '1_account',
387		'text' => elgg_echo('usersettings:user:opt:linktext'),
388		'href' => "settings/user/{$user->username}",
389		'section' => 'configure',
390	]);
391
392	$return[] = \ElggMenuItem::factory([
393		'name' => '1_plugins',
394		'text' => elgg_echo('usersettings:plugins:opt:linktext'),
395		'href' => '#',
396		'section' => 'configure',
397	]);
398
399	$return[] = \ElggMenuItem::factory([
400		'name' => '1_statistics',
401		'text' => elgg_echo('usersettings:statistics:opt:linktext'),
402		'href' => "settings/statistics/{$user->username}",
403		'section' => 'configure',
404	]);
405
406	// register plugin user settings menu items
407	$active_plugins = elgg_get_plugins();
408
409	foreach ($active_plugins as $plugin) {
410		$plugin_id = $plugin->getID();
411		if (!elgg_view_exists("usersettings/$plugin_id/edit") && !elgg_view_exists("plugins/$plugin_id/usersettings")) {
412			continue;
413		}
414
415		if (elgg_language_key_exists($plugin_id . ':usersettings:title')) {
416			$title = elgg_echo($plugin_id . ':usersettings:title');
417		} else {
418			$title = $plugin->getDisplayName();
419		}
420
421		$return[] = \ElggMenuItem::factory([
422			'name' => $plugin_id,
423			'text' => $title,
424			'href' => elgg_generate_url('settings:tools', [
425				'username' => $user->username,
426				'plugin_id' => $plugin_id,
427			]),
428			'parent_name' => '1_plugins',
429			'section' => 'configure',
430		]);
431	}
432
433	return $return;
434}
435
436/**
437 * Prepares the page menu to strip out empty plugins menu item for user settings
438 *
439 * @param \Elgg\Hook $hook 'prepare', 'menu:page'
440 *
441 * @return void|array
442 * @internal
443 */
444function _elgg_user_settings_menu_prepare(\Elgg\Hook $hook) {
445	$value = $hook->getValue();
446	if (empty($value)) {
447		return;
448	}
449
450	if (!elgg_in_context("settings")) {
451		return;
452	}
453
454	$configure = elgg_extract("configure", $value);
455	if (empty($configure)) {
456		return;
457	}
458
459	foreach ($configure as $index => $menu_item) {
460		if (!($menu_item instanceof ElggMenuItem)) {
461			continue;
462		}
463
464		if ($menu_item->getName() == "1_plugins") {
465			if (!$menu_item->getChildren()) {
466				// no need for this menu item if it has no children
467				unset($value["configure"][$index]);
468			}
469		}
470	}
471
472	return $value;
473}
474
475/**
476 * Initialize the user settings library
477 *
478 * @return void
479 * @internal
480 */
481function _elgg_user_settings_init() {
482
483	elgg_register_plugin_hook_handler('register', 'menu:page', '_elgg_user_settings_menu_register');
484	elgg_register_plugin_hook_handler('prepare', 'menu:page', '_elgg_user_settings_menu_prepare');
485
486	elgg_register_plugin_hook_handler('usersettings:save', 'user', '_elgg_set_user_language');
487	elgg_register_plugin_hook_handler('usersettings:save', 'user', '_elgg_set_user_password'); // this needs to be before email change, for security reasons
488	elgg_register_plugin_hook_handler('usersettings:save', 'user', '_elgg_set_user_default_access');
489	elgg_register_plugin_hook_handler('usersettings:save', 'user', '_elgg_set_user_name');
490	elgg_register_plugin_hook_handler('usersettings:save', 'user', '_elgg_set_user_username');
491	elgg_register_plugin_hook_handler('usersettings:save', 'user', '_elgg_set_user_email');
492
493	// extend the account settings form
494	elgg_extend_view('forms/usersettings/save', 'core/settings/account/username', 100);
495	elgg_extend_view('forms/usersettings/save', 'core/settings/account/name', 100);
496	elgg_extend_view('forms/usersettings/save', 'core/settings/account/password', 100);
497	elgg_extend_view('forms/usersettings/save', 'core/settings/account/email', 100);
498	elgg_extend_view('forms/usersettings/save', 'core/settings/account/language', 100);
499	elgg_extend_view('forms/usersettings/save', 'core/settings/account/default_access', 100);
500}
501
502/**
503 * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
504 */
505return function (\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
506	$events->registerHandler('init', 'system', '_elgg_user_settings_init');
507};
508