Issues (1065)

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 https://www.simplemachines.org
12
 * @copyright 2023 Simple Machines and individual contributors
13
 * @license https://www.simplemachines.org/about/smf/license.php BSD
14
 *
15
 * @version 2.1.4
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('view_warning_own', 'view_warning_any', 'issue_warning', 'moderate_forum'),
226
						'any' => array('view_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' => 'alerts',
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
				'getprofiledata' => array(
419
					'label' => $txt['export_profile_data'],
420
					'file' => 'Profile-Export.php',
421
					'function' => 'export_profile_data',
422
					'icon' => 'packages',
423
					// 'token' => 'profile-ex%u', // This is not checked here. We do it in the function itself - but if it was checked, this is what it'd be.
424
					'permission' => array(
425
						'own' => array('profile_view_own'),
426
						'any' => array('moderate_forum'),
427
					),
428
				),
429
				'download' => array(
430
					'label' => $txt['export_profile_data'],
431
					'file' => 'Profile-Export.php',
432
					'function' => 'download_export_file',
433
					'icon' => 'packages',
434
					'hidden' => true,
435
					'select' => 'getprofiledata',
436
					'permission' => array(
437
						'own' => array('profile_view_own'),
438
						'any' => array('moderate_forum'),
439
					),
440
				),
441
				'dlattach' => array(
442
					'label' => $txt['export_profile_data'],
443
					'file' => 'Profile-Export.php',
444
					'function' => 'export_attachment',
445
					'icon' => 'packages',
446
					'hidden' => true,
447
					'select' => 'getprofiledata',
448
					'permission' => array(
449
						'own' => array('profile_view_own'),
450
						'any' => array(),
451
					),
452
				),
453
				'deleteaccount' => array(
454
					'label' => $txt['deleteAccount'],
455
					'file' => 'Profile-Actions.php',
456
					'function' => 'deleteAccount',
457
					'icon' => 'members_delete',
458
					'sc' => 'post',
459
					'token' => 'profile-da%u',
460
					'password' => true,
461
					'permission' => array(
462
						'own' => array('profile_remove_any', 'profile_remove_own'),
463
						'any' => array('profile_remove_any'),
464
					),
465
				),
466
				'activateaccount' => array(
467
					'file' => 'Profile-Actions.php',
468
					'function' => 'activateAccount',
469
					'icon' => 'regcenter',
470
					'sc' => 'get',
471
					'token' => 'profile-aa%u',
472
					'token_type' => 'get',
473
					'permission' => array(
474
						'own' => array(),
475
						'any' => array('moderate_forum'),
476
					),
477
				),
478
				// A logout link just for the popup menu.
479
				'logout' => array(
480
					'label' => $txt['logout'],
481
					'custom_url' => $scripturl . '?action=logout;%1$s=%2$s',
482
					'icon' => 'logout',
483
					'enabled' => !empty($_REQUEST['area']) && $_REQUEST['area'] === 'popup',
484
					'permission' => array(
485
						'own' => array('is_not_guest'),
486
						'any' => array(),
487
					),
488
				),
489
			),
490
		),
491
	);
492
493
	// Let them modify profile areas easily.
494
	call_integration_hook('integrate_profile_areas', array(&$profile_areas));
495
496
	// Deprecated since 2.1.4 and will be removed in 3.0.0. Kept for compatibility with early versions of 2.1.
497
	// @todo add runtime warnings.
498
	call_integration_hook('integrate_pre_profile_areas', array(&$profile_areas));
499
500
	// Do some cleaning ready for the menu function.
501
	$context['password_areas'] = array();
502
	$current_area = isset($_REQUEST['area']) ? $_REQUEST['area'] : '';
503
504
	foreach ($profile_areas as $section_id => $section)
505
	{
506
		// Do a bit of spring cleaning so to speak.
507
		foreach ($section['areas'] as $area_id => $area)
508
		{
509
			// If it said no permissions that meant it wasn't valid!
510
			if (empty($area['permission'][$context['user']['is_owner'] ? 'own' : 'any']))
511
				$profile_areas[$section_id]['areas'][$area_id]['enabled'] = false;
512
			// Otherwise pick the right set.
513
			else
514
				$profile_areas[$section_id]['areas'][$area_id]['permission'] = $area['permission'][$context['user']['is_owner'] ? 'own' : 'any'];
515
516
			// Password required in most cases
517
			if (!empty($area['password']))
518
				$context['password_areas'][] = $area_id;
519
		}
520
	}
