Completed
Pull Request — patch_1-1-4 (#3210)
by Emanuele
12:56
created

Bans.subs.php ➔ updateBanMembers()   C

Complexity

Conditions 14
Paths 64

Size

Total Lines 128

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 0
Metric Value
cc 14
nc 64
nop 0
dl 0
loc 128
rs 5.0133
c 0
b 0
f 0
ccs 0
cts 85
cp 0
crap 210

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 functions that are specifically done by administrators.
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
15
 *
16
 */
17
18
/**
19
 * Saves one or more ban triggers into a ban item: according to the suggestions
20
 *
21
 * What it does:
22
 *
23
 * - Checks the $_POST variable to verify if the trigger is present
24
 * - Load triggers in to an array for validation
25
 * - Validates and saves/updates the triggers for a given ban
26
 *
27
 * @package Bans
28
 * @param mixed[] $suggestions A bit messy array, it should look something like:
29
 *                 array(
30
 *                   'main_ip' => '123.123.123.123',
31
 *                   'hostname' => 'hostname.tld',
32
 *                   'email' => '[email protected]',
33
 *                   'ban_suggestions' => array(
34
 *                     'main_ip',     // <= these two are those that will be
35
 *                     'hostname',    // <= used for the ban, so no email
36
 *                     'other_ips' => array(
37
 *                       'ips_in_messages' => array(...),
38
 *                       'ips_in_errors' => array(...),
39
 *                       'other_custom' => array(...),
40
 *                     )
41
 *                   )
42
 *                 )
43
 * @param int $ban_group
44
 * @param int $member
45
 * @param int $trigger_id
46
 * @return mixed array with the saved triggers or false on failure
47
 */
48
function saveTriggers($suggestions, $ban_group, $member = 0, $trigger_id = 0)
49
{
50
	$triggers = array(
51
		'main_ip' => '',
52
		'hostname' => '',
53
		'email' => '',
54
		'member' => array(
55
			'id' => $member,
56
		)
57
	);
58
59
	$ban_errors = ElkArte\Errors\ErrorContext::context('ban', 1);
60
61
	if (!is_array($suggestions))
62
		return false;
63
64
	// What triggers are we adding (like ip, host, email, etc)
65
	foreach ($suggestions['ban_suggestions'] as $key => $value)
66
	{
67
		if (is_array($value))
68
			$triggers[$key] = $value;
69
		else
70
			$triggers[$value] = !empty($suggestions[$value]) ? $suggestions[$value] : '';
71
	}
72
73
	// Make sure the triggers for this ban are valid
74
	$ban_triggers = validateTriggers($triggers);
75
76
	// Time to save or update!
77
	if (!empty($ban_triggers['ban_triggers']) && !$ban_errors->hasErrors())
78
	{
79
		if (empty($trigger_id))
80
			addTriggers($ban_group, $ban_triggers['ban_triggers'], $ban_triggers['log_info']);
81
		else
82
			updateTriggers($trigger_id, $ban_group, array_shift($ban_triggers['ban_triggers']), $ban_triggers['log_info']);
83
	}
84
85
	// No errors, then return the ban triggers
86
	if ($ban_errors->hasErrors())
87
		return $triggers;
88
	else
89
		return false;
90
}
91
92
/**
93
 * This function removes a batch of triggers based on ids
94
 *
95
 * What it does:
96
 *
97
 * - Doesn't clean the inputs, expects valid input
98
 * - Removes the ban triggers by id or group
99
 *
100
 * @package Bans
101
 * @param int[]|int $items_ids
102
 * @param int|boolean $group_id
103
 * @return bool
104
 */
105
function removeBanTriggers($items_ids = array(), $group_id = false)
106
{
107
	$db = database();
108
109
	if ($group_id !== false)
110
		$group_id = (int) $group_id;
111
112
	if (empty($group_id) && empty($items_ids))
113
		return false;
114
115
	if (!is_array($items_ids))
116
		$items_ids = array($items_ids);
117
118
	// Log the ban removals so others know
119
	$log_info = banLogItems(banDetails($items_ids, $group_id));
120
	logTriggersUpdates($log_info, 'remove');
121
122
	// Remove the ban triggers by id's or groups
123
	if ($group_id !== false)
124
	{
125
		$db->query('', '
126
			DELETE FROM {db_prefix}ban_items
127
			WHERE id_ban IN ({array_int:ban_list})
128
				AND id_ban_group = {int:ban_group}',
129
			array(
130
				'ban_list' => $items_ids,
131
				'ban_group' => $group_id,
132
			)
133
		);
134
	}
135
	elseif (!empty($items_ids))
136
	{
137
		$db->query('', '
138
			DELETE FROM {db_prefix}ban_items
139
			WHERE id_ban IN ({array_int:ban_list})',
140
			array(
141
				'ban_list' => $items_ids,
142
			)
143
		);
144
	}
145
146
	return true;
147
}
148
149
/**
150
 * This function removes a batch of ban groups based on ids
151
 *
152
 * What it does:
153
 *
154
 * - Doesn't clean the inputs
155
 * - Removes entries from the ban group list, one or many
156
 *
157
 * @package Bans
158
 * @param int[]|int $group_ids
159
 * @return bool
160
 */
161
function removeBanGroups($group_ids)
162
{
163
	$db = database();
164
165
	if (!is_array($group_ids))
166
		$group_ids = array($group_ids);
167
168
	$group_ids = array_unique($group_ids);
169
170
	if (empty($group_ids))
171
		return false;
172
173
	$db->query('', '
174
		DELETE FROM {db_prefix}ban_groups
175
		WHERE id_ban_group IN ({array_int:ban_list})',
176
		array(
177
			'ban_list' => $group_ids,
178
		)
179
	);
180
181
	return true;
182
}
183
184
/**
185
 * Removes ban logs
186
 *
187
 * What it does:
188
 *
189
 * - By default (no id's passed) truncate the table
190
 * - Doesn't clean the inputs
191
 *
192
 * @package Bans
193
 * @param int[]|int|null $ids (optional)
194
 * @return bool
195
 */
196
function removeBanLogs($ids = array())
197
{
198
	$db = database();
199
200
	// No specific id's passed, we truncate the entire table
201
	if (empty($ids))
202
		$db->query('truncate_table', '
203
			TRUNCATE {db_prefix}log_banned',
204
			array(
205
			)
206
		);
207
	else
208
	{
209
		if (!is_array($ids))
210
			$ids = array($ids);
211
212
		// Can only remove it once
213
		$ids = array_unique($ids);
214
215
		if (empty($ids))
216
			return false;
217
218
		// Remove this grouping
219
		$db->query('', '
220
			DELETE FROM {db_prefix}log_banned
221
			WHERE id_ban_log IN ({array_int:ban_list})',
222
			array(
223
				'ban_list' => $ids,
224
			)
225
		);
226
	}
227
228
	return true;
229
}
230
231
/**
232
 * This function validates the ban triggers
233
 *
234
 * @package Bans
235
 * @param mixed[] $triggers
236
 */
237
function validateTriggers(&$triggers)
238
{
239
	$db = database();
240
241
	$ban_errors = ElkArte\Errors\ErrorContext::context('ban', 1);
242
	if (empty($triggers))
243
		$ban_errors->addError('ban_empty_triggers');
244
245
	$ban_triggers = array();
246
	$log_info = array();
247
248
	// Go through each trigger and make sure its valid
249
	foreach ($triggers as $key => $value)
250
	{
251
		if (!empty($value))
252
		{
253
			if ($key == 'member')
254
				continue;
255
256
			if ($key == 'main_ip')
257
			{
258
				$value = trim($value);
259
				$ip_parts = ip2range($value);
260
				$ban_trigger = validateIPBan($ip_parts, $value);
0 ignored issues
show
Documentation introduced by
$ip_parts is of type array|string, but the function expects a array<integer,integer>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
261
				if (empty($ban_trigger['error']))
262
				{
263
					$ban_triggers['main_ip'] = $ban_trigger;
264
				}
265
				else
266
					$ban_errors->addError($ban_trigger['error']);
267
			}
268
			elseif ($key == 'hostname')
269
			{
270
				if (preg_match('/[^\w.\-*]/', $value) == 1)
271
					$ban_errors->addError('invalid_hostname');
272
				else
273
				{
274
					// Replace the * wildcard by a MySQL wildcard %.
275
					$value = substr(str_replace('*', '%', $value), 0, 255);
276
277
					$ban_triggers['hostname']['hostname'] = $value;
278
				}
279
			}
280
			elseif ($key == 'email')
281
			{
282
				if (preg_match('/[^\w.\-\+*@]/', $value) == 1)
283
					$ban_errors->addError('invalid_email');
284
285
				// Check the user is not banning an admin.
286
				$request = $db->query('', '
287
					SELECT id_member
288
					FROM {db_prefix}members
289
					WHERE (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0)
290
						AND email_address LIKE {string:email}
291
					LIMIT 1',
292
					array(
293
						'admin_group' => 1,
294
						'email' => $value,
295
					)
296
				);
297
				if ($db->num_rows($request) != 0)
298
					$ban_errors->addError('no_ban_admin');
299
				$db->free_result($request);
300
301
				$value = substr(strtolower(str_replace('*', '%', $value)), 0, 255);
302
303
				$ban_triggers['email']['email_address'] = $value;
304
			}
305
			elseif ($key == 'user')
306
			{
307
				$user = preg_replace('~&amp;#(\d{4,5}|[2-9]\d{2,4}|1[2-9]\d);~', '&#$1;', Util::htmlspecialchars($value, ENT_QUOTES));
308
309
				$request = $db->query('', '
310
					SELECT id_member, (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0) AS isAdmin
311
					FROM {db_prefix}members
312
					WHERE member_name = {string:username} OR real_name = {string:username}
313
					LIMIT 1',
314
					array(
315
						'admin_group' => 1,
316
						'username' => $user,
317
					)
318
				);
319
				if ($db->num_rows($request) == 0)
320
					$ban_errors->addError('invalid_username');
321
				list ($value, $isAdmin) = $db->fetch_row($request);
322
				$db->free_result($request);
323
324
				if ($isAdmin && strtolower($isAdmin) != 'f')
325
				{
326
					unset($value);
327
					$ban_errors->addError('no_ban_admin');
328
				}
329
				else
330
					$ban_triggers['user']['id_member'] = $value;
331
			}
332
			elseif (in_array($key, array('ips_in_messages', 'ips_in_errors')))
333
			{
334
				// Special case, those two are arrays themselves
335
				$values = array_unique($value);
336
337
				// Don't add the main IP again.
338
				if (isset($triggers['main_ip']))
339
					$values = array_diff($values, array($triggers['main_ip']));
340
341
				unset($value);
342
				foreach ($values as $val)
343
				{
344
					$val = trim($val);
345
					$ip_parts = ip2range($val);
346
					$ban_trigger = validateIPBan($ip_parts, $val);
0 ignored issues
show
Documentation introduced by
$ip_parts is of type array|string, but the function expects a array<integer,integer>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
347
348
					if (empty($ban_trigger['error']))
349
					{
350
						$ban_triggers[$key][] = $ban_trigger;
351
352
						$log_info[] = array(
353
							'value' => $val,
354
							'bantype' => 'ip_range',
355
						);
356
					}
357
					else
358
						$ban_errors->addError($ban_trigger['error']);
359
				}
360
			}
361
			else
362
				$ban_errors->addError('no_bantype_selected');
363
364
			if (isset($value) && !is_array($value))
365
				$log_info[] = array(
366
					'value' => $value,
367
					'bantype' => $key,
368
				);
369
		}
370
	}
371
372
	return array('ban_triggers' => $ban_triggers, 'log_info' => $log_info);
373
}
374
375
/**
376
 * This function actually inserts the ban triggers into the database
377
 *
378
 * @package Bans
379
 * @param int $group_id
380
 * @param mixed[] $triggers associative array of trigger keys and the values
381
 * @param mixed[] $logs
382
 * @return boolean
383
 */
384
function addTriggers($group_id = 0, $triggers = array(), $logs = array())
385
{
386
	$db = database();
387
388
	$ban_errors = ElkArte\Errors\ErrorContext::context('ban', 1);
389
390
	if (empty($group_id))
391
		$ban_errors->addError('ban_not_found');
392
393
	// Preset all values that are required.
394
	$values = array(
395
		'id_ban_group' => $group_id,
396
		'hostname' => '',
397
		'email_address' => '',
398
		'id_member' => 0,
399
		'ip_low1' => 0,
400
		'ip_high1' => 0,
401
		'ip_low2' => 0,
402
		'ip_high2' => 0,
403
		'ip_low3' => 0,
404
		'ip_high3' => 0,
405
		'ip_low4' => 0,
406
		'ip_high4' => 0,
407
		'ip_low5' => 0,
408
		'ip_high5' => 0,
409
		'ip_low6' => 0,
410
		'ip_high6' => 0,
411
		'ip_low7' => 0,
412
		'ip_high7' => 0,
413
		'ip_low8' => 0,
414
		'ip_high8' => 0,
415
	);
416
417
	$insertKeys = array(
418
		'id_ban_group' => 'int',
419
		'hostname' => 'string',
420
		'email_address' => 'string',
421
		'id_member' => 'int',
422
		'ip_low1' => 'int',
423
		'ip_high1' => 'int',
424
		'ip_low2' => 'int',
425
		'ip_high2' => 'int',
426
		'ip_low3' => 'int',
427
		'ip_high3' => 'int',
428
		'ip_low4' => 'int',
429
		'ip_high4' => 'int',
430
		'ip_low5' => 'int',
431
		'ip_high5' => 'int',
432
		'ip_low6' => 'int',
433
		'ip_high6' => 'int',
434
		'ip_low7' => 'int',
435
		'ip_high7' => 'int',
436
		'ip_low8' => 'int',
437
		'ip_high8' => 'int',
438
	);
439
440
	$insertTriggers = array();
441
	foreach ($triggers as $key => $trigger)
442
	{
443
		// Exceptions, exceptions, exceptions...always exceptions... :P
444
		if (in_array($key, array('ips_in_messages', 'ips_in_errors')))
445
			foreach ($trigger as $real_trigger)
446
				$insertTriggers[] = array_merge($values, $real_trigger);
447
		else
448
			$insertTriggers[] = array_merge($values, $trigger);
449
	}
450
451
	if (empty($insertTriggers))
452
		$ban_errors->addError('ban_no_triggers');
453
454
	if ($ban_errors->hasErrors())
455
		return false;
456
457
	$db->insert('ignore',
458
		'{db_prefix}ban_items',
459
		$insertKeys,
460
		$insertTriggers,
461
		array('id_ban')
462
	);
463
464
	logTriggersUpdates($logs, true);
465
466
	return true;
467
}
468
469
/**
470
 * This function updates an existing ban trigger into the database
471
 *
472
 * @package Bans
473
 * @param int $ban_item
474
 * @param int $group_id
475
 * @param mixed[] $trigger associative array of ban trigger => value
476
 * @param mixed[] $logs
477
 */
478
function updateTriggers($ban_item = 0, $group_id = 0, $trigger = array(), $logs = array())
479
{
480
	$db = database();
481
482
	$ban_errors = ElkArte\Errors\ErrorContext::context('ban', 1);
483
484
	if (empty($ban_item))
485
		$ban_errors->addError('ban_ban_item_empty');
486
	if (empty($group_id))
487
		$ban_errors->addError('ban_not_found');
488
	if (empty($trigger))
489
		$ban_errors->addError('ban_no_triggers');
490
491
	// Any errors then we are not updating it
492
	if ($ban_errors->hasErrors())
493
		return;
494
495
	// Preset all values that are required.
496
	$values = array(
497
		'id_ban_group' => $group_id,
498
		'hostname' => '',
499
		'email_address' => '',
500
		'id_member' => 0,
501
		'ip_low1' => 0,
502
		'ip_high1' => 0,
503
		'ip_low2' => 0,
504
		'ip_high2' => 0,
505
		'ip_low3' => 0,
506
		'ip_high3' => 0,
507
		'ip_low4' => 0,
508
		'ip_high4' => 0,
509
		'ip_low5' => 0,
510
		'ip_high5' => 0,
511
		'ip_low6' => 0,
512
		'ip_high6' => 0,
513
		'ip_low7' => 0,
514
		'ip_high7' => 0,
515
		'ip_low8' => 0,
516
		'ip_high8' => 0,
517
	);
518
519
	$trigger = array_merge($values, $trigger);
520
521
	$db->query('', '
522
		UPDATE {db_prefix}ban_items
523
		SET
524
			hostname = {string:hostname}, email_address = {string:email_address}, id_member = {int:id_member},
525
			ip_low1 = {int:ip_low1}, ip_high1 = {int:ip_high1},
526
			ip_low2 = {int:ip_low2}, ip_high2 = {int:ip_high2},
527
			ip_low3 = {int:ip_low3}, ip_high3 = {int:ip_high3},
528
			ip_low4 = {int:ip_low4}, ip_high4 = {int:ip_high4},
529
			ip_low5 = {int:ip_low5}, ip_high5 = {int:ip_high5},
530
			ip_low6 = {int:ip_low6}, ip_high6 = {int:ip_high6},
531
			ip_low7 = {int:ip_low7}, ip_high7 = {int:ip_high7},
532
			ip_low8 = {int:ip_low8}, ip_high8 = {int:ip_high8}
533
		WHERE id_ban = {int:ban_item}
534
			AND id_ban_group = {int:id_ban_group}',
535
		array_merge($trigger, array(
536
			'id_ban_group' => $group_id,
537
			'ban_item' => $ban_item,
538
		))
539
	);
540
541
	logTriggersUpdates($logs, false);
542
}
543
544
/**
545
 * A small function to unify logging of triggers (updates and new)
546
 *
547
 * @package Bans
548
 * @param mixed[] $logs an array of logs, each log contains the following keys:
549
 * - bantype: a known type of ban (ip_range, hostname, email, user, main_ip)
550
 * - value: the value of the bantype (e.g. the IP or the email address banned)
551
 * @param boolean|string $new type of trigger
552
 * - if the trigger is new (true), an update (false), or a removal ('remove') of an existing one
553
 */
554
function logTriggersUpdates($logs, $new = true)
555
{
556
	if (empty($logs))
557
		return;
558
559
	$log_name_map = array(
560
		'main_ip' => 'ip_range',
561
		'hostname' => 'hostname',
562
		'email' => 'email',
563
		'user' => 'member',
564
		'ip_range' => 'ip_range',
565
	);
566
567
	// Log the addition of the ban entries into the moderation log.
568
	foreach ($logs as $log)
569
		logAction('ban', array(
570
			$log_name_map[$log['bantype']] => $log['value'],
571
			'new' => empty($new) ? 0 : ($new === true ? 1 : -1),
572
			'type' => $log['bantype'],
573
		));
574
}
575
576
/**
577
 * Updates an existing ban group
578
 *
579
 * - If the name doesn't exists a new one is created
580
 *
581
 * @package Bans
582
 * @param mixed[] $ban_info
583
 * @return nothing
584
 */
585
function updateBanGroup($ban_info = array())
586
{
587
	$db = database();
588
589
	// Lets check for errors first
590
	$ban_errors = ElkArte\Errors\ErrorContext::context('ban', 1);
591
592
	if (empty($ban_info['name']))
593
		$ban_errors->addError('ban_name_empty');
594
595
	if (empty($ban_info['id']))
596
		$ban_errors->addError('ban_id_empty');
597
598
	if ($ban_errors->hasErrors())
599
		return false;
600
601
	// No problems found, so lets add this to the ban list
602
	$request = $db->query('', '
603
		SELECT id_ban_group
604
		FROM {db_prefix}ban_groups
605
		WHERE name = {string:new_ban_name}
606
			AND id_ban_group = {int:ban_group}
607
		LIMIT 1',
608
		array(
609
			'ban_group' => $ban_info['id'],
610
			'new_ban_name' => $ban_info['name'],
611
		)
612
	);
613
	if ($db->num_rows($request) == 0)
614
		return insertBanGroup($ban_info);
615
	$db->free_result($request);
616
617
	$db->query('', '
618
		UPDATE {db_prefix}ban_groups
619
		SET
620
			name = {string:ban_name},
621
			reason = {string:reason},
622
			notes = {string:notes},
623
			expire_time = {raw:expiration},
624
			cannot_access = {int:cannot_access},
625
			cannot_post = {int:cannot_post},
626
			cannot_register = {int:cannot_register},
627
			cannot_login = {int:cannot_login}
628
		WHERE id_ban_group = {int:id_ban_group}',
629
		array(
630
			'expiration' => $ban_info['db_expiration'],
631
			'cannot_access' => $ban_info['cannot']['access'],
632
			'cannot_post' => $ban_info['cannot']['post'],
633
			'cannot_register' => $ban_info['cannot']['register'],
634
			'cannot_login' => $ban_info['cannot']['login'],
635
			'id_ban_group' => $ban_info['id'],
636
			'ban_name' => $ban_info['name'],
637
			'reason' => $ban_info['reason'],
638
			'notes' => $ban_info['notes'],
639
		)
640
	);
641
642
	return $ban_info['id'];
643
}
644
645
/**
646
 * Creates a new ban group
647
 *
648
 * What it does:
649
 *
650
 * - If a ban group with the same name already exists or the group s successfully created the ID is returned
651
 * - On error the error code is returned or false
652
 *
653
 * @package Bans
654
 * @param mixed[] $ban_info
655
 * @return int the ban group's ID
656
 */
657
function insertBanGroup($ban_info = array())
658
{
659
	$db = database();
660
661
	$ban_errors = ElkArte\Errors\ErrorContext::context('ban', 1);
662
663
	if (empty($ban_info['name']))
664
		$ban_errors->addError('ban_name_empty');
665
666
	if (empty($ban_info['cannot']['access']) && empty($ban_info['cannot']['register']) && empty($ban_info['cannot']['post']) && empty($ban_info['cannot']['login']))
667
		$ban_errors->addError('ban_unknown_restriction_type');
668
669
	if ($ban_errors->hasErrors())
670
		return false;
671
672
	// Check whether a ban with this name already exists.
673
	$request = $db->query('', '
674
		SELECT id_ban_group
675
		FROM {db_prefix}ban_groups
676
		WHERE name = {string:new_ban_name}' . '
677
		LIMIT 1',
678
		array(
679
			'new_ban_name' => $ban_info['name'],
680
		)
681
	);
682
683
	// @todo shouldn't be an error here?
684 View Code Duplication
	if ($db->num_rows($request) == 1)
685
	{
686
		list ($id_ban) = $db->fetch_row($request);
687
		$db->free_result($request);
688
		return $id_ban;
689
	}
690
	$db->free_result($request);
691
692
	// Yes yes, we're ready to add now.
693
	$db->insert('',
694
		'{db_prefix}ban_groups',
695
		array(
696
			'name' => 'string-20', 'ban_time' => 'int', 'expire_time' => 'raw', 'cannot_access' => 'int', 'cannot_register' => 'int',
697
			'cannot_post' => 'int', 'cannot_login' => 'int', 'reason' => 'string-255', 'notes' => 'string-65534',
698
		),
699
		array(
700
			$ban_info['name'], time(), $ban_info['db_expiration'], $ban_info['cannot']['access'], $ban_info['cannot']['register'],
701
			$ban_info['cannot']['post'], $ban_info['cannot']['login'], $ban_info['reason'], $ban_info['notes'],
702
		),
703
		array('id_ban_group')
704
	);
705
	$ban_info['id'] = $db->insert_id('{db_prefix}ban_groups', 'id_ban_group');
706
707
	if (empty($ban_info['id']))
708
		$ban_errors->addError('impossible_insert_new_bangroup');
709
710
	return $ban_info['id'];
711
}
712
713
/**
714
 * Convert a range of given IP number into a single string.
715
 *
716
 * - It's practically the reverse function of ip2range().
717
 *
718
 * @example
719
 * range2ip(array(10, 10, 10, 0), array(10, 10, 20, 255)) returns '10.10.10-20.*
720
 * @package Bans
721
 * @param int[] $low IPv4 format
722
 * @param int[] $high IPv4 format
723
 * @return string
724
 */
725
function range2ip($low, $high)
726
{
727
	$ip = array();
728
729
	// IPv6 check.
730
	if (!empty($high[4]) || !empty($high[5]) || !empty($high[6]) || !empty($high[7]))
731
	{
732
		if (count($low) != 8 || count($high) != 8)
733
			return '';
734
735
		for ($i = 0; $i < 8; $i++)
736
		{
737
			if ($low[$i] == $high[$i])
738
				$ip[$i] = dechex($low[$i]);
739 View Code Duplication
			elseif ($low[$i] == '0' && $high[$i] == '255')
740
				$ip[$i] = '*';
741
			else
742
				$ip[$i] = dechex($low[$i]) . '-' . dechex($high[$i]);
743
		}
744
745
		return implode(':', $ip);
746
	}
747
748
	// Legacy IPv4 stuff.
749
	// (count($low) != 4 || count($high) != 4) would not work because $low and $high always contain 8 elements!
750
	if ((count($low) != 4 || count($high) != 4) && (count($low) != 8 || count($high) != 8))
751
			return '';
752
753
	for ($i = 0; $i < 4; $i++)
754
	{
755
		if ($low[$i] == $high[$i])
756
			$ip[$i] = $low[$i];
757 View Code Duplication
		elseif ($low[$i] == '0' && $high[$i] == '255')
758
			$ip[$i] = '*';
759
		else
760
			$ip[$i] = $low[$i] . '-' . $high[$i];
761
	}
762
763
	// Pretending is fun... the IP can't be this, so use it for 'unknown'.
764
	if ($ip == array(255, 255, 255, 255))
765
		return 'unknown';
766
767
	return implode('.', $ip);
768
}
769
770
/**
771
 * Checks whether a given IP range already exists in the trigger list.
772
 *
773
 * What it does:
774
 *
775
 * - If yes, it returns an error message.
776
 * - Otherwise, it returns an array
777
 * - optimized for the database.
778
 *
779
 * @package Bans
780
 * @param int[] $ip_array array of ip array ints
781
 * @param string $fullip
782
 * @return boolean
783
 */
784
function validateIPBan($ip_array, $fullip = '')
785
{
786
	global $scripturl;
787
788
	$db = database();
789
790
	if (count($ip_array) == 4 || count($ip_array) == 8)
791
		$values = array(
792
			'ip_low1' => $ip_array[0]['low'],
793
			'ip_high1' => $ip_array[0]['high'],
794
			'ip_low2' => $ip_array[1]['low'],
795
			'ip_high2' => $ip_array[1]['high'],
796
			'ip_low3' => $ip_array[2]['low'],
797
			'ip_high3' => $ip_array[2]['high'],
798
			'ip_low4' => $ip_array[3]['low'],
799
			'ip_high4' => $ip_array[3]['high'],
800
			'ip_low5' => $ip_array[4]['low'],
801
			'ip_high5' => $ip_array[4]['high'],
802
			'ip_low6' => $ip_array[5]['low'],
803
			'ip_high6' => $ip_array[5]['high'],
804
			'ip_low7' => $ip_array[6]['low'],
805
			'ip_high7' => $ip_array[6]['high'],
806
			'ip_low8' => $ip_array[7]['low'],
807
			'ip_high8' => $ip_array[7]['high'],
808
		);
809
	else
810
		$values = array('error' => 'invalid_ip');
811
812
	$request = $db->query('', '
813
		SELECT bg.id_ban_group, bg.name
814
		FROM {db_prefix}ban_groups AS bg
815
		INNER JOIN {db_prefix}ban_items AS bi ON
816
			(bi.id_ban_group = bg.id_ban_group)
817
			AND ip_low1 = {int:ip_low1} AND ip_high1 = {int:ip_high1}
818
			AND ip_low2 = {int:ip_low2} AND ip_high2 = {int:ip_high2}
819
			AND ip_low3 = {int:ip_low3} AND ip_high3 = {int:ip_high3}
820
			AND ip_low4 = {int:ip_low4} AND ip_high4 = {int:ip_high4}
821
			AND ip_low5 = {int:ip_low5} AND ip_high5 = {int:ip_high5}
822
			AND ip_low6 = {int:ip_low6} AND ip_high6 = {int:ip_high6}
823
			AND ip_low7 = {int:ip_low7} AND ip_high7 = {int:ip_high7}
824
			AND ip_low8 = {int:ip_low8} AND ip_high8 = {int:ip_high8}
825
		LIMIT 1',
826
		$values
827
	);
828
	if ($db->num_rows($request) != 0)
829
	{
830
		list ($error_id_ban, $error_ban_name) = $db->fetch_row($request);
831
		$values = array('error' => array('ban_trigger_already_exists', array(
832
			$fullip,
833
			'<a href="' . $scripturl . '?action=admin;area=ban;sa=edit;bg=' . $error_id_ban . '">' . $error_ban_name . '</a>',
834
		)));
835
	}
836
	$db->free_result($request);
837
838
	return $values;
839
}
840
841
/**
842
 * Checks whether a given IP range already exists in the trigger list.
843
 *
844
 * What it does:
845
 *
846
 * - If yes, it returns an error message.
847
 * - Otherwise, it returns an array
848
 * - optimized for the database.
849
 *
850
 * @package Bans
851
 *
852
 * @param int[]  $ip_array array of ip array ints
853
 * @param string $fullip
854
 *
855
 * @return bool
856
 * @throws Elk_Exception
857
 * @deprecated since 1.1 - use validateIPBan instead
858
 */
859
function checkExistingTriggerIP($ip_array, $fullip = '')
860
{
861
	$return = validateIPBan($ip_array, $fullip);
862
863
	if (empty($return['error']))
864
		return $return;
865
866
	if ($return['error'] === 'ban_trigger_already_exists')
867
		throw new Elk_Exception($return['error'][0], false, $return['error'][1]);
868
869
	return false;
870
}
871
872
/**
873
 * As it says... this tries to review the list of banned members, to match new bans.
874
 *
875
 * - Note: is_activated >= 10: a member is banned.
876
 *
877
 * @package Bans
878
 */
879
function updateBanMembers()
880
{
881
	$db = database();
882
883
	$updates = array();
884
	$allMembers = array();
885
	$newMembers = array();
886
	$memberIDs = array();
887
	$memberEmails = array();
888
	$memberEmailWild = array();
889
890
	// Start by getting all active bans - it's quicker doing this in parts...
891
	$db->fetchQueryCallback('
892
		SELECT bi.id_member, bi.email_address
893
		FROM {db_prefix}ban_items AS bi
894
			INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group)
895
		WHERE (bi.id_member > {int:no_member} OR bi.email_address != {string:blank_string})
896
			AND bg.cannot_access = {int:cannot_access_on}
897
			AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time})',
