Issues (1061)

Sources/Profile.php (8 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 2020 Simple Machines and individual contributors
13
 * @license https://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
				'getprofiledata' => array(
419
					'label' => $txt['export_profile_data'],
420
					'file' => 'Profile-Actions.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-Actions.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
				'deleteaccount' => array(
442
					'label' => $txt['deleteAccount'],
443
					'file' => 'Profile-Actions.php',
444
					'function' => 'deleteAccount',
445
					'icon' => 'members_delete',
446
					'sc' => 'post',
447
					'token' => 'profile-da%u',
448
					'password' => true,
449
					'permission' => array(
450
						'own' => array('profile_remove_any', 'profile_remove_own'),
451
						'any' => array('profile_remove_any'),
452
					),
453
				),
454
				'activateaccount' => array(
455
					'file' => 'Profile-Actions.php',
456
					'function' => 'activateAccount',
457
					'icon' => 'regcenter',
458
					'sc' => 'get',
459
					'token' => 'profile-aa%u',
460
					'token_type' => 'get',
461
					'permission' => array(
462
						'own' => array(),
463
						'any' => array('moderate_forum'),
464
					),
465
				),
466
				// A logout link just for the popup menu.
467
				'logout' => array(
468
					'label' => $txt['logout'],
469
					'custom_url' => $scripturl . '?action=logout;%1$s=%2$s',
470
					'enabled' => !empty($_REQUEST['area']) && $_REQUEST['area'] === 'popup',
471
					'permission' => array(
472
						'own' => array('is_not_guest'),
473
						'any' => array(),
474
					),
475
				),
476
			),
477
		),
478
	);
479
480
	// Let them modify profile areas easily.
481
	call_integration_hook('integrate_pre_profile_areas', array(&$profile_areas));
482
483
	// Do some cleaning ready for the menu function.
484
	$context['password_areas'] = array();
485
	$current_area = isset($_REQUEST['area']) ? $_REQUEST['area'] : '';
486
487
	foreach ($profile_areas as $section_id => $section)
488
	{
489
		// Do a bit of spring cleaning so to speak.
490
		foreach ($section['areas'] as $area_id => $area)
491
		{
492
			// If it said no permissions that meant it wasn't valid!
493
			if (empty($area['permission'][$context['user']['is_owner'] ? 'own' : 'any']))
494
				$profile_areas[$section_id]['areas'][$area_id]['enabled'] = false;
495
			// Otherwise pick the right set.
496
			else
497
				$profile_areas[$section_id]['areas'][$area_id]['permission'] = $area['permission'][$context['user']['is_owner'] ? 'own' : 'any'];
498
499
			// Password required in most cases
500
			if (!empty($area['password']))
501
				$context['password_areas'][] = $area_id;
502
		}
503
	}
504
505
	// Is there an updated message to show?
506
	if (isset($_GET['updated']))
507
		$context['profile_updated'] = $txt['profile_updated_own'];
508
509
	// Set a few options for the menu.
510
	$menuOptions = array(
511
		'disable_url_session_check' => true,
512
		'current_area' => $current_area,
513
		'extra_url_parameters' => array(
514
			'u' => $context['id_member'],
515
		),
516
	);
517
518
	// Logging out requires the session id in the url.
519
	$profile_areas['profile_action']['areas']['logout']['custom_url'] = sprintf($profile_areas['profile_action']['areas']['logout']['custom_url'], $context['session_var'], $context['session_id']);
520
521
	// Actually create the menu!
522
	$profile_include_data = createMenu($profile_areas, $menuOptions);
523
524
	// No menu means no access.
525
	if (!$profile_include_data && (!$user_info['is_guest'] || validateSession()))
526
		fatal_lang_error('no_access', false);
527
528
	// Make a note of the Unique ID for this menu.
529
	$context['profile_menu_id'] = $context['max_menu_id'];
530
	$context['profile_menu_name'] = 'menu_data_' . $context['profile_menu_id'];
531
532
	// Set the selected item - now it's been validated.