521
522
	// Is there an updated message to show?
523
	if (isset($_GET['updated']))
524
		$context['profile_updated'] = $txt['profile_updated_own'];
525
526
	// Set a few options for the menu.
527
	$menuOptions = array(
528
		'disable_hook_call' => true,
529
		'disable_url_session_check' => true,
530
		'current_area' => $current_area,
531
		'extra_url_parameters' => array(
532
			'u' => $context['id_member'],
533
		),
534
	);
535
536
	// Logging out requires the session id in the url.
537
	$profile_areas['profile_action']['areas']['logout']['custom_url'] = sprintf($profile_areas['profile_action']['areas']['logout']['custom_url'], $context['session_var'], $context['session_id']);
538
539
	// Actually create the menu!
540
	$profile_include_data = createMenu($profile_areas, $menuOptions);
541
542
	// No menu means no access.
543
	if (!$profile_include_data && (!$user_info['is_guest'] || validateSession()))
544
		fatal_lang_error('no_access', false);
545
546
	// Make a note of the Unique ID for this menu.
547
	$context['profile_menu_id'] = $context['max_menu_id'];
548
	$context['profile_menu_name'] = 'menu_data_' . $context['profile_menu_id'];
549
550
	// Set the selected item - now it's been validated.
551
	$current_area = $profile_include_data['current_area'];
552
	$current_sa = $profile_include_data['current_subsection'];
553
	$context['menu_item_selected'] = $current_area;
554
555
	// 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!
556
	$context['completed_save'] = false;
557
	$context['do_preview'] = isset($_REQUEST['preview_signature']);
558
559
	$security_checks = array();
560
	$found_area = false;
561
	foreach ($profile_areas as $section_id => $section)
562
	{
563
		// Do a bit of spring cleaning so to speak.
564
		foreach ($section['areas'] as $area_id => $area)
565
		{
566
			// Is this our area?
567
			if ($current_area == $area_id)
568
			{
569
				// This can't happen - but is a security check.
570
				if ((isset($section['enabled']) && $section['enabled'] == false) || (isset($area['enabled']) && $area['enabled'] == false))
571
					fatal_lang_error('no_access', false);
572
573
				// Are we saving data in a valid area?
574
				if (isset($area['sc']) && (isset($_REQUEST['save']) || $context['do_preview']))
575
				{
576
					$security_checks['session'] = $area['sc'];
577
					$context['completed_save'] = true;
578
				}
579
580
				// Do we need to perform a token check?
581
				if (!empty($area['token']))
582
				{
583
					$security_checks[isset($_REQUEST['save']) ? 'validateToken' : 'needsToken'] = $area['token'];
584
					$token_name = $area['token'] !== true ? str_replace('%u', $context['id_member'], $area['token']) : 'profile-u' . $context['id_member'];
585
586
					$token_type = isset($area['token_type']) && in_array($area['token_type'], array('request', 'post', 'get')) ? $area['token_type'] : 'post';
587
				}
588
589
				// Does this require session validating?
590
				if (!empty($area['validate']) || (isset($_REQUEST['save']) && !$context['user']['is_owner'] && ($area_id != 'issuewarning' || empty($modSettings['securityDisable_moderate']))))
591
					$security_checks['validate'] = true;
592
593
				// Permissions for good measure.
594
				if (!empty($profile_include_data['permission']))
595
					$security_checks['permission'] = $profile_include_data['permission'];
596
597
				// Either way got something.
598
				$found_area = true;
599
			}
600
		}
601
	}
602
603
	// Oh dear, some serious security lapse is going on here... we'll put a stop to that!
604
	if (!$found_area)
605
		fatal_lang_error('no_access', false);
606
607
	// Release this now.
608
	unset($profile_areas);