898
		array(
899
			'no_member' => 0,
900
			'cannot_access_on' => 1,
901
			'current_time' => time(),
902
			'blank_string' => '',
903
		),
904
		function ($row) use (&$memberIDs, &$memberEmails, &$memberEmailWild)
905
		{
906
			if ($row['id_member'])
907
				$memberIDs[$row['id_member']] = $row['id_member'];
908
			if ($row['email_address'])
909
			{
910
				// Does it have a wildcard - if so we can't do a IN on it.
911
				if (strpos($row['email_address'], '%') !== false)
912
					$memberEmailWild[$row['email_address']] = $row['email_address'];
913
				else
914
					$memberEmails[$row['email_address']] = $row['email_address'];
915
			}
916
		}
917
	);
918
919
	// Build up the query.
920
	$queryPart = array();
921
	$queryValues = array();
922
	if (!empty($memberIDs))
923
	{
924
		$queryPart[] = 'mem.id_member IN ({array_string:member_ids})';
925
		$queryValues['member_ids'] = $memberIDs;
926
	}
927
928
	if (!empty($memberEmails))
929
	{
930
		$queryPart[] = 'mem.email_address IN ({array_string:member_emails})';
931
		$queryValues['member_emails'] = $memberEmails;
932
	}
933
934
	$count = 0;
