Completed
Pull Request — release-2.1 (#19)
by Mathias
09:32 queued 04:36
created

BanEditTrigger()   C

Complexity

Conditions 15
Paths 160

Size

Total Lines 103
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 60
nc 160
nop 0
dl 0
loc 103
rs 5.4166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file contains all the functions used for the ban center.
5
 *
6
 * @todo refactor as controller-model
7
 *
8
 * Simple Machines Forum (SMF)
9
 *
10
 * @package SMF
11
 * @author Simple Machines http://www.simplemachines.org
12
 * @copyright 2019 Simple Machines and individual contributors
13
 * @license http://www.simplemachines.org/about/smf/license.php BSD
14
 *
15
 * @version 2.1 RC1
16
 */
17
18
if (!defined('SMF'))
19
	die('No direct access...');
20
21
/**
22
 * Ban center. The main entrance point for all ban center functions.
23
 * It is accesssed by ?action=admin;area=ban.
24
 * It choses a function based on the 'sa' parameter, like many others.
25
 * The default sub-action is BanList().
26
 * It requires the ban_members permission.
27
 * It initializes the admin tabs.
28
 *
29
 * @uses ManageBans template.
30
 */
31
function Ban()
32
{
33
	global $context, $txt, $scripturl;
34
35
	isAllowedTo('manage_bans');
36
37
	loadTemplate('ManageBans');
38
39
	$subActions = array(
40
		'add' => 'BanEdit',
41
		'browse' => 'BanBrowseTriggers',
42
		'edittrigger' => 'BanEditTrigger',
43
		'edit' => 'BanEdit',
44
		'list' => 'BanList',
45
		'log' => 'BanLog',
46
	);
47
48
	// Default the sub-action to 'view ban list'.
49
	$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'list';
50
51
	$context['page_title'] = $txt['ban_title'];
52
	$context['sub_action'] = $_REQUEST['sa'];
53
54
	// Tabs for browsing the different ban functions.
55
	$context[$context['admin_menu_name']]['tab_data'] = array(
56
		'title' => $txt['ban_title'],
57
		'help' => 'ban_members',
58
		'description' => $txt['ban_description'],
59
		'tabs' => array(
60
			'list' => array(
61
				'description' => $txt['ban_description'],
62
				'href' => $scripturl . '?action=admin;area=ban;sa=list',
63
				'is_selected' => $_REQUEST['sa'] == 'list' || $_REQUEST['sa'] == 'edit' || $_REQUEST['sa'] == 'edittrigger',
64
			),
65
			'add' => array(
66
				'description' => $txt['ban_description'],
67
				'href' => $scripturl . '?action=admin;area=ban;sa=add',
68
				'is_selected' => $_REQUEST['sa'] == 'add',
69
			),
70
			'browse' => array(
71
				'description' => $txt['ban_trigger_browse_description'],
72
				'href' => $scripturl . '?action=admin;area=ban;sa=browse',
73
				'is_selected' => $_REQUEST['sa'] == 'browse',
74
			),
75
			'log' => array(
76
				'description' => $txt['ban_log_description'],
77
				'href' => $scripturl . '?action=admin;area=ban;sa=log',
78
				'is_selected' => $_REQUEST['sa'] == 'log',
79
				'is_last' => true,
80
			),
81
		),
82
	);
83
84
	call_integration_hook('integrate_manage_bans', array(&$subActions));
85
86
	// Call the right function for this sub-action.
87
	call_helper($subActions[$_REQUEST['sa']]);
88
}
89
90
/**
91
 * Shows a list of bans currently set.
92
 * It is accessed by ?action=admin;area=ban;sa=list.
93
 * It removes expired bans.
94
 * It allows sorting on different criteria.
95
 * It also handles removal of selected ban items.
96
 *
97
 * @uses the main ManageBans template.
98
 */
99
function BanList()
100
{
101
	global $txt, $context, $scripturl;
102
	global $user_info, $sourcedir, $modSettings;
103
104
	// User pressed the 'remove selection button'.
105
	if (!empty($_POST['removeBans']) && !empty($_POST['remove']) && is_array($_POST['remove']))
106
	{
107
		checkSession();
108
109
		// Make sure every entry is a proper integer.
110
		array_map('intval', $_POST['remove']);
111
112
		// Unban them all!
113
		removeBanGroups($_POST['remove']);
114
		removeBanTriggers($_POST['remove']);
115
116
		// No more caching this ban!
117
		updateSettings(array('banLastUpdated' => time()));
118
119
		// Some members might be unbanned now. Update the members table.
120
		updateBanMembers();
121
	}
122
123
	// Create a date string so we don't overload them with date info.
124
	if (preg_match('~%[AaBbCcDdeGghjmuYy](?:[^%]*%[AaBbCcDdeGghjmuYy])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0]))
125
		$context['ban_time_format'] = $user_info['time_format'];
126
	else
127
		$context['ban_time_format'] = $matches[0];
128
129
	$listOptions = array(
130
		'id' => 'ban_list',
131
		'title' => $txt['ban_title'],
132
		'items_per_page' => $modSettings['defaultMaxListItems'],
133
		'base_href' => $scripturl . '?action=admin;area=ban;sa=list',
134
		'default_sort_col' => 'added',
135
		'default_sort_dir' => 'desc',
136
		'get_items' => array(
137
			'function' => 'list_getBans',
138
		),
139
		'get_count' => array(
140
			'function' => 'list_getNumBans',
141
		),
142
		'no_items_label' => $txt['ban_no_entries'],
143
		'columns' => array(
144
			'name' => array(
145
				'header' => array(
146
					'value' => $txt['ban_name'],
147
				),
148
				'data' => array(
149
					'db' => 'name',
150
				),
151
				'sort' => array(
152
					'default' => 'bg.name',
153
					'reverse' => 'bg.name DESC',
154
				),
155
			),
156
			'notes' => array(
157
				'header' => array(
158
					'value' => $txt['ban_notes'],
159
				),
160
				'data' => array(
161
					'db' => 'notes',
162
					'class' => 'smalltext',
163
				),
164
				'sort' => array(
165
					'default' => 'LENGTH(bg.notes) > 0 DESC, bg.notes',
166
					'reverse' => 'LENGTH(bg.notes) > 0, bg.notes DESC',
167
				),
168
			),
169
			'reason' => array(
170
				'header' => array(
171
					'value' => $txt['ban_reason'],
172
				),
173
				'data' => array(
174
					'db' => 'reason',
175
					'class' => 'smalltext',
176
				),
177
				'sort' => array(
178
					'default' => 'LENGTH(bg.reason) > 0 DESC, bg.reason',
179
					'reverse' => 'LENGTH(bg.reason) > 0, bg.reason DESC',
180
				),
181
			),
182
			'added' => array(
183
				'header' => array(
184
					'value' => $txt['ban_added'],
185
				),
186
				'data' => array(
187
					'function' => function($rowData) use ($context)
188
					{
189
						return timeformat($rowData['ban_time'], empty($context['ban_time_format']) ? true : $context['ban_time_format']);
190
					},
191
				),
192
				'sort' => array(
193
					'default' => 'bg.ban_time',
194
					'reverse' => 'bg.ban_time DESC',
195
				),
196
			),
197
			'expires' => array(
198
				'header' => array(
199
					'value' => $txt['ban_expires'],
200
				),
201
				'data' => array(
202
					'function' => function($rowData) use ($txt)
203
					{
204
						// This ban never expires...whahaha.
205
						if ($rowData['expire_time'] === null)
206
							return $txt['never'];
207
208
						// This ban has already expired.
209
						elseif ($rowData['expire_time'] < time())
210
							return sprintf('<span class="red">%1$s</span>', $txt['ban_expired']);
211
212
						// Still need to wait a few days for this ban to expire.
213
						else
214
							return sprintf('%1$d&nbsp;%2$s', ceil(($rowData['expire_time'] - time()) / (60 * 60 * 24)), $txt['ban_days']);
215
					},
216
				),
217
				'sort' => array(
218
					'default' => 'COALESCE(bg.expire_time, 1=1) DESC, bg.expire_time DESC',
219
					'reverse' => 'COALESCE(bg.expire_time, 1=1), bg.expire_time',
220
				),
221
			),
222
			'num_triggers' => array(
223
				'header' => array(
224
					'value' => $txt['ban_triggers'],
225
				),
226
				'data' => array(
227
					'db' => 'num_triggers',
228
				),
229
				'sort' => array(
230
					'default' => 'num_triggers DESC',
231
					'reverse' => 'num_triggers',
232
				),
233
			),
234
			'actions' => array(
235
				'header' => array(
236
					'value' => $txt['ban_actions'],
237
					'class' => 'centercol',
238
				),
239
				'data' => array(
240
					'sprintf' => array(
241
						'format' => '<a href="' . $scripturl . '?action=admin;area=ban;sa=edit;bg=%1$d">' . $txt['modify'] . '</a>',
242
						'params' => array(
243
							'id_ban_group' => false,
244
						),
245
					),
246
					'class' => 'centercol',
247
				),
248
			),
249
			'check' => array(
250
				'header' => array(
251
					'value' => '<input type="checkbox" onclick="invertAll(this, this.form);">',
252
					'class' => 'centercol',
253
				),
254
				'data' => array(
255
					'sprintf' => array(
256
						'format' => '<input type="checkbox" name="remove[]" value="%1$d">',
257
						'params' => array(
258
							'id_ban_group' => false,
259
						),
260
					),
261
					'class' => 'centercol',
262
				),
263
			),
264
		),
265
		'form' => array(
266
			'href' => $scripturl . '?action=admin;area=ban;sa=list',
267
		),
268
		'additional_rows' => array(
269
			array(
270
				'position' => 'top_of_list',
271
				'value' => '<input type="submit" name="removeBans" value="' . $txt['ban_remove_selected'] . '" class="button">',
272
			),
273
			array(
274
				'position' => 'bottom_of_list',
275
				'value' => '<input type="submit" name="removeBans" value="' . $txt['ban_remove_selected'] . '" class="button">',
276
			),
277
		),
278
		'javascript' => '
279
		var removeBans = $("input[name=\'removeBans\']");
280
281
		removeBans.on( "click", function(e) {
282
			var removeItems = $("input[name=\'remove[]\']:checked").length;
283
284
			if (removeItems == 0)
285
			{
286
				e.preventDefault();
287
				return alert("' . $txt['select_item_check'] . '");
288
			}
289
290
291
			return confirm("' . $txt['ban_remove_selected_confirm'] . '");
292
		});',
293
	);
294
295
	require_once($sourcedir . '/Subs-List.php');
296
	createList($listOptions);
297
298
	$context['sub_template'] = 'show_list';
299
	$context['default_list'] = 'ban_list';
300
}
301
302
/**
303
 * Get bans, what else? For the given options.
304
 *
305
 * @param int $start Which item to start with (for pagination purposes)
306
 * @param int $items_per_page How many items to show on each page
307
 * @param string $sort A string telling ORDER BY how to sort the results
308
 * @return array An array of information about the bans for the list
309
 */
310
function list_getBans($start, $items_per_page, $sort)
311
{
312
	global $smcFunc;
313
314
	$request = $smcFunc['db_query']('', '
315
		SELECT bg.id_ban_group, bg.name, bg.ban_time, bg.expire_time, bg.reason, bg.notes, COUNT(bi.id_ban) AS num_triggers
316
		FROM {db_prefix}ban_groups AS bg
317
			LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_ban_group = bg.id_ban_group)
318
		GROUP BY bg.id_ban_group, bg.name, bg.ban_time, bg.expire_time, bg.reason, bg.notes
319
		ORDER BY {raw:sort}
320
		LIMIT {int:offset}, {int:limit}',
321
		array(
322
			'sort' => $sort,
323
			'offset' => $start,
324
			'limit' => $items_per_page,
325
		)
326
	);
327
	$bans = array();
328
	while ($row = $smcFunc['db_fetch_assoc']($request))
329
		$bans[] = $row;
330
331
	$smcFunc['db_free_result']($request);
332
333
	return $bans;
334
}
335
336
/**
337
 * Get the total number of ban from the ban group table
338
 *
339
 * @return int The total number of bans
340
 */
341
function list_getNumBans()
342
{
343
	global $smcFunc;
344
345
	$request = $smcFunc['db_query']('', '
346
		SELECT COUNT(*) AS num_bans
347
		FROM {db_prefix}ban_groups',
348
		array(
349
		)
350
	);
351
	list ($numBans) = $smcFunc['db_fetch_row']($request);
352
	$smcFunc['db_free_result']($request);
353
354
	return $numBans;
355
}
356
357
/**
358
 * This function is behind the screen for adding new bans and modifying existing ones.
359
 * Adding new bans:
360
 * 	- is accessed by ?action=admin;area=ban;sa=add.
361
 * 	- uses the ban_edit sub template of the ManageBans template.
362
 * Modifying existing bans:
363
 *  - is accessed by ?action=admin;area=ban;sa=edit;bg=x
364
 *  - uses the ban_edit sub template of the ManageBans template.
365
 *  - shows a list of ban triggers for the specified ban.
366
 */
367
function BanEdit()
368
{
369
	global $txt, $modSettings, $context, $scripturl, $smcFunc, $sourcedir;
370
371
	if ((isset($_POST['add_ban']) || isset($_POST['modify_ban']) || isset($_POST['remove_selection'])) && empty($context['ban_errors']))
372
		BanEdit2();
373
374
	$ban_group_id = isset($context['ban']['id']) ? $context['ban']['id'] : (isset($_REQUEST['bg']) ? (int) $_REQUEST['bg'] : 0);
375
376
	// Template needs this to show errors using javascript
377
	loadLanguage('Errors');
378
	createToken('admin-bet');
379
	$context['form_url'] = $scripturl . '?action=admin;area=ban;sa=edit';
380
381
	if (!empty($context['ban_errors']))
382
		foreach ($context['ban_errors'] as $error)
383
			$context['error_messages'][$error] = $txt[$error];
384
385
	else
386
	{
387
		// If we're editing an existing ban, get it from the database.
388
		if (!empty($ban_group_id))
389
		{
390
			$context['ban_group_id'] = $ban_group_id;
391
392
			// We're going to want this for making our list.
393
			require_once($sourcedir . '/Subs-List.php');
394
395
			$listOptions = array(
396
				'id' => 'ban_items',
397
				'base_href' => $scripturl . '?action=admin;area=ban;sa=edit;bg=' . $ban_group_id,
398
				'no_items_label' => $txt['ban_no_triggers'],
399
				'items_per_page' => $modSettings['defaultMaxListItems'],
400
				'get_items' => array(
401
					'function' => 'list_getBanItems',
402
					'params' => array(
403
						'ban_group_id' => $ban_group_id,
404
					),
405
				),
406
				'get_count' => array(
407
					'function' => 'list_getNumBanItems',
408
					'params' => array(
409
						'ban_group_id' => $ban_group_id,
410
					),
411
				),
412
				'columns' => array(
413
					'type' => array(
414
						'header' => array(
415
							'value' => $txt['ban_banned_entity'],
416
							'style' => 'width: 60%;text-align: left;',
417
						),
418
						'data' => array(
419
							'function' => function($ban_item) use ($txt)
420
							{
421
								if (in_array($ban_item['type'], array('ip', 'hostname', 'email')))
422
									return '<strong>' . $txt[$ban_item['type']] . ':</strong>&nbsp;' . $ban_item[$ban_item['type']];
423
								elseif ($ban_item['type'] == 'user')
424
									return '<strong>' . $txt['username'] . ':</strong>&nbsp;' . $ban_item['user']['link'];
425
								else
426
									return '<strong>' . $txt['unknown'] . ':</strong>&nbsp;' . $ban_item['no_bantype_selected'];
427
							},
428
							'style' => 'text-align: left;',
429
						),
430
					),
431
					'hits' => array(
432
						'header' => array(
433
							'value' => $txt['ban_hits'],
434
							'style' => 'width: 15%; text-align: center;',
435
						),
436
						'data' => array(
437
							'db' => 'hits',
438
							'style' => 'text-align: center;',
439
						),
440
					),
441
					'id' => array(
442
						'header' => array(
443
							'value' => $txt['ban_actions'],
444
							'style' => 'width: 15%; text-align: center;',
445
						),
446
						'data' => array(
447
							'function' => function($ban_item) use ($txt, $context, $scripturl)
448
							{
449
								return '<a href="' . $scripturl . '?action=admin;area=ban;sa=edittrigger;bg=' . $context['ban_group_id'] . ';bi=' . $ban_item['id'] . '">' . $txt['ban_edit_trigger'] . '</a>';
450
							},
451
							'style' => 'text-align: center;',
452
						),
453
					),
454
					'checkboxes' => array(
455
						'header' => array(
456
							'value' => '<input type="checkbox" onclick="invertAll(this, this.form, \'ban_items\');">',
457
							'style' => 'width: 5%; text-align: center;',
458
						),
459
						'data' => array(
460
							'sprintf' => array(
461
								'format' => '<input type="checkbox" name="ban_items[]" value="%1$d">',
462
								'params' => array(
463
									'id' => false,
464
								),
465
							),
466
							'style' => 'text-align: center;',
467
						),
468
					),
469
				),
470
				'form' => array(
471
					'href' => $scripturl . '?action=admin;area=ban;sa=edit;bg=' . $ban_group_id,
472
				),
473
				'additional_rows' => array(
474
					array(
475
						'position' => 'above_table_headers',
476
						'value' => '
477
						<input type="submit" name="remove_selection" value="' . $txt['ban_remove_selected_triggers'] . '" class="button"> <a class="button" href="' . $scripturl . '?action=admin;area=ban;sa=edittrigger;bg=' . $ban_group_id . '">' . $txt['ban_add_trigger'] . '</a>',
478
						'style' => 'text-align: right;',
479
					),
480
					array(
481
						'position' => 'above_table_headers',
482
						'value' => '
483
						<input type="hidden" name="bg" value="' . $ban_group_id . '">
484
						<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '">
485
						<input type="hidden" name="' . $context['admin-bet_token_var'] . '" value="' . $context['admin-bet_token'] . '">',
486
					),
487
					array(
488
						'position' => 'below_table_data',
489
						'value' => '
490
						<input type="submit" name="remove_selection" value="' . $txt['ban_remove_selected_triggers'] . '" class="button"> <a class="button" href="' . $scripturl . '?action=admin;area=ban;sa=edittrigger;bg=' . $ban_group_id . '">' . $txt['ban_add_trigger'] . '</a>',
491
						'style' => 'text-align: right;',
492
					),
493
					array(
494
						'position' => 'below_table_data',
495
						'value' => '
496
						<input type="hidden" name="bg" value="' . $ban_group_id . '">
497
						<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '">
498
						<input type="hidden" name="' . $context['admin-bet_token_var'] . '" value="' . $context['admin-bet_token'] . '">',
499
					),
500
				),
501
				'javascript' => '
502
		var removeBans = $("input[name=\'remove_selection\']");
503
504
		removeBans.on( "click", function(e) {
505
			var removeItems = $("input[name=\'ban_items[]\']:checked").length;
506
507
			if (removeItems == 0)
508
			{
509
				e.preventDefault();
510
				return alert("' . $txt['select_item_check'] . '");
511
			}
512
513
514
			return confirm("' . $txt['ban_remove_selected_confirm'] . '");
515
		});',
516
			);
517
			createList($listOptions);
518
		}
519
		// Not an existing one, then it's probably a new one.
520
		else
521
		{
522
			$context['ban'] = array(
523
				'id' => 0,
524
				'name' => '',
525
				'expiration' => array(
526
					'status' => 'never',
527
					'days' => 0
528
				),
529
				'reason' => '',
530
				'notes' => '',
531
				'ban_days' => 0,
532
				'cannot' => array(
533
					'access' => true,
534
					'post' => false,
535
					'register' => false,
536
					'login' => false,
537
				),
538
				'is_new' => true,
539
			);
540
			$context['ban_suggestions'] = array(
541
				'main_ip' => '',
542
				'hostname' => '',
543
				'email' => '',
544
				'member' => array(
545
					'id' => 0,
546
				),
547
			);
548
549
			// Overwrite some of the default form values if a user ID was given.
550
			if (!empty($_REQUEST['u']))
551
			{
552
				$request = $smcFunc['db_query']('', '
553
					SELECT id_member, real_name, member_ip, email_address
554
					FROM {db_prefix}members
555
					WHERE id_member = {int:current_user}
556
					LIMIT 1',
557
					array(
558
						'current_user' => (int) $_REQUEST['u'],
559
					)
560
				);
561
				if ($smcFunc['db_num_rows']($request) > 0)
562
				{
563
					list ($context['ban_suggestions']['member']['id'], $context['ban_suggestions']['member']['name'], $context['ban_suggestions']['main_ip'], $context['ban_suggestions']['email']) = $smcFunc['db_fetch_row']($request);
564
					$context['ban_suggestions']['main_ip'] = inet_dtop($context['ban_suggestions']['main_ip']);
565
				}
566
				$smcFunc['db_free_result']($request);
567
568
				if (!empty($context['ban_suggestions']['member']['id']))
569
				{
570
					$context['ban_suggestions']['href'] = $scripturl . '?action=profile;u=' . $context['ban_suggestions']['member']['id'];
571
					$context['ban_suggestions']['member']['link'] = '<a href="' . $context['ban_suggestions']['href'] . '">' . $context['ban_suggestions']['member']['name'] . '</a>';
572
573
					// Default the ban name to the name of the banned member.
574
					$context['ban']['name'] = $context['ban_suggestions']['member']['name'];
575
					// @todo: there should be a better solution...used to lock the "Ban on Username" input when banning from profile
576
					$context['ban']['from_user'] = true;
577
578
					// Would be nice if we could also ban the hostname.
579
					if ((preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $context['ban_suggestions']['main_ip']) == 1 || isValidIPv6($context['ban_suggestions']['main_ip'])) && empty($modSettings['disableHostnameLookup']))
580
						$context['ban_suggestions']['hostname'] = host_from_ip($context['ban_suggestions']['main_ip']);
581
582
					$context['ban_suggestions']['other_ips'] = banLoadAdditionalIPs($context['ban_suggestions']['member']['id']);
583
				}
584
			}
585
586
			// We come from the mod center.
587
			elseif (isset($_GET['msg']) && !empty($_GET['msg']))
588
			{
589
				$request = $smcFunc['db_query']('', '
590
					SELECT poster_name, poster_ip, poster_email
591
					FROM {db_prefix}messages
592
					WHERE id_msg = {int:message}
593
					LIMIT 1',
594
					array(
595
						'message' => (int) $_REQUEST['msg'],
596
					)
597
				);
598
				if ($smcFunc['db_num_rows']($request) > 0)
599
				{
600
					list ($context['ban_suggestions']['member']['name'], $context['ban_suggestions']['main_ip'], $context['ban_suggestions']['email']) = $smcFunc['db_fetch_row']($request);
601
					$context['ban_suggestions']['main_ip'] = inet_dtop($context['ban_suggestions']['main_ip']);
602
				}
603
				$smcFunc['db_free_result']($request);
604
605
				// Can't hurt to ban base on the guest name...
606
				$context['ban']['name'] = $context['ban_suggestions']['member']['name'];
607
				$context['ban']['from_user'] = true;
608
			}
609
		}
610
	}
611
612
	loadJavaScriptFile('suggest.js', array('minimize' => true), 'smf_suggest');
613
	$context['sub_template'] = 'ban_edit';
614
615
}
616
617
/**
618
 * Retrieves all the ban items belonging to a certain ban group
619
 *
620
 * @param int $start Which item to start with (for pagination purposes)
621
 * @param int $items_per_page How many items to show on each page
622
 * @param int $sort Not used here
623
 * @param int $ban_group_id The ID of the group to get the bans for
624
 * @return array An array with information about the returned ban items
625
 */
626
function list_getBanItems($start = 0, $items_per_page = 0, $sort = 0, $ban_group_id = 0)
627
{
628
	global $context, $smcFunc, $scripturl;
629
630
	$ban_items = array();
631
	$request = $smcFunc['db_query']('', '
632
		SELECT
633
			bi.id_ban, bi.hostname, bi.email_address, bi.id_member, bi.hits,
634
			bi.ip_low, bi.ip_high,
635
			bg.id_ban_group, bg.name, bg.ban_time, COALESCE(bg.expire_time, 0) AS expire_time, bg.reason, bg.notes, bg.cannot_access, bg.cannot_register, bg.cannot_login, bg.cannot_post,
636
			COALESCE(mem.id_member, 0) AS id_member, mem.member_name, mem.real_name
637
		FROM {db_prefix}ban_groups AS bg
638
			LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_ban_group = bg.id_ban_group)
639
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)
640
		WHERE bg.id_ban_group = {int:current_ban}
641
		LIMIT {int:start}, {int:items_per_page}',
642
		array(
643
			'current_ban' => $ban_group_id,
644
			'start' => $start,
645
			'items_per_page' => $items_per_page,
646
		)
647
	);
