Issues (1027)

Sources/Profile.php (2 issues)

1
<?php
2
3
/**
4
 * This file has the primary job of showing and editing people's profiles.
5
 * It also allows the user to change some of their or another's preferences,
6
 * and such things.
7
 *
8
 * Simple Machines Forum (SMF)
9
 *
10
 * @package SMF
11
 * @author Simple Machines http://www.simplemachines.org
12
 * @copyright 2019 Simple Machines and individual contributors
13
 * @license http://www.simplemachines.org/about/smf/license.php BSD
14
 *
15
 * @version 2.1 RC2
16
 */
17
18
if (!defined('SMF'))
19
	die('No direct access...');
20
21
/**
22
 * The main designating function for modifying profiles. Loads up info, determins what to do, etc.
23
 *
24
 * @param array $post_errors Any errors that occurred
25
 */
26
function ModifyProfile($post_errors = array())
27
{
28
	global $txt, $scripturl, $user_info, $context, $sourcedir, $user_profile, $cur_profile;
29
	global $modSettings, $memberContext, $profile_vars, $post_errors, $smcFunc;
30
31
	// Don't reload this as we may have processed error strings.
32
	if (empty($post_errors))
33
		loadLanguage('Profile+Drafts');
34
	loadTemplate('Profile');
35
36
	require_once($sourcedir . '/Subs-Menu.php');
37
38
	// Did we get the user by name...
39
	if (isset($_REQUEST['user']))
40
		$memberResult = loadMemberData($_REQUEST['user'], true, 'profile');
41
	// ... or by id_member?
42
	elseif (!empty($_REQUEST['u']))
43
		$memberResult = loadMemberData((int) $_REQUEST['u'], false, 'profile');
44
	// If it was just ?action=profile, edit your own profile, but only if you're not a guest.
45
	else
46
	{
47
		// Members only...
48
		is_not_guest();
49
		$memberResult = loadMemberData($user_info['id'], false, 'profile');
50
	}
51
52
	// Check if loadMemberData() has returned a valid result.
53
	if (!$memberResult)
54
		fatal_lang_error('not_a_user', false, 404);
55
56
	// If all went well, we have a valid member ID!
57
	list ($memID) = $memberResult;
58
	$memID = (int) $memID;
59
	$context['id_member'] = $memID;
60
	$cur_profile = $user_profile[$memID];
61
62
	// Let's have some information about this member ready, too.
63
	loadMemberContext($memID);
64
	$context['member'] = $memberContext[$memID];
65
66
	// Is this the profile of the user himself or herself?
67
	$context['user']['is_owner'] = $memID == $user_info['id'];
68
69
	// Group management isn't actually a permission. But we need it to be for this, so we need a phantom permission.
70
	// And we care about what the current user can do, not what the user whose profile it is.
71
	if ($user_info['mod_cache']['gq'] != '0=1')
72
		$user_info['permissions'][] = 'approve_group_requests';
73
74
	// If paid subscriptions are enabled, make sure we actually have at least one subscription available...
75
	$context['subs_available'] = false;
76
77
	if (!empty($modSettings['paid_enabled']))
78
	{
79
		$get_active_subs = $smcFunc['db_query']('', '
80
			SELECT COUNT(*)
81
			FROM {db_prefix}subscriptions
82
			WHERE active = {int:active}', array(
83
				'active' => 1,
84
			)
85
		);
86
87
		list ($num_subs) = $smcFunc['db_fetch_row']($get_active_subs);
88
89
		$context['subs_available'] = ($num_subs > 0);
90
91
		$smcFunc['db_free_result']($get_active_subs);
92
	}
93
94
	/* Define all the sections within the profile area!
95
		We start by defining the permission required - then SMF takes this and turns it into the relevant context ;)
96
		Possible fields:
97
			For Section:
98
				string $title:		Section title.
99
				array $areas:		Array of areas within this section.
100
101
			For Areas:
102
				string $label:		Text string that will be used to show the area in the menu.
103
				string $file:		Optional text string that may contain a file name that's needed for inclusion in order to display the area properly.
104
				string $custom_url:	Optional href for area.
105
				string $function:	Function to execute for this section. Can be a call to an static method: class::method
106
				string $class		If your function is a method, set the class field with your class's name and SMF will create a new instance for it.
107
				bool $enabled:		Should area be shown?
108
				string $sc:			Session check validation to do on save - note without this save will get unset - if set.
109
				bool $hidden:		Does this not actually appear on the menu?
110
				bool $password:		Whether to require the user's password in order to save the data in the area.
111
				array $subsections:	Array of subsections, in order of appearance.
112
				array $permission:	Array of permissions to determine who can access this area. Should contain arrays $own and $any.
113
	*/