609
610
	// Now the context is setup have we got any security checks to carry out additional to that above?
611
	if (isset($security_checks['session']))
612
		checkSession($security_checks['session']);
613
	if (isset($security_checks['validate']))
614
		validateSession();
615
	if (isset($security_checks['validateToken']))
616
		validateToken($token_name, $token_type);
0 ignored issues
show
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...
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...
617
	if (isset($security_checks['permission']))
618
		isAllowedTo($security_checks['permission']);
619
620
	// Create a token if needed.
621
	if (isset($security_checks['needsToken']) || isset($security_checks['validateToken']))
622
	{
623
		createToken($token_name, $token_type);
624
		$context['token_check'] = $token_name;
625
	}
626
627
	// File to include?
628
	if (isset($profile_include_data['file']))
629
		require_once($sourcedir . '/' . $profile_include_data['file']);
630
631
	// Build the link tree.
632
	$context['linktree'][] = array(
633
		'url' => $scripturl . '?action=profile' . ($memID != $user_info['id'] ? ';u=' . $memID : ''),
634
		'name' => sprintf($txt['profile_of_username'], $context['member']['name']),
635
	);
636
637
	if (!empty($profile_include_data['label']))
638
		$context['linktree'][] = array(
639
			'url' => $scripturl . '?action=profile' . ($memID != $user_info['id'] ? ';u=' . $memID : '') . ';area=' . $profile_include_data['current_area'],
640
			'name' => $profile_include_data['label'],
641
		);
642
643
	if (!empty($profile_include_data['current_subsection']) && $profile_include_data['subsections'][$profile_include_data['current_subsection']][0] != $profile_include_data['label'])
644
		$context['linktree'][] = array(
645
			'url' => $scripturl . '?action=profile' . ($memID != $user_info['id'] ? ';u=' . $memID : '') . ';area=' . $profile_include_data['current_area'] . ';sa=' . $profile_include_data['current_subsection'],
646
			'name' => $profile_include_data['subsections'][$profile_include_data['current_subsection']][0],
647
		);
648
649
	// Set the template for this area and add the profile layer.
650
	$context['sub_template'] = $profile_include_data['function'];
651
	$context['template_layers'][] = 'profile';
652
653
	// All the subactions that require a user password in order to validate.
654
	$check_password = $context['user']['is_owner'] && in_array($profile_include_data['current_area'], $context['password_areas']);
655
	$context['require_password'] = $check_password;
656
657
	loadJavaScriptFile('profile.js', array('defer' => false, 'minimize' => true), 'smf_profile');
658
659
	// These will get populated soon!
660
	$post_errors = array();
661
	$profile_vars = array();
662
663
	// Right - are we saving - if so let's save the old data first.
664
	if ($context['completed_save'])
