Failed Conditions
Branch release-2.1 (4e22cf)
by Rick
06:39
created

Subs-Members.php ➔ populateDuplicateMembers()   F

Complexity

Conditions 20
Paths 1055

Size

Total Lines 117
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 63
nc 1055
nop 1
dl 0
loc 117
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file contains some useful functions for members and membergroups.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2017 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Delete one or more members.
21
 * Requires profile_remove_own or profile_remove_any permission for
22
 * respectively removing your own account or any account.
23
 * Non-admins cannot delete admins.
24
 * The function:
25
 *   - changes author of messages, topics and polls to guest authors.
26
 *   - removes all log entries concerning the deleted members, except the
27
 * error logs, ban logs and moderation logs.
28
 *   - removes these members' personal messages (only the inbox), avatars,
29
 * ban entries, theme settings, moderator positions, poll and votes.
30
 *   - updates member statistics afterwards.
31
 *
32
 * @param int|array $users The ID of a user or an array of user IDs
33
 * @param bool $check_not_admin Whether to verify that the users aren't admins
34
 */
35
function deleteMembers($users, $check_not_admin = false)
36
{
37
	global $sourcedir, $modSettings, $user_info, $smcFunc;
38
39
	// Try give us a while to sort this out...
40
	@set_time_limit(600);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
41
42
	// Try to get some more memory.
43
	setMemoryLimit('128M');
44
45
	// If it's not an array, make it so!
46
	if (!is_array($users))
47
		$users = array($users);
48
	else
49
		$users = array_unique($users);
50
51
	// Make sure there's no void user in here.
52
	$users = array_diff($users, array(0));
53
54
	// How many are they deleting?
55
	if (empty($users))
56
		return;
57
	elseif (count($users) == 1)
58
	{
59
		list ($user) = $users;
60
61
		if ($user == $user_info['id'])
62
			isAllowedTo('profile_remove_own');
63
		else
64
			isAllowedTo('profile_remove_any');
65
	}
66
	else
67
	{
68
		foreach ($users as $k => $v)
69
			$users[$k] = (int) $v;
70
71
		// Deleting more than one?  You can't have more than one account...
72
		isAllowedTo('profile_remove_any');
73
	}
74
75
	// Get their names for logging purposes.
76
	$request = $smcFunc['db_query']('', '
77
		SELECT id_member, member_name, CASE WHEN id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0 THEN 1 ELSE 0 END AS is_admin
78
		FROM {db_prefix}members
79
		WHERE id_member IN ({array_int:user_list})
80
		LIMIT {int:limit}',
81
		array(
82
			'user_list' => $users,
83
			'admin_group' => 1,
84
			'limit' => count($users),
85
		)
86
	);
87
	$admins = array();
88
	$user_log_details = array();
89 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
90
	{
91
		if ($row['is_admin'])
92
			$admins[] = $row['id_member'];
93
		$user_log_details[$row['id_member']] = array($row['id_member'], $row['member_name']);
94
	}
95
	$smcFunc['db_free_result']($request);
96
97
	if (empty($user_log_details))
98
		return;
99
100
	// Make sure they aren't trying to delete administrators if they aren't one.  But don't bother checking if it's just themself.
101
	if (!empty($admins) && ($check_not_admin || (!allowedTo('admin_forum') && (count($users) != 1 || $users[0] != $user_info['id']))))
102
	{
103
		$users = array_diff($users, $admins);
104
		foreach ($admins as $id)
105
			unset($user_log_details[$id]);
106
	}
107
108
	// No one left?
109
	if (empty($users))
110
		return;
111
112
	// Log the action - regardless of who is deleting it.
113
	$log_changes = array();
114
	foreach ($user_log_details as $user)
115
	{
116
		$log_changes[] = array(
117
			'action' => 'delete_member',
118
			'log_type' => 'admin',
119
			'extra' => array(
120
				'member' => $user[0],
121
				'name' => $user[1],
122
				'member_acted' => $user_info['name'],
123
			),
124
		);
125
126
		// Remove any cached data if enabled.
127 View Code Duplication
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
128
			cache_put_data('user_settings-' . $user[0], null, 60);
129
	}
130
131
	// Make these peoples' posts guest posts.
132
	$smcFunc['db_query']('', '
133
		UPDATE {db_prefix}messages
134
		SET id_member = {int:guest_id}' . (!empty($modSettings['deleteMembersRemovesEmail']) ? ',
135
		poster_email = {string:blank_email}' : '') . '
136
		WHERE id_member IN ({array_int:users})',
137
		array(
138
			'guest_id' => 0,
139
			'blank_email' => '',
140
			'users' => $users,
141
		)
142
	);
143
	$smcFunc['db_query']('', '
144
		UPDATE {db_prefix}polls
145
		SET id_member = {int:guest_id}
146
		WHERE id_member IN ({array_int:users})',
147
		array(
148
			'guest_id' => 0,
149
			'users' => $users,
150
		)
151
	);
152
153
	// Make these peoples' posts guest first posts and last posts.
154
	$smcFunc['db_query']('', '
155
		UPDATE {db_prefix}topics
156
		SET id_member_started = {int:guest_id}
157
		WHERE id_member_started IN ({array_int:users})',
158
		array(
159
			'guest_id' => 0,
160
			'users' => $users,
161
		)
162
	);
163
	$smcFunc['db_query']('', '
164
		UPDATE {db_prefix}topics
165
		SET id_member_updated = {int:guest_id}
166
		WHERE id_member_updated IN ({array_int:users})',
167
		array(
168
			'guest_id' => 0,
169
			'users' => $users,
170
		)
171
	);
172
173
	$smcFunc['db_query']('', '
174
		UPDATE {db_prefix}log_actions
175
		SET id_member = {int:guest_id}
176
		WHERE id_member IN ({array_int:users})',
177
		array(
178
			'guest_id' => 0,
179
			'users' => $users,
180
		)
181
	);
182
183
	$smcFunc['db_query']('', '
184
		UPDATE {db_prefix}log_banned
185
		SET id_member = {int:guest_id}
186
		WHERE id_member IN ({array_int:users})',
187
		array(
188
			'guest_id' => 0,
189
			'users' => $users,
190
		)
191
	);
192
193
	$smcFunc['db_query']('', '
194
		UPDATE {db_prefix}log_errors
195
		SET id_member = {int:guest_id}
196
		WHERE id_member IN ({array_int:users})',
197
		array(
198
			'guest_id' => 0,
199
			'users' => $users,
200
		)
201
	);
202
203
	// Delete the member.
204
	$smcFunc['db_query']('', '
205
		DELETE FROM {db_prefix}members
206
		WHERE id_member IN ({array_int:users})',
207
		array(
208
			'users' => $users,
209
		)
210
	);
211
212
	// Delete any drafts...
213
	$smcFunc['db_query']('', '
214
		DELETE FROM {db_prefix}user_drafts
215
		WHERE id_member IN ({array_int:users})',
216
		array(
217
			'users' => $users,
218
		)
219
	);
220
221
	// Delete anything they liked.
222
	$smcFunc['db_query']('', '
223
		DELETE FROM {db_prefix}user_likes
224
		WHERE id_member IN ({array_int:users})',
225
		array(
226
			'users' => $users,
227
		)
228
	);
229
230
	// Delete their mentions
231
	$smcFunc['db_query']('', '
232
		DELETE FROM {db_prefix}mentions
233
		WHERE id_member IN ({array_int:members})',
234
		array(
235
			'members' => $users,
236
		)
237
	);
238
239
	// Delete the logs...
240
	$smcFunc['db_query']('', '
241
		DELETE FROM {db_prefix}log_actions
242
		WHERE id_log = {int:log_type}
243
			AND id_member IN ({array_int:users})',
244
		array(
245
			'log_type' => 2,
246
			'users' => $users,
247
		)
248
	);
249
	$smcFunc['db_query']('', '
250
		DELETE FROM {db_prefix}log_boards
251
		WHERE id_member IN ({array_int:users})',
252
		array(
253
			'users' => $users,
254
		)
255
	);
256
	$smcFunc['db_query']('', '
257
		DELETE FROM {db_prefix}log_comments
258
		WHERE id_recipient IN ({array_int:users})
259
			AND comment_type = {string:warntpl}',
260
		array(
261
			'users' => $users,
262
			'warntpl' => 'warntpl',
263
		)
264
	);
265
	$smcFunc['db_query']('', '
266
		DELETE FROM {db_prefix}log_group_requests
267
		WHERE id_member IN ({array_int:users})',
268
		array(
269
			'users' => $users,
270
		)
271
	);
272
	$smcFunc['db_query']('', '
273
		DELETE FROM {db_prefix}log_mark_read
274
		WHERE id_member IN ({array_int:users})',
275
		array(
276
			'users' => $users,
277
		)
278
	);
279
	$smcFunc['db_query']('', '
280
		DELETE FROM {db_prefix}log_notify
281
		WHERE id_member IN ({array_int:users})',
282
		array(
283
			'users' => $users,
284
		)
285
	);
286
	$smcFunc['db_query']('', '
287
		DELETE FROM {db_prefix}log_online
288
		WHERE id_member IN ({array_int:users})',
289
		array(
290
			'users' => $users,
291
		)
292
	);
293
	$smcFunc['db_query']('', '
294
		DELETE FROM {db_prefix}log_subscribed
295
		WHERE id_member IN ({array_int:users})',
296
		array(
297
			'users' => $users,
298
		)
299
	);
300
	$smcFunc['db_query']('', '
301
		DELETE FROM {db_prefix}log_topics
302
		WHERE id_member IN ({array_int:users})',
303
		array(
304
			'users' => $users,
305
		)
306
	);
307
308
	// Make their votes appear as guest votes - at least it keeps the totals right.
309
	// @todo Consider adding back in cookie protection.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
310
	$smcFunc['db_query']('', '
311
		UPDATE {db_prefix}log_polls
312
		SET id_member = {int:guest_id}
313
		WHERE id_member IN ({array_int:users})',
314
		array(
315
			'guest_id' => 0,
316
			'users' => $users,
317
		)
318
	);
319
320
	// Delete personal messages.
321
	require_once($sourcedir . '/PersonalMessage.php');
322
	deleteMessages(null, null, $users);
323
324
	$smcFunc['db_query']('', '
325
		UPDATE {db_prefix}personal_messages
326
		SET id_member_from = {int:guest_id}
327
		WHERE id_member_from IN ({array_int:users})',
328
		array(
329
			'guest_id' => 0,
330
			'users' => $users,
331
		)
332
	);
333
334
	// They no longer exist, so we don't know who it was sent to.
335
	$smcFunc['db_query']('', '
336
		DELETE FROM {db_prefix}pm_recipients
337
		WHERE id_member IN ({array_int:users})',
338
		array(
339
			'users' => $users,
340
		)
341
	);
342
343
	// Delete avatar.
344
	require_once($sourcedir . '/ManageAttachments.php');
345
	removeAttachments(array('id_member' => $users));
346
347
	// It's over, no more moderation for you.
348
	$smcFunc['db_query']('', '
349
		DELETE FROM {db_prefix}moderators
350
		WHERE id_member IN ({array_int:users})',
351
		array(
352
			'users' => $users,
353
		)
354
	);
355
	$smcFunc['db_query']('', '
356
		DELETE FROM {db_prefix}group_moderators
357
		WHERE id_member IN ({array_int:users})',
358
		array(
359
			'users' => $users,
360
		)
361
	);
362
363
	// If you don't exist we can't ban you.
364
	$smcFunc['db_query']('', '
365
		DELETE FROM {db_prefix}ban_items
366
		WHERE id_member IN ({array_int:users})',
367
		array(
368
			'users' => $users,
369
		)
370
	);
371
372
	// Remove individual theme settings.
373
	$smcFunc['db_query']('', '
374
		DELETE FROM {db_prefix}themes
375
		WHERE id_member IN ({array_int:users})',
376
		array(
377
			'users' => $users,
378
		)
379
	);
380
381
	// These users are nobody's buddy nomore.
382
	$request = $smcFunc['db_query']('', '
383
		SELECT id_member, pm_ignore_list, buddy_list
384
		FROM {db_prefix}members
385
		WHERE FIND_IN_SET({raw:pm_ignore_list}, pm_ignore_list) != 0 OR FIND_IN_SET({raw:buddy_list}, buddy_list) != 0',
386
		array(
387
			'pm_ignore_list' => implode(', pm_ignore_list) != 0 OR FIND_IN_SET(', $users),
388
			'buddy_list' => implode(', buddy_list) != 0 OR FIND_IN_SET(', $users),
389
		)
390
	);
391
	while ($row = $smcFunc['db_fetch_assoc']($request))
392
		$smcFunc['db_query']('', '
393
			UPDATE {db_prefix}members
394
			SET
395
				pm_ignore_list = {string:pm_ignore_list},
396
				buddy_list = {string:buddy_list}
397
			WHERE id_member = {int:id_member}',
398
			array(
399
				'id_member' => $row['id_member'],
400
				'pm_ignore_list' => implode(',', array_diff(explode(',', $row['pm_ignore_list']), $users)),
401
				'buddy_list' => implode(',', array_diff(explode(',', $row['buddy_list']), $users)),
402
			)
403
		);
404
	$smcFunc['db_free_result']($request);
405
406
	// Make sure no member's birthday is still sticking in the calendar...
407
	updateSettings(array(
408
		'calendar_updated' => time(),
409
	));
410
411
	// Integration rocks!
412
	call_integration_hook('integrate_delete_members', array($users));
413
414
	updateStats('member');
415
416
	require_once($sourcedir . '/Logging.php');
417
	logActions($log_changes);
418
}
419
420
/**
421
 * Registers a member to the forum.
422
 * Allows two types of interface: 'guest' and 'admin'. The first
423
 * includes hammering protection, the latter can perform the
424
 * registration silently.
425
 * The strings used in the options array are assumed to be escaped.
426
 * Allows to perform several checks on the input, e.g. reserved names.
427
 * The function will adjust member statistics.
428
 * If an error is detected will fatal error on all errors unless return_errors is true.
429
 *
430
 * @param array $regOptions An array of registration options
431
 * @param bool $return_errors Whether to return the errors
432
 * @return int|array The ID of the newly registered user or an array of error info if $return_errors is true
433
 */