114
	$profile_areas = array(
115
		'info' => array(
116
			'title' => $txt['profileInfo'],
117
			'areas' => array(
118
				'summary' => array(
119
					'label' => $txt['summary'],
120
					'file' => 'Profile-View.php',
121
					'function' => 'summary',
122
					'icon' => 'administration',
123
					'permission' => array(
124
						'own' => 'is_not_guest',
125
						'any' => 'profile_view',
126
					),
127
				),
128
				'popup' => array(
129
					'function' => 'profile_popup',
130
					'permission' => array(
131
						'own' => 'is_not_guest',
132
						'any' => array(),
133
					),
134
					'select' => 'summary',
135
				),
136
				'alerts_popup' => array(
137
					'function' => 'alerts_popup',
138
					'permission' => array(
139
						'own' => 'is_not_guest',
140
						'any' => array(),
141
					),
142
					'select' => 'summary',
143
				),
144
				'statistics' => array(
145
					'label' => $txt['statPanel'],
146
					'file' => 'Profile-View.php',
147
					'function' => 'statPanel',
148
					'icon' => 'stats',
149
					'permission' => array(
150
						'own' => 'is_not_guest',
151
						'any' => 'profile_view',
152
					),
153
				),
154
				'showposts' => array(
155
					'label' => $txt['showPosts'],
156
					'file' => 'Profile-View.php',
157
					'function' => 'showPosts',
158
					'icon' => 'posts',
159
					'subsections' => array(
160
						'messages' => array($txt['showMessages'], array('is_not_guest', 'profile_view')),
161
						'topics' => array($txt['showTopics'], array('is_not_guest', 'profile_view')),
162
						'unwatchedtopics' => array($txt['showUnwatched'], array('is_not_guest', 'profile_view'), 'enabled' => $context['user']['is_owner']),
163
						'attach' => array($txt['showAttachments'], array('is_not_guest', 'profile_view')),
164
					),
165
					'permission' => array(
166
						'own' => 'is_not_guest',
167
						'any' => 'profile_view',
168
					),
169
				),
170
				'showdrafts' => array(
171
					'label' => $txt['drafts_show'],
172
					'file' => 'Drafts.php',
173
					'function' => 'showProfileDrafts',
174
					'icon' => 'drafts',
175
					'enabled' => !empty($modSettings['drafts_post_enabled']) && $context['user']['is_owner'],
176
					'permission' => array(
177
						'own' => 'is_not_guest',
178
						'any' => array(),
179
					),
180
				),
181
				'showalerts' => array(
182
					'label' => $txt['alerts_show'],
183
					'file' => 'Profile-View.php',
184
					'function' => 'showAlerts',
185
					'icon' => 'alerts',
186
					'permission' => array(
187
						'own' => 'is_not_guest',
188
						'any' => array(),
189
					),
190
				),
191
				'permissions' => array(
192
					'label' => $txt['showPermissions'],
193
					'file' => 'Profile-View.php',
194
					'function' => 'showPermissions',
195
					'icon' => 'permissions',
196
					'permission' => array(
197
						'own' => 'manage_permissions',
198
						'any' => 'manage_permissions',
199
					),
200
				),
201
				'tracking' => array(
202
					'label' => $txt['trackUser'],
203
					'file' => 'Profile-View.php',
204
					'function' => 'tracking',
205
					'icon' => 'logs',
206
					'subsections' => array(
207
						'activity' => array($txt['trackActivity'], 'moderate_forum'),
208
						'ip' => array($txt['trackIP'], 'moderate_forum'),
209
						'edits' => array($txt['trackEdits'], 'moderate_forum', 'enabled' => !empty($modSettings['userlog_enabled'])),
210
						'groupreq' => array($txt['trackGroupRequests'], 'approve_group_requests', 'enabled' => !empty($modSettings['show_group_membership'])),
211
						'logins' => array($txt['trackLogins'], 'moderate_forum', 'enabled' => !empty($modSettings['loginHistoryDays'])),
212
					),
213
					'permission' => array(
214
						'own' => array('moderate_forum', 'approve_group_requests'),
215
						'any' => array('moderate_forum', 'approve_group_requests'),
216
					),
217
				),
218
				'viewwarning' => array(
219
					'label' => $txt['profile_view_warnings'],
220
					'enabled' => $modSettings['warning_settings'][0] == 1 && $cur_profile['warning'],
221
					'file' => 'Profile-View.php',
222
					'function' => 'viewWarning',
223
					'icon' => 'warning',
224
					'permission' => array(
225
						'own' => array('profile_warning_own', 'profile_warning_any', 'issue_warning', 'moderate_forum'),
226
						'any' => array('profile_warning_any', 'issue_warning', 'moderate_forum'),
227
					),
228
				),
229
			),
230
		),
231
		'edit_profile' => array(
232
			'title' => $txt['forumprofile'],
233
			'areas' => array(
234
				'account' => array(
235
					'label' => $txt['account'],
236
					'file' => 'Profile-Modify.php',
237
					'function' => 'account',
238
					'icon' => 'maintain',
239
					'enabled' => $context['user']['is_admin'] || ($cur_profile['id_group'] != 1 && !in_array(1, explode(',', $cur_profile['additional_groups']))),
240
					'sc' => 'post',
241
					'token' => 'profile-ac%u',
242
					'password' => true,
243
					'permission' => array(
244
						'own' => array('profile_identity_any', 'profile_identity_own', 'profile_password_any', 'profile_password_own', 'manage_membergroups'),
245
						'any' => array('profile_identity_any', 'profile_password_any', 'manage_membergroups'),
246
					),
247
				),
248
				'tfasetup' => array(
249
					'label' => $txt['account'],
250
					'file' => 'Profile-Modify.php',
251
					'function' => 'tfasetup',
252
					'token' => 'profile-tfa%u',
253
					'enabled' => !empty($modSettings['tfa_mode']),
254
					'hidden' => true,
255
					'select' => 'account',
256
					'permission' => array(
257
						'own' => array('profile_password_own'),
258
						'any' => array('profile_password_any'),
259
					),
260
				),
261
				'tfadisable' => array(
262
					'label' => $txt['account'],
263
					'file' => 'Profile-Modify.php',
264
					'function' => 'tfadisable',
265
					'token' => 'profile-tfa%u',
266
					'sc' => 'post',
267
					'password' => true,
268
					'enabled' => !empty($modSettings['tfa_mode']),
269
					'hidden' => true,
270
					'select' => 'account',
271
					'permission' => array(
272
						'own' => array('profile_password_own'),
273
						'any' => array('profile_password_any'),
274
					),
275
				),
276
				'forumprofile' => array(
277
					'label' => $txt['forumprofile'],
278
					'file' => 'Profile-Modify.php',
279
					'function' => 'forumProfile',
280
					'icon' => 'members',
281
					'sc' => 'post',
282
					'token' => 'profile-fp%u',
283
					'permission' => array(
284
						'own' => array('profile_forum_any', 'profile_forum_own'),
285
						'any' => array('profile_forum_any'),
286
					),
287
				),
288
				'theme' => array(
289
					'label' => $txt['theme'],
290
					'file' => 'Profile-Modify.php',
291
					'function' => 'theme',
292
					'icon' => 'features',
293
					'sc' => 'post',
294
					'token' => 'profile-th%u',
295
					'permission' => array(
296
						'own' => array('profile_extra_any', 'profile_extra_own'),
297
						'any' => array('profile_extra_any'),
298
					),
299
				),
300
				'notification' => array(
301
					'label' => $txt['notification'],
302
					'file' => 'Profile-Modify.php',
303
					'function' => 'notification',
304
					'icon' => 'mail',
305
					'sc' => 'post',
306
					//'token' => 'profile-nt%u', This is not checked here. We do it in the function itself - but if it was checked, this is what it'd be.
307
					'subsections' => array(
308
						'alerts' => array($txt['alert_prefs'], array('is_not_guest', 'profile_extra_any')),
309
						'topics' => array($txt['watched_topics'], array('is_not_guest', 'profile_extra_any')),
310
						'boards' => array($txt['watched_boards'], array('is_not_guest', 'profile_extra_any')),
311
					),
312
					'permission' => array(
313
						'own' => array('is_not_guest'),
314
						'any' => array('profile_extra_any'), // If you change this, update it in the functions themselves; we delegate all saving checks there.
315
					),
316
				),
317
				'ignoreboards' => array(
318
					'label' => $txt['ignoreboards'],
319
					'file' => 'Profile-Modify.php',
320
					'function' => 'ignoreboards',
321
					'icon' => 'boards',
322
					'enabled' => !empty($modSettings['allow_ignore_boards']),
323
					'sc' => 'post',
324
					'token' => 'profile-ib%u',
325
					'permission' => array(
326
						'own' => array('profile_extra_any', 'profile_extra_own'),
327
						'any' => array('profile_extra_any'),
328
					),
329
				),
330
				'lists' => array(
331
					'label' => $txt['editBuddyIgnoreLists'],
332
					'file' => 'Profile-Modify.php',
333
					'function' => 'editBuddyIgnoreLists',
334
					'icon' => 'frenemy',
335
					'enabled' => !empty($modSettings['enable_buddylist']) && $context['user']['is_owner'],
336
					'sc' => 'post',
337
					'subsections' => array(
338
						'buddies' => array($txt['editBuddies']),
339
						'ignore' => array($txt['editIgnoreList']),
340
					),
341
					'permission' => array(
342
						'own' => array('profile_extra_any', 'profile_extra_own'),
343
						'any' => array(),
344
					),
345
				),
346
				'groupmembership' => array(
347
					'label' => $txt['groupmembership'],
348
					'file' => 'Profile-Modify.php',
349
					'function' => 'groupMembership',
350
					'icon' => 'people',
351
					'enabled' => !empty($modSettings['show_group_membership']) && $context['user']['is_owner'],
352
					'sc' => 'request',
353
					'token' => 'profile-gm%u',
354
					'token_type' => 'request',
355
					'permission' => array(
356
						'own' => array('is_not_guest'),
357
						'any' => array('manage_membergroups'),
358
					),
359
				),
360
			),
361
		),
362
		'profile_action' => array(
363
			'title' => $txt['profileAction'],
364
			'areas' => array(
365
				'sendpm' => array(
366
					'label' => $txt['profileSendIm'],
367
					'custom_url' => $scripturl . '?action=pm;sa=send',
368
					'icon' => 'personal_message',
369
					'enabled' => allowedTo('profile_view'),
370
					'permission' => array(
371
						'own' => array(),
372
						'any' => array('pm_send'),
373
					),
374
				),
375
				'report' => array(
376
					'label' => $txt['report_profile'],
377
					'custom_url' => $scripturl . '?action=reporttm;' . $context['session_var'] . '=' . $context['session_id'],
378
					'icon' => 'warning',
379
					'enabled' => allowedTo('profile_view'),
380
					'permission' => array(
381
						'own' => array(),
382
						'any' => array('report_user'),
383
					),
384
				),
385
				'issuewarning' => array(
386
					'label' => $txt['profile_issue_warning'],
387
					'enabled' => $modSettings['warning_settings'][0] == 1,
388
					'file' => 'Profile-Actions.php',
389
					'function' => 'issueWarning',
390
					'icon' => 'warning',
391
					'token' => 'profile-iw%u',
392
					'permission' => array(
393
						'own' => array(),
394
						'any' => array('issue_warning'),
395
					),
396
				),
397
				'banuser' => array(
398
					'label' => $txt['profileBanUser'],
399
					'custom_url' => $scripturl . '?action=admin;area=ban;sa=add',
400
					'icon' => 'ban',
401
					'enabled' => $cur_profile['id_group'] != 1 && !in_array(1, explode(',', $cur_profile['additional_groups'])),
402
					'permission' => array(
403
						'own' => array(),
404
						'any' => array('manage_bans'),
405
					),
406
				),
407
				'subscriptions' => array(
408
					'label' => $txt['subscriptions'],
409
					'file' => 'Profile-Actions.php',
410
					'function' => 'subscriptions',
411
					'icon' => 'paid',
412
					'enabled' => !empty($modSettings['paid_enabled']) && $context['subs_available'],
413
					'permission' => array(
414
						'own' => array('is_not_guest'),
415
						'any' => array('moderate_forum'),
416
					),
417
				),
418
				'deleteaccount' => array(
419
					'label' => $txt['deleteAccount'],
420
					'file' => 'Profile-Actions.php',
421
					'function' => 'deleteAccount',
422
					'icon' => 'members_delete',
423
					'sc' => 'post',
424
					'token' => 'profile-da%u',
425
					'password' => true,
426
					'permission' => array(
427
						'own' => array('profile_remove_any', 'profile_remove_own'),
428
						'any' => array('profile_remove_any'),
429
					),
430
				),
431
				'activateaccount' => array(
432
					'file' => 'Profile-Actions.php',
433
					'function' => 'activateAccount',
434
					'icon' => 'regcenter',
435
					'sc' => 'get',
436
					'token' => 'profile-aa%u',
437
					'token_type' => 'get',
438
					'permission' => array(
439
						'own' => array(),
440
						'any' => array('moderate_forum'),
441
					),
442
				),
443
			),
444
		),
445
	);
