Completed
Pull Request — development (#2979)
by Stephen
08:55
created

Members.subs.php ➔ membersByIP()   C

Complexity

Conditions 9
Paths 24

Size

Total Lines 48
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 9.0033

Importance

Changes 0
Metric Value
cc 9
eloc 27
nc 24
nop 3
dl 0
loc 48
rs 5.5102
c 0
b 0
f 0
ccs 28
cts 29
cp 0.9655
crap 9.0033
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
		)
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.
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 1
	global $scripturl, $txt;
480
481 1
	$db = database();
482
483 1
	loadLanguage('Login');
484
485
	// We'll need some external functions.
486 1
	require_once(SUBSDIR . '/Auth.subs.php');
487 1
	require_once(SUBSDIR . '/Mail.subs.php');
488
489
	// Put any errors in here.
490 1
	$reg_errors = ElkArte\Errors\ErrorContext::context($ErrorContext, 0);
491
492
	// What method of authorization are we going to use?
493 1
	if (empty($regOptions['auth_method']) || !in_array($regOptions['auth_method'], array('password', 'openid')))
494 1
	{
495
		if (!empty($regOptions['openid']))
496
			$regOptions['auth_method'] = 'openid';
497
		else
498
			$regOptions['auth_method'] = 'password';
499
	}
500
501
	// Spaces and other odd characters are evil...
502 1
	$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 1
	if (!Data_Validator::is_valid($regOptions, array('email' => 'valid_email|required|max_length[255]'), array('email' => 'trim')))
506 1
		$reg_errors->addError('bad_email');
507
508 1
	validateUsername(0, $regOptions['username'], $ErrorContext, !empty($regOptions['check_reserved_name']));
509
510
	// Generate a validation code if it's supposed to be emailed.
511 1
	$validation_code = $regOptions['require'] === 'activation' ? generateValidationCode(14) : '';
512
513
	// Does the first password match the second?
514 1
	if ($regOptions['password'] != $regOptions['password_check'] && $regOptions['auth_method'] == 'password')
515 1
		$reg_errors->addError('passwords_dont_match');
516
517
	// That's kind of easy to guess...
518 1
	if ($regOptions['password'] == '')