533
	$current_area = $profile_include_data['current_area'];
534
	$current_sa = $profile_include_data['current_subsection'];
535
	$context['menu_item_selected'] = $current_area;
536
537
	// 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!
538
	$context['completed_save'] = false;
539
	$context['do_preview'] = isset($_REQUEST['preview_signature']);
540
541
	$security_checks = array();
542
	$found_area = false;
543
	foreach ($profile_areas as $section_id => $section)
544
	{
545
		// Do a bit of spring cleaning so to speak.
546
		foreach ($section['areas'] as $area_id => $area)
547
		{
548
			// Is this our area?
549
			if ($current_area == $area_id)
550
			{
551
				// This can't happen - but is a security check.
552
				if ((isset($section['enabled']) && $section['enabled'] == false) || (isset($area['enabled']) && $area['enabled'] == false))
553
					fatal_lang_error('no_access', false);
554
555
				// Are we saving data in a valid area?
556
				if (isset($area['sc']) && (isset($_REQUEST['save']) || $context['do_preview']))
557
				{
558
					$security_checks['session'] = $area['sc'];
559
					$context['completed_save'] = true;
560
				}
561
562
				// Do we need to perform a token check?
563
				if (!empty($area['token']))
564
				{
565
					$security_checks[isset($_REQUEST['save']) ? 'validateToken' : 'needsToken'] = $area['token'];
566
					$token_name = $area['token'] !== true ? str_replace('%u', $context['id_member'], $area['token']) : 'profile-u' . $context['id_member'];
567
568
					$token_type = isset($area['token_type']) && in_array($area['token_type'], array('request', 'post', 'get')) ? $area['token_type'] : 'post';
569
				}
570
571
				// Does this require session validating?
572
				if (!empty($area['validate']) || (isset($_REQUEST['save']) && !$context['user']['is_owner']))
573
					$security_checks['validate'] = true;
574
575
				// Permissions for good measure.
576
				if (!empty($profile_include_data['permission']))
577
					$security_checks['permission'] = $profile_include_data['permission'];
578
579
				// Either way got something.
580
				$found_area = true;
581
			}
582
		}
583
	}
584
585
	// Oh dear, some serious security lapse is going on here... we'll put a stop to that!
586
	if (!$found_area)
587
		fatal_lang_error('no_access', false);
588
589
	// Release this now.
590
	unset($profile_areas);
591
592
	// Now the context is setup have we got any security checks to carry out additional to that above?
593
	if (isset($security_checks['session']))
594
		checkSession($security_checks['session']);
595
	if (isset($security_checks['validate']))
596
		validateSession();
597
	if (isset($security_checks['validateToken']))
598
		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...
599
	if (isset($security_checks['permission']))
600
		isAllowedTo($security_checks['permission']);
601
602
	// Create a token if needed.
603
	if (isset($security_checks['needsToken']) || isset($security_checks['validateToken']))
604
	{
605
		createToken($token_name, $token_type);
606
		$context['token_check'] = $token_name;
607
	}
608
609
	// File to include?
610
	if (isset($profile_include_data['file']))
611
		require_once($sourcedir . '/' . $profile_include_data['file']);
612
613
	// Build the link tree.
614
	$context['linktree'][] = array(
615
		'url' => $scripturl . '?action=profile' . ($memID != $user_info['id'] ? ';u=' . $memID : ''),
616
		'name' => sprintf($txt['profile_of_username'], $context['member']['name']),
617
	);
618
619
	if (!empty($profile_include_data['label']))
620
		$context['linktree'][] = array(
621
			'url' => $scripturl . '?action=profile' . ($memID != $user_info['id'] ? ';u=' . $memID : '') . ';area=' . $profile_include_data['current_area'],
622
			'name' => $profile_include_data['label'],
623
		);
624
625
	if (!empty($profile_include_data['current_subsection']) && $profile_include_data['subsections'][$profile_include_data['current_subsection']][0] != $profile_include_data['label'])