434
function registerMember(&$regOptions, $return_errors = false)
435
{
436
	global $scripturl, $txt, $modSettings, $context, $sourcedir;
437
	global $user_info, $smcFunc;
438
439
	loadLanguage('Login');
440
441
	// We'll need some external functions.
442
	require_once($sourcedir . '/Subs-Auth.php');
443
	require_once($sourcedir . '/Subs-Post.php');
444
445
	// Put any errors in here.
446
	$reg_errors = array();
447
448
	// Registration from the admin center, let them sweat a little more.
449
	if ($regOptions['interface'] == 'admin')
450
	{
451
		is_not_guest();
452
		isAllowedTo('moderate_forum');
453
	}
454
	// If you're an admin, you're special ;).
455
	elseif ($regOptions['interface'] == 'guest')
456
	{
457
		// You cannot register twice...
458
		if (empty($user_info['is_guest']))
459
			redirectexit();
460
461
		// Make sure they didn't just register with this session.
462
		if (!empty($_SESSION['just_registered']) && empty($modSettings['disableRegisterCheck']))
463
			fatal_lang_error('register_only_once', false);
464
	}
465
466
	// Spaces and other odd characters are evil...
467
	$regOptions['username'] = trim(preg_replace('~[\t\n\r \x0B\0' . ($context['utf8'] ? '\x{A0}\x{AD}\x{2000}-\x{200F}\x{201F}\x{202F}\x{3000}\x{FEFF}' : '\x00-\x08\x0B\x0C\x0E-\x19\xA0') . ']+~' . ($context['utf8'] ? 'u' : ''), ' ', $regOptions['username']));
468
469
	// @todo Separate the sprintf?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
470
	if (empty($regOptions['email']) || !filter_var($regOptions['email'], FILTER_VALIDATE_EMAIL) || strlen($regOptions['email']) > 255)
471
		$reg_errors[] = array('lang', 'profile_error_bad_email');
472
473
	$username_validation_errors = validateUsername(0, $regOptions['username'], true, !empty($regOptions['check_reserved_name']));
474
	if (!empty($username_validation_errors))
475
		$reg_errors = array_merge($reg_errors, $username_validation_errors);
476
477
	// Generate a validation code if it's supposed to be emailed.
478
	$validation_code = '';
479
	if ($regOptions['require'] == 'activation')
480
		$validation_code = generateValidationCode();
481
482
	// If you haven't put in a password generate one.
483
	if ($regOptions['interface'] == 'admin' && $regOptions['password'] == '')
484
	{
485
		mt_srand(time() + 1277);
486
		$regOptions['password'] = generateValidationCode();
487
		$regOptions['password_check'] = $regOptions['password'];
488
	}
489
	// Does the first password match the second?
490
	elseif ($regOptions['password'] != $regOptions['password_check'])
491
		$reg_errors[] = array('lang', 'passwords_dont_match');
492
493
	// That's kind of easy to guess...
494
	if ($regOptions['password'] == '')
495
	{
496
		$reg_errors[] = array('lang', 'no_password');
497
	}
498
499
	// Now perform hard password validation as required.
500
	if (!empty($regOptions['check_password_strength']) && $regOptions['password'] != '')
501
	{
502
		$passwordError = validatePassword($regOptions['password'], $regOptions['username'], array($regOptions['email']));
503
504
		// Password isn't legal?
505
		if ($passwordError != null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $passwordError of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
506
			$reg_errors[] = array('lang', 'profile_error_password_' . $passwordError);
507
	}
508
509
	// You may not be allowed to register this email.
510
	if (!empty($regOptions['check_email_ban']))
511
		isBannedEmail($regOptions['email'], 'cannot_register', $txt['ban_register_prohibited']);
512
513
	// Check if the email address is in use.
514
	$request = $smcFunc['db_query']('', '
515
		SELECT id_member
516
		FROM {db_prefix}members
517
		WHERE email_address = {string:email_address}
518
			OR email_address = {string:username}
519
		LIMIT 1',
520
		array(
521
			'email_address' => $regOptions['email'],
522
			'username' => $regOptions['username'],
523
		)
524
	);
525
	// @todo Separate the sprintf?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
526
	if ($smcFunc['db_num_rows']($request) != 0)
527
		$reg_errors[] = array('lang', 'email_in_use', false, array($smcFunc['htmlspecialchars']($regOptions['email'])));
528
529
	$smcFunc['db_free_result']($request);
530
531
	// Perhaps someone else wants to check this user.
532
	call_integration_hook('integrate_register_check', array(&$regOptions, &$reg_errors));
533
534
	// If we found any errors we need to do something about it right away!
535
	foreach ($reg_errors as $key => $error)
536
	{
537
		/* Note for each error:
538
			0 = 'lang' if it's an index, 'done' if it's clear text.
539
			1 = The text/index.
540
			2 = Whether to log.
541
			3 = sprintf data if necessary. */
542
		if ($error[0] == 'lang')
543
			loadLanguage('Errors');
544
		$message = $error[0] == 'lang' ? (empty($error[3]) ? $txt[$error[1]] : vsprintf($txt[$error[1]], $error[3])) : $error[1];
545
546
		// What to do, what to do, what to do.
547
		if ($return_errors)
548
		{
549
			if (!empty($error[2]))
550
				log_error($message, $error[2]);
551
			$reg_errors[$key] = $message;
552
		}
553
		else
554
			fatal_error($message, empty($error[2]) ? false : $error[2]);
555
	}
556
557
	// If there's any errors left return them at once!
558
	if (!empty($reg_errors))
559
		return $reg_errors;
560
561
	$reservedVars = array(
562
		'actual_theme_url',
563
		'actual_images_url',
564
		'base_theme_dir',
565
		'base_theme_url',
566
		'default_images_url',
567
		'default_theme_dir',
568
		'default_theme_url',
569
		'default_template',
570
		'images_url',
571
		'number_recent_posts',
572
		'smiley_sets_default',
573
		'theme_dir',
574
		'theme_id',
575
		'theme_layers',
576
		'theme_templates',
577
		'theme_url',
578
	);
579
580
	// Can't change reserved vars.
581
	if (isset($regOptions['theme_vars']) && count(array_intersect(array_keys($regOptions['theme_vars']), $reservedVars)) != 0)
582
		fatal_lang_error('no_theme');
583
584
	// Some of these might be overwritten. (the lower ones that are in the arrays below.)
585
	$regOptions['register_vars'] = array(
586
		'member_name' => $regOptions['username'],
587
		'email_address' => $regOptions['email'],
588
		'passwd' => hash_password($regOptions['username'], $regOptions['password']),
589
		'password_salt' => substr(md5(mt_rand()), 0, 4) ,
590
		'posts' => 0,
591
		'date_registered' => time(),
592
		'member_ip' => $regOptions['interface'] == 'admin' ? '127.0.0.1' : $user_info['ip'],
593
		'member_ip2' => $regOptions['interface'] == 'admin' ? '127.0.0.1' : $_SERVER['BAN_CHECK_IP'],
594
		'validation_code' => $validation_code,
595
		'real_name' => $regOptions['username'],
596
		'personal_text' => $modSettings['default_personal_text'],
597
		'id_theme' => 0,
598
		'id_post_group' => 4,
599
		'lngfile' => '',
600
		'buddy_list' => '',
601
		'pm_ignore_list' => '',
602
		'website_title' => '',
603
		'website_url' => '',
604
		'time_format' => '',
605
		'signature' => '',
606
		'avatar' => '',
607
		'usertitle' => '',
608
		'secret_question' => '',
609
		'secret_answer' => '',
610
		'additional_groups' => '',
611
		'ignore_boards' => '',
612
		'smiley_set' => '',
613
		'timezone' => !empty($regOptions['timezone']) ? $regOptions['timezone'] : 'UTC',
614
	);
615
616
	// Setup the activation status on this new account so it is correct - firstly is it an under age account?
617
	if ($regOptions['require'] == 'coppa')
618
	{
619
		$regOptions['register_vars']['is_activated'] = 5;
620
		// @todo This should be changed.  To what should be it be changed??
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
621
		$regOptions['register_vars']['validation_code'] = '';
622
	}
623
	// Maybe it can be activated right away?
624
	elseif ($regOptions['require'] == 'nothing')
625
		$regOptions['register_vars']['is_activated'] = 1;
626
	// Maybe it must be activated by email?
627
	elseif ($regOptions['require'] == 'activation')
628
		$regOptions['register_vars']['is_activated'] = 0;
629
	// Otherwise it must be awaiting approval!
630
	else
631
		$regOptions['register_vars']['is_activated'] = 3;
632
633
	if (isset($regOptions['memberGroup']))
634
	{
635
		// Make sure the id_group will be valid, if this is an administrator.
636
		$regOptions['register_vars']['id_group'] = $regOptions['memberGroup'] == 1 && !allowedTo('admin_forum') ? 0 : $regOptions['memberGroup'];
637
638
		// Check if this group is assignable.
639
		$unassignableGroups = array(-1, 3);
640
		$request = $smcFunc['db_query']('', '
641
			SELECT id_group
642
			FROM {db_prefix}membergroups
643
			WHERE min_posts != {int:min_posts}' . (allowedTo('admin_forum') ? '' : '
644
				OR group_type = {int:is_protected}'),
645
			array(
646
				'min_posts' => -1,
647
				'is_protected' => 1,
648
			)
649
		);
650
		while ($row = $smcFunc['db_fetch_assoc']($request))
651
			$unassignableGroups[] = $row['id_group'];
652
		$smcFunc['db_free_result']($request);
653
654
		if (in_array($regOptions['register_vars']['id_group'], $unassignableGroups))
655
			$regOptions['register_vars']['id_group'] = 0;
656
	}
657
658
	// Integrate optional member settings to be set.
659
	if (!empty($regOptions['extra_register_vars']))
660
		foreach ($regOptions['extra_register_vars'] as $var => $value)
0 ignored issues
show
Bug introduced by
The expression $regOptions['extra_register_vars'] of type string|array<string,?,{"...","timezone":"string"}> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
661
			$regOptions['register_vars'][$var] = $value;
662
663
	// Integrate optional user theme options to be set.
664
	$theme_vars = array();
665
	if (!empty($regOptions['theme_vars']))
666
		foreach ($regOptions['theme_vars'] as $var => $value)
0 ignored issues
show
Bug introduced by
The expression $regOptions['theme_vars'] of type string|array<string,?,{"...","timezone":"string"}> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
667
			$theme_vars[$var] = $value;
668
669
	// Right, now let's prepare for insertion.
670
	$knownInts = array(
671
		'date_registered', 'posts', 'id_group', 'last_login', 'instant_messages', 'unread_messages',
672
		'new_pm', 'pm_prefs', 'show_online',
673
		'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning',
674
	);
675
	$knownFloats = array(
676
		'time_offset',
677
	);
678
	$knownInets = array(
679
		'member_ip','member_ip2',
680
	);
681
682
	// Call an optional function to validate the users' input.
683
	call_integration_hook('integrate_register', array(&$regOptions, &$theme_vars, &$knownInts, &$knownFloats));
684
685
	$column_names = array();
686
	$values = array();
687
	foreach ($regOptions['register_vars'] as $var => $val)
688
	{
689
		$type = 'string';
690
		if (in_array($var, $knownInts))
691
			$type = 'int';
692
		elseif (in_array($var, $knownFloats))
693
			$type = 'float';
694
		elseif (in_array($var, $knownInets))
695
			$type = 'inet';
696
		elseif ($var == 'birthdate')
697
			$type = 'date';
698
699
		$column_names[$var] = $type;
700
		$values[$var] = $val;
701
	}
702
703
	// Register them into the database.
704
	$memberID = $smcFunc['db_insert']('',
705
		'{db_prefix}members',
706
		$column_names,
707
		$values,
708
		array('id_member'),
709
		1
710
	);
711
712
	// Call an optional function as notification of registration.
713
	call_integration_hook('integrate_post_register', array(&$regOptions, &$theme_vars, &$memberID));
714
715
	// Update the number of members and latest member's info - and pass the name, but remove the 's.
716
	if ($regOptions['register_vars']['is_activated'] == 1)
717
		updateStats('member', $memberID, $regOptions['register_vars']['real_name']);
718
	else
719
		updateStats('member');
720
721
	// Theme variables too?
722
	if (!empty($theme_vars))
723
	{
724
		$inserts = array();
725
		foreach ($theme_vars as $var => $val)
726
			$inserts[] = array($memberID, $var, $val);
727
		$smcFunc['db_insert']('insert',
728
			'{db_prefix}themes',
729
			array('id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
730
			$inserts,
731
			array('id_member', 'variable')
732
		);
733
	}
734
735
	// If it's enabled, increase the registrations for today.
736
	trackStats(array('registers' => '+'));
737
738
	// Administrative registrations are a bit different...
739
	if ($regOptions['interface'] == 'admin')
740
	{
741
		if ($regOptions['require'] == 'activation')
742
			$email_message = 'admin_register_activate';
743
		elseif (!empty($regOptions['send_welcome_email']))
744
			$email_message = 'admin_register_immediate';
745
746
		if (isset($email_message))
747
		{
748
			$replacements = array(
749
				'REALNAME' => $regOptions['register_vars']['real_name'],
750
				'USERNAME' => $regOptions['username'],
751
				'PASSWORD' => $regOptions['password'],
752
				'FORGOTPASSWORDLINK' => $scripturl . '?action=reminder',
753
				'ACTIVATIONLINK' => $scripturl . '?action=activate;u=' . $memberID . ';code=' . $validation_code,
754
				'ACTIVATIONLINKWITHOUTCODE' => $scripturl . '?action=activate;u=' . $memberID,
755
				'ACTIVATIONCODE' => $validation_code,
756
			);
757
758
			$emaildata = loadEmailTemplate($email_message, $replacements);
759
760
			sendmail($regOptions['email'], $emaildata['subject'], $emaildata['body'], null, $email_message . $memberID, $emaildata['is_html'], 0);
0 ignored issues
show
Bug introduced by
It seems like $regOptions['email'] can also be of type null or string; however, sendmail() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
761
		}
762
763
		// All admins are finished here.
764
		return $memberID;
765
	}
766
767
	// Can post straight away - welcome them to your fantastic community...
768
	if ($regOptions['require'] == 'nothing')
769
	{
770 View Code Duplication
		if (!empty($regOptions['send_welcome_email']))
771
		{
772
			$replacements = array(
773
				'REALNAME' => $regOptions['register_vars']['real_name'],
774
				'USERNAME' => $regOptions['username'],
775
				'PASSWORD' => $regOptions['password'],
776
				'FORGOTPASSWORDLINK' => $scripturl . '?action=reminder',
777
			);
778
			$emaildata = loadEmailTemplate('register_immediate', $replacements);
779
			sendmail($regOptions['email'], $emaildata['subject'], $emaildata['body'], null, 'register', $emaildata['is_html'], 0);
780
		}
781
782
		// Send admin their notification.
783
		adminNotify('standard', $memberID, $regOptions['username']);
784
	}
785
	// Need to activate their account - or fall under COPPA.
786
	elseif ($regOptions['require'] == 'activation' || $regOptions['require'] == 'coppa')
787
	{
788
		$replacements = array(
789
			'REALNAME' => $regOptions['register_vars']['real_name'],
790
			'USERNAME' => $regOptions['username'],
791
			'PASSWORD' => $regOptions['password'],
792
			'FORGOTPASSWORDLINK' => $scripturl . '?action=reminder',
793
		);
794
795
		if ($regOptions['require'] == 'activation')
796
			$replacements += array(
797
				'ACTIVATIONLINK' => $scripturl . '?action=activate;u=' . $memberID . ';code=' . $validation_code,
798
				'ACTIVATIONLINKWITHOUTCODE' => $scripturl . '?action=activate;u=' . $memberID,
799
				'ACTIVATIONCODE' => $validation_code,
800
			);
801
		else
802
			$replacements += array(
803
				'COPPALINK' => $scripturl . '?action=coppa;u=' . $memberID,
804
			);
805
806
		$emaildata = loadEmailTemplate('register_' . ($regOptions['require'] == 'activation' ? 'activate' : 'coppa'), $replacements);
807
808
		sendmail($regOptions['email'], $emaildata['subject'], $emaildata['body'], null, 'reg_' . $regOptions['require'] . $memberID, $emaildata['is_html'], 0);
809
	}
810
	// Must be awaiting approval.
811 View Code Duplication
	else
812
	{
813
		$replacements = array(
814
			'REALNAME' => $regOptions['register_vars']['real_name'],
815
			'USERNAME' => $regOptions['username'],
816
			'PASSWORD' => $regOptions['password'],
817
			'FORGOTPASSWORDLINK' => $scripturl . '?action=reminder',
818
		);
819
820
		$emaildata = loadEmailTemplate('register_pending', $replacements);
821
822
		sendmail($regOptions['email'], $emaildata['subject'], $emaildata['body'], null, 'reg_pending', $emaildata['is_html'], 0);
823
824
		// Admin gets informed here...
825
		adminNotify('approval', $memberID, $regOptions['username']);
826
	}
827
828
	// Okay, they're for sure registered... make sure the session is aware of this for security. (Just married :P!)
829
	$_SESSION['just_registered'] = 1;
830
831
	// If they are for sure registered, let other people to know about it
832
	call_integration_hook('integrate_register_after', array($regOptions, $memberID));
833
834
	return $memberID;
835
}
836
837
838
/**
839
 * Check if a name is in the reserved words list.
840
 * (name, current member id, name/username?.)
841
 * - checks if name is a reserved name or username.
842
 * - if is_name is false, the name is assumed to be a username.
843
 * - the id_member variable is used to ignore duplicate matches with the
844
 * current member.
845
 *
846
 * @param string $name The name to check
847
 * @param int $current_ID_MEMBER The ID of the current member (to avoid false positives with the current member)
848
 * @param bool $is_name Whether we're checking against reserved names or just usernames
849
 * @param bool $fatal Whether to die with a fatal error if the name is reserved
850
 * @return bool|void False if name is not reserved, otherwise true if $fatal is false or dies with a fatal_lang_error if $fatal is true
851
 */
852
function isReservedName($name, $current_ID_MEMBER = 0, $is_name = true, $fatal = true)
853
{
854
	global $modSettings, $smcFunc;
855
856
	$name = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'replaceEntities__callback', $name);
857
	$checkName = $smcFunc['strtolower']($name);
858
859
	// Administrators are never restricted ;).
860
	if (!allowedTo('moderate_forum') && ((!empty($modSettings['reserveName']) && $is_name) || !empty($modSettings['reserveUser']) && !$is_name))
861
	{
862
		$reservedNames = explode("\n", $modSettings['reserveNames']);
863
		// Case sensitive check?
864
		$checkMe = empty($modSettings['reserveCase']) ? $checkName : $name;
865
866
		// Check each name in the list...
867
		foreach ($reservedNames as $reserved)
868
		{
869
			if ($reserved == '')
870
				continue;
871
872
			// The admin might've used entities too, level the playing field.
873
			$reservedCheck = preg_replace('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'replaceEntities__callback', $reserved);
874
875
			// Case sensitive name?
876
			if (empty($modSettings['reserveCase']))
877
				$reservedCheck = $smcFunc['strtolower']($reservedCheck);
878
879
			// If it's not just entire word, check for it in there somewhere...
880
			if ($checkMe == $reservedCheck || ($smcFunc['strpos']($checkMe, $reservedCheck) !== false && empty($modSettings['reserveWord'])))
881
				if ($fatal)
882
					fatal_lang_error('username_reserved', 'password', array($reserved));
883
				else
884
					return true;
885
		}
886
887
		$censor_name = $name;
888
		if (censorText($censor_name) != $name)
889
			if ($fatal)
890
				fatal_lang_error('name_censored', 'password', array($name));
891
			else
892
				return true;
893
	}
894
895
	// Characters we just shouldn't allow, regardless.
896
	foreach (array('*') as $char)
897
		if (strpos($checkName, $char) !== false)
898
			if ($fatal)
899
				fatal_lang_error('username_reserved', 'password', array($char));
900
			else
901
				return true;
902
903
	// Get rid of any SQL parts of the reserved name...
904
	$checkName = strtr($name, array('_' => '\\_', '%' => '\\%'));
905
906
	//when we got no wildcard we can use equal -> fast
907
	$operator = (strpos($checkName, '%') || strpos($checkName, '_') ? 'LIKE' : '=' );
908
909
	// Make sure they don't want someone else's name.
910
	$request = $smcFunc['db_query']('', '
911
		SELECT id_member
912
		FROM {db_prefix}members
913
		WHERE ' . (empty($current_ID_MEMBER) ? '' : 'id_member != {int:current_member}
914
			AND ') . '({raw:real_name} {raw:operator} LOWER({string:check_name}) OR {raw:member_name} {raw:operator} LOWER({string:check_name}))
915
		LIMIT 1',
916
		array(
917
			'real_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name',
918
			'member_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(member_name)' : 'member_name',
919
			'current_member' => $current_ID_MEMBER,
920
			'check_name' => $checkName,
921
			'operator' => $operator,
922
		)
923
	);
924 View Code Duplication
	if ($smcFunc['db_num_rows']($request) > 0)
925
	{
926
		$smcFunc['db_free_result']($request);
927
		return true;
928
	}
929
930
	// Does name case insensitive match a member group name?
931
	$request = $smcFunc['db_query']('', '
932
		SELECT id_group
933
		FROM {db_prefix}membergroups
934
		WHERE {raw:group_name} LIKE {string:check_name}
935
		LIMIT 1',
936
		array(
937
			'group_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(group_name)' : 'group_name',
938
			'check_name' => $checkName,
939
		)
940
	);
941 View Code Duplication
	if ($smcFunc['db_num_rows']($request) > 0)
942
	{
943
		$smcFunc['db_free_result']($request);
944
		return true;
945
	}
946
947
	// Okay, they passed.
948
	return false;
949
}
950
951
// Get a list of groups that have a given permission (on a given board).
952
/**
953
 * Retrieves a list of membergroups that are allowed to do the given
954
 * permission. (on the given board)
955
 * If board_id is not null, a board permission is assumed.
956
 * The function takes different permission settings into account.
957
 *
958
 * @param string $permission The permission to check
959
 * @param int $board_id = null If set, checks permissions for the specified board
0 ignored issues
show
Documentation introduced by
Should the type for parameter $board_id not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
960
 * @return array An array containing two arrays - 'allowed', which has which groups are allowed to do it and 'denied' which has the groups that are denied
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
961
 */
962
function groupsAllowedTo($permission, $board_id = null)
963
{
964
	global $board_info, $smcFunc;
965
966
	// Admins are allowed to do anything.
967
	$member_groups = array(
968
		'allowed' => array(1),
969
		'denied' => array(),
970
	);
971
972
	// Assume we're dealing with regular permissions (like profile_view).
973
	if ($board_id === null)
974
	{
975
		$request = $smcFunc['db_query']('', '
976
			SELECT id_group, add_deny
977
			FROM {db_prefix}permissions
978
			WHERE permission = {string:permission}',
979
			array(
980
				'permission' => $permission,
981
			)
982
		);
983 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
984
			$member_groups[$row['add_deny'] === '1' ? 'allowed' : 'denied'][] = $row['id_group'];
985
		$smcFunc['db_free_result']($request);
986
	}
987
988
	// Otherwise it's time to look at the board.
989
	else
990
	{
991
		// First get the profile of the given board.
992
		if (isset($board_info['id']) && $board_info['id'] == $board_id)
993
			$profile_id = $board_info['profile'];
994 View Code Duplication
		elseif ($board_id !== 0)
995
		{
996
			$request = $smcFunc['db_query']('', '
997
				SELECT id_profile
998
				FROM {db_prefix}boards
999
				WHERE id_board = {int:id_board}
1000
				LIMIT 1',
1001
				array(
1002
					'id_board' => $board_id,
1003
				)
1004
			);
1005
			if ($smcFunc['db_num_rows']($request) == 0)
1006
				fatal_lang_error('no_board');
1007
			list ($profile_id) = $smcFunc['db_fetch_row']($request);
1008
			$smcFunc['db_free_result']($request);
1009
		}
1010
		else
1011
			$profile_id = 1;
1012
1013
		$request = $smcFunc['db_query']('', '
1014
			SELECT bp.id_group, bp.add_deny
1015
			FROM {db_prefix}board_permissions AS bp
1016
			WHERE bp.permission = {string:permission}
1017
				AND bp.id_profile = {int:profile_id}',
1018
			array(
1019
				'profile_id' => $profile_id,
1020
				'permission' => $permission,
1021
			)
1022
		);
1023 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
1024
			$member_groups[$row['add_deny'] === '1' ? 'allowed' : 'denied'][] = $row['id_group'];
1025
		$smcFunc['db_free_result']($request);
1026
1027
		$moderator_groups = array();
1028
1029
		// "Inherit" any moderator permissions as needed
1030 View Code Duplication
		if (isset($board_info['moderator_groups']))
1031
		{
1032
			$moderator_groups = array_keys($board_info['moderator_groups']);
1033
		}
1034
		elseif ($board_id !== 0)
1035
		{
1036
			// Get the groups that can moderate this board
1037
			$request = $smcFunc['db_query']('', '
1038
				SELECT id_group
1039
				FROM {db_prefix}moderator_groups
1040
				WHERE id_board = {int:board_id}',
1041
				array(
1042
					'board_id' => $board_id,
1043
				)
1044
			);
1045
1046
			while ($row = $smcFunc['db_fetch_assoc']($request))
1047
			{
1048
				$moderator_groups[] = $row['id_group'];
1049
			}
1050
1051
			$smcFunc['db_free_result']($request);
1052
		}
1053
1054
		// "Inherit" any additional permissions from the "Moderators" group
1055
		foreach ($moderator_groups as $mod_group)
1056
		{
1057
			// If they're not specifically allowed, but the moderator group is, then allow it
1058 View Code Duplication
			if (in_array(3, $member_groups['allowed']) && !in_array($mod_group, $member_groups['allowed']))
1059
			{
1060
				$member_groups['allowed'][] = $mod_group;
1061
			}
1062
1063
			// They're not denied, but the moderator group is, so deny it
1064 View Code Duplication
			if (in_array(3, $member_groups['denied']) && !in_array($mod_group, $member_groups['denied']))
1065
			{
1066
				$member_groups['denied'][] = $mod_group;
1067
			}
1068
		}
1069
	}
1070
1071
	// Denied is never allowed.
1072
	$member_groups['allowed'] = array_diff($member_groups['allowed'], $member_groups['denied']);
1073
1074
	return $member_groups;
1075
}
1076
1077
/**
1078
 * Retrieves a list of members that have a given permission
1079
 * (on a given board).
1080
 * If board_id is not null, a board permission is assumed.
1081
 * Takes different permission settings into account.
1082
 * Takes possible moderators (on board 'board_id') into account.
1083
 *
1084
 * @param string $permission The permission to check
1085
 * @param int $board_id If set, checks permission for that specific board
0 ignored issues
show
Documentation introduced by
Should the type for parameter $board_id not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1086
 * @return array An array containing the IDs of the members having that permission
1087
 */
1088
function membersAllowedTo($permission, $board_id = null)
1089
{
1090
	global $smcFunc;
1091
1092
	$member_groups = groupsAllowedTo($permission, $board_id);
1093
1094
	$all_groups = array_merge($member_groups['allowed'], $member_groups['denied']);
1095
1096
	$include_moderators = in_array(3, $member_groups['allowed']) && $board_id !== null;
1097
	$member_groups['allowed'] = array_diff($member_groups['allowed'], array(3));
1098
1099
	$exclude_moderators = in_array(3, $member_groups['denied']) && $board_id !== null;
1100
	$member_groups['denied'] = array_diff($member_groups['denied'], array(3));
1101
1102
	$request = $smcFunc['db_query']('', '
1103
		SELECT mem.id_member
1104
		FROM {db_prefix}members AS mem' . ($include_moderators || $exclude_moderators ? '
1105
			LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_member = mem.id_member AND mods.id_board = {int:board_id})
1106
			LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_group IN ({array_int:all_member_groups}))' : '') . '
1107
		WHERE (' . ($include_moderators ? 'mods.id_member IS NOT NULL OR modgs.id_group IS NOT NULL OR ' : '') . 'mem.id_group IN ({array_int:member_groups_allowed}) OR FIND_IN_SET({raw:member_group_allowed_implode}, mem.additional_groups) != 0 OR mem.id_post_group IN ({array_int:member_groups_allowed}))' . (empty($member_groups['denied']) ? '' : '
1108
			AND NOT (' . ($exclude_moderators ? 'mods.id_member IS NOT NULL OR modgs.id_group IS NOT NULL OR ' : '') . 'mem.id_group IN ({array_int:member_groups_denied}) OR FIND_IN_SET({raw:member_group_denied_implode}, mem.additional_groups) != 0 OR mem.id_post_group IN ({array_int:member_groups_denied}))'),
1109
		array(
1110
			'member_groups_allowed' => $member_groups['allowed'],
1111
			'member_groups_denied' => $member_groups['denied'],
1112
			'all_member_groups' => $all_groups,
1113
			'board_id' => $board_id,
1114
			'member_group_allowed_implode' => implode(', mem.additional_groups) != 0 OR FIND_IN_SET(', $member_groups['allowed']),
1115
			'member_group_denied_implode' => implode(', mem.additional_groups) != 0 OR FIND_IN_SET(', $member_groups['denied']),
1116
		)
1117
	);
1118
	$members = array();
1119
	while ($row = $smcFunc['db_fetch_assoc']($request))
1120
		$members[] = $row['id_member'];
1121
	$smcFunc['db_free_result']($request);
1122
1123
	return $members;
1124
}
1125
1126
/**
1127
 * This function is used to reassociate members with relevant posts.
1128
 * Reattribute guest posts to a specified member.
1129
 * Does not check for any permissions.
1130
 * If add_to_post_count is set, the member's post count is increased.
1131
 *
1132
 * @param int $memID The ID of the original poster
1133
 * @param bool|string $email If set, should be the email of the poster
1134
 * @param bool|string $membername If set, the membername of the poster
1135
 * @param bool $post_count Whether to adjust post counts
1136
 * @return array An array containing the number of messages, topics and reports updated
1137
 */