648
	if ($smcFunc['db_num_rows']($request) == 0)
649
		fatal_lang_error('ban_not_found', false);
650
651
	while ($row = $smcFunc['db_fetch_assoc']($request))
652
	{
653
		if (!isset($context['ban']))
654
		{
655
			$context['ban'] = array(
656
				'id' => $row['id_ban_group'],
657
				'name' => $row['name'],
658
				'expiration' => array(
659
					'status' => empty($row['expire_time']) ? 'never' : ($row['expire_time'] < time() ? 'expired' : 'one_day'),
660
					'days' => $row['expire_time'] > time() ? ($row['expire_time'] - time() < 86400 ? 1 : ceil(($row['expire_time'] - time()) / 86400)) : 0
661
				),
662
				'reason' => $row['reason'],
663
				'notes' => $row['notes'],
664
				'cannot' => array(
665
					'access' => !empty($row['cannot_access']),
666
					'post' => !empty($row['cannot_post']),
667
					'register' => !empty($row['cannot_register']),
668
					'login' => !empty($row['cannot_login']),
669
				),
670
				'is_new' => false,
671
				'hostname' => '',
672
				'email' => '',
673
			);
674
		}
675
676
		if (!empty($row['id_ban']))
677
		{
678
			$ban_items[$row['id_ban']] = array(
679
				'id' => $row['id_ban'],
680
				'hits' => $row['hits'],
681
			);
682
			if (!empty($row['ip_high']))
683
			{
684
				$ban_items[$row['id_ban']]['type'] = 'ip';
685
				$ban_items[$row['id_ban']]['ip'] = range2ip($row['ip_low'], $row['ip_high']);
686
			}
687
			elseif (!empty($row['hostname']))
688
			{
689
				$ban_items[$row['id_ban']]['type'] = 'hostname';
690
				$ban_items[$row['id_ban']]['hostname'] = str_replace('%', '*', $row['hostname']);
691
			}
692
			elseif (!empty($row['email_address']))
693
			{
694
				$ban_items[$row['id_ban']]['type'] = 'email';
695
				$ban_items[$row['id_ban']]['email'] = str_replace('%', '*', $row['email_address']);
696
			}
697
			elseif (!empty($row['id_member']))
698
			{
699
				$ban_items[$row['id_ban']]['type'] = 'user';
700
				$ban_items[$row['id_ban']]['user'] = array(
701
					'id' => $row['id_member'],
702
					'name' => $row['real_name'],
703
					'href' => $scripturl . '?action=profile;u=' . $row['id_member'],
704
					'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>',
705
				);
706
			}
707
			// Invalid ban (member probably doesn't exist anymore).
708
			else
709
			{
710
				unset($ban_items[$row['id_ban']]);
711
				removeBanTriggers($row['id_ban']);
712
			}
713
		}
714
	}