935
	foreach ($memberEmailWild as $email)
936
	{
937
		$queryPart[] = 'mem.email_address LIKE {string:wild_' . $count . '}';
938
		$queryValues['wild_' . ($count++)] = $email;
939
	}
940
941
	// Find all banned members.
942
	if (!empty($queryPart))
943
	{
944
		$db->fetchQueryCallback('
945
			SELECT mem.id_member, mem.is_activated
946
			FROM {db_prefix}members AS mem
947
			WHERE ' . implode(' OR ', $queryPart),
948
			$queryValues,
949
			function ($row) use (&$allMembers, &$updates, &$newMembers)
950
			{
951
				if (!in_array($row['id_member'], $allMembers))
952
				{
953
					$allMembers[] = $row['id_member'];
954
					// Do they need an update?
955
					if ($row['is_activated'] < 10)
956
					{
957
						$updates[($row['is_activated'] + 10)][] = $row['id_member'];
958
						$newMembers[] = $row['id_member'];
959
					}
960
				}
961
			}
962
		);
963
	}
964
965
	// We welcome our new members in the realm of the banned.
966
	if (!empty($newMembers))
967
	{
968
		require_once(SUBSDIR . '/Logging.subs.php');
969
		logOnline($newMembers, false);
970
	}
971
972
	// Find members that are wrongfully marked as banned.