1138
function reattributePosts($memID, $email = false, $membername = false, $post_count = false)
1139
{
1140
	global $smcFunc, $modSettings;
1141
1142
	$updated = array(
1143
		'messages' => 0,
1144
		'topics' => 0,
1145
		'reports' => 0,
1146
	);
1147
1148
	// Firstly, if email and username aren't passed find out the members email address and name.
1149
	if ($email === false && $membername === false)
1150
	{
1151
		$request = $smcFunc['db_query']('', '
1152
			SELECT email_address, member_name
1153
			FROM {db_prefix}members
1154
			WHERE id_member = {int:memID}
1155
			LIMIT 1',
1156
			array(
1157
				'memID' => $memID,
1158
			)
1159
		);
1160
		list ($email, $membername) = $smcFunc['db_fetch_row']($request);
1161
		$smcFunc['db_free_result']($request);
1162
	}
1163
1164
	// If they want the post count restored then we need to do some research.
1165
	if ($post_count)
1166
	{
1167
		$recycle_board = !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) ? (int) $modSettings['recycle_board'] : 0;
1168
		$request = $smcFunc['db_query']('', '
1169
			SELECT COUNT(*)
1170
			FROM {db_prefix}messages AS m
1171
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND b.count_posts = {int:count_posts})
1172
			WHERE m.id_member = {int:guest_id}
1173
				AND m.approved = {int:is_approved}' . (!empty($recycle_board) ? '
1174
				AND m.id_board != {int:recycled_board}' : '') . (empty($email) ? '' : '
1175
				AND m.poster_email = {string:email_address}') . (empty($membername) ? '' : '
1176
				AND m.poster_name = {string:member_name}'),
1177
			array(
1178
				'count_posts' => 0,
1179
				'guest_id' => 0,
1180
				'email_address' => $email,
1181
				'member_name' => $membername,
1182
				'is_approved' => 1,
1183
				'recycled_board' => $recycle_board,
1184
			)
1185
		);