715
	$smcFunc['db_free_result']($request);
716
717
	return $ban_items;
718
}
719
720
/**
721
 * Gets the number of ban items belonging to a certain ban group
722
 *
723
 * @return int The number of ban items
724
 */
725
function list_getNumBanItems()
726
{
727
	global $smcFunc, $context;
728
729
	$ban_group_id = isset($context['ban_group_id']) ? $context['ban_group_id'] : 0;
730
731
	$request = $smcFunc['db_query']('', '
732
		SELECT COUNT(bi.id_ban)
733
		FROM {db_prefix}ban_groups AS bg
734
			LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_ban_group = bg.id_ban_group)
735
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)
736
		WHERE bg.id_ban_group = {int:current_ban}',
737
		array(
738
			'current_ban' => $ban_group_id,
739
		)
740
	);
741
	list($banNumber) = $smcFunc['db_fetch_row']($request);
742
	$smcFunc['db_free_result']($request);
743
744
	return $banNumber;
745
}
746
747
/**
748
 * Finds additional IPs related to a certain user
749
 *
750
 * @param int $member_id The ID of the member to get additional IPs for
751
 * @return array An containing two arrays - ips_in_messages (IPs used in posts) and ips_in_errors (IPs used in error messages)
752
 */
753
function banLoadAdditionalIPs($member_id)
754
{
755
	// Borrowing a few language strings from profile.
756
	loadLanguage('Profile');
757
758
	$search_list = array();
759
	call_integration_hook('integrate_load_addtional_ip_ban', array(&$search_list));
760
	$search_list += array('ips_in_messages' => 'banLoadAdditionalIPsMember', 'ips_in_errors' => 'banLoadAdditionalIPsError');
761
762
	$return = array();
763
	foreach ($search_list as $key => $callable)
764
		if (is_callable($callable))
765
			$return[$key] = call_user_func($callable, $member_id);
766
767
	return $return;
768
}
769
770
/**
771
 * @param int $member_id The ID of the member
772
 * @return array An array of IPs used in posts by this member
773
 */
774
function banLoadAdditionalIPsMember($member_id)
775
{
776
	global $smcFunc;
777
778
	// Find some additional IP's used by this member.
779
	$message_ips = array();
780
	$request = $smcFunc['db_query']('', '
781
		SELECT DISTINCT poster_ip
782
		FROM {db_prefix}messages
783
		WHERE id_member = {int:current_user}
784
			AND poster_ip IS NOT NULL
785
		ORDER BY poster_ip',
786
		array(
787
			'current_user' => $member_id,
788
		)
789
	);
790
	while ($row = $smcFunc['db_fetch_assoc']($request))
791
		$message_ips[] = inet_dtop($row['poster_ip']);
792
793
	$smcFunc['db_free_result']($request);
794
795
	return $message_ips;
796
}
797
798
/**
799
 * @param int $member_id The ID of the member
800
 * @return array An array of IPs associated with error messages generated by this user
801
 */
802
function banLoadAdditionalIPsError($member_id)
803
{
804
	global $smcFunc;
805
806
	$error_ips = array();
807
	$request = $smcFunc['db_query']('', '
808
		SELECT DISTINCT ip
809
		FROM {db_prefix}log_errors
810
		WHERE id_member = {int:current_user}
811
			AND ip IS NOT NULL
812
		ORDER BY ip',
813
		array(
814
			'current_user' => $member_id,
815
		)
816
	);
817
	while ($row = $smcFunc['db_fetch_assoc']($request))
818
		$error_ips[] = inet_dtop($row['ip']);
819
820
	$smcFunc['db_free_result']($request);
821
822
	return $error_ips;
823
}
824
825
/**
826
 * This function handles submitted forms that add, modify or remove ban triggers.
827
 */
828
function banEdit2()
829
{
830
	global $smcFunc, $context;
831
832
	checkSession();
833
	validateToken('admin-bet');
834
835
	$context['ban_errors'] = array();
836
837
	// Adding or editing a ban group
838
	if (isset($_POST['add_ban']) || isset($_POST['modify_ban']))
839
	{
840
		// Let's collect all the information we need
841
		$ban_info['id'] = isset($_REQUEST['bg']) ? (int) $_REQUEST['bg'] : 0;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$ban_info was never initialized. Although not strictly required by PHP, it is generally a good practice to add $ban_info = array(); before regardless.
Loading history...
842
		$ban_info['is_new'] = empty($ban_info['id']);
843
		$ban_info['expire_date'] = !empty($_POST['expire_date']) ? (int) $_POST['expire_date'] : 0;
844
		$ban_info['expiration'] = array(
845
			'status' => isset($_POST['expiration']) && in_array($_POST['expiration'], array('never', 'one_day', 'expired')) ? $_POST['expiration'] : 'never',
846
			'days' => $ban_info['expire_date'],
847
		);
848
		$ban_info['db_expiration'] = $ban_info['expiration']['status'] == 'never' ? 'NULL' : ($ban_info['expiration']['status'] == 'one_day' ? time() + 24 * 60 * 60 * $ban_info['expire_date'] : 0);
849
		$ban_info['full_ban'] = empty($_POST['full_ban']) ? 0 : 1;
850
		$ban_info['reason'] = !empty($_POST['reason']) ? $smcFunc['htmlspecialchars']($_POST['reason'], ENT_QUOTES) : '';
851
		$ban_info['name'] = !empty($_POST['ban_name']) ? $smcFunc['htmlspecialchars']($_POST['ban_name'], ENT_QUOTES) : '';
852
		$ban_info['notes'] = isset($_POST['notes']) ? $smcFunc['htmlspecialchars']($_POST['notes'], ENT_QUOTES) : '';
853
		$ban_info['notes'] = str_replace(array("\r", "\n", '  '), array('', '<br>', '&nbsp; '), $ban_info['notes']);
854
		$ban_info['cannot']['access'] = empty($ban_info['full_ban']) ? 0 : 1;
855
		$ban_info['cannot']['post'] = !empty($ban_info['full_ban']) || empty($_POST['cannot_post']) ? 0 : 1;
856
		$ban_info['cannot']['register'] = !empty($ban_info['full_ban']) || empty($_POST['cannot_register']) ? 0 : 1;
857
		$ban_info['cannot']['login'] = !empty($ban_info['full_ban']) || empty($_POST['cannot_login']) ? 0 : 1;
858
859
		// Adding a new ban group
860
		if (empty($_REQUEST['bg']))
861
			$ban_group_id = insertBanGroup($ban_info);
862
		// Editing an existing ban group
863
		else
864
			$ban_group_id = updateBanGroup($ban_info);
865
866
		if (is_numeric($ban_group_id))
867
		{
868
			$ban_info['id'] = $ban_group_id;
869
			$ban_info['is_new'] = false;
870
		}
871
872
		$context['ban'] = $ban_info;
873
	}
874
875
	if (isset($_POST['ban_suggestions']))
876
		// @TODO: is $_REQUEST['bi'] ever set?
877
		$saved_triggers = saveTriggers($_POST['ban_suggestions'], $ban_info['id'], isset($_REQUEST['u']) ? (int) $_REQUEST['u'] : 0, isset($_REQUEST['bi']) ? (int) $_REQUEST['bi'] : 0);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ban_info does not seem to be defined for all execution paths leading up to this point.
Loading history...
878
879
	// Something went wrong somewhere... Oh well, let's go back.
880
	if (!empty($context['ban_errors']))
881
	{
882
		$context['ban_suggestions'] = !empty($saved_triggers) ? $saved_triggers : array();
883
		$context['ban']['from_user'] = true;
884
		$context['ban_suggestions'] = array_merge($context['ban_suggestions'], getMemberData((int) $_REQUEST['u']));
885
886
		// Not strictly necessary, but it's nice
887
		if (!empty($context['ban_suggestions']['member']['id']))
888
			$context['ban_suggestions']['other_ips'] = banLoadAdditionalIPs($context['ban_suggestions']['member']['id']);
889
		return BanEdit();
890
	}
891
	$context['ban_suggestions']['saved_triggers'] = !empty($saved_triggers) ? $saved_triggers : array();
892
893
	if (isset($_POST['ban_items']))
894
	{
895
		$ban_group_id = isset($_REQUEST['bg']) ? (int) $_REQUEST['bg'] : 0;
896
		array_map('intval', $_POST['ban_items']);
897
898
		removeBanTriggers($_POST['ban_items'], $ban_group_id);
899
	}
900
901
	// Register the last modified date.
902
	updateSettings(array('banLastUpdated' => time()));
903
904
	// Update the member table to represent the new ban situation.
905
	updateBanMembers();
906
	redirectexit('action=admin;area=ban;sa=edit;bg=' . $ban_group_id);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ban_group_id does not seem to be defined for all execution paths leading up to this point.
Loading history...
907
}
908
909
/**
910
 * Saves one or more ban triggers into a ban item: according to the suggestions
911
 * checks the $_POST variable to verify if the trigger is present
912
 *
913
 * @param array $suggestions An array of suggestedtriggers (IP, email, etc.)
914
 * @param int $ban_group The ID of the group we're saving bans for
915
 * @param int $member The ID of the member associated with this ban (if applicable)
916
 * @param int $ban_id The ID of the ban (0 if this is a new ban)
917
 *
918
 * @return array|bool An array with the triggers if there were errors or false on success
919
 */