519 1
	{
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 1
	if (!empty($regOptions['check_password_strength']) && $regOptions['password'] != '')
528 1
	{
529 1
		$passwordError = validatePassword($regOptions['password'], $regOptions['username'], array($regOptions['email']));
530
531
		// Password isn't legal?
532 1
		if ($passwordError !== null)
533 1
			$reg_errors->addError('profile_error_password_' . $passwordError);
534 1
	}
535
536
	// @todo move to controller
537
	// You may not be allowed to register this email.
538 1
	if (!empty($regOptions['check_email_ban']))
539 1
		isBannedEmail($regOptions['email'], 'cannot_register', $txt['ban_register_prohibited']);
540
541
	// Check if the email address is in use.
542 1
	if (userByEmail($regOptions['email'], $regOptions['username']))
543 1
	{
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 1
	call_integration_hook('integrate_register_check', array(&$regOptions, &$reg_errors));
549
550
	// If there's any errors left return them at once!
551 1
	if ($reg_errors->hasErrors())
552 1
		return false;
553
554
	$reservedVars = array(
555 1
		'actual_theme_url',
556 1
		'actual_images_url',
557 1
		'base_theme_dir',
558 1
		'base_theme_url',
559 1
		'default_images_url',
560 1
		'default_theme_dir',
561 1
		'default_theme_url',
562 1
		'default_template',
563 1
		'images_url',
564 1
		'number_recent_posts',
565 1
		'smiley_sets_default',
566 1
		'theme_dir',
567 1
		'theme_id',
568 1
		'theme_layers',
569 1
		'theme_templates',
570 1
		'theme_url',
571 1
	);
572
573
	// Can't change reserved vars.
574 1
	if (isset($regOptions['theme_vars']) && count(array_intersect(array_keys($regOptions['theme_vars']), $reservedVars)) != 0)
575 1
		throw new Elk_Exception('no_theme');
576
577 1
	$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 1
	$password = $regOptions['password'];
584
585
	// Some of these might be overwritten. (the lower ones that are in the arrays below.)
586 1
	$regOptions['register_vars'] = array(
587 1
		'member_name' => $regOptions['username'],
588 1
		'email_address' => $regOptions['email'],
589 1
		'passwd' => validateLoginPassword($password, '', $regOptions['username'], true),
590 1
		'password_salt' => $tokenizer->generate_hash(4),
591 1
		'posts' => 0,
592 1
		'date_registered' => !empty($regOptions['time']) ? $regOptions['time'] : time(),
593 1
		'member_ip' => $regOptions['interface'] == 'admin' ? '127.0.0.1' : $regOptions['ip'],
594 1
		'member_ip2' => $regOptions['interface'] == 'admin' ? '127.0.0.1' : $regOptions['ip2'],
595 1
		'validation_code' => substr(hash('sha256', $validation_code), 0, 10),
596 1
		'real_name' => !empty($regOptions['real_name']) ? $regOptions['real_name'] : $regOptions['username'],
597 1
		'pm_email_notify' => 1,
598 1
		'id_theme' => 0,
599 1
		'id_post_group' => 4,
600 1
		'lngfile' => '',
601 1
		'buddy_list' => '',
602 1
		'pm_ignore_list' => '',
603 1
		'message_labels' => '',
604 1
		'website_title' => '',
605 1
		'website_url' => '',
606 1
		'time_format' => '',
607 1
		'signature' => '',
608 1
		'avatar' => '',
609 1
		'usertitle' => '',
610 1
		'secret_question' => '',
611 1
		'secret_answer' => '',
612 1
		'additional_groups' => '',
613 1
		'ignore_boards' => '',
614 1
		'smiley_set' => '',
615 1
		'openid_uri' => (!empty($regOptions['openid']) ? $regOptions['openid'] : ''),
616 1
		'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 1
	if ($regOptions['require'] == 'coppa')
621 1
	{
622
		$regOptions['register_vars']['is_activated'] = 5;
623
		// @todo This should be changed.  To what should be it be changed??
624
		$regOptions['register_vars']['validation_code'] = '';
625
	}
626
	// Maybe it can be activated right away?
627 1
	elseif ($regOptions['require'] == 'nothing')
628 1
		$regOptions['register_vars']['is_activated'] = 1;
629
	// Maybe it must be activated by email?
630
	elseif ($regOptions['require'] == 'activation')
631
		$regOptions['register_vars']['is_activated'] = 0;
632
	// Otherwise it must be awaiting approval!
633
	else
634
		$regOptions['register_vars']['is_activated'] = 3;
635
636 1
	if (isset($regOptions['memberGroup']))
637 1
	{
638 1
		require_once(SUBSDIR . '/Membergroups.subs.php');
639
640
		// Make sure the id_group will be valid, if this is an administrator.
641 1
		$regOptions['register_vars']['id_group'] = $regOptions['memberGroup'] == 1 && !allowedTo('admin_forum') ? 0 : $regOptions['memberGroup'];
642
643
		// Check if this group is assignable.
644 1
		$unassignableGroups = getUnassignableGroups(allowedTo('admin_forum'));
645
646 1
		if (in_array($regOptions['register_vars']['id_group'], $unassignableGroups))
647 1
			$regOptions['register_vars']['id_group'] = 0;
648 1
	}
649
650
	// Integrate optional member settings to be set.
651 1
	if (!empty($regOptions['extra_register_vars']))
652 1
		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 1
	$theme_vars = array();
657 1
	if (!empty($regOptions['theme_vars']))
658 1
		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 1
		'date_registered', 'posts', 'id_group', 'last_login', 'personal_messages', 'unread_messages', 'notifications',
664 1
		'new_pm', 'pm_prefs', 'hide_email', 'show_online', 'pm_email_notify', 'karma_good', 'karma_bad',
665 1
		'notify_announcements', 'notify_send_body', 'notify_regularity', 'notify_types',
666 1
		'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning',
667 1
	);
668
	$knownFloats = array(
669 1
		'time_offset',
670 1
	);
671
672
	// Call an optional function to validate the users' input.
673 1
	call_integration_hook('integrate_register', array(&$regOptions, &$theme_vars, &$knownInts, &$knownFloats));
674
675 1
	$column_names = array();
676 1
	$values = array();
677 1
	foreach ($regOptions['register_vars'] as $var => $val)
678
	{
679 1
		$type = 'string';
680 1 View Code Duplication
		if (in_array($var, $knownInts))
681 1
			$type = 'int';
682 1
		elseif (in_array($var, $knownFloats))
683
			$type = 'float';
684 1
		elseif ($var == 'birthdate')
685
			$type = 'date';
686
687 1
		$column_names[$var] = $type;
688 1
		$values[$var] = $val;
689 1
	}
690
691
	// Register them into the database.
692 1
	$db->insert('',
693 1
		'{db_prefix}members',
694 1
		$column_names,
695 1
		$values,
696 1
		array('id_member')
697 1
	);
698 1
	$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 1
	if ($regOptions['register_vars']['is_activated'] == 1)
702 1
		updateMemberStats($memberID, $regOptions['register_vars']['real_name']);
703
	else
704
		updateMemberStats();
705
706
	// @todo there's got to be a method that does this
707
	// Theme variables too?
708 1
	if (!empty($theme_vars))
709 1
	{
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 1
	trackStats(array('registers' => '+'));
723
724
	// @todo emails should be sent from the controller, with a new method.
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 1
		'REALNAME' => $regOptions['register_vars']['real_name'],
729 1
		'USERNAME' => $regOptions['username'],
730 1
		'PASSWORD' => $regOptions['password'],
731 1
		'FORGOTPASSWORDLINK' => $scripturl . '?action=reminder',
732 1
		'ACTIVATIONLINK' => $scripturl . '?action=register;sa=activate;u=' . $memberID . ';code=' . $validation_code,
733 1
		'ACTIVATIONLINKWITHOUTCODE' => $scripturl . '?action=register;sa=activate;u=' . $memberID,
734 1
		'ACTIVATIONCODE' => $validation_code,
735 1
		'OPENID' => !empty($regOptions['openid']) ? $regOptions['openid'] : '',
736 1
		'COPPALINK' => $scripturl . '?action=register;sa=coppa;u=' . $memberID,
737 1
	);
738
739
	// Administrative registrations are a bit different...
740 1
	if ($regOptions['interface'] == 'admin')
741 1
	{
742
		if ($regOptions['require'] == 'activation')
743
			$email_message = 'admin_register_activate';
744
		elseif (!empty($regOptions['send_welcome_email']))
745
			$email_message = 'admin_register_immediate';
746
747
		if (isset($email_message))
748
		{
749
			$emaildata = loadEmailTemplate($email_message, $replacements);
750
751
			sendmail($regOptions['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 0);
752
		}
753
	}
754
	else
755
	{
756
		// Can post straight away - welcome them to your fantastic community...
757 1
		if ($regOptions['require'] == 'nothing')
758 1
		{
759 1
			if (!empty($regOptions['send_welcome_email']))
760 1
			{
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 1
			require_once(SUBSDIR . '/Notification.subs.php');
774 1
			sendAdminNotifications('standard', $memberID, $regOptions['username']);
775 1
		}
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 1
		$_SESSION['just_registered'] = 1;
806
	}
807
808
	// If they are for sure registered, let other people to know about it
809 1
	call_integration_hook('integrate_register_after', array($regOptions, $memberID));
810
811 1
	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 1
	global $modSettings;
834
835 1
	$db = database();
836
837 1
	$name = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'replaceEntities__callback', $name);
838 1
	$checkName = Util::strtolower($name);
839
840
	// Administrators are never restricted ;).
841 1
	if (!allowedTo('admin_forum') && ((!empty($modSettings['reserveName']) && $is_name) || !empty($modSettings['reserveUser']) && !$is_name))
842 1
	{
843 1
		$reservedNames = explode("\n", $modSettings['reserveNames']);
844
		// Case sensitive check?
845 1
		$checkMe = empty($modSettings['reserveCase']) ? $checkName : $name;
846
847
		// Check each name in the list...
848 1
		foreach ($reservedNames as $reserved)
849
		{
850 1
			if ($reserved == '')
851 1
				continue;
852
853
			// The admin might've used entities too, level the playing field.
854 1
			$reservedCheck = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'replaceEntities__callback', $reserved);
855
856
			// Case sensitive name?
857 1
			if (empty($modSettings['reserveCase']))
858 1
				$reservedCheck = Util::strtolower($reservedCheck);
859
860
			// If it's not just entire word, check for it in there somewhere...
861 1
			if ($checkMe == $reservedCheck || (Util::strpos($checkMe, $reservedCheck) !== false && empty($modSettings['reserveWord'])))
862 1
				if ($fatal)
863
					throw new Elk_Exception('username_reserved', 'password', array($reserved));
864
				else
865
					return true;
866 1
		}
867
868 1
		$censor_name = $name;
869 1
		if (censor($censor_name) != $name)
870 1
			if ($fatal)
871
				throw new Elk_Exception('name_censored', 'password', array($name));
872
			else
873
				return true;
874 1
	}
875
876
	// Characters we just shouldn't allow, regardless.
877 1
	foreach (array('*') as $char)
878 1
		if (strpos($checkName, $char) !== false)
879 1
			if ($fatal)
880
				throw new Elk_Exception('username_reserved', 'password', array($char));
881
			else
882
				return true;
883
884
	// Get rid of any SQL parts of the reserved name...
885 1
	$checkName = strtr($name, array('_' => '\\_', '%' => '\\%'));
886
887
	// Make sure they don't want someone else's name.
888 1
	$request = $db->query('', '
889
		SELECT id_member
890
		FROM {db_prefix}members
891 1
		WHERE ' . (empty($current_ID_MEMBER) ? '' : 'id_member != {int:current_member}
892 1
			AND ') . '({raw:real_name} LIKE {string:check_name} OR {raw:member_name} LIKE {string:check_name})
893 1
		LIMIT 1',
894
		array(
895 1
			'real_name' => defined('DB_CASE_SENSITIVE') ? 'LOWER(real_name)' : 'real_name',
896 1
			'member_name' => defined('DB_CASE_SENSITIVE') ? 'LOWER(member_name)' : 'member_name',
897 1
			'current_member' => $current_ID_MEMBER,
898 1
			'check_name' => $checkName,
899
		)
900 1
	);
901 1
	if ($db->num_rows($request) > 0)
902 1
	{
903
		$db->free_result($request);
904
		return true;
905
	}
906
907
	// Does name case insensitive match a member group name?
908 1
	$request = $db->query('', '
909
		SELECT id_group
910
		FROM {db_prefix}membergroups
911
		WHERE {raw:group_name} LIKE {string:check_name}
912 1
		LIMIT 1',
913
		array(
914 1
			'group_name' => defined('DB_CASE_SENSITIVE') ? 'LOWER(group_name)' : 'group_name',
915 1
			'check_name' => $checkName,
916
		)
917 1
	);
918 1
	if ($db->num_rows($request) > 0)
919 1
	{
920
		$db->free_result($request);
921
		return true;
922
	}
923
924
	// Okay, they passed.
925 1
	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
	$potential_dupes = membersByIP($ips, 'exact', true);
1248
1249
	$duplicate_members = array();
1250
	$duplicate_ids = array();
1251
	foreach ($potential_dupes 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
	{
1307
		foreach ($members as $key => $member)
1308
		{
1309
			if (isset($duplicate_members[$member['member_ip']]))
1310
				$members[$key]['duplicate_members'] = $duplicate_members[$member['member_ip']];
1311
			if ($member['member_ip'] != $member['member_ip2'] && isset($duplicate_members[$member['member_ip2']]))
1312
				$members[$key]['duplicate_members'] = array_merge($member['duplicate_members'], $duplicate_members[$member['member_ip2']]);
1313
1314
			// Check we don't have lots of the same member.
1315
			$member_track = array($member['id_member']);
1316
			foreach ($members[$key]['duplicate_members'] as $duplicate_id_member => $duplicate_member)
1317
			{
1318
				if (in_array($duplicate_member['id'], $member_track))
1319
				{
1320
					unset($members[$key]['duplicate_members'][$duplicate_id_member]);
1321
					continue;
1322
				}
1323
1324
				$member_track[] = $duplicate_member['id'];
1325
			}
1326
		}
1327
	}
1328
}
1329
1330
/**
1331
 * Find members with a given IP (first, second, exact or "relaxed")
1332
 *
1333
 * @package Members
1334
 * @param string|string[] $ip1 An IP or an array of IPs
1335
 * @param string $match (optional, default 'exact') if the match should be exact
1336
 *                of "relaxed" (using LIKE)
1337
 * @param bool $ip2 (optional, default false) If the query should check IP2 as well
1338
 */
1339
function membersByIP($ip1, $match = 'exact', $ip2 = false)
1340
{
1341 1
	$db = database();
1342
1343 1
	$ip_params = array('ips' => array());
1344 1
	$ip_query = array();
1345 1
	foreach (array($ip1, $ip2) as $id => $ip)
1346
	{
1347 1
		if ($ip === false)
1348 1
			continue;
1349
1350 1
		if ($match === 'exact')
1351 1
			$ip_params['ips'] = array_merge($ip_params['ips'], (array) $ip);
1352
		else
1353
		{
1354 1
			$ip = (array) $ip;
1355 1
			foreach ($ip as $id_var => $ip_var)
1356
			{
1357 1
				$ip_var = str_replace('*', '%', $ip_var);
1358 1
				$ip_query[] = strpos($ip_var, '%') === false ? '= {string:ip_address_' . $id . '_' . $id_var . '}' : 'LIKE {string:ip_address_' . $id . '_' . $id_var . '}';
1359 1
				$ip_params['ip_address_' . $id . '_' . $id_var] = $ip_var;
1360 1
			}
1361
		}
1362 1
	}
1363
1364 1
	if ($match === 'exact')
1365 1
	{
1366 1
		$where = 'member_ip IN ({array_string:ips})';
1367 1
		if ($ip2 !== false)
1368 1
			$where .= '
1369 1
			OR member_ip2 IN ({array_string:ips})';
1370 1
	}
1371
	else
1372
	{
1373 1
		$where = 'member_ip ' . implode(' OR member_ip', $ip_query);
1374 1
		if ($ip2 !== false)
1375 1
			$where .= '
1376
			OR member_ip2 ' . implode(' OR member_ip', $ip_query);
1377
	}
1378
1379 1
	return $db->fetchQuery('
1380
		SELECT
1381
			id_member, member_name, email_address, member_ip, member_ip2, is_activated
1382
		FROM {db_prefix}members
1383 1
		WHERE ' . $where,
1384
		$ip_params
1385 1
	);
1386
}
1387
1388
/**
1389
 * Find out if there is another admin than the given user.
1390
 *
1391
 * @package Members
1392
 * @param int $memberID ID of the member, to compare with.
1393
 */
1394 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...
1395
{
1396
	$db = database();
1397
1398
	$request = $db->query('', '
1399
		SELECT id_member
1400
		FROM {db_prefix}members
1401
		WHERE (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0)
1402
			AND id_member != {int:selected_member}
1403
		LIMIT 1',
1404
		array(
1405
			'admin_group' => 1,
1406
			'selected_member' => $memberID,
1407
		)
1408
	);
1409
	list ($another) = $db->fetch_row($request);
1410
	$db->free_result($request);
1411
1412
	return $another;
1413
}
1414
1415
/**
1416
 * This function retrieves a list of member ids based on a set of conditions
1417
 *
1418
 * @package Members
1419
 * @param mixed[]|string $query see prepareMembersByQuery
1420
 * @param mixed[] $query_params see prepareMembersByQuery
1421
 * @param bool $details if true returns additional member details (name, email, ip, etc.)
1422
 *             false will only return an array of member id's that match the conditions
1423
 * @param bool $only_active see prepareMembersByQuery
1424
 */
1425
function membersBy($query, $query_params, $details = false, $only_active = true)
1426
{
1427
	$db = database();
1428
1429
	$query_where = prepareMembersByQuery($query, $query_params, $only_active);
1430
1431
	// Lets see who we can find that meets the built up conditions
1432
	$members = array();
1433
	$request = $db->query('', '
1434
		SELECT id_member' . ($details ? ', member_name, real_name, email_address, member_ip, date_registered, last_login,
1435
				hide_email, posts, is_activated, real_name' : '') . '
1436
		FROM {db_prefix}members
1437
		WHERE ' . $query_where . (isset($query_params['start']) ? '
1438
		LIMIT {int:start}, {int:limit}' : '') . (!empty($query_params['order']) ? '
1439
		ORDER BY {raw:order}' : ''),
1440
		$query_params
1441
	);
1442
1443
	// Return all the details for each member found
1444
	if ($details)
1445
	{
1446
		while ($row = $db->fetch_assoc($request))
1447
			$members[$row['id_member']] = $row;
1448
	}
1449
	// Or just a int[] of found member id's
1450
	else
1451
	{
1452
		while ($row = $db->fetch_assoc($request))
1453
			$members[] = $row['id_member'];
1454
	}
1455
	$db->free_result($request);
1456
1457
	return $members;
1458
}
1459
1460
/**
1461
 * Counts the number of members based on conditions
1462
 *
1463
 * @package Members
1464
 * @param string[]|string $query see prepareMembersByQuery
1465
 * @param mixed[] $query_params see prepareMembersByQuery
1466
 * @param boolean $only_active see prepareMembersByQuery
1467
 */
1468
function countMembersBy($query, $query_params, $only_active = true)
1469
{
1470
	$db = database();
1471
1472
	$query_where = prepareMembersByQuery($query, $query_params, $only_active);
1473
1474
	$request = $db->query('', '
1475
		SELECT COUNT(*)
1476
		FROM {db_prefix}members
1477
		WHERE ' . $query_where,
1478
		$query_params
1479
	);
1480
1481
	list ($num_members) = $db->fetch_row($request);
1482
	$db->free_result($request);
1483
1484
	return $num_members;
1485
}
1486
1487
/**
1488
 * Builds the WHERE clause for the functions countMembersBy and membersBy
1489
 *
1490
 * @package Members
1491
 * @param mixed[]|string $query can be an array of "type" of conditions,
1492
 *             or a string used as raw query
1493
 *             or a string that represents one of the built-in conditions
1494
 *             like member_names, not_in_group, etc
1495
 * @param mixed[] $query_params is an array containing the parameters passed to the query
1496
 *             'start' and 'limit' used in LIMIT
1497
 *             'order' used raw in ORDER BY
1498
 *             others passed as query params
1499
 * @param bool $only_active only fetch active members
1500
 */
1501
function prepareMembersByQuery($query, &$query_params, $only_active = true)
1502
{
1503
	$allowed_conditions = array(
1504
		'member_ids'   => 'id_member IN ({array_int:member_ids})',
1505
		'member_names' => function (&$members)
1506
		{
1507
			$mem_query = array();
1508
1509
			foreach ($members['member_names'] as $key => $param)
1510
			{
1511
				$mem_query[] = (defined('DB_CASE_SENSITIVE') ? 'LOWER(real_name)' : 'real_name') . ' LIKE {string:member_names_' . $key . '}';
1512
				$members['member_names_' . $key] = defined('DB_CASE_SENSITIVE') ? strtolower($param) : $param;
1513
			}
1514
			return implode("\n\t\t\tOR ", $mem_query);
1515
		},
1516
		'not_in_group'     => '(id_group != {int:not_in_group} AND FIND_IN_SET({int:not_in_group}, additional_groups) = 0)',
1517
		'in_group'         => '(id_group = {int:in_group} OR FIND_IN_SET({int:in_group}, additional_groups) != 0)',
1518
		'in_group_primary' => 'id_group = {int:in_group_primary}',
1519
		'in_post_group'    => 'id_post_group = {int:in_post_group}',
1520
		'in_group_no_add'  => '(id_group = {int:in_group_no_add} AND FIND_IN_SET({int:in_group_no_add}, additional_groups) = 0)',
1521
	);
1522
1523
	// Are there multiple parts to this query
1524
	if (is_array($query))
1525
	{
1526
		$query_parts = array('or' => array(), 'and' => array());
1527
		foreach ($query as $type => $query_conditions)
1528
		{
1529
			if (is_array($query_conditions))
1530
			{
1531
				foreach ($query_conditions as $condition => $query_condition)
1532
				{
1533
					if ($query_condition == 'member_names')
1534
						$query_parts[$condition === 'or' ? 'or' : 'and'][] = $allowed_conditions[$query_condition]($query_params);
1535
					else
1536
						$query_parts[$condition === 'or' ? 'or' : 'and'][] = isset($allowed_conditions[$query_condition]) ? $allowed_conditions[$query_condition] : $query_condition;
1537
				}
1538
			}
1539
			elseif ($query == 'member_names')
1540
				$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 1531. 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...
1541
			else
1542
				$query_parts['and'][] = isset($allowed_conditions[$query]) ? $allowed_conditions[$query] : $query;
1543
		}
1544
1545
		if (!empty($query_parts['or']))
1546
			$query_parts['and'][] = implode("\n\t\t\tOR ", $query_parts['or']);
1547
1548
		$query_where = implode("\n\t\t\tAND ", $query_parts['and']);
1549
	}
1550
	// Is it one of our predefined querys like member_ids, member_names, etc
1551
	elseif (isset($allowed_conditions[$query]))
1552
	{
1553
		if ($query == 'member_names')
1554
			$query_where = $allowed_conditions[$query]($query_params);
1555
		else
1556
			$query_where = $allowed_conditions[$query];
1557
	}
1558
	// Something else, be careful ;)
1559
	else
1560
		$query_where = $query;
1561
1562
	// Lazy loading, our favorite
1563
	if (empty($query_where))
1564
		return false;
1565
1566
	// Only want active members
1567
	if ($only_active)
1568
	{
1569
		$query_where .= '
1570
			AND is_activated = {int:is_activated}';
1571
		$query_params['is_activated'] = 1;
1572
	}
1573
1574
	return $query_where;
1575
}
1576
1577
/**
1578
 * Retrieve administrators of the site.
1579
 *
1580
 * - The function returns basic information: name, language file.
1581
 * - It is used in personal messages reporting.
1582
 *
1583
 * @package Members
1584
 * @param int $id_admin = 0 if requested, only data about a specific admin is retrieved
1585
 */
1586
function admins($id_admin = 0)
1587
{
1588
	$db = database();
1589
1590
	// Now let's get out and loop through the admins.
1591
	$request = $db->query('', '
1592
		SELECT id_member, real_name, lngfile
1593
		FROM {db_prefix}members
1594
		WHERE (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0)
1595
			' . (empty($id_admin) ? '' : 'AND id_member = {int:specific_admin}') . '
1596
		ORDER BY real_name, lngfile',
1597
		array(
1598
			'admin_group' => 1,
1599
			'specific_admin' => isset($id_admin) ? (int) $id_admin : 0,
1600
		)
1601
	);
1602
1603
	$admins = array();
1604
	while ($row = $db->fetch_assoc($request))
1605
		$admins[$row['id_member']] = array($row['real_name'], $row['lngfile']);
1606
	$db->free_result($request);
1607
1608
	return $admins;
1609
}
1610
1611
/**
1612
 * Get the last known id_member
1613
 * @return int
1614
 */
1615 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...
1616
{
1617
	$db = database();
1618
1619
	$request = $db->query('', '
1620
		SELECT MAX(id_member)
1621
		FROM {db_prefix}members',
1622
		array(
1623
		)
1624
	);
1625
	list ($max_id) = $db->fetch_row($request);
1626
	$db->free_result($request);
1627
1628
	return $max_id;
1629
}
1630
1631
/**
1632
 * Load some basic member information
1633
 *
1634
 * @package Members
1635
 * @param int[]|int $member_ids an array of member IDs or a single ID
1636
 * @param mixed[] $options an array of possible little alternatives, can be:
1637
 * - 'add_guest' (bool) to add a guest user to the returned array
1638
 * - 'limit' int if set overrides the default query limit
1639
 * - 'sort' (string) a column to sort the results
1640
 * - 'moderation' (bool) includes member_ip, id_group, additional_groups, last_login
1641
 * - 'authentication' (bool) includes secret_answer, secret_question, openid_uri,
1642
 *    is_activated, validation_code, passwd_flood
1643
 * - 'preferences' (bool) includes lngfile, mod_prefs, notify_types, signature
1644
 * @return array
1645
 */
1646
function getBasicMemberData($member_ids, $options = array())
1647
{
1648 3
	global $txt, $language;
1649
1650 3
	$db = database();
1651
1652 3
	$members = array();
1653
1654 3
	if (empty($member_ids))
1655 3
		return false;
1656
1657 3
	if (!is_array($member_ids))
1658 3
	{
1659 1
		$single = true;
1660 1
		$member_ids = array($member_ids);
1661 1
	}
1662
1663 3
	if (!empty($options['add_guest']))
1664 3
	{
1665
		$single = false;
1666
		// This is a guest...
1667
		$members[0] = array(
1668
			'id_member' => 0,
1669
			'member_name' => '',
1670
			'real_name' => $txt['guest_title'],
1671
			'email_address' => '',
1672
		);
1673
	}
1674
1675
	// Get some additional member info...
1676 3
	$request = $db->query('', '
1677 3
		SELECT id_member, member_name, real_name, email_address, hide_email, posts, id_theme' . (!empty($options['moderation']) ? ',
1678 3
		member_ip, id_group, additional_groups, last_login, id_post_group' : '') . (!empty($options['authentication']) ? ',
1679 3
		secret_answer, secret_question, openid_uri, is_activated, validation_code, passwd_flood' : '') . (!empty($options['preferences']) ? ',
1680 3
		lngfile, mod_prefs, notify_types, signature' : '') . '
1681
		FROM {db_prefix}members
1682
		WHERE id_member IN ({array_int:member_list})
1683 3
		' . (isset($options['sort']) ? '
1684 3
		ORDER BY {raw:sort}' : '') . '
1685 3
		LIMIT {int:limit}',
1686
		array(
1687 3
			'member_list' => $member_ids,
1688 3
			'limit' => isset($options['limit']) ? $options['limit'] : count($member_ids),
1689 3
			'sort' => isset($options['sort']) ? $options['sort'] : '',
1690
		)
1691 3
	);
1692 3
	while ($row = $db->fetch_assoc($request))
1693
	{
1694 3
		if (empty($row['lngfile']))
1695 3
			$row['lngfile'] = $language;
1696
1697 3
		if (!empty($single))
1698 3
			$members = $row;
1699
		else
1700 2
			$members[$row['id_member']] = $row;
1701 3
	}
1702 3
	$db->free_result($request);
1703
1704 3
	return $members;
1705
}
1706
1707
/**
1708
 * Counts all inactive members
1709
 *
1710
 * @package Members
1711
 * @return array $inactive_members
1712
 */
1713
function countInactiveMembers()
1714
{
1715
	$db = database();
1716
1717
	$inactive_members = array();
1718
1719
	$request = $db->query('', '
1720
		SELECT COUNT(*) AS total_members, is_activated
1721
		FROM {db_prefix}members
1722
		WHERE is_activated != {int:is_activated}
1723
		GROUP BY is_activated',
1724
		array(
1725
			'is_activated' => 1,
1726
		)
1727
	);
1728
1729
	while ($row = $db->fetch_assoc($request))
1730
		$inactive_members[$row['is_activated']] = $row['total_members'];
1731
	$db->free_result($request);
1732
1733
	return $inactive_members;
1734
}
1735
1736
/**
1737
 * Get the member's id and group
1738
 *
1739
 * @package Members
1740
 * @param string $name
1741
 * @param bool $flexible if true searches for both real_name and member_name (default false)
1742
 * @return integer
1743
 */
1744
function getMemberByName($name, $flexible = false)
1745
{
1746
	$db = database();
1747
1748
	$request = $db->query('', '
1749
		SELECT id_member, id_group
1750
		FROM {db_prefix}members
1751
		WHERE {raw:real_name} LIKE {string:name}' . ($flexible ? '
1752
			OR {raw:member_name} LIKE {string:name}' : '') . '
1753
		LIMIT 1',
1754
		array(
1755
			'name' => Util::strtolower($name),
1756
			'real_name' => defined('DB_CASE_SENSITIVE') ? 'LOWER(real_name)' : 'real_name',
1757
			'member_name' => defined('DB_CASE_SENSITIVE') ? 'LOWER(member_name)' : 'member_name',
1758
		)
1759
	);
1760
	if ($db->num_rows($request) == 0)
1761
		return false;
1762
	$member = $db->fetch_assoc($request);
1763
	$db->free_result($request);
1764
1765
	return $member;
1766
}
1767
1768
/**
1769
 * Finds a member from the database using supplied string as real_name
1770
 *
1771
 * - Optionally will only search/find the member in a buddy list
1772
 *
1773
 * @package Members
1774
 * @param string $search string to search real_name for like finds
1775
 * @param int[]|null $buddies
1776
 */
1777
function getMember($search, $buddies = array())
1778
{
1779
	$db = database();
1780
1781
	$xml_data = array(
1782
		'items' => array(
1783
			'identifier' => 'item',
1784
			'children' => array(),
1785
		),
1786
	);
1787
	// Find the member.
1788
	$xml_data['items']['children'] = $db->fetchQueryCallback('
1789
		SELECT id_member, real_name
1790
		FROM {db_prefix}members
1791
		WHERE {raw:real_name} LIKE {string:search}' . (!empty($buddies) ? '
1792
			AND id_member IN ({array_int:buddy_list})' : '') . '
1793
			AND is_activated IN ({array_int:activation_status})
1794
		ORDER BY LENGTH(real_name), real_name
1795
		LIMIT {int:limit}',
1796
		array(
1797
			'real_name' => defined('DB_CASE_SENSITIVE') ? 'LOWER(real_name)' : 'real_name',
1798
			'buddy_list' => $buddies,
1799
			'search' => Util::strtolower($search),
1800
			'activation_status' => array(1, 12),
1801
			'limit' => Util::strlen($search) <= 2 ? 100 : 200,
1802
		),
1803
		function ($row)
1804
		{
1805
			$row['real_name'] = strtr($row['real_name'], array('&amp;' => '&#038;', '&lt;' => '&#060;', '&gt;' => '&#062;', '&quot;' => '&#034;'));
1806
1807
			return array(
1808
				'attributes' => array(
1809
					'id' => $row['id_member'],
1810
				),
1811
				'value' => $row['real_name'],
1812
			);
1813
		}
1814
	);
1815
1816
	return $xml_data;
1817
}
1818
1819
/**
1820
 * Retrieves MemberData based on conditions
1821
 *
1822
 * @package Members
1823
 * @param mixed[] $conditions associative array holding the conditions for the WHERE clause of the query.
1824
 * Possible keys:
1825
 * - activated_status (boolean) must be present
1826
 * - time_before (integer)
1827
 * - members (array of integers)
1828
 * - member_greater (integer) a member id, it will be used to filter only members with id_member greater than this
1829
 * - group_list (array) array of group IDs
1830
 * - notify_announcements (integer)
1831
 * - order_by (string)
1832
 * - limit (int)
1833
 * @return array
1834
 */
1835
function retrieveMemberData($conditions)
1836
{
1837
	global $modSettings, $language;
1838
1839
	// We badly need this
1840
	assert(isset($conditions['activated_status']));
1841
1842
	$db = database();
1843
1844
	$available_conditions = array(
1845
		'time_before' => '
1846
				AND date_registered < {int:time_before}',
1847
		'members' => '
1848
				AND id_member IN ({array_int:members})',
1849
		'member_greater' => '
1850
				AND id_member > {int:member_greater}',
1851
		'member_greater_equal' => '
1852
				AND id_member >= {int:member_greater_equal}',
1853
		'member_lesser' => '
1854
				AND id_member < {int:member_lesser}',
1855
		'member_lesser_equal' => '
1856
				AND id_member <= {int:member_lesser_equal}',
1857
		'group_list' => '
1858
				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)',
1859
		'notify_announcements' => '
1860
				AND notify_announcements = {int:notify_announcements}'
1861
	);
1862
1863
	$query_cond = array();
1864
	foreach ($conditions as $key => $dummy)
1865
		if (isset($available_conditions[$key]))
1866
			$query_cond[] = $available_conditions[$key];
1867
1868
	if (isset($conditions['group_list']))
1869
		$conditions['additional_group_list'] = implode(', additional_groups) != 0 OR FIND_IN_SET(', $conditions['group_list']);
1870
1871
	$data = array();
1872
1873
	if (!isset($conditions['order_by']))
1874
		$conditions['order_by'] = 'lngfile';
1875
1876
	$limit = (isset($conditions['limit'])) ? '
1877
		LIMIT {int:limit}' : '';
1878
1879
	// Get information on each of the members, things that are important to us, like email address...
1880
	$request = $db->query('', '
1881
		SELECT id_member, member_name, real_name, email_address, validation_code, lngfile
1882
		FROM {db_prefix}members
1883
		WHERE is_activated = {int:activated_status}' . implode('', $query_cond) . '
1884
		ORDER BY {raw:order_by}' . $limit,
1885
		$conditions
1886
	);
1887
1888
	$data['member_count'] = $db->num_rows($request);
1889
1890
	if ($data['member_count'] == 0)
1891
		return $data;
1892
1893
	// Fill the info array.
1894
	while ($row = $db->fetch_assoc($request))
1895
	{
1896
		$data['members'][] = $row['id_member'];
1897
		$data['member_info'][] = array(
1898
			'id' => $row['id_member'],
1899
			'username' => $row['member_name'],
1900
			'name' => $row['real_name'],
1901
			'email' => $row['email_address'],
1902
			'language' => empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile'],
1903
			'code' => $row['validation_code']
1904
		);
1905
	}
1906
	$db->free_result($request);
1907
1908
	return $data;
1909
}
1910
1911
/**
1912
 * Activate members
1913
 *
1914
 * @package Members
1915
 * @param mixed[] $conditions associative array holding the conditions for the WHERE clause of the query.
1916
 * Possible keys:
1917
 * - activated_status (boolean) must be present
1918
 * - time_before (integer)
1919
 * - members (array of integers)
1920
 */
1921
function approveMembers($conditions)
1922
{
1923
	$db = database();
1924
1925
	// This shall be present
1926
	assert(isset($conditions['activated_status']));
1927
1928
	$available_conditions = array(
1929
		'time_before' => '
1930
				AND date_registered < {int:time_before}',
1931
		'members' => '
1932
				AND id_member IN ({array_int:members})',
1933
	);
1934
1935
	// @todo maybe an hook here?
1936
	$query_cond = array();
1937
	$query = false;
1938
	foreach ($conditions as $key => $dummy)
1939
	{
1940
		if (isset($available_conditions[$key]))
1941
		{
1942
			if ($key === 'time_before')
1943
				$query = true;
1944
			$query_cond[] = $available_conditions[$key];
1945
		}
1946
	}
1947
1948
	if ($query)
1949
	{
1950
		$data = retrieveMemberData($conditions);
1951
		$members_id = array();
1952
		foreach ($data['member_info'] as $member)
1953
			$members_id[] = $member['username'];
1954
	}
1955
	else
1956
	{
1957
		$members_id = $conditions['members'];
1958
	}
1959
1960
	$conditions['is_activated'] = $conditions['activated_status'] >= 10 ? 11 : 1;
1961
	$conditions['blank_string'] = '';
1962
1963
	// Approve/activate this member.
1964
	$db->query('', '
1965
		UPDATE {db_prefix}members
1966
		SET validation_code = {string:blank_string}, is_activated = {int:is_activated}
1967
		WHERE is_activated = {int:activated_status}' . implode('', $query_cond),
1968
		$conditions
1969
	);
1970
1971
	// Let the integration know that they've been activated!
1972
	foreach ($members_id as $member_id)
1973
		call_integration_hook('integrate_activate', array($member_id, $conditions['activated_status'], $conditions['is_activated']));
1974
1975
	return $conditions['is_activated'];
1976
}
1977
1978
/**
1979
 * Set these members for activation
1980
 *
1981
 * @package Members
1982
 * @param mixed[] $conditions associative array holding the conditions for the  WHERE clause of the query.
1983
 * Possible keys:
1984
 * - selected_member (integer) must be present
1985
 * - activated_status (boolean) must be present
1986
 * - validation_code (string) must be present
1987
 * - members (array of integers)
1988
 * - time_before (integer)
1989
 */
1990
function enforceReactivation($conditions)
1991
{
1992
	$db = database();
1993
1994
	// We need all of these
1995
	assert(isset($conditions['activated_status']));
1996
	assert(isset($conditions['selected_member']));
1997
	assert(isset($conditions['validation_code']));
1998
1999
	$conditions['validation_code'] = substr(hash('sha256', $conditions['validation_code']), 0, 10);
2000
2001
	$available_conditions = array(
2002
		'time_before' => '
2003
				AND date_registered < {int:time_before}',
2004
		'members' => '
2005
				AND id_member IN ({array_int:members})',
2006
	);
2007
2008
	$query_cond = array();
2009
	foreach ($conditions as $key => $dummy)
2010
		$query_cond[] = $available_conditions[$key];
2011
2012
	$conditions['not_activated'] = 0;
2013
2014
	$db->query('', '
2015
		UPDATE {db_prefix}members
2016
		SET validation_code = {string:validation_code}, is_activated = {int:not_activated}
2017
		WHERE is_activated = {int:activated_status}
2018
			' . implode('', $query_cond) . '
2019
			AND id_member = {int:selected_member}',
2020
		$conditions
2021
	);
2022
}
2023
2024
/**
2025
 * Count members of a given group
2026
 *
2027
 * @package Members
2028
 * @param int $id_group
2029
 * @return int
2030
 */
2031 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...
2032
{
2033
	$db = database();
2034
2035
	// Determine the number of ungrouped members.
2036
	$request = $db->query('', '
2037
		SELECT COUNT(*)
2038
		FROM {db_prefix}members
2039
		WHERE id_group = {int:group}',
2040
		array(
2041
			'group' => $id_group,
2042
		)
2043
	);
2044
	list ($num_members) = $db->fetch_row($request);
2045
	$db->free_result($request);
2046
2047
	return $num_members;
2048
}
2049
2050
/**
2051
 * Get the total amount of members online.
2052
 *
2053
 * @package Members
2054
 * @param string[] $conditions
2055
 * @return int
2056
 */
2057 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...
2058
{
2059
	$db = database();
2060
2061
	$request = $db->query('', '
2062
		SELECT COUNT(*)
2063
		FROM {db_prefix}log_online AS lo
2064
			LEFT JOIN {db_prefix}members AS mem ON (lo.id_member = mem.id_member)' . (!empty($conditions) ? '
2065
		WHERE ' . implode(' AND ', $conditions) : ''),
2066
		array(
2067
		)
2068
	);
2069
	list ($totalMembers) = $db->fetch_row($request);
2070
	$db->free_result($request);
2071
2072
	return $totalMembers;
2073
}
2074
2075
/**
2076
 * Look for people online, provided they don't mind if you see they are.
2077
 *
2078
 * @package Members
2079
 * @param string[] $conditions
2080
 * @param string $sort_method
2081
 * @param string $sort_direction
2082
 * @param int $start
2083
 * @return array
2084
 */
2085
function onlineMembers($conditions, $sort_method, $sort_direction, $start)
2086
{
2087
	global $modSettings;
2088
2089
	$db = database();
2090
2091
	return $db->fetchQuery('
2092
		SELECT
2093
			lo.log_time, lo.id_member, lo.url, lo.ip, mem.real_name,
2094
			lo.session, mg.online_color, COALESCE(mem.show_online, 1) AS show_online,
2095
			lo.id_spider
2096
		FROM {db_prefix}log_online AS lo
2097
			LEFT JOIN {db_prefix}members AS mem ON (lo.id_member = mem.id_member)
2098
			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) ? '
2099
		WHERE ' . implode(' AND ', $conditions) : '') . '
2100
		ORDER BY {raw:sort_method} {raw:sort_direction}
2101
		LIMIT {int:offset}, {int:limit}',
2102
		array(
2103
			'regular_member' => 0,
2104
			'sort_method' => $sort_method,
2105
			'sort_direction' => $sort_direction == 'up' ? 'ASC' : 'DESC',
2106
			'offset' => $start,
2107
			'limit' => $modSettings['defaultMaxMembers'],
2108
		)
2109
	);
2110
}
2111
2112
/**
2113
 * Check if the OpenID URI is already registered for an existing member
2114
 *
2115
 * @package Members
2116
 * @param string $url
2117
 * @return array
2118
 */
2119
function memberExists($url)
2120
{
2121
	$db = database();
2122
2123
	$request = $db->query('', '
2124
		SELECT mem.id_member, mem.member_name
2125
		FROM {db_prefix}members AS mem
2126
		WHERE mem.openid_uri = {string:openid_uri}',
2127
		array(
2128
			'openid_uri' => $url,
2129
		)
2130
	);
2131
	$member = $db->fetch_assoc($request);
2132
	$db->free_result($request);
2133
2134
	return $member;
2135
}
2136
2137
/**
2138
 * Find the most recent members
2139
 *
2140
 * @package Members
2141
 * @param int $limit
2142
 */
2143
function recentMembers($limit)
2144
{
2145
	$db = database();
2146
2147
	// Find the most recent members.
2148
	return $db->fetchQuery('
2149
		SELECT id_member, member_name, real_name, date_registered, last_login
2150
		FROM {db_prefix}members
2151
		ORDER BY id_member DESC
2152
		LIMIT {int:limit}',
2153
		array(
2154
			'limit' => $limit,
2155
		)
2156
	);
2157
}
2158
2159
/**
2160
 * Assign membergroups to members.
2161
 *
2162
 * @package Members
2163
 * @param int $member
2164
 * @param int $primary_group
2165
 * @param int[] $additional_groups
2166
 */
2167
function assignGroupsToMember($member, $primary_group, $additional_groups)
2168
{
2169
	updateMemberData($member, array('id_group' => $primary_group, 'additional_groups' => implode(',', $additional_groups)));
2170
}
2171
2172
/**
2173
 * Get a list of members from a membergroups request.
2174
 *
2175
 * @package Members
2176
 * @param int[] $groups
2177
 * @param string $where
2178
 * @param boolean $change_groups = false
2179
 * @return mixed
2180
 */
2181
function getConcernedMembers($groups, $where, $change_groups = false)
2182
{
2183
	global $modSettings, $language;
2184
2185
	$db = database();
2186
2187
		// Get the details of all the members concerned...
2188
	$request = $db->query('', '
2189
		SELECT lgr.id_request, lgr.id_member, lgr.id_group, mem.email_address, mem.id_group AS primary_group,
2190
			mem.additional_groups AS additional_groups, mem.lngfile, mem.member_name, mem.notify_types,
2191
			mg.hidden, mg.group_name
2192
		FROM {db_prefix}log_group_requests AS lgr
2193
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = lgr.id_member)
2194
			INNER JOIN {db_prefix}membergroups AS mg ON (mg.id_group = lgr.id_group)
2195
		WHERE ' . $where . '
2196
			AND lgr.id_request IN ({array_int:request_list})
2197
		ORDER BY mem.lngfile',
2198
		array(
2199
			'request_list' => $groups,
2200
		)
2201
	);
2202
2203
	$email_details = array();
2204
	$group_changes = array();
2205
2206
	while ($row = $db->fetch_assoc($request))
2207
	{
2208
		$row['lngfile'] = empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile'];
2209
2210
		// If we are approving work out what their new group is.
2211
		if ($change_groups)
2212
		{
2213
			// For people with more than one request at once.
2214
			if (isset($group_changes[$row['id_member']]))
2215
			{
2216
				$row['additional_groups'] = $group_changes[$row['id_member']]['add'];
2217
				$row['primary_group'] = $group_changes[$row['id_member']]['primary'];
2218
			}
2219
			else
2220
				$row['additional_groups'] = explode(',', $row['additional_groups']);
2221
				// Don't have it already?
2222
			if ($row['primary_group'] == $row['id_group'] || in_array($row['id_group'], $row['additional_groups']))
2223
				continue;
2224
				// Should it become their primary?
2225
			if ($row['primary_group'] == 0 && $row['hidden'] == 0)
2226
				$row['primary_group'] = $row['id_group'];
2227
			else
2228
				$row['additional_groups'][] = $row['id_group'];
2229
2230
			// Add them to the group master list.
2231
			$group_changes[$row['id_member']] = array(
2232
				'primary' => $row['primary_group'],
2233
				'add' => $row['additional_groups'],
2234
			);
2235
		}
2236
2237
		// Add required information to email them.
2238
		if ($row['notify_types'] != 4)
2239
			$email_details[] = array(
2240
				'rid' => $row['id_request'],
2241
				'member_id' => $row['id_member'],
2242
				'member_name' => $row['member_name'],
2243
				'group_id' => $row['id_group'],
2244
				'group_name' => $row['group_name'],
2245
				'email' => $row['email_address'],
2246
				'language' => $row['lngfile'],
2247
			);
2248
	}
2249
	$db->free_result($request);
2250
2251
	$output = array(
2252
		'email_details' => $email_details,
2253
		'group_changes' => $group_changes
2254
	);
2255
2256
	return $output;
2257
}
2258
2259
/**
2260
 * Determine if the current user ($user_info) can contact another user ($who)
2261
 *
2262
 * @package Members
2263
 * @param int $who The id of the user to contact
2264
 */