1186
		list ($messageCount) = $smcFunc['db_fetch_row']($request);
1187
		$smcFunc['db_free_result']($request);
1188
1189
		updateMemberData($memID, array('posts' => 'posts + ' . $messageCount));
1190
	}
1191
1192
	$query_parts = array();
1193
	if (!empty($email))
1194
		$query_parts[] = 'poster_email = {string:email_address}';
1195
	if (!empty($membername))
1196
		$query_parts[] = 'poster_name = {string:member_name}';
1197
	$query = implode(' AND ', $query_parts);
1198
1199
	// Finally, update the posts themselves!
1200
	$smcFunc['db_query']('', '
1201
		UPDATE {db_prefix}messages
1202
		SET id_member = {int:memID}
1203
		WHERE ' . $query,
1204
		array(
1205
			'memID' => $memID,
1206
			'email_address' => $email,
1207
			'member_name' => $membername,
1208
		)
1209
	);
1210
	$updated['messages'] = $smcFunc['db_affected_rows']();
1211
1212
	// Did we update any messages?
1213
	if ($updated['messages'] > 0)
1214
	{
1215
		// First, check for updated topics.
1216
		$smcFunc['db_query']('', '
1217
			UPDATE {db_prefix}topics as t, {db_prefix}messages as m
1218
			SET t.id_member_started = {int:memID}
1219
			WHERE m.id_member = {int:memID}
1220
				AND t.id_first_msg = m.id_msg',
1221
			array(
1222
				'memID' => $memID,
1223
			)
1224
		);
1225
		$updated['topics'] = $smcFunc['db_affected_rows']();
1226
1227
		// Second, check for updated reports.
1228
		$smcFunc['db_query']('', '
1229
			UPDATE {db_prefix}log_reported AS lr, {db_prefix}messages AS m
1230
			SET lr.id_member = {int:memID}
1231
			WHERE lr.id_msg = m.id_msg
1232
				AND m.id_member = {int:memID}',
1233
			array(
1234
				'memID' => $memID,
1235
			)
1236
		);
1237
		$updated['reports'] = $smcFunc['db_affected_rows']();
1238
	}