920
function saveTriggers($suggestions = array(), $ban_group, $member = 0, $ban_id = 0)
921
{
922
	global $context;
923
924
	$triggers = array(
925
		'main_ip' => '',
926
		'hostname' => '',
927
		'email' => '',
928
		'member' => array(
929
			'id' => $member,
930
		)
931
	);
932
933
	foreach ($suggestions as $key => $value)
934
	{
935
		if (is_array($value))
936
			$triggers[$key] = $value;
937
		else
938
			$triggers[$value] = !empty($_POST[$value]) ? $_POST[$value] : '';
939
	}
940
941
	$ban_triggers = validateTriggers($triggers);
942
943
	// Time to save!
944
	if (!empty($ban_triggers['ban_triggers']) && empty($context['ban_errors']))
945
	{
946
		if (empty($ban_id))
947
			addTriggers($ban_group, $ban_triggers['ban_triggers'], $ban_triggers['log_info']);
948
		else
949
			updateTriggers($ban_id, $ban_group, array_shift($ban_triggers['ban_triggers']), $ban_triggers['log_info']);
950
	}
951
	if (!empty($context['ban_errors']))
952
		return $triggers;
953
	else
954
		return false;
955
}
956
957
/**
958
 * This function removes a bunch of triggers based on ids
959
 * Doesn't clean the inputs
960
 *
961
 * @param array $items_ids The items to remove
962
 * @param bool|int $group_id The ID of the group these triggers are associated with or false if deleting them from all groups
963
 * @return bool Always returns true
964
 */
965
function removeBanTriggers($items_ids = array(), $group_id = false)
966
{
967
	global $smcFunc, $scripturl;
968
969
	if ($group_id !== false)
970
		$group_id = (int) $group_id;
971
972
	if (empty($group_id) && empty($items_ids))
973
		return false;
974
975
	if (!is_array($items_ids))
0 ignored issues
show
introduced by
The condition is_array($items_ids) is always true.
Loading history...
976
		$items_ids = array($items_ids);
977
978
	$log_info = array();
979
	$ban_items = array();
980
981
	// First order of business: Load up the info so we can log this...
982
	$request = $smcFunc['db_query']('', '
983
		SELECT
984
			bi.id_ban, bi.hostname, bi.email_address, bi.id_member, bi.hits,
985
			bi.ip_low, bi.ip_high,
986
			COALESCE(mem.id_member, 0) AS id_member, mem.member_name, mem.real_name
987
		FROM {db_prefix}ban_items AS bi
988
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)
989
		WHERE bi.id_ban IN ({array_int:ban_list})',
990
		array(
991
			'ban_list' => $items_ids,
992
		)
993
	);
994
995
	// Get all the info for the log
996
	while ($row = $smcFunc['db_fetch_assoc']($request))
997
	{
998
		if (!empty($row['id_ban']))
999
		{
1000
			$ban_items[$row['id_ban']] = array(
1001
				'id' => $row['id_ban'],
1002
			);
1003
			if (!empty($row['ip_high']))
1004
			{
1005
				$ban_items[$row['id_ban']]['type'] = 'ip';
1006
				$ban_items[$row['id_ban']]['ip'] = range2ip($row['ip_low'], $row['ip_high']);
1007
1008
				$is_range = (strpos($ban_items[$row['id_ban']]['ip'], '-') !== false || strpos($ban_items[$row['id_ban']]['ip'], '*') !== false);
1009
1010
				$log_info[] = array(
1011
					'bantype' => ($is_range ? 'ip_range' : 'main_ip'),
1012
					'value' => $ban_items[$row['id_ban']]['ip'],
1013
				);
1014
			}
1015
			elseif (!empty($row['hostname']))
1016
			{
1017
				$ban_items[$row['id_ban']]['type'] = 'hostname';
1018
				$ban_items[$row['id_ban']]['hostname'] = str_replace('%', '*', $row['hostname']);
1019
				$log_info[] = array(
1020
					'bantype' => 'hostname',
1021
					'value' => $row['hostname'],
1022
				);
1023
			}
1024
			elseif (!empty($row['email_address']))
1025
			{
1026
				$ban_items[$row['id_ban']]['type'] = 'email';
1027
				$ban_items[$row['id_ban']]['email'] = str_replace('%', '*', $row['email_address']);
1028
				$log_info[] = array(
1029
					'bantype' => 'email',
1030
					'value' => $ban_items[$row['id_ban']]['email'],
1031
				);
1032
			}
1033
			elseif (!empty($row['id_member']))
1034
			{
1035
				$ban_items[$row['id_ban']]['type'] = 'user';
1036
				$ban_items[$row['id_ban']]['user'] = array(
1037
					'id' => $row['id_member'],
1038
					'name' => $row['real_name'],
1039
					'href' => $scripturl . '?action=profile;u=' . $row['id_member'],
1040
					'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>',
1041
				);
1042
				$log_info[] = array(
1043
					'bantype' => 'user',
1044
					'value' => $row['id_member'],
1045
				);
1046
			}
1047
		}
1048
	}
1049
1050
	// Log this!
1051
	logTriggersUpdates($log_info, false, true);
1052
1053
	$smcFunc['db_free_result']($request);
1054
1055
	if ($group_id !== false)
1056
	{
1057
		$smcFunc['db_query']('', '
1058
			DELETE FROM {db_prefix}ban_items
1059
			WHERE id_ban IN ({array_int:ban_list})
1060
				AND id_ban_group = {int:ban_group}',
1061
			array(
1062
				'ban_list' => $items_ids,
1063
				'ban_group' => $group_id,
1064
			)
1065
		);
1066
	}
1067
	elseif (!empty($items_ids))
1068
	{
1069
		$smcFunc['db_query']('', '
1070
			DELETE FROM {db_prefix}ban_items
1071
			WHERE id_ban IN ({array_int:ban_list})',
1072
			array(
1073
				'ban_list' => $items_ids,
1074
			)
1075
		);
1076
	}
1077
1078
	return true;
1079
}
1080
1081
/**
1082
 * This function removes a bunch of ban groups based on ids
1083
 * Doesn't clean the inputs
1084
 *
1085
 * @param int[] $group_ids The IDs of the groups to remove
1086
 * @return bool Returns ture if successful or false if $group_ids is empty
1087
 */
1088
function removeBanGroups($group_ids)
1089
{
1090
	global $smcFunc;
1091
1092
	if (!is_array($group_ids))
0 ignored issues
show
introduced by
The condition is_array($group_ids) is always true.
Loading history...
1093
		$group_ids = array($group_ids);
1094
1095
	$group_ids = array_unique($group_ids);
1096
1097
	if (empty($group_ids))
1098
		return false;
1099
1100
	$smcFunc['db_query']('', '
1101
		DELETE FROM {db_prefix}ban_groups
1102
		WHERE id_ban_group IN ({array_int:ban_list})',
1103
		array(
1104
			'ban_list' => $group_ids,
1105
		)
1106
	);
1107
1108
	return true;
1109
}
1110
1111
/**
1112
 * Removes logs - by default truncate the table
1113
 * Doesn't clean the inputs
1114
 *
1115
 * @param array $ids Empty array to clear the ban log or the IDs of the log entries to remove
1116
 * @return bool Returns true if successful or false if $ids is invalid
1117
 */
1118
function removeBanLogs($ids = array())
1119
{
1120
	global $smcFunc;
1121
1122
	if (empty($ids))
1123
		$smcFunc['db_query']('truncate_table', '
1124
			TRUNCATE {db_prefix}log_banned',
1125
			array(
1126
			)
1127
		);
1128
	else
1129
	{
1130
		if (!is_array($ids))
0 ignored issues
show
introduced by
The condition is_array($ids) is always true.
Loading history...
1131
			$ids = array($ids);
1132
1133
		$ids = array_unique($ids);
1134
1135
		if (empty($ids))
1136
			return false;
1137
1138
		$smcFunc['db_query']('', '
1139
			DELETE FROM {db_prefix}log_banned
1140
			WHERE id_ban_log IN ({array_int:ban_list})',
1141
			array(
1142
				'ban_list' => $ids,
1143
			)
1144
		);
1145
	}
1146
1147
	return true;
1148
}
1149
1150
/**
1151
 * This function validates the ban triggers
1152
 *
1153
 * Errors in $context['ban_errors']
1154
 *
1155
 * @param array $triggers The triggers to validate
1156
 * @return array An array of riggers and log info ready to be used
1157
 */
1158
function validateTriggers(&$triggers)
1159
{
1160
	global $context, $smcFunc;
1161
1162
	if (empty($triggers))
1163
		$context['ban_erros'][] = 'ban_empty_triggers';
1164
1165
	$ban_triggers = array();
1166
	$log_info = array();
1167
1168
	foreach ($triggers as $key => $value)
1169
	{
1170
		if (!empty($value))
1171
		{
1172
			if ($key == 'member')
1173
				continue;
1174
1175
			if ($key == 'main_ip')
1176
			{
1177
				$value = trim($value);
1178
				$ip_parts = ip2range($value);
1179
				if (!checkExistingTriggerIP($ip_parts, $value))
1180
					$context['ban_erros'][] = 'invalid_ip';
1181
				else
1182
				{
1183
					$ban_triggers['main_ip'] = array(
1184
						'ip_low' => $ip_parts['low'],
1185
						'ip_high' => $ip_parts['high']
1186
					);
1187
				}
1188
			}
1189
			elseif ($key == 'hostname')
1190
			{
1191
				if (preg_match('/[^\w.\-*]/', $value) == 1)
1192
					$context['ban_erros'][] = 'invalid_hostname';
1193
				else
1194
				{
1195
					// Replace the * wildcard by a MySQL wildcard %.
1196
					$value = substr(str_replace('*', '%', $value), 0, 255);
1197
1198
					$ban_triggers['hostname']['hostname'] = $value;
1199
				}
1200
			}
1201
			elseif ($key == 'email')
1202
			{
1203
				if (preg_match('/[^\w.\-\+*@]/', $value) == 1)
1204
					$context['ban_erros'][] = 'invalid_email';
1205
1206
				// Check the user is not banning an admin.
1207
				$request = $smcFunc['db_query']('', '
1208
					SELECT id_member
1209
					FROM {db_prefix}members
1210
					WHERE (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0)
1211
						AND email_address LIKE {string:email}
1212
					LIMIT 1',
1213
					array(
1214
						'admin_group' => 1,
1215
						'email' => $value,
1216
					)
1217
				);
1218
				if ($smcFunc['db_num_rows']($request) != 0)
1219
					$context['ban_erros'][] = 'no_ban_admin';
1220
				$smcFunc['db_free_result']($request);
1221
1222
				$value = substr(strtolower(str_replace('*', '%', $value)), 0, 255);
1223
1224
				$ban_triggers['email']['email_address'] = $value;
1225
			}
1226
			elseif ($key == 'user')
1227
			{
1228
				$user = preg_replace('~&amp;#(\d{4,5}|[2-9]\d{2,4}|1[2-9]\d);~', '&#$1;', $smcFunc['htmlspecialchars']($value, ENT_QUOTES));
1229
1230
				$request = $smcFunc['db_query']('', '
1231
					SELECT id_member, (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0) AS isAdmin
1232
					FROM {db_prefix}members
1233
					WHERE member_name = {string:username} OR real_name = {string:username}
1234
					LIMIT 1',
1235
					array(
1236
						'admin_group' => 1,
1237
						'username' => $user,
1238
					)
1239
				);
1240
				if ($smcFunc['db_num_rows']($request) == 0)
1241
					$context['ban_erros'][] = 'invalid_username';
1242
				list ($value, $isAdmin) = $smcFunc['db_fetch_row']($request);
1243
				$smcFunc['db_free_result']($request);
1244
1245
				if ($isAdmin && strtolower($isAdmin) != 'f')
1246
				{
1247
					unset($value);
1248
					$context['ban_erros'][] = 'no_ban_admin';
1249
				}
1250
				else
1251
					$ban_triggers['user']['id_member'] = $value;
1252
			}
1253
			elseif (in_array($key, array('ips_in_messages', 'ips_in_errors')))
1254
			{
1255
				// Special case, those two are arrays themselves
1256
				$values = array_unique($value);
1257
				// Don't add the main IP again.
1258
				if (isset($triggers['main_ip']))
1259
					$values = array_diff($values, array($triggers['main_ip']));
1260
				unset($value);
1261
				foreach ($values as $val)
1262
				{
1263
					$val = trim($val);
1264
					$ip_parts = ip2range($val);
1265
					if (!checkExistingTriggerIP($ip_parts, $val))
1266
						$context['ban_erros'][] = 'invalid_ip';
1267
					else
1268
					{
1269
						$ban_triggers[$key][] = array(
1270
							'ip_low' => $ip_parts['low'],
1271
							'ip_high' => $ip_parts['high'],
1272
						);
1273
1274
						$log_info[] = array(
1275
							'value' => $val,
1276
							'bantype' => 'ip_range',
1277
						);
1278
					}
1279
				}
1280
			}
1281
			else
1282
				$context['ban_erros'][] = 'no_bantype_selected';
1283
1284
			if (isset($value) && !is_array($value))
1285
				$log_info[] = array(
1286
					'value' => $value,
1287
					'bantype' => $key,
1288
				);
1289
		}
1290
	}
1291
	return array('ban_triggers' => $ban_triggers, 'log_info' => $log_info);
1292
}
1293
1294
/**
1295
 * This function actually inserts the ban triggers into the database
1296
 *
1297
 * Errors in $context['ban_errors']
1298
 *
1299
 * @param int $group_id The ID of the group to add the triggers to (0 to create a new one)
1300
 * @param array $triggers The triggers to add
1301
 * @param array $logs The log data
1302
 * @return bool Whether or not the action was successful
1303
 */