973
	$db->fetchQueryCallback('
974
		SELECT mem.id_member, mem.is_activated - 10 AS new_value
975
		FROM {db_prefix}members AS mem
976
			LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_member = mem.id_member OR mem.email_address LIKE bi.email_address)
977
			LEFT JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group AND bg.cannot_access = {int:cannot_access_activated} AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time}))
978
		WHERE (bi.id_ban IS NULL OR bg.id_ban_group IS NULL)
979
			AND mem.is_activated >= {int:ban_flag}',
980
		array(
981
			'cannot_access_activated' => 1,
982
			'current_time' => time(),
983
			'ban_flag' => 10,
984
		),
985
		function ($row) use (&$allMembers, &$updates)
986
		{
987
			// Don't do this twice!
988
			if (!in_array($row['id_member'], $allMembers))
989
			{
990
				$updates[$row['new_value']][] = $row['id_member'];
991
				$allMembers[] = $row['id_member'];
992
			}
993
		}
994
	);
995
996
	if (!empty($updates))
997
	{
998
		require_once(SUBSDIR . '/Members.subs.php');
999
		foreach ($updates as $newStatus => $members)
1000
			updateMemberData($members, array('is_activated' => $newStatus));
1001
	}
1002
1003
	// Update the latest member and our total members as banning may change them.