626
		$context['linktree'][] = array(
627
			'url' => $scripturl . '?action=profile' . ($memID != $user_info['id'] ? ';u=' . $memID : '') . ';area=' . $profile_include_data['current_area'] . ';sa=' . $profile_include_data['current_subsection'],
628
			'name' => $profile_include_data['subsections'][$profile_include_data['current_subsection']][0],
629
		);
630
631
	// Set the template for this area and add the profile layer.
632
	$context['sub_template'] = $profile_include_data['function'];
633
	$context['template_layers'][] = 'profile';
634
635
	// All the subactions that require a user password in order to validate.
636
	$check_password = $context['user']['is_owner'] && in_array($profile_include_data['current_area'], $context['password_areas']);
637
	$context['require_password'] = $check_password;
638
639
	loadJavaScriptFile('profile.js', array('defer' => false, 'minimize' => true), 'smf_profile');
640
641
	// These will get populated soon!
642
	$post_errors = array();
643
	$profile_vars = array();
644
645
	// Right - are we saving - if so let's save the old data first.
646
	if ($context['completed_save'])
647
	{
648
		// Clean up the POST variables.
649
		$_POST = htmltrim__recursive($_POST);
650
		$_POST = htmlspecialchars__recursive($_POST);
651
652
		if ($check_password)
653
		{
654
			// Check to ensure we're forcing SSL for authentication
655
			if (!empty($modSettings['force_ssl']) && empty($maintenance) && !httpsOn())
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $maintenance seems to never exist and therefore empty should always be true.
Loading history...
656
				fatal_lang_error('login_ssl_required', false);
657
658
			$password = isset($_POST['oldpasswrd']) ? $_POST['oldpasswrd'] :  '';
659
660
			// You didn't even enter a password!
661
			if (trim($password) == '')
662
				$post_errors[] = 'no_password';
663
664
			// Since the password got modified due to all the $_POST cleaning, lets undo it so we can get the correct password
665
			$password = un_htmlspecialchars($password);
666
667
			// Does the integration want to check passwords?
668
			$good_password = in_array(true, call_integration_hook('integrate_verify_password', array($cur_profile['member_name'], $password, false)), true);
669
670
			// Bad password!!!
671
			if (!$good_password && !hash_verify_password($user_profile[$memID]['member_name'], un_htmlspecialchars(stripslashes($password)), $user_info['passwd']))
672
				$post_errors[] = 'bad_password';
673
674
			// Warn other elements not to jump the gun and do custom changes!
675
			if (in_array('bad_password', $post_errors))
676
				$context['password_auth_failed'] = true;
677
		}
678
679
		// Change the IP address in the database.
680
		if ($context['user']['is_owner'] && $menuOptions['current_area'] != 'tfasetup')
681
			$profile_vars['member_ip'] = $user_info['ip'];
682
683
		// Now call the sub-action function...
684
		if ($current_area == 'activateaccount')
685
		{
686
			if (empty($post_errors))
687
				activateAccount($memID);
688
		}
689
		elseif ($current_area == 'deleteaccount')
690
		{
691
			if (empty($post_errors))
692
			{
693
				deleteAccount2($memID);
694
				redirectexit();
695
			}
696
		}
697
		elseif ($menuOptions['current_area'] == 'tfadisable')
698
		{
699
			// Already checked the password, token, permissions, and session.
700
			$profile_vars += array(
701
				'tfa_secret' => '',
702
				'tfa_backup' => '',
703
			);
704
		}
705
		elseif ($current_area == 'groupmembership' && empty($post_errors))
706
		{
707
			$msg = groupMembership2($profile_vars, $post_errors, $memID);
708
709
			// Whatever we've done, we have nothing else to do here...
710
			redirectexit('action=profile' . ($context['user']['is_owner'] ? '' : ';u=' . $memID) . ';area=groupmembership' . (!empty($msg) ? ';msg=' . $msg : ''));
711
		}
712
		elseif (in_array($current_area, array('account', 'forumprofile', 'theme')))
713
			saveProfileFields();
714
		else
715
		{
716
			$force_redirect = true;
717
			// Ensure we include this.
718
			require_once($sourcedir . '/Profile-Modify.php');
719
			saveProfileChanges($profile_vars, $post_errors, $memID);
720
		}
721
722
		call_integration_hook('integrate_profile_save', array(&$profile_vars, &$post_errors, $memID, $cur_profile, $current_area));
723
724
		// There was a problem, let them try to re-enter.
725
		if (!empty($post_errors))
726
		{
727
			// Load the language file so we can give a nice explanation of the errors.
728
			loadLanguage('Errors');
729
			$context['post_errors'] = $post_errors;
730
		}
731
		elseif (!empty($profile_vars))
732
		{
733
			// If we've changed the password, notify any integration that may be listening in.
734
			if (isset($profile_vars['passwd']))
735
				call_integration_hook('integrate_reset_pass', array($cur_profile['member_name'], $cur_profile['member_name'], $_POST['passwrd2']));
736
737
			updateMemberData($memID, $profile_vars);
738
739
			// What if this is the newest member?
740
			if ($modSettings['latestMember'] == $memID)
741
				updateStats('member');
742
			elseif (isset($profile_vars['real_name']))
743
				updateSettings(array('memberlist_updated' => time()));
744
745
			// If the member changed his/her birthdate, update calendar statistics.
746
			if (isset($profile_vars['birthdate']) || isset($profile_vars['real_name']))
747
				updateSettings(array(
748
					'calendar_updated' => time(),
749
				));
750
751
			// Anything worth logging?
752
			if (!empty($context['log_changes']) && !empty($modSettings['modlog_enabled']))
753
			{
754
				$log_changes = array();
755
				require_once($sourcedir . '/Logging.php');
756
				foreach ($context['log_changes'] as $k => $v)
757
					$log_changes[] = array(
758
						'action' => $k,
759
						'log_type' => 'user',
760
						'extra' => array_merge($v, array(
761
							'applicator' => $user_info['id'],
762
							'member_affected' => $memID,
763
						)),
764
					);
765
766
				logActions($log_changes);
767
			}
768
769
			// Have we got any post save functions to execute?
770
			if (!empty($context['profile_execute_on_save']))
771
				foreach ($context['profile_execute_on_save'] as $saveFunc)
772
					$saveFunc();
773
774
			// Let them know it worked!
775
			$context['profile_updated'] = $context['user']['is_owner'] ? $txt['profile_updated_own'] : sprintf($txt['profile_updated_else'], $cur_profile['member_name']);
776
777
			// Invalidate any cached data.
778
			cache_put_data('member_data-profile-' . $memID, null, 0);
779
		}
780
	}