1304
function addTriggers($group_id = 0, $triggers = array(), $logs = array())
1305
{
1306
	global $smcFunc, $context;
1307
1308
	if (empty($group_id))
1309
		$context['ban_errors'][] = 'ban_id_empty';
1310
1311
	// Preset all values that are required.
1312
	$values = array(
1313
		'id_ban_group' => $group_id,
1314
		'hostname' => '',
1315
		'email_address' => '',
1316
		'id_member' => 0,
1317
		'ip_low' => 'null',
1318
		'ip_high' => 'null',
1319
	);
1320
1321
	$insertKeys = array(
1322
		'id_ban_group' => 'int',
1323
		'hostname' => 'string',
1324
		'email_address' => 'string',
1325
		'id_member' => 'int',
1326
		'ip_low' => 'inet',
1327
		'ip_high' => 'inet',
1328
	);
1329
1330
	$insertTriggers = array();
1331
	foreach ($triggers as $key => $trigger)
1332
	{
1333
		// Exceptions, exceptions, exceptions...always exceptions... :P
1334
		if (in_array($key, array('ips_in_messages', 'ips_in_errors')))
1335
			foreach ($trigger as $real_trigger)
1336
				$insertTriggers[] = array_merge($values, $real_trigger);
1337
		else
1338
			$insertTriggers[] = array_merge($values, $trigger);
1339
	}
1340
1341
	if (empty($insertTriggers))
1342
		$context['ban_errors'][] = 'ban_no_triggers';
1343
1344
	if (!empty($context['ban_errors']))
1345
		return false;
1346
1347
	$smcFunc['db_insert']('',
1348
		'{db_prefix}ban_items',
1349
		$insertKeys,
1350
		$insertTriggers,
1351
		array('id_ban')
1352
	);
1353
1354
	logTriggersUpdates($logs, true);
1355
1356
	return true;
1357
}
1358
1359
/**
1360
 * This function updates an existing ban trigger into the database
1361
 *
1362
 * Errors in $context['ban_errors']
1363
 *
1364
 * @param int $ban_item The ID of the ban item
1365
 * @param int $group_id The ID of the ban group
1366
 * @param array $trigger An array of triggers
1367
 * @param array $logs An array of log info
1368
 */
1369
function updateTriggers($ban_item = 0, $group_id = 0, $trigger = array(), $logs = array())
1370
{
1371
	global $smcFunc, $context;
1372
1373
	if (empty($ban_item))
1374
		$context['ban_errors'][] = 'ban_ban_item_empty';
1375
	if (empty($group_id))
1376
		$context['ban_errors'][] = 'ban_id_empty';
1377
	if (empty($trigger))
1378
		$context['ban_errors'][] = 'ban_no_triggers';
1379
1380
	if (!empty($context['ban_errors']))
1381
		return;
1382
1383
	// Preset all values that are required.
1384
	$values = array(
1385
		'id_ban_group' => $group_id,
1386
		'hostname' => '',
1387
		'email_address' => '',
1388
		'id_member' => 0,
1389
		'ip_low' => 'null',
1390
		'ip_high' => 'null',
1391
	);
1392
1393
	$trigger = array_merge($values, $trigger);
1394
1395
	$smcFunc['db_query']('', '
1396
		UPDATE {db_prefix}ban_items
1397
		SET
1398
			hostname = {string:hostname}, email_address = {string:email_address}, id_member = {int:id_member},
1399
			ip_low = {inet:ip_low}, ip_high = {inet:ip_high}
1400
		WHERE id_ban = {int:ban_item}
1401
			AND id_ban_group = {int:id_ban_group}',
1402
		array_merge($trigger, array(
1403
			'id_ban_group' => $group_id,
1404
			'ban_item' => $ban_item,
1405
		))
1406
	);
1407
1408
	logTriggersUpdates($logs, false);
1409
}
1410
1411
/**
1412
 * A small function to unify logging of triggers (updates and new)
1413
 *
1414
 * @param array $logs an array of logs, each log contains the following keys:
1415
 *                - bantype: a known type of ban (ip_range, hostname, email, user, main_ip)
1416
 *                - value: the value of the bantype (e.g. the IP or the email address banned)
1417
 * @param bool $new Whether the trigger is new or an update of an existing one
1418
 * @param bool $removal Whether the trigger is being deleted
1419
 */
1420
function logTriggersUpdates($logs, $new = true, $removal = false)
1421
{
1422
	if (empty($logs))
1423
		return;
1424
1425
	$log_name_map = array(
1426
		'main_ip' => 'ip_range',
1427
		'hostname' => 'hostname',
1428
		'email' => 'email',
1429
		'user' => 'member',
1430
		'ip_range' => 'ip_range',
1431
	);
1432
1433
	// Log the addion of the ban entries into the moderation log.
1434
	foreach ($logs as $log)
1435
		logAction('ban' . ($removal == true ? 'remove' : ''), array(
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1436
			$log_name_map[$log['bantype']] => $log['value'],
1437
			'new' => empty($new) ? 0 : 1,
1438
			'remove' => empty($removal) ? 0 : 1,
1439
			'type' => $log['bantype'],
1440
		));
1441
}
1442
1443
/**
1444
 * Updates an existing ban group
1445
 *
1446
 * Errors in $context['ban_errors']
1447
 *
1448
 * @param array $ban_info An array of info about the ban group. Should have name and may also have an id.
1449
 * @return int The ban group's ID
1450
 */
1451
function updateBanGroup($ban_info = array())
1452
{
1453
	global $smcFunc, $context;
1454
1455
	if (empty($ban_info['name']))
1456
		$context['ban_errors'][] = 'ban_name_empty';
1457
	if (empty($ban_info['id']))
1458
		$context['ban_errors'][] = 'ban_id_empty';
1459
	if (empty($ban_info['cannot']['access']) && empty($ban_info['cannot']['register']) && empty($ban_info['cannot']['post']) && empty($ban_info['cannot']['login']))
1460
		$context['ban_errors'][] = 'ban_unknown_restriction_type';
1461
1462
	if (!empty($ban_info['id']))
1463
	{
1464
		// Verify the ban group exists.
1465
		$request = $smcFunc['db_query']('', '
1466
			SELECT id_ban_group
1467
			FROM {db_prefix}ban_groups
1468
			WHERE id_ban_group = {int:ban_group}
1469
			LIMIT 1',
1470
			array(
1471
				'ban_group' => $ban_info['id']
1472
			)
1473
		);
1474
1475
		if ($smcFunc['db_num_rows']($request) == 0)
1476
			$context['ban_errors'][] = 'ban_not_found';
1477
		$smcFunc['db_free_result']($request);
1478
	}
1479
1480
	if (!empty($ban_info['name']))
1481
	{
1482
		// Make sure the name does not already exist (Of course, if it exists in the ban group we are editing, proceed.)
1483
		$request = $smcFunc['db_query']('', '
1484
			SELECT id_ban_group
1485
			FROM {db_prefix}ban_groups
1486
			WHERE name = {string:new_ban_name}
1487
				AND id_ban_group != {int:ban_group}
1488
			LIMIT 1',
1489
			array(
1490
				'ban_group' => empty($ban_info['id']) ? 0 : $ban_info['id'],
1491
				'new_ban_name' => $ban_info['name'],
1492
			)
1493
		);
1494
		if ($smcFunc['db_num_rows']($request) != 0)
1495
			$context['ban_errors'][] = 'ban_name_exists';
1496
		$smcFunc['db_free_result']($request);
1497
	}
1498
1499
	if (!empty($context['ban_errors']))
1500
		return $ban_info['id'];
1501
1502
	$smcFunc['db_query']('', '
1503
		UPDATE {db_prefix}ban_groups
1504
		SET
1505
			name = {string:ban_name},
1506
			reason = {string:reason},
1507
			notes = {string:notes},
1508
			expire_time = {raw:expiration},
1509
			cannot_access = {int:cannot_access},
1510
			cannot_post = {int:cannot_post},
1511
			cannot_register = {int:cannot_register},
1512
			cannot_login = {int:cannot_login}
1513
		WHERE id_ban_group = {int:id_ban_group}',
1514
		array(
1515
			'expiration' => $ban_info['db_expiration'],
1516
			'cannot_access' => $ban_info['cannot']['access'],
1517
			'cannot_post' => $ban_info['cannot']['post'],
1518
			'cannot_register' => $ban_info['cannot']['register'],
1519
			'cannot_login' => $ban_info['cannot']['login'],
1520
			'id_ban_group' => $ban_info['id'],
1521
			'ban_name' => $ban_info['name'],
1522
			'reason' => $ban_info['reason'],
1523
			'notes' => $ban_info['notes'],
1524
		)
1525
	);
1526
1527
	return $ban_info['id'];
1528
}
1529
1530
/**
1531
 * Creates a new ban group
1532
 * If the group is successfully created the ID is returned
1533
 * On error the error code is returned or false
1534
 *
1535
 * Errors in $context['ban_errors']
1536
 *
1537
 * @param array $ban_info An array containing 'name', which is the name of the ban group
1538
 * @return int The ban group's ID
1539
 */