446
447
	// Let them modify profile areas easily.
448
	call_integration_hook('integrate_pre_profile_areas', array(&$profile_areas));
449
450
	// Do some cleaning ready for the menu function.
451
	$context['password_areas'] = array();
452
	$current_area = isset($_REQUEST['area']) ? $_REQUEST['area'] : '';
453
454
	foreach ($profile_areas as $section_id => $section)
455
	{
456
		// Do a bit of spring cleaning so to speak.
457
		foreach ($section['areas'] as $area_id => $area)
458
		{
459
			// If it said no permissions that meant it wasn't valid!
460
			if (empty($area['permission'][$context['user']['is_owner'] ? 'own' : 'any']))
461
				$profile_areas[$section_id]['areas'][$area_id]['enabled'] = false;
462
			// Otherwise pick the right set.
463
			else
464
				$profile_areas[$section_id]['areas'][$area_id]['permission'] = $area['permission'][$context['user']['is_owner'] ? 'own' : 'any'];
465
466
			// Password required in most cases
467
			if (!empty($area['password']))
468
				$context['password_areas'][] = $area_id;
469
		}
470
	}
471
472
	// Is there an updated message to show?
473
	if (isset($_GET['updated']))
474
		$context['profile_updated'] = $txt['profile_updated_own'];
