Completed
Pull Request — development (#2907)
by Martyn
13:27
created

Members.subs.php ➔ deleteMembers()   D

Complexity

Conditions 18
Paths 170

Size

Total Lines 412
Code Lines 179

Duplication

Lines 7
Ratio 1.7 %

Code Coverage

Tests 1
CRAP Score 337.5493

Importance

Changes 0
Metric Value
cc 18
eloc 179
nc 170
nop 2
dl 7
loc 412
ccs 1
cts 217
cp 0.0046
crap 337.5493
rs 4.5072
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
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * This file contains code covered by:
11
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
12
 * license:  	BSD, See included LICENSE.TXT for terms and conditions.
13
 *
14
 * @version 1.1 Release Candidate 1
15
 *
16
 */
17
18
/**
19
 * Delete one or more members.
20
 *
21
 * What it does:
22
 *
23
 * - Requires profile_remove_own or profile_remove_any permission for
24
 * respectively removing your own account or any account.
25
 * - Non-admins cannot delete admins.
26
 *
27
 * What id does
28
 * - Changes author of messages, topics and polls to guest authors.
29
 * - Removes all log entries concerning the deleted members, except the
30
 * error logs, ban logs and moderation logs.
31
 * - Removes these members' personal messages (only the inbox)
32
 * - Removes avatars, ban entries, theme settings, moderator positions, poll votes,
33
 * likes, mentions, notifications
34
 * - Removes custom field data associated with them
35
 * - Updates member statistics afterwards.
36
 *
37
 * @package Members
38
 * @param int[]|int $users
39
 * @param bool $check_not_admin = false
40
 * @throws Elk_Exception
41
 */
42
function deleteMembers($users, $check_not_admin = false)
43
{
44
	global $modSettings, $user_info;
45
46
	$db = database();
47
48
	// Try give us a while to sort this out...
49
	detectServer()->setTimeLimit(600);
50
51
	// Try to get some more memory.
52
	detectServer()->setMemoryLimit('128M');
53
54
	// If it's not an array, make it so!
55
	if (!is_array($users))
56
		$users = array($users);
57
	else
58
		$users = array_unique($users);
59
60
	// Make sure there's no void user in here.
61
	$users = array_diff($users, array(0));
62
63
	// How many are they deleting?
64
	if (empty($users))
65
		return;
66
	elseif (count($users) == 1)
67
	{
68
		list ($user) = $users;
69
70
		if ($user == $user_info['id'])
71
			isAllowedTo('profile_remove_own');
72
		else
73
			isAllowedTo('profile_remove_any');
74
	}
75
	else
76
	{
77
		foreach ($users as $k => $v)
78
			$users[$k] = (int) $v;
79
80
		// Deleting more than one?  You can't have more than one account...
81
		isAllowedTo('profile_remove_any');
82
	}
83
84
	// Get their names for logging purposes.
85
	$request = $db->query('', '
86
		SELECT id_member, member_name, email_address, 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
87
		FROM {db_prefix}members
88
		WHERE id_member IN ({array_int:user_list})
89
		LIMIT ' . count($users),
90
		array(
91
			'user_list' => $users,
92
			'admin_group' => 1,
93
		)
94
	);
95
	$admins = array();
96
	$emails = array();
97
	$user_log_details = array();
98 View Code Duplication
	while ($row = $db->fetch_assoc($request))
99
	{
100
		if ($row['is_admin'])
101
			$admins[] = $row['id_member'];
102
		$user_log_details[$row['id_member']] = array($row['id_member'], $row['member_name']);
103
		$emails[] = $row['email_address'];
104
	}
105
	$db->free_result($request);
106
107
	if (empty($user_log_details))
108
		return;
109
110
	// Make sure they aren't trying to delete administrators if they aren't one.  But don't bother checking if it's just themselves.
111
	if (!empty($admins) && ($check_not_admin || (!allowedTo('admin_forum') && (count($users) != 1 || $users[0] != $user_info['id']))))
112
	{
113
		$users = array_diff($users, $admins);
114
		foreach ($admins as $id)
115
			unset($user_log_details[$id]);
116
	}
117
118
	// No one left?
119
	if (empty($users))
120
		return;
121
122
	// Log the action - regardless of who is deleting it.
123
	$log_changes = array();
124
	foreach ($user_log_details as $user)
125
	{
126
		$log_changes[] = array(
127
			'action' => 'delete_member',
128
			'log_type' => 'admin',
129
			'extra' => array(
130
				'member' => $user[0],
131
				'name' => $user[1],
132
				'member_acted' => $user_info['name'],
133
			),
134
		);
135
136
		// Remove any cached data if enabled.
137
		Cache::instance()->remove('user_settings-' . $user[0]);
138
	}
139
140
	// Make these peoples' posts guest posts.
141
	$db->query('', '
142
		UPDATE {db_prefix}messages
143
		SET id_member = {int:guest_id}' . (!empty($modSettings['deleteMembersRemovesEmail']) ? ',
144
		poster_email = {string:blank_email}' : '') . '
145
		WHERE id_member IN ({array_int:users})',
146
		array(
147
			'guest_id' => 0,
148
			'blank_email' => '',
149
			'users' => $users,
150
		)
151
	);
152
	$db->query('', '
153
		UPDATE {db_prefix}polls
154
		SET id_member = {int:guest_id}
155
		WHERE id_member IN ({array_int:users})',
156
		array(
157
			'guest_id' => 0,
158
			'users' => $users,
159
		)
160
	);
161
162
	// Make these peoples' posts guest first posts and last posts.
163
	$db->query('', '
164
		UPDATE {db_prefix}topics
165
		SET id_member_started = {int:guest_id}
166
		WHERE id_member_started IN ({array_int:users})',
167
		array(
168
			'guest_id' => 0,
169
			'users' => $users,
170
		)
171
	);
172
	$db->query('', '
173
		UPDATE {db_prefix}topics
174
		SET id_member_updated = {int:guest_id}
175
		WHERE id_member_updated IN ({array_int:users})',
176
		array(
177
			'guest_id' => 0,
178
			'users' => $users,
179
		)
180
	);
181
182
	$db->query('', '
183
		UPDATE {db_prefix}log_actions
184
		SET id_member = {int:guest_id}
185
		WHERE id_member IN ({array_int:users})',
186
		array(
187
			'guest_id' => 0,
188
			'users' => $users,
189
		)
190
	);
191
192
	$db->query('', '
193
		UPDATE {db_prefix}log_banned
194
		SET id_member = {int:guest_id}
195
		WHERE id_member IN ({array_int:users})',
196
		array(
197
			'guest_id' => 0,
198
			'users' => $users,
199
		)
200
	);
201
202
	$db->query('', '
203
		UPDATE {db_prefix}log_errors
204
		SET id_member = {int:guest_id}
205
		WHERE id_member IN ({array_int:users})',
206
		array(
207
			'guest_id' => 0,
208
			'users' => $users,
209
		)
210
	);
211
212
	// Delete the member.
213
	$db->query('', '
214
		DELETE FROM {db_prefix}members
215
		WHERE id_member IN ({array_int:users})',
216
		array(
217
			'users' => $users,
218
		)
219
	);
220
221
	// Delete any likes...
222
	$db->query('', '
223
		DELETE FROM {db_prefix}message_likes
224
		WHERE id_member IN ({array_int:users})',
225
		array(
226
			'users' => $users,
227
		)
228
	);
229
230
	// Delete any custom field data...
231
	$db->query('', '
232
		DELETE FROM {db_prefix}custom_fields_data
233
		WHERE id_member IN ({array_int:users})',
234
		array(
235
			'users' => $users,
236
		)
237
	);
238
239
	// Delete any post by email keys...
240
	$db->query('', '
241
		DELETE FROM {db_prefix}postby_emails
242
		WHERE email_to IN ({array_string:emails})',
243
		array(
244
			'emails' => $emails,
245
		)
246
	);
247
248
	// Delete the logs...
249
	$db->query('', '
250
		DELETE FROM {db_prefix}log_actions
251
		WHERE id_log = {int:log_type}
252
			AND id_member IN ({array_int:users})',
253
		array(
254
			'log_type' => 2,
255
			'users' => $users,
256
		)
257
	);
258
	$db->query('', '
259
		DELETE FROM {db_prefix}log_boards
260
		WHERE id_member IN ({array_int:users})',
261
		array(
262
			'users' => $users,
263
		)
264
	);
265
	$db->query('', '
266
		DELETE FROM {db_prefix}log_comments
267
		WHERE id_recipient IN ({array_int:users})
268
			AND comment_type = {string:warntpl}',
269
		array(
270
			'users' => $users,
271
			'warntpl' => 'warntpl',
272 1
		)
273
	);
274
	$db->query('', '
275
		DELETE FROM {db_prefix}log_group_requests
276
		WHERE id_member IN ({array_int:users})',
277
		array(
278
			'users' => $users,
279
		)
280
	);
281
	$db->query('', '
282
		DELETE FROM {db_prefix}log_karma
283
		WHERE id_target IN ({array_int:users})
284
			OR id_executor IN ({array_int:users})',
285
		array(
286
			'users' => $users,
287
		)
288
	);
289
	$db->query('', '
290
		DELETE FROM {db_prefix}log_mark_read
291
		WHERE id_member IN ({array_int:users})',
292
		array(
293
			'users' => $users,
294
		)
295
	);
296
	$db->query('', '
297
		DELETE FROM {db_prefix}log_notify
298
		WHERE id_member IN ({array_int:users})',
299
		array(
300
			'users' => $users,
301
		)
302
	);
303
	$db->query('', '
304
		DELETE FROM {db_prefix}log_online
305
		WHERE id_member IN ({array_int:users})',
306
		array(
307
			'users' => $users,
308
		)
309
	);
310
	$db->query('', '
311
		DELETE FROM {db_prefix}log_subscribed
312
		WHERE id_member IN ({array_int:users})',
313
		array(
314
			'users' => $users,
315
		)
316
	);
317
	$db->query('', '
318
		DELETE FROM {db_prefix}log_topics
319
		WHERE id_member IN ({array_int:users})',
320
		array(
321
			'users' => $users,
322
		)
323
	);
324
	$db->query('', '
325
		DELETE FROM {db_prefix}collapsed_categories
326
		WHERE id_member IN ({array_int:users})',
327
		array(
328
			'users' => $users,
329
		)
330
	);
331
332
	// Make their votes appear as guest votes - at least it keeps the totals right.
333
	// @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...
334
	$db->query('', '
335
		UPDATE {db_prefix}log_polls
336
		SET id_member = {int:guest_id}
337
		WHERE id_member IN ({array_int:users})',
338
		array(
339
			'guest_id' => 0,
340
			'users' => $users,
341
		)
342
	);
343
344
	// Remove the mentions
345
	$db->query('', '
346
		DELETE FROM {db_prefix}log_mentions
347
		WHERE id_member IN ({array_int:users})',
348
		array(
349
			'users' => $users,
350
		)
351
	);
352
	// And null all those that were added by him
353
	$db->query('', '
354
		UPDATE {db_prefix}log_mentions
355
		SET id_member_from = {int:zero}
356
		WHERE id_member_from IN ({array_int:users})',
357
		array(
358
			'zero' => 0,
359
			'users' => $users,
360
		)
361
	);
362
363
	// Delete personal messages.
364
	require_once(SUBSDIR . '/PersonalMessage.subs.php');
365
	deleteMessages(null, null, $users);
366
367
	$db->query('', '
368
		UPDATE {db_prefix}personal_messages
369
		SET id_member_from = {int:guest_id}
370
		WHERE id_member_from IN ({array_int:users})',
371
		array(
372
			'guest_id' => 0,
373
			'users' => $users,
374
		)
375
	);
376
377
	// They no longer exist, so we don't know who it was sent to.
378
	$db->query('', '
379
		DELETE FROM {db_prefix}pm_recipients
380
		WHERE id_member IN ({array_int:users})',
381
		array(
382
			'users' => $users,
383
		)
384
	);
385
386
	// Delete avatar.
387
	require_once(SUBSDIR . '/ManageAttachments.subs.php');
388
	removeAttachments(array('id_member' => $users));
389
390
	// It's over, no more moderation for you.
391
	$db->query('', '
392
		DELETE FROM {db_prefix}moderators
393
		WHERE id_member IN ({array_int:users})',
394
		array(
395
			'users' => $users,
396
		)
397
	);
398
	$db->query('', '
399
		DELETE FROM {db_prefix}group_moderators
400
		WHERE id_member IN ({array_int:users})',
401
		array(
402
			'users' => $users,
403
		)
404
	);
405
406
	// If you don't exist we can't ban you.
407
	$db->query('', '
408
		DELETE FROM {db_prefix}ban_items
409
		WHERE id_member IN ({array_int:users})',
410
		array(
411
			'users' => $users,
412
		)
413
	);
414
415
	// Remove individual theme settings.
416
	$db->query('', '
417
		DELETE FROM {db_prefix}themes
418
		WHERE id_member IN ({array_int:users})',
419
		array(
420
			'users' => $users,
421
		)
422
	);
423
424
	// These users are nobody's buddy nomore.
425
	$db->fetchQueryCallback('
426
		SELECT id_member, pm_ignore_list, buddy_list
427
		FROM {db_prefix}members
428
		WHERE FIND_IN_SET({raw:pm_ignore_list}, pm_ignore_list) != 0 OR FIND_IN_SET({raw:buddy_list}, buddy_list) != 0',
429
		array(
430
			'pm_ignore_list' => implode(', pm_ignore_list) != 0 OR FIND_IN_SET(', $users),
431
			'buddy_list' => implode(', buddy_list) != 0 OR FIND_IN_SET(', $users),
432
		),
433
		function ($row) use ($users)
434
		{
435
			updateMemberData($row['id_member'], array(
436
				'pm_ignore_list' => implode(',', array_diff(explode(',', $row['pm_ignore_list']), $users)),
437
				'buddy_list' => implode(',', array_diff(explode(',', $row['buddy_list']), $users))
438
			));
439
		}
440
	);
441
442
	// Make sure no member's birthday is still sticking in the calendar...
443
	updateSettings(array(
444
		'calendar_updated' => time(),
445
	));
446
447
	// Integration rocks!
448
	call_integration_hook('integrate_delete_members', array($users));
449
450
	updateMemberStats();
451
452
	logActions($log_changes);
453
}
454
455
/**
456
 * Registers a member to the forum.
457
 *
458
 * What it does:
459
 *
460
 * - Allows two types of interface: 'guest' and 'admin'. The first
461
 * - includes hammering protection, the latter can perform the registration silently.
462
 * - The strings used in the options array are assumed to be escaped.
463
 * - Allows to perform several checks on the input, e.g. reserved names.
464
 * - The function will adjust member statistics.
465
 * - If an error is detected will fatal error on all errors unless return_errors is true.
466
 *
467
 * @package Members
468
 * @uses Auth.subs.php
469
 * @uses Mail.subs.php
470
 *
471
 * @param mixed[] $regOptions
472
 * @param string  $ErrorContext
473
 *
474
 * @return int the ID of the newly created member
475
 * @throws Elk_Exception no_theme
476
 */
477
function registerMember(&$regOptions, $ErrorContext = 'register')
478
{
479 8
	global $scripturl, $txt;
480
481 8
	$db = database();
482
483 8
	loadLanguage('Login');
484
485
	// We'll need some external functions.
486 8
	require_once(SUBSDIR . '/Auth.subs.php');
487 8
	require_once(SUBSDIR . '/Mail.subs.php');
488
489
	// Put any errors in here.
490 8
	$reg_errors = ElkArte\Errors\ErrorContext::context($ErrorContext, 0);
491
492
	// What method of authorization are we going to use?
493 8
	if (empty($regOptions['auth_method']) || !in_array($regOptions['auth_method'], array('password', 'openid')))
494 6
	{
495 2
		if (!empty($regOptions['openid']))
496 2
			$regOptions['auth_method'] = 'openid';
497
		else
498 2
			$regOptions['auth_method'] = 'password';
499 2
	}
500
501
	// Spaces and other odd characters are evil...
502 8
	$regOptions['username'] = trim(preg_replace('~[\t\n\r \x0B\0\x{A0}\x{AD}\x{2000}-\x{200F}\x{201F}\x{202F}\x{3000}\x{FEFF}]+~u', ' ', $regOptions['username']));
503
504
	// Valid emails only
505 8
	if (!Data_Validator::is_valid($regOptions, array('email' => 'valid_email|required|max_length[255]'), array('email' => 'trim')))
506 6
		$reg_errors->addError('bad_email');
507
508 8
	validateUsername(0, $regOptions['username'], $ErrorContext, !empty($regOptions['check_reserved_name']));
509
510
	// Generate a validation code if it's supposed to be emailed.
511 8
	$validation_code = $regOptions['require'] === 'activation' ? generateValidationCode(14) : '';
512
513
	// Does the first password match the second?
514 8
	if ($regOptions['password'] != $regOptions['password_check'] && $regOptions['auth_method'] == 'password')
515 6
		$reg_errors->addError('passwords_dont_match');
516
517
	// That's kind of easy to guess...
518 8
	if ($regOptions['password'] == '')
519 6
	{
520
		if ($regOptions['auth_method'] == 'password')
521
			$reg_errors->addError('no_password');
522
		else
523
			$regOptions['password'] = sha1(mt_rand());
524
	}
525
526
	// Now perform hard password validation as required.
527 8
	if (!empty($regOptions['check_password_strength']) && $regOptions['password'] != '')
528 6
	{
529 6
		$passwordError = validatePassword($regOptions['password'], $regOptions['username'], array($regOptions['email']));
530
531
		// Password isn't legal?
532 6
		if ($passwordError !== null)
533 4
			$reg_errors->addError('profile_error_password_' . $passwordError);
534 4
	}
535
536
	// @todo move to controller
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...
537
	// You may not be allowed to register this email.
538 8
	if (!empty($regOptions['check_email_ban']))
539 8
		isBannedEmail($regOptions['email'], 'cannot_register', $txt['ban_register_prohibited']);
540
541
	// Check if the email address is in use.
542 8
	if (userByEmail($regOptions['email'], $regOptions['username']))
543 6
	{
544
		$reg_errors->addError(array('email_in_use', array(htmlspecialchars($regOptions['email'], ENT_COMPAT, 'UTF-8'))));
545
	}
546
547
	// Perhaps someone else wants to check this user
548 8
	call_integration_hook('integrate_register_check', array(&$regOptions, &$reg_errors));
549
550
	// If there's any errors left return them at once!
551 8
	if ($reg_errors->hasErrors())
552 6
		return false;
553
554
	$reservedVars = array(
555 8
		'actual_theme_url',
556 6
		'actual_images_url',
557 6
		'base_theme_dir',
558 6
		'base_theme_url',
559 6
		'default_images_url',
560 6
		'default_theme_dir',
561 6
		'default_theme_url',
562 6
		'default_template',
563 6
		'images_url',
564 6
		'number_recent_posts',
565 6
		'smiley_sets_default',
566 6
		'theme_dir',
567 6
		'theme_id',
568 6
		'theme_layers',
569 6
		'theme_templates',
570 6
		'theme_url',
571 6
	);
572
573
	// Can't change reserved vars.
574 8
	if (isset($regOptions['theme_vars']) && count(array_intersect(array_keys($regOptions['theme_vars']), $reservedVars)) != 0)
575 6
		throw new Elk_Exception('no_theme');
576
577 8
	$tokenizer = new Token_Hash();
578
579
	// @since 1.0.7 - This is necessary because validateLoginPassword
580
	// uses a pass-by-ref and would convert to hash $regOptions['password']
581
	// making it impossible to send the reminder email and even integrate
582
	// the registration
583 8
	$password = $regOptions['password'];
584
585
	// Some of these might be overwritten. (the lower ones that are in the arrays below.)
586 8
	$regOptions['register_vars'] = array(
587 8
		'member_name' => $regOptions['username'],
588 8
		'email_address' => $regOptions['email'],
589 8
		'passwd' => validateLoginPassword($password, '', $regOptions['username'], true),
590 8
		'password_salt' => $tokenizer->generate_hash(4),
591 8
		'posts' => 0,
592 8
		'date_registered' => !empty($regOptions['time']) ? $regOptions['time'] : time(),
593 8
		'member_ip' => $regOptions['interface'] == 'admin' ? '127.0.0.1' : $regOptions['ip'],
594 8
		'member_ip2' => $regOptions['interface'] == 'admin' ? '127.0.0.1' : $regOptions['ip2'],
595 8
		'validation_code' => substr(hash('sha256', $validation_code), 0, 10),
596 8
		'real_name' => !empty($regOptions['real_name']) ? $regOptions['real_name'] : $regOptions['username'],
597 8
		'pm_email_notify' => 1,
598 8
		'id_theme' => 0,
599 8
		'id_post_group' => 4,
600 8
		'lngfile' => '',
601 8
		'buddy_list' => '',
602 8
		'pm_ignore_list' => '',
603 8
		'message_labels' => '',
604 8
		'website_title' => '',
605 8
		'website_url' => '',
606 8
		'time_format' => '',
607 8
		'signature' => '',
608 8
		'avatar' => '',
609 8
		'usertitle' => '',
610 8
		'secret_question' => '',
611 8
		'secret_answer' => '',
612 8
		'additional_groups' => '',
613 8
		'ignore_boards' => '',
614 8
		'smiley_set' => '',
615 8
		'openid_uri' => (!empty($regOptions['openid']) ? $regOptions['openid'] : ''),
616 8
		'notify_announcements' => (!empty($regOptions['notify_announcements']) ? 1 : 0),
617
	);
618
619
	// Setup the activation status on this new account so it is correct - firstly is it an under age account?
620 8
	if ($regOptions['require'] == 'coppa')
621 6
	{
622
		$regOptions['register_vars']['is_activated'] = 5;
623
		// @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...
624
		$regOptions['register_vars']['validation_code'] = '';
625
	}
626
	// Maybe it can be activated right away?
627 8
	elseif ($regOptions['require'] == 'nothing')
628 7
		$regOptions['register_vars']['is_activated'] = 1;
629
	// Maybe it must be activated by email?
630 1
	elseif ($regOptions['require'] == 'activation')
631 1
		$regOptions['register_vars']['is_activated'] = 0;
632
	// Otherwise it must be awaiting approval!
633
	else
634 1
		$regOptions['register_vars']['is_activated'] = 3;
635
636 8
	if (isset($regOptions['memberGroup']))
637 6
	{
638 7
		require_once(SUBSDIR . '/Membergroups.subs.php');
639
640
		// Make sure the id_group will be valid, if this is an administrator.
641 7
		$regOptions['register_vars']['id_group'] = $regOptions['memberGroup'] == 1 && !allowedTo('admin_forum') ? 0 : $regOptions['memberGroup'];
642
643
		// Check if this group is assignable.
644 7
		$unassignableGroups = getUnassignableGroups(allowedTo('admin_forum'));
645
646 7
		if (in_array($regOptions['register_vars']['id_group'], $unassignableGroups))
647 5
			$regOptions['register_vars']['id_group'] = 0;
648 5
	}
649
650
	// Integrate optional member settings to be set.
651 8
	if (!empty($regOptions['extra_register_vars']))
652 6
		foreach ($regOptions['extra_register_vars'] as $var => $value)
653
			$regOptions['register_vars'][$var] = $value;
654
655
	// Integrate optional user theme options to be set.
656 8
	$theme_vars = array();
657 8
	if (!empty($regOptions['theme_vars']))
658 6
		foreach ($regOptions['theme_vars'] as $var => $value)
659
			$theme_vars[$var] = $value;
660
661
	// Right, now let's prepare for insertion.
662
	$knownInts = array(
663 8
		'date_registered', 'posts', 'id_group', 'last_login', 'personal_messages', 'unread_messages', 'notifications',
664 6
		'new_pm', 'pm_prefs', 'hide_email', 'show_online', 'pm_email_notify', 'karma_good', 'karma_bad',
665 6
		'notify_announcements', 'notify_send_body', 'notify_regularity', 'notify_types',
666 6
		'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning',
667 6
	);
668
	$knownFloats = array(
669 8
		'time_offset',
670 6
	);
671
672
	// Call an optional function to validate the users' input.
673 8
	call_integration_hook('integrate_register', array(&$regOptions, &$theme_vars, &$knownInts, &$knownFloats));
674
675 8
	$column_names = array();
676 8
	$values = array();
677 8
	foreach ($regOptions['register_vars'] as $var => $val)
678
	{
679 8
		$type = 'string';
680 8 View Code Duplication
		if (in_array($var, $knownInts))
681 8
			$type = 'int';
682 8
		elseif (in_array($var, $knownFloats))
683
			$type = 'float';
684 8
		elseif ($var == 'birthdate')
685
			$type = 'date';
686
687 8
		$column_names[$var] = $type;
688 8
		$values[$var] = $val;
689 6
	}
690
691
	// Register them into the database.
692 8
	$db->insert('',
693 8
		'{db_prefix}members',
694 6
		$column_names,
695 6
		$values,
696 8
		array('id_member')
697 6
	);
698 8
	$memberID = $db->insert_id('{db_prefix}members', 'id_member');
699
700
	// Update the number of members and latest member's info - and pass the name, but remove the 's.
701 8
	if ($regOptions['register_vars']['is_activated'] == 1)
702 8
		updateMemberStats($memberID, $regOptions['register_vars']['real_name']);
703
	else
704 1
		updateMemberStats();
705
706
	// @todo there's got to be a method that does this
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...
707
	// Theme variables too?
708 8
	if (!empty($theme_vars))
709 6
	{
710
		$inserts = array();
711
		foreach ($theme_vars as $var => $val)
712
			$inserts[] = array($memberID, $var, $val);
713
		$db->insert('insert',
714
			'{db_prefix}themes',
715
			array('id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
716
			$inserts,
717
			array('id_member', 'variable')
718
		);
719
	}
720
721
	// If it's enabled, increase the registrations for today.
722 8
	trackStats(array('registers' => '+'));
723
724
	// @todo emails should be sent from the controller, with a new method.
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...
725
726
	// Don't worry about what the emails might want to replace. Just give them everything and let them sort it out.
727
	$replacements = array(
728 8
		'REALNAME' => $regOptions['register_vars']['real_name'],
729 8
		'USERNAME' => $regOptions['username'],
730 8
		'PASSWORD' => $regOptions['password'],
731 8
		'FORGOTPASSWORDLINK' => $scripturl . '?action=reminder',
732 8
		'ACTIVATIONLINK' => $scripturl . '?action=register;sa=activate;u=' . $memberID . ';code=' . $validation_code,
733 8
		'ACTIVATIONLINKWITHOUTCODE' => $scripturl . '?action=register;sa=activate;u=' . $memberID,
734 8
		'ACTIVATIONCODE' => $validation_code,
735 8
		'OPENID' => !empty($regOptions['openid']) ? $regOptions['openid'] : '',
736 8
		'COPPALINK' => $scripturl . '?action=register;sa=coppa;u=' . $memberID,
737 6
	);
738
739
	// Administrative registrations are a bit different...
740 8
	if ($regOptions['interface'] == 'admin')
741 6
	{
742 2
		if ($regOptions['require'] == 'activation')
743 2
			$email_message = 'admin_register_activate';
744 2
		elseif (!empty($regOptions['send_welcome_email']))
745
			$email_message = 'admin_register_immediate';
746
747 2
		if (isset($email_message))
748 2
		{
749 1
			$emaildata = loadEmailTemplate($email_message, $replacements);
750
751 1
			sendmail($regOptions['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 0);
752 1
		}
753 2
	}
754
	else
755
	{
756
		// Can post straight away - welcome them to your fantastic community...
757 6
		if ($regOptions['require'] == 'nothing')
758 4
		{
759 6
			if (!empty($regOptions['send_welcome_email']))
760 4
			{
761
				$replacements = array(
762
					'REALNAME' => $regOptions['register_vars']['real_name'],
763
					'USERNAME' => $regOptions['username'],
764
					'PASSWORD' => $regOptions['password'],
765
					'FORGOTPASSWORDLINK' => $scripturl . '?action=reminder',
766
					'OPENID' => !empty($regOptions['openid']) ? $regOptions['openid'] : '',
767
				);
768
				$emaildata = loadEmailTemplate('register_' . ($regOptions['auth_method'] == 'openid' ? 'openid_' : '') . 'immediate', $replacements);
769
				sendmail($regOptions['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 0);
770
			}
771
772
			// Send admin their notification.
773 6
			require_once(SUBSDIR . '/Notification.subs.php');
774 6
			sendAdminNotifications('standard', $memberID, $regOptions['username']);
775 4
		}
776
		// Need to activate their account - or fall under COPPA.
777
		elseif ($regOptions['require'] == 'activation' || $regOptions['require'] == 'coppa')
778
		{
779
780
			$emaildata = loadEmailTemplate('register_' . ($regOptions['auth_method'] == 'openid' ? 'openid_' : '') . ($regOptions['require'] == 'activation' ? 'activate' : 'coppa'), $replacements);
781
782
			sendmail($regOptions['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 0);
783
		}
784
		// Must be awaiting approval.
785
		else
786
		{
787
			$replacements = array(
788
				'REALNAME' => $regOptions['register_vars']['real_name'],
789
				'USERNAME' => $regOptions['username'],
790
				'PASSWORD' => $regOptions['password'],
791
				'FORGOTPASSWORDLINK' => $scripturl . '?action=reminder',
792
				'OPENID' => !empty($regOptions['openid']) ? $regOptions['openid'] : '',
793
			);
794
795
			$emaildata = loadEmailTemplate('register_' . ($regOptions['auth_method'] == 'openid' ? 'openid_' : '') . 'pending', $replacements);
796
797
			sendmail($regOptions['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 0);
798
799
			// Admin gets informed here...
800
			require_once(SUBSDIR . '/Notification.subs.php');
801
			sendAdminNotifications('approval', $memberID, $regOptions['username']);
802
		}
803
804
		// Okay, they're for sure registered... make sure the session is aware of this for security. (Just married :P!)
805 6
		$_SESSION['just_registered'] = 1;
806
	}
807
808
	// If they are for sure registered, let other people to know about it
809 8
	call_integration_hook('integrate_register_after', array($regOptions, $memberID));
810
811 8
	return $memberID;
812
}
813
814
/**
815
 * Check if a name is in the reserved words list. (name, current member id, name/username?.)
816
 *
817
 * - checks if name is a reserved name or username.
818
 * - if is_name is false, the name is assumed to be a username.
819
 * - the id_member variable is used to ignore duplicate matches with the current member.
820
 *
821
 * @package Members
822
 *
823
 * @param string $name
824
 * @param int    $current_ID_MEMBER
825
 * @param bool   $is_name
826
 * @param bool   $fatal
827
 *
828
 * @return bool
829
 * @throws Elk_Exception username_reserved, name_censored
830
 */
831
function isReservedName($name, $current_ID_MEMBER = 0, $is_name = true, $fatal = true)
832
{
833 6
	global $modSettings;
834
835 6
	$db = database();
836
837 6
	$name = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'replaceEntities__callback', $name);
838 6
	$checkName = Util::strtolower($name);
839
840
	// Administrators are never restricted ;).
841 6
	if (!allowedTo('admin_forum') && ((!empty($modSettings['reserveName']) && $is_name) || !empty($modSettings['reserveUser']) && !$is_name))
842 4
	{
843 6
		$reservedNames = explode("\n", $modSettings['reserveNames']);
844
		// Case sensitive check?
845 6
		$checkMe = empty($modSettings['reserveCase']) ? $checkName : $name;
846
847
		// Check each name in the list...
848 6
		foreach ($reservedNames as $reserved)
849
		{
850 6
			if ($reserved == '')
851 4
				continue;
852
853
			// The admin might've used entities too, level the playing field.
854 6
			$reservedCheck = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'replaceEntities__callback', $reserved);
855
856
			// Case sensitive name?
857 6
			if (empty($modSettings['reserveCase']))
858 4
				$reservedCheck = Util::strtolower($reservedCheck);
859
860
			// If it's not just entire word, check for it in there somewhere...
861 6
			if ($checkMe == $reservedCheck || (Util::strpos($checkMe, $reservedCheck) !== false && empty($modSettings['reserveWord'])))
862 4
				if ($fatal)
863
					throw new Elk_Exception('username_reserved', 'password', array($reserved));
864
				else
865 2
					return true;
866 4
		}
867
868 6
		$censor_name = $name;
869 6
		if (censor($censor_name) != $name)
870 4
			if ($fatal)
871
				throw new Elk_Exception('name_censored', 'password', array($name));
872
			else
873
				return true;
874 4
	}
875
876
	// Characters we just shouldn't allow, regardless.
877 6
	foreach (array('*') as $char)
878 6
		if (strpos($checkName, $char) !== false)
879 4
			if ($fatal)
880
				throw new Elk_Exception('username_reserved', 'password', array($char));
881
			else
882 2
				return true;
883
884
	// Get rid of any SQL parts of the reserved name...
885 6
	$checkName = strtr($name, array('_' => '\\_', '%' => '\\%'));
886
887
	// Make sure they don't want someone else's name.
888 6
	$request = $db->query('', '
889
		SELECT id_member
890
		FROM {db_prefix}members
891 6
		WHERE ' . (empty($current_ID_MEMBER) ? '' : 'id_member != {int:current_member}
892 6
			AND ') . '({raw:real_name} LIKE {string:check_name} OR {raw:member_name} LIKE {string:check_name})
893 6
		LIMIT 1',
894
		array(
895 6
			'real_name' => defined('DB_CASE_SENSITIVE') ? 'LOWER(real_name)' : 'real_name',
896 6
			'member_name' => defined('DB_CASE_SENSITIVE') ? 'LOWER(member_name)' : 'member_name',
897 6
			'current_member' => $current_ID_MEMBER,
898 6
			'check_name' => $checkName,
899
		)
900 4
	);
901 6
	if ($db->num_rows($request) > 0)
902 4
	{
903
		$db->free_result($request);
904
		return true;
905
	}
906
907
	// Does name case insensitive match a member group name?
908 6
	$request = $db->query('', '
909
		SELECT id_group
910
		FROM {db_prefix}membergroups
911
		WHERE {raw:group_name} LIKE {string:check_name}
912 6
		LIMIT 1',
913
		array(
914 6
			'group_name' => defined('DB_CASE_SENSITIVE') ? 'LOWER(group_name)' : 'group_name',
915 6
			'check_name' => $checkName,
916
		)
917 4
	);
918 6
	if ($db->num_rows($request) > 0)
919 4
	{
920
		$db->free_result($request);
921
		return true;
922
	}
923
924
	// Okay, they passed.
925 6
	return false;
926
}
927
928
/**
929
 * Retrieves a list of membergroups that are allowed to do the given
930
 * permission. (on the given board)
931
 *
932
 * - If board_id is not null, a board permission is assumed.
933
 * - The function takes different permission settings into account.
934
 *
935
 * @package Members
936
 *
937
 * @param string       $permission
938
 * @param integer|null $board_id = null
939
 *
940
 * @return array containing an array for the allowed membergroup ID's
941
 * and an array for the denied membergroup ID's.
942
 * @throws Elk_Exception no_board
943
 */
944
function groupsAllowedTo($permission, $board_id = null)
945
{
946
	global $board_info;
947
948
	$db = database();
949
950
	// Admins are allowed to do anything.
951
	$member_groups = array(
952
		'allowed' => array(1),
953
		'denied' => array(),
954
	);
955
956
	// Assume we're dealing with regular permissions (like profile_view_own).
957
	if ($board_id === null)
958
	{
959
		$request = $db->query('', '
960
			SELECT id_group, add_deny
961
			FROM {db_prefix}permissions
962
			WHERE permission = {string:permission}',
963
			array(
964
				'permission' => $permission,
965
			)
966
		);
967 View Code Duplication
		while ($row = $db->fetch_assoc($request))
968
			$member_groups[$row['add_deny'] === '1' ? 'allowed' : 'denied'][] = $row['id_group'];
969
		$db->free_result($request);
970
	}
971
972
	// Otherwise it's time to look at the board.
973
	else
974
	{
975
		// First get the profile of the given board.
976
		if (isset($board_info['id']) && $board_info['id'] == $board_id)
977
			$profile_id = $board_info['profile'];
978
		elseif ($board_id !== 0)
979
		{
980
			require_once(SUBSDIR . '/Boards.subs.php');
981
			$board_data = fetchBoardsInfo(array('boards' => $board_id), array('selects' => 'permissions'));
982
983
			if (empty($board_data))
984
				throw new Elk_Exception('no_board');
985
			$profile_id = $board_data[$board_id]['id_profile'];
986
		}
987
		else
988
			$profile_id = 1;
989
990
		$request = $db->query('', '
991
			SELECT bp.id_group, bp.add_deny
992
			FROM {db_prefix}board_permissions AS bp
993
			WHERE bp.permission = {string:permission}
994
				AND bp.id_profile = {int:profile_id}',
995
			array(
996
				'profile_id' => $profile_id,
997
				'permission' => $permission,
998
			)
999
		);
1000 View Code Duplication
		while ($row = $db->fetch_assoc($request))
1001
			$member_groups[$row['add_deny'] === '1' ? 'allowed' : 'denied'][] = $row['id_group'];
1002
		$db->free_result($request);
1003
	}
1004
1005
	// Denied is never allowed.
1006
	$member_groups['allowed'] = array_diff($member_groups['allowed'], $member_groups['denied']);
1007
1008
	return $member_groups;
1009
}
1010
1011
/**
1012
 * Retrieves a list of members that have a given permission (on a given board).
1013
 *
1014
 * - If board_id is not null, a board permission is assumed.
1015
 * - Takes different permission settings into account.
1016
 * - Takes possible moderators (on board 'board_id') into account.
1017
 *
1018
 * @package Members
1019
 * @param string $permission
1020
 * @param integer|null $board_id = null
1021
 *
1022
 * @return int[] an array containing member ID's.
1023
 * @throws Elk_Exception
1024
 */
1025
function membersAllowedTo($permission, $board_id = null)
1026
{
1027
	$db = database();
1028
1029
	$member_groups = groupsAllowedTo($permission, $board_id);
1030
1031
	$include_moderators = in_array(3, $member_groups['allowed']) && $board_id !== null;
1032
	$member_groups['allowed'] = array_diff($member_groups['allowed'], array(3));
1033
1034
	$exclude_moderators = in_array(3, $member_groups['denied']) && $board_id !== null;
1035
	$member_groups['denied'] = array_diff($member_groups['denied'], array(3));
1036
1037
	return $db->fetchQueryCallback('
1038
		SELECT mem.id_member
1039
		FROM {db_prefix}members AS mem' . ($include_moderators || $exclude_moderators ? '
1040
			LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_member = mem.id_member AND mods.id_board = {int:board_id})' : '') . '
1041
		WHERE (' . ($include_moderators ? 'mods.id_member 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']) ? '' : '
1042
			AND NOT (' . ($exclude_moderators ? 'mods.id_member 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}))'),
1043
		array(
1044
			'member_groups_allowed' => $member_groups['allowed'],
1045
			'member_groups_denied' => $member_groups['denied'],
1046
			'board_id' => $board_id,
1047
			'member_group_allowed_implode' => implode(', mem.additional_groups) != 0 OR FIND_IN_SET(', $member_groups['allowed']),
1048
			'member_group_denied_implode' => implode(', mem.additional_groups) != 0 OR FIND_IN_SET(', $member_groups['denied']),
1049
		),
1050
		function ($row)
1051
		{
1052
			return $row['id_member'];
1053
		}
1054
	);
1055
}
1056
1057
/**
1058
 * This function is used to re-associate members with relevant posts.
1059
 *
1060
 * - Re-attribute guest posts to a specified member.
1061
 * - Does not check for any permissions.
1062
 * - If add_to_post_count is set, the member's post count is increased.
1063
 *
1064
 * @package Members
1065
 *
1066
 * @param int $memID
1067
 * @param bool|false|string $email = false
1068
 * @param bool|false|string $membername = false
1069
 * @param bool $post_count = false
1070
 */
1071
function reattributePosts($memID, $email = false, $membername = false, $post_count = false)
1072
{
1073
	$db = database();
1074
1075
	// Firstly, if email and username aren't passed find out the members email address and name.
1076
	if ($email === false && $membername === false)
1077
	{
1078
		require_once(SUBSDIR . '/Members.subs.php');
1079
		$result = getBasicMemberData($memID);
1080
		$email = $result['email_address'];
1081
		$membername = $result['member_name'];
1082
	}
1083
1084
	// If they want the post count restored then we need to do some research.
1085
	if ($post_count)
1086
	{
1087
		$request = $db->query('', '
1088
			SELECT COUNT(*)
1089
			FROM {db_prefix}messages AS m
1090
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND b.count_posts = {int:count_posts})
1091
			WHERE m.id_member = {int:guest_id}
1092
				AND m.approved = {int:is_approved}
1093
				AND m.icon != {string:recycled_icon}' . (empty($email) ? '' : '
1094
				AND m.poster_email = {string:email_address}') . (empty($membername) ? '' : '
1095
				AND m.poster_name = {string:member_name}'),
1096
			array(
1097
				'count_posts' => 0,
1098
				'guest_id' => 0,
1099
				'email_address' => $email,
1100
				'member_name' => $membername,
1101
				'is_approved' => 1,
1102
				'recycled_icon' => 'recycled',
1103
			)
1104
		);
1105
		list ($messageCount) = $db->fetch_row($request);
1106
		$db->free_result($request);
1107
1108
		updateMemberData($memID, array('posts' => 'posts + ' . $messageCount));
1109
	}
1110
1111
	$query_parts = array();
1112
	if (!empty($email))
1113
		$query_parts[] = 'poster_email = {string:email_address}';
1114
	if (!empty($membername))
1115
		$query_parts[] = 'poster_name = {string:member_name}';
1116
	$query = implode(' AND ', $query_parts);
1117
1118
	// Finally, update the posts themselves!
1119
	$db->query('', '
1120
		UPDATE {db_prefix}messages
1121
		SET id_member = {int:memID}
1122
		WHERE ' . $query,
1123
		array(
1124
			'memID' => $memID,
1125
			'email_address' => $email,
1126
			'member_name' => $membername,
1127
		)
1128
	);
1129
1130
	// ...and the topics too!
1131
	$db->query('', '
1132
		UPDATE {db_prefix}topics as t, {db_prefix}messages as m
1133
		SET t.id_member_started = {int:memID}
1134
		WHERE m.id_member = {int:memID}
1135
			AND t.id_first_msg = m.id_msg',
1136
		array(
1137
			'memID' => $memID,
1138
		)
1139
	);
1140
1141
	// Allow mods with their own post tables to re-attribute posts as well :)
1142
	call_integration_hook('integrate_reattribute_posts', array($memID, $email, $membername, $post_count));
1143
}
1144
1145
/**
1146
 * Gets a listing of members, Callback for createList().
1147
 *
1148
 * @package Members
1149
 * @param int $start The item to start with (for pagination purposes)
1150
 * @param int $items_per_page  The number of items to show per page
1151
 * @param string $sort A string indicating how to sort the results
1152
 * @param string $where
1153
 * @param mixed[] $where_params
1154
 * @param boolean $get_duplicates
1155
 */
1156
function list_getMembers($start, $items_per_page, $sort, $where, $where_params = array(), $get_duplicates = false)
1157
{
1158
	$db = database();
1159
1160
	$members = $db->fetchQuery('
1161
		SELECT
1162
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.member_ip, mem.member_ip2, mem.last_login,
1163
			mem.posts, mem.is_activated, mem.date_registered, mem.id_group, mem.additional_groups, mg.group_name
1164
		FROM {db_prefix}members AS mem
1165
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)
1166
		WHERE ' . ($where == '1' ? '1=1' : $where) . '
1167
		ORDER BY {raw:sort}
1168
		LIMIT {int:start}, {int:per_page}',
1169
		array_merge($where_params, array(
1170
			'sort' => $sort,
1171
			'start' => $start,
1172
			'per_page' => $items_per_page,
1173
		))
1174
	);
1175
1176
	// If we want duplicates pass the members array off.
1177
	if ($get_duplicates)
1178
		populateDuplicateMembers($members);
1179
1180
	return $members;
1181
}
1182
1183
/**
1184
 * Gets the number of members, Callback for createList().
1185
 *
1186
 * @package Members
1187
 * @param string $where
1188
 * @param mixed[] $where_params
1189
 */
1190
function list_getNumMembers($where, $where_params = array())
1191
{
1192
	global $modSettings;
1193
1194
	$db = database();
1195
1196
	// We know how many members there are in total.
1197
	if (empty($where) || $where == '1=1')
1198
		$num_members = $modSettings['totalMembers'];
1199
1200
	// The database knows the amount when there are extra conditions.
1201
	else
1202
	{
1203
		$request = $db->query('', '
1204
			SELECT COUNT(*)
1205
			FROM {db_prefix}members AS mem
1206
			WHERE ' . $where,
1207
			array_merge($where_params, array(
1208
			))
1209
		);
1210
		list ($num_members) = $db->fetch_row($request);
1211
		$db->free_result($request);
1212
	}
1213
1214
	return $num_members;
1215
}
1216
1217
/**
1218
 * Find potential duplicate registration members based on the same IP address
1219
 *
1220
 * @package Members
1221
 * @param mixed[] $members
1222
 */
1223
function populateDuplicateMembers(&$members)
1224
{
1225
	$db = database();
1226
1227
	// This will hold all the ip addresses.
1228
	$ips = array();
1229
	foreach ($members as $key => $member)
1230
	{
1231
		// Create the duplicate_members element.
1232
		$members[$key]['duplicate_members'] = array();
1233
1234
		// Store the IPs.
1235
		if (!empty($member['member_ip']))
1236
			$ips[] = $member['member_ip'];
1237
		if (!empty($member['member_ip2']))
1238
			$ips[] = $member['member_ip2'];
1239
	}
1240
1241
	$ips = array_unique($ips);
1242
1243
	if (empty($ips))
1244
		return false;
1245
1246
	// Fetch all members with this IP address, we'll filter out the current ones in a sec.
1247
	$members = membersByIP($ips, 'exact', true);
1248
1249
	$duplicate_members = array();
1250
	$duplicate_ids = array();
1251
	foreach ($members as $row)
1252
	{
1253
		//$duplicate_ids[] = $row['id_member'];
1254
1255
		$member_context = array(
1256
			'id' => $row['id_member'],
1257
			'name' => $row['member_name'],
1258
			'email' => $row['email_address'],
1259
			'is_banned' => $row['is_activated'] > 10,
1260
			'ip' => $row['member_ip'],
1261
			'ip2' => $row['member_ip2'],
1262
		);
1263
1264
		if (in_array($row['member_ip'], $ips))
1265
			$duplicate_members[$row['member_ip']][] = $member_context;
1266
		if ($row['member_ip'] != $row['member_ip2'] && in_array($row['member_ip2'], $ips))
1267
			$duplicate_members[$row['member_ip2']][] = $member_context;
1268
	}
1269
1270
	// Also try to get a list of messages using these ips.
1271
	$request = $db->query('', '
1272
		SELECT
1273
			m.poster_ip, mem.id_member, mem.member_name, mem.email_address, mem.is_activated
1274
		FROM {db_prefix}messages AS m
1275
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
1276
		WHERE m.id_member != 0
1277
			' . (!empty($duplicate_ids) ? 'AND m.id_member NOT IN ({array_int:duplicate_ids})' : '') . '
1278
			AND m.poster_ip IN ({array_string:ips})',
1279
		array(
1280
			'duplicate_ids' => $duplicate_ids,
1281
			'ips' => $ips,
1282
		)
1283
	);
1284
1285
	$had_ips = array();
1286
	while ($row = $db->fetch_assoc($request))
1287
	{
1288
		// Don't collect lots of the same.
1289
		if (isset($had_ips[$row['poster_ip']]) && in_array($row['id_member'], $had_ips[$row['poster_ip']]))
1290
			continue;
1291
		$had_ips[$row['poster_ip']][] = $row['id_member'];
1292
1293
		$duplicate_members[$row['poster_ip']][] = array(
1294
			'id' => $row['id_member'],
1295
			'name' => $row['member_name'],
1296
			'email' => $row['email_address'],
1297
			'is_banned' => $row['is_activated'] > 10,
1298
			'ip' => $row['poster_ip'],
1299
			'ip2' => $row['poster_ip'],
1300
		);
1301
	}
1302
	$db->free_result($request);
1303
1304
	// Now we have all the duplicate members, stick them with their respective member in the list.
1305
	if (!empty($duplicate_members))
1306
		foreach ($members as $key => $member)
1307
		{
1308
			if (isset($duplicate_members[$member['member_ip']]))
1309
				$members[$key]['duplicate_members'] = $duplicate_members[$member['member_ip']];
1310
			if ($member['member_ip'] != $member['member_ip2'] && isset($duplicate_members[$member['member_ip2']]))
1311
				$members[$key]['duplicate_members'] = array_merge($member['duplicate_members'], $duplicate_members[$member['member_ip2']]);
1312
1313
			// Check we don't have lots of the same member.
1314
			$member_track = array($member['id_member']);
1315
			foreach ($members[$key]['duplicate_members'] as $duplicate_id_member => $duplicate_member)
1316
			{
1317
				if (in_array($duplicate_member['id'], $member_track))
1318
				{
1319
					unset($members[$key]['duplicate_members'][$duplicate_id_member]);
1320
					continue;
1321
				}
1322
1323
				$member_track[] = $duplicate_member['id'];
1324
			}
1325
		}
1326
}
1327
1328
/**
1329
 * Find members with a given IP (first, second, exact or "relaxed")
1330
 *
1331
 * @package Members
1332
 * @param string|string[] $ip1 An IP or an array of IPs
1333
 * @param string $match (optional, default 'exact') if the match should be exact
1334
 *                of "relaxed" (using LIKE)
1335
 * @param bool $ip2 (optional, default false) If the query should check IP2 as well
1336
 */
1337
function membersByIP($ip1, $match = 'exact', $ip2 = false)
1338
{
1339 6
	$db = database();
1340
1341 6
	$ip_params = array('ips' => array());
1342 6
	$ip_query = array();
1343 6
	foreach (array($ip1, $ip2) as $id => $ip)
1344
	{
1345 6
		if ($ip === false)
1346 6
			continue;
1347
1348 6
		if ($match === 'exact')
1349 6
			$ip_params['ips'] = array_merge($ip_params['ips'], (array) $ip);
1350
		else
1351
		{
1352 6
			$ip = (array) $ip;
1353 6
			foreach ($ip as $id_var => $ip_var)
1354
			{
1355 6
				$ip_var = str_replace('*', '%', $ip_var);
1356 6
				$ip_query[] = strpos($ip_var, '%') === false ? '= {string:ip_address_' . $id . '_' . $id_var . '}' : 'LIKE {string:ip_address_' . $id . '_' . $id_var . '}';
1357 6
				$ip_params['ip_address_' . $id . '_' . $id_var] = $ip_var;
1358 4
			}
1359
		}
1360 4
	}
1361
1362 6
	if ($match === 'exact')
1363 4
	{
1364 6
		$where = 'member_ip IN ({array_string:ips})';
1365 6
		if ($ip2 !== false)
1366 4
			$where .= '
1367 6
			OR member_ip2 IN ({array_string:ips})';
1368 4
	}
1369
	else
1370
	{
1371 6
		$where = 'member_ip ' . implode(' OR member_ip', $ip_query);
1372 6
		if ($ip2 !== false)
1373 4
			$where .= '
1374
			OR member_ip2 ' . implode(' OR member_ip', $ip_query);
1375
	}
1376
1377 6
	return $db->fetchQuery('
1378
		SELECT
1379
			id_member, member_name, email_address, member_ip, member_ip2, is_activated
1380
		FROM {db_prefix}members
1381 6
		WHERE ' . $where,
1382
		$ip_params
1383 4
	);
1384
}
1385
1386
/**
1387
 * Find out if there is another admin than the given user.
1388
 *
1389
 * @package Members
1390
 * @param int $memberID ID of the member, to compare with.
1391
 */
1392 View Code Duplication
function isAnotherAdmin($memberID)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1393
{
1394
	$db = database();
1395
1396
	$request = $db->query('', '
1397
		SELECT id_member
1398
		FROM {db_prefix}members
1399
		WHERE (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0)
1400
			AND id_member != {int:selected_member}
1401
		LIMIT 1',
1402
		array(
1403
			'admin_group' => 1,
1404
			'selected_member' => $memberID,
1405
		)
1406
	);
1407
	list ($another) = $db->fetch_row($request);
1408
	$db->free_result($request);
1409
1410
	return $another;
1411
}
1412
1413
/**
1414
 * This function retrieves a list of member ids based on a set of conditions
1415
 *
1416
 * @package Members
1417
 * @param mixed[]|string $query see prepareMembersByQuery
1418
 * @param mixed[] $query_params see prepareMembersByQuery
1419
 * @param bool $details if true returns additional member details (name, email, ip, etc.)
1420
 *             false will only return an array of member id's that match the conditions
1421
 * @param bool $only_active see prepareMembersByQuery
1422
 */
1423
function membersBy($query, $query_params, $details = false, $only_active = true)
1424
{
1425
	$db = database();
1426
1427
	$query_where = prepareMembersByQuery($query, $query_params, $only_active);
1428
1429
	// Lets see who we can find that meets the built up conditions
1430
	$members = array();
1431
	$request = $db->query('', '
1432
		SELECT id_member' . ($details ? ', member_name, real_name, email_address, member_ip, date_registered, last_login,
1433
				hide_email, posts, is_activated, real_name' : '') . '
1434
		FROM {db_prefix}members
1435
		WHERE ' . $query_where . (isset($query_params['start']) ? '
1436
		LIMIT {int:start}, {int:limit}' : '') . (!empty($query_params['order']) ? '
1437
		ORDER BY {raw:order}' : ''),
1438
		$query_params
1439
	);
1440
1441
	// Return all the details for each member found
1442
	if ($details)
1443
	{
1444
		while ($row = $db->fetch_assoc($request))
1445
			$members[$row['id_member']] = $row;
1446
	}
1447
	// Or just a int[] of found member id's
1448
	else
1449
	{
1450
		while ($row = $db->fetch_assoc($request))
1451
			$members[] = $row['id_member'];
1452
	}
1453
	$db->free_result($request);
1454
1455
	return $members;
1456
}
1457
1458
/**
1459
 * Counts the number of members based on conditions
1460
 *
1461
 * @package Members
1462
 * @param string[]|string $query see prepareMembersByQuery
1463
 * @param mixed[] $query_params see prepareMembersByQuery
1464
 * @param boolean $only_active see prepareMembersByQuery
1465
 */
1466
function countMembersBy($query, $query_params, $only_active = true)
1467
{
1468
	$db = database();
1469
1470
	$query_where = prepareMembersByQuery($query, $query_params, $only_active);
1471
1472
	$request = $db->query('', '
1473
		SELECT COUNT(*)
1474
		FROM {db_prefix}members
1475
		WHERE ' . $query_where,
1476
		$query_params
1477
	);
1478
1479
	list ($num_members) = $db->fetch_row($request);
1480
	$db->free_result($request);
1481
1482
	return $num_members;
1483
}
1484
1485
/**
1486
 * Builds the WHERE clause for the functions countMembersBy and membersBy
1487
 *
1488
 * @package Members
1489
 * @param mixed[]|string $query can be an array of "type" of conditions,
1490
 *             or a string used as raw query
1491
 *             or a string that represents one of the built-in conditions
1492
 *             like member_names, not_in_group, etc
1493
 * @param mixed[] $query_params is an array containing the parameters passed to the query
1494
 *             'start' and 'limit' used in LIMIT
1495
 *             'order' used raw in ORDER BY
1496
 *             others passed as query params
1497
 * @param bool $only_active only fetch active members
1498
 */
1499
function prepareMembersByQuery($query, &$query_params, $only_active = true)
1500
{
1501
	$allowed_conditions = array(
1502
		'member_ids'   => 'id_member IN ({array_int:member_ids})',
1503
		'member_names' => function (&$members)
1504
		{
1505
			$mem_query = array();
1506
1507
			foreach ($members['member_names'] as $key => $param)
1508
			{
1509
				$mem_query[] = (defined('DB_CASE_SENSITIVE') ? 'LOWER(real_name)' : 'real_name') . ' LIKE {string:member_names_' . $key . '}';
1510
				$members['member_names_' . $key] = defined('DB_CASE_SENSITIVE') ? strtolower($param) : $param;
1511
			}
1512
			return implode("\n\t\t\tOR ", $mem_query);
1513
		},
1514
		'not_in_group'     => '(id_group != {int:not_in_group} AND FIND_IN_SET({int:not_in_group}, additional_groups) = 0)',
1515
		'in_group'         => '(id_group = {int:in_group} OR FIND_IN_SET({int:in_group}, additional_groups) != 0)',
1516
		'in_group_primary' => 'id_group = {int:in_group_primary}',
1517
		'in_post_group'    => 'id_post_group = {int:in_post_group}',
1518
		'in_group_no_add'  => '(id_group = {int:in_group_no_add} AND FIND_IN_SET({int:in_group_no_add}, additional_groups) = 0)',
1519
	);
1520
1521
	// Are there multiple parts to this query
1522
	if (is_array($query))
1523
	{
1524
		$query_parts = array('or' => array(), 'and' => array());
1525
		foreach ($query as $type => $query_conditions)
1526
		{
1527
			if (is_array($query_conditions))
1528
			{
1529
				foreach ($query_conditions as $condition => $query_condition)
1530
				{
1531
					if ($query_condition == 'member_names')
1532
						$query_parts[$condition === 'or' ? 'or' : 'and'][] = $allowed_conditions[$query_condition]($query_params);
1533
					else
1534
						$query_parts[$condition === 'or' ? 'or' : 'and'][] = isset($allowed_conditions[$query_condition]) ? $allowed_conditions[$query_condition] : $query_condition;
1535
				}
1536
			}
1537
			elseif ($query == 'member_names')
1538
				$query_parts[$condition === 'or' ? 'or' : 'and'][] = $allowed_conditions[$query]($query_params);
0 ignored issues
show
Bug introduced by
The variable $condition seems to be defined by a foreach iteration on line 1529. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
1539
			else
1540
				$query_parts['and'][] = isset($allowed_conditions[$query]) ? $allowed_conditions[$query] : $query;
1541
		}
1542
1543
		if (!empty($query_parts['or']))
1544
			$query_parts['and'][] = implode("\n\t\t\tOR ", $query_parts['or']);
1545
1546
		$query_where = implode("\n\t\t\tAND ", $query_parts['and']);
1547
	}
1548
	// Is it one of our predefined querys like member_ids, member_names, etc
1549
	elseif (isset($allowed_conditions[$query]))
1550
	{
1551
		if ($query == 'member_names')
1552
			$query_where = $allowed_conditions[$query]($query_params);
1553
		else
1554
			$query_where = $allowed_conditions[$query];
1555
	}
1556
	// Something else, be careful ;)
1557
	else
1558
		$query_where = $query;
1559
1560
	// Lazy loading, our favorite
1561
	if (empty($query_where))
1562
		return false;
1563
1564
	// Only want active members
1565
	if ($only_active)
1566
	{
1567
		$query_where .= '
1568
			AND is_activated = {int:is_activated}';
1569
		$query_params['is_activated'] = 1;
1570
	}
1571
1572
	return $query_where;
1573
}
1574
1575
/**
1576
 * Retrieve administrators of the site.
1577
 *
1578
 * - The function returns basic information: name, language file.
1579
 * - It is used in personal messages reporting.
1580
 *
1581
 * @package Members
1582
 * @param int $id_admin = 0 if requested, only data about a specific admin is retrieved
1583
 */
1584
function admins($id_admin = 0)
1585
{
1586
	$db = database();
1587
1588
	// Now let's get out and loop through the admins.
1589
	$request = $db->query('', '
1590
		SELECT id_member, real_name, lngfile
1591
		FROM {db_prefix}members
1592
		WHERE (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0)
1593
			' . (empty($id_admin) ? '' : 'AND id_member = {int:specific_admin}') . '
1594
		ORDER BY real_name, lngfile',
1595
		array(
1596
			'admin_group' => 1,
1597
			'specific_admin' => isset($id_admin) ? (int) $id_admin : 0,
1598
		)
1599
	);
1600
1601
	$admins = array();
1602
	while ($row = $db->fetch_assoc($request))
1603
		$admins[$row['id_member']] = array($row['real_name'], $row['lngfile']);
1604
	$db->free_result($request);
1605
1606
	return $admins;
1607
}
1608
1609
/**
1610
 * Get the last known id_member
1611
 * @return int
1612
 */
1613 View Code Duplication
function maxMemberID()
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1614
{
1615
	$db = database();
1616
1617
	$request = $db->query('', '
1618
		SELECT MAX(id_member)
1619
		FROM {db_prefix}members',
1620
		array(
1621
		)
1622
	);
1623
	list ($max_id) = $db->fetch_row($request);
1624
	$db->free_result($request);
1625
1626
	return $max_id;
1627
}
1628
1629
/**
1630
 * Load some basic member information
1631
 *
1632
 * @package Members
1633
 * @param int[]|int $member_ids an array of member IDs or a single ID
1634
 * @param mixed[] $options an array of possible little alternatives, can be:
1635
 * - 'add_guest' (bool) to add a guest user to the returned array
1636
 * - 'limit' int if set overrides the default query limit
1637
 * - 'sort' (string) a column to sort the results
1638
 * - 'moderation' (bool) includes member_ip, id_group, additional_groups, last_login
1639
 * - 'authentication' (bool) includes secret_answer, secret_question, openid_uri,
1640
 *    is_activated, validation_code, passwd_flood
1641
 * - 'preferences' (bool) includes lngfile, mod_prefs, notify_types, signature
1642
 * @return array
1643
 */
1644
function getBasicMemberData($member_ids, $options = array())
1645
{
1646 16
	global $txt, $language;
1647
1648 16
	$db = database();
1649
1650 16
	$members = array();
1651
1652 16
	if (empty($member_ids))
1653 11
		return false;
1654
1655 16
	if (!is_array($member_ids))
1656 11
	{
1657 4
		$single = true;
1658 4
		$member_ids = array($member_ids);
1659 3
	}
1660
1661 16
	if (!empty($options['add_guest']))
1662 11
	{
1663
		$single = false;
1664
		// This is a guest...
1665
		$members[0] = array(
1666
			'id_member' => 0,
1667
			'member_name' => '',
1668
			'real_name' => $txt['guest_title'],
1669
			'email_address' => '',
1670
		);
1671
	}
1672
1673
	// Get some additional member info...
1674 16
	$request = $db->query('', '
1675 16
		SELECT id_member, member_name, real_name, email_address, hide_email, posts, id_theme' . (!empty($options['moderation']) ? ',
1676 16
		member_ip, id_group, additional_groups, last_login, id_post_group' : '') . (!empty($options['authentication']) ? ',
1677 16
		secret_answer, secret_question, openid_uri, is_activated, validation_code, passwd_flood' : '') . (!empty($options['preferences']) ? ',
1678 16
		lngfile, mod_prefs, notify_types, signature' : '') . '
1679
		FROM {db_prefix}members
1680
		WHERE id_member IN ({array_int:member_list})
1681 16
		' . (isset($options['sort']) ? '
1682 16
		ORDER BY {raw:sort}' : '') . '
1683 16
		LIMIT {int:limit}',
1684
		array(
1685 16
			'member_list' => $member_ids,
1686 16
			'limit' => isset($options['limit']) ? $options['limit'] : count($member_ids),
1687 16
			'sort' => isset($options['sort']) ? $options['sort'] : '',
1688
		)
1689 11
	);
1690 16
	while ($row = $db->fetch_assoc($request))
1691
	{
1692 16
		if (empty($row['lngfile']))
1693 16
			$row['lngfile'] = $language;
1694
1695 16
		if (!empty($single))
1696 12
			$members = $row;
1697
		else
1698 12
			$members[$row['id_member']] = $row;
1699 11
	}
1700 16
	$db->free_result($request);
1701
1702 16
	return $members;
1703
}
1704
1705
/**
1706
 * Counts all inactive members
1707
 *
1708
 * @package Members
1709
 * @return array $inactive_members
1710
 */
1711
function countInactiveMembers()
1712
{
1713
	$db = database();
1714
1715
	$inactive_members = array();
1716
1717
	$request = $db->query('', '
1718
		SELECT COUNT(*) AS total_members, is_activated
1719
		FROM {db_prefix}members
1720
		WHERE is_activated != {int:is_activated}
1721
		GROUP BY is_activated',
1722
		array(
1723
			'is_activated' => 1,
1724
		)
1725
	);
1726
1727
	while ($row = $db->fetch_assoc($request))
1728
		$inactive_members[$row['is_activated']] = $row['total_members'];
1729
	$db->free_result($request);
1730
1731
	return $inactive_members;
1732
}
1733
1734
/**
1735
 * Get the member's id and group
1736
 *
1737
 * @package Members
1738
 * @param string $name
1739
 * @param bool $flexible if true searches for both real_name and member_name (default false)
1740
 * @return integer
1741
 */
1742
function getMemberByName($name, $flexible = false)
1743
{
1744
	$db = database();
1745
1746
	$request = $db->query('', '
1747
		SELECT id_member, id_group
1748
		FROM {db_prefix}members
1749
		WHERE {raw:real_name} LIKE {string:name}' . ($flexible ? '
1750
			OR {raw:member_name} LIKE {string:name}' : '') . '
1751
		LIMIT 1',
1752
		array(
1753
			'name' => Util::strtolower($name),
1754
			'real_name' => defined('DB_CASE_SENSITIVE') ? 'LOWER(real_name)' : 'real_name',
1755
			'member_name' => defined('DB_CASE_SENSITIVE') ? 'LOWER(member_name)' : 'member_name',
1756
		)
1757
	);
1758
	if ($db->num_rows($request) == 0)
1759
		return false;
1760
	$member = $db->fetch_assoc($request);
1761
	$db->free_result($request);
1762
1763
	return $member;
1764
}
1765
1766
/**
1767
 * Finds a member from the database using supplied string as real_name
1768
 *
1769
 * - Optionally will only search/find the member in a buddy list
1770
 *
1771
 * @package Members
1772
 * @param string $search string to search real_name for like finds
1773
 * @param int[]|null $buddies
1774
 */
1775
function getMember($search, $buddies = array())
1776
{
1777
	$db = database();
1778
1779
	$xml_data = array(
1780
		'items' => array(
1781
			'identifier' => 'item',
1782
			'children' => array(),
1783
		),
1784
	);
1785
	// Find the member.
1786
	$xml_data['items']['children'] = $db->fetchQueryCallback('
1787
		SELECT id_member, real_name
1788
		FROM {db_prefix}members
1789
		WHERE {raw:real_name} LIKE {string:search}' . (!empty($buddies) ? '
1790
			AND id_member IN ({array_int:buddy_list})' : '') . '
1791
			AND is_activated IN ({array_int:activation_status})
1792
		ORDER BY LENGTH(real_name), real_name
1793
		LIMIT {int:limit}',
1794
		array(
1795
			'real_name' => defined('DB_CASE_SENSITIVE') ? 'LOWER(real_name)' : 'real_name',
1796
			'buddy_list' => $buddies,
1797
			'search' => Util::strtolower($search),
1798
			'activation_status' => array(1, 12),
1799
			'limit' => Util::strlen($search) <= 2 ? 100 : 200,
1800
		),
1801
		function ($row)
1802
		{
1803
			$row['real_name'] = strtr($row['real_name'], array('&amp;' => '&#038;', '&lt;' => '&#060;', '&gt;' => '&#062;', '&quot;' => '&#034;'));
1804
1805
			return array(
1806
				'attributes' => array(
1807
					'id' => $row['id_member'],
1808
				),
1809
				'value' => $row['real_name'],
1810
			);
1811
		}
1812
	);
1813
1814
	return $xml_data;
1815
}
1816
1817
/**
1818
 * Retrieves MemberData based on conditions
1819
 *
1820
 * @package Members
1821
 * @param mixed[] $conditions associative array holding the conditions for the WHERE clause of the query.
1822
 * Possible keys:
1823
 * - activated_status (boolean) must be present
1824
 * - time_before (integer)
1825
 * - members (array of integers)
1826
 * - member_greater (integer) a member id, it will be used to filter only members with id_member greater than this
1827
 * - group_list (array) array of group IDs
1828
 * - notify_announcements (integer)
1829
 * - order_by (string)
1830
 * - limit (int)
1831
 * @return array
1832
 */
1833
function retrieveMemberData($conditions)
1834
{
1835
	global $modSettings, $language;
1836
1837
	// We badly need this
1838
	assert(isset($conditions['activated_status']));
1839
1840
	$db = database();
1841
1842
	$available_conditions = array(
1843
		'time_before' => '
1844
				AND date_registered < {int:time_before}',
1845
		'members' => '
1846
				AND id_member IN ({array_int:members})',
1847
		'member_greater' => '
1848
				AND id_member > {int:member_greater}',
1849
		'member_greater_equal' => '
1850
				AND id_member >= {int:member_greater_equal}',
1851
		'member_lesser' => '
1852
				AND id_member < {int:member_lesser}',
1853
		'member_lesser_equal' => '
1854
				AND id_member <= {int:member_lesser_equal}',
1855
		'group_list' => '
1856
				AND (id_group IN ({array_int:group_list}) OR id_post_group IN ({array_int:group_list}) OR FIND_IN_SET({raw:additional_group_list}, additional_groups) != 0)',
1857
		'notify_announcements' => '
1858
				AND notify_announcements = {int:notify_announcements}'
1859
	);
1860
1861
	$query_cond = array();
1862
	foreach ($conditions as $key => $dummy)
1863
		if (isset($available_conditions[$key]))
1864
			$query_cond[] = $available_conditions[$key];
1865
1866
	if (isset($conditions['group_list']))
1867
		$conditions['additional_group_list'] = implode(', additional_groups) != 0 OR FIND_IN_SET(', $conditions['group_list']);
1868
1869
	$data = array();
1870
1871
	if (!isset($conditions['order_by']))
1872
		$conditions['order_by'] = 'lngfile';
1873
1874
	$limit = (isset($conditions['limit'])) ? '
1875
		LIMIT {int:limit}' : '';
1876
1877
	// Get information on each of the members, things that are important to us, like email address...
1878
	$request = $db->query('', '
1879
		SELECT id_member, member_name, real_name, email_address, validation_code, lngfile
1880
		FROM {db_prefix}members
1881
		WHERE is_activated = {int:activated_status}' . implode('', $query_cond) . '
1882
		ORDER BY {raw:order_by}' . $limit,
1883
		$conditions
1884
	);
1885
1886
	$data['member_count'] = $db->num_rows($request);
1887
1888
	if ($data['member_count'] == 0)
1889
		return $data;
1890
1891
	// Fill the info array.
1892
	while ($row = $db->fetch_assoc($request))
1893
	{
1894
		$data['members'][] = $row['id_member'];
1895
		$data['member_info'][] = array(
1896
			'id' => $row['id_member'],
1897
			'username' => $row['member_name'],
1898
			'name' => $row['real_name'],
1899
			'email' => $row['email_address'],
1900
			'language' => empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile'],
1901
			'code' => $row['validation_code']
1902
		);
1903
	}
1904
	$db->free_result($request);
1905
1906
	return $data;
1907
}
1908
1909
/**
1910
 * Activate members
1911
 *
1912
 * @package Members
1913
 * @param mixed[] $conditions associative array holding the conditions for the WHERE clause of the query.
1914
 * Possible keys:
1915
 * - activated_status (boolean) must be present
1916
 * - time_before (integer)
1917
 * - members (array of integers)
1918
 */
1919
function approveMembers($conditions)
1920
{
1921
	$db = database();
1922
1923
	// This shall be present
1924
	assert(isset($conditions['activated_status']));
1925
1926
	$available_conditions = array(
1927
		'time_before' => '
1928
				AND date_registered < {int:time_before}',
1929
		'members' => '
1930
				AND id_member IN ({array_int:members})',
1931
	);
1932
1933
	// @todo maybe an hook here?
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...
1934
	$query_cond = array();
1935
	$query = false;
1936
	foreach ($conditions as $key => $dummy)
1937
	{
1938
		if (isset($available_conditions[$key]))
1939
		{
1940
			if ($key === 'time_before')
1941
				$query = true;
1942
			$query_cond[] = $available_conditions[$key];
1943
		}
1944
	}
1945
1946
	if ($query)
1947
	{
1948
		$data = retrieveMemberData($conditions);
1949
		$members_id = array();
1950
		foreach ($data['member_info'] as $member)
1951
			$members_id[] = $member['username'];
1952
	}
1953
	else
1954
	{
1955
		$members_id = $conditions['members'];
1956
	}
1957
1958
	$conditions['is_activated'] = $conditions['activated_status'] >= 10 ? 11 : 1;
1959
	$conditions['blank_string'] = '';
1960
1961
	// Approve/activate this member.
1962
	$db->query('', '
1963
		UPDATE {db_prefix}members
1964
		SET validation_code = {string:blank_string}, is_activated = {int:is_activated}
1965
		WHERE is_activated = {int:activated_status}' . implode('', $query_cond),
1966
		$conditions
1967
	);
1968
1969
	// Let the integration know that they've been activated!
1970
	foreach ($members_id as $member_id)
1971
		call_integration_hook('integrate_activate', array($member_id, $conditions['activated_status'], $conditions['is_activated']));
1972
1973
	return $conditions['is_activated'];
1974
}
1975
1976
/**
1977
 * Set these members for activation
1978
 *
1979
 * @package Members
1980
 * @param mixed[] $conditions associative array holding the conditions for the  WHERE clause of the query.
1981
 * Possible keys:
1982
 * - selected_member (integer) must be present
1983
 * - activated_status (boolean) must be present
1984
 * - validation_code (string) must be present
1985
 * - members (array of integers)
1986
 * - time_before (integer)
1987
 */
1988
function enforceReactivation($conditions)
1989
{
1990
	$db = database();
1991
1992
	// We need all of these
1993
	assert(isset($conditions['activated_status']));
1994
	assert(isset($conditions['selected_member']));
1995
	assert(isset($conditions['validation_code']));
1996
1997
	$conditions['validation_code'] = substr(hash('sha256', $conditions['validation_code']), 0, 10);
1998
1999
	$available_conditions = array(
2000
		'time_before' => '
2001
				AND date_registered < {int:time_before}',
2002
		'members' => '
2003
				AND id_member IN ({array_int:members})',
2004
	);
2005
2006
	$query_cond = array();
2007
	foreach ($conditions as $key => $dummy)
2008
		$query_cond[] = $available_conditions[$key];
2009
2010
	$conditions['not_activated'] = 0;
2011
2012
	$db->query('', '
2013
		UPDATE {db_prefix}members
2014
		SET validation_code = {string:validation_code}, is_activated = {int:not_activated}
2015
		WHERE is_activated = {int:activated_status}
2016
			' . implode('', $query_cond) . '
2017
			AND id_member = {int:selected_member}',
2018
		$conditions
2019
	);
2020
}
2021
2022
/**
2023
 * Count members of a given group
2024
 *
2025
 * @package Members
2026
 * @param int $id_group
2027
 * @return int
2028
 */
2029 View Code Duplication
function countMembersInGroup($id_group = 0)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2030
{
2031
	$db = database();
2032
2033
	// Determine the number of ungrouped members.
2034
	$request = $db->query('', '
2035
		SELECT COUNT(*)
2036
		FROM {db_prefix}members
2037
		WHERE id_group = {int:group}',
2038
		array(
2039
			'group' => $id_group,
2040
		)
2041
	);
2042
	list ($num_members) = $db->fetch_row($request);
2043
	$db->free_result($request);
2044
2045
	return $num_members;
2046
}
2047
2048
/**
2049
 * Get the total amount of members online.
2050
 *
2051
 * @package Members
2052
 * @param string[] $conditions
2053
 * @return int
2054
 */
2055 View Code Duplication
function countMembersOnline($conditions)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2056
{
2057
	$db = database();
2058
2059
	$request = $db->query('', '
2060
		SELECT COUNT(*)
2061
		FROM {db_prefix}log_online AS lo
2062
			LEFT JOIN {db_prefix}members AS mem ON (lo.id_member = mem.id_member)' . (!empty($conditions) ? '
2063
		WHERE ' . implode(' AND ', $conditions) : ''),
2064
		array(
2065
		)
2066
	);
2067
	list ($totalMembers) = $db->fetch_row($request);
2068
	$db->free_result($request);
2069
2070
	return $totalMembers;
2071
}
2072
2073
/**
2074
 * Look for people online, provided they don't mind if you see they are.
2075
 *
2076
 * @package Members
2077
 * @param string[] $conditions
2078
 * @param string $sort_method
2079
 * @param string $sort_direction
2080
 * @param int $start
2081
 * @return array
2082
 */
2083
function onlineMembers($conditions, $sort_method, $sort_direction, $start)
2084
{
2085
	global $modSettings;
2086
2087
	$db = database();
2088
2089
	return $db->fetchQuery('
2090
		SELECT
2091
			lo.log_time, lo.id_member, lo.url, lo.ip, mem.real_name,
2092
			lo.session, mg.online_color, COALESCE(mem.show_online, 1) AS show_online,
2093
			lo.id_spider
2094
		FROM {db_prefix}log_online AS lo
2095
			LEFT JOIN {db_prefix}members AS mem ON (lo.id_member = mem.id_member)
2096
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:regular_member} THEN mem.id_post_group ELSE mem.id_group END)' . (!empty($conditions) ? '
2097
		WHERE ' . implode(' AND ', $conditions) : '') . '
2098
		ORDER BY {raw:sort_method} {raw:sort_direction}
2099
		LIMIT {int:offset}, {int:limit}',
2100
		array(
2101
			'regular_member' => 0,
2102
			'sort_method' => $sort_method,
2103
			'sort_direction' => $sort_direction == 'up' ? 'ASC' : 'DESC',
2104
			'offset' => $start,
2105
			'limit' => $modSettings['defaultMaxMembers'],
2106
		)
2107
	);
2108
}
2109
2110
/**
2111
 * Check if the OpenID URI is already registered for an existing member
2112
 *
2113
 * @package Members
2114
 * @param string $url
2115
 * @return array
2116
 */
2117
function memberExists($url)
2118
{
2119
	$db = database();
2120
2121
	$request = $db->query('', '
2122
		SELECT mem.id_member, mem.member_name
2123
		FROM {db_prefix}members AS mem
2124
		WHERE mem.openid_uri = {string:openid_uri}',
2125
		array(
2126
			'openid_uri' => $url,
2127
		)
2128
	);
2129
	$member = $db->fetch_assoc($request);
2130
	$db->free_result($request);
2131
2132
	return $member;
2133
}
2134
2135
/**
2136
 * Find the most recent members
2137
 *
2138
 * @package Members
2139
 * @param int $limit
2140
 */
2141
function recentMembers($limit)
2142
{
2143
	$db = database();
2144
2145
	// Find the most recent members.
2146
	return $db->fetchQuery('
2147
		SELECT id_member, member_name, real_name, date_registered, last_login
2148
		FROM {db_prefix}members
2149
		ORDER BY id_member DESC
2150
		LIMIT {int:limit}',
2151
		array(
2152
			'limit' => $limit,
2153
		)
2154
	);
2155
}
2156
2157
/**
2158
 * Assign membergroups to members.
2159
 *
2160
 * @package Members
2161
 * @param int $member
2162
 * @param int $primary_group
2163
 * @param int[] $additional_groups
2164
 */
2165
function assignGroupsToMember($member, $primary_group, $additional_groups)
2166
{
2167
	updateMemberData($member, array('id_group' => $primary_group, 'additional_groups' => implode(',', $additional_groups)));
2168
}
2169
2170
/**
2171
 * Get a list of members from a membergroups request.
2172
 *
2173
 * @package Members
2174
 * @param int[] $groups
2175
 * @param string $where
2176
 * @param boolean $change_groups = false
2177
 * @return mixed
2178
 */
2179
function getConcernedMembers($groups, $where, $change_groups = false)
2180
{
2181
	global $modSettings, $language;
2182
2183
	$db = database();
2184
2185
		// Get the details of all the members concerned...
2186
	$request = $db->query('', '
2187
		SELECT lgr.id_request, lgr.id_member, lgr.id_group, mem.email_address, mem.id_group AS primary_group,
2188
			mem.additional_groups AS additional_groups, mem.lngfile, mem.member_name, mem.notify_types,
2189
			mg.hidden, mg.group_name
2190
		FROM {db_prefix}log_group_requests AS lgr
2191
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = lgr.id_member)
2192
			INNER JOIN {db_prefix}membergroups AS mg ON (mg.id_group = lgr.id_group)
2193
		WHERE ' . $where . '
2194
			AND lgr.id_request IN ({array_int:request_list})
2195
		ORDER BY mem.lngfile',
2196
		array(
2197
			'request_list' => $groups,
2198
		)
2199
	);
2200
2201
	$email_details = array();
2202
	$group_changes = array();
2203
2204
	while ($row = $db->fetch_assoc($request))
2205
	{
2206
		$row['lngfile'] = empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile'];
2207
2208
		// If we are approving work out what their new group is.
2209
		if ($change_groups)
2210
		{
2211
			// For people with more than one request at once.
2212
			if (isset($group_changes[$row['id_member']]))
2213
			{
2214
				$row['additional_groups'] = $group_changes[$row['id_member']]['add'];
2215
				$row['primary_group'] = $group_changes[$row['id_member']]['primary'];
2216
			}
2217
			else
2218
				$row['additional_groups'] = explode(',', $row['additional_groups']);
2219
				// Don't have it already?
2220
			if ($row['primary_group'] == $row['id_group'] || in_array($row['id_group'], $row['additional_groups']))
2221
				continue;
2222
				// Should it become their primary?
2223
			if ($row['primary_group'] == 0 && $row['hidden'] == 0)
2224
				$row['primary_group'] = $row['id_group'];
2225
			else
2226
				$row['additional_groups'][] = $row['id_group'];
2227
2228
			// Add them to the group master list.
2229
			$group_changes[$row['id_member']] = array(
2230
				'primary' => $row['primary_group'],
2231
				'add' => $row['additional_groups'],
2232
			);
2233
		}
2234
2235
		// Add required information to email them.
2236
		if ($row['notify_types'] != 4)
2237
			$email_details[] = array(
2238
				'rid' => $row['id_request'],
2239
				'member_id' => $row['id_member'],
2240
				'member_name' => $row['member_name'],
2241
				'group_id' => $row['id_group'],
2242
				'group_name' => $row['group_name'],
2243
				'email' => $row['email_address'],
2244
				'language' => $row['lngfile'],
2245
			);
2246
	}
2247
	$db->free_result($request);
2248
2249
	$output = array(
2250
		'email_details' => $email_details,
2251
		'group_changes' => $group_changes
2252
	);
2253
2254
	return $output;
2255
}
2256
2257
/**
2258
 * Determine if the current user ($user_info) can contact another user ($who)
2259
 *
2260
 * @package Members
2261
 * @param int $who The id of the user to contact
2262
 */
2263
function canContact($who)
2264
{
2265
	global $user_info;
2266
2267
	$db = database();
2268
2269
	$request = $db->query('', '
2270
		SELECT receive_from, buddy_list, pm_ignore_list
2271
		FROM {db_prefix}members
2272
		WHERE id_member = {int:member}',
2273
		array(
2274
			'member' => $who,
2275
		)
2276
	);
2277
	list ($receive_from, $buddies, $ignore) = $db->fetch_row($request);
2278
	$db->free_result($request);
2279
2280
	$buddy_list = array_map('intval', explode(',', $buddies));
2281
	$ignore_list = array_map('intval', explode(',', $ignore));
2282
2283
	// 0 = all members
2284
	if ($receive_from == 0)
2285
		return true;
2286
	// 1 = all except ignore
2287
	elseif ($receive_from == 1)
2288
		return !(!empty($ignore_list) && in_array($user_info['id'], $ignore_list));
2289
	// 2 = buddies and admin
2290
	elseif ($receive_from == 2)
2291
		return ($user_info['is_admin'] || (!empty($buddy_list) && in_array($user_info['id'], $buddy_list)));
2292
	// 3 = admin only
2293
	else
2294
		return (bool) $user_info['is_admin'];
2295
}
2296
2297
/**
2298
 * This function updates the latest member, the total membercount, and the
2299
 * number of unapproved members.
2300
 *
2301
 * - It also only counts approved members when approval is on,
2302
 * but is much more efficient with it off.
2303
 *
2304
 * @package Members
2305
 * @param integer|null $id_member = null If not an integer reload from the database
2306
 * @param string|null $real_name = null
2307
 */
2308
function updateMemberStats($id_member = null, $real_name = null)
2309
{
2310 8
	global $modSettings;
2311
2312 8
	$db = database();
2313
2314
	$changes = array(
2315 8
		'memberlist_updated' => time(),
2316 6
	);
2317
2318
	// #1 latest member ID, #2 the real name for a new registration.
2319 8
	if (is_int($id_member))
2320 6
	{
2321 4
		$changes['latestMember'] = $id_member;
2322 4
		$changes['latestRealName'] = $real_name;
2323
2324 4
		updateSettings(array('totalMembers' => true), true);
2325 3
	}
2326
	// We need to calculate the totals.
2327
	else
2328
	{
2329
		// Update the latest activated member (highest id_member) and count.
2330 4
		$request = $db->query('', '
2331
			SELECT COUNT(*), MAX(id_member)
2332
			FROM {db_prefix}members
2333 4
			WHERE is_activated = {int:is_activated}',
2334
			array(
2335 4
				'is_activated' => 1,
2336
			)
2337 3
		);
2338 4
		list ($changes['totalMembers'], $changes['latestMember']) = $db->fetch_row($request);
2339 4
		$db->free_result($request);
2340
2341
		// Get the latest activated member's display name.
2342 4
		$request = getBasicMemberData((int) $changes['latestMember']);
2343 4
		$changes['latestRealName'] = $request['real_name'];
2344
2345
		// Are we using registration approval?
2346 4
		if ((!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion']))
2347 3
		{
2348
			// Update the amount of members awaiting approval - ignoring COPPA accounts, as you can't approve them until you get permission.
2349
			$request = $db->query('', '
2350
				SELECT COUNT(*)
2351
				FROM {db_prefix}members
2352
				WHERE is_activated IN ({array_int:activation_status})',
2353
				array(
2354
					'activation_status' => array(3, 4),
2355
				)
2356
			);
2357
			list ($changes['unapprovedMembers']) = $db->fetch_row($request);
2358
			$db->free_result($request);
2359
		}
2360
	}
2361
2362 8
	updateSettings($changes);
2363 8
}
2364
2365
/**
2366
 * Builds the 'query_see_board' element for a certain member
2367
 *
2368
 * @package Members
2369
 * @param integer $id_member a valid member id
2370
 */
2371
function memberQuerySeeBoard($id_member)
2372
{
2373
	global $modSettings;
2374
2375
	$member = getBasicMemberData($id_member, array('moderation' => true));
2376
2377 View Code Duplication
	if (empty($member['additional_groups']))
2378
		$groups = array($member['id_group'], $member['id_post_group']);
2379
	else
2380
		$groups = array_merge(
2381
			array($member['id_group'], $member['id_post_group']),
2382
			explode(',', $member['additional_groups'])
2383
		);
2384
2385
	foreach ($groups as $k => $v)
2386
		$groups[$k] = (int) $v;
2387
	$groups = array_unique($groups);
2388
2389
	if (in_array(1, $groups))
2390
		return '1=1';
2391
	else
2392
	{
2393
		require_once(SUBSDIR . '/Boards.subs.php');
2394
2395
		$boards_mod = boardsModerated($id_member);
2396
		$mod_query = empty($boards_mod) ? '' : ' OR b.id_board IN (' . implode(',', $boards_mod) . ')';
2397
2398
		return '((FIND_IN_SET(' . implode(', b.member_groups) != 0 OR FIND_IN_SET(', $groups) . ', b.member_groups) != 0)' . (!empty($modSettings['deny_boards_access']) ? ' AND (FIND_IN_SET(' . implode(', b.deny_member_groups) = 0 AND FIND_IN_SET(', $groups) . ', b.deny_member_groups) = 0)' : '') . $mod_query . ')';
2399
	}
2400
}
2401
2402
/**
2403
 * Updates the columns in the members table.
2404
 *
2405
 * What it does:
2406
 *
2407
 * - Assumes the data has been htmlspecialchar'd, no sanitation is performed on the data.
2408
 * - This function should be used whenever member data needs to be updated in place of an UPDATE query.
2409
 * - $data is an associative array of the columns to be updated and their respective values.
2410
 * any string values updated should be quoted and slashed.
2411
 * - The value of any column can be '+' or '-', which mean 'increment' and decrement, respectively.
2412
 * - If the member's post number is updated, updates their post groups.
2413
 *
2414
 * @param int[]|int $members An array of member ids
2415
 * @param mixed[] $data An associative array of the columns to be updated and their respective values.
2416
 */
2417
function updateMemberData($members, $data)
2418
{
2419 54
	global $modSettings, $user_info;
2420
2421 54
	$db = database();
2422
2423 54
	$parameters = array();
2424 54
	if (is_array($members))
2425 36
	{
2426
		$condition = 'id_member IN ({array_int:members})';
2427
		$parameters['members'] = $members;
2428
	}
2429 54
	elseif ($members === null)
2430
		$condition = '1=1';
2431
	else
2432
	{
2433 54
		$condition = 'id_member = {int:member}';
2434 54
		$parameters['member'] = $members;
2435
	}
2436
2437
	// Everything is assumed to be a string unless it's in the below.
2438
	$knownInts = array(
2439 54
		'date_registered', 'posts', 'id_group', 'last_login', 'personal_messages', 'unread_messages', 'mentions',
2440 36
		'new_pm', 'pm_prefs', 'hide_email', 'show_online', 'pm_email_notify', 'receive_from', 'karma_good', 'karma_bad',
2441 36
		'notify_announcements', 'notify_send_body', 'notify_regularity', 'notify_types',
2442 36
		'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning', 'likes_given',
2443 36
		'likes_received', 'enable_otp', 'otp_used'
2444 36
	);
2445
	$knownFloats = array(
2446 54
		'time_offset',
2447 36
	);
2448
2449 54
	if (!empty($modSettings['integrate_change_member_data']))
2450 36
	{
2451
		// Only a few member variables are really interesting for integration.
2452
		$integration_vars = array(
2453
			'member_name',
2454
			'real_name',
2455
			'email_address',
2456
			'id_group',
2457
			'birthdate',
2458
			'website_title',
2459
			'website_url',
2460
			'hide_email',
2461
			'time_format',
2462
			'time_offset',
2463
			'avatar',
2464
			'lngfile',
2465
		);
2466
		$vars_to_integrate = array_intersect($integration_vars, array_keys($data));
2467
2468
		// Only proceed if there are any variables left to call the integration function.
2469
		if (count($vars_to_integrate) != 0)
2470
		{
2471
			// Fetch a list of member_names if necessary
2472
			if ((!is_array($members) && $members === $user_info['id']) || (is_array($members) && count($members) == 1 && in_array($user_info['id'], $members)))
2473
				$member_names = array($user_info['username']);
2474
			else
2475
			{
2476
				$member_names = $db->fetchQueryCallback('
2477
					SELECT member_name
2478
					FROM {db_prefix}members
2479
					WHERE ' . $condition,
2480
					$parameters,
2481
					function ($row)
2482
					{
2483
						return $row['member_name'];
2484
					}
2485
				);
2486
			}
2487
2488
			if (!empty($member_names))
2489
				foreach ($vars_to_integrate as $var)
2490
					call_integration_hook('integrate_change_member_data', array($member_names, &$var, &$data[$var], &$knownInts, &$knownFloats));
2491
		}
2492
	}
2493
2494 54
	$setString = '';
2495 54
	foreach ($data as $var => $val)
2496
	{
2497 54
		$type = 'string';
2498
2499 54 View Code Duplication
		if (in_array($var, $knownInts))
2500 54
			$type = 'int';
2501
		elseif (in_array($var, $knownFloats))
2502
			$type = 'float';
2503
		elseif ($var == 'birthdate')
2504
			$type = 'date';
2505
2506
		// Doing an increment?
2507 54
		if ($type == 'int' && ($val === '+' || $val === '-'))
2508 36
		{
2509 24
			$val = $var . ' ' . $val . ' 1';
2510 24
			$type = 'raw';
2511 16
		}
2512
2513
		// Ensure posts, personal_messages, and unread_messages don't overflow or underflow.
2514 54
		if (in_array($var, array('posts', 'personal_messages', 'unread_messages')))
2515 36
		{
2516 36
			if (preg_match('~^' . $var . ' (\+ |- |\+ -)([\d]+)~', $val, $match))
2517 24
			{
2518 36
				if ($match[1] != '+ ')
2519 36
					$val = 'CASE WHEN ' . $var . ' <= ' . abs($match[2]) . ' THEN 0 ELSE ' . $val . ' END';
2520 36
				$type = 'raw';
2521 24
			}
2522 24
		}
2523
2524 54
		$setString .= ' ' . $var . ' = {' . $type . ':p_' . $var . '},';
2525 54
		$parameters['p_' . $var] = $val;
2526 36
	}
2527
2528 54
	$db->query('', '
2529
		UPDATE {db_prefix}members
2530 54
		SET' . substr($setString, 0, -1) . '
2531 54
		WHERE ' . $condition,
2532
		$parameters
2533 36
	);
2534
2535 54
	require_once(SUBSDIR . '/Membergroups.subs.php');
2536 54
	updatePostGroupStats($members, array_keys($data));
2537
2538 54
	$cache = Cache::instance();
2539
2540
	// Clear any caching?
2541 54
	if ($cache->levelHigherThan(1) && !empty($members))
2542 36
	{
2543
		if (!is_array($members))
2544
			$members = array($members);
2545
2546
		foreach ($members as $member)
2547
		{
2548
			if ($cache->levelHigherThan(2))
2549
			{
2550
				$cache->remove('member_data-profile-' . $member);
2551
				$cache->remove('member_data-normal-' . $member);
2552
				$cache->remove('member_data-minimal-' . $member);
2553
			}
2554
2555
			$cache->remove('user_settings-' . $member);
2556
		}
2557
	}
2558 54
}
2559
2560
/**
2561
 * Loads members who are associated with an ip address
2562
 *
2563
 * @param string $ip_string raw value to use in where clause
2564
 * @param string $ip_var
2565
 */
2566
function loadMembersIPs($ip_string, $ip_var)
2567
{
2568
	global $scripturl;
2569
2570
	$db = database();
2571
2572
	$request = $db->query('', '
2573
		SELECT
2574
			id_member, real_name AS display_name, member_ip
2575
		FROM {db_prefix}members
2576
		WHERE member_ip ' . $ip_string,
2577
		array(
2578
			'ip_address' => $ip_var,
2579
		)
2580
	);
2581
	$ips = array();
2582 View Code Duplication
	while ($row = $db->fetch_assoc($request))
2583
		$ips[$row['member_ip']][] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>';
2584
	$db->free_result($request);
2585
2586
	ksort($ips);
2587
2588
	return $ips;
2589
}
2590