1004
	require_once(SUBSDIR . '/Members.subs.php');
1005
	updateMemberStats();
1006
}
1007
1008
/**
1009
 * Returns member data for a given member id in a suggestion format used by bans
1010
 *
1011
 * @package Bans
1012
 * @uses getBasicMemberData
1013
 * @param int $id
1014
 */
1015
function getMemberData($id)
1016
{
1017
	$suggestions = array();
1018
1019
	require_once(SUBSDIR . '/Members.subs.php');
1020
1021
	$result = getBasicMemberData($id, array('moderation' => true));
1022
	if (!empty($result))
1023
		$suggestions = array(
1024
			'member' => array(
1025
				'id' => $result['id_member'],
1026
				'name' => $result['real_name'],
1027
			),
1028
			'main_ip' => $result['member_ip'],
1029
			'email' => $result['email_address'],
1030
		);
1031
1032
	return $suggestions;
1033
}
1034
1035
/**
1036
 * Get ban triggers for the given parameters.
1037
 *
1038
 * @package Bans
1039
 * @param int $start The item to start with (for pagination purposes)
1040
 * @param int $items_per_page  The number of items to show per page
1041
 * @param string $sort A string indicating how to sort the results
1042
 * @param string $trigger_type
1043
 * @return array
1044
 */