665
	{
666
		// Clean up the POST variables.
667
		$_POST = htmltrim__recursive($_POST);
668
		$_POST = htmlspecialchars__recursive($_POST);
669
670
		if ($check_password)
671
		{
672
			// Check to ensure we're forcing SSL for authentication
673
			if (!empty($modSettings['force_ssl']) && empty($maintenance) && !httpsOn())
674
				fatal_lang_error('login_ssl_required', false);
675
676
			$password = isset($_POST['oldpasswrd']) ? $_POST['oldpasswrd'] :  '';
677
678
			// You didn't even enter a password!
679
			if (trim($password) == '')
680
				$post_errors[] = 'no_password';
681
682
			// Since the password got modified due to all the $_POST cleaning, lets undo it so we can get the correct password
683
			$password = un_htmlspecialchars($password);
684
685
			// Does the integration want to check passwords?
686
			$good_password = in_array(true, call_integration_hook('integrate_verify_password', array($cur_profile['member_name'], $password, false)), true);
687
688
			// Bad password!!!
689
			if (!$good_password && !hash_verify_password($user_profile[$memID]['member_name'], $password, $user_info['passwd']))
690
				$post_errors[] = 'bad_password';
691
692
			// Warn other elements not to jump the gun and do custom changes!
693
			if (in_array('bad_password', $post_errors))
694
				$context['password_auth_failed'] = true;
695
		}
696
697
		// Change the IP address in the database.
698
		if ($context['user']['is_owner'] && $menuOptions['current_area'] != 'tfasetup')
699
			$profile_vars['member_ip'] = $user_info['ip'];
700
701
		// Now call the sub-action function...
702
		if ($current_area == 'activateaccount')
703
		{
704
			if (empty($post_errors))
705
				activateAccount($memID);
706
		}
707
		elseif ($current_area == 'deleteaccount')
708
		{
709
			if (empty($post_errors))
710
			{
711
				deleteAccount2($memID);
712
				redirectexit();
713
			}
714
		}
715
		elseif ($menuOptions['current_area'] == 'tfadisable')
716
		{
717
			// Already checked the password, token, permissions, and session.
718
			$profile_vars += array(
719
				'tfa_secret' => '',
720
				'tfa_backup' => '',
721
			);
722
		}
723
		elseif ($current_area == 'groupmembership' && empty($post_errors))
724
		{
725
			$msg = groupMembership2($profile_vars, $post_errors, $memID);
726
727
			// Whatever we've done, we have nothing else to do here...
728
			redirectexit('action=profile' . ($context['user']['is_owner'] ? '' : ';u=' . $memID) . ';area=groupmembership' . (!empty($msg) ? ';msg=' . $msg : ''));
729
		}
730
		elseif (in_array($current_area, array('account', 'forumprofile', 'theme')))
731
			saveProfileFields();
732
		else
733
		{
734
			$force_redirect = true;
735
			// Ensure we include this.
736
			require_once($sourcedir . '/Profile-Modify.php');
737
			saveProfileChanges($profile_vars, $post_errors, $memID);
738
		}
739
740
		call_integration_hook('integrate_profile_save', array(&$profile_vars, &$post_errors, $memID, $cur_profile, $current_area));
741
742
		// There was a problem, let them try to re-enter.
743
		if (!empty($post_errors))
744
		{
745
			// Load the language file so we can give a nice explanation of the errors.
746
			loadLanguage('Errors');
747
			$context['post_errors'] = $post_errors;
748
		}
749
		elseif (!empty($profile_vars))
750
		{
751
			// If we've changed the password, notify any integration that may be listening in.
752
			if (isset($profile_vars['passwd']))
753
				call_integration_hook('integrate_reset_pass', array($cur_profile['member_name'], $cur_profile['member_name'], $_POST['passwrd2']));
754
755
			updateMemberData($memID, $profile_vars);
756
757
			// What if this is the newest member?
758
			if ($modSettings['latestMember'] == $memID)
759
				updateStats('member');
760
			elseif (isset($profile_vars['real_name']))
761
				updateSettings(array('memberlist_updated' => time()));
762
763
			// If the member changed his/her birthdate, update calendar statistics.
764
			if (isset($profile_vars['birthdate']) || isset($profile_vars['real_name']))
765
				updateSettings(array(
766
					'calendar_updated' => time(),
767
				));
768
769
			// Anything worth logging?
770
			if (!empty($context['log_changes']) && !empty($modSettings['modlog_enabled']))
771
			{
772
				$log_changes = array();
773
				require_once($sourcedir . '/Logging.php');
774
				foreach ($context['log_changes'] as $k => $v)
775
					$log_changes[] = array(
776
						'action' => $k,
777
						'log_type' => 'user',
778
						'extra' => array_merge($v, array(
779
							'applicator' => $user_info['id'],
780
							'member_affected' => $memID,
781
						)),
782
					);
783
784
				logActions($log_changes);
785
			}
786
787
			// Have we got any post save functions to execute?
788
			if (!empty($context['profile_execute_on_save']))
789
				foreach ($context['profile_execute_on_save'] as $saveFunc)
790
					$saveFunc();
791
792
			// Let them know it worked!
793
			$context['profile_updated'] = $context['user']['is_owner'] ? $txt['profile_updated_own'] : sprintf($txt['profile_updated_else'], $cur_profile['member_name']);
794
795
			// Invalidate any cached data.
796
			cache_put_data('member_data-profile-' . $memID, null, 0);
797
		}
798
	}