1239
1240
	// Allow mods with their own post tables to reattribute posts as well :)
1241
 	call_integration_hook('integrate_reattribute_posts', array($memID, $email, $membername, $post_count, &$updated));
1242
1243
	return $updated;
1244
}
1245
1246
/**
1247
 * This simple function adds/removes the passed user from the current users buddy list.
1248
 * Requires profile_identity_own permission.
1249
 * Called by ?action=buddy;u=x;session_id=y.
1250
 * Redirects to ?action=profile;u=x.
1251
 */
1252
function BuddyListToggle()
1253
{
1254
	global $user_info, $smcFunc;
1255
1256
	checkSession('get');
1257
1258
	isAllowedTo('profile_identity_own');
1259
	is_not_guest();
1260
1261
	$userReceiver = (int) !empty($_REQUEST['u']) ? $_REQUEST['u'] : 0;
1262
1263
	if (empty($userReceiver))
1264
		fatal_lang_error('no_access', false);
1265
1266
	// Remove if it's already there...
1267
	if (in_array($userReceiver, $user_info['buddies']))
1268
		$user_info['buddies'] = array_diff($user_info['buddies'], array($userReceiver));
1269
1270
	// ...or add if it's not and if it's not you.
1271
	elseif ($user_info['id'] != $userReceiver)
1272
	{
1273
		$user_info['buddies'][] = $userReceiver;
1274
1275
		// And add a nice alert. Don't abuse though!
1276
		if ((cache_get_data('Buddy-sent-'. $user_info['id'] .'-'. $userReceiver, 86400)) == null)
1277
		{
1278
			$smcFunc['db_insert']('insert',
1279
				'{db_prefix}background_tasks',
1280
				array('task_file' => 'string', 'task_class' => 'string', 'task_data' => 'string', 'claimed_time' => 'int'),
1281
				array('$sourcedir/tasks/Buddy-Notify.php', 'Buddy_Notify_Background', $smcFunc['json_encode'](array(
1282
					'receiver_id' => $userReceiver,
1283
					'id_member' => $user_info['id'],
1284
					'member_name' => $user_info['username'],
1285
					'time' => time(),
1286
				)), 0),
1287
				array('id_task')
1288
			);
1289
1290
			// Store this in a cache entry to avoid creating multiple alerts. Give it a long life cycle.
1291
			cache_put_data('Buddy-sent-'. $user_info['id'] .'-'. $userReceiver, '1', 86400);
1292
		}
1293
	}
1294
1295
	// Update the settings.
1296
	updateMemberData($user_info['id'], array('buddy_list' => implode(',', $user_info['buddies'])));
1297
1298
	// Redirect back to the profile
1299
	redirectexit('action=profile;u=' . $userReceiver);
1300
}
1301
1302
/**
1303
 * Callback for createList().
1304
 *
1305
 * @param int $start Which item to start with (for pagination purposes)
1306
 * @param int $items_per_page How many items to show per page
1307
 * @param string $sort An SQL query indicating how to sort the results
1308
 * @param string $where An SQL query used to filter the results
1309
 * @param array $where_params An array of parameters for $where
1310
 * @param bool $get_duplicates Whether to get duplicates (used for the admin member list)
1311
 * @return array An array of information for displaying the list of members
1312
 */