1045
function list_getBanTriggers($start, $items_per_page, $sort, $trigger_type)
1046
{
1047
	$db = database();
1048
1049
	$where = array(
1050
		'ip' => 'bi.ip_low1 > 0',
1051
		'hostname' => 'bi.hostname != {string:blank_string}',
1052
		'email' => 'bi.email_address != {string:blank_string}',
1053
	);
1054
1055
	return $db->fetchQuery('
1056
		SELECT
1057
			bi.id_ban, bi.ip_low1, bi.ip_high1, bi.ip_low2, bi.ip_high2, bi.ip_low3, bi.ip_high3, bi.ip_low4, bi.ip_high4, bi.ip_low5, bi.ip_high5, bi.ip_low6, bi.ip_high6, bi.ip_low7, bi.ip_high7, bi.ip_low8, bi.ip_high8, bi.hostname, bi.email_address, bi.hits,
1058
			bg.id_ban_group, bg.name' . ($trigger_type === 'member' ? ',
1059
			mem.id_member, mem.real_name' : '') . '
1060
		FROM {db_prefix}ban_items AS bi
1061
			INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group)' . ($trigger_type === 'member' ? '
1062
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)' : '
1063
		WHERE ' . $where[$trigger_type]) . '
1064
		ORDER BY ' . $sort . '
1065
		LIMIT ' . $start . ', ' . $items_per_page,
1066
		array(
1067
			'blank_string' => '',
1068
		)
1069
	);
1070
}
1071
1072
/**
1073
 * Used to see if a user is banned
1074
 *
1075
 * - Checks banning by ip, hostname, email or member id
1076
 *
1077
 * @package Bans
1078
 * @param int $memID
1079
 * @param string $hostname
1080
 * @param string $email
1081
 */
1082
function BanCheckUser($memID, $hostname = '', $email = '')
1083
{
1084 1
	global $memberContext, $scripturl, $txt;
1085
1086 1
	$db = database();
1087 1
	$bans = array();
1088
1089
	// This is a valid member id, we at least need that
1090 1
	if (loadMemberContext($memID) && isset($memberContext[$memID]))
1091 1
	{
1092 1
		$ban_query = array();
1093
		$ban_query_vars = array(
1094 1
			'time' => time(),
1095 1
		);
1096
1097
		// Member id and ip
1098 1
		$ban_query[] = 'id_member = ' . $memID;
1099 1
		require_once(SOURCEDIR . '/Security.php');
1100 1
		$ban_query[] = constructBanQueryIP($memberContext[$memID]['ip']);
1101
1102
		// Do we have a hostname?
1103 1
		if (!empty($hostname))
1104 1
		{
1105
			$ban_query[] = '({string:hostname} LIKE hostname)';
1106
			$ban_query_vars['hostname'] = $hostname;
1107
		}
1108
1109
		// Check their email as well...
1110 1
		if (strlen($email) != 0)
1111 1
		{
1112 1
			$ban_query[] = '({string:email} LIKE bi.email_address)';
1113 1
			$ban_query_vars['email'] = $email;
1114 1
		}
1115
1116
		// So... are they banned?  Dying to know!
1117 1
		$request = $db->query('', '
1118
			SELECT bg.id_ban_group, bg.name, bg.cannot_access, bg.cannot_post, bg.cannot_register,
1119
				bg.cannot_login, bg.reason
1120
			FROM {db_prefix}ban_items AS bi
1121
				INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group AND (bg.expire_time IS NULL OR bg.expire_time > {int:time}))
1122 1
			WHERE (' . implode(' OR ', $ban_query) . ')',
1123
			$ban_query_vars
1124 1
		);
1125 1
		$bans = array();
1126 1
		while ($row = $db->fetch_assoc($request))
1127
		{
1128
			// Work out what restrictions we actually have.
1129
			$ban_restrictions = array();
1130
			foreach (array('access', 'register', 'login', 'post') as $type)
1131
				if ($row['cannot_' . $type])
1132
					$ban_restrictions[] = $txt['ban_type_' . $type];
1133
1134
			// No actual ban in place?
1135
			if (empty($ban_restrictions))
1136
				continue;
1137
1138
			// Prepare the link for context.
1139
			$ban_explanation = sprintf($txt['user_cannot_due_to'], implode(', ', $ban_restrictions), '<a href="' . $scripturl . '?action=admin;area=ban;sa=edit;bg=' . $row['id_ban_group'] . '">' . $row['name'] . '</a>');
1140
1141
			$bans[$row['id_ban_group']] = array(
1142
				'reason' => empty($row['reason']) ? '' : '<br /><br /><strong>' . $txt['ban_reason'] . ':</strong> ' . $row['reason'],
1143
				'cannot' => array(
1144
					'access' => !empty($row['cannot_access']),
1145
					'register' => !empty($row['cannot_register']),
1146
					'post' => !empty($row['cannot_post']),
1147
					'login' => !empty($row['cannot_login']),
1148
				),
1149
				'explanation' => $ban_explanation,
1150
			);
1151
		}
1152 1
		$db->free_result($request);
1153 1
	}
1154
1155 1
	return $bans;
1156
}
1157
1158
/**
1159
 * This returns the total number of ban triggers of the given type.
1160
 *
1161
 * @package Bans
1162
 * @param string $trigger_type
1163
 * @return int
1164
 */
1165
function list_getNumBanTriggers($trigger_type)
1166
{
1167
	$db = database();
1168
1169
	$where = array(
1170
		'ip' => 'bi.ip_low1 > 0',
1171
		'hostname' => 'bi.hostname != {string:blank_string}',
1172
		'email' => 'bi.email_address != {string:blank_string}',
1173
	);
1174
1175
	$request = $db->query('', '
1176
		SELECT COUNT(*)
1177
		FROM {db_prefix}ban_items AS bi' . ($trigger_type === 'member' ? '
1178
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)' : '
1179
		WHERE ' . $where[$trigger_type]),
1180
		array(
1181
			'blank_string' => '',
1182
		)
1183
	);
1184
	list ($num_triggers) = $db->fetch_row($request);
1185
	$db->free_result($request);
1186
1187
	return $num_triggers;
1188
}
1189
1190
/**
1191
 * Load a list of ban log entries from the database.
1192
 *
1193
 * - no permissions checks are done
1194
 *
1195
 * @package Bans
1196
 * @param int $start The item to start with (for pagination purposes)
1197
 * @param int $items_per_page  The number of items to show per page
1198
 * @param string $sort A string indicating how to sort the results
1199
 */
1200
function list_getBanLogEntries($start, $items_per_page, $sort)
1201
{
1202
	$db = database();
1203
1204
	return $db->fetchQuery('
1205
		SELECT lb.id_ban_log, lb.id_member, COALESCE(lb.ip, {string:dash}) AS ip, COALESCE(lb.email, {string:dash}) AS email, lb.log_time, COALESCE(mem.real_name, {string:blank_string}) AS real_name
1206
		FROM {db_prefix}log_banned AS lb
1207
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lb.id_member)
1208
		ORDER BY ' . $sort . '
1209
		LIMIT ' . $start . ', ' . $items_per_page,
1210
		array(
1211
			'blank_string' => '',
1212
			'dash' => '-',
1213
		)
1214
	);
1215
}
1216
1217
/**
1218
 * This returns the total count of ban log entries.
1219
 *
1220
 * @package Bans
1221
 */