799
800
	// Have some errors for some reason?
801
	if (!empty($post_errors))
802
	{
803
		// Set all the errors so the template knows what went wrong.
804
		foreach ($post_errors as $error_type)
805
			$context['modify_error'][$error_type] = true;
806
	}
807
	// If it's you then we should redirect upon save.
808
	elseif (!empty($profile_vars) && $context['user']['is_owner'] && !$context['do_preview'])
809
		redirectexit('action=profile;area=' . $current_area . (!empty($current_sa) ? ';sa=' . $current_sa : '') . ';updated');
810
	elseif (!empty($force_redirect))
811
		redirectexit('action=profile' . ($context['user']['is_owner'] ? '' : ';u=' . $memID) . ';area=' . $current_area);
812
813
	// Get the right callable.
814
	$call = call_helper($profile_include_data['function'], true);
815
816
	// Is it valid?
817
	if (!empty($call))
818
		call_user_func($call, $memID);
819
820
	// Set the page title if it's not already set...
821
	if (!isset($context['page_title']))
822
		$context['page_title'] = $txt['profile'] . (isset($txt[$current_area]) ? ' - ' . $txt[$current_area] : '');
823
}
824
825
/**
826
 * Set up the requirements for the profile popup - the area that is shown as the popup menu for the current user.
827
 *
828
 * @param int $memID The ID of the member
829
 */
830
function profile_popup($memID)
831
{
832
	global $context, $scripturl, $txt, $db_show_debug;
833
834
	// We do not want to output debug information here.
835
	$db_show_debug = false;
836
837
	// We only want to output our little layer here.
838
	$context['template_layers'] = array();
839
840
	// This list will pull from the master list wherever possible. Hopefully it should be clear what does what.
841
	$profile_items = array(
842
		array(
843
			'menu' => 'edit_profile',
844
			'area' => 'account',
845
		),
846
		array(
847
			'menu' => 'edit_profile',
848
			'area' => 'forumprofile',
849
			'title' => $txt['popup_forumprofile'],
850
		),
851
		array(
852
			'menu' => 'edit_profile',
853
			'area' => 'theme',
854
			'title' => $txt['theme'],
855
		),
856
		array(
857
			'menu' => 'edit_profile',
858
			'area' => 'notification',
859
		),
860
		array(
861
			'menu' => 'edit_profile',
862
			'area' => 'ignoreboards',
863
		),
864
		array(
865
			'menu' => 'edit_profile',
866
			'area' => 'lists',
867
			'url' => $scripturl . '?action=profile;area=lists;sa=ignore',
868
			'title' => $txt['popup_ignore'],
869
		),
870
		array(
871
			'menu' => 'info',
872
			'area' => 'showposts',
873
			'title' => $txt['popup_showposts'],
874
		),
875
		array(
876
			'menu' => 'info',
877
			'area' => 'showdrafts',
878
			'title' => $txt['popup_showdrafts'],
879
		),
880
		array(
881
			'menu' => 'edit_profile',
882
			'area' => 'groupmembership',
883
			'title' => $txt['popup_groupmembership'],
884
		),
885
		array(
886
			'menu' => 'profile_action',
887
			'area' => 'subscriptions',
888
			'title' => $txt['popup_subscriptions'],
889
		),
890
		array(
891
			'menu' => 'profile_action',
892
			'area' => 'logout',
893
		),
894
	);
895
896
	call_integration_hook('integrate_profile_popup', array(&$profile_items));
897
898
	// Now check if these items are available
899
	$context['profile_items'] = array();
900
	$menu_context = &$context[$context['profile_menu_name']]['sections'];
901
	foreach ($profile_items as $item)
902
	{
903
		if (isset($menu_context[$item['menu']]['areas'][$item['area']]))
904
		{
905
			$context['profile_items'][] = $item;
906
		}
907
	}
908
}
909
910
/**
911
 * Set up the requirements for the alerts popup - the area that shows all the alerts just quickly for the current user.
912
 *
913
 * @param int $memID The ID of the member
914
 */