475
476
	// Set a few options for the menu.
477
	$menuOptions = array(
478
		'disable_url_session_check' => true,
479
		'current_area' => $current_area,
480
		'extra_url_parameters' => array(
481
			'u' => $context['id_member'],
482
		),
483
	);
484
485
	// Actually create the menu!
486
	$profile_include_data = createMenu($profile_areas, $menuOptions);
487
488
	// No menu means no access.
489
	if (!$profile_include_data && (!$user_info['is_guest'] || validateSession()))
490
		fatal_lang_error('no_access', false);
491
492
	// Make a note of the Unique ID for this menu.
493
	$context['profile_menu_id'] = $context['max_menu_id'];
494
	$context['profile_menu_name'] = 'menu_data_' . $context['profile_menu_id'];
495
496
	// Set the selected item - now it's been validated.
497
	$current_area = $profile_include_data['current_area'];
498
	$current_sa = $profile_include_data['current_subsection'];
499
	$context['menu_item_selected'] = $current_area;
500
501
	// Before we go any further, let's work on the area we've said is valid. Note this is done here just in case we ever compromise the menu function in error!
502
	$context['completed_save'] = false;
503
	$context['do_preview'] = isset($_REQUEST['preview_signature']);
504
505
	$security_checks = array();
506
	$found_area = false;
507
	foreach ($profile_areas as $section_id => $section)
508
	{
509
		// Do a bit of spring cleaning so to speak.
510
		foreach ($section['areas'] as $area_id => $area)
511
		{
512
			// Is this our area?
513
			if ($current_area == $area_id)
514
			{
515
				// This can't happen - but is a security check.
516
				if ((isset($section['enabled']) && $section['enabled'] == false) || (isset($area['enabled']) && $area['enabled'] == false))
517
					fatal_lang_error('no_access', false);
518
519
				// Are we saving data in a valid area?
520
				if (isset($area['sc']) && (isset($_REQUEST['save']) || $context['do_preview']))
521
				{
522
					$security_checks['session'] = $area['sc'];
523
					$context['completed_save'] = true;
524
				}
525
526
				// Do we need to perform a token check?
527
				if (!empty($area['token']))
528
				{
529
					$security_checks[isset($_REQUEST['save']) ? 'validateToken' : 'needsToken'] = $area['token'];
530
					$token_name = $area['token'] !== true ? str_replace('%u', $context['id_member'], $area['token']) : 'profile-u' . $context['id_member'];
531
532
					$token_type = isset($area['token_type']) && in_array($area['token_type'], array('request', 'post', 'get')) ? $area['token_type'] : 'post';
533
				}
534
535
				// Does this require session validating?
536
				if (!empty($area['validate']) || (isset($_REQUEST['save']) && !$context['user']['is_owner']))
537
					$security_checks['validate'] = true;
538
539
				// Permissions for good measure.
540
				if (!empty($profile_include_data['permission']))
541
					$security_checks['permission'] = $profile_include_data['permission'];
542
543
				// Either way got something.
544
				$found_area = true;
545
			}
546
		}
547
	}