2265
function canContact($who)
2266
{
2267
	global $user_info;
2268
2269
	$db = database();
2270
2271
	$request = $db->query('', '
2272
		SELECT receive_from, buddy_list, pm_ignore_list
2273
		FROM {db_prefix}members
2274
		WHERE id_member = {int:member}',
2275
		array(
2276
			'member' => $who,
2277
		)
2278
	);
2279
	list ($receive_from, $buddies, $ignore) = $db->fetch_row($request);
2280
	$db->free_result($request);
2281
2282
	$buddy_list = array_map('intval', explode(',', $buddies));
2283
	$ignore_list = array_map('intval', explode(',', $ignore));
2284
2285
	// 0 = all members
2286
	if ($receive_from == 0)
2287
		return true;
2288
	// 1 = all except ignore
2289
	elseif ($receive_from == 1)
2290
		return !(!empty($ignore_list) && in_array($user_info['id'], $ignore_list));
2291
	// 2 = buddies and admin
2292
	elseif ($receive_from == 2)
2293
		return ($user_info['is_admin'] || (!empty($buddy_list) && in_array($user_info['id'], $buddy_list)));
2294
	// 3 = admin only
2295
	else
2296
		return (bool) $user_info['is_admin'];
2297
}
2298
2299
/**
2300
 * This function updates the latest member, the total membercount, and the
2301
 * number of unapproved members.
2302
 *
2303
 * - It also only counts approved members when approval is on,
2304
 * but is much more efficient with it off.
2305
 *
2306
 * @package Members
2307
 * @param integer|null $id_member = null If not an integer reload from the database
2308
 * @param string|null $real_name = null
2309
 */