1540
function insertBanGroup($ban_info = array())
1541
{
1542
	global $smcFunc, $context;
1543
1544
	if (empty($ban_info['name']))
1545
		$context['ban_errors'][] = 'ban_name_empty';
1546
	if (empty($ban_info['cannot']['access']) && empty($ban_info['cannot']['register']) && empty($ban_info['cannot']['post']) && empty($ban_info['cannot']['login']))
1547
		$context['ban_errors'][] = 'ban_unknown_restriction_type';
1548
1549
	if (!empty($ban_info['name']))
1550
	{
1551
		// Check whether a ban with this name already exists.
1552
		$request = $smcFunc['db_query']('', '
1553
			SELECT id_ban_group
1554
			FROM {db_prefix}ban_groups
1555
			WHERE name = {string:new_ban_name}' . '
1556
			LIMIT 1',
1557
			array(
1558
				'new_ban_name' => $ban_info['name'],
1559
			)
1560
		);
1561
1562
		if ($smcFunc['db_num_rows']($request) == 1)
1563
			$context['ban_errors'][] = 'ban_name_exists';
1564
		$smcFunc['db_free_result']($request);
1565
	}
1566
1567
	if (!empty($context['ban_errors']))
1568
		return;
1569
1570
	// Yes yes, we're ready to add now.
1571
	$ban_info['id'] = $smcFunc['db_insert']('',
1572
		'{db_prefix}ban_groups',
1573
		array(
1574
			'name' => 'string-20', 'ban_time' => 'int', 'expire_time' => 'raw', 'cannot_access' => 'int', 'cannot_register' => 'int',
1575
			'cannot_post' => 'int', 'cannot_login' => 'int', 'reason' => 'string-255', 'notes' => 'string-65534',
1576
		),
1577
		array(
1578
			$ban_info['name'], time(), $ban_info['db_expiration'], $ban_info['cannot']['access'], $ban_info['cannot']['register'],
1579
			$ban_info['cannot']['post'], $ban_info['cannot']['login'], $ban_info['reason'], $ban_info['notes'],
1580
		),
1581
		array('id_ban_group'),
1582
		1
1583
	);
1584
1585
	if (empty($ban_info['id']))
1586
		$context['ban_errors'][] = 'impossible_insert_new_bangroup';
1587
1588
	return $ban_info['id'];
1589
}
1590
1591
/**
1592
 * This function handles the ins and outs of the screen for adding new ban
1593
 * triggers or modifying existing ones.
1594
 * Adding new ban triggers:
1595
 * 	- is accessed by ?action=admin;area=ban;sa=edittrigger;bg=x
1596
 * 	- uses the ban_edit_trigger sub template of ManageBans.
1597
 * Editing existing ban triggers:
1598
 *  - is accessed by ?action=admin;area=ban;sa=edittrigger;bg=x;bi=y
1599
 *  - uses the ban_edit_trigger sub template of ManageBans.
1600
 */
1601
function BanEditTrigger()
1602
{
1603
	global $context, $smcFunc, $scripturl;
1604
1605
	$context['sub_template'] = 'ban_edit_trigger';
1606
	$context['form_url'] = $scripturl . '?action=admin;area=ban;sa=edittrigger';
1607
1608
	$ban_group = isset($_REQUEST['bg']) ? (int) $_REQUEST['bg'] : 0;
1609
	$ban_id = isset($_REQUEST['bi']) ? (int) $_REQUEST['bi'] : 0;
1610
1611
	if (empty($ban_group))
1612
		fatal_lang_error('ban_not_found', false);
1613
1614
	if (isset($_POST['add_new_trigger']) && !empty($_POST['ban_suggestions']))
1615
	{
1616
		saveTriggers($_POST['ban_suggestions'], $ban_group, 0, $ban_id);
1617
		redirectexit('action=admin;area=ban;sa=edit' . (!empty($ban_group) ? ';bg=' . $ban_group : ''));
1618
	}
1619
	elseif (isset($_POST['edit_trigger']) && !empty($_POST['ban_suggestions']))
1620
	{
1621
		// The first replaces the old one, the others are added new (simplification, otherwise it would require another query and some work...)
1622
		saveTriggers($_POST['ban_suggestions'], $ban_group, 0, $ban_id);
1623
1624
		redirectexit('action=admin;area=ban;sa=edit' . (!empty($ban_group) ? ';bg=' . $ban_group : ''));
1625
	}
1626
	elseif (isset($_POST['edit_trigger']))
1627
	{
1628
		removeBanTriggers($ban_id);
0 ignored issues
show
Bug introduced by
$ban_id of type integer is incompatible with the type array expected by parameter $items_ids of removeBanTriggers(). ( Ignorable by Annotation )

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

1628
		removeBanTriggers(/** @scrutinizer ignore-type */ $ban_id);
Loading history...
1629
		redirectexit('action=admin;area=ban;sa=edit' . (!empty($ban_group) ? ';bg=' . $ban_group : ''));
1630
	}
1631
1632
	loadJavaScriptFile('suggest.js', array('minimize' => true), 'smf_suggest');
1633
1634
	if (empty($ban_id))
1635
	{
1636
		$context['ban_trigger'] = array(
1637
			'id' => 0,
1638
			'group' => $ban_group,
1639
			'ip' => array(
1640
				'value' => '',
1641
				'selected' => true,
1642
			),
1643
			'hostname' => array(
1644
				'selected' => false,
1645
				'value' => '',
1646
			),
1647
			'email' => array(
1648
				'value' => '',
1649
				'selected' => false,
1650
			),
1651
			'banneduser' => array(
1652
				'value' => '',
1653
				'selected' => false,
1654
			),
1655
			'is_new' => true,
1656
		);
1657
	}
1658
	else
1659
	{
1660
		$request = $smcFunc['db_query']('', '
1661
			SELECT
1662
				bi.id_ban, bi.id_ban_group, bi.hostname, bi.email_address, bi.id_member,
1663
				bi.ip_low, bi.ip_high,
1664
				mem.member_name, mem.real_name
1665
			FROM {db_prefix}ban_items AS bi
1666
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)
1667
			WHERE bi.id_ban = {int:ban_item}
1668
				AND bi.id_ban_group = {int:ban_group}
1669
			LIMIT 1',
1670
			array(
1671
				'ban_item' => $ban_id,
1672
				'ban_group' => $ban_group,
1673
			)
1674
		);
1675
		if ($smcFunc['db_num_rows']($request) == 0)
1676
			fatal_lang_error('ban_not_found', false);
1677
		$row = $smcFunc['db_fetch_assoc']($request);
1678
		$smcFunc['db_free_result']($request);
1679
1680
		$context['ban_trigger'] = array(
1681
			'id' => $row['id_ban'],
1682
			'group' => $row['id_ban_group'],
1683
			'ip' => array(
1684
				'value' => empty($row['ip_low']) ? '' : range2ip($row['ip_low'], $row['ip_high']),
1685
				'selected' => !empty($row['ip_low']),
1686
			),
1687
			'hostname' => array(
1688
				'value' => str_replace('%', '*', $row['hostname']),
1689
				'selected' => !empty($row['hostname']),
1690
			),
1691
			'email' => array(
1692
				'value' => str_replace('%', '*', $row['email_address']),
1693
				'selected' => !empty($row['email_address'])
1694
			),
1695
			'banneduser' => array(
1696
				'value' => $row['member_name'],
1697
				'selected' => !empty($row['member_name'])
1698
			),
1699
			'is_new' => false,
1700
		);
1701
	}
1702
1703
	createToken('admin-bet');
1704
}
1705
1706
/**
1707
 * This handles the screen for showing the banned entities
1708
 * It is accessed by ?action=admin;area=ban;sa=browse
1709
 * It uses sub-tabs for browsing by IP, hostname, email or username.
1710
 *
1711
 * @uses ManageBans template, browse_triggers sub template.
1712
 */
1713
function BanBrowseTriggers()
1714
{
1715
	global $modSettings, $context, $scripturl, $smcFunc, $txt;
1716
	global $sourcedir, $settings;
1717
1718
	if (!empty($_POST['remove_triggers']) && !empty($_POST['remove']) && is_array($_POST['remove']))
1719
	{
1720
		checkSession();
1721
1722
		removeBanTriggers($_POST['remove']);
1723
1724
		// Rehabilitate some members.
1725
		if ($_REQUEST['entity'] == 'member')
1726
			updateBanMembers();
1727
1728
		// Make sure the ban cache is refreshed.
1729
		updateSettings(array('banLastUpdated' => time()));
1730
	}
1731
1732
	$context['selected_entity'] = isset($_REQUEST['entity']) && in_array($_REQUEST['entity'], array('ip', 'hostname', 'email', 'member')) ? $_REQUEST['entity'] : 'ip';
1733
1734
	$listOptions = array(
1735
		'id' => 'ban_trigger_list',
1736
		'title' => $txt['ban_trigger_browse'],
1737
		'items_per_page' => $modSettings['defaultMaxListItems'],
1738
		'base_href' => $scripturl . '?action=admin;area=ban;sa=browse;entity=' . $context['selected_entity'],
1739
		'default_sort_col' => 'banned_entity',
1740
		'no_items_label' => $txt['ban_no_triggers'],
1741
		'get_items' => array(
1742
			'function' => 'list_getBanTriggers',
1743
			'params' => array(
1744
				$context['selected_entity'],
1745
			),
1746
		),
1747
		'get_count' => array(
1748
			'function' => 'list_getNumBanTriggers',
1749
			'params' => array(
1750
				$context['selected_entity'],
1751
			),
1752
		),
1753
		'columns' => array(
1754
			'banned_entity' => array(
1755
				'header' => array(
1756
					'value' => $txt['ban_banned_entity'],
1757
				),
1758
			),
1759
			'ban_name' => array(
1760
				'header' => array(
1761
					'value' => $txt['ban_name'],
1762
				),
1763
				'data' => array(
1764
					'sprintf' => array(
1765
						'format' => '<a href="' . $scripturl . '?action=admin;area=ban;sa=edit;bg=%1$d">%2$s</a>',
1766
						'params' => array(
1767
							'id_ban_group' => false,
1768
							'name' => false,
1769
						),
1770
					),
1771
				),
1772
				'sort' => array(
1773
					'default' => 'bg.name',
1774
					'reverse' => 'bg.name DESC',
1775
				),
1776
			),
1777
			'hits' => array(
1778
				'header' => array(
1779
					'value' => $txt['ban_hits'],
1780
				),
1781
				'data' => array(
1782
					'db' => 'hits',
1783
				),
1784
				'sort' => array(
1785
					'default' => 'bi.hits DESC',
1786
					'reverse' => 'bi.hits',
1787
				),
1788
			),
1789
			'check' => array(
1790
				'header' => array(
1791
					'value' => '<input type="checkbox" onclick="invertAll(this, this.form);">',
1792
					'class' => 'centercol',
1793
				),
1794
				'data' => array(
1795
					'sprintf' => array(
1796
						'format' => '<input type="checkbox" name="remove[]" value="%1$d">',
1797
						'params' => array(
1798
							'id_ban' => false,
1799
						),
1800
					),
1801
					'class' => 'centercol',
1802
				),
1803
			),
1804
		),
1805
		'form' => array(
1806
			'href' => $scripturl . '?action=admin;area=ban;sa=browse;entity=' . $context['selected_entity'],
1807
			'include_start' => true,
1808
			'include_sort' => true,
1809
		),
1810
		'additional_rows' => array(
1811
			array(
1812
				'position' => 'above_column_headers',
1813
				'value' => '<a href="' . $scripturl . '?action=admin;area=ban;sa=browse;entity=ip">' . ($context['selected_entity'] == 'ip' ? '<img src="' . $settings['images_url'] . '/selected.png" alt="&gt;"> ' : '') . $txt['ip'] . '</a>&nbsp;|&nbsp;<a href="' . $scripturl . '?action=admin;area=ban;sa=browse;entity=hostname">' . ($context['selected_entity'] == 'hostname' ? '<img src="' . $settings['images_url'] . '/selected.png" alt="&gt;"> ' : '') . $txt['hostname'] . '</a>&nbsp;|&nbsp;<a href="' . $scripturl . '?action=admin;area=ban;sa=browse;entity=email">' . ($context['selected_entity'] == 'email' ? '<img src="' . $settings['images_url'] . '/selected.png" alt="&gt;"> ' : '') . $txt['email'] . '</a>&nbsp;|&nbsp;<a href="' . $scripturl . '?action=admin;area=ban;sa=browse;entity=member">' . ($context['selected_entity'] == 'member' ? '<img src="' . $settings['images_url'] . '/selected.png" alt="&gt;"> ' : '') . $txt['username'] . '</a>',
1814
			),
1815
			array(
1816
				'position' => 'bottom_of_list',
1817
				'value' => '<input type="submit" name="remove_triggers" value="' . $txt['ban_remove_selected_triggers'] . '" data-confirm="' . $txt['ban_remove_selected_triggers_confirm'] . '" class="button you_sure">',
1818
			),
1819
		),
1820
	);
1821
1822
	// Specific data for the first column depending on the selected entity.
1823
	if ($context['selected_entity'] === 'ip')
1824
	{
1825
		$listOptions['columns']['banned_entity']['data'] = array(
1826
			'function' => function($rowData)
1827
			{
1828
				return range2ip(
1829
					$rowData['ip_low']
1830
					,
1831
					$rowData['ip_high']
1832
				);
1833
			},
1834
		);
1835
		$listOptions['columns']['banned_entity']['sort'] = array(
1836
			'default' => 'bi.ip_low, bi.ip_high, bi.ip_low',
1837
			'reverse' => 'bi.ip_low DESC, bi.ip_high DESC',
1838
		);
1839
	}
1840
	elseif ($context['selected_entity'] === 'hostname')
1841
	{
1842
		$listOptions['columns']['banned_entity']['data'] = array(
1843
			'function' => function($rowData) use ($smcFunc)
1844
			{
1845
				return strtr($smcFunc['htmlspecialchars']($rowData['hostname']), array('%' => '*'));
1846
			},
1847
		);
1848
		$listOptions['columns']['banned_entity']['sort'] = array(
1849
			'default' => 'bi.hostname',
1850
			'reverse' => 'bi.hostname DESC',
1851
		);
1852
	}
1853
	elseif ($context['selected_entity'] === 'email')
1854
	{
1855
		$listOptions['columns']['banned_entity']['data'] = array(
1856
			'function' => function($rowData) use ($smcFunc)
1857
			{
1858
				return strtr($smcFunc['htmlspecialchars']($rowData['email_address']), array('%' => '*'));
1859
			},
1860
		);
1861
		$listOptions['columns']['banned_entity']['sort'] = array(
1862
			'default' => 'bi.email_address',
1863
			'reverse' => 'bi.email_address DESC',
1864
		);
1865
	}
1866
	elseif ($context['selected_entity'] === 'member')
1867
	{
1868
		$listOptions['columns']['banned_entity']['data'] = array(
1869
			'sprintf' => array(
1870
				'format' => '<a href="' . $scripturl . '?action=profile;u=%1$d">%2$s</a>',
1871
				'params' => array(
1872
					'id_member' => false,
1873
					'real_name' => false,
1874
				),
1875
			),
1876
		);
1877
		$listOptions['columns']['banned_entity']['sort'] = array(
1878
			'default' => 'mem.real_name',
1879
			'reverse' => 'mem.real_name DESC',
1880
		);
1881
	}
1882
1883
	// Create the list.
1884
	require_once($sourcedir . '/Subs-List.php');
1885
	createList($listOptions);
1886
1887
	// The list is the only thing to show, so make it the default sub template.
1888
	$context['sub_template'] = 'show_list';
1889
	$context['default_list'] = 'ban_trigger_list';
1890
}
1891
1892
/**
1893
 * Get ban triggers for the given parameters. Callback from $listOptions['get_items'] in BanBrowseTriggers()
1894
 *
1895
 * @param int $start The item to start with (for pagination purposes)
1896
 * @param int $items_per_page How many items to show on each page
1897
 * @param string $sort A string telling ORDER BY how to sort the results
1898
 * @param string $trigger_type The trigger type - can be 'ip', 'hostname' or 'email'
1899
 * @return array An array of ban trigger info for the list
1900
 */