1222
function list_getNumBanLogEntries()
1223
{
1224
	$db = database();
1225
1226
	$request = $db->query('', '
1227
		SELECT COUNT(*)
1228
		FROM {db_prefix}log_banned AS lb',
1229
		array(
1230
		)
1231
	);
1232
	list ($num_entries) = $db->fetch_row($request);
1233
	$db->free_result($request);
1234
1235
	return $num_entries;
1236
}
1237
1238
/**
1239
 * Get the total number of ban from the ban group table
1240
 *
1241
 * @package Bans
1242
 * @return int
1243
 */
1244
function list_getNumBans()
1245
{
1246
	$db = database();
1247
1248
	$request = $db->query('', '
1249
		SELECT COUNT(*) AS num_bans
1250
		FROM {db_prefix}ban_groups',
1251
		array(
1252
		)
1253
	);
1254
	list ($numBans) = $db->fetch_row($request);
1255
	$db->free_result($request);
1256
1257
	return $numBans;
1258
}
1259
1260
/**
1261
 * Retrieves all the ban items belonging to a certain ban group
1262
 *
1263
 * @package Bans
1264
 *
1265
 * @param int $start The item to start with (for pagination purposes)
1266
 * @param int $items_per_page The number of items to show per page
1267
 * @param int $sort A string indicating how to sort the results
1268
 * @param int $ban_group_id
1269
 *
1270
 * @return array
1271
 * @throws Elk_Exception ban_not_found
1272
 */
1273
function list_getBanItems($start = 0, $items_per_page = 0, $sort = 0, $ban_group_id = 0)
0 ignored issues
show
Unused Code introduced by
The parameter $sort is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1274
{
1275
	global $context, $scripturl;
1276
1277
	$db = database();
1278
1279
	$ban_items = array();
1280
	$request = $db->query('', '
1281
		SELECT
1282
			bi.id_ban, bi.hostname, bi.email_address, bi.id_member, bi.hits,
1283
			bi.ip_low1, bi.ip_high1, bi.ip_low2, bi.ip_high2, bi.ip_low3, bi.ip_high3, bi.ip_low4, bi.ip_high4,
1284
			bi.ip_low5, bi.ip_high5, bi.ip_low6, bi.ip_high6, bi.ip_low7, bi.ip_high7, bi.ip_low8, bi.ip_high8,
1285
			bg.id_ban_group, bg.name, bg.ban_time, bg.expire_time, bg.reason, bg.notes, bg.cannot_access, bg.cannot_register, bg.cannot_login, bg.cannot_post,
1286
			COALESCE(mem.id_member, 0) AS id_member, mem.member_name, mem.real_name
1287
		FROM {db_prefix}ban_groups AS bg
1288
			LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_ban_group = bg.id_ban_group)
1289
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)
1290
		WHERE bg.id_ban_group = {int:current_ban}
1291
		LIMIT {int:start}, {int:items_per_page}',
1292
		array(
1293
			'current_ban' => $ban_group_id,
1294
			'start' => $start,
1295
			'items_per_page' => $items_per_page,
1296
		)
1297
	);
1298
	if ($db->num_rows($request) == 0)
1299
		throw new Elk_Exception('ban_not_found', false);
1300
	while ($row = $db->fetch_assoc($request))
1301
	{
1302
		if (!isset($context['ban']))
1303
		{
1304
			$context['ban'] = array(
1305
				'id' => $row['id_ban_group'],
1306
				'name' => $row['name'],
1307
				'expiration' => array(
1308
					'status' => $row['expire_time'] === null ? 'never' : ($row['expire_time'] < time() ? 'expired' : 'one_day'),
1309
					'days' => $row['expire_time'] > time() ? floor(($row['expire_time'] - time()) / 86400) : 0
1310
				),
1311
				'reason' => $row['reason'],
1312
				'notes' => $row['notes'],
1313
				'cannot' => array(
1314
					'access' => !empty($row['cannot_access']),
1315
					'post' => !empty($row['cannot_post']),
1316
					'register' => !empty($row['cannot_register']),
1317
					'login' => !empty($row['cannot_login']),
1318
				),
1319
				'is_new' => false,
1320
				'hostname' => '',
1321
				'email' => '',
1322
			);
1323
		}
1324
1325
		if (!empty($row['id_ban']))
1326
		{
1327
			$ban_items[$row['id_ban']] = array(
1328
				'id' => $row['id_ban'],
1329
				'hits' => $row['hits'],
1330
			);
1331
			if (!empty($row['ip_high1']))
1332
			{
1333
				$ban_items[$row['id_ban']]['type'] = 'ip';
1334
				$ban_items[$row['id_ban']]['ip'] = range2ip(array($row['ip_low1'], $row['ip_low2'], $row['ip_low3'], $row['ip_low4'], $row['ip_low5'], $row['ip_low6'], $row['ip_low7'], $row['ip_low8']), array($row['ip_high1'], $row['ip_high2'], $row['ip_high3'], $row['ip_high4'], $row['ip_high5'], $row['ip_high6'], $row['ip_high7'], $row['ip_high8']));
1335
			}
1336 View Code Duplication
			elseif (!empty($row['hostname']))
1337
			{
1338
				$ban_items[$row['id_ban']]['type'] = 'hostname';
1339
				$ban_items[$row['id_ban']]['hostname'] = str_replace('%', '*', $row['hostname']);
1340
			}
1341 View Code Duplication
			elseif (!empty($row['email_address']))
1342
			{
1343
				$ban_items[$row['id_ban']]['type'] = 'email';
1344
				$ban_items[$row['id_ban']]['email'] = str_replace('%', '*', $row['email_address']);
1345
			}
1346
			elseif (!empty($row['id_member']))
1347
			{
1348
				$ban_items[$row['id_ban']]['type'] = 'user';
1349
				$ban_items[$row['id_ban']]['user'] = array(
1350
					'id' => $row['id_member'],
1351
					'name' => $row['real_name'],
1352
					'href' => $scripturl . '?action=profile;u=' . $row['id_member'],
1353
					'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>',
1354
				);
1355
			}
1356
			// Invalid ban (member probably doesn't exist anymore).
1357
			else
1358
			{
1359
				unset($ban_items[$row['id_ban']]);
1360
				removeBanTriggers($row['id_ban']);
1361
			}
1362
		}
1363
	}
1364
	$db->free_result($request);
1365
1366
	return $ban_items;
1367
}
1368
1369
/**
1370
 * Get bans, what else? For the given options.
1371
 *
1372
 * @package Bans
1373
 * @param int $start The item to start with (for pagination purposes)
1374
 * @param int $items_per_page  The number of items to show per page
1375
 * @param string $sort A string indicating how to sort the results
1376
 * @return array
1377
 */
1378
function list_getBans($start, $items_per_page, $sort)
1379
{
1380
	$db = database();
1381
1382
	return $db->fetchQuery('
1383
		SELECT bg.id_ban_group, bg.name, bg.ban_time, bg.expire_time, bg.reason, bg.notes, COUNT(bi.id_ban) AS num_triggers
1384
		FROM {db_prefix}ban_groups AS bg
1385
			LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_ban_group = bg.id_ban_group)
1386
		GROUP BY bg.id_ban_group, bg.name, bg.ban_time, bg.expire_time, bg.reason, bg.notes
1387
		ORDER BY {raw:sort}
1388
		LIMIT {int:offset}, {int:limit}',
1389
		array(
1390
			'sort' => $sort,
1391
			'offset' => $start,
1392
			'limit' => $items_per_page,
1393
		)
1394
	);
1395
}
1396
1397
/**
1398
 * Gets the number of ban items belonging to a certain ban group
1399
 *
1400
 * @package Bans
1401
 * @return int
1402
 */