548
549
	// Oh dear, some serious security lapse is going on here... we'll put a stop to that!
550
	if (!$found_area)
551
		fatal_lang_error('no_access', false);
552
553
	// Release this now.
554
	unset($profile_areas);
555
556
	// Now the context is setup have we got any security checks to carry out additional to that above?
557
	if (isset($security_checks['session']))
558
		checkSession($security_checks['session']);
559
	if (isset($security_checks['validate']))
560
		validateSession();
561
	if (isset($security_checks['validateToken']))
562
		validateToken($token_name, $token_type);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $token_name does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $token_type does not seem to be defined for all execution paths leading up to this point.
Loading history...
563
	if (isset($security_checks['permission']))
564
		isAllowedTo($security_checks['permission']);
565
566
	// Create a token if needed.
567
	if (isset($security_checks['needsToken']) || isset($security_checks['validateToken']))
568
	{
569
		createToken($token_name, $token_type);
570
		$context['token_check'] = $token_name;
571
	}
572
573
	// File to include?
574
	if (isset($profile_include_data['file']))
575
		require_once($sourcedir . '/' . $profile_include_data['file']);
576
577
	// Build the link tree.
578
	$context['linktree'][] = array(
579
		'url' => $scripturl . '?action=profile' . ($memID != $user_info['id'] ? ';u=' . $memID : ''),
580
		'name' => sprintf($txt['profile_of_username'], $context['member']['name']),
581
	);
582
583
	if (!empty($profile_include_data['label']))
584
		$context['linktree'][] = array(
585
			'url' => $scripturl . '?action=profile' . ($memID != $user_info['id'] ? ';u=' . $memID : '') . ';area=' . $profile_include_data['current_area'],
586
			'name' => $profile_include_data['label'],
587
		);
588
589
	if (!empty($profile_include_data['current_subsection']) && $profile_include_data['subsections'][$profile_include_data['current_subsection']][0] != $profile_include_data['label'])
590
		$context['linktree'][] = array(
591
			'url' => $scripturl . '?action=profile' . ($memID != $user_info['id'] ? ';u=' . $memID : '') . ';area=' . $profile_include_data['current_area'] . ';sa=' . $profile_include_data['current_subsection'],
592
			'name' => $profile_include_data['subsections'][$profile_include_data['current_subsection']][0],
593
		);
594
595
	// Set the template for this area and add the profile layer.
596
	$context['sub_template'] = $profile_include_data['function'];
597
	$context['template_layers'][] = 'profile';
598
599
	// All the subactions that require a user password in order to validate.
600
	$check_password = $context['user']['is_owner'] && in_array($profile_include_data['current_area'], $context['password_areas']);
601
	$context['require_password'] = $check_password;
602
603
	loadJavaScriptFile('profile.js', array('defer' => false, 'minimize' => true), 'smf_profile');
604
605
	// These will get populated soon!
606
	$post_errors = array();
607
	$profile_vars = array();
608
609
	// Right - are we saving - if so let's save the old data first.
610
	if ($context['completed_save'])