1313
function list_getMembers($start, $items_per_page, $sort, $where, $where_params = array(), $get_duplicates = false)
1314
{
1315
	global $smcFunc;
1316
1317
	$request = $smcFunc['db_query']('', '
1318
		SELECT
1319
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.member_ip, mem.member_ip2, mem.last_login,
1320
			mem.posts, mem.is_activated, mem.date_registered, mem.id_group, mem.additional_groups, mg.group_name
1321
		FROM {db_prefix}members AS mem
1322
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)
1323
		WHERE ' . ($where == '1' ? '1=1' : $where) . '
1324
		ORDER BY {raw:sort}
1325
		LIMIT {int:start}, {int:per_page}',
1326
		array_merge($where_params, array(
1327
			'sort' => $sort,
1328
			'start' => $start,
1329
			'per_page' => $items_per_page,
1330
		))
1331
	);
1332
1333
	$members = array();
1334 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
1335
	{
1336
		$row['member_ip'] = inet_dtop($row['member_ip']);
1337
		$row['member_ip2'] = inet_dtop($row['member_ip2']);
1338
		$members[] = $row;
1339
	}
1340
	$smcFunc['db_free_result']($request);
1341
1342
	// If we want duplicates pass the members array off.
1343
	if ($get_duplicates)
1344
		populateDuplicateMembers($members);