1901
function list_getBanTriggers($start, $items_per_page, $sort, $trigger_type)
1902
{
1903
	global $smcFunc;
1904
1905
	$where = array(
1906
		'ip' => 'bi.ip_low is not null',
1907
		'hostname' => 'bi.hostname != {string:blank_string}',
1908
		'email' => 'bi.email_address != {string:blank_string}',
1909
	);
1910
1911
	$request = $smcFunc['db_query']('', '
1912
		SELECT
1913
			bi.id_ban, bi.ip_low, bi.ip_high, bi.hostname, bi.email_address, bi.hits,
1914
			bg.id_ban_group, bg.name' . ($trigger_type === 'member' ? ',
1915
			mem.id_member, mem.real_name' : '') . '
1916
		FROM {db_prefix}ban_items AS bi
1917
			INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group)' . ($trigger_type === 'member' ? '
1918
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)' : '
1919
		WHERE ' . $where[$trigger_type]) . '
1920
		ORDER BY {raw:sort}
1921
		LIMIT {int:start}, {int:max}',
1922
		array(
1923
			'blank_string' => '',
1924
			'sort' => $sort,
1925
			'start' => $start,
1926
			'max' => $items_per_page,
1927
		)
1928
	);
1929
	$ban_triggers = array();
1930
	while ($row = $smcFunc['db_fetch_assoc']($request))
1931
		$ban_triggers[] = $row;
1932
	$smcFunc['db_free_result']($request);
1933
1934
	return $ban_triggers;
1935
}
1936
1937
/**
1938
 * This returns the total number of ban triggers of the given type. Callback for $listOptions['get_count'] in BanBrowseTriggers().
1939
 *
1940
 * @param string $trigger_type The trigger type. Can be 'ip', 'hostname' or 'email'
1941
 * @return int The number of triggers of the specified type
1942
 */
1943
function list_getNumBanTriggers($trigger_type)
1944
{
1945
	global $smcFunc;
1946
1947
	$where = array(
1948
		'ip' => 'bi.ip_low is not null',
1949
		'hostname' => 'bi.hostname != {string:blank_string}',
1950
		'email' => 'bi.email_address != {string:blank_string}',
1951
	);
1952
1953
	$request = $smcFunc['db_query']('', '
1954
		SELECT COUNT(*)
1955
		FROM {db_prefix}ban_items AS bi' . ($trigger_type === 'member' ? '
1956
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = bi.id_member)' : '
1957
		WHERE ' . $where[$trigger_type]),
1958
		array(
1959
			'blank_string' => '',
1960
		)
1961
	);
1962
	list ($num_triggers) = $smcFunc['db_fetch_row']($request);
1963
	$smcFunc['db_free_result']($request);
1964
1965
	return $num_triggers;
1966
}
1967
1968
/**
1969
 * This handles the listing of ban log entries, and allows their deletion.
1970
 * Shows a list of logged access attempts by banned users.
1971
 * It is accessed by ?action=admin;area=ban;sa=log.
1972
 * How it works:
1973
 *  - allows sorting of several columns.
1974
 *  - also handles deletion of (a selection of) log entries.
1975
 */
1976
function BanLog()
1977
{
1978
	global $scripturl, $context, $sourcedir, $txt, $modSettings;
1979
1980
	// Delete one or more entries.
1981
	if (!empty($_POST['removeAll']) || (!empty($_POST['removeSelected']) && !empty($_POST['remove'])))
1982
	{
1983
		checkSession();
1984
		validateToken('admin-bl');
1985
1986
		// 'Delete all entries' button was pressed.
1987
		if (!empty($_POST['removeAll']))
1988
			removeBanLogs();
1989
		// 'Delete selection' button was pressed.
1990
		else
1991
		{
1992
			array_map('intval', $_POST['remove']);
1993
			removeBanLogs($_POST['remove']);
1994
		}
1995
	}
1996
1997
	$listOptions = array(
1998
		'id' => 'ban_log',
1999
		'title' => $txt['ban_log'],
2000
		'items_per_page' => $modSettings['defaultMaxListItems'],
2001
		'base_href' => $context['admin_area'] == 'ban' ? $scripturl . '?action=admin;area=ban;sa=log' : $scripturl . '?action=admin;area=logs;sa=banlog',
2002
		'default_sort_col' => 'date',
2003
		'get_items' => array(
2004
			'function' => 'list_getBanLogEntries',
2005
		),
2006
		'get_count' => array(
2007
			'function' => 'list_getNumBanLogEntries',
2008
		),
2009
		'no_items_label' => $txt['ban_log_no_entries'],
2010
		'columns' => array(
2011
			'ip' => array(
2012
				'header' => array(
2013
					'value' => $txt['ban_log_ip'],
2014
				),
2015
				'data' => array(
2016
					'sprintf' => array(
2017
						'format' => '<a href="' . $scripturl . '?action=trackip;searchip=%1$s">%1$s</a>',
2018
						'params' => array(
2019
							'ip' => false,
2020
						),
2021
					),
2022
				),
2023
				'sort' => array(
2024
					'default' => 'lb.ip',
2025
					'reverse' => 'lb.ip DESC',
2026
				),
2027
			),
2028
			'email' => array(
2029
				'header' => array(
2030
					'value' => $txt['ban_log_email'],
2031
				),
2032
				'data' => array(
2033
					'db_htmlsafe' => 'email',
2034
				),
2035
				'sort' => array(
2036
					'default' => 'lb.email = \'\', lb.email',
2037
					'reverse' => 'lb.email != \'\', lb.email DESC',
2038
				),
2039
			),
2040
			'member' => array(
2041
				'header' => array(
2042
					'value' => $txt['ban_log_member'],
2043
				),
2044
				'data' => array(
2045
					'sprintf' => array(
2046
						'format' => '<a href="' . $scripturl . '?action=profile;u=%1$d">%2$s</a>',
2047
						'params' => array(
2048
							'id_member' => false,
2049
							'real_name' => false,
2050
						),
2051
					),
2052
				),
2053
				'sort' => array(
2054
					'default' => 'COALESCE(mem.real_name, 1=1), mem.real_name',
2055
					'reverse' => 'COALESCE(mem.real_name, 1=1) DESC, mem.real_name DESC',
2056
				),
2057
			),
2058
			'date' => array(
2059
				'header' => array(
2060
					'value' => $txt['ban_log_date'],
2061
				),
2062
				'data' => array(
2063
					'function' => function($rowData)
2064
					{
2065
						return timeformat($rowData['log_time']);
2066
					},
2067
				),
2068
				'sort' => array(
2069
					'default' => 'lb.log_time DESC',
2070
					'reverse' => 'lb.log_time',
2071
				),
2072
			),
2073
			'check' => array(
2074
				'header' => array(
2075
					'value' => '<input type="checkbox" onclick="invertAll(this, this.form);">',
2076
					'class' => 'centercol',
2077
				),
2078
				'data' => array(
2079
					'sprintf' => array(
2080
						'format' => '<input type="checkbox" name="remove[]" value="%1$d">',
2081
						'params' => array(
2082
							'id_ban_log' => false,
2083
						),
2084
					),
2085
					'class' => 'centercol',
2086
				),
2087
			),
2088
		),
2089
		'form' => array(
2090
			'href' => $context['admin_area'] == 'ban' ? $scripturl . '?action=admin;area=ban;sa=log' : $scripturl . '?action=admin;area=logs;sa=banlog',
2091
			'include_start' => true,
2092
			'include_sort' => true,
2093
			'token' => 'admin-bl',
2094
		),
2095
		'additional_rows' => array(
2096
			array(
2097
				'position' => 'top_of_list',
2098
				'value' => '
2099
					<input type="submit" name="removeSelected" value="' . $txt['ban_log_remove_selected'] . '" data-confirm="' . $txt['ban_log_remove_selected_confirm'] . '" class="button you_sure">
2100
					<input type="submit" name="removeAll" value="' . $txt['ban_log_remove_all'] . '" data-confirm="' . $txt['ban_log_remove_all_confirm'] . '" class="button you_sure">',
2101
			),
2102
			array(
2103
				'position' => 'bottom_of_list',
2104
				'value' => '
2105
					<input type="submit" name="removeSelected" value="' . $txt['ban_log_remove_selected'] . '" data-confirm="' . $txt['ban_log_remove_selected_confirm'] . '" class="button you_sure">
2106
					<input type="submit" name="removeAll" value="' . $txt['ban_log_remove_all'] . '" data-confirm="' . $txt['ban_log_remove_all_confirm'] . '" class="button you_sure">',
2107
			),
2108
		),
2109
	);
2110
2111
	createToken('admin-bl');
2112
2113
	require_once($sourcedir . '/Subs-List.php');
2114
	createList($listOptions);
2115
2116
	$context['page_title'] = $txt['ban_log'];
2117
	$context['sub_template'] = 'show_list';
2118
	$context['default_list'] = 'ban_log';
2119
}
2120
2121
/**
2122
 * Load a list of ban log entries from the database.
2123
 * (no permissions check). Callback for $listOptions['get_items'] in BanLog()
2124
 *
2125
 * @param int $start The item to start with (for pagination purposes)
2126
 * @param int $items_per_page How many items to show on each page
2127
 * @param string $sort A string telling ORDER BY how to sort the results
2128
 * @return array An array of info about the ban log entries for the list.
2129
 */