781
782
	// Have some errors for some reason?
783
	if (!empty($post_errors))
784
	{
785
		// Set all the errors so the template knows what went wrong.
786
		foreach ($post_errors as $error_type)
787
			$context['modify_error'][$error_type] = true;
788
	}
789
	// If it's you then we should redirect upon save.
790
	elseif (!empty($profile_vars) && $context['user']['is_owner'] && !$context['do_preview'])
791
		redirectexit('action=profile;area=' . $current_area . (!empty($current_sa) ? ';sa=' . $current_sa : '') . ';updated');
792
	elseif (!empty($force_redirect))
793
		redirectexit('action=profile' . ($context['user']['is_owner'] ? '' : ';u=' . $memID) . ';area=' . $current_area);
794
795
	// Get the right callable.
796
	$call = call_helper($profile_include_data['function'], true);
797
798
	// Is it valid?
799
	if (!empty($call))
800
		call_user_func($call, $memID);
801
802
	// Set the page title if it's not already set...
803
	if (!isset($context['page_title']))
804
		$context['page_title'] = $txt['profile'] . (isset($txt[$current_area]) ? ' - ' . $txt[$current_area] : '');
805
}
806
807
/**
808
 * Set up the requirements for the profile popup - the area that is shown as the popup menu for the current user.
809
 *
810
 * @param int $memID The ID of the member
811
 */