1345
1346
	return $members;
1347
}
1348
1349
/**
1350
 * Callback for createList().
1351
 *
1352
 * @param string $where An SQL query to filter the results
1353
 * @param array $where_params An array of parameters for $where
1354
 * @return int The number of members matching the given situation
1355
 */
1356
function list_getNumMembers($where, $where_params = array())
1357
{
1358
	global $smcFunc, $modSettings;
1359
1360
	// We know how many members there are in total.
1361
	if (empty($where) || $where == '1=1')
1362
		$num_members = $modSettings['totalMembers'];
1363
1364
	// The database knows the amount when there are extra conditions.
1365 View Code Duplication
	else
1366
	{
1367
		$request = $smcFunc['db_query']('', '
1368
			SELECT COUNT(*)
1369
			FROM {db_prefix}members AS mem
1370
			WHERE ' . $where,
1371
			array_merge($where_params, array(
1372
			))
1373
		);
1374
		list ($num_members) = $smcFunc['db_fetch_row']($request);
1375
		$smcFunc['db_free_result']($request);
1376
	}
1377
1378
	return $num_members;
1379
}
1380
1381
/**
1382
 * Find potential duplicate registration members based on the same IP address
1383
 *
1384
 * @param array $members An array of members
1385
 */
1386
function populateDuplicateMembers(&$members)
1387
{
1388
	global $smcFunc;
1389
1390
	// This will hold all the ip addresses.
1391
	$ips = array();
1392
	foreach ($members as $key => $member)
1393
	{
1394
		// Create the duplicate_members element.
1395
		$members[$key]['duplicate_members'] = array();
1396
1397
		// Store the IPs.
1398
		if (!empty($member['member_ip']))
1399
			$ips[] = $member['member_ip'];
1400
		if (!empty($member['member_ip2']))
1401
			$ips[] = $member['member_ip2'];
1402
	}
1403
1404
	$ips = array_unique($ips);
1405
1406
	if (empty($ips))
1407
		return false;
1408
1409
	// Fetch all members with this IP address, we'll filter out the current ones in a sec.
1410
	$request = $smcFunc['db_query']('', '
1411
		SELECT
1412
			id_member, member_name, email_address, member_ip, member_ip2, is_activated
1413
		FROM {db_prefix}members
1414
		WHERE member_ip IN ({array_inet:ips})
1415
			OR member_ip2 IN ({array_inet:ips})',
1416
		array(
1417
			'ips' => $ips,
1418
		)
1419
	);
1420
	$duplicate_members = array();
1421
	$duplicate_ids = array();
1422
	while ($row = $smcFunc['db_fetch_assoc']($request))
1423
	{
1424
		//$duplicate_ids[] = $row['id_member'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
73% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1425
		$row['member_ip'] = inet_dtop($row['member_ip']);
1426
		$row['member_ip2'] = inet_dtop($row['member_ip2']);
1427
1428
		$member_context = array(
1429
			'id' => $row['id_member'],
1430
			'name' => $row['member_name'],
1431
			'email' => $row['email_address'],
1432
			'is_banned' => $row['is_activated'] > 10,
1433
			'ip' => $row['member_ip'],
1434
			'ip2' => $row['member_ip2'],
1435
		);
1436
1437
		if (in_array($row['member_ip'], $ips))
1438
			$duplicate_members[$row['member_ip']][] = $member_context;
1439
		if ($row['member_ip'] != $row['member_ip2'] && in_array($row['member_ip2'], $ips))
1440
			$duplicate_members[$row['member_ip2']][] = $member_context;
1441
	}
1442
	$smcFunc['db_free_result']($request);
1443
1444
	// Also try to get a list of messages using these ips.
1445
	$request = $smcFunc['db_query']('', '
1446
		SELECT
1447
			m.poster_ip, mem.id_member, mem.member_name, mem.email_address, mem.is_activated
1448
		FROM {db_prefix}messages AS m
1449
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
1450
		WHERE m.id_member != 0
1451
			' . (!empty($duplicate_ids) ? 'AND m.id_member NOT IN ({array_int:duplicate_ids})' : '') . '
1452
			AND m.poster_ip IN ({array_inet:ips})',
1453
		array(
1454
			'duplicate_ids' => $duplicate_ids,
1455
			'ips' => $ips,
1456
		)
1457
	);
1458
1459
	$had_ips = array();
1460
	while ($row = $smcFunc['db_fetch_assoc']($request))
1461
	{
1462
		$row['poster_ip'] = inet_dtop($row['poster_ip']);
1463
1464
		// Don't collect lots of the same.
1465
		if (isset($had_ips[$row['poster_ip']]) && in_array($row['id_member'], $had_ips[$row['poster_ip']]))
1466
			continue;
1467
		$had_ips[$row['poster_ip']][] = $row['id_member'];
1468
1469
		$duplicate_members[$row['poster_ip']][] = array(
1470
			'id' => $row['id_member'],
1471
			'name' => $row['member_name'],
1472
			'email' => $row['email_address'],
1473
			'is_banned' => $row['is_activated'] > 10,
1474
			'ip' => $row['poster_ip'],
1475
			'ip2' => $row['poster_ip'],
1476
		);
1477
	}
1478
	$smcFunc['db_free_result']($request);
1479
1480
	// Now we have all the duplicate members, stick them with their respective member in the list.
1481
	if (!empty($duplicate_members))
1482
		foreach ($members as $key => $member)
1483
		{
1484
			if (isset($duplicate_members[$member['member_ip']]))
1485
				$members[$key]['duplicate_members'] = $duplicate_members[$member['member_ip']];
1486
			if ($member['member_ip'] != $member['member_ip2'] && isset($duplicate_members[$member['member_ip2']]))
1487
				$members[$key]['duplicate_members'] = array_merge($member['duplicate_members'], $duplicate_members[$member['member_ip2']]);
1488
1489
			// Check we don't have lots of the same member.
1490
			$member_track = array($member['id_member']);
1491
			foreach ($members[$key]['duplicate_members'] as $duplicate_id_member => $duplicate_member)
1492
			{
1493
				if (in_array($duplicate_member['id'], $member_track))
1494
				{
1495
					unset($members[$key]['duplicate_members'][$duplicate_id_member]);
1496
					continue;
1497
				}
1498
1499
				$member_track[] = $duplicate_member['id'];
1500
			}
1501
		}
1502
}
1503
1504
/**
1505
 * Generate a random validation code.
1506
 * @todo Err. Whatcha doin' here.
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
1507
 *
1508
 * @return string A random validation code
1509
 */
1510
function generateValidationCode()
1511
{
1512
	global $smcFunc, $modSettings;
1513
1514
	$request = $smcFunc['db_query']('get_random_number', '
1515
		SELECT RAND()',
1516
		array(
1517
		)
1518
	);
1519
1520
	list ($dbRand) = $smcFunc['db_fetch_row']($request);
1521
	$smcFunc['db_free_result']($request);
1522
1523
	return substr(preg_replace('/\W/', '', sha1(microtime() . mt_rand() . $dbRand . $modSettings['rand_seed'])), 0, 10);
1524
}
1525
1526
?>