611
	{
612
		// Clean up the POST variables.
613
		$_POST = htmltrim__recursive($_POST);
614
		$_POST = htmlspecialchars__recursive($_POST);
615
616
		if ($check_password)
617
		{
618
			// Check to ensure we're forcing SSL for authentication
619
			if (!empty($modSettings['force_ssl']) && empty($maintenance) && !httpsOn())
620
				fatal_lang_error('login_ssl_required', false);
621
622
			$password = isset($_POST['oldpasswrd']) ? $_POST['oldpasswrd'] :  '';
623
624
			// You didn't even enter a password!
625
			if (trim($password) == '')
626
				$post_errors[] = 'no_password';
627
628
			// Since the password got modified due to all the $_POST cleaning, lets undo it so we can get the correct password
629
			$password = un_htmlspecialchars($password);
630
631
			// Does the integration want to check passwords?
632
			$good_password = in_array(true, call_integration_hook('integrate_verify_password', array($cur_profile['member_name'], $password, false)), true);
633
634
			// Bad password!!!
635
			if (!$good_password && !hash_verify_password($user_profile[$memID]['member_name'], un_htmlspecialchars(stripslashes($password)), $user_info['passwd']))
636
				$post_errors[] = 'bad_password';
637
638
			// Warn other elements not to jump the gun and do custom changes!
639
			if (in_array('bad_password', $post_errors))
640
				$context['password_auth_failed'] = true;
641
		}
642
643
		// Change the IP address in the database.
644
		if ($context['user']['is_owner'] && $menuOptions['current_area'] != 'tfasetup')
645
			$profile_vars['member_ip'] = $user_info['ip'];
646
647
		// Now call the sub-action function...
648
		if ($current_area == 'activateaccount')
649
		{
650
			if (empty($post_errors))
651
				activateAccount($memID);
652
		}
653
		elseif ($current_area == 'deleteaccount')
654
		{
655
			if (empty($post_errors))
656
			{
657
				deleteAccount2($memID);
658
				redirectexit();
659
			}
660
		}
661
		elseif ($menuOptions['current_area'] == 'tfadisable')
662
		{
663
			// Already checked the password, token, permissions, and session.
664
			$profile_vars += array(
665
				'tfa_secret' => '',
666
				'tfa_backup' => '',
667
			);
668
		}
669
		elseif ($current_area == 'groupmembership' && empty($post_errors))
670
		{
671
			$msg = groupMembership2($profile_vars, $post_errors, $memID);
672
673
			// Whatever we've done, we have nothing else to do here...
674
			redirectexit('action=profile' . ($context['user']['is_owner'] ? '' : ';u=' . $memID) . ';area=groupmembership' . (!empty($msg) ? ';msg=' . $msg : ''));
675
		}
676
		elseif (in_array($current_area, array('account', 'forumprofile', 'theme')))
677
			saveProfileFields();
678
		else
679
		{
680
			$force_redirect = true;
681
			// Ensure we include this.
682
			require_once($sourcedir . '/Profile-Modify.php');
683
			saveProfileChanges($profile_vars, $post_errors, $memID);
684
		}
685
686
		call_integration_hook('integrate_profile_save', array(&$profile_vars, &$post_errors, $memID, $cur_profile, $current_area));
687
688
		// There was a problem, let them try to re-enter.
689
		if (!empty($post_errors))
690
		{
691
			// Load the language file so we can give a nice explanation of the errors.
692
			loadLanguage('Errors');
693
			$context['post_errors'] = $post_errors;
694
		}
695
		elseif (!empty($profile_vars))
696
		{
697
			// If we've changed the password, notify any integration that may be listening in.
698
			if (isset($profile_vars['passwd']))
699
				call_integration_hook('integrate_reset_pass', array($cur_profile['member_name'], $cur_profile['member_name'], $_POST['passwrd2']));
700
701
			updateMemberData($memID, $profile_vars);
702
703
			// What if this is the newest member?
704
			if ($modSettings['latestMember'] == $memID)
705
				updateStats('member');
706
			elseif (isset($profile_vars['real_name']))
707
				updateSettings(array('memberlist_updated' => time()));
708
709
			// If the member changed his/her birthdate, update calendar statistics.
710
			if (isset($profile_vars['birthdate']) || isset($profile_vars['real_name']))
711
				updateSettings(array(
712
					'calendar_updated' => time(),
713
				));
714
715
			// Anything worth logging?
716
			if (!empty($context['log_changes']) && !empty($modSettings['modlog_enabled']))
717
			{
718
				$log_changes = array();
719
				require_once($sourcedir . '/Logging.php');
720
				foreach ($context['log_changes'] as $k => $v)
721
					$log_changes[] = array(
722
						'action' => $k,
723
						'log_type' => 'user',
724
						'extra' => array_merge($v, array(
725
							'applicator' => $user_info['id'],
726
							'member_affected' => $memID,
727
						)),
728
					);
729
730
				logActions($log_changes);
731
			}
732
733
			// Have we got any post save functions to execute?
734
			if (!empty($context['profile_execute_on_save']))
735
				foreach ($context['profile_execute_on_save'] as $saveFunc)
736
					$saveFunc();
737
738
			// Let them know it worked!
739
			$context['profile_updated'] = $context['user']['is_owner'] ? $txt['profile_updated_own'] : sprintf($txt['profile_updated_else'], $cur_profile['member_name']);
740
741
			// Invalidate any cached data.
742
			cache_put_data('member_data-profile-' . $memID, null, 0);
743
		}
744
	}
745
746
	// Have some errors for some reason?
747
	if (!empty($post_errors))
748
	{
749
		// Set all the errors so the template knows what went wrong.
750
		foreach ($post_errors as $error_type)
751
			$context['modify_error'][$error_type] = true;
752
	}
753
	// If it's you then we should redirect upon save.
754
	elseif (!empty($profile_vars) && $context['user']['is_owner'] && !$context['do_preview'])
755
		redirectexit('action=profile;area=' . $current_area . (!empty($current_sa) ? ';sa=' . $current_sa : '') . ';updated');
756
	elseif (!empty($force_redirect))
757
		redirectexit('action=profile' . ($context['user']['is_owner'] ? '' : ';u=' . $memID) . ';area=' . $current_area);
758
759
	// Get the right callable.
760
	$call = call_helper($profile_include_data['function'], true);
761
762
	// Is it valid?
763
	if (!empty($call))
764
		call_user_func($call, $memID);
765
766
	// Set the page title if it's not already set...
767
	if (!isset($context['page_title']))
768
		$context['page_title'] = $txt['profile'] . (isset($txt[$current_area]) ? ' - ' . $txt[$current_area] : '');
769
}
770
771
/**
772
 * Set up the requirements for the profile popup - the area that is shown as the popup menu for the current user.
773
 *
774
 * @param int $memID The ID of the member
775
 */