812
function profile_popup($memID)
813
{
814
	global $context, $scripturl, $txt, $db_show_debug;
815
816
	// We do not want to output debug information here.
817
	$db_show_debug = false;
818
819
	// We only want to output our little layer here.
820
	$context['template_layers'] = array();
821
822
	// This list will pull from the master list wherever possible. Hopefully it should be clear what does what.
823
	$profile_items = array(
824
		array(
825
			'menu' => 'edit_profile',
826
			'area' => 'account',
827
		),
828
		array(
829
			'menu' => 'edit_profile',
830
			'area' => 'forumprofile',
831
			'title' => $txt['popup_forumprofile'],
832
		),
833
		array(
834
			'menu' => 'edit_profile',
835
			'area' => 'theme',
836
			'title' => $txt['theme'],
837
		),
838
		array(
839
			'menu' => 'edit_profile',
840
			'area' => 'notification',
841
		),
842
		array(
843
			'menu' => 'edit_profile',
844
			'area' => 'ignoreboards',
845
		),
846
		array(
847
			'menu' => 'edit_profile',
848
			'area' => 'lists',
849
			'url' => $scripturl . '?action=profile;area=lists;sa=ignore',
850
			'title' => $txt['popup_ignore'],
851
		),
852
		array(
853
			'menu' => 'info',
854
			'area' => 'showposts',
855
			'title' => $txt['popup_showposts'],
856
		),
857
		array(
858
			'menu' => 'info',
859
			'area' => 'showdrafts',
860
			'title' => $txt['popup_showdrafts'],
861
		),
862
		array(
863
			'menu' => 'edit_profile',
864
			'area' => 'groupmembership',
865
			'title' => $txt['popup_groupmembership'],
866
		),
867
		array(
868
			'menu' => 'profile_action',
869
			'area' => 'subscriptions',
870
			'title' => $txt['popup_subscriptions'],
871
		),
872
		array(
873
			'menu' => 'profile_action',
874
			'area' => 'logout',
875
		),
876
	);
877
878
	call_integration_hook('integrate_profile_popup', array(&$profile_items));
879
880
	// Now check if these items are available
881
	$context['profile_items'] = array();
882
	$menu_context = &$context[$context['profile_menu_name']]['sections'];
883
	foreach ($profile_items as $item)
884
	{
885
		if (isset($menu_context[$item['menu']]['areas'][$item['area']]))
886
		{
887
			$context['profile_items'][] = $item;
888
		}
889
	}
890
}
891
892
/**
893
 * Set up the requirements for the alerts popup - the area that shows all the alerts just quickly for the current user.
894
 *
895
 * @param int $memID The ID of the member
896
 */
897
function alerts_popup($memID)
898
{
899
	global $context, $sourcedir, $db_show_debug, $cur_profile;
900
901
	// Load the Alerts language file.
902
	loadLanguage('Alerts');
903
904
	// We do not want to output debug information here.
905
	$db_show_debug = false;
906
907
	// We only want to output our little layer here.
908
	$context['template_layers'] = array();
909
910
	// No funny business allowed
911
	$counter = isset($_REQUEST['counter']) ? max(0, (int) $_REQUEST['counter']) : 0;
912
913
	$context['unread_alerts'] = array();
914
	if ($counter < $cur_profile['alerts'])
915
	{
916
		// Now fetch me my unread alerts, pronto!
917
		require_once($sourcedir . '/Profile-View.php');
918
		$context['unread_alerts'] = fetch_alerts($memID, false, !empty($counter) ? $cur_profile['alerts'] - $counter : 0, 0, !isset($_REQUEST['counter']));
0 ignored issues
show
0 of type integer is incompatible with the type array expected by parameter $offset of fetch_alerts(). ( Ignorable by Annotation )

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

918
		$context['unread_alerts'] = fetch_alerts($memID, false, !empty($counter) ? $cur_profile['alerts'] - $counter : 0, /** @scrutinizer ignore-type */ 0, !isset($_REQUEST['counter']));
Loading history...
! empty($counter) ? $cur...alerts'] - $counter : 0 of type integer is incompatible with the type array expected by parameter $limit of fetch_alerts(). ( Ignorable by Annotation )

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

918
		$context['unread_alerts'] = fetch_alerts($memID, false, /** @scrutinizer ignore-type */ !empty($counter) ? $cur_profile['alerts'] - $counter : 0, 0, !isset($_REQUEST['counter']));
Loading history...
919
920
		// This shouldn't happen, but just in case...
921
		if (empty($counter) && $cur_profile['alerts'] != count($context['unread_alerts']))
922
			updateMemberData($memID, array('alerts' => count($context['unread_alerts'])));
923
	}
924
}
925
926
/**
927
 * Load any custom fields for this area... no area means load all, 'summary' loads all public ones.
928
 *
929
 * @param int $memID The ID of the member
930
 * @param string $area Which area to load fields for
931
 */