2310
function updateMemberStats($id_member = null, $real_name = null)
2311
{
2312 1
	global $modSettings;
2313
2314 1
	$db = database();
2315
2316
	$changes = array(
2317 1
		'memberlist_updated' => time(),
2318 1
	);
2319
2320
	// #1 latest member ID, #2 the real name for a new registration.
2321 1
	if (is_int($id_member))
2322 1
	{
2323
		$changes['latestMember'] = $id_member;
2324
		$changes['latestRealName'] = $real_name;
2325
2326
		updateSettings(array('totalMembers' => true), true);
2327
	}
2328
	// We need to calculate the totals.
2329
	else
2330
	{
2331
		// Update the latest activated member (highest id_member) and count.
2332 1
		$request = $db->query('', '
2333
			SELECT COUNT(*), MAX(id_member)
2334
			FROM {db_prefix}members
2335 1
			WHERE is_activated = {int:is_activated}',
2336
			array(
2337 1
				'is_activated' => 1,
2338
			)
2339 1
		);
2340 1
		list ($changes['totalMembers'], $changes['latestMember']) = $db->fetch_row($request);
2341 1
		$db->free_result($request);
2342
2343
		// Get the latest activated member's display name.
2344 1
		$request = getBasicMemberData((int) $changes['latestMember']);
2345 1
		$changes['latestRealName'] = $request['real_name'];
2346
2347
		// Are we using registration approval?
2348 1
		if ((!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion']))
2349 1
		{
2350
			// Update the amount of members awaiting approval - ignoring COPPA accounts, as you can't approve them until you get permission.
2351
			$request = $db->query('', '
2352
				SELECT COUNT(*)
2353
				FROM {db_prefix}members
2354
				WHERE is_activated IN ({array_int:activation_status})',
2355
				array(
2356
					'activation_status' => array(3, 4),
2357
				)
2358
			);
2359
			list ($changes['unapprovedMembers']) = $db->fetch_row($request);
2360
			$db->free_result($request);
2361
		}
2362
	}
2363
2364 1
	updateSettings($changes);
2365 1
}
2366
2367
/**
2368
 * Builds the 'query_see_board' element for a certain member
2369
 *
2370
 * @package Members
2371
 * @param integer $id_member a valid member id
2372
 * @return string Query string
2373
 */
2374
function memberQuerySeeBoard($id_member)
2375
{
2376
	global $modSettings;
2377
2378
	$member = getBasicMemberData($id_member, array('moderation' => true));
2379
	if (empty($member))
2380
	{
2381
		return '0=1';
2382
	}
2383
2384 View Code Duplication
	if (empty($member['additional_groups']))
2385
	{
2386
		$groups = array($member['id_group'], $member['id_post_group']);
2387
	}
2388
	else
2389
	{
2390
		$groups = array_merge(
2391
			array($member['id_group'], $member['id_post_group']),
2392
			explode(',', $member['additional_groups'])
2393
		);
2394
	}
2395
2396
	foreach ($groups as $k => $v)
2397
	{
2398
		$groups[$k] = (int) $v;
2399
	}
2400
	$groups = array_unique($groups);
2401
2402
	if (in_array(1, $groups))
2403
	{
2404
		return '1=1';
2405
	}
2406
	else
2407
	{
2408
		require_once(SUBSDIR . '/Boards.subs.php');
2409
2410
		$boards_mod = boardsModerated($id_member);
2411
		$mod_query = empty($boards_mod) ? '' : ' OR b.id_board IN (' . implode(',', $boards_mod) . ')';
2412
2413
		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 . ')';
2414
	}
2415
}
2416
2417
/**
2418
 * Updates the columns in the members table.
2419
 *
2420
 * What it does:
2421
 *
2422
 * - Assumes the data has been htmlspecialchar'd, no sanitation is performed on the data.
2423
 * - This function should be used whenever member data needs to be updated in place of an UPDATE query.
2424
 * - $data is an associative array of the columns to be updated and their respective values.
2425
 * any string values updated should be quoted and slashed.
2426
 * - The value of any column can be '+' or '-', which mean 'increment' and decrement, respectively.
2427
 * - If the member's post number is updated, updates their post groups.
2428
 *
2429
 * @param int[]|int $members An array of member ids
2430
 * @param mixed[] $data An associative array of the columns to be updated and their respective values.
2431
 */
2432
function updateMemberData($members, $data)
2433
{
2434 9
	global $modSettings, $user_info;
2435
2436 9
	$db = database();
2437
2438 9
	$parameters = array();
2439 9
	if (is_array($members))
2440 9
	{
2441
		$condition = 'id_member IN ({array_int:members})';
2442
		$parameters['members'] = $members;
2443
	}
2444 9
	elseif ($members === null)
2445
		$condition = '1=1';
2446
	else
2447
	{
2448 9
		$condition = 'id_member = {int:member}';
2449 9
		$parameters['member'] = $members;
2450
	}
2451
2452
	// Everything is assumed to be a string unless it's in the below.
2453
	$knownInts = array(
2454 9
		'date_registered', 'posts', 'id_group', 'last_login', 'personal_messages', 'unread_messages', 'mentions',
2455 9
		'new_pm', 'pm_prefs', 'hide_email', 'show_online', 'pm_email_notify', 'receive_from', 'karma_good', 'karma_bad',
2456 9
		'notify_announcements', 'notify_send_body', 'notify_regularity', 'notify_types',
2457 9
		'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning', 'likes_given',
2458 9
		'likes_received', 'enable_otp', 'otp_used'
2459 9
	);
2460
	$knownFloats = array(
2461 9
		'time_offset',
2462 9
	);
2463
2464 9
	if (!empty($modSettings['integrate_change_member_data']))
2465 9
	{
2466
		// Only a few member variables are really interesting for integration.
2467
		$integration_vars = array(
2468
			'member_name',
2469
			'real_name',
2470
			'email_address',
2471
			'id_group',
2472
			'birthdate',
2473
			'website_title',
2474
			'website_url',
2475
			'hide_email',
2476
			'time_format',
2477
			'time_offset',
2478
			'avatar',
2479
			'lngfile',
2480
		);
2481
		$vars_to_integrate = array_intersect($integration_vars, array_keys($data));
2482
2483
		// Only proceed if there are any variables left to call the integration function.
2484
		if (count($vars_to_integrate) != 0)
2485
		{
2486
			// Fetch a list of member_names if necessary
2487
			if ((!is_array($members) && $members === $user_info['id']) || (is_array($members) && count($members) == 1 && in_array($user_info['id'], $members)))
2488
				$member_names = array($user_info['username']);
2489
			else
2490
			{
2491
				$member_names = $db->fetchQueryCallback('
2492
					SELECT member_name
2493
					FROM {db_prefix}members
2494
					WHERE ' . $condition,
2495
					$parameters,
2496
					function ($row)
2497
					{
2498
						return $row['member_name'];
2499
					}
2500
				);
2501
			}
2502
2503
			if (!empty($member_names))
2504
				foreach ($vars_to_integrate as $var)
2505
					call_integration_hook('integrate_change_member_data', array($member_names, &$var, &$data[$var], &$knownInts, &$knownFloats));
2506
		}
2507
	}
2508
2509 9
	$setString = '';
2510 9
	foreach ($data as $var => $val)
2511
	{
2512 9
		$type = 'string';
2513
2514 9 View Code Duplication
		if (in_array($var, $knownInts))
2515 9
			$type = 'int';
2516
		elseif (in_array($var, $knownFloats))
2517
			$type = 'float';
2518
		elseif ($var == 'birthdate')
2519
			$type = 'date';
2520
2521
		// Doing an increment?
2522 9
		if ($type == 'int' && ($val === '+' || $val === '-'))
2523 9
		{
2524 4
			$val = $var . ' ' . $val . ' 1';
2525 4
			$type = 'raw';
2526 4
		}
2527
2528
		// Ensure posts, personal_messages, and unread_messages don't overflow or underflow.
2529 9
		if (in_array($var, array('posts', 'personal_messages', 'unread_messages')))
2530 9
		{
2531 6
			if (preg_match('~^' . $var . ' (\+ |- |\+ -)([\d]+)~', $val, $match))
2532 6
			{
2533 6
				if ($match[1] != '+ ')
2534 6
					$val = 'CASE WHEN ' . $var . ' <= ' . abs($match[2]) . ' THEN 0 ELSE ' . $val . ' END';
2535 6
				$type = 'raw';
2536 6
			}
2537 6
		}
2538
2539 9
		$setString .= ' ' . $var . ' = {' . $type . ':p_' . $var . '},';
2540 9
		$parameters['p_' . $var] = $val;
2541 9
	}
2542
2543 9
	$db->query('', '
2544
		UPDATE {db_prefix}members
2545 9
		SET' . substr($setString, 0, -1) . '
2546 9
		WHERE ' . $condition,
2547
		$parameters
2548 9
	);
2549
2550 9
	require_once(SUBSDIR . '/Membergroups.subs.php');
2551 9
	updatePostGroupStats($members, array_keys($data));
2552
2553 9
	$cache = Cache::instance();
2554
2555
	// Clear any caching?
2556 9
	if ($cache->levelHigherThan(1) && !empty($members))
2557 9
	{
2558
		if (!is_array($members))
2559
			$members = array($members);
2560
2561
		foreach ($members as $member)
2562
		{
2563
			if ($cache->levelHigherThan(2))
2564
			{
2565
				$cache->remove('member_data-profile-' . $member);
2566
				$cache->remove('member_data-normal-' . $member);
2567
				$cache->remove('member_data-minimal-' . $member);
2568
			}
2569
2570
			$cache->remove('user_settings-' . $member);
2571
		}
2572
	}
2573 9
}
2574
2575
/**
2576
 * Loads members who are associated with an ip address
2577
 *
2578
 * @param string $ip_string raw value to use in where clause
2579
 * @param string $ip_var
2580
 */
2581
function loadMembersIPs($ip_string, $ip_var)
2582
{
2583
	global $scripturl;
2584
2585
	$db = database();
2586
2587
	$request = $db->query('', '
2588
		SELECT
2589
			id_member, real_name AS display_name, member_ip
2590
		FROM {db_prefix}members
2591
		WHERE member_ip ' . $ip_string,
2592
		array(
2593
			'ip_address' => $ip_var,
2594
		)
2595
	);
2596
	$ips = array();
2597 View Code Duplication
	while ($row = $db->fetch_assoc($request))
2598
		$ips[$row['member_ip']][] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>';
2599
	$db->free_result($request);
2600
2601
	ksort($ips);
2602
2603
	return $ips;
2604
}
2605