776
function profile_popup($memID)
777
{
778
	global $context, $scripturl, $txt, $db_show_debug;
779
780
	// We do not want to output debug information here.
781
	$db_show_debug = false;
782
783
	// We only want to output our little layer here.
784
	$context['template_layers'] = array();
785
786
	// This list will pull from the master list wherever possible. Hopefully it should be clear what does what.
787
	$profile_items = array(
788
		array(
789
			'menu' => 'info',
790
			'area' => 'summary',
791
			'title' => $txt['popup_summary'],
792
		),
793
		array(
794
			'menu' => 'edit_profile',
795
			'area' => 'account',
796
		),
797
		array(
798
			'menu' => 'info',
799
			'area' => 'showposts',
800
			'title' => $txt['popup_showposts'],
801
		),
802
		array(
803
			'menu' => 'edit_profile',
804
			'area' => 'forumprofile',
805
			'title' => $txt['forumprofile'],
806
		),
807
		array(
808
			'menu' => 'edit_profile',
809
			'area' => 'notification',
810
		),
811
		array(
812
			'menu' => 'edit_profile',
813
			'area' => 'theme',
814
			'title' => $txt['theme'],
815
		),
816
		array(
817
			'menu' => 'edit_profile',
818
			'area' => 'ignoreboards',
819
		),
820
		array(
821
			'menu' => 'edit_profile',
822
			'area' => 'lists',
823
			'url' => $scripturl . '?action=profile;area=lists;sa=ignore',
824
			'title' => $txt['popup_ignore'],
825
		),
826
		array(
827
			'menu' => 'edit_profile',
828
			'area' => 'groupmembership',
829
		),
830
		array(
831
			'menu' => 'profile_action',
832
			'area' => 'subscriptions',
833
		),
834
	);
835
836
	call_integration_hook('integrate_profile_popup', array(&$profile_items));
837
838
	// Now check if these items are available
839
	$context['profile_items'] = array();
840
	$menu_context = &$context[$context['profile_menu_name']]['sections'];
841
	foreach ($profile_items as $item)
842
	{
843
		if (isset($menu_context[$item['menu']]['areas'][$item['area']]))
844
		{
845
			$context['profile_items'][] = $item;
846
		}
847
	}
848
}
849
850
/**
851
 * Set up the requirements for the alerts popup - the area that shows all the alerts just quickly for the current user.
852
 *
853
 * @param int $memID The ID of the member
854
 */
855
function alerts_popup($memID)
856
{
857
	global $context, $sourcedir, $db_show_debug, $cur_profile;
858
859
	// Load the Alerts language file.
860
	loadLanguage('Alerts');
861
862
	// We do not want to output debug information here.
863
	$db_show_debug = false;
864
865
	// We only want to output our little layer here.
866
	$context['template_layers'] = array();
867
868
	// No funny business allowed
869
	$counter = isset($_REQUEST['counter']) ? max(0, (int) $_REQUEST['counter']) : 0;
870
871
	$context['unread_alerts'] = array();
872
	if ($counter < $cur_profile['alerts'])
873
	{
874
		// Now fetch me my unread alerts, pronto!
875
		require_once($sourcedir . '/Profile-View.php');
876
		$context['unread_alerts'] = fetch_alerts($memID, false, !empty($counter) ? $cur_profile['alerts'] - $counter : 0, 0, !isset($_REQUEST['counter']));
877
878
		// This shouldn't happen, but just in case...
879
		if (empty($counter) && $cur_profile['alerts'] != count($context['unread_alerts']))
880
			updateMemberData($memID, array('alerts' => count($context['unread_alerts'])));
881
	}
882
}
883
884
/**
885
 * Load any custom fields for this area... no area means load all, 'summary' loads all public ones.
886
 *
887
 * @param int $memID The ID of the member
888
 * @param string $area Which area to load fields for
889
 */