1403
function list_getNumBanItems()
1404
{
1405
	global $context;
1406
1407
	$db = database();
1408
1409
	$ban_group_id = isset($context['ban_group_id']) ? (int) $context['ban_group_id'] : 0;
1410
1411
	$request = $db->query('', '
1412
		SELECT COUNT(bi.id_ban)
1413
		FROM {db_prefix}ban_groups AS bg
1414
			LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_ban_group = bg.id_ban_group)
1415
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)
1416
		WHERE bg.id_ban_group = {int:current_ban}',
1417
		array(
1418
			'current_ban' => $ban_group_id,
1419
		)
1420
	);
1421
	list ($banNumber) = $db->fetch_row($request);
1422
	$db->free_result($request);
1423
1424
	return $banNumber;
1425
}
1426
1427
/**
1428
 * Load other IPs the given member has used on forum while posting.
1429
 *
1430
 * @package Bans
1431
 * @param int $member_id
1432
 */
1433
function banLoadAdditionalIPsMember($member_id)
1434
{
1435
	$db = database();
1436
1437
	// Find some additional IP's used by this member.
1438
	$message_ips = array();
1439
	$request = $db->query('ban_suggest_message_ips', '
1440
		SELECT DISTINCT poster_ip
1441
		FROM {db_prefix}messages
1442
		WHERE id_member = {int:current_user}
1443
			AND poster_ip RLIKE {string:poster_ip_regex}
1444
		ORDER BY poster_ip',
1445
		array(
1446
			'current_user' => $member_id,
1447
			'poster_ip_regex' => '^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$',
1448
		)
1449
	);
1450
	while ($row = $db->fetch_assoc($request))
1451
		$message_ips[] = $row['poster_ip'];
1452
	$db->free_result($request);
1453
1454
	return $message_ips;
1455
}
1456
1457
/**
1458
 * Load other IPs the given member has received errors logged while they were using them.
1459
 *
1460
 * @package Bans
1461
 * @param int $member_id
1462
 */
1463
function banLoadAdditionalIPsError($member_id)
1464
{
1465
	$db = database();
1466
1467
	$error_ips = array();
1468
	$request = $db->query('ban_suggest_error_ips', '
1469
		SELECT DISTINCT ip
1470
		FROM {db_prefix}log_errors
1471
		WHERE id_member = {int:current_user}
1472
			AND ip RLIKE {string:poster_ip_regex}
1473
		ORDER BY ip',
1474
		array(
1475
			'current_user' => $member_id,
1476
			'poster_ip_regex' => '^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$',
1477
		)
1478
	);
1479
	while ($row = $db->fetch_assoc($request))
1480
		$error_ips[] = $row['ip'];
1481
	$db->free_result($request);
1482
1483
	return $error_ips;
1484
}
1485
1486
/**
1487
 * Finds additional IPs related to a certain user
1488
 *
1489
 * @package Bans
1490
 * @param int $member_id
1491
 * @return array
1492
 */
1493
function banLoadAdditionalIPs($member_id)
1494
{
1495
	// Borrowing a few language strings from profile.
1496
	loadLanguage('Profile');
1497
1498
	$search_list = array();
1499
	call_integration_hook('integrate_load_additional_ip_ban', array(&$search_list));
1500
	$search_list += array('ips_in_messages' => 'banLoadAdditionalIPsMember', 'ips_in_errors' => 'banLoadAdditionalIPsError');
1501
1502
	$return = array();
1503
	foreach ($search_list as $key => $callable)
1504
		if (is_callable($callable))
1505
			$return[$key] = call_user_func($callable, $member_id);
1506
1507
	return $return;
1508
}
1509
1510
/**
1511
 * Fetches ban details
1512
 *
1513
 * @package Bans
1514
 * @param int[]|int $ban_ids
1515
 * @param int|bool $ban_group
1516
 */
1517
function banDetails($ban_ids, $ban_group = false)
1518
{
1519
	$db = database();
1520
1521
	if (!is_array($ban_ids))
1522
		$ban_ids = array($ban_ids);
1523
1524
	$request = $db->query('', '
1525
		SELECT
1526
			bi.id_ban, bi.id_ban_group, bi.hostname, bi.email_address, bi.id_member,
1527
			bi.ip_low1, bi.ip_high1, bi.ip_low2, bi.ip_high2, bi.ip_low3, bi.ip_high3, bi.ip_low4, bi.ip_high4,
1528
			bi.ip_low5, bi.ip_high5, bi.ip_low6, bi.ip_high6, bi.ip_low7, bi.ip_high7, bi.ip_low8, bi.ip_high8,
1529
			mem.member_name, mem.real_name
1530
		FROM {db_prefix}ban_items AS bi
1531
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)
1532
		WHERE bi.id_ban IN ({array_int:ban_items})' . ($ban_group !== false ? '
1533
			AND bi.id_ban_group = {int:ban_group}' : ''),
1534
		array(
1535
			'ban_items' => $ban_ids,
1536
			'ban_group' => $ban_group,
1537
		)
1538
	);
1539
	$details = array();
1540
	while ($row = $db->fetch_assoc($request))
1541
		$details[$row['id_ban']] = $row;
1542
	$db->free_result($request);
1543
1544
	return $details;
1545
}
1546
1547
/**
1548
 * When removing a ban trigger, this will return the specifics of whats being
1549
 * removed so it can be logged
1550
 *
1551
 * @package Bans
1552
 * @param mixed[] $ban_details
1553
 */
1554
function banLogItems($ban_details)
1555
{
1556
	$log_info = array();
1557
1558
	// For each ban, get the details for logging
1559
	foreach ($ban_details as $row)
1560
	{
1561
		// An ip ban
1562
		if (!empty($row['ip_high1']))
1563
		{
1564
			$ip = range2ip(array($row['ip_low1'], $row['ip_low2'], $row['ip_low3'], $row['ip_low4'], $row['ip_low5'], $row['ip_low6'], $row['ip_low7'], $row['ip_low8']), array($row['ip_high1'], $row['ip_high2'], $row['ip_high3'], $row['ip_high4'], $row['ip_high5'], $row['ip_high6'], $row['ip_high7'], $row['ip_high8']));
1565
			$is_range = (strpos($ip, '-') !== false || strpos($ip, '*') !== false);
1566
1567
			$log_info[] = array(
1568
				'bantype' => ($is_range ? 'ip_range' : 'main_ip'),
1569
				'value' => $ip,
1570
			);
1571
		}
1572
		// Hostname
1573 View Code Duplication
		elseif (!empty($row['hostname']))
1574
		{
1575
			$log_info[] = array(
1576
				'bantype' => 'hostname',
1577
				'value' => $row['hostname'],
1578
			);
1579
		}
1580
		// Email Address
1581
		elseif (!empty($row['email_address']))
1582
		{
1583
			$log_info[] = array(
1584
				'bantype' => 'email',
1585
				'value' => str_replace('%', '*', $row['email_address']),
1586
			);
1587
		}
1588
		// Member ID
1589 View Code Duplication
		elseif (!empty($row['id_member']))
1590
		{
1591
			$log_info[] = array(
1592
				'bantype' => 'user',
1593
				'value' => $row['id_member'],
1594
			);
1595
		}
1596
	}
1597
1598
	return $log_info;
1599
}
1600