932
function loadCustomFields($memID, $area = 'summary')
933
{
934
	global $context, $txt, $user_profile, $smcFunc, $user_info, $settings, $scripturl;
935
936
	// Get the right restrictions in place...
937
	$where = 'active = 1';
938
	if (!allowedTo('admin_forum') && $area != 'register')
939
	{
940
		// If it's the owner they can see two types of private fields, regardless.
941
		if ($memID == $user_info['id'])
942
			$where .= $area == 'summary' ? ' AND private < 3' : ' AND (private = 0 OR private = 2)';
943
		else
944
			$where .= $area == 'summary' ? ' AND private < 2' : ' AND private = 0';
945
	}
946
947
	if ($area == 'register')
948
		$where .= ' AND show_reg != 0';
949
	elseif ($area != 'summary')
950
		$where .= ' AND show_profile = {string:area}';
951
952
	// Load all the relevant fields - and data.
953
	$request = $smcFunc['db_query']('', '
954
		SELECT
955
			col_name, field_name, field_desc, field_type, field_order, show_reg, field_length, field_options,
956
			default_value, bbc, enclose, placement
957
		FROM {db_prefix}custom_fields
958
		WHERE ' . $where . '
959
		ORDER BY field_order',
960
		array(
961
			'area' => $area,
962
		)
963
	);
964
	$context['custom_fields'] = array();
965
	$context['custom_fields_required'] = false;
966
	while ($row = $smcFunc['db_fetch_assoc']($request))
967
	{
968
		// Shortcut.
969
		$exists = $memID && isset($user_profile[$memID], $user_profile[$memID]['options'][$row['col_name']]);
970
		$value = $exists ? $user_profile[$memID]['options'][$row['col_name']] : '';
971
972
		$currentKey = 0;
973
		if (!empty($row['field_options']))
974
		{
975
			$fieldOptions = explode(',', $row['field_options']);
976
			foreach ($fieldOptions as $k => $v)
977
			{
978
				if (empty($currentKey))
979
					$currentKey = $v === $value ? $k : 0;
980
			}
981
		}
982
983
		// If this was submitted already then make the value the posted version.
984
		if (isset($_POST['customfield']) && isset($_POST['customfield'][$row['col_name']]))
985
		{
986
			$value = $smcFunc['htmlspecialchars']($_POST['customfield'][$row['col_name']]);
987
			if (in_array($row['field_type'], array('select', 'radio')))
988
				$value = ($options = explode(',', $row['field_options'])) && isset($options[$value]) ? $options[$value] : '';
989
		}
990
991
		// Don't show the "disabled" option for the "gender" field if we are on the "summary" area.
992
		if ($area == 'summary' && $row['col_name'] == 'cust_gender' && $value == 'None')
993
			continue;
994
995
		// HTML for the input form.
996
		$output_html = $value;
997
		if ($row['field_type'] == 'check')
998
		{
999
			$true = (!$exists && $row['default_value']) || $value;
0 ignored issues
show
Consider adding parentheses for clarity. Current Interpretation: $true = (! $exists && $r...ault_value'] || $value), Probably Intended Meaning: $true = ! $exists && ($r...ault_value'] || $value)
Loading history...
1000
			$input_html = '<input type="checkbox" name="customfield[' . $row['col_name'] . ']" id="customfield[' . $row['col_name'] . ']"' . ($true ? ' checked' : '') . '>';
1001
			$output_html = $true ? $txt['yes'] : $txt['no'];
1002
		}
1003
		elseif ($row['field_type'] == 'select')
1004
		{
1005
			$input_html = '<select name="customfield[' . $row['col_name'] . ']" id="customfield[' . $row['col_name'] . ']"><option value="-1"></option>';
1006
			$options = explode(',', $row['field_options']);
1007
			foreach ($options as $k => $v)
1008
			{
1009
				$true = (!$exists && $row['default_value'] == $v) || $value == $v;
0 ignored issues
show
Consider adding parentheses for clarity. Current Interpretation: $true = (! $exists && $r... == $v || $value == $v), Probably Intended Meaning: $true = ! $exists && ($r... == $v || $value == $v)
Loading history...
1010
				$input_html .= '<option value="' . $k . '"' . ($true ? ' selected' : '') . '>' . $v . '</option>';
1011
				if ($true)
1012
					$output_html = $v;
1013
			}
1014
1015
			$input_html .= '</select>';
1016
		}
1017
		elseif ($row['field_type'] == 'radio')
1018
		{
1019
			$input_html = '<fieldset>';
1020
			$options = explode(',', $row['field_options']);
1021
			foreach ($options as $k => $v)
1022
			{
1023
				$true = (!$exists && $row['default_value'] == $v) || $value == $v;
0 ignored issues
show
Consider adding parentheses for clarity. Current Interpretation: $true = (! $exists && $r... == $v || $value == $v), Probably Intended Meaning: $true = ! $exists && ($r... == $v || $value == $v)
Loading history...
1024
				$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>';
1025
				if ($true)
1026
					$output_html = $v;
1027
			}
1028
			$input_html .= '</fieldset>';
1029
		}
1030
		elseif ($row['field_type'] == 'text')
1031
		{
1032
			$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' : '') . '>';
1033
		}
1034
		else
1035
		{
1036
			@list ($rows, $cols) = @explode(',', $row['default_value']);
1037
			$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>';
1038
		}
1039
1040
		// Parse BBCode
1041
		if ($row['bbc'])
1042
			$output_html = parse_bbc($output_html);
1043
		elseif ($row['field_type'] == 'textarea')
1044
			// Allow for newlines at least
1045
			$output_html = strtr($output_html, array("\n" => '<br>'));
1046
1047
		// Enclosing the user input within some other text?
1048
		if (!empty($row['enclose']) && !empty($output_html))
1049
			$output_html = strtr($row['enclose'], array(
1050
				'{SCRIPTURL}' => $scripturl,
1051
				'{IMAGES_URL}' => $settings['images_url'],
1052
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1053
				'{INPUT}' => un_htmlspecialchars($output_html),
1054
				'{KEY}' => $currentKey
1055
			));
1056
1057
		$context['custom_fields'][] = array(
1058
			'name' => $row['field_name'],
1059
			'desc' => $row['field_desc'],
1060
			'type' => $row['field_type'],
1061
			'order' => $row['field_order'],
1062
			'input_html' => $input_html,
1063
			'output_html' => $output_html,
1064
			'placement' => $row['placement'],
1065
			'colname' => $row['col_name'],
1066
			'value' => $value,
1067
			'show_reg' => $row['show_reg'],
1068
		);
1069
		$context['custom_fields_required'] = $context['custom_fields_required'] || $row['show_reg'] == 2;
1070
	}
1071
	$smcFunc['db_free_result']($request);
1072
1073
	call_integration_hook('integrate_load_custom_profile_fields', array($memID, $area));
1074
}
1075
1076
?>