890
function loadCustomFields($memID, $area = 'summary')
891
{
892
	global $context, $txt, $user_profile, $smcFunc, $user_info, $settings, $scripturl;
893
894
	// Get the right restrictions in place...
895
	$where = 'active = 1';
896
	if (!allowedTo('admin_forum') && $area != 'register')
897
	{
898
		// If it's the owner they can see two types of private fields, regardless.
899
		if ($memID == $user_info['id'])
900
			$where .= $area == 'summary' ? ' AND private < 3' : ' AND (private = 0 OR private = 2)';
901
		else
902
			$where .= $area == 'summary' ? ' AND private < 2' : ' AND private = 0';
903
	}
904
905
	if ($area == 'register')
906
		$where .= ' AND show_reg != 0';
907
	elseif ($area != 'summary')
908
		$where .= ' AND show_profile = {string:area}';
909
910
	// Load all the relevant fields - and data.
911
	$request = $smcFunc['db_query']('', '
912
		SELECT
913
			col_name, field_name, field_desc, field_type, field_order, show_reg, field_length, field_options,
914
			default_value, bbc, enclose, placement
915
		FROM {db_prefix}custom_fields
916
		WHERE ' . $where . '
917
		ORDER BY field_order',
918
		array(
919
			'area' => $area,
920
		)
921
	);
922
	$context['custom_fields'] = array();
923
	$context['custom_fields_required'] = false;
924
	while ($row = $smcFunc['db_fetch_assoc']($request))
925
	{
926
		// Shortcut.
927
		$exists = $memID && isset($user_profile[$memID], $user_profile[$memID]['options'][$row['col_name']]);
928
		$value = $exists ? $user_profile[$memID]['options'][$row['col_name']] : '';
929
930
		$currentKey = 0;
931
		if (!empty($row['field_options']))
932
		{
933
			$fieldOptions = explode(',', $row['field_options']);
934
			foreach ($fieldOptions as $k => $v)
935
			{
936
				if (empty($currentKey))
937
					$currentKey = $v === $value ? $k : 0;
938
			}
939
		}
940
941
		// If this was submitted already then make the value the posted version.
942
		if (isset($_POST['customfield']) && isset($_POST['customfield'][$row['col_name']]))
943
		{
944
			$value = $smcFunc['htmlspecialchars']($_POST['customfield'][$row['col_name']]);
945
			if (in_array($row['field_type'], array('select', 'radio')))
946
				$value = ($options = explode(',', $row['field_options'])) && isset($options[$value]) ? $options[$value] : '';
947
		}
948
949
		// Don't show the "disabled" option for the "gender" field if we are on the "summary" area.
950
		if ($area == 'summary' && $row['col_name'] == 'cust_gender' && $value == 'None')
951
			continue;
952
953
		// HTML for the input form.
954
		$output_html = $value;
955
		if ($row['field_type'] == 'check')
956
		{
957
			$true = (!$exists && $row['default_value']) || $value;
958
			$input_html = '<input type="checkbox" name="customfield[' . $row['col_name'] . ']" id="customfield[' . $row['col_name'] . ']"' . ($true ? ' checked' : '') . '>';
959
			$output_html = $true ? $txt['yes'] : $txt['no'];
960
		}
961
		elseif ($row['field_type'] == 'select')
962
		{
963
			$input_html = '<select name="customfield[' . $row['col_name'] . ']" id="customfield[' . $row['col_name'] . ']"><option value="-1"></option>';
964
			$options = explode(',', $row['field_options']);
965
			foreach ($options as $k => $v)
966
			{
967
				$true = (!$exists && $row['default_value'] == $v) || $value == $v;
968
				$input_html .= '<option value="' . $k . '"' . ($true ? ' selected' : '') . '>' . $v . '</option>';
969
				if ($true)
970
					$output_html = $v;
971
			}
972
973
			$input_html .= '</select>';
974
		}
975
		elseif ($row['field_type'] == 'radio')
976
		{
977
			$input_html = '<fieldset>';
978
			$options = explode(',', $row['field_options']);
979
			foreach ($options as $k => $v)
980
			{
981
				$true = (!$exists && $row['default_value'] == $v) || $value == $v;
982
				$input_html .= '<label for="customfield_' . $row['col_name'] . '_' . $k . '"><input type="radio" name="customfield[' . $row['col_name'] . ']" id="customfield_' . $row['col_name'] . '_' . $k . '" value="' . $k . '"' . ($true ? ' checked' : '') . '>' . $v . '</label><br>';
983
				if ($true)
984
					$output_html = $v;
985
			}
986
			$input_html .= '</fieldset>';
987
		}
988
		elseif ($row['field_type'] == 'text')
989
		{
990
			$input_html = '<input type="text" name="customfield[' . $row['col_name'] . ']" id="customfield[' . $row['col_name'] . ']"' . ($row['field_length'] != 0 ? ' maxlength="' . $row['field_length'] . '"' : '') . ' size="' . ($row['field_length'] == 0 || $row['field_length'] >= 50 ? 50 : ($row['field_length'] > 30 ? 30 : ($row['field_length'] > 10 ? 20 : 10))) . '" value="' . un_htmlspecialchars($value) . '"' . ($row['show_reg'] == 2 ? ' required' : '') . '>';
991
		}
992
		else
993
		{
994
			@list ($rows, $cols) = @explode(',', $row['default_value']);
995
			$input_html = '<textarea name="customfield[' . $row['col_name'] . ']" id="customfield[' . $row['col_name'] . ']"' . (!empty($rows) ? ' rows="' . $rows . '"' : '') . (!empty($cols) ? ' cols="' . $cols . '"' : '') . ($row['show_reg'] == 2 ? ' required' : '') . '>' . un_htmlspecialchars($value) . '</textarea>';
996
		}
997
998
		// Parse BBCode
999
		if ($row['bbc'])
1000
			$output_html = parse_bbc($output_html);
1001
		elseif ($row['field_type'] == 'textarea')
1002
			// Allow for newlines at least
1003
			$output_html = strtr($output_html, array("\n" => '<br>'));
1004
1005
		// Enclosing the user input within some other text?
1006
		if (!empty($row['enclose']) && !empty($output_html))
1007
			$output_html = strtr($row['enclose'], array(
1008
				'{SCRIPTURL}' => $scripturl,
1009
				'{IMAGES_URL}' => $settings['images_url'],
1010
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1011
				'{INPUT}' => un_htmlspecialchars($output_html),
1012
				'{KEY}' => $currentKey
1013
			));
1014
1015
		$context['custom_fields'][] = array(
1016
			'name' => $row['field_name'],
1017
			'desc' => $row['field_desc'],
1018
			'type' => $row['field_type'],
1019
			'order' => $row['field_order'],
1020
			'input_html' => $input_html,
1021
			'output_html' => $output_html,
1022
			'placement' => $row['placement'],
1023
			'colname' => $row['col_name'],
1024
			'value' => $value,
1025
			'show_reg' => $row['show_reg'],
1026
		);
1027
		$context['custom_fields_required'] = $context['custom_fields_required'] || $row['show_reg'] == 2;
1028
	}
1029
	$smcFunc['db_free_result']($request);
1030
1031
	call_integration_hook('integrate_load_custom_profile_fields', array($memID, $area));
1032
}
1033
1034
?>