2130
function list_getBanLogEntries($start, $items_per_page, $sort)
2131
{
2132
	global $smcFunc;
2133
2134
	$dash = '-';
2135
2136
	$request = $smcFunc['db_query']('', '
2137
		SELECT lb.id_ban_log, lb.id_member, lb.ip AS ip, COALESCE(lb.email, {string:dash}) AS email, lb.log_time, COALESCE(mem.real_name, {string:blank_string}) AS real_name
2138
		FROM {db_prefix}log_banned AS lb
2139
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lb.id_member)
2140
		ORDER BY {raw:sort}
2141
		LIMIT {int:start}, {int:items}',
2142
		array(
2143
			'blank_string' => '',
2144
			'dash' => $dash,
2145
			'sort' => $sort,
2146
			'start' => $start,
2147
			'items' => $items_per_page,
2148
		)
2149
	);
2150
	$log_entries = array();
2151
	while ($row = $smcFunc['db_fetch_assoc']($request))
2152
	{
2153
		$row['ip'] = $row['ip'] === null ? $dash : inet_dtop($row['ip']);
2154
		$log_entries[] = $row;
2155
	}
2156
	$smcFunc['db_free_result']($request);
2157
2158
	return $log_entries;
2159
}
2160
2161
/**
2162
 * This returns the total count of ban log entries. Callback for $listOptions['get_count'] in BanLog().
2163
 *
2164
 * @return int The total number of ban log entries.
2165
 */
2166
function list_getNumBanLogEntries()
2167
{
2168
	global $smcFunc;
2169
2170
	$request = $smcFunc['db_query']('', '
2171
		SELECT COUNT(*)
2172
		FROM {db_prefix}log_banned AS lb',
2173
		array(
2174
		)
2175
	);
2176
	list ($num_entries) = $smcFunc['db_fetch_row']($request);
2177
	$smcFunc['db_free_result']($request);
2178
2179
	return $num_entries;
2180
}
2181
2182
/**
2183
 * Convert a range of given IP number into a single string.
2184
 * It's practically the reverse function of ip2range().
2185
 *
2186
 * @example
2187
 * range2ip(array(10, 10, 10, 0), array(10, 10, 20, 255)) returns '10.10.10-20.*
2188
 *
2189
 * @param array $low The low end of the range in IPv4 format
2190
 * @param array $high The high end of the range in IPv4 format
2191
 * @return string A string indicating the range
2192
 */
2193
function range2ip($low, $high)
2194
{
2195
	$low = inet_dtop($low);
0 ignored issues
show
Bug introduced by
$low of type array is incompatible with the type string expected by parameter $bin of inet_dtop(). ( Ignorable by Annotation )

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

2195
	$low = inet_dtop(/** @scrutinizer ignore-type */ $low);
Loading history...
2196
	$high = inet_dtop($high);
2197
2198
	if ($low == '255.255.255.255') return 'unknown';
2199
	if ($low == $high)
2200
		return $low;
2201
	else
2202
		return $low . '-' . $high;
2203
}
2204
2205
/**
2206
 * Checks whether a given IP range already exists in the trigger list.
2207
 * If yes, it returns an error message. Otherwise, it returns an array
2208
 *  optimized for the database.
2209
 *
2210
 * @param array $ip_array An array of IP trigger data
2211
 * @param string $fullip The full IP
2212
 * @return boolean|array False if the trigger array is invalid or the passed array if the value doesn't exist in the database
2213
 */
2214
function checkExistingTriggerIP($ip_array, $fullip = '')
2215
{
2216
	global $smcFunc, $scripturl;
2217
2218
	$values = array(
2219
		'ip_low' => $ip_array['low'],
2220
		'ip_high' => $ip_array['high']
2221
	);
2222
2223
	$request = $smcFunc['db_query']('', '
2224
		SELECT bg.id_ban_group, bg.name
2225
		FROM {db_prefix}ban_groups AS bg
2226
		INNER JOIN {db_prefix}ban_items AS bi ON
2227
			(bi.id_ban_group = bg.id_ban_group)
2228
			AND ip_low = {inet:ip_low} AND ip_high = {inet:ip_high}
2229
		LIMIT 1',
2230
		$values
2231
	);
2232
	if ($smcFunc['db_num_rows']($request) != 0)
2233
	{
2234
		list ($error_id_ban, $error_ban_name) = $smcFunc['db_fetch_row']($request);
2235
		fatal_lang_error('ban_trigger_already_exists', false, array(
2236
			$fullip,
2237
			'<a href="' . $scripturl . '?action=admin;area=ban;sa=edit;bg=' . $error_id_ban . '">' . $error_ban_name . '</a>',
2238
		));
2239
	}
2240
	$smcFunc['db_free_result']($request);
2241
2242
	return $values;
2243
}
2244
2245
/**
2246
 * As it says... this tries to review the list of banned members, to match new bans.
2247
 * Note: is_activated >= 10: a member is banned.
2248
 */
2249
function updateBanMembers()
2250
{
2251
	global $smcFunc;
2252
2253
	$updates = array();
2254
	$allMembers = array();
2255
	$newMembers = array();
2256
2257
	// Start by getting all active bans - it's quicker doing this in parts...
2258
	$request = $smcFunc['db_query']('', '
2259
		SELECT bi.id_member, bi.email_address
2260
		FROM {db_prefix}ban_items AS bi
2261
			INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group)
2262
		WHERE (bi.id_member > {int:no_member} OR bi.email_address != {string:blank_string})
2263
			AND bg.cannot_access = {int:cannot_access_on}
2264
			AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time})',
2265
		array(
2266
			'no_member' => 0,
2267
			'cannot_access_on' => 1,
2268
			'current_time' => time(),
2269
			'blank_string' => '',
2270
		)
2271
	);
2272
	$memberIDs = array();
2273
	$memberEmails = array();
2274
	$memberEmailWild = array();
2275
	while ($row = $smcFunc['db_fetch_assoc']($request))
2276
	{
2277
		if ($row['id_member'])
2278
			$memberIDs[$row['id_member']] = $row['id_member'];
2279
		if ($row['email_address'])
2280
		{
2281
			// Does it have a wildcard - if so we can't do a IN on it.
2282
			if (strpos($row['email_address'], '%') !== false)
2283
				$memberEmailWild[$row['email_address']] = $row['email_address'];
2284
			else
2285
				$memberEmails[$row['email_address']] = $row['email_address'];
2286
		}
2287
	}
2288
	$smcFunc['db_free_result']($request);
2289
2290
	// Build up the query.
2291
	$queryPart = array();
2292
	$queryValues = array();
2293
	if (!empty($memberIDs))
2294
	{
2295
		$queryPart[] = 'mem.id_member IN ({array_string:member_ids})';
2296
		$queryValues['member_ids'] = $memberIDs;
2297
	}
2298
	if (!empty($memberEmails))
2299
	{
2300
		$queryPart[] = 'mem.email_address IN ({array_string:member_emails})';
2301
		$queryValues['member_emails'] = $memberEmails;
2302
	}
2303
	$count = 0;
2304
	foreach ($memberEmailWild as $email)
2305
	{
2306
		$queryPart[] = 'mem.email_address LIKE {string:wild_' . $count . '}';
2307
		$queryValues['wild_' . $count++] = $email;
2308
	}
2309
2310
	// Find all banned members.
2311
	if (!empty($queryPart))
2312
	{
2313
		$request = $smcFunc['db_query']('', '
2314
			SELECT mem.id_member, mem.is_activated
2315
			FROM {db_prefix}members AS mem
2316
			WHERE ' . implode(' OR ', $queryPart),
2317
			$queryValues
2318
		);
2319
		while ($row = $smcFunc['db_fetch_assoc']($request))
2320
		{
2321
			if (!in_array($row['id_member'], $allMembers))
2322
			{
2323
				$allMembers[] = $row['id_member'];
2324
				// Do they need an update?
2325
				if ($row['is_activated'] < 10)
2326
				{
2327
					$updates[($row['is_activated'] + 10)][] = $row['id_member'];
2328
					$newMembers[] = $row['id_member'];
2329
				}
2330
			}
2331
		}
2332
		$smcFunc['db_free_result']($request);
2333
	}
2334
2335
	// We welcome our new members in the realm of the banned.
2336
	if (!empty($newMembers))
2337
		$smcFunc['db_query']('', '
2338
			DELETE FROM {db_prefix}log_online
2339
			WHERE id_member IN ({array_int:new_banned_members})',
2340
			array(
2341
				'new_banned_members' => $newMembers,
2342
			)
2343
		);
2344
2345
	// Find members that are wrongfully marked as banned.
2346
	$request = $smcFunc['db_query']('', '
2347
		SELECT mem.id_member, mem.is_activated - 10 AS new_value
2348
		FROM {db_prefix}members AS mem
2349
			LEFT JOIN {db_prefix}ban_items AS bi ON (bi.id_member = mem.id_member OR mem.email_address LIKE bi.email_address)
2350
			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}))
2351
		WHERE (bi.id_ban IS NULL OR bg.id_ban_group IS NULL)
2352
			AND mem.is_activated >= {int:ban_flag}',
2353
		array(
2354
			'cannot_access_activated' => 1,
2355
			'current_time' => time(),
2356
			'ban_flag' => 10,
2357
		)
2358
	);
2359
	while ($row = $smcFunc['db_fetch_assoc']($request))
2360
	{
2361
		// Don't do this twice!
2362
		if (!in_array($row['id_member'], $allMembers))
2363
		{
2364
			$updates[$row['new_value']][] = $row['id_member'];
2365
			$allMembers[] = $row['id_member'];
2366
		}
2367
	}
2368
	$smcFunc['db_free_result']($request);
2369
2370
	if (!empty($updates))
2371
		foreach ($updates as $newStatus => $members)
2372
			updateMemberData($members, array('is_activated' => $newStatus));
2373
2374
	// Update the latest member and our total members as banning may change them.
2375
	updateStats('member');
2376
}
2377
2378
/**
2379
 * Gets basic member data for the ban
2380
 *
2381
 * @param int $id The ID of the member to get data for
2382
 * @return array An aray containing the ID, name, main IP and email address of the specified user
2383
 */
2384
function getMemberData($id)
2385
{
2386
	global $smcFunc;
2387
2388
	$suggestions = array();
2389
	$request = $smcFunc['db_query']('', '
2390
		SELECT id_member, real_name, member_ip, email_address
2391
		FROM {db_prefix}members
2392
		WHERE id_member = {int:current_user}
2393
		LIMIT 1',
2394
		array(
2395
			'current_user' => $id,
2396
		)
2397
	);
2398
	if ($smcFunc['db_num_rows']($request) > 0)
2399
	{
2400
		list ($suggestions['member']['id'], $suggestions['member']['name'], $suggestions['main_ip'], $suggestions['email']) = $smcFunc['db_fetch_row']($request);
2401
		$suggestions['main_ip'] = inet_dtop($suggestions['main_ip']);
2402
	}
2403
	$smcFunc['db_free_result']($request);
2404
2405
	return $suggestions;
2406
}
2407
2408
?>