915
function alerts_popup($memID)
916
{
917
	global $context, $sourcedir, $db_show_debug, $cur_profile, $modSettings;
918
919
	// Load the Alerts language file.
920
	loadLanguage('Alerts');
921
922
	// We do not want to output debug information here.
923
	$db_show_debug = false;
924
925
	// We only want to output our little layer here.
926
	$context['template_layers'] = array();
927
928
	// No funny business allowed
929
	$counter = isset($_REQUEST['counter']) ? max(0, (int) $_REQUEST['counter']) : 0;
930
	$limit = !empty($modSettings['alerts_per_page']) && (int) $modSettings['alerts_per_page'] < 1000 ? min((int) $modSettings['alerts_per_page'], 1000) : 25;
931
932
	$context['unread_alerts'] = array();
933
	if ($counter < $cur_profile['alerts'])
934
	{
935
		// Now fetch me my unread alerts, pronto!
936
		require_once($sourcedir . '/Profile-View.php');
937
		$context['unread_alerts'] = fetch_alerts($memID, false, !empty($counter) ? $cur_profile['alerts'] - $counter : $limit, 0, !isset($_REQUEST['counter']));
938
	}
939
}
940
941
/**
942
 * Load any custom fields for this area... no area means load all, 'summary' loads all public ones.
943
 *
944
 * @param int $memID The ID of the member
945
 * @param string $area Which area to load fields for
946
 */
947
function loadCustomFields($memID, $area = 'summary')
948
{
949
	global $context, $txt, $user_profile, $smcFunc, $user_info, $settings, $scripturl;
950
951
	// Get the right restrictions in place...
952
	$where = 'active = 1';
953
	if (!allowedTo('admin_forum') && $area != 'register')
954
	{
955
		// If it's the owner they can see two types of private fields, regardless.
956
		if ($memID == $user_info['id'])
957
			$where .= $area == 'summary' ? ' AND private < 3' : ' AND (private = 0 OR private = 2)';
958
		else
959
			$where .= $area == 'summary' ? ' AND private < 2' : ' AND private = 0';
960
	}
961
962
	if ($area == 'register')
963
		$where .= ' AND show_reg != 0';
964
	elseif ($area != 'summary')
965
		$where .= ' AND show_profile = {string:area}';
966
967
	// Load all the relevant fields - and data.
968
	$request = $smcFunc['db_query']('', '
969
		SELECT
970
			col_name, field_name, field_desc, field_type, field_order, show_reg, field_length, field_options,
971
			default_value, bbc, enclose, placement
972
		FROM {db_prefix}custom_fields
973
		WHERE ' . $where . '
974
		ORDER BY field_order',
975
		array(
976
			'area' => $area,
977
		)
978
	);
979
	$context['custom_fields'] = array();
980
	$context['custom_fields_required'] = false;
981
	while ($row = $smcFunc['db_fetch_assoc']($request))
982
	{
983
		// Shortcut.
984
		$exists = $memID && isset($user_profile[$memID], $user_profile[$memID]['options'][$row['col_name']]);
985
		$value = $exists ? $user_profile[$memID]['options'][$row['col_name']] : '';
986
987
		$currentKey = 0;
988
		if (!empty($row['field_options']))
989
		{
990
			$fieldOptions = explode(',', $row['field_options']);
991
			foreach ($fieldOptions as $k => $v)
992
			{
993
				if (empty($currentKey))
994
					$currentKey = $v === $value ? $k : 0;
995
			}
996
		}
997
998
		// If this was submitted already then make the value the posted version.
999
		if (isset($_POST['customfield']) && isset($_POST['customfield'][$row['col_name']]))
1000
		{
1001
			$value = $smcFunc['htmlspecialchars']($_POST['customfield'][$row['col_name']]);
1002
			if (in_array($row['field_type'], array('select', 'radio')))
1003
				$value = ($options = explode(',', $row['field_options'])) && isset($options[$value]) ? $options[$value] : '';
1004
		}
1005
1006
		// Don't show the "disabled" option for the "gender" field if we are on the "summary" area.
1007
		if ($area == 'summary' && $row['col_name'] == 'cust_gender' && $value == '{gender_0}')
1008
			continue;
1009
1010
		// HTML for the input form.
1011
		$output_html = $value;
1012
		if ($row['field_type'] == 'check')
1013
		{
1014
			$true = (!$exists && $row['default_value']) || $value;
1015
			$input_html = '<input type="checkbox" name="customfield[' . $row['col_name'] . ']" id="customfield[' . $row['col_name'] . ']"' . ($true ? ' checked' : '') . '>';
1016
			$output_html = $true ? $txt['yes'] : $txt['no'];
1017
		}
1018
		elseif ($row['field_type'] == 'select')
1019
		{
1020
			$input_html = '<select name="customfield[' . $row['col_name'] . ']" id="customfield[' . $row['col_name'] . ']"><option value="-1"></option>';
1021
			$options = explode(',', $row['field_options']);
1022
			foreach ($options as $k => $v)
1023
			{
1024
				$true = (!$exists && $row['default_value'] == $v) || $value == $v;
1025
				$input_html .= '<option value="' . $k . '"' . ($true ? ' selected' : '') . '>' . tokenTxtReplace($v) . '</option>';
1026
				if ($true)
1027
					$output_html = $v;
1028
			}
1029
1030
			$input_html .= '</select>';
1031
		}
1032
		elseif ($row['field_type'] == 'radio')
1033
		{
1034
			$input_html = '<fieldset>';
1035
			$options = explode(',', $row['field_options']);
1036
			foreach ($options as $k => $v)
1037
			{
1038
				$true = (!$exists && $row['default_value'] == $v) || $value == $v;
1039
				$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' : '') . '>' . tokenTxtReplace($v) . '</label><br>';
1040
				if ($true)
1041
					$output_html = $v;
1042
			}
1043
			$input_html .= '</fieldset>';
1044
		}
1045
		elseif ($row['field_type'] == 'text')
1046
		{
1047
			$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' : '') . '>';
1048
		}
1049
		else
1050
		{
1051
			@list ($rows, $cols) = @explode(',', $row['default_value']);
1052
			$input_html = '<textarea name="customfield[' . $row['col_name'] . ']" id="customfield[' . $row['col_name'] . ']"' . ($row['field_length'] != 0 ? ' maxlength="' . $row['field_length'] . '"' : '') . (!empty($rows) ? ' rows="' . $rows . '"' : '') . (!empty($cols) ? ' cols="' . $cols . '"' : '') . ($row['show_reg'] == 2 ? ' required' : '') . '>' . un_htmlspecialchars($value) . '</textarea>';
1053
		}
1054
1055
		// Parse BBCode
1056
		if ($row['bbc'])
1057
			$output_html = parse_bbc($output_html);
1058
		elseif ($row['field_type'] == 'textarea')
1059
			// Allow for newlines at least
1060
			$output_html = strtr($output_html, array("\n" => '<br>'));
1061
1062
		// Enclosing the user input within some other text?
1063
		if (!empty($row['enclose']) && !empty($output_html))
1064
			$output_html = strtr($row['enclose'], array(
1065
				'{SCRIPTURL}' => $scripturl,
1066
				'{IMAGES_URL}' => $settings['images_url'],
1067
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1068
				'{INPUT}' => un_htmlspecialchars($output_html),
1069
				'{KEY}' => $currentKey
1070
			));
1071
1072
		$context['custom_fields'][] = array(
1073
			'name' => tokenTxtReplace($row['field_name']),
1074
			'desc' => tokenTxtReplace($row['field_desc']),
1075
			'type' => $row['field_type'],
1076
			'order' => $row['field_order'],
1077
			'input_html' => $input_html,
1078
			'output_html' => tokenTxtReplace($output_html),
1079
			'placement' => $row['placement'],
1080
			'colname' => $row['col_name'],
1081
			'value' => $value,
1082
			'show_reg' => $row['show_reg'],
1083
		);
1084
		$context['custom_fields_required'] = $context['custom_fields_required'] || $row['show_reg'] == 2;
1085
	}
1086
	$smcFunc['db_free_result']($request);
1087
1088
	call_integration_hook('integrate_load_custom_profile_fields', array($memID, $area));
1089
}
1